Spaces:
Running
Running
module.exports = class WatchParty { | |
getName() { | |
return "WatchParty"; | |
} | |
getImage() { | |
return "https://raw.githubusercontent.com/MateusAquino/WatchParty/main/logo.png"; | |
} | |
getDescription() { | |
return "Start a Stremio session with friends: watch party, chat (soon) and share controls. No addon sharing required."; | |
} | |
getVersion() { | |
return "1.0.1"; | |
} | |
getAuthor() { | |
return "MateusAquino"; | |
} | |
getShareURL() { | |
return "https://github.com/MateusAquino/WatchParty"; | |
} | |
getUpdateURL() { | |
return "https://raw.githubusercontent.com/MateusAquino/WatchParty/main/WatchParty.plugin.js"; | |
} | |
onBoot() {} | |
onReady() {} | |
onLoad() { | |
const windowControls = document.getElementById("window-controls"); | |
const WatchPartyControl = document.createElement("li"); | |
WatchPartyControl.id = "wp-control"; | |
WatchPartyControl.innerHTML = this.control(); | |
const WatchPartyPopup = document.createElement("div"); | |
WatchPartyPopup.id = "wp-popup"; | |
WatchPartyPopup.innerHTML = this.popup(); | |
WatchPartyPopup.classList.add("wp-noparty"); | |
WatchPartyPopup.classList.add("wp-noparty"); | |
WatchPartyControl.onclick = () => this.openUI(); | |
windowControls.insertBefore(WatchPartyControl, windowControls.firstChild); | |
windowControls.insertBefore( | |
WatchPartyPopup, | |
windowControls.firstChild.nextSibling | |
); | |
window.WatchParty = {}; | |
window.WatchParty.code = () => WatchParty.client?.party?.code; | |
window.WatchParty.create = () => this.btnCreate(this.element); | |
window.WatchParty.join = () => this.btnJoin(this.element); | |
window.WatchParty.confirm = () => this.btnConfirm(this.element); | |
window.WatchParty.toggle = (userId) => | |
WatchParty.client.send(`toggle:${userId}`); | |
window.WatchParty.broadcast = this.broadcastCommand; | |
window.WatchParty.execute = this.execCommand; | |
window.WatchParty.inject = this.inject; | |
window.WatchParty.leave = () => | |
WatchParty.client?.terminate?.() || WatchParty.client?.close?.(); | |
window.WatchParty.failedServers = []; | |
const servers = this.getServers(); | |
for (const server of Object.values(servers)) { | |
const statusEndpoint = server | |
.replace("ws://", "http://") | |
.replace("wss://", "https://"); | |
const xhr = new XMLHttpRequest(); | |
xhr.open("GET", statusEndpoint, true); | |
xhr.onload = () => { | |
if (xhr.status === 200) | |
console.log("[WatchParty] Server available: ", server); | |
}; | |
xhr.send(); | |
} | |
this.inject(); | |
} | |
onEnable() { | |
this.onLoad(); | |
} | |
onDisable() { | |
WatchParty.client?.terminate?.() || WatchParty.client?.close?.(); | |
this.element("control").remove(); | |
window.WatchParty = undefined; | |
} | |
getServers() { | |
return { | |
L: "ws://localhost:3000/", | |
R: "wss://watchparty-kyiy.onrender.com", | |
G: "wss://dramatic-hazel-epoch.glitch.me", | |
// A: "wss://watch-party.adaptable.app", | |
}; | |
} | |
pickRandom(partyServer) { | |
const servers = this.getServers(); | |
const availableServers = Object.keys(servers).filter( | |
(server) => | |
server.startsWith(partyServer) && | |
!window.WatchParty.failedServers.includes(servers[server]) | |
); | |
if (availableServers.length > 0) { | |
const randomServer = | |
availableServers[Math.floor(Math.random() * availableServers.length)]; | |
return servers[randomServer]; | |
} else { | |
return false; | |
} | |
} | |
connect(protocol, partyServer = "") { | |
if (WatchParty.client) | |
WatchParty.client.terminate?.() || WatchParty.client.close?.(); | |
const server = this.pickRandom(partyServer); | |
if (!server) { | |
document.getElementById("wp-popup").classList.remove("wp-loading"); | |
document.getElementById("wp-popup").classList.add("wp-noparty"); | |
return BetterStremio.Toasts.error( | |
"Failed to create/join party.", | |
"Couldn't find any servers available! Try again in a minute." | |
); | |
} | |
WatchParty.client = new WebSocket(server, protocol); | |
WatchParty.client.heartbeat = () => { | |
const client = this; | |
clearTimeout(this.pingTimeout); | |
this.pingTimeout = setTimeout(() => { | |
client.terminate?.() || client.close?.(); | |
console.error("[WatchParty] Connection timeout!"); | |
BetterStremio.Toasts.error( | |
"Disconnected from party!", | |
"You have been disconnected due to timeout." | |
); | |
}, 30000 + 4000); | |
}; | |
const retry = () => this.connect(protocol, partyServer); | |
WatchParty.client.onerror = () => { | |
window.WatchParty.failedServers.push(server); | |
retry(); | |
setTimeout(() => { | |
const index = window.WatchParty.failedServers.indexOf(server); | |
if (index > -1) window.WatchParty.failedServers.splice(index, 1); | |
}, 1 * 60 * 1000); | |
}; | |
WatchParty.client.onmessage = this.handleMessage; | |
WatchParty.client.onopen = WatchParty.client.heartbeat; | |
window.WatchParty.warnOnClose = false; | |
const warnTimeout = setTimeout(() => { | |
window.WatchParty.warnOnClose = true; | |
}, 5000); | |
WatchParty.client.onclose = () => { | |
clearTimeout(warnTimeout); | |
if (window.WatchParty.warnOnClose) | |
BetterStremio.Toasts.warning("Disconnected!", "You've left the party."); | |
document.getElementById("wp-popup").classList.remove("wp-loading"); | |
document.getElementById("wp-popup").classList.add("wp-noparty"); | |
console.log("[WatchParty] Connection closed."); | |
clearTimeout(this.pingTimeout); | |
}; | |
} | |
openUI() { | |
this.element("popup").classList.toggle("show"); | |
} | |
element(id) { | |
return document.getElementById(`wp-${id}`); | |
} | |
btnCreate(element) { | |
delete this.isJoining; | |
element("create-btn").classList.add("selected"); | |
element("join-btn").classList.remove("selected"); | |
element("create").classList.remove("hidden"); | |
element("join").classList.add("hidden"); | |
} | |
btnJoin(element) { | |
this.isJoining = true; | |
element("create-btn").classList.remove("selected"); | |
element("join-btn").classList.add("selected"); | |
element("create").classList.add("hidden"); | |
element("join").classList.remove("hidden"); | |
} | |
btnConfirm(element) { | |
element("popup").classList.add("wp-loading"); | |
if (!this.isJoining) { | |
const username = encodeURIComponent(element("create-user").value); | |
const partyName = encodeURIComponent(element("create-name").value); | |
const partyPass = encodeURIComponent(element("create-pass").value); | |
const joinAsHost = element("create-joinashost").checked; | |
const protocol = `c#1#${username}#${partyPass}#${partyName}#${ | |
joinAsHost ? "1" : "0" | |
}`; | |
this.connect(protocol); | |
} else { | |
const username = encodeURIComponent(element("join-user").value); | |
const partyCode = encodeURIComponent( | |
element("join-code").value | |
).toUpperCase(); | |
const partyPass = encodeURIComponent(element("join-pass").value); | |
const protocol = `j#1#${username}#${partyCode}#${partyPass}`; | |
this.connect(protocol, partyCode.substring(0, 1)); | |
} | |
} | |
handleMessage(message) { | |
document.getElementById("wp-popup").classList.remove("wp-loading"); | |
const client = message.currentTarget; | |
if (message.data === "ping") { | |
client.send("pong"); | |
client.heartbeat(); | |
} else if (message.data === "badroom") { | |
document.getElementById("wp-join-pass").value = ""; | |
BetterStremio.Toasts.error( | |
"Failed to join party!", | |
"The combination code/password does not exists." | |
); | |
} else if (message.data === "upgrade") | |
BetterStremio.Toasts.error( | |
"WatchParty version not supported!", | |
"Please upgrade your plugin version to use " | |
); | |
else if (message.data.startsWith("party:")) { | |
const newParty = JSON.parse(message.data.substring(6)); | |
if (client.party && newParty) { | |
const oldMembers = client.party.members; | |
const newMembers = newParty.members; | |
if (oldMembers.length < newMembers.length) { | |
const joinedMember = newMembers.find( | |
(member) => | |
!oldMembers.map((el) => el.userId).includes(member.userId) | |
); | |
BetterStremio.Toasts.info( | |
"Party Update", | |
`${joinedMember.userName} has joined the party` | |
); | |
} else if (oldMembers.length > newMembers.length) { | |
const leftMember = oldMembers.find( | |
(member) => | |
!newMembers.map((el) => el.userId).includes(member.userId) | |
); | |
BetterStremio.Toasts.info( | |
"Party Update", | |
`${leftMember.userName} has left the party` | |
); | |
} else { | |
for (let i = 0; i < newMembers.length; i++) { | |
if (oldMembers[i].isHost !== newMembers[i].isHost) { | |
const member = newMembers[i]; | |
const action = member.isHost ? "promoted to" : "demoted from"; | |
BetterStremio.Toasts.info( | |
"Party Update", | |
`${member.userName} was ${action} host` | |
); | |
} | |
} | |
} | |
} | |
client.party = newParty; | |
document.getElementById("wp-popup").classList.remove("wp-noparty"); | |
document.getElementById("wp-partyname").innerText = newParty.name; | |
document.getElementById("wp-partycode").innerText = newParty.code; | |
document.getElementById("wp-partymembers").innerHTML = newParty.members | |
.map( | |
(member) => | |
`<div class="row user-row"> | |
${member.userName} ${member.isHost ? "(host)" : ""} | |
<button onclick="WatchParty.toggle('${ | |
member.userId | |
}')">Toggle Host</button> | |
</div>` | |
) | |
.join(""); | |
} else if (message.data.startsWith("cmd:")) { | |
const cmdLine = message.data.substring(4); | |
const separatorIndex = cmdLine.indexOf(":"); | |
if (separatorIndex === -1) return; | |
const latency = cmdLine.substring(0, separatorIndex); | |
const remainingData = cmdLine.substring(separatorIndex + 1); | |
const secondSeparatorIndex = remainingData.indexOf(":"); | |
if (secondSeparatorIndex === -1) return; | |
const cmd = remainingData.substring(0, secondSeparatorIndex); | |
const jsonData = remainingData.substring(secondSeparatorIndex + 1); | |
if (!jsonData) return; | |
const data = JSON.parse(jsonData); | |
if (data) window.WatchParty.execute(latency, cmd, data); | |
} | |
} | |
inject() { | |
if (!window.WatchParty.injectedStateChange) { | |
BetterStremio.StremioRoot.$on("$stateChangeSuccess", (_scs) => { | |
window.WatchParty.inject(); | |
}); | |
window.WatchParty.injectedStateChange = true; | |
} | |
if (!window.WatchParty.injectedGo) { | |
const oldGoFn = BetterStremio.Modules.$state.go; | |
BetterStremio.Modules.$state.go = function () { | |
if (arguments[0] === "player/NO_SPREAD") arguments[0] = "player"; | |
else if (arguments[0] === "player") | |
window.WatchParty.broadcast("go", Array.from(arguments)); | |
return oldGoFn.apply(oldGoFn, arguments); | |
}; | |
window.WatchParty.injectedGo = true; | |
} | |
if (!window.WatchParty.injectedHtml5 && BetterStremio.Modules.deviceHtml5) { | |
BetterStremio.Modules.deviceHtml5.addListener("statechanged", (state) => { | |
if (state.NO_SPREAD) return; | |
if ( | |
BetterStremio.Modules.deviceHtml5.time === | |
window.WatchParty.NO_SPREAD_TIME | |
) | |
return; | |
window.WatchParty.broadcast("state", { | |
state, | |
time: BetterStremio.Modules.deviceHtml5.time, | |
paused: BetterStremio.Modules.deviceHtml5.paused, | |
playbackSpeed: BetterStremio.Modules.deviceHtml5.playbackSpeed, | |
}); | |
}); | |
BetterStremio.Modules.deviceHtml5.addListener("timeupdate", () => { | |
delete window.WatchParty.NO_SPREAD; | |
}); | |
BetterStremio.Modules.deviceHtml5.addListener("error", (error) => { | |
window.WatchParty.broadcast("error", error); | |
}); | |
window.WatchParty.injectedHtml5 = true; | |
} | |
if (!window.WatchParty.injectedMPV && BetterStremio.Modules.deviceMPV) { | |
BetterStremio.Modules.deviceMPV.addListener("statechanged", (state) => { | |
if (state.NO_SPREAD) return; | |
if ( | |
BetterStremio.Modules.deviceMPV.time === | |
window.WatchParty.NO_SPREAD_TIME | |
) | |
return; | |
window.WatchParty.broadcast("state", { | |
state, | |
time: BetterStremio.Modules.deviceMPV.time, | |
paused: BetterStremio.Modules.deviceMPV.paused, | |
playbackSpeed: BetterStremio.Modules.deviceMPV.playbackSpeed, | |
}); | |
}); | |
BetterStremio.Modules.deviceMPV.addListener("timeupdate", () => { | |
delete window.WatchParty.NO_SPREAD; | |
}); | |
BetterStremio.Modules.deviceMPV.addListener("error", (error) => { | |
window.WatchParty.broadcast("error", error); | |
}); | |
window.WatchParty.injectedMPV = true; | |
} | |
} | |
broadcastCommand(cmd, data) { | |
if (WatchParty.client && !window.WatchParty.NO_SPREAD) { | |
try { | |
const dataStr = JSON.stringify(data); | |
WatchParty.client.send?.(`cmd:${cmd}:${dataStr}`); | |
} catch (e) {} | |
} | |
} | |
execCommand(latency, cmd, data) { | |
const strData = JSON.stringify(data); | |
if (cmd === "go") { | |
const playerData = data; | |
playerData.shift(); | |
BetterStremio.Modules.$state.go("player/NO_SPREAD", ...playerData); | |
} else if ( | |
cmd === "state" && | |
(BetterStremio.Modules.deviceMPV || BetterStremio.Modules.deviceHtml5) && | |
!window.WatchParty.NO_SPREAD | |
) { | |
window.WatchParty.NO_SPREAD = true; | |
if (data.playbackSpeed) | |
BetterStremio.Modules.deviceHtml5.playbackSpeed = data.playbackSpeed; | |
if (data.playbackSpeed) | |
BetterStremio.Modules.deviceMPV.playbackSpeed = data.playbackSpeed; | |
if (BetterStremio.Modules.deviceHtml5.paused !== data.paused) | |
BetterStremio.Modules.deviceHtml5.paused = data.paused; | |
if (BetterStremio.Modules.deviceMPV.paused !== data.paused) | |
BetterStremio.Modules.deviceMPV.paused = data.paused; | |
if (window.WatchParty.LAST_STATE !== strData && !data.paused) { | |
window.WatchParty.NO_SPREAD_TIME = data.time + parseInt(latency); | |
BetterStremio.Modules.deviceHtml5.time = data.time + parseInt(latency); | |
BetterStremio.Modules.deviceMPV.time = data.time + parseInt(latency); | |
} | |
BetterStremio.Modules.deviceHtml5?.emit?.("statechanged", { | |
...data.state, | |
NO_SPREAD: true, | |
}); | |
BetterStremio.Modules.deviceMPV?.emit?.("statechanged", { | |
...data.state, | |
NO_SPREAD: true, | |
}); | |
window.WatchParty.LAST_STATE = strData; | |
delete window.WatchParty.NO_SPREAD; | |
} | |
} | |
control() { | |
return `<svg class="icon" viewBox="0,0,256,256"><path d="M 106.56 10.56 C 94.613 10.56 84.171 17.067 78.731 26.88 L 78.4 26.453 C 77.12 26.24 75.744 26.251 74.357 26.251 C 58.997 26.251 46.176 37.013 43.083 51.52 C 35.893 55.467 30.443 62.123 27.957 70.101 C 26.4 70.741 27.093 70.336 24.683 71.307 C 23.029 72.395 22.912 72.491 22.155 73.824 C 21.227 75.072 20.363 77.76 20.789 80.853 L 42.123 235.52 C 42.869 240.853 47.456 244.789 52.789 244.789 L 141.941 244.789 C 142.528 244.896 143.072 245.12 143.691 245.12 L 207.691 245.12 C 213.237 245.12 217.824 240.853 218.357 235.413 L 228.171 122.667 L 234.667 122.667 L 234.667 101.333 L 219.083 101.333 C 218.901 101.323 218.741 101.227 218.56 101.227 L 211.872 101.227 L 215.456 90.56 L 230.837 90.56 L 230.837 69.227 L 207.893 69.227 C 203.307 69.227 199.083 71.467 197.696 75.84 L 197.547 76.267 L 189.248 101.227 L 179.712 101.227 L 181.12 80.416 C 181.333 77.536 180.277 74.677 178.357 72.544 C 177.205 71.328 175.755 70.528 174.208 69.984 C 171.755 62.336 166.571 55.947 159.787 52.064 C 159.253 35.744 146.453 22.507 130.453 21.227 C 124.587 14.72 116.053 10.56 106.56 10.56 Z M 105.813 31.349 C 109.867 31.349 113.291 33.483 115.211 36.789 C 117.344 40.523 121.504 42.656 125.877 42.123 L 127.349 42.123 C 133.216 42.123 138.016 46.816 138.016 52.789 C 138.016 53.856 137.92 54.827 137.707 55.787 C 136.853 58.56 137.28 61.653 138.88 64.213 C 140.16 66.453 142.293 68.064 144.853 68.917 L 56.651 68.917 C 60.917 67.424 63.797 63.253 63.691 58.667 L 63.691 58.357 C 63.691 52.384 68.384 47.691 74.357 47.691 C 76.384 47.691 78.187 48.203 79.787 49.163 C 82.88 50.976 86.709 51.2 90.016 49.6 C 93.216 48 95.467 44.789 95.893 41.269 C 96.533 35.936 99.989 31.349 105.536 31.349 L 105.813 31.349 Z M 43.872 90.357 L 54.56 90.357 L 72.917 223.456 L 62.187 223.456 L 43.872 90.357 Z M 76.107 90.357 L 158.912 90.357 L 158.229 101.227 L 133.227 101.227 C 133.067 101.227 132.917 101.323 132.747 101.333 L 117.333 101.333 L 117.333 122.667 L 123.232 122.667 L 132 223.456 L 94.453 223.456 L 76.107 90.357 Z M 144.853 122.667 L 206.731 122.667 L 197.984 223.893 L 159.456 223.893 L 159.456 223.456 L 153.557 223.456 L 144.853 122.667 Z" style="paint-order: fill; fill: currentColor; stroke: rgb(0, 0, 0); mix-blend-mode: exclusion; stroke-width: 6px;" fill-rule="nonzero" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none"></path></svg>`; | |
} | |
popup() { | |
return ` | |
<h3>WatchParty <span>v${this.getVersion()}</span></h3> | |
<button id="wp-create-btn" class="wp-noparty-show selected" onclick="WatchParty.create()">Create</button> | |
<button id="wp-join-btn" class="wp-noparty-show" onclick="WatchParty.join()">Join</button> | |
<div id="wp-create" class="tab"> | |
<div class="row">Username: <input id="wp-create-user" oninput="BetterStremio.Data.store('WatchParty', 'user', this.value)" value="${ | |
BetterStremio.Data.read("WatchParty", "user") || "" | |
}" autocomplete="false" type="text"/></div> | |
<div class="row">Party Name: <input id="wp-create-name" oninput="BetterStremio.Data.store('WatchParty', 'party', this.value)" value="${ | |
BetterStremio.Data.read("WatchParty", "party") || "Watch Party" | |
}" autocomplete="false" type="text"/></div> | |
<div class="row">Party Pass: <input id="wp-create-pass" oninput="BetterStremio.Data.store('WatchParty', 'pass', this.value)" value="${ | |
BetterStremio.Data.read("WatchParty", "pass") || "" | |
}" autocomplete="false" type="text"/></div> | |
<div class="row">New members as host: <input id="wp-create-joinashost" onchange="BetterStremio.Data.store('WatchParty', 'joinAsHost', this.checked)" ${ | |
BetterStremio.Data.read("WatchParty", "joinAsHost") === "true" | |
? "checked" | |
: "" | |
} autocomplete="false" type="checkbox"/></div> | |
</div> | |
<div id="wp-join" class="tab hidden"> | |
<div class="row">Username: <input id="wp-join-user" oninput="BetterStremio.Data.store('WatchParty', 'user', this.value)" value="${ | |
BetterStremio.Data.read("WatchParty", "user") || "" | |
}" autocomplete="false" type="text"/></div> | |
<div class="row">Party Code: <input id="wp-join-code" autocomplete="false" type="text"/></div> | |
<div class="row">Party Pass: <input id="wp-join-pass" oninput="BetterStremio.Data.store('WatchParty', 'pass', this.value)" value="${ | |
BetterStremio.Data.read("WatchParty", "pass") || "" | |
}" autocomplete="false" type="text"/></div> | |
</div> | |
<div id="wp-loading" class="tab"> | |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" width="208" height="208" style="shape-rendering: auto;display: block;"><g data-idx="1"><circle stroke-linecap="round" fill="none" stroke="#9370db" stroke-width="3" r="18" cy="50" cx="50" data-idx="2" stroke-dasharray="28.274333882308138 28.274333882308138"></circle><g data-idx="4"></g></g></svg> | |
</div> | |
<div id="wp-inparty" class="tab" style="margin-top: 10px;"> | |
<h3 class="row" style="justify-content: flex-start; margin-bottom: 10px;"> | |
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0,0,256,256"><g fill="currentColor" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10"><g transform="scale(10.66667,10.66667)"><path d="M12,2.09961l-11,9.90039h3v9h1h15v-9h3zM12,4.79102l6,5.40039v8.80859h-2v-6h-3v6h-7v-8.80859zM8,13v3h3v-3z"></path></g></g></svg> | |
<span id="wp-partyname" style="color: inherit; font-size: 1em;"></span> | |
<button style="margin: 0; margin-left: auto; margin-right: 8px;" onclick="WatchParty.leave()">Leave</button> | |
</h3> | |
<h3 class="row" onclick="BetterStremio.Sharing.copyToClipboard(WatchParty.code())" style="cursor: pointer; color: gray;justify-content: flex-start;margin-bottom: 10px;"> | |
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18px" height="18px" viewBox="0,0,256,256" style=" margin-left: 3px; margin-right: 3px; "><g fill="currentColor" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10"><g transform="scale(10.66667,10.66667)"><path d="M12,1c-1.64501,0 -3,1.35499 -3,3c0,0.35185 0.07394,0.68511 0.1875,1h-6.1875c-1.103,0 -2,0.897 -2,2v12c0,1.103 0.897,2 2,2h18c1.103,0 2,-0.897 2,-2v-12c0,-1.103 -0.897,-2 -2,-2h-6.1875c0.11356,-0.31489 0.1875,-0.64815 0.1875,-1c0,-1.64501 -1.35499,-3 -3,-3zM12,3c0.56413,0 1,0.43587 1,1c0,0.56413 -0.43587,1 -1,1c-0.56413,0 -1,-0.43587 -1,-1c0,-0.56413 0.43587,-1 1,-1zM3,7h9h9l0.00195,12h-18.00195zM9,9c-1.10457,0 -2,0.89543 -2,2c0,1.10457 0.89543,2 2,2c1.10457,0 2,-0.89543 2,-2c0,-1.10457 -0.89543,-2 -2,-2zM15,10v2h4v-2zM9,14c-2.185,0 -4,0.9088 -4,2.2168v0.7832h8v-0.7832c0,-1.308 -1.815,-2.2168 -4,-2.2168zM15,14v2h4v-2z"></path></g></g></svg> | |
<span id="wp-partycode" style="color: inherit;font-size: .8em;"></span> | |
</h3> | |
<div id="wp-partymembers"></div> | |
</div> | |
<button class="wp-noparty-show" onclick="WatchParty.confirm()" style=" float: right; margin-top: 15px;">Enter</button> | |
<style type="text/css"> | |
#wp-popup .tab.hidden { | |
display: none; | |
} | |
#wp-popup { | |
display: none; | |
background: #0c0c11; | |
min-width: 300px; | |
width: fit-content; | |
position: absolute; | |
top: 50px; | |
right: 95px; | |
border-radius: 8px; | |
box-shadow: rgba(0, 0, 0, 0.2) 0px 10px 15px; | |
padding: 20px; | |
} | |
#wp-popup.show { | |
display: block; | |
} | |
#wp-popup h3 span { | |
color: rgba(255, 255, 255, 0.5); | |
font-size: 0.7em; | |
} | |
#wp-popup button { | |
padding: 4px 10px; | |
margin: 8px 8px 8px 0px; | |
border-radius: 4px; | |
background-color: mediumpurple; | |
color: white; | |
cursor: pointer; | |
font-weight: bold; | |
} | |
#wp-popup button:hover, | |
#wp-popup button.selected { | |
background-color: rebeccapurple; | |
} | |
#wp-popup .row { | |
display: flex; | |
align-items: center; | |
margin-top: 8px; | |
white-space: nowrap; | |
gap: 10px; | |
justify-content: space-between; | |
} | |
#wp-popup .row.user-row { | |
padding-top: 5px; | |
margin-top: 5px; | |
border-top: 1px solid #ffffff22; | |
} | |
#wp-popup .row div { | |
display: none !important; | |
} | |
#wp-popup input[type="text"] { | |
width: 100%; | |
min-width: 30px; | |
} | |
.wp-loading div, #wp-loading { | |
display: none; | |
} | |
.wp-loading #wp-loading { | |
display: flex; | |
justify-content: center; | |
} | |
.wp-loading button { | |
display: none !important; | |
} | |
.wp-noparty button, | |
.wp-noparty-show, | |
#wp-popup:not(.wp-noparty) .tab, | |
#wp-inparty { | |
display: none; | |
} | |
#wp-popup:not(.wp-noparty) #wp-inparty { | |
display: block; | |
} | |
.wp-noparty button.wp-noparty-show { | |
display: inline; | |
} | |
#wp-popup #wp-loading svg { | |
animation-name: spin; | |
animation-duration: 650ms; | |
animation-iteration-count: infinite; | |
animation-timing-function: linear; | |
} | |
@keyframes spin {from {transform:rotate(0deg);}to {transform:rotate(360deg);}} | |
@media (min-width: 1500px) { | |
#wp-popup { | |
top: 60px; | |
right: 118px; | |
} | |
} | |
</style>`; | |
} | |
}; | |