1
0
Fork 0
AAGaming 1 year ago
commit 9862f0c0f8
Signed by: aa
GPG Key ID: 3050166AC290BDF5

@ -0,0 +1,26 @@
The MIT License (MIT)
=====================
Copyright © `2021` `AAGaming`
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,19 @@
import { React } from '@goosemod/webpack/common';
import { findByProps } from '@goosemod/webpack';
export default class Button extends React.Component {
constructor(props) {
super(props);
this.classes = findByProps('iconWrapper', 'clickable');
}
render() {
return (
<>
<div className={`yt-button ${this.classes.clickable}`} onClick={(e) => {
e.preventDefault();
this.props.addPlayer(this.props.id, this.props.timestamp);
}}><div className={`${this.classes.icon}`}><svg class="connectedAccountOpenIcon-2cNbq5" aria-hidden="false" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M10 5V3H5.375C4.06519 3 3 4.06519 3 5.375V18.625C3 19.936 4.06519 21 5.375 21H18.625C19.936 21 21 19.936 21 18.625V14H19V19H5V5H10Z"></path><path fill="currentColor" d="M21 2.99902H14V4.99902H17.586L9.29297 13.292L10.707 14.706L19 6.41302V9.99902H21V2.99902Z"></path></svg></div></div>
</>
);
}
};

@ -0,0 +1,13 @@
import { React } from '@goosemod/webpack/common';
import Player from './Player.jsx';
const {useState, useEffect} = React;
export default function YoutubePopoutController ({ setAddPlayer }) {
const [player, setPlayer] = useState(null);
useEffect(() => setAddPlayer((id, timestamp) => {
setPlayer(<Player id={id} timestamp={timestamp} unmount={() => {
setPlayer(null);
}} />)
}), []);
return player;
}

@ -0,0 +1,62 @@
import { React } from '@goosemod/webpack/common';
import { findByDisplayName, find } from '@goosemod/webpack';
const Icon = findByDisplayName("Close");
const Draggable = find(x => x?.default?.displayName === 'Draggable' && x?.default?.prototype.animateToPosition)?.default;
module.exports = React.memo(class Player extends React.Component {
constructor(props) {
super(props);
this.state = {
width: window.innerWidth,
height: window.innerHeight,
closing: false
};
}
componentDidMount() {
this.updateWindowDimensions();
window.addEventListener('resize', this.updateWindowDimensions);
}
componentWillUnmount() {
window.removeEventListener('resize', this.updateWindowDimensions);
this.props.unmount?.();
}
updateWindowDimensions = () => {
this.setState({
...this.state, width: window.innerWidth,
height: window.innerHeight
});
}
close() {
this.setState({ ...this.state, closing: true });
setTimeout(() => {
this.componentWillUnmount()
}, 300);
}
render() {
return (
<>
<Draggable
dragAnywhere={true}
className='pictureInPictureWindow-1B5qSe playerDrag'
maxX={this.state.width}
maxY={this.state.height}
initialX={50}
initialY={50}
initialPosition={{top:0, left: 0}}
>
<div className={`playerContent ${this.state.closing ? 'playerClose' : ''}`}>
<div className='playerTitle'>
<div onClick={() => this.close()}>
<Icon className="playerCloseButton" style={{ float: 'right', color: '#ffffff' }} />
</div>
</div>
<iframe className="playerVideo" width="400" height="225" src={`https://www.youtube.com/embed/${this.props.id}?autoplay=1` + (this.props.timestamp ? `&start=${this.props.timestamp}` : '')} frameBorder="0" allowFullScreen="" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-presentation" />
</div>
</Draggable>
</>)
}
});

@ -0,0 +1,12 @@
{
"name": "Youtube Popouts",
"description": "Adds a button to make youtube video embeds load in a floating picture in picture",
"version": "1.0.0",
"authors": [
"373833473091436546"
],
"tags": [
"ui"
],
"main": "index.js"
}

@ -0,0 +1,65 @@
import Plugin from '@goosemod/plugin';
import { find } from '@goosemod/webpack';
import { React, ReactDOM } from '@goosemod/webpack/common';
import { inject, uninject } from '@goosemod/patcher';
import Button from './components/Button.jsx';
import style from './style.css.js';
import YoutubePopoutController from './components/Controller.jsx';
class YoutubePopouts extends Plugin {
isYouTubeLink(url) {
return (/^(?:https?:\/\/)?(?:m\.|www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(\?\S*)?$/gi).test(
url
);
}
getYouTubeVideoID(url) {
const regExp = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=)([^#\&\?]*).*/;
const match = url.match(regExp);
return match && match[1].length == 11 ? match[1] : false;
}
getYouTubeTimestamp(url) {
const regExp = /.*(?:t=)([^#\&\?]*).*/;
const match = url.match(regExp);
return match && match[1];
}
onImport() {
if (document.getElementById("youtubepopouts-css")) document.getElementById("youtubepopouts-css").remove();
this.css = Object.assign(document.createElement("style"), {
textContent: style,
id: "youtubepopouts-css"
});
document.head.appendChild(this.css);
this.element = document.createElement('div');
this.element.classList.add('playerContainer');
document.getElementById('app-mount').appendChild(this.element);
let addPlayer = () => {};
ReactDOM.render(React.createElement(YoutubePopoutController, {setAddPlayer: (add) => addPlayer = add}), this.element);
const Anchor = find(m => m?.default?.displayName === 'Anchor');
inject('youtube-link', Anchor, 'default', (args, res) => {
if (!args[0].href || args[0]?.className?.includes('iconWrapper-21idzA')) {
return res;
}
if (this.isYouTubeLink(args[0].href)) {
// console.log(res.props.children);
res.props.children = [res.props.children];
res.props.children.push(React.createElement(Button, { id: this.getYouTubeVideoID(args[0].href), timestamp: this.getYouTubeTimestamp(args[0].href), addPlayer }));
}
// console.log(args, res);
return res;
}, false);
Anchor.default.displayName = 'Anchor';
}
onRemove() {
this.css.remove();
uninject("youtube-link")
document.getElementById('app-mount').removeChild(document.querySelector('.playerContainer'));
}
};
export default new YoutubePopouts();

@ -0,0 +1,63 @@
const style = `.playerContainer {
z-index: 49873289154659285;
width: auto;
height: auto;
position: absolute;
}
.yt-button {
float: left;
width: 24px;
height: 24px;
white-space: nowrap;
}
.playerTitle {
width: 100%;
height: 25px;
cursor: move;
background-color: var(--background-secondary);
}
.playerVideo {
user-select: none;
}
.playerClose {
cursor: pointer;
}
.playerDrag {
border-radius: 5px;
}
/* animations */
.playerContent {
animation: open 250ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.playerClose {
animation: close 250ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes close {
from {
transform: scale(1);
}
to {
transform: scale(0);
}
}
@keyframes open {
0% {
transform: scale(0);
}
99% {
transform: scale(1);
}
100% {
}
}`
export default style;
Loading…
Cancel
Save