diff --git a/app/lib/sequence-builder.tsx b/app/lib/sequence-builder.tsx index bdd8f92..67dbc7b 100644 --- a/app/lib/sequence-builder.tsx +++ b/app/lib/sequence-builder.tsx @@ -130,3 +130,49 @@ export const SequenceBuilder = ({ ) } + +export const SequenceViewer = ({ + queue, + sounds, + label +}: { + queue: string[] + sounds: Audio[] + label: string +}) => { + const {t} = useTranslation() + + let duration = 0 + + return ( +
+ {label} +
+ {queue.map((queuedId, i) => { + const sound = sounds.filter(({id}) => { + return id === queuedId + })[0] + + duration += sound.duration + + return ( +
+

{sound.name}

+

+ {getSecondsAsTime(sound.duration)} +

+
+ ) + })} +
+ {t('broadcast.builder.totalDuration', { + duration: getSecondsAsTime(duration) + })} +
+
+
+ ) +} diff --git a/app/locales/en.ts b/app/locales/en.ts index cbca71d..ba606d3 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -53,7 +53,9 @@ export const en = { 'An emoji to use as the action icon. Note that emoji render differently on the RPi screen.', 'actions.form.type.label': 'Type', 'actions.form.type.helper': - 'Broadcast runs a broadcast to the supplied zone. Lockdown toggles a system wide lockdown.', + 'Broadcast runs a broadcast to the supplied zone, you can build the broadcaast once the action has been created. Lockdown toggles a system wide lockdown.', + 'actions.form.sequence.label': 'Sequence', + 'actions.form.sequence.helper': 'Build your broadcast sequence.', 'actions.form.sound.label': 'Sound', 'actions.form.sound.helper': 'Which sound should be used when broadcasting?', 'button.cancel': 'Cancel', @@ -66,6 +68,7 @@ export const en = { 'actions.detail.sound': 'Sound:', 'actions.edit.metaTitle': 'Edit {{name}}', 'actions.edit.pageTitle': 'Edit {{name}}', + 'actions.detail.sequence.label': 'Sequence', 'backup.pageTitle': 'Backups', 'backup.create': 'Create Backup', 'broadcast.pageTitle': 'Broadcast', diff --git a/app/routes/actions.$action._index.tsx b/app/routes/actions.$action._index.tsx index 33c25f6..c267f76 100644 --- a/app/routes/actions.$action._index.tsx +++ b/app/routes/actions.$action._index.tsx @@ -12,6 +12,7 @@ import {Page, Actions} from '~/lib/ui' import {useTranslation} from '~/lib/i18n' import {translate} from '~/lib/i18n.shared' import {getRootI18n} from '~/lib/i18n.meta' +import {SequenceViewer} from '~/lib/sequence-builder' export const meta: MetaFunction = ({matches}) => { const {messages} = getRootI18n(matches) @@ -28,15 +29,16 @@ export const loader = async ({request, params}: LoaderFunctionArgs) => { const prisma = getPrisma() const action = await prisma.action.findFirstOrThrow({ - where: {id: params.action}, - include: {audio: true} + where: {id: params.action} }) - return {action} + const sounds = await prisma.audio.findMany({orderBy: {name: 'asc'}}) + + return {action, sounds} } const Action = () => { - const {action} = useLoaderData() + const {action, sounds} = useLoaderData() const navigate = useNavigate() const {t} = useTranslation() const typeLabels: Record = { @@ -54,11 +56,12 @@ const Action = () => { {t('actions.detail.type')}:{' '} {typeLabels[action.action] ?? action.action}

-

- {t('actions.detail.sound')}{' '} - {action.audio!.name} -

+ = ({matches, data}) => { const {messages} = getRootI18n(matches) @@ -61,16 +62,16 @@ export const action = async ({params, request}: ActionFunctionArgs) => { const name = formData.get('name') as string | undefined const icon = formData.get('icon') as string | undefined const action = formData.get('action') as string | undefined - const sound = formData.get('sound') as string | undefined + const data = formData.get('data') as string | undefined invariant(name) invariant(icon) invariant(action) - invariant(sound) + invariant(data) await prisma.action.update({ where: {id: params.action}, - data: {name, icon, action, audioId: sound} + data: {name, icon, action, data} }) return redirect(`/actions/${params.action}`) @@ -117,24 +118,13 @@ const AddAction = () => { - - - + { const name = formData.get('name') as string | undefined const icon = formData.get('icon') as string | undefined const action = formData.get('action') as string | undefined - const sound = formData.get('sound') as string | undefined invariant(name) invariant(icon) invariant(action) - invariant(sound) const newAction = await prisma.action.create({ - data: {name, icon, action, audioId: sound} + data: {name, icon, action} }) void trigger(`New Action: ${name}`, 'newAction') @@ -101,20 +99,6 @@ const AddAction = () => { - - - { } if (webhook.action.audioId) { - await broadcast(zone, webhook.action.audioId) + await broadcast(zone, webhook.action.data) } break case 'lockdown': diff --git a/prisma/migrations/20260323193952_add_data_to_action/migration.sql b/prisma/migrations/20260323193952_add_data_to_action/migration.sql new file mode 100644 index 0000000..6568213 --- /dev/null +++ b/prisma/migrations/20260323193952_add_data_to_action/migration.sql @@ -0,0 +1,17 @@ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Action" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "icon" TEXT NOT NULL, + "action" TEXT NOT NULL, + "data" TEXT NOT NULL DEFAULT '', + "audioId" TEXT, + CONSTRAINT "Action_audioId_fkey" FOREIGN KEY ("audioId") REFERENCES "Audio" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_Action" ("action", "audioId", "icon", "id", "name") SELECT "action", "audioId", "icon", "id", "name" FROM "Action"; +DROP TABLE "Action"; +ALTER TABLE "new_Action" RENAME TO "Action"; +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fcfdc5a..14b37e0 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -106,7 +106,9 @@ model Action { name String icon String action String + data String @default("") + // BREAKING CHANGE When we make the jump to 2.x this can be removed. Not Worth a full V2 bump just to take it out. audio Audio? @relation(fields: [audioId], references: [id]) audioId String? diff --git a/prisma/seed.js b/prisma/seed.js index c44dba2..d20836d 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -74,6 +74,19 @@ const main = async () => { await Promise.all(promises) } + + const actionsWithAudioIdAndNoData = await prisma.action.findMany({ + where: {audioId: {not: ''}, data: ''} + }) + + await Promise.all( + actionsWithAudioIdAndNoData.map(action => { + return prisma.action.update({ + where: {id: action.id}, + data: {data: `["${action.audioId}"]`} + }) + }) + ) } main()