Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Edit video and audio like you edit text — fully offline, on your device.

**✨ Try it in the browser [wassgha.github.io/rescript](https://wassgha.github.io/rescript/)** or download the [Desktop App](#download)

[![GitHub](https://img.shields.io/badge/GitHub-wassgha%2Frescript-181717?logo=github&logoColor=white)](https://github.com/wassgha/rescript)
[![Join the Discord](https://img.shields.io/badge/Discord-Join%20the%20server-5865F2?logo=discord&logoColor=white)](https://discord.gg/qJAhYFydat)
[![Follow @wassgha on X](https://img.shields.io/badge/Follow%20@wassgha-000000?logo=x&logoColor=white)](https://x.com/wassgha)

[![Rescript Demo](./screenshots/rescript.png)](https://wassgha.github.io/rescript/)
Expand Down Expand Up @@ -115,4 +117,5 @@ MIT

---

Built by [@wassgha](https://x.com/wassgha) — follow along on X for updates.
Built by [@wassgha](https://x.com/wassgha) — follow along on X for updates, or
come say hi in the [Discord](https://discord.gg/RCv4gcwE4).
4 changes: 2 additions & 2 deletions components/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import TranscriptPanel from "./TranscriptPanel";
import MediaPreview from "./MediaPreview";
import Timeline from "./Timeline";
import ExportDialog from "./ExportDialog";
import GitHubLink from "./GitHubLink";
import SocialLinks from "./SocialLinks";
import { Download, Redo2, Undo2 } from "lucide-react";
import { ModelOption, ModelOptionSeparator } from "./ModelSelector";
import ModelSelector from "./ModelSelector";
Expand Down Expand Up @@ -145,7 +145,7 @@ export default function Editor() {
) : (
<>
<TopBar>
<GitHubLink />
<SocialLinks />
<button
onClick={undo}
disabled={!canUndo}
Expand Down
49 changes: 0 additions & 49 deletions components/GitHubLink.tsx

This file was deleted.

110 changes: 110 additions & 0 deletions components/SocialLinks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
export const GITHUB_REPO_URL = "https://github.com/wassgha/rescript";
export const DISCORD_INVITE_URL = "https://discord.gg/qJAhYFydat";
export const X_PROFILE_URL = "https://x.com/wassgha";

function GitHubIcon({ size }: { size: number }) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden
>
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.269 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.295 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2Z" />
</svg>
);
}

function DiscordIcon({ size }: { size: number }) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden
>
<path d="M20.317 4.369a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.369a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128c.126-.094.252-.192.372-.291a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.061 0a.074.074 0 0 1 .078.009c.12.099.246.198.373.292a.077.077 0 0 1-.006.127 12.3 12.3 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03ZM8.02 15.331c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418Zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418Z" />
</svg>
);
}

function XIcon({ size }: { size: number }) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden
>
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231 5.45-6.231Zm-1.161 17.52h1.833L7.084 4.126H5.117L17.083 19.77Z" />
</svg>
);
}

const LINKS = [
{
label: "GitHub",
href: GITHUB_REPO_URL,
ariaLabel: "GitHub repository",
Icon: GitHubIcon,
},
{
label: "Discord",
href: DISCORD_INVITE_URL,
ariaLabel: "Discord server",
Icon: DiscordIcon,
},
{
label: "X",
href: X_PROFILE_URL,
ariaLabel: "X profile",
Icon: XIcon,
},
];

/** Compact links to the repo, Discord, and X — icons for the editor bar, labeled for the upload footer. */
export default function SocialLinks({
variant = "icon",
}: {
variant?: "icon" | "text";
}) {
if (variant === "text") {
return (
<div className="flex items-center gap-3">
{LINKS.map(({ label, href, Icon }) => (
<a
key={label}
href={href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-xs text-zinc-400 transition hover:text-zinc-600"
>
<Icon size={12} />
{label}
</a>
))}
</div>
);
}

return (
<>
{LINKS.map(({ label, href, ariaLabel, Icon }) => (
<a
key={label}
href={href}
target="_blank"
rel="noopener noreferrer"
title={label}
aria-label={ariaLabel}
className="flex h-8 w-8 items-center justify-center rounded-lg text-zinc-500 transition hover:bg-zinc-100 hover:text-zinc-800"
>
<Icon size={16} />
</a>
))}
</>
);
}
4 changes: 2 additions & 2 deletions components/UploadScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
Type,
} from "lucide-react";
import logo from "@/assets/logo.png";
import GitHubLink from "./GitHubLink";
import SocialLinks from "./SocialLinks";
import ModelSelector, {
ModelOption,
ModelOptionSeparator,
Expand Down Expand Up @@ -381,7 +381,7 @@ export default function UploadScreen({
<Lock size={12} />
No uploads, no accounts — your media never leaves this device.
</p>
<GitHubLink variant="text" />
<SocialLinks variant="text" />
</div>}
</div>
</div>
Expand Down
28 changes: 14 additions & 14 deletions hooks/useWindowChrome.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useEffect, useState } from "react";
import { useEffect, useState, useSyncExternalStore } from "react";

export interface WindowChrome {
/** The native title bar is hidden — the page must supply its own drag region. */
Expand All @@ -9,14 +9,26 @@ export interface WindowChrome {
trafficLights: boolean;
}

function subscribeFocus(onChange: () => void) {
window.addEventListener("focus", onChange);
window.addEventListener("blur", onChange);
return () => {
window.removeEventListener("focus", onChange);
window.removeEventListener("blur", onChange);
};
}

const isFocused = () => document.hasFocus();
const isFocusedOnServer = () => true;

/**
* Describes how much window chrome the page is responsible for drawing. In a
* browser tab this is all false; in the Electron shell on macOS the title bar
* is hidden (see electron/main.ts) so the top bar doubles as the drag handle.
*/
export function useWindowChrome(): WindowChrome {
const [fullScreen, setFullScreen] = useState(false);
const [focused, setFocused] = useState(true);
const focused = useSyncExternalStore(subscribeFocus, isFocused, isFocusedOnServer);

useEffect(() => {
const desktop = window.rescriptDesktop;
Expand All @@ -33,18 +45,6 @@ export function useWindowChrome(): WindowChrome {
};
}, []);

useEffect(() => {
setFocused(document.hasFocus());
const onFocus = () => setFocused(true);
const onBlur = () => setFocused(false);
window.addEventListener("focus", onFocus);
window.addEventListener("blur", onBlur);
return () => {
window.removeEventListener("focus", onFocus);
window.removeEventListener("blur", onBlur);
};
}, []);

// Safe to read during render: every consumer lives under the client-only
// (ssr: false) Editor, so there is no server pass to mismatch against.
const draggable =
Expand Down
Loading