feat: ship a web app (#218)
* feat(orion): add endpoint for link metadata * refactor: cleanup comments * wip: plumbing for building a web app * setup deployment materials for web app * fix(web): favicon
This commit was merged in pull request #218.
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { useAutoplayPayloadStore } from "@/stores/autoplay-payload-store";
|
||||
import type { AutoplayPayload } from "@/lib/autoplay-ipc";
|
||||
import { apiClient } from "@/api/client";
|
||||
import type { Platform } from "./types";
|
||||
|
||||
declare const __APP_VERSION__: string;
|
||||
|
||||
function detectWebPlatform(): "darwin" | "win32" | "linux" | "web" {
|
||||
if (typeof navigator === "undefined") return "web";
|
||||
const ua = navigator.userAgent;
|
||||
if (/Mac|iPhone|iPad|iPod/i.test(ua)) return "darwin";
|
||||
if (/Win/i.test(ua)) return "win32";
|
||||
if (/Linux|X11/i.test(ua)) return "linux";
|
||||
return "web";
|
||||
}
|
||||
|
||||
function downloadCrossOrigin(url: string, filename?: string) {
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
if (filename) a.download = filename;
|
||||
a.target = "_blank";
|
||||
a.rel = "noopener noreferrer";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
const baseTitle = typeof document !== "undefined" ? document.title : "llink";
|
||||
|
||||
function applyDockBadge(count: number) {
|
||||
if (typeof document === "undefined") return;
|
||||
document.title = count > 0 ? `(${count}) ${baseTitle}` : baseTitle;
|
||||
}
|
||||
|
||||
const NOT_SUPPORTED = "Not supported in the web app";
|
||||
|
||||
export const webPlatform: Platform = {
|
||||
kind: "web",
|
||||
|
||||
window: {
|
||||
minimize: () => {},
|
||||
maximize: () => {},
|
||||
fullscreen: () => {},
|
||||
close: () => {},
|
||||
onMaximizeChange: () => () => {},
|
||||
platform: detectWebPlatform(),
|
||||
},
|
||||
|
||||
huddle: {
|
||||
isSupported: false,
|
||||
open: () => { throw new Error(NOT_SUPPORTED); },
|
||||
close: () => {},
|
||||
getScreenSources: async () => { throw new Error(NOT_SUPPORTED); },
|
||||
},
|
||||
|
||||
screenRecord: {
|
||||
isSupported: false,
|
||||
start: () => { throw new Error(NOT_SUPPORTED); },
|
||||
stop: () => {},
|
||||
cancel: () => {},
|
||||
onStopRequested: () => () => {},
|
||||
getScreenSources: async () => { throw new Error(NOT_SUPPORTED); },
|
||||
},
|
||||
|
||||
autoplay: {
|
||||
play: (payload: AutoplayPayload) => {
|
||||
useAutoplayPayloadStore.getState().setPayload(payload);
|
||||
},
|
||||
dismiss: () => {
|
||||
useAutoplayPayloadStore.getState().setPayload(null);
|
||||
},
|
||||
navigate: (d) => {
|
||||
useAutoplayPayloadStore.getState().setPayload(null);
|
||||
useAutoplayPayloadStore.getState().setPendingNav(d);
|
||||
},
|
||||
onPlay: (cb) =>
|
||||
useAutoplayPayloadStore.subscribe((state, prev) => {
|
||||
if (state.payload && state.payload !== prev.payload) cb(state.payload);
|
||||
}),
|
||||
onStop: (cb) =>
|
||||
useAutoplayPayloadStore.subscribe((state, prev) => {
|
||||
if (!state.payload && prev.payload) cb();
|
||||
}),
|
||||
onNavigate: (cb) =>
|
||||
useAutoplayPayloadStore.subscribe((state, prev) => {
|
||||
if (state.pendingNav && state.pendingNav !== prev.pendingNav) {
|
||||
cb({ networkId: state.pendingNav.networkId, streamId: state.pendingNav.streamId });
|
||||
}
|
||||
}),
|
||||
},
|
||||
|
||||
link: {
|
||||
fetchMetadata: (url) => apiClient.getLinkMetadata(url).catch(() => null),
|
||||
openExternal: async (url) => {
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
},
|
||||
},
|
||||
|
||||
attachment: {
|
||||
download: downloadCrossOrigin,
|
||||
},
|
||||
|
||||
app: {
|
||||
setDockBadge: applyDockBadge,
|
||||
getVersion: async () => __APP_VERSION__,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user