feat(playlists): библиотека плейлистов приложения

CRUD в редакторе, read-only окно на пульте, отдельный аудиоканал, экспорт/импорт и маппинг из Foundry.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-09-03 10:37:21 +08:00
parent cfb6e62331
commit 278eb78637
46 changed files with 2438 additions and 41 deletions
+6 -2
View File
@@ -2,8 +2,9 @@ import fs from 'node:fs/promises';
import { session } from 'electron';
import { asAssetId, asPlayerId, asTokenId } from '../../shared/types/ids';
import { asAssetId, asPlayerId, asPlaylistAudioId, asTokenId } from '../../shared/types/ids';
import type { PlayersStore } from '../players/playersStore';
import type { PlaylistsStore } from '../playlists/playlistsStore';
import type { ZipProjectStore } from '../project/zipStore';
import type { TokensStore } from '../tokens/tokensStore';
@@ -76,12 +77,13 @@ async function serveFile(info: ReadInfo, request: Request): Promise<Response> {
}
/**
* Обслуживает `dnd://asset?...`, `dnd://token?...` и `dnd://player?...`.
* Обслуживает `dnd://asset?...`, `dnd://token?...`, `dnd://player?...` и `dnd://playlist-audio?...`.
*/
export function registerDndAssetProtocol(
projectStore: ZipProjectStore,
tokensStore: TokensStore,
playersStore: PlayersStore | null,
playlistsStore: PlaylistsStore | null = null,
): void {
session.defaultSession.protocol.handle('dnd', async (request) => {
const url = new URL(request.url);
@@ -96,6 +98,8 @@ export function registerDndAssetProtocol(
info = tokensStore.getImageReadInfo(asTokenId(id));
} else if (url.hostname === 'player') {
info = playersStore?.getImageReadInfo(asPlayerId(id)) ?? null;
} else if (url.hostname === 'playlist-audio') {
info = playlistsStore?.getAudioReadInfo(asPlaylistAudioId(id)) ?? null;
}
if (!info) {
return new Response(null, { status: 404 });