fix(music-together): typing

This commit is contained in:
JellyBrick
2024-01-05 23:01:55 +09:00
parent 3810955e56
commit 895386f6f8
11 changed files with 232 additions and 127 deletions

View File

@ -472,6 +472,7 @@
"disconnected": "Music Together disconnected", "disconnected": "Music Together disconnected",
"host-failed": "Failed to host Music Together", "host-failed": "Failed to host Music Together",
"id-copied": "Host ID copied to clipboard", "id-copied": "Host ID copied to clipboard",
"id-copy-failed": "Failed to copy Host ID to clipboard",
"join-failed": "Failed to join Music Together", "join-failed": "Failed to join Music Together",
"joined": "Joined Music Together", "joined": "Joined Music Together",
"permission-changed": "Music Together permission changed to \"{{permission}}\"", "permission-changed": "Music Together permission changed to \"{{permission}}\"",

View File

@ -1,5 +1,7 @@
import prompt from 'custom-electron-prompt'; import prompt from 'custom-electron-prompt';
import { DataConnection } from 'peerjs';
import { t } from '@/i18n'; import { t } from '@/i18n';
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
import promptOptions from '@/providers/prompt-options'; import promptOptions from '@/providers/prompt-options';
@ -17,7 +19,6 @@ import style from './style.css?inline';
import type { YoutubePlayer } from '@/types/youtube-player'; import type { YoutubePlayer } from '@/types/youtube-player';
import type { RendererContext } from '@/types/contexts'; import type { RendererContext } from '@/types/contexts';
import type { VideoDataChanged } from '@/types/video-data-changed'; import type { VideoDataChanged } from '@/types/video-data-changed';
import { DataConnection } from 'peerjs';
type RawAccountData = { type RawAccountData = {
accountName: { accountName: {
@ -34,7 +35,44 @@ type RawAccountData = {
}; };
}; };
export default createPlugin({ export default createPlugin<
unknown,
unknown,
{
connection?: Connection;
ipc?: RendererContext<never>['ipc'];
api: HTMLElement & AppAPI | null;
queue?: Queue;
playerApi?: YoutubePlayer;
showPrompt: (title: string, label: string) => Promise<string>;
popups: {
host: ReturnType<typeof createHostPopup>;
guest: ReturnType<typeof createGuestPopup>;
setting: ReturnType<typeof createSettingPopup>;
};
elements: {
setting: HTMLElement;
icon: SVGElement;
spinner: HTMLElement;
};
stateInterval?: number;
updateNext: boolean;
ignoreChange: boolean;
rollbackInjector?: (() => void);
me?: Omit<Profile, 'id'>;
profiles: Record<string, Profile>;
permission: Permission;
videoChangeListener: (event: CustomEvent<VideoDataChanged>) => void;
videoStateChangeListener: () => void;
onHost: () => Promise<boolean>;
onJoin: () => Promise<boolean>;
onStop: () => void;
putProfile: (id: string, profile?: Profile) => void;
showSpinner: () => void;
hideSpinner: () => void;
initMyProfile: () => void;
}
>({
name: () => t('plugins.music-together.name'), name: () => t('plugins.music-together.name'),
description: () => t('plugins.music-together.description'), description: () => t('plugins.music-together.description'),
restartNeeded: false, restartNeeded: false,
@ -43,50 +81,38 @@ export default createPlugin({
enabled: false enabled: false
}, },
stylesheets: [style], stylesheets: [style],
backend: { backend({ ipc }) {
async start({ ipc }) { ipc.handle('music-together:prompt', async (title: string, label: string) => prompt({
ipc.handle('music-together:prompt', async (title: string, label: string) => prompt({ title,
title, label,
label, type: 'input',
type: 'input', ...promptOptions()
...promptOptions() }));
}));
}
}, },
renderer: { renderer: {
connection: null as Connection | null, updateNext: false,
ipc: null as RendererContext<never>['ipc'] | null, ignoreChange: false,
permission: 'playlist',
api: null as (HTMLElement & AppAPI) | null,
queue: null as Queue | null,
playerApi: null as YoutubePlayer | null,
showPrompt: (async () => null) as ((title: string, label: string) => Promise<string | null>),
elements: {} as {
setting: HTMLElement;
icon: SVGElement;
spinner: HTMLElement;
},
popups: {} as { popups: {} as {
host: ReturnType<typeof createHostPopup>; host: ReturnType<typeof createHostPopup>;
guest: ReturnType<typeof createGuestPopup>; guest: ReturnType<typeof createGuestPopup>;
setting: ReturnType<typeof createSettingPopup>; setting: ReturnType<typeof createSettingPopup>;
}, },
stateInterval: null as number | null, elements: {} as {
updateNext: false, setting: HTMLElement;
ignoreChange: false, icon: SVGElement;
rollbackInjector: null as (() => void) | null, spinner: HTMLElement;
},
me: null as Omit<Profile, 'id'> | null, profiles: {},
profiles: {} as Record<string, Profile>, showPrompt: () => Promise.resolve(''),
permission: 'playlist' as Permission, api: null,
/* events */ /* events */
videoChangeListener(event: CustomEvent<VideoDataChanged>) { videoChangeListener(event: CustomEvent<VideoDataChanged>) {
if (event.detail.name === 'dataloaded' || this.updateNext) { if (event.detail.name === 'dataloaded' || this.updateNext) {
if (this.connection?.mode === 'host') { if (this.connection?.mode === 'host') {
const videoList: VideoData[] = this.queue?.flatItems.map((it: any) => ({ const videoList: VideoData[] = this.queue?.flatItems.map((it) => ({
videoId: it.videoId, videoId: it!.videoId,
ownerId: this.connection!.id ownerId: this.connection!.id
} satisfies VideoData)) ?? []; } satisfies VideoData)) ?? [];
@ -123,8 +149,8 @@ export default createPlugin({
if (!wait) return false; if (!wait) return false;
if (!this.me) this.me = getDefaultProfile(this.connection.id); if (!this.me) this.me = getDefaultProfile(this.connection.id);
const rawItems = this.queue?.flatItems?.map((it: any) => ({ const rawItems = this.queue?.flatItems?.map((it) => ({
videoId: it.videoId, videoId: it!.videoId,
ownerId: this.connection!.id ownerId: this.connection!.id
} satisfies VideoData)) ?? []; } satisfies VideoData)) ?? [];
this.queue?.setOwner({ this.queue?.setOwner({
@ -170,7 +196,7 @@ export default createPlugin({
case 'REMOVE_SONG': { case 'REMOVE_SONG': {
if (conn && this.permission === 'host-only') return; if (conn && this.permission === 'host-only') return;
await this.queue?.removeVideo(event.payload.index); this.queue?.removeVideo(event.payload.index);
await this.connection?.broadcast('REMOVE_SONG', event.payload); await this.connection?.broadcast('REMOVE_SONG', event.payload);
break; break;
} }
@ -295,11 +321,11 @@ export default createPlugin({
break; break;
} }
case 'REMOVE_SONG': { case 'REMOVE_SONG': {
await this.queue?.removeVideo(event.payload.index); this.queue?.removeVideo(event.payload.index);
break; break;
} }
case 'MOVE_SONG': { case 'MOVE_SONG': {
await this.queue?.moveItem(event.payload.fromIndex, event.payload.toIndex); this.queue?.moveItem(event.payload.fromIndex, event.payload.toIndex);
break; break;
} }
case 'IDENTIFY': { case 'IDENTIFY': {
@ -461,7 +487,7 @@ export default createPlugin({
this.queue?.removeQueueOwner(); this.queue?.removeQueueOwner();
if (this.rollbackInjector) { if (this.rollbackInjector) {
this.rollbackInjector(); this.rollbackInjector();
this.rollbackInjector = null; this.rollbackInjector = undefined;
} }
this.profiles = {}; this.profiles = {};
@ -530,7 +556,7 @@ export default createPlugin({
start({ ipc }) { start({ ipc }) {
this.ipc = ipc; this.ipc = ipc;
this.showPrompt = async (title: string, label: string) => ipc.invoke('music-together:prompt', title, label); this.showPrompt = async (title: string, label: string) => ipc.invoke('music-together:prompt', title, label) as Promise<string>;
this.api = document.querySelector<HTMLElement & AppAPI>('ytmusic-app'); this.api = document.querySelector<HTMLElement & AppAPI>('ytmusic-app');
/* setup */ /* setup */
@ -571,10 +597,15 @@ export default createPlugin({
} }
if (id === 'music-together-copy-id') { if (id === 'music-together-copy-id') {
navigator.clipboard.writeText(this.connection?.id ?? ''); navigator.clipboard.writeText(this.connection?.id ?? '')
.then(() => {
this.api?.openToast(t('plugins.music-together.toast.id-copied')); this.api?.openToast(t('plugins.music-together.toast.id-copied'));
hostPopup.dismiss(); hostPopup.dismiss();
})
.catch(() => {
this.api?.openToast(t('plugins.music-together.toast.id-copy-failed'));
hostPopup.dismiss();
});
} }
if (id === 'music-together-permission') { if (id === 'music-together-permission') {
@ -614,9 +645,14 @@ export default createPlugin({
this.hideSpinner(); this.hideSpinner();
if (result) { if (result) {
navigator.clipboard.writeText(this.connection?.id ?? ''); navigator.clipboard.writeText(this.connection?.id ?? '')
this.api?.openToast(t('plugins.music-together.toast.id-copied')); .then(() => {
hostPopup.showAtAnchor(setting); this.api?.openToast(t('plugins.music-together.toast.id-copied'));
hostPopup.showAtAnchor(setting);
}).catch(() => {
this.api?.openToast(t('plugins.music-together.toast.id-copy-failed'));
hostPopup.showAtAnchor(setting);
});
} else { } else {
this.api?.openToast(t('plugins.music-together.toast.host-failed')); this.api?.openToast(t('plugins.music-together.toast.host-failed'));
} }
@ -642,7 +678,7 @@ export default createPlugin({
guest: guestPopup, guest: guestPopup,
setting: settingPopup setting: settingPopup
}; };
setting.addEventListener('click', async () => { setting.addEventListener('click', () => {
let popup = settingPopup; let popup = settingPopup;
if (this.connection?.mode === 'host') popup = hostPopup; if (this.connection?.mode === 'host') popup = hostPopup;
if (this.connection?.mode === 'guest') popup = guestPopup; if (this.connection?.mode === 'guest') popup = guestPopup;

View File

@ -1,12 +1,51 @@
import { getMusicQueueRenderer } from './song'; import { getMusicQueueRenderer } from './song';
import { mapQueueItem } from './utils'; import { mapQueueItem } from './utils';
import type { Profile, QueueAPI, VideoData } from '../types';
import { ConnectionEventUnion } from '@/plugins/music-together/connection'; import { ConnectionEventUnion } from '@/plugins/music-together/connection';
import { t } from '@/i18n'; import { t } from '@/i18n';
import type { Profile, QueueAPI, VideoData } from '../types';
import type { QueueItem } from '@/types/datahost-get-state';
const getHeaderPayload = (() => { const getHeaderPayload = (() => {
let payload: unknown = null; let payload: {
items?: QueueItem[] | undefined;
title: {
runs: {
text: string;
}[];
};
subtitle: {
runs: {
text: string;
}[];
};
buttons: {
chipCloudChipRenderer: {
style: {
styleType: string;
};
text: {
runs: {
text: string;
}[];
};
navigationEndpoint: {
saveQueueToPlaylistCommand: unknown;
};
icon: {
iconType: string;
};
accessibilityData: {
accessibilityData: {
label: string;
};
};
isSelected: boolean;
uniqueId: string;
};
}[];
} | null = null;
return () => { return () => {
if (!payload) { if (!payload) {
@ -58,7 +97,7 @@ const getHeaderPayload = (() => {
} }
return payload; return payload;
} };
})(); })();
export type QueueOptions = { export type QueueOptions = {
@ -70,11 +109,11 @@ export type QueueOptions = {
export type QueueEventListener = (event: ConnectionEventUnion) => void; export type QueueEventListener = (event: ConnectionEventUnion) => void;
export class Queue { export class Queue {
private queue: (HTMLElement & QueueAPI) | null = null; private queue: (HTMLElement & QueueAPI);
private originalDispatch: ((obj: { private originalDispatch?: (obj: {
type: string; type: string;
payload?: unknown; payload?: { items?: QueueItem[] | undefined; };
}) => void) | null = null; }) => void;
private internalDispatch = false; private internalDispatch = false;
private ignoreFlag = false; private ignoreFlag = false;
private listeners: QueueEventListener[] = []; private listeners: QueueEventListener[] = [];
@ -83,7 +122,7 @@ export class Queue {
constructor(options: QueueOptions) { constructor(options: QueueOptions) {
this.getProfile = options.getProfile; this.getProfile = options.getProfile;
this.queue = options.queue ?? document.querySelector<HTMLElement & QueueAPI>('#queue'); this.queue = options.queue ?? document.querySelector<HTMLElement & QueueAPI>('#queue')!;
this.owner = options.owner ?? null; this.owner = options.owner ?? null;
this._videoList = options.videoList ?? []; this._videoList = options.videoList ?? [];
} }
@ -96,7 +135,7 @@ export class Queue {
} }
get selectedIndex() { get selectedIndex() {
return mapQueueItem((it) => it?.selected, this.queue?.store.getState().queue.items).findIndex(Boolean) ?? 0; return mapQueueItem((it) => it?.selected, this.queue.store.getState().queue.items).findIndex(Boolean) ?? 0;
} }
get rawItems() { get rawItems() {
@ -146,7 +185,7 @@ export class Queue {
return true; return true;
} }
async removeVideo(index: number) { removeVideo(index: number) {
this.internalDispatch = true; this.internalDispatch = true;
this._videoList.splice(index, 1); this._videoList.splice(index, 1);
this.queue?.dispatch({ this.queue?.dispatch({
@ -233,10 +272,10 @@ export class Queue {
if (event.type === 'ADD_ITEMS') { if (event.type === 'ADD_ITEMS') {
if (this.ignoreFlag) { if (this.ignoreFlag) {
this.ignoreFlag = false; this.ignoreFlag = false;
const videoList = mapQueueItem((it: any) => ({ const videoList = mapQueueItem((it) => ({
videoId: it.videoId, videoId: it!.videoId,
ownerId: this.owner!.id ownerId: this.owner!.id
} satisfies VideoData), (event.payload as any).items); } satisfies VideoData), event.payload!.items!);
const index = this._videoList.length + videoList.length - 1; const index = this._videoList.length + videoList.length - 1;
if (videoList.length > 0) { if (videoList.length > 0) {
@ -255,15 +294,17 @@ export class Queue {
] ]
}); });
} }
} else if ((event.payload as any).items.length === 1) { } else if ((event.payload as {
items: unknown[];
}).items.length === 1) {
this.broadcast({ // add playlist this.broadcast({ // add playlist
type: 'ADD_SONGS', type: 'ADD_SONGS',
payload: { payload: {
// index: (event.payload as any).index, // index: (event.payload as any).index,
videoList: mapQueueItem((it: any) => ({ videoList: mapQueueItem((it) => ({
videoId: it.videoId, videoId: it!.videoId,
ownerId: this.owner!.id ownerId: this.owner!.id
} satisfies VideoData), (event.payload as any).items) } satisfies VideoData), event.payload!.items!)
} }
}); });
} }
@ -275,8 +316,12 @@ export class Queue {
this.broadcast({ this.broadcast({
type: 'MOVE_SONG', type: 'MOVE_SONG',
payload: { payload: {
fromIndex: (event.payload as any).fromIndex, fromIndex: (event.payload as {
toIndex: (event.payload as any).toIndex fromIndex: number;
}).fromIndex,
toIndex: (event.payload as {
toIndex: number;
}).toIndex
} }
}); });
return; return;
@ -306,7 +351,7 @@ export class Queue {
event.payload = undefined; event.payload = undefined;
} }
if (event.type === 'SET_PLAYER_UI_STATE') { if (event.type === 'SET_PLAYER_UI_STATE') {
if (event.payload === 'INACTIVE' && this.videoList.length > 0) { if (event.payload as string === 'INACTIVE' && this.videoList.length > 0) {
return; return;
} }
} }
@ -321,12 +366,12 @@ export class Queue {
dispatch: this.originalDispatch dispatch: this.originalDispatch
} }
}; };
this.originalDispatch!.call(fakeContext, event); this.originalDispatch?.call(fakeContext, event);
}; };
} }
/* sync */ /* sync */
async initQueue() { initQueue() {
if (!this.queue) return; if (!this.queue) return;
this.internalDispatch = true; this.internalDispatch = true;
@ -369,13 +414,13 @@ export class Queue {
return true; return true;
} }
async syncQueueOwner() { syncQueueOwner() {
const allQueue = document.querySelectorAll('#queue'); const allQueue = document.querySelectorAll('#queue');
allQueue.forEach((queue) => { allQueue.forEach((queue) => {
const list = Array.from(queue?.querySelectorAll<HTMLElement>('ytmusic-player-queue-item') ?? []); const list = Array.from(queue?.querySelectorAll<HTMLElement>('ytmusic-player-queue-item') ?? []);
list.forEach((item, index) => { list.forEach((item, index: number | undefined) => {
if (typeof index !== 'number') return; if (typeof index !== 'number') return;
const id = this._videoList[index]?.ownerId; const id = this._videoList[index]?.ownerId;

View File

@ -12,14 +12,14 @@ export function SHA1Hash(): {
} }
function processBlock(block: number[]): void { function processBlock(block: number[]): void {
let words: number[] = []; const words: number[] = [];
for (let i = 0; i < 64; i += 4) { for (let i = 0; i < 64; i += 4) {
words[i / 4] = block[i] << 24 | block[i + 1] << 16 | block[i + 2] << 8 | block[i + 3]; words[i / 4] = (block[i] << 24) | (block[i + 1] << 16) | (block[i + 2] << 8) | block[i + 3];
} }
for (let i = 16; i < 80; i++) { for (let i = 16; i < 80; i++) {
let temp = words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16]; const temp = words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16];
words[i] = (temp << 1 | temp >>> 31) & 4294967295; words[i] = ((temp << 1) | (temp >>> 31)) & 4294967295;
} }
let a = hash[0], let a = hash[0],
@ -30,22 +30,22 @@ export function SHA1Hash(): {
for (let i = 0; i < 80; i++) { for (let i = 0; i < 80; i++) {
let f, k; let f, k;
if (i < 20) { if (i < 20) {
f = d ^ b & (c ^ d); f = d ^ (b & (c ^ d));
k = 1518500249; k = 1518500249;
} else if (i < 40) { } else if (i < 40) {
f = b ^ c ^ d; f = b ^ c ^ d;
k = 1859775393; k = 1859775393;
} else if (i < 60) { } else if (i < 60) {
f = b & c | d & (b | c); f = (b & c) | (d & (b | c));
k = 2400959708; k = 2400959708;
} else { } else {
f = b ^ c ^ d; f = b ^ c ^ d;
k = 3395469782; k = 3395469782;
} }
let temp = ((a << 5 | a >>> 27) & 4294967295) + f + e + k + words[i] & 4294967295; const temp = (((a << 5) | (a >>> 27)) & 4294967295) + f + e + k + words[i] & 4294967295;
e = d; e = d;
d = c; d = c;
c = (b << 30 | b >>> 2) & 4294967295; c = ((b << 30) | (b >>> 2)) & 4294967295;
b = a; b = a;
a = temp; a = temp;
} }
@ -58,8 +58,9 @@ export function SHA1Hash(): {
function update(message: string | number[], length?: number): void { function update(message: string | number[], length?: number): void {
if ('string' === typeof message) { if ('string' === typeof message) {
// HACK: to decode UTF-8
message = unescape(encodeURIComponent(message)); message = unescape(encodeURIComponent(message));
let bytes: number[] = []; const bytes: number[] = [];
for (let i = 0, len = message.length; i < len; ++i) for (let i = 0, len = message.length; i < len; ++i)
bytes.push(message.charCodeAt(i)); bytes.push(message.charCodeAt(i));
message = bytes; message = bytes;
@ -67,48 +68,59 @@ export function SHA1Hash(): {
length || (length = message.length); length || (length = message.length);
let i = 0; let i = 0;
if (0 == currentLength) if (0 == currentLength)
for (; i + 64 < length;) for (; i + 64 < length;) {
processBlock(message.slice(i, i + 64)), processBlock(message.slice(i, i + 64));
i += 64, i += 64;
totalLength += 64;
}
for (; i < length;) {
if (buffer[currentLength++] = message[i++], totalLength++, 64 == currentLength)
for (currentLength = 0, processBlock(buffer); i + 64 < length;) {
processBlock(message.slice(i, i + 64));
i += 64;
totalLength += 64; totalLength += 64;
for (; i < length;) }
if (buffer[currentLength++] = message[i++], }
totalLength++,
64 == currentLength)
for (currentLength = 0,
processBlock(buffer); i + 64 < length;)
processBlock(message.slice(i, i + 64)),
i += 64,
totalLength += 64;
} }
function finalize(): number[] { function finalize(): number[] {
let result: number[] = [] const result: number[] = [];
, bits = 8 * totalLength; let bits = 8 * totalLength;
if (currentLength < 56) if (currentLength < 56) {
update(padding, 56 - currentLength); update(padding, 56 - currentLength);
else } else {
update(padding, 64 - (currentLength - 56)); update(padding, 64 - (currentLength - 56));
for (let i = 63; i >= 56; i--) }
buffer[i] = bits & 255, for (let i = 63; i >= 56; i--) {
bits >>>= 8; buffer[i] = bits & 255;
bits >>>= 8;
}
processBlock(buffer); processBlock(buffer);
for (let i = 0; i < 5; i++) for (let i = 0; i < 5; i++) {
for (let j = 24; j >= 0; j -= 8) for (let j = 24; j >= 0; j -= 8) {
result.push(hash[i] >> j & 255); result.push((hash[i] >> j) & 255);
}
}
return result; return result;
} }
let buffer: number[] = [], padding: number[] = [128], totalLength: number, currentLength: number; const buffer: number[] = [];
for (let i = 1; i < 64; ++i) const padding: number[] = [128];
let totalLength: number;
let currentLength: number;
for (let i = 1; i < 64; ++i) {
padding[i] = 0; padding[i] = 0;
}
initialize(); initialize();
return { return {
reset: initialize, reset: initialize,
update: update, update: update,
digest: finalize, digest: finalize,
digestString: function(): string { digestString: function(): string {
let hash = finalize(), hex = ''; const hash = finalize();
let hex = '';
for (let i = 0; i < hash.length; i++) for (let i = 0; i < hash.length; i++)
hex += '0123456789ABCDEF'.charAt(Math.floor(hash[i] / 16)) + '0123456789ABCDEF'.charAt(hash[i] % 16); hex += '0123456789ABCDEF'.charAt(Math.floor(hash[i] / 16)) + '0123456789ABCDEF'.charAt(hash[i] % 16);
return hex; return hex;

View File

@ -1,15 +1,21 @@
export const mapQueueItem = <T>(map: (item: any | null) => T, array: any[]): T[] => array import {
ItemPlaylistPanelVideoRenderer,
PlaylistPanelVideoWrapperRenderer,
QueueItem
} from '@/types/datahost-get-state';
export const mapQueueItem = <T>(map: (item?: ItemPlaylistPanelVideoRenderer) => T, array: QueueItem[]): T[] => array
.map((item) => { .map((item) => {
if ('playlistPanelVideoWrapperRenderer' in item) { if ('playlistPanelVideoWrapperRenderer' in item) {
const keys = Object.keys(item.playlistPanelVideoWrapperRenderer.primaryRenderer); const keys = Object.keys(item.playlistPanelVideoWrapperRenderer!.primaryRenderer) as (keyof PlaylistPanelVideoWrapperRenderer['primaryRenderer'])[];
return item.playlistPanelVideoWrapperRenderer.primaryRenderer[keys[0]]; return item.playlistPanelVideoWrapperRenderer!.primaryRenderer[keys[0]];
} }
if ('playlistPanelVideoRenderer' in item) { if ('playlistPanelVideoRenderer' in item) {
return item.playlistPanelVideoRenderer; return item.playlistPanelVideoRenderer;
} }
console.error('Music Together: Unknown item', item); console.error('Music Together: Unknown item', item);
return null; return undefined;
}) })
.map(map); .map(map);

View File

@ -1,14 +1,14 @@
<div class="music-together-status"> <div class="music-together-status">
<div class="music-together-status-container"> <div class="music-together-status-container">
<img class="music-together-profile big"> <img class="music-together-profile big" alt="Profile Image">
<div class="music-together-status-item"> <div class="music-together-status-item">
<ytmd-trans key="plugins.music-together.name"></ytmd-trans> <ytmd-trans key="plugins.music-together.name"></ytmd-trans>
<span id="music-together-status-label"> <span id="music-together-status-label">
<ytmd-trans key="plugins.music-together.menu.status.disconnected"></ytmd-trans> <ytmd-trans key="plugins.music-together.menu.status.disconnected"></ytmd-trans>
</span> </span>
<span id="music-together-permission-label"> <marquee id="music-together-permission-label">
<ytmd-trans key="plugins.music-together.menu.permission.playlist" style="color: rgba(255, 255, 255, 0.75)"></ytmd-trans> <ytmd-trans key="plugins.music-together.menu.permission.playlist" style="color: rgba(255, 255, 255, 0.75)"></ytmd-trans>
</span> </marquee>
</div> </div>
</div> </div>
<div class="music-together-divider horizontal" style="margin: 16px 0;"></div> <div class="music-together-divider horizontal" style="margin: 16px 0;"></div>

View File

@ -1,15 +1,19 @@
import { YoutubePlayer } from '@/types/youtube-player'; import { YoutubePlayer } from '@/types/youtube-player';
type StoreState = any; import { GetState, QueueItem } from '@/types/datahost-get-state';
type StoreState = GetState;
type Store = { type Store = {
dispatch: (obj: { dispatch: (obj: {
type: string; type: string;
payload?: unknown; payload?: {
items?: QueueItem[];
};
}) => void; }) => void;
getState: () => StoreState; getState: () => StoreState;
replaceReducer: (param1: unknown) => unknown; replaceReducer: (param1: unknown) => unknown;
subscribe: (callback: () => void) => unknown; subscribe: (callback: () => void) => unknown;
}; }
export type QueueAPI = { export type QueueAPI = {
dispatch(obj: { dispatch(obj: {
type: string; type: string;
@ -28,8 +32,6 @@ export type AppAPI = {
// TODO: Add more // TODO: Add more
}; };
export type Profile = { export type Profile = {
id: string; id: string;
handleId: string; handleId: string;

View File

@ -1,6 +1,8 @@
import { ElementFromHtml } from '@/plugins/utils/renderer'; import { ElementFromHtml } from '@/plugins/utils/renderer';
import statusHTML from '../templates/status.html?raw';
import { t } from '@/i18n'; import { t } from '@/i18n';
import statusHTML from '../templates/status.html?raw';
import type { Permission, Profile } from '../types'; import type { Permission, Profile } from '../types';
export const createStatus = () => { export const createStatus = () => {
@ -9,7 +11,7 @@ export const createStatus = () => {
const profile = element.querySelector<HTMLImageElement>('.music-together-profile')!; const profile = element.querySelector<HTMLImageElement>('.music-together-profile')!;
const statusLabel = element.querySelector<HTMLSpanElement>('#music-together-status-label')!; const statusLabel = element.querySelector<HTMLSpanElement>('#music-together-status-label')!;
const permissionLabel = element.querySelector<HTMLSpanElement>('#music-together-permission-label')!; const permissionLabel = element.querySelector<HTMLMarqueeElement>('#music-together-permission-label')!;
profile.src = icon?.src ?? ''; profile.src = icon?.src ?? '';

View File

@ -127,7 +127,7 @@ export default (api: YoutubePlayer) => {
const waitingEvent = new Set<string>(); const waitingEvent = new Set<string>();
// Name = "dataloaded" and abit later "dataupdated" // Name = "dataloaded" and abit later "dataupdated"
api.addEventListener('videodatachange', (name: string, videoData) => { api.addEventListener('videodatachange', (name, videoData) => {
videoEventDispatcher(name, videoData); videoEventDispatcher(name, videoData);
if (name === 'dataupdated' && waitingEvent.has(videoData.videoId)) { if (name === 'dataupdated' && waitingEvent.has(videoData.videoId)) {

View File

@ -256,6 +256,7 @@ export type VideoDataChangeValue = Record<string, unknown> & {
export interface PlayerAPIEvents { export interface PlayerAPIEvents {
videodatachange: { videodatachange: {
value: VideoDataChangeValue; value: VideoDataChangeValue;
} & ({ name: 'dataloaded' } | { name: 'dataupdated ' }); name: 'dataloaded' | 'dataupdated';
};
onStateChange: number; onStateChange: number;
} }

View File

@ -357,8 +357,8 @@ export interface YoutubePlayer {
type: K, type: K,
listener: ( listener: (
this: Document, this: Document,
name: PlayerAPIEvents[K]['name'], name: K extends 'videodatachange' ? PlayerAPIEvents[K]['name'] : never,
data: PlayerAPIEvents[K]['value'], data: K extends 'videodatachange' ? PlayerAPIEvents[K]['value'] : never,
) => void, ) => void,
options?: boolean | AddEventListenerOptions | undefined, options?: boolean | AddEventListenerOptions | undefined,
) => void; ) => void;
@ -366,8 +366,8 @@ export interface YoutubePlayer {
type: K, type: K,
listener: ( listener: (
this: Document, this: Document,
name: PlayerAPIEvents[K]['name'], name: K extends 'videodatachange' ? PlayerAPIEvents[K]['name'] : never,
data: PlayerAPIEvents[K]['value'], data: K extends 'videodatachange' ? PlayerAPIEvents[K]['value'] : never,
) => void, ) => void,
options?: boolean | EventListenerOptions | undefined, options?: boolean | EventListenerOptions | undefined,
) => void; ) => void;