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
32 changes: 31 additions & 1 deletion app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { listen } from "@tauri-apps/api/event";
import { getCurrentWebview } from "@tauri-apps/api/webview";
import {
Expand Down Expand Up @@ -30,6 +30,7 @@ import { Vault } from "./screens/Vault";
import { Scanning } from "./screens/Scanning";
import { Settings } from "./screens/Settings";
import { Setup, SetupOffer } from "./screens/Setup";
import { ShellContext, type Shell } from "./shell";
import { applyFirstRun, FirstRun, finishLanIfPending, type FirstRunAnswers } from "./FirstRun";
import { resetTour, Tour, tourPending } from "./Tour";
import { Banner, Button, Loading, Mark, NavTab } from "./ui";
Expand Down Expand Up @@ -218,7 +219,35 @@ export default function App() {

const canCompare = !!view && view.golden_version !== null && view.machines.length > 0;

// For parts deep in a screen (the files guide): the Link dialog, tabs, and
// whether this is the Primary Rig's machine.
const isPrimary = !!view?.machines.some((m) => m.golden_source && m.is_this_machine);
const thisId = overview?.machine?.id ?? null;
const ownPlan = canCompare && !!thisId && !isPrimary;
const shell = useMemo<Shell>(
() => ({
linked,
isPrimary,
link: () => setLinking("choose"),
setUpSync: () => {
setTab("rig");
setSyncSetupAt(Date.now());
},
settings: () => setTab("settings"),
upToSpeed:
ownPlan && thisId
? () => {
setFocus({ id: thisId, plan: true, at: Date.now() });
setCompareId(thisId);
setTab("compare");
}
: null,
}),
[linked, isPrimary, ownPlan, thisId],
);

return (
<ShellContext.Provider value={shell}>
<main className="flex h-full flex-col bg-canvas text-fg antialiased">
{overview?.machine && !scanning && !updateScan && !replay && (
<nav className="flex h-10 shrink-0 items-center gap-0.5 border-b border-line bg-panel px-2">
Expand Down Expand Up @@ -353,5 +382,6 @@ export default function App() {
/>
)}
</main>
</ShellContext.Provider>
);
}
6 changes: 3 additions & 3 deletions app/src/Backup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -637,8 +637,8 @@ export function SyncHint() {
return (
<>
Which cloud service do you use? Pick the same one on each machine. New and changed templates, scripts and presets
go through a <span className="font-medium">Patch Pilot Backup</span> folder there and land on your other machines
on their own.
go through a <span className="font-medium">Patch Pilot Backup</span> folder there and land on your linked
machines on their own.
</>
);
}
Expand Down Expand Up @@ -993,7 +993,7 @@ function restoreSummary(r: RestoreReport): string {
}

/** "Dropbox", or the folder's name. */
function placeName(view: BackupView): string {
export function placeName(view: BackupView): string {
if (view.github) return "GitHub";
if (view.lan) return "your home network";
const cloud = view.clouds.find((c) => c.path === view.settings.folder);
Expand Down
11 changes: 9 additions & 2 deletions app/src/BringOver.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react
import { bringFiles, fileKey, filesToBring, ignoreItems, type BringResult, type BringView, type FileToBring } from "./api";
import { bringSummary, isReaperScript, isTemplate, Needs } from "./bring";
import { IgnoreButton, IgnoredToggle, pathRules } from "./Ignore";
import { useShell } from "./shell";
import { Badge, Button, buildTree, Card, Folder, FolderTree, isConflictCopy, Tile, treeItems, useFolders } from "./ui";

/**
Expand All @@ -19,6 +20,7 @@ export function BringOver({ onError }: { onError: (e: string) => void }) {
const [busy, setBusy] = useState(false);
const [result, setResult] = useState<BringResult | null>(null);
const [leftOut, setLeftOut] = useState(0);
const shell = useShell();

const files = useMemo(() => view?.files ?? [], [view]);
const needs = useMemo(() => new Needs(files), [files]);
Expand Down Expand Up @@ -162,8 +164,13 @@ export function BringOver({ onError }: { onError: (e: string) => void }) {
}
>
{!view.linked && files.length > 0 && (
<p className="mt-1.5 text-[11px] text-dim">
Link this machine with the Primary one (Link a machine, top bar) to bring these over.
<p className="mt-1.5 flex flex-wrap items-center gap-x-1 text-[11px] text-dim">
Link this machine with the Primary one to bring these over.
{shell && (
<Button variant="link" className="h-5 px-1 text-[11px]" onClick={shell.link}>
Link a machine…
</Button>
)}
</p>
)}

Expand Down
177 changes: 177 additions & 0 deletions app/src/FilesGuide.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { useEffect, useState, type ReactNode } from "react";
import { backupView, onBackupChanged, onSyncChanged, type BackupView } from "./api";
import { placeName } from "./Backup";
import { useShell } from "./shell";
import { Button, Notice, Tile, useFolders, type GlyphName, type Tone } from "./ui";

/** The screen the guide is on: a button to that same screen is left out. */
type Where = "plan" | "setup" | "rig" | "settings";

/**
* How scripts, templates and presets get to a machine (decision #122). A
* first-time user found the ways spread over This machine, Get Up To Speed
* and Settings with nothing tying them together, so this one line names the
* next thing to set up and opens to the ways in order: link the machines,
* Bring over what's missing, Keep Files in Sync (a cloud folder, GitHub or
* the home network), Choose what syncs, and what never travels. Each says
* whether it's done here and has its button.
*
* On a checklist it starts open until syncing is on; elsewhere it starts
* folded. Once toggled, that's remembered. On This machine it shows only
* while something is left to set up, since Sync & backup says the rest.
*/
export function FilesGuide({ where }: { where: Where }) {
const shell = useShell();
const [view, setView] = useState<BackupView | null>(null);
useEffect(() => {
const load = () => backupView().then(setView, () => {});
load();
const offBackup = onBackupChanged(load);
const offSync = onSyncChanged(load);
return () => {
offBackup.then((f) => f());
offSync.then((f) => f());
};
}, []);
const syncing = !!view?.settings.keep_in_sync;
const checklist = where === "plan" || where === "setup";
const folders = useFolders("files-guide", () => checklist && !syncing);
if (!shell || !view) return null;

const { linked, isPrimary } = shell;
if (where === "rig" && linked && syncing) return null;
const place = placeName(view);
const open = folders.isOpen("guide");

const linkButton = (
<Button variant="link" className="h-6 shrink-0 px-2" onClick={shell.link}>
Link a machine…
</Button>
);
const syncButton = where !== "settings" && (
<Button variant="link" className="h-6 shrink-0 px-2" onClick={shell.setUpSync}>
Set up sync…
</Button>
);

let tone: Tone = "accent";
let text: string;
let next: ReactNode = null;
if (!linked) {
text = syncing
? `Syncing through ${place}. Link your other machines so they can share it.`
: "Link your machines so these can move between them.";
next = linkButton;
} else if (!syncing) {
text = isPrimary
? "Turn on Keep Files in Sync so new ones reach your other machines."
: "Bring over copies them once; Keep Files in Sync keeps them matching.";
next = syncButton;
} else if (!view.dir) {
tone = "warn";
text = `Keep Files in Sync is on, but ${place} isn't there now (signed out, or not connected).`;
} else {
tone = "ok";
text = `Kept in sync with your other machines through ${place}.`;
}

const bringText = isPrimary
? "This machine is the Primary Rig, so your other machines bring over its templates, scripts, themes and presets from their Get Up To Speed."
: `${checklist ? "The Bring over step" : "Get Up To Speed's Bring over step"} copies the Primary Rig's templates, scripts, themes and presets this machine lacks, with the files each needs. Anything replaced is saved first.`;

return (
<Notice
tone={tone}
icon="copies"
text={
<span className="text-dim">
<span className="font-medium text-fg">Scripts, templates and presets</span> · {text}
</span>
}
actions={!open && next}
open={open}
onToggle={() => folders.toggle("guide")}
toggle={["How it works", "Hide"]}
>
<ol className="flex flex-col gap-2">
<Way glyph="link" title="Link your machines" done={linked && "Linked"} action={!linked && linkButton}>
Files only move between linked machines. Show a code on one and type it on the other.
</Way>
<Way
glyph="copies"
title="Bring over what's missing"
action={
!checklist &&
shell.upToSpeed && (
<Button variant="link" className="h-6 shrink-0 px-2" onClick={shell.upToSpeed}>
Open Up To Speed
</Button>
)
}
>
{bringText}
</Way>
<Way
glyph="sync"
title="Keep Files in Sync, on every machine"
done={syncing && `On, through ${place}`}
action={!syncing && syncButton}
>
Pick the same cloud folder (Dropbox, Google Drive, OneDrive, iCloud), private GitHub repo or home network
on each. New, changed and renamed files then travel both ways on their own; History can undo each.
</Way>
<Way
glyph="sliders"
title="Choose what syncs"
action={
where !== "settings" && (
<Button variant="link" className="h-6 shrink-0 px-2" onClick={shell.settings}>
Choose…
</Button>
)
}
>
{where === "settings" ? "Under What syncs below" : "In Settings"}, untick a kind to keep it this machine's
own. DAW settings such as shortcuts and menus change only while that DAW is closed.
</Way>
</ol>
<p className="text-[11px] leading-relaxed text-dim">
<span className="font-medium text-fg">Never copied:</span> plug-ins, Reaper extensions (SWS, ReaPack,
ReaImGui) and sample libraries need their own installers, so{" "}
{checklist ? "they're steps in this checklist" : "Get Up To Speed lists them"}. Reaper's folder settings and
ReaPack repositories are set from {checklist ? "the" : "Get Up To Speed's"} Reaper setup card. reaper.ini and
audio and MIDI settings stay with each machine.
</p>
</Notice>
);
}

/** One way files travel: its icon (a green tick once done here), what it is, and its button. */
function Way({
glyph,
title,
done = false,
action,
children,
}: {
glyph: GlyphName;
title: string;
/** What's done here, shown in green beside the title. */
done?: string | false;
action?: ReactNode;
children: ReactNode;
}) {
return (
<li className="flex items-start gap-2.5">
<Tile name={done ? "check" : glyph} tone={done ? "ok" : "accent"} className="" />
<div className="min-w-0 flex-1">
<p className="font-medium">
{title}
{done && <span className="ml-2 text-[11px] font-normal text-ok">{done}</span>}
</p>
<p className="text-[11px] leading-relaxed text-dim">{children}</p>
</div>
{action}
</li>
);
}
4 changes: 2 additions & 2 deletions app/src/Tour.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ function SyncStep() {
}

const intro =
"Templates, scripts, presets and DAW settings reach your other machines on their own. Choose what syncs on each machine; Reaper shortcuts move between PC and Mac keys.";
"Link your machines, then pick the same cloud folder on each. Templates, scripts, presets and DAW settings then reach the others on their own; Get Up To Speed brings over what a machine still lacks. Choose what syncs per machine; Reaper shortcuts move between PC and Mac keys.";
if (view?.github && view.settings.keep_in_sync) {
return (
<>
Expand Down Expand Up @@ -216,7 +216,7 @@ function SyncStep() {
missing={false}
preferred={view.settings.folder ?? view.transfer_folder}
onChoose={choose}
hint="Which cloud service do you use? Pick the same one on each machine, or GitHub later on This machine."
hint="Which cloud service do you use? Pick the same one on each machine, or GitHub or your home network later on This machine."
/>
</span>
)}
Expand Down
2 changes: 2 additions & 0 deletions app/src/screens/Compare.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { TemplateLine } from "../Templates";
import { DiskList, DiskSummary, LowDiskNotice } from "../Disks";
import { ReaperExtensionsNotice } from "../ReaperExtensions";
import { ReaperStartupNotice } from "../ReaperStartup";
import { FilesGuide } from "../FilesGuide";
import {
Badge,
Button,
Expand Down Expand Up @@ -1802,6 +1803,7 @@ function PlanView({
)}
{progress && <OtherFormatsNote items={progress.other_formats} />}
<IgnoredNotice ignores={ignores} machine={here ? null : id} name={name} onError={onError} />
{here && <FilesGuide where="plan" />}
</Panel>
<StepList steps={steps} scope={`plan.${id}`} onTick={tick} actions={actions} className="mx-3 mt-3" />
{here && progress && <StepExtras steps={steps} onError={onError} className="mx-3 mt-3" />}
Expand Down
2 changes: 2 additions & 0 deletions app/src/screens/Rig.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BackupLine } from "../Backup";
import { FilesGuide } from "../FilesGuide";
import { BigFiles } from "../BigFiles";
import { useEffect, useMemo, useState, type ReactNode } from "react";
import {
Expand Down Expand Up @@ -138,6 +139,7 @@ export function Rig({
<Panel className="mx-3 mt-3 p-1">
<GoldenLine golden={golden} busy={busy} onMakeGolden={onMakeGolden} />
<BackupLine onError={onError} fresh={!golden} openAt={syncSetupAt} />
<FilesGuide where="rig" />
{setupOffer}
{/* Errors here surface where the files are listed, on Up To Speed. */}
<BigFiles onError={() => {}} compact />
Expand Down
2 changes: 2 additions & 0 deletions app/src/screens/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
type Theme,
} from "../api";
import { BackupLine, SyncChoices } from "../Backup";
import { FilesGuide } from "../FilesGuide";
import { IgnoredToggle, useIgnores } from "../Ignore";
import { Button, Glyph, Label, Loading, Panel, type GlyphName } from "../ui";

Expand Down Expand Up @@ -100,6 +101,7 @@ export function Settings({

<Section title="Sync" icon="sync" flush>
<BackupLine onError={onError} fresh={false} choices={false} />
<FilesGuide where="settings" />
</Section>

<Section title="What syncs" icon="copies">
Expand Down
7 changes: 5 additions & 2 deletions app/src/screens/Setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { RestoreFromBackup } from "../Backup";
import { BigFiles } from "../BigFiles";
import { BringOver } from "../BringOver";
import { FilesGuide } from "../FilesGuide";
import { ReaperExtras } from "../ReaperExtras";
import { IgnoreButton, IgnoredNotice, productRules, useIgnores } from "../Ignore";
import { AppButton } from "./Compare";
Expand Down Expand Up @@ -137,8 +138,9 @@ function Start({
<h1 className="text-base font-semibold">Set up this machine</h1>
<p className="mt-1 text-xs leading-relaxed text-dim">
Patch Pilot lists what this machine needs from your Primary Rig, in the order to do it: licences and
vendor apps first, then plug-ins, then your DAW files. It never installs or copies plug-ins for you. As
you install things, it ticks them off after each rescan, and you get a report at the end.
vendor apps first, then plug-ins, then your DAW files. It never installs or copies plug-ins for you, but
once your machines are linked it can bring over your templates, scripts and presets. As you install things,
it ticks them off after each rescan, and you get a report at the end.
</p>
</div>
</div>
Expand Down Expand Up @@ -300,6 +302,7 @@ function Progress({
)}
<OtherFormatsNote items={p.other_formats} />
<IgnoredNotice ignores={ignores} machine={null} onError={onError} />
<FilesGuide where="setup" />
</Panel>

<StepList
Expand Down
Loading
Loading