Release 0.5.10
This commit is contained in:
1 parent
c3033479b5
commit
4fe61971e4
52 files changed
+38565
-466
No files matched your search
+164
-11
@@ -69,7 +69,19 @@ export type PieceUsage = {
|
||||
workUrl: string;
|
||||
note: string;
|
||||
}[];
|
||||
topics: string[];
|
||||
topics: UsageTopic[];
|
||||
media: PieceUsageMedia[];
|
||||
presentationMediaId: string | null;
|
||||
};
|
||||
|
||||
export type UsageTopic = { name: string; iconName: string };
|
||||
|
||||
export type PieceUsageMedia = {
|
||||
id: string;
|
||||
originalMediaId: string;
|
||||
altText: string;
|
||||
position: number;
|
||||
isPresentation: boolean;
|
||||
};
|
||||
|
||||
export type SentPieceUsage = {
|
||||
@@ -158,6 +170,7 @@ export type PieceUsageUpdate = {
|
||||
ownNotes: string;
|
||||
durationSeconds: number;
|
||||
tools?: { id: string; workUrl: string; note: string }[];
|
||||
presentationMediaId?: string | null;
|
||||
};
|
||||
|
||||
export type MovePieceUsageInput = {
|
||||
@@ -363,6 +376,43 @@ function usageSection(
|
||||
return row;
|
||||
}
|
||||
|
||||
function requireUsagesRemovable(
|
||||
database: Database.Database,
|
||||
showId: string,
|
||||
usageIds: string[]
|
||||
): void {
|
||||
if (!usageIds.length) return;
|
||||
const placeholders = usageIds.map(() => '?').join(', ');
|
||||
const state = database
|
||||
.prepare(
|
||||
`SELECT current_usage_id, presentation_usage_id FROM show_live_states WHERE show_id = ?`
|
||||
)
|
||||
.get(showId) as
|
||||
{ current_usage_id: string | null; presentation_usage_id: string | null } | undefined;
|
||||
if (state?.current_usage_id && usageIds.includes(state.current_usage_id)) {
|
||||
throw new Error('USAGE_IS_CURRENT');
|
||||
}
|
||||
if (state?.presentation_usage_id && usageIds.includes(state.presentation_usage_id)) {
|
||||
throw new Error('USAGE_IS_PRESENTED');
|
||||
}
|
||||
if (
|
||||
database
|
||||
.prepare(`SELECT 1 FROM live_usage_runs WHERE usage_id IN (${placeholders}) LIMIT 1`)
|
||||
.get(...usageIds)
|
||||
) {
|
||||
throw new Error('USAGE_HAS_LIVE_RUNS');
|
||||
}
|
||||
if (
|
||||
database
|
||||
.prepare(
|
||||
`SELECT 1 FROM piece_usages WHERE id IN (${placeholders}) AND status != 'open' LIMIT 1`
|
||||
)
|
||||
.get(...usageIds)
|
||||
) {
|
||||
throw new Error('USAGE_NOT_OPEN');
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePositions(
|
||||
database: Database.Database,
|
||||
table: 'show_sections' | 'piece_usages',
|
||||
@@ -450,9 +500,39 @@ function usageFromRow(database: Database.Database, row: UsageRow): PieceUsage {
|
||||
})),
|
||||
topics: (
|
||||
database
|
||||
.prepare('SELECT name FROM usage_topic_snapshots WHERE usage_id = ? ORDER BY position')
|
||||
.all(row.id) as { name: string }[]
|
||||
).map((topic) => topic.name)
|
||||
.prepare(
|
||||
'SELECT name, icon_name FROM usage_topic_snapshots WHERE usage_id = ? ORDER BY position'
|
||||
)
|
||||
.all(row.id) as { name: string; icon_name: string }[]
|
||||
).map((topic) => ({ name: topic.name, iconName: topic.icon_name })),
|
||||
media: (
|
||||
database
|
||||
.prepare(
|
||||
`SELECT id, original_media_id, alt_text, position, is_presentation
|
||||
FROM usage_media_snapshots WHERE usage_id = ? ORDER BY position`
|
||||
)
|
||||
.all(row.id) as {
|
||||
id: string;
|
||||
original_media_id: string;
|
||||
alt_text: string;
|
||||
position: number;
|
||||
is_presentation: number;
|
||||
}[]
|
||||
).map((media) => ({
|
||||
id: media.id,
|
||||
originalMediaId: media.original_media_id,
|
||||
altText: media.alt_text,
|
||||
position: media.position,
|
||||
isPresentation: media.is_presentation === 1
|
||||
})),
|
||||
presentationMediaId:
|
||||
(
|
||||
database
|
||||
.prepare(
|
||||
'SELECT id FROM usage_media_snapshots WHERE usage_id = ? AND is_presentation = 1'
|
||||
)
|
||||
.get(row.id) as { id: string } | undefined
|
||||
)?.id ?? null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -460,6 +540,7 @@ function snapshotPieceRelations(database: Database.Database, usageId: string, pi
|
||||
database.prepare('DELETE FROM usage_source_snapshots WHERE usage_id = ?').run(usageId);
|
||||
database.prepare('DELETE FROM usage_tool_snapshots WHERE usage_id = ?').run(usageId);
|
||||
database.prepare('DELETE FROM usage_topic_snapshots WHERE usage_id = ?').run(usageId);
|
||||
database.prepare('DELETE FROM usage_media_snapshots WHERE usage_id = ?').run(usageId);
|
||||
const sources = database
|
||||
.prepare(
|
||||
`SELECT label, url, timecode_seconds, position FROM piece_sources
|
||||
@@ -518,14 +599,44 @@ function snapshotPieceRelations(database: Database.Database, usageId: string, pi
|
||||
}
|
||||
const topics = database
|
||||
.prepare(
|
||||
`SELECT t.name FROM piece_topics pt JOIN topics t ON t.id = pt.topic_id
|
||||
`SELECT t.name, t.icon_name FROM piece_topics pt JOIN topics t ON t.id = pt.topic_id
|
||||
WHERE pt.piece_id = ? ORDER BY t.name, t.id`
|
||||
)
|
||||
.all(pieceId) as { name: string }[];
|
||||
.all(pieceId) as { name: string; icon_name: string }[];
|
||||
const addTopic = database.prepare(
|
||||
'INSERT INTO usage_topic_snapshots (usage_id, name, position) VALUES (?, ?, ?)'
|
||||
'INSERT INTO usage_topic_snapshots (usage_id, name, icon_name, position) VALUES (?, ?, ?, ?)'
|
||||
);
|
||||
topics.forEach((topic, position) => addTopic.run(usageId, topic.name, position));
|
||||
topics.forEach((topic, position) => addTopic.run(usageId, topic.name, topic.icon_name, position));
|
||||
const media = database
|
||||
.prepare(
|
||||
`SELECT pm.id, pm.alt_text, pm.position,
|
||||
CASE WHEN ppm.media_id IS NULL THEN 0 ELSE 1 END AS is_presentation
|
||||
FROM piece_media pm
|
||||
LEFT JOIN piece_presentation_media ppm
|
||||
ON ppm.piece_id = pm.piece_id AND ppm.media_id = pm.id
|
||||
WHERE pm.piece_id = ? AND pm.removed_at_utc IS NULL ORDER BY pm.position`
|
||||
)
|
||||
.all(pieceId) as {
|
||||
id: string;
|
||||
alt_text: string;
|
||||
position: number;
|
||||
is_presentation: number;
|
||||
}[];
|
||||
const addMedia = database.prepare(
|
||||
`INSERT INTO usage_media_snapshots
|
||||
(id, usage_id, original_media_id, alt_text, position, is_presentation)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`
|
||||
);
|
||||
for (const item of media) {
|
||||
addMedia.run(
|
||||
randomUUID(),
|
||||
usageId,
|
||||
item.id,
|
||||
item.alt_text,
|
||||
item.position,
|
||||
item.is_presentation
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function copyUsageRelations(database: Database.Database, sourceUsageId: string, usageId: string) {
|
||||
@@ -584,8 +695,16 @@ function copyUsageRelations(database: Database.Database, sourceUsageId: string,
|
||||
);
|
||||
database
|
||||
.prepare(
|
||||
`INSERT INTO usage_topic_snapshots (usage_id, name, position)
|
||||
SELECT ?, name, position FROM usage_topic_snapshots WHERE usage_id = ?`
|
||||
`INSERT INTO usage_topic_snapshots (usage_id, name, icon_name, position)
|
||||
SELECT ?, name, icon_name, position FROM usage_topic_snapshots WHERE usage_id = ?`
|
||||
)
|
||||
.run(usageId, sourceUsageId);
|
||||
database
|
||||
.prepare(
|
||||
`INSERT INTO usage_media_snapshots
|
||||
(id, usage_id, original_media_id, alt_text, position, is_presentation)
|
||||
SELECT lower(hex(randomblob(16))), ?, original_media_id, alt_text, position, is_presentation
|
||||
FROM usage_media_snapshots WHERE usage_id = ?`
|
||||
)
|
||||
.run(usageId, sourceUsageId);
|
||||
}
|
||||
@@ -949,6 +1068,12 @@ export function removeShowSection(
|
||||
return database.transaction(() => {
|
||||
const result = bumpShow(database, showId, expectedRevision);
|
||||
sectionBelongsToShow(database, showId, sectionId);
|
||||
const usageIds = (
|
||||
database.prepare('SELECT id FROM piece_usages WHERE show_section_id = ?').all(sectionId) as {
|
||||
id: string;
|
||||
}[]
|
||||
).map((usage) => usage.id);
|
||||
requireUsagesRemovable(database, showId, usageIds);
|
||||
database.prepare('DELETE FROM show_sections WHERE id = ?').run(sectionId);
|
||||
const ids = (
|
||||
database
|
||||
@@ -1034,6 +1159,13 @@ export function addPieceUsage(
|
||||
}
|
||||
| undefined;
|
||||
if (!piece) throw new Error('NOT_FOUND');
|
||||
if (
|
||||
!database
|
||||
.prepare('SELECT 1 FROM pieces WHERE id = ? AND deleted_at_utc IS NULL')
|
||||
.get(input.pieceId)
|
||||
) {
|
||||
throw new Error('NOT_FOUND');
|
||||
}
|
||||
if (piece.status !== 'ready') throw new Error('PIECE_NOT_READY');
|
||||
const ids = (
|
||||
database
|
||||
@@ -1237,7 +1369,7 @@ export function refreshPieceUsageFromOriginal(
|
||||
FROM piece_usages u JOIN pieces p ON p.id = u.original_piece_id
|
||||
LEFT JOIN piece_sources ps ON ps.piece_id = p.id
|
||||
AND ps.position = p.presentation_source_position
|
||||
WHERE u.id = ?`
|
||||
WHERE u.id = ? AND p.deleted_at_utc IS NULL`
|
||||
)
|
||||
.get(usageId) as
|
||||
| {
|
||||
@@ -1400,6 +1532,26 @@ export function updatePieceUsage(
|
||||
updateTool.run(workUrl, cleanText(tool.note, 2_000, 'tools'), tool.id, usageId);
|
||||
}
|
||||
}
|
||||
if (input.presentationMediaId !== undefined) {
|
||||
if (
|
||||
input.presentationMediaId !== null &&
|
||||
!database
|
||||
.prepare('SELECT 1 FROM usage_media_snapshots WHERE id = ? AND usage_id = ?')
|
||||
.get(input.presentationMediaId, usageId)
|
||||
) {
|
||||
throw new Error('INVALID_PRESENTATION_MEDIA');
|
||||
}
|
||||
database
|
||||
.prepare('UPDATE usage_media_snapshots SET is_presentation = 0 WHERE usage_id = ?')
|
||||
.run(usageId);
|
||||
if (input.presentationMediaId !== null) {
|
||||
database
|
||||
.prepare(
|
||||
'UPDATE usage_media_snapshots SET is_presentation = 1 WHERE id = ? AND usage_id = ?'
|
||||
)
|
||||
.run(input.presentationMediaId, usageId);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
}
|
||||
@@ -1413,6 +1565,7 @@ export function removePieceUsage(
|
||||
return database.transaction(() => {
|
||||
const result = bumpShow(database, showId, expectedRevision);
|
||||
const source = usageSection(database, showId, usageId);
|
||||
requireUsagesRemovable(database, showId, [usageId]);
|
||||
database.prepare('DELETE FROM piece_usages WHERE id = ?').run(usageId);
|
||||
const ids = (
|
||||
database
|
||||
|
||||
Reference in new issue
Block a user