wip: plumbing for building a web app
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
import { useDownloadUrl } from "@/hooks/use-download-url";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useSuspendPlayback } from "@/hooks/use-suspend-playback";
|
||||
import { platform } from "@/lib/platform";
|
||||
|
||||
export interface AttachmentItem {
|
||||
id: string;
|
||||
@@ -95,7 +96,7 @@ export function AttachmentLightbox({
|
||||
|
||||
const handleDownload = () => {
|
||||
if (!current || !url || current.source.kind !== "remote") return;
|
||||
window.electronAttachment.download(url, current.filename);
|
||||
platform.attachment.download(url, current.filename);
|
||||
};
|
||||
|
||||
const handleRemove = () => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Label } from "@/components/ui/label";
|
||||
import { H3, Muted } from "@/components/ui/typography";
|
||||
import { PRIVACY_URL, TERMS_URL } from "@/lib/constants";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import { platform } from "@/lib/platform";
|
||||
|
||||
interface EmailStepProps {
|
||||
onCodeSent: (email: string) => void;
|
||||
@@ -62,7 +63,7 @@ export function EmailStep({ onCodeSent }: EmailStepProps) {
|
||||
By continuing, you agree to our{" "}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => window.electronLink.openExternal(TERMS_URL)}
|
||||
onClick={() => platform.link.openExternal(TERMS_URL)}
|
||||
className="underline underline-offset-2 hover:text-foreground"
|
||||
>
|
||||
Terms of Service
|
||||
@@ -70,7 +71,7 @@ export function EmailStep({ onCodeSent }: EmailStepProps) {
|
||||
and{" "}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => window.electronLink.openExternal(PRIVACY_URL)}
|
||||
onClick={() => platform.link.openExternal(PRIVACY_URL)}
|
||||
className="underline underline-offset-2 hover:text-foreground"
|
||||
>
|
||||
Privacy Policy
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
getAttachmentHandler,
|
||||
type AttachmentItem,
|
||||
} from "@/features/attachments/attachment-lightbox";
|
||||
import { platform } from "@/lib/platform";
|
||||
|
||||
export interface PendingAttachment {
|
||||
id: string;
|
||||
@@ -120,7 +121,7 @@ function LinkPreviewThumbnail({ entry }: { entry: LinkPreviewEntry }) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => window.electronLink.openExternal(metadata.url)}
|
||||
onClick={() => platform.link.openExternal(metadata.url)}
|
||||
className="flex h-16 w-28 shrink-0 flex-col justify-center gap-1 overflow-hidden rounded-lg bg-white/10 px-2 py-1.5 text-left transition-colors hover:bg-white/15"
|
||||
>
|
||||
<div className="flex items-center gap-1 text-[10px] text-white/40">
|
||||
|
||||
@@ -23,6 +23,8 @@ import { MAX_ATTACHMENT_SIZE_BYTES, MAX_ATTACHMENTS } from "@/lib/constants";
|
||||
import type { PendingAttachment } from "@/features/compose/attachment-strip";
|
||||
import { useSuspendPlayback } from "@/hooks/use-suspend-playback";
|
||||
import { useComposeIntentStore } from "@/stores/compose-intent-store";
|
||||
import { platform } from "@/lib/platform";
|
||||
import { requireDesktop } from "@/lib/platform/desktop-only";
|
||||
|
||||
export type ComposeStep = "idle" | "picking" | "recording" | "reviewing" | "typing" | "configuring" | "submitting";
|
||||
|
||||
@@ -512,6 +514,7 @@ export function ComposeOverlay({
|
||||
} else if (e.key === "s" || e.key === "S") {
|
||||
e.preventDefault();
|
||||
if (!guardIdle()) break;
|
||||
if (!requireDesktop("Screen recording")) break;
|
||||
setRecordingSource("screen");
|
||||
setStepSync("picking");
|
||||
} else if (e.key === "t" || e.key === "T") {
|
||||
@@ -594,7 +597,7 @@ export function ComposeOverlay({
|
||||
<ScreenSourcePicker
|
||||
title="Record your screen"
|
||||
confirmLabel="Record"
|
||||
getSources={window.electronScreen.getScreenSources}
|
||||
getSources={platform.screenRecord.getScreenSources}
|
||||
onSelect={handleScreenSourceSelected}
|
||||
onCancel={cancel}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { platform } from "@/lib/platform";
|
||||
import { requireDesktop } from "@/lib/platform/desktop-only";
|
||||
|
||||
const VIDEO_PREFERRED_MIME = "video/webm;codecs=vp9,opus";
|
||||
const VIDEO_FALLBACK_MIME = "video/webm";
|
||||
@@ -75,6 +77,7 @@ export function useScreenRecorder({
|
||||
|
||||
const startRecording = useCallback(
|
||||
async (sourceId: string) => {
|
||||
if (!requireDesktop("Screen recording")) return;
|
||||
try {
|
||||
// 1. Screen video
|
||||
const screenStream = await navigator.mediaDevices.getUserMedia({
|
||||
@@ -113,7 +116,7 @@ export function useScreenRecorder({
|
||||
const durationMs = Date.now() - startTimeRef.current;
|
||||
const blob = new Blob(chunksRef.current, { type: mime });
|
||||
stopAllTracks();
|
||||
window.electronScreen.stopRecordingWindow();
|
||||
platform.screenRecord.cancel();
|
||||
|
||||
if (blob.size > 0) {
|
||||
onFinishRef.current(blob, durationMs, mime);
|
||||
@@ -123,17 +126,17 @@ export function useScreenRecorder({
|
||||
recorder.start(1000);
|
||||
|
||||
// 4. Show floating control window
|
||||
window.electronScreen.startRecordingWindow();
|
||||
platform.screenRecord.start();
|
||||
|
||||
// 5. Listen for stop from floating window
|
||||
cleanupIpcRef.current = window.electronScreen.onStopRequested(() => {
|
||||
cleanupIpcRef.current = platform.screenRecord.onStopRequested(() => {
|
||||
if (recorderRef.current?.state === "recording") {
|
||||
recorderRef.current.stop();
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
stopAllTracks();
|
||||
window.electronScreen.stopRecordingWindow();
|
||||
platform.screenRecord.cancel();
|
||||
onErrorRef.current(
|
||||
err instanceof Error ? err.message : "Failed to start screen recording",
|
||||
);
|
||||
@@ -157,14 +160,14 @@ export function useScreenRecorder({
|
||||
}
|
||||
}
|
||||
stopAllTracks();
|
||||
window.electronScreen.stopRecordingWindow();
|
||||
platform.screenRecord.cancel();
|
||||
}, [stopAllTracks]);
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
stopAllTracks();
|
||||
window.electronScreen.stopRecordingWindow();
|
||||
platform.screenRecord.cancel();
|
||||
};
|
||||
}, [stopAllTracks]);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { useNetworkUsage } from "@/hooks/use-network-usage";
|
||||
import { useIsNetworkAdmin } from "@/hooks/use-networks";
|
||||
import type { BillingCadence, BillingStatus } from "@/api/types";
|
||||
import { platform } from "@/lib/platform";
|
||||
|
||||
function formatCents(cents: number): string {
|
||||
if (cents % 100 === 0) return `$${cents / 100}`;
|
||||
@@ -171,7 +172,7 @@ function FreeBilling({
|
||||
|
||||
const handleUpgrade = () => {
|
||||
createCheckout.mutate(cadence, {
|
||||
onSuccess: ({ url }) => window.electronLink.openExternal(url),
|
||||
onSuccess: ({ url }) => platform.link.openExternal(url),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -228,7 +229,7 @@ function ProBilling({
|
||||
|
||||
const handleManage = () => {
|
||||
createPortal.mutate(undefined, {
|
||||
onSuccess: ({ url }) => window.electronLink.openExternal(url),
|
||||
onSuccess: ({ url }) => platform.link.openExternal(url),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
getAttachmentHandler,
|
||||
type AttachmentItem,
|
||||
} from "@/features/attachments/attachment-lightbox";
|
||||
import { platform } from "@/lib/platform";
|
||||
|
||||
type FileParticle = Extract<Particle, { type: "file" }>;
|
||||
|
||||
@@ -46,7 +47,7 @@ function openParticle(
|
||||
if (getAttachmentHandler(particle.properties.mime_type) === "lightbox") {
|
||||
onPreview(index);
|
||||
} else if (url) {
|
||||
window.electronLink.openExternal(url);
|
||||
platform.link.openExternal(url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +66,7 @@ function ImageAttachment({
|
||||
|
||||
const handleDownload = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
window.electronAttachment.download(url, particle.properties.filename);
|
||||
platform.attachment.download(url, particle.properties.filename);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -113,7 +114,7 @@ function FileAttachment({
|
||||
const handleDownload = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (!url) return;
|
||||
window.electronAttachment.download(url, particle.properties.filename);
|
||||
platform.attachment.download(url, particle.properties.filename);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -25,6 +25,8 @@ import { WindowControls } from "@/components/window-controls";
|
||||
import { RelativeTimestamp } from "@/components/relative-timestamp";
|
||||
import { useStreamPresence } from "@/features/particles/stream-presence-context";
|
||||
import { resolveHumanDisplay } from "@/lib/humans";
|
||||
import { platform } from "@/lib/platform";
|
||||
import { requireDesktop } from "@/lib/platform/desktop-only";
|
||||
|
||||
function getParticleDisplayName(particle: Particle): string {
|
||||
switch (particle.type) {
|
||||
@@ -71,8 +73,9 @@ export function TopBar({ networkId, particle, streamParticle }: TopBarProps) {
|
||||
const hasActiveHuddle = huddleParticipants.length > 0;
|
||||
|
||||
const handleJoinHuddle = () => {
|
||||
if (!requireDesktop("Huddle")) return;
|
||||
apiClient.getLivekitToken(networkId, streamParticle.id).then(({ token, server_url }) => {
|
||||
window.electronWindow.openHuddle({ token, serverUrl: server_url });
|
||||
platform.huddle.open({ token, serverUrl: server_url });
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ import { usePlaybackPauseStore, selectIsPaused } from "@/stores/playback-pause-s
|
||||
import { usePlaybackKeys } from "@/hooks/use-playback-keys";
|
||||
import { useStreamNavigationKeys } from "@/hooks/use-stream-navigation-keys";
|
||||
import { useStreamActionKeys } from "@/hooks/use-stream-action-keys";
|
||||
import { c } from "vite/dist/node/types.d-aGj9QkWt";
|
||||
import { platform } from "@/lib/platform";
|
||||
import { requireDesktop } from "@/lib/platform/desktop-only";
|
||||
|
||||
function getReactions(particle: Particle): Record<string, string[]> | undefined {
|
||||
if (isParticleDeleted(particle)) return undefined;
|
||||
@@ -150,7 +151,7 @@ function StreamViewInner({ path, streamParticle }: StreamViewProps) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useMount(() => {
|
||||
window.electronAutoplay.dismiss();
|
||||
platform.autoplay.dismiss();
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -220,8 +221,9 @@ function StreamViewInner({ path, streamParticle }: StreamViewProps) {
|
||||
});
|
||||
|
||||
const handleOpenHuddle = useCallback(() => {
|
||||
if (!requireDesktop("Huddle")) return;
|
||||
apiClient.getLivekitToken(networkId, streamParticle.id).then(({ token, server_url }) => {
|
||||
window.electronWindow.openHuddle({ token, serverUrl: server_url });
|
||||
platform.huddle.open({ token, serverUrl: server_url });
|
||||
});
|
||||
navigate(`/${networkId}`);
|
||||
}, [networkId, streamParticle.id, navigate]);
|
||||
|
||||
@@ -16,6 +16,7 @@ import { logError, toUserMessage } from "@/lib/errors";
|
||||
import { toast } from "sonner";
|
||||
import { PRIVACY_URL, SUPPORT_EMAIL, TERMS_URL } from "@/lib/constants";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { platform } from "@/lib/platform";
|
||||
|
||||
interface SettingsRowProps {
|
||||
icon: React.ReactNode;
|
||||
@@ -79,7 +80,7 @@ export default function SettingsPage() {
|
||||
const [version, setVersion] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
window.electronApp.getVersion().then(setVersion);
|
||||
platform.app.getVersion().then(setVersion);
|
||||
}, []);
|
||||
|
||||
const handleToggleEmailNotifications = async (checked: boolean) => {
|
||||
@@ -190,12 +191,12 @@ export default function SettingsPage() {
|
||||
<SettingsRow
|
||||
icon={<Shield className="size-4" />}
|
||||
label="Privacy Policy"
|
||||
onClick={() => window.electronLink.openExternal(PRIVACY_URL)}
|
||||
onClick={() => platform.link.openExternal(PRIVACY_URL)}
|
||||
/>
|
||||
<SettingsRow
|
||||
icon={<FileText className="size-4" />}
|
||||
label="Terms of Service"
|
||||
onClick={() => window.electronLink.openExternal(TERMS_URL)}
|
||||
onClick={() => platform.link.openExternal(TERMS_URL)}
|
||||
/>
|
||||
</SettingsGroup>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user