diff --git a/assets/css/wizard.css b/assets/css/wizard.css index 468b492..0988dca 100644 --- a/assets/css/wizard.css +++ b/assets/css/wizard.css @@ -62,27 +62,6 @@ nav.music { height: 100%; } -.inverted-class .wgtable { - width: calc(100vw - 32px); - max-width: 50rem; - max-height: 25rem; - overflow: auto; - height: 100vh; - box-sizing: border-box; - gap: 8px; - background: white; - color: black; - padding: 0 16px; - border-radius: 8px; - --color-grayscaled-lightness: 0%; - --color-grayscaled-font: white; -} - -.inverted-class :not(.artists-list)>.grayscaled.wiconbutton { - color: hsl(var(--background-color-h), var(--background-color-s), var(--background-color-l)); - background: transparent; -} - .wbutton.disabled { z-index: 0; } @@ -128,11 +107,7 @@ span.title { } } -.artist-table .wgtable { - gap: 12px; - margin: 12px 0; -} - -.artist-table .wgtable .column { - gap: 12px; +.inverted-class .wgtable { + width: calc(100vw - 32px); + max-width: 50rem; } \ No newline at end of file diff --git a/pages/_legacy/music/changeDrop.ts b/pages/_legacy/music/changeDrop.ts index 9f9b7ce..444d4af 100644 --- a/pages/_legacy/music/changeDrop.ts +++ b/pages/_legacy/music/changeDrop.ts @@ -2,15 +2,15 @@ import * as zod from "https://deno.land/x/zod@v3.22.4/mod.ts"; import { ZodError } from "https://deno.land/x/zod@v3.22.4/mod.ts"; import { API } from "shared/mod.ts"; import { stupidErrorAlert } from "shared/restSpec.ts"; -import { AdvancedImage, Box, Button, CenterV, DropAreaInput, DropDownInput, Empty, Grid, Horizontal, IconButton, Image, Label, MIcon, Spacer, StateHandler, TextInput, Validate, asState, createFilePicker, getErrorMessage } from "webgen/mod.ts"; +import { AdvancedImage, Box, Button, CenterV, DropAreaInput, DropDownInput, Empty, Grid, Horizontal, IconButton, Image, Label, MIcon, Spacer, TextInput, Validate, asState, createFilePicker, getErrorMessage } from "webgen/mod.ts"; import artwork from "../../../assets/img/template-artwork.png"; import genres from "../../../data/genres.json" with { type: "json" }; import language from "../../../data/language.json" with { type: "json" }; -import { Artist, DATE_PATTERN, artist, song, userString } from "../../../spec/music.ts"; +import { DATE_PATTERN, Drop, artist, song, userString } from "../../../spec/music.ts"; import { EditArtistsDialog, allowedImageFormats, getSecondary } from "../helper.ts"; import { uploadArtwork } from "./data.ts"; -export function ChangeDrop(drop: StateHandler<{ _id: string | undefined, title: string | undefined, release: string | undefined, language: string | undefined, artists: Artist[], artwork: string | undefined, compositionCopyright: string | undefined, soundRecordingCopyright: string | undefined, primaryGenre: string | undefined, secondaryGenre: string | undefined; }>) { +export function ChangeDrop(drop: Drop) { const state = asState({ artworkClientData: (drop.artwork ? { type: "direct", source: () => API.music.id(drop._id!).artwork().then(stupidErrorAlert) } : undefined), loading: false, @@ -18,7 +18,7 @@ export function ChangeDrop(drop: StateHandler<{ _id: string | undefined, title: }); const { data, error, validate } = Validate( - drop, + asState(drop), zod.object({ title: userString, artists: artist.array().refine(x => x.some(([ , , type ]) => type == "PRIMARY"), { message: "At least one primary artist is required" }), @@ -91,9 +91,7 @@ export function ChangeDrop(drop: StateHandler<{ _id: string | undefined, title: Grid( DropDownInput("Primary Genre", Object.keys(genres)) .sync(data, "primaryGenre") - .onChange(() => { - data.secondaryGenre = undefined!; - }), + .onChange(() => data.secondaryGenre = undefined!), data.$primaryGenre.map(primaryGenre => DropDownInput("Secondary Genre", getSecondary(genres, primaryGenre) ?? []) .sync(data, "secondaryGenre") .addClass("border-box") diff --git a/pages/_legacy/music/changeSongs.ts b/pages/_legacy/music/changeSongs.ts index da5e93a..ab7bf1e 100644 --- a/pages/_legacy/music/changeSongs.ts +++ b/pages/_legacy/music/changeSongs.ts @@ -1,65 +1,47 @@ import * as zod from "https://deno.land/x/zod@v3.22.4/mod.ts"; import { API } from "shared/mod.ts"; -import { Box, Button, CenterV, Empty, Grid, Horizontal, Label, Spacer, StateHandler, Validate, asState, createFilePicker, getErrorMessage } from "webgen/mod.ts"; -import { Artist, DATE_PATTERN, Song, artist, song, userString } from "../../../spec/music.ts"; +import { Box, Button, CenterV, Empty, Grid, Horizontal, Label, Spacer, Validate, asState, createFilePicker, getErrorMessage } from "webgen/mod.ts"; +import { Drop, song } from "../../../spec/music.ts"; import { allowedAudioFormats } from "../helper.ts"; import { uploadSongToDrop } from "./data.ts"; import { ManageSongs } from "./table.ts"; -export function ChangeSongs(drop: StateHandler<{ _id: string, songs: Song[], title: string | undefined, release: string | undefined, language: string | undefined, artists: Artist[], primaryGenre: string | undefined, secondaryGenre: string | undefined, }>) { +export function ChangeSongs(drop: Drop) { const state = asState({ uploadingSongs: [], validationState: undefined }); const { data, error, validate } = Validate( - drop, + asState(drop), zod.object({ - title: userString, - artists: artist.array().refine(x => x.some(([ , , type ]) => type == "PRIMARY"), { message: "At least one primary artist is required" }), - release: zod.string().regex(DATE_PATTERN, { message: "Not a date" }), - language: zod.string(), - primaryGenre: zod.string(), - secondaryGenre: zod.string(), - compositionCopyright: userString, - soundRecordingCopyright: userString, - artwork: zod.string(), songs: song.array().min(1, { message: "At least one song is required" }), }) ); return Grid( - [ - { width: 2 }, - Horizontal( - Box(state.$validationState.map(error => error ? CenterV( - Label(getErrorMessage(error)) - .addClass("error-message") - .setMargin("0 0.5rem 0 0") - ) - : Empty()).asRefComponent()), - Spacer(), - Button("Save") - .onClick(async () => { - const validation = validate(); - if (error.getValue()) return state.validationState = error.getValue(); - if (validation) await API.music.id(data._id!).update(validation); - location.reload(); // Handle this Smarter => Make it a Reload Event. - }) - ), - ], - [ - { width: 2 }, - ManageSongs(data), - ], - [ - { width: 2 }, - Horizontal( - Spacer(), - Button("Add a new Song") - .onClick(() => createFilePicker(allowedAudioFormats.join(",")).then(file => uploadSongToDrop(data, state.$uploadingSongs, file))) + Horizontal( + Box(state.$validationState.map(error => error ? CenterV( + Label(getErrorMessage(error)) + .addClass("error-message") + .setMargin("0 0.5rem 0 0") ) - ], + : Empty()).asRefComponent()), + Spacer(), + Button("Save") + .onClick(async () => { + const validation = validate(); + if (error.getValue()) return state.validationState = error.getValue(); + if (validation) await API.music.id(data._id!).update(validation); + location.reload(); // Handle this Smarter => Make it a Reload Event. + }) + ), + ManageSongs(data), + Horizontal( + Spacer(), + Button("Add a new Song") + .onClick(() => createFilePicker(allowedAudioFormats.join(",")).then(file => uploadSongToDrop(data, state.$uploadingSongs, file))) + ) ) .setGap("15px") .setPadding("15px 0 0 0"); diff --git a/pages/_legacy/music/data.ts b/pages/_legacy/music/data.ts index 866ebcc..9bb2b06 100644 --- a/pages/_legacy/music/data.ts +++ b/pages/_legacy/music/data.ts @@ -58,7 +58,8 @@ export function uploadSongToDrop(state: StateHandler<{ songs: Song[]; artists: A }, file); } -export function uploadArtwork(id: string, file: File, artworkClientData: Reference, loading: Reference, artwork: Reference) { +//is there a better way for those typings?? +export function uploadArtwork(id: string, file: File, artworkClientData: Reference, loading: Reference, artwork: Reference | Reference) { const blobUrl = URL.createObjectURL(file); artworkClientData.setValue({ type: "uploading", filename: file.name, blobUrl, percentage: 0 }); loading.setValue(true); diff --git a/pages/_legacy/music/edit.ts b/pages/_legacy/music/edit.ts index aca43af..06d7357 100644 --- a/pages/_legacy/music/edit.ts +++ b/pages/_legacy/music/edit.ts @@ -1,9 +1,9 @@ import { API, LoadingSpinner, Navigation, createActionList, createBreadcrumb, createTagList, stupidErrorAlert } from "shared/mod.ts"; -import { Body, Empty, Grid, Horizontal, Label, Spacer, Vertical, WebGen, asState, isMobile, ref } from "webgen/mod.ts"; +import { Body, Empty, Grid, Horizontal, Label, Spacer, Vertical, WebGen, asState, isMobile } from "webgen/mod.ts"; import '../../../assets/css/main.css'; import '../../../assets/css/music.css'; import { DynaNavigation } from "../../../components/nav.ts"; -import { Artist, Drop, DropType, Song } from "../../../spec/music.ts"; +import { Drop, DropType } from "../../../spec/music.ts"; import '../../hosting/views/table2.css'; import { DropTypeToText } from "../../music/views/list.ts"; import { RegisterAuthRefresh, changeThemeColor, permCheck, renewAccessTokenIfNeeded, saveBlob, sheetStack, showPreviewImage } from "../helper.ts"; @@ -26,29 +26,17 @@ if (!data.id) { } const state = asState({ - loaded: false, - _id: data.id, - title: undefined, - type: undefined, - release: undefined, - language: undefined, - artists: [], - primaryGenre: undefined, - secondaryGenre: undefined, - compositionCopyright: undefined, - soundRecordingCopyright: undefined, - artwork: undefined, - songs: [], + drop: undefined, }); sheetStack.setDefault(Vertical( DynaNavigation("Music"), - state.$loaded.map(loaded => loaded ? Navigation({ - title: ref`${state.$title}`, + state.$drop.map(drop => drop ? Navigation({ + title: drop.title, children: [ Horizontal( //TODO: Make this look better - Label(DropTypeToText(state.type!)).setTextSize("2xl"), + Label(DropTypeToText(drop.type)).setTextSize("2xl"), Spacer() ), { @@ -56,7 +44,7 @@ sheetStack.setDefault(Vertical( title: "Drop", subtitle: "Change Title, Release Date, ...", children: [ - ChangeDrop(state) + ChangeDrop(drop) ] }, { @@ -64,7 +52,7 @@ sheetStack.setDefault(Vertical( title: "Songs", subtitle: "Move Songs, Remove Songs, Add Songs, ...", children: [ - ChangeSongs(state), + ChangeSongs(drop), ] }, { @@ -72,37 +60,37 @@ sheetStack.setDefault(Vertical( title: "Export", subtitle: "Download your complete Drop with every Song", clickHandler: async () => { - const blob = await API.music.id(state._id).download().then(stupidErrorAlert); - saveBlob(blob, `${state.title}.tar`); + const blob = await API.music.id(drop._id).download().then(stupidErrorAlert); + saveBlob(blob, `${drop.title}.tar`); } }, - Permissions.canCancelReview(state.type!) ? + Permissions.canCancelReview(drop.type) ? { id: "cancel-review", title: "Cancel Review", subtitle: "Need to change Something? Cancel it now", clickHandler: async () => { - await API.music.id(state._id).type.post(DropType.Private); + await API.music.id(drop._id).type.post(DropType.Private); location.reload(); }, } : Empty(), - Permissions.canSubmit(state.type!) ? + Permissions.canSubmit(drop.type) ? { id: "publish", title: "Publish", subtitle: "Submit your Drop for Approval", clickHandler: async () => { - await API.music.id(state._id).type.post(DropType.UnderReview); + await API.music.id(drop._id).type.post(DropType.UnderReview); location.reload(); }, } : Empty(), - Permissions.canTakedown(state.type!) ? + Permissions.canTakedown(drop.type) ? { id: "takedown", title: "Takedown", subtitle: "Completely Takedown your Drop", clickHandler: async () => { - await API.music.id(state._id).type.post(DropType.Private); + await API.music.id(drop._id).type.post(DropType.Private); location.reload(); }, } : Empty() @@ -116,7 +104,7 @@ sheetStack.setDefault(Vertical( const list = Vertical( menu.path.map(x => x == "-/" ? Grid( - showPreviewImage({ _id: state._id, artwork: state.artwork }).addClass("image-preview") + showPreviewImage({ _id: drop._id, artwork: drop.artwork }).addClass("image-preview") ).setEvenColumns(1, "10rem") : Empty() ).asRefComponent(), @@ -137,7 +125,7 @@ Body(sheetStack); const Permissions = { canTakedown: (type: DropType) => type == "PUBLISHED", - canSubmit: (type: DropType) => ([ "UNSUBMITTED", "PRIVATE" ]).includes(type), + canSubmit: (type: DropType) => ([ "UNSUBMITTED", "PRIVATE" ]).includes(type), canEdit: (type: DropType) => (type == "PRIVATE" || type == "UNSUBMITTED") || permCheck("/bbn/manage/drops"), canCancelReview: (type: DropType) => type == "UNDER_REVIEW" }; @@ -145,18 +133,5 @@ const Permissions = { renewAccessTokenIfNeeded().then(async () => { await API.music.id(data.id).get() .then(stupidErrorAlert) - .then(drop => { - state.type = drop.type; - state.title = drop.title; - state.release = drop.release; - state.language = drop.language; - state.artists = asState(drop.artists ?? []); - state.primaryGenre = drop.primaryGenre; - state.secondaryGenre = drop.secondaryGenre; - state.compositionCopyright = drop.compositionCopyright; - state.soundRecordingCopyright = drop.soundRecordingCopyright; - state.artwork = drop.artwork; - state.songs = asState(drop.songs ?? []); - }) - .then(() => state.loaded = true); + .then(drop => state.drop = drop); }); \ No newline at end of file diff --git a/pages/_legacy/music/table.css b/pages/_legacy/music/table.css new file mode 100644 index 0000000..d2d1f23 --- /dev/null +++ b/pages/_legacy/music/table.css @@ -0,0 +1,27 @@ +.inverted-class .wgtable { + max-height: 25rem; + overflow: auto; + height: 100vh; + box-sizing: border-box; + gap: 8px; + background: white; + color: black; + padding: 0 16px; + border-radius: 8px; + --color-grayscaled-lightness: 0%; + --color-grayscaled-font: white; +} + +.inverted-class :not(.artists-list)>.grayscaled.wiconbutton { + color: hsl(var(--background-color-h), var(--background-color-s), var(--background-color-l)); + background: transparent; +} + +.artist-table .wgtable { + gap: 12px; + margin: 12px 0; +} + +.artist-table .wgtable .column { + gap: 12px; +} \ No newline at end of file diff --git a/pages/_legacy/music/table.ts b/pages/_legacy/music/table.ts index 75e3fd7..01d207f 100644 --- a/pages/_legacy/music/table.ts +++ b/pages/_legacy/music/table.ts @@ -5,6 +5,7 @@ import language from "../../../data/language.json" with { type: "json" }; import { Artist, Song } from "../../../spec/music.ts"; import { Table2 } from "../../hosting/views/table2.ts"; import { EditArtistsDialog, ProfilePicture, getSecondary, getYearList } from "../helper.ts"; +import "./table.css"; export function ManageSongs(state: StateHandler<{ songs: Song[]; primaryGenre: string | undefined; }>) { return new Table2(state.$songs) diff --git a/pages/_legacy/newDrop.ts b/pages/_legacy/newDrop.ts index cfe1ad6..b8e021b 100644 --- a/pages/_legacy/newDrop.ts +++ b/pages/_legacy/newDrop.ts @@ -48,7 +48,7 @@ API.music.id(dropId).get().then(stupidErrorAlert) const state = asState({ loaded: false, _id: dropId, - upc: undefined, + upc: undefined, title: undefined, release: undefined, language: undefined, diff --git a/pages/admin/dialog.ts b/pages/admin/dialog.ts index 404ecae..d8c42eb 100644 --- a/pages/admin/dialog.ts +++ b/pages/admin/dialog.ts @@ -1,20 +1,12 @@ import * as zod from "https://deno.land/x/zod@v3.22.4/mod.ts"; import { ZodError } from "https://deno.land/x/zod@v3.22.4/mod.ts"; import { API } from "shared/mod.ts"; -import { Box, Button, ButtonStyle, CenterV, Checkbox, Custom, Empty, Horizontal, Image, Label, SheetDialog, Spacer, Validate, Vertical, asState, createElement, getErrorMessage } from "webgen/mod.ts"; +import { Box, Button, ButtonStyle, CenterV, Checkbox, Custom, Empty, Horizontal, Image, Label, SheetDialog, Spacer, Validate, Vertical, asState, createElement, css, getErrorMessage } from "webgen/mod.ts"; import reviewTexts from "../../data/reviewTexts.json" with { type: "json" }; import { Drop, ReviewResponse } from "../../spec/music.ts"; import { sheetStack } from "../_legacy/helper.ts"; import { clientRender, dropPatternMatching, rawTemplate, render } from "./email.ts"; -function css(data: TemplateStringsArray, ...expr: string[]) { - const merge = data.map((x, i) => x + (expr[ i ] || '')); - - const style = new CSSStyleSheet(); - style.replaceSync(merge.join("")); - return style; -} - document.adoptedStyleSheets.push(css` .footer { gap: 0.5rem; @@ -162,7 +154,7 @@ export const DeclineDialog = SheetDialog(sheetStack, "Decline Drop", : Empty()).asRefComponent()), Spacer(), Button("Cancel").setStyle(ButtonStyle.Secondary).onClick(() => DeclineDialog.close()), - Button("Next").onClick(async () => { + Button("Next").onClick(() => { const { error, validate } = Validate( rejectState, zod.object({ diff --git a/pages/shared/restSpec.ts b/pages/shared/restSpec.ts index 908347e..c8b9f6c 100644 --- a/pages/shared/restSpec.ts +++ b/pages/shared/restSpec.ts @@ -463,7 +463,7 @@ export const API = { get: () => fetch(`${API.BASE_URL}music/drops/${id}`, { headers: headers(API.getToken()) }) - .then(json>()) + .then(json()) .catch(reject), update: (data: Partial) => fetch(`${API.BASE_URL}music/drops/${id}`, { method: "PATCH", diff --git a/spec/music.ts b/spec/music.ts index 90f6e16..ecae782 100644 --- a/spec/music.ts +++ b/spec/music.ts @@ -67,9 +67,9 @@ export const song = zod.object({ .refine(({ instrumental, explicit }) => !(instrumental && explicit), "Can't have an explicit instrumental song"); export const pureDrop = zod.object({ - upc: zod.string().nullish() - .transform(x => x?.trim()) - .transform(x => x?.length == 0 ? null : x) + upc: zod.string().nullable() + .transform(x => x ? x.trim() : x) + .transform(x => x ? x : null) .refine(x => x == null || [ 12, 13 ].includes(x.length), { message: "Not a valid UPC" }), title: userString, artists: artist.array().refine(x => x.some(([ , , type ]) => type == "PRIMARY"), { message: "At least one primary artist is required" }), @@ -92,9 +92,9 @@ export const drop = pureDrop })); const pageOne = zod.object({ - upc: zod.string().nullish() - .transform(x => x?.trim()) - .transform(x => x?.length == 0 ? null : x) + upc: zod.string().nullable() + .transform(x => x ? x.trim() : x) + .transform(x => x ? x : null) .refine(x => x == null || [ 12, 13 ].includes(x.length), { message: "Not a valid UPC" }) }); @@ -122,7 +122,7 @@ const pageFive = zod.object({ uploadingSongs: zod.array(zod.string()).max(0, { message: "Some uploads are still in progress" }), }); -export const pages = []>[ pageOne, pageTwo, pageThree, pageFour, pageFive ]; +export const pages = [ pageOne, pageTwo, pageThree, pageFour, pageFive ]; export const payout = zod.object({ _id: zod.string(),