From 7ee42afc8669d011181fc2ea6be3e360cad5f70e Mon Sep 17 00:00:00 2001 From: Schlauer-Hax Date: Mon, 7 Oct 2024 15:44:59 +0200 Subject: [PATCH] feat: share links --- pages/music/edit.ts | 70 ++++++++++++++++++++++++++++------------ pages/music/share.css | 28 ++++++++++++++++ pages/music/share.ts | 61 ++++++++++++++++++++++++++++++++++ pages/shared/helper.ts | 18 ++++++++++- pages/shared/restSpec.ts | 37 ++++++++++++++++++++- serve.ts | 1 + spec/music.ts | 1 + 7 files changed, 193 insertions(+), 23 deletions(-) create mode 100644 pages/music/share.css create mode 100644 pages/music/share.ts diff --git a/pages/music/edit.ts b/pages/music/edit.ts index 69e446c..65e8aeb 100644 --- a/pages/music/edit.ts +++ b/pages/music/edit.ts @@ -1,23 +1,14 @@ import { API, createActionList, createBreadcrumb, createTagList, LoadingSpinner, Navigation, stupidErrorAlert } from "shared/mod.ts"; -import { asRef, Body, Button, Empty, Grid, Image, ImageComponent, isMobile, Label, SheetDialog, Vertical, WebGen } from "webgen/mod.ts"; +import { asRef, Body, Button, ButtonStyle, Color, Empty, Grid, Horizontal, isMobile, Label, LinkButton, SheetDialog, Vertical, WebGen } from "webgen/mod.ts"; import "../../assets/css/main.css"; import "../../assets/css/music.css"; import { DynaNavigation } from "../../components/nav.ts"; -import { Drop, DropType } from "../../spec/music.ts"; -import { changeThemeColor, permCheck, RegisterAuthRefresh, renewAccessTokenIfNeeded, saveBlob, sheetStack, showPreviewImage } from "../shared/helper.ts"; +import { Drop, DropType, Share } from "../../spec/music.ts"; +import { changeThemeColor, permCheck, RegisterAuthRefresh, renewAccessTokenIfNeeded, saveBlob, sheetStack, showPreviewImage, streamingImages } from "../shared/helper.ts"; import { ChangeDrop } from "./views/changeDrop.ts"; import { ChangeSongs } from "./views/changeSongs.ts"; import { DropTypeToText } from "./views/list.ts"; -// @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" -import spotify from "../music-landing/assets/spotify.svg"; -// @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" -import deezer from "../music-landing/assets/deezer.svg"; -// @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" -import tidal from "../music-landing/assets/tidal.svg"; -// @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" -import apple from "../music-landing/assets/apple.svg"; - await RegisterAuthRefresh(); WebGen({ @@ -35,6 +26,7 @@ if (!data.id) { const drop = asRef( undefined); const services = asRef | undefined>(undefined); +const share = asRef( undefined); sheetStack.setDefault(Vertical( DynaNavigation("Music"), @@ -115,6 +107,17 @@ sheetStack.setDefault(Vertical( }, } : Empty(), + drop.type === "PUBLISHED" + ? { + id: "share", + title: "Sharing Link", + subtitle: "Show your music to all your listeners", + clickHandler: async () => { + share.setValue(await API.music.id(drop._id).share.get().then(stupidErrorAlert)); + SharingDialog.open(); + }, + } + : Empty(), ], }) .addClass( @@ -156,18 +159,11 @@ renewAccessTokenIfNeeded().then(async () => { .then((x) => drop.setValue(x)); }); -const streamingImages: Record = { - spotify: Image(spotify, "Spotify"), - deezer: Image(deezer, "Deezer"), - tidal: Image(tidal, "Tidal"), - apple: Image(apple, "Apple Music"), -}; - const StreamingServiesDialog = SheetDialog( sheetStack, "Streaming Services", - services.map((services) => services === undefined ? Empty() : - Vertical( + services.map((services) => + services === undefined ? Empty() : Vertical( ...Object.entries(services).map(([key, value]) => Button("Open in " + key[0].toUpperCase() + key.slice(1)) .onClick(() => globalThis.open(value, "_blank")) @@ -182,3 +178,35 @@ const StreamingServiesDialog = SheetDialog( ).setGap("0.5rem") ).asRefComponent(), ); + +const SharingDialog = SheetDialog( + sheetStack, + "Manage Sharing Link", + share.map((shareVal) => + shareVal + ? Vertical( + Label("Your Link:").setTextSize("xl").setCssStyle("color", shareVal.slug ? "" : "gray"), + LinkButton("bbn.music/" + (shareVal.slug ?? "xxx"), "https://bbn.music/" + (shareVal.slug ?? "xxx"), "_blank") + .addClass("link") + .setStyle(ButtonStyle.Inline).setColor(shareVal.slug ? Color.Colored : Color.Disabled), + Label("Services Found:").setTextSize("xl").setCssStyle("color", shareVal.slug ? "" : "gray"), + Horizontal( + ...Object.keys(shareVal.slug ? shareVal.services : { spotify: "", deezer: "", tidal: "", apple: "" }).map((img) => + streamingImages[img] + .setHeight("1.5rem") + .setWidth("1.5rem") + .setCssStyle("filter", shareVal.slug ? "brightness(0) invert(1)" : "brightness(0) invert(1) brightness(0.1)") + ), + ).setGap("1rem").setJustifyContent("start").setPadding("0 .3rem"), + Button(shareVal.slug ? "Disable Link Sharing" : "Enable Link Sharing").onPromiseClick(async () => { + if (shareVal.slug) { + await API.music.id(drop.getValue()!._id).share.remove(); + share.setValue({ slug: undefined }); + } else { + share.setValue(await API.music.id(drop.getValue()!._id).share.create().then(stupidErrorAlert)); + } + }).setMargin("1rem 0 0 0"), + ) + : Empty() + ).asRefComponent(), +); diff --git a/pages/music/share.css b/pages/music/share.css new file mode 100644 index 0000000..199298c --- /dev/null +++ b/pages/music/share.css @@ -0,0 +1,28 @@ +.bgImg { + filter: blur(20px) brightness(25%); + -webkit-filter: blur(20px) brightness(25%); + + + width: 100vw; + height: 100vh; + + + background-position: center; + background-repeat: no-repeat; + background-size: cover; + + overflow: hidden; + position: fixed; + top: 0; + left: 0; + z-index: -1; +} + +.share { + background-color: black; + z-index: 2; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} diff --git a/pages/music/share.ts b/pages/music/share.ts new file mode 100644 index 0000000..2a31f8d --- /dev/null +++ b/pages/music/share.ts @@ -0,0 +1,61 @@ +import { API, stupidErrorAlert } from "shared/restSpec.ts"; +import { asRef, Body, Empty, Horizontal, Image, Label, LinkButton, Vertical, WebGen } from "webgen/mod.ts"; +import { changeThemeColor, sheetStack, streamingImages } from "../shared/helper.ts"; +import "./share.css"; + +WebGen({ + events: { + themeChanged: changeThemeColor(), + }, +}); + +const params = new URLSearchParams(location.search); +const data = Object.fromEntries(params.entries()); +if (!data.s) { + alert("slug is missing"); + location.href = "https://bbn.one/"; +} + +const share = asRef( + ; + title: string; + artistNames: string[]; + artwork: string; + }> undefined, +); + +share.setValue(await API.music.share(data.s).get().then(stupidErrorAlert)); + +sheetStack.setDefault(Vertical( + Image({ type: "direct", source: () => API.music.share(data.s).artwork().then(stupidErrorAlert) }, "Background").addClass("bgImg"), + share.map((shareVal) => + shareVal + ? Horizontal( + Vertical( + Image({ type: "direct", source: () => API.music.share(data.s).artwork().then(stupidErrorAlert) }, "A Song Artwork") + .setMinHeight("250px").setMinWidth("250px").setBorderRadius("mid"), + Label(shareVal.title).setTextAlign("center").setTextSize("2xl").setMargin("0 10px 0 0"), + Label(shareVal.artistNames.join(", ")).setTextAlign("center"), + Vertical( + ...Object.entries(shareVal.services).map(([key, val]) => + LinkButton( + Horizontal( + streamingImages[key] + .setHeight("1.5rem") + .setWidth("1.5rem").setMargin("0 10px 0 0"), + Label(key[0].toUpperCase() + key.slice(1)).setTextSize("xl"), + ), + val, + "_blank", + ) + ), + ).setGap("0.5rem").setMargin("10px 0 0 0"), + Label("Powered by BBN Music").setTextAlign("center").setMargin("10px 0 0 0"), + ).addClass("share").setPadding("1rem").setBorderRadius("mid"), + ) + : Empty() + ).asRefComponent(), +)); + +Body(sheetStack); diff --git a/pages/shared/helper.ts b/pages/shared/helper.ts index c3a9ce0..e3641e4 100644 --- a/pages/shared/helper.ts +++ b/pages/shared/helper.ts @@ -1,9 +1,18 @@ import { API, fileCache, Permission, stupidErrorAlert } from "shared/mod.ts"; -import { asState, Box, Cache, CenterV, Component, Custom, Horizontal, Image, Label, SheetsStack, Spacer, Style, SupportedThemes } from "webgen/mod.ts"; +import { asState, Box, Cache, CenterV, Component, Custom, Horizontal, Image, ImageComponent, Label, SheetsStack, Spacer, Style, SupportedThemes } from "webgen/mod.ts"; import { templateArtwork } from "../../assets/imports.ts"; import { loginRequired } from "../../components/pages.ts"; import { Drop } from "../../spec/music.ts"; +// @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" +import spotify from "../music-landing/assets/spotify.svg"; +// @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" +import deezer from "../music-landing/assets/deezer.svg"; +// @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" +import tidal from "../music-landing/assets/tidal.svg"; +// @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" +import apple from "../music-landing/assets/apple.svg"; + export const allowedAudioFormats = ["audio/flac", "audio/wav", "audio/mp3"]; export const allowedImageFormats = ["image/png", "image/jpeg"]; @@ -248,3 +257,10 @@ export function showProfilePicture(x: ProfileData) { x.profile.username, ); } + +export const streamingImages: Record = { + spotify: Image(spotify, "Spotify"), + deezer: Image(deezer, "Deezer"), + tidal: Image(tidal, "Tidal"), + apple: Image(apple, "Apple Music"), +}; diff --git a/pages/shared/restSpec.ts b/pages/shared/restSpec.ts index 4a8176c..dfca67c 100644 --- a/pages/shared/restSpec.ts +++ b/pages/shared/restSpec.ts @@ -1,4 +1,4 @@ -import { Artist, BugReport, Drop, DropType, Group, Meta, OAuthApp, Payout, RequestPayoutResponse, Server, ServerAudit, ServerCreate, ServerTypes, Song, StoreItems, Wallet } from "../../spec/music.ts"; +import { Artist, BugReport, Drop, DropType, Group, Meta, OAuthApp, Payout, RequestPayoutResponse, Server, ServerAudit, ServerCreate, ServerTypes, Share, Song, StoreItems, Wallet } from "../../spec/music.ts"; import { SearchResult } from "../admin/state.ts"; import { ProfileData } from "./helper.ts"; @@ -504,6 +504,26 @@ export const API = { .catch(reject), }, id: (id: string) => ({ + share: { + get: () => + fetch(`${API.BASE_URL}music/drops/share/${id}`, { + headers: headers(API.getToken()), + }).then(json()) + .catch(reject), + create: () => + fetch(`${API.BASE_URL}music/drops/share`, { + headers: headers(API.getToken()), + method: "POST", + body: JSON.stringify({ + id, + }), + }).then(json()).catch(reject), + remove: () => + fetch(`${API.BASE_URL}music/drops/share/${id}`, { + headers: headers(API.getToken()), + method: "DELETE", + }).then(none()).catch(reject), + }, get: () => fetch(`${API.BASE_URL}music/drops/${id}`, { headers: headers(API.getToken()), @@ -556,6 +576,21 @@ export const API = { .then(json<{ spotify: string }>()) .catch(reject), }), + share: (slug: string) => ({ + get: () => + fetch(`${API.BASE_URL}music/share/${slug}`) + .then(json<{ + services: Record; + title: string; + artistNames: string[]; + artwork: string; + }>()) + .catch(reject), + artwork: () => + fetch(`${API.BASE_URL}music/share/${slug}/artwork`) + .then(blob()) + .catch(reject), + }), }), }; diff --git a/serve.ts b/serve.ts index 05f702e..d6357dd 100644 --- a/serve.ts +++ b/serve.ts @@ -69,6 +69,7 @@ serve({ "admin": "./pages/admin/admin.ts", "admin/review": "./pages/admin/review.ts", "wallet": "./pages/wallet/wallet.ts", + "share": "./pages/music/share.ts", }, defaultTemplate: createTemplate, poylfills: [ diff --git a/spec/music.ts b/spec/music.ts index 82e32d7..243ec56 100644 --- a/spec/music.ts +++ b/spec/music.ts @@ -654,3 +654,4 @@ export type StoreItems = zod.infer; export type Transcript = zod.infer; export type Wallet = zod.infer; export type SidecarFile = zod.infer; +export type Share = zod.infer;