Run oxfmt across repo, add format script and docs

Add .oxfmtignore to skip generated bindings and wasm-pack output.
Add npm format script, update DEVELOPMENT.md for Vite+ toolchain,
and format all non-generated files with oxfmt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Gregory Schier
2026-03-13 10:15:49 -07:00
parent 45262edfbd
commit b4a1c418bb
664 changed files with 13638 additions and 13492 deletions

View File

@@ -1,7 +1,7 @@
import { useState } from 'react';
import { Button } from '../core/Button';
import { Select } from '../core/Select';
import { HStack, VStack } from '../core/Stacks';
import { useState } from "react";
import { Button } from "../core/Button";
import { Select } from "../core/Select";
import { HStack, VStack } from "../core/Stacks";
interface Props {
branches: string[];
@@ -11,7 +11,7 @@ interface Props {
}
export function BranchSelectionDialog({ branches, onCancel, onSelect, selectText }: Props) {
const [branch, setBranch] = useState<string>('__NONE__');
const [branch, setBranch] = useState<string>("__NONE__");
return (
<VStack
className="mb-4"

View File

@@ -1,6 +1,5 @@
import type { GitStatusEntry } from '@yaakapp-internal/git';
import { useGit } from '@yaakapp-internal/git';
import type { GitStatusEntry } from "@yaakapp-internal/git";
import { useGit } from "@yaakapp-internal/git";
import type {
Environment,
Folder,
@@ -8,26 +7,26 @@ import type {
HttpRequest,
WebsocketRequest,
Workspace,
} from '@yaakapp-internal/models';
import classNames from 'classnames';
import { useCallback, useMemo, useState } from 'react';
import { modelToYaml } from '../../lib/diffYaml';
import { resolvedModelName } from '../../lib/resolvedModelName';
import { showErrorToast } from '../../lib/toast';
import { Banner } from '../core/Banner';
import { Button } from '../core/Button';
import type { CheckboxProps } from '../core/Checkbox';
import { Checkbox } from '../core/Checkbox';
import { DiffViewer } from '../core/Editor/DiffViewer';
import { Icon } from '../core/Icon';
import { InlineCode } from '../core/InlineCode';
import { Input } from '../core/Input';
import { Separator } from '../core/Separator';
import { SplitLayout } from '../core/SplitLayout';
import { HStack } from '../core/Stacks';
import { EmptyStateText } from '../EmptyStateText';
import { gitCallbacks } from './callbacks';
import { handlePushResult } from './git-util';
} from "@yaakapp-internal/models";
import classNames from "classnames";
import { useCallback, useMemo, useState } from "react";
import { modelToYaml } from "../../lib/diffYaml";
import { resolvedModelName } from "../../lib/resolvedModelName";
import { showErrorToast } from "../../lib/toast";
import { Banner } from "../core/Banner";
import { Button } from "../core/Button";
import type { CheckboxProps } from "../core/Checkbox";
import { Checkbox } from "../core/Checkbox";
import { DiffViewer } from "../core/Editor/DiffViewer";
import { Icon } from "../core/Icon";
import { InlineCode } from "../core/InlineCode";
import { Input } from "../core/Input";
import { Separator } from "../core/Separator";
import { SplitLayout } from "../core/SplitLayout";
import { HStack } from "../core/Stacks";
import { EmptyStateText } from "../EmptyStateText";
import { gitCallbacks } from "./callbacks";
import { handlePushResult } from "./git-util";
interface Props {
syncDir: string;
@@ -49,7 +48,7 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) {
);
const [isPushing, setIsPushing] = useState(false);
const [commitError, setCommitError] = useState<string | null>(null);
const [message, setMessage] = useState<string>('');
const [message, setMessage] = useState<string>("");
const [selectedEntry, setSelectedEntry] = useState<GitStatusEntry | null>(null);
const handleCreateCommit = async () => {
@@ -70,8 +69,8 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) {
onDone();
} catch (err) {
showErrorToast({
id: 'git-commit-and-push-error',
title: 'Error committing and pushing',
id: "git-commit-and-push-error",
title: "Error committing and pushing",
message: String(err),
});
} finally {
@@ -96,11 +95,11 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) {
}, [status.data?.entries]);
const hasAddedAnything = allEntries.find((e) => e.staged) != null;
const hasAnythingToAdd = allEntries.find((e) => e.status !== 'current') != null;
const hasAnythingToAdd = allEntries.find((e) => e.status !== "current") != null;
const tree: CommitTreeNode | null = useMemo(() => {
const next = (
model: CommitTreeNode['model'],
model: CommitTreeNode["model"],
ancestors: CommitTreeNode[],
): CommitTreeNode | null => {
const statusEntry = internalEntries?.find((s) => s.relaPath.includes(model.id));
@@ -122,12 +121,12 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) {
if (childModel == null) continue;
// TODO: Figure out why not all of these show up
if ('folderId' in childModel && childModel.folderId != null) {
if ("folderId" in childModel && childModel.folderId != null) {
if (childModel.folderId === model.id) {
const c = next(childModel, [...ancestors, node]);
if (c != null) node.children.push(c);
}
} else if ('workspaceId' in childModel && childModel.workspaceId === model.id) {
} else if ("workspaceId" in childModel && childModel.workspaceId === model.id) {
const c = next(childModel, [...ancestors, node]);
if (c != null) node.children.push(c);
} else {
@@ -144,7 +143,7 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) {
const checkNode = useCallback(
(treeNode: CommitTreeNode) => {
const checked = nodeCheckedStatus(treeNode);
const newChecked = checked === 'indeterminate' ? true : !checked;
const newChecked = checked === "indeterminate" ? true : !checked;
setCheckedAndChildren(treeNode, newChecked, unstage.mutate, add.mutate);
// TODO: Also ensure parents are added properly
},
@@ -206,7 +205,7 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) {
onSelect={handleSelectChild}
selectedPath={selectedEntry?.relaPath ?? null}
/>
{externalEntries.find((e) => e.status !== 'current') && (
{externalEntries.find((e) => e.status !== "current") && (
<>
<Separator className="mt-3 mb-1">External file changes</Separator>
{externalEntries.map((entry) => (
@@ -297,13 +296,13 @@ function TreeNodeChildren({
return (
<div
className={classNames(
depth > 0 && 'pl-4 ml-2 border-l border-dashed border-border-subtle relative',
depth > 0 && "pl-4 ml-2 border-l border-dashed border-border-subtle relative",
)}
>
<div
className={classNames(
'relative flex gap-1 w-full h-xs items-center',
isSelected ? 'text-text' : 'text-text-subtle',
"relative flex gap-1 w-full h-xs items-center",
isSelected ? "text-text" : "text-text-subtle",
)}
>
{isSelected && (
@@ -311,39 +310,39 @@ function TreeNodeChildren({
)}
<Checkbox
checked={checked}
title={checked ? 'Unstage change' : 'Stage change'}
title={checked ? "Unstage change" : "Stage change"}
hideLabel
onChange={(checked) => onCheck(node, checked)}
/>
<button
type="button"
className={classNames('flex-1 min-w-0 flex items-center gap-1 px-1 py-0.5 text-left')}
onClick={() => node.status.status !== 'current' && onSelect(node.status)}
className={classNames("flex-1 min-w-0 flex items-center gap-1 px-1 py-0.5 text-left")}
onClick={() => node.status.status !== "current" && onSelect(node.status)}
>
{node.model.model !== 'http_request' &&
node.model.model !== 'grpc_request' &&
node.model.model !== 'websocket_request' ? (
{node.model.model !== "http_request" &&
node.model.model !== "grpc_request" &&
node.model.model !== "websocket_request" ? (
<Icon
color="secondary"
icon={
node.model.model === 'folder'
? 'folder'
: node.model.model === 'environment'
? 'variable'
: 'house'
node.model.model === "folder"
? "folder"
: node.model.model === "environment"
? "variable"
: "house"
}
/>
) : (
<span aria-hidden className="w-4" />
)}
<div className="truncate flex-1">{resolvedModelName(node.model)}</div>
{node.status.status !== 'current' && (
{node.status.status !== "current" && (
<InlineCode
className={classNames(
'py-0 bg-transparent w-[6rem] text-center shrink-0',
node.status.status === 'modified' && 'text-info',
node.status.status === 'untracked' && 'text-success',
node.status.status === 'removed' && 'text-danger',
"py-0 bg-transparent w-[6rem] text-center shrink-0",
node.status.status === "modified" && "text-info",
node.status.status === "untracked" && "text-success",
node.status.status === "removed" && "text-danger",
)}
>
{node.status.status}
@@ -375,7 +374,7 @@ function ExternalTreeNode({
entry: GitStatusEntry;
onCheck: (entry: GitStatusEntry) => void;
}) {
if (entry.status === 'current') {
if (entry.status === "current") {
return null;
}
@@ -391,10 +390,10 @@ function ExternalTreeNode({
<div className="truncate">{entry.relaPath}</div>
<InlineCode
className={classNames(
'py-0 ml-auto bg-transparent w-[6rem] text-center',
entry.status === 'modified' && 'text-info',
entry.status === 'untracked' && 'text-success',
entry.status === 'removed' && 'text-danger',
"py-0 ml-auto bg-transparent w-[6rem] text-center",
entry.status === "modified" && "text-info",
entry.status === "untracked" && "text-success",
entry.status === "removed" && "text-danger",
)}
>
{entry.status}
@@ -405,14 +404,14 @@ function ExternalTreeNode({
);
}
function nodeCheckedStatus(root: CommitTreeNode): CheckboxProps['checked'] {
function nodeCheckedStatus(root: CommitTreeNode): CheckboxProps["checked"] {
let numVisited = 0;
let numChecked = 0;
let numCurrent = 0;
const visitChildren = (n: CommitTreeNode) => {
numVisited += 1;
if (n.status.status === 'current') {
if (n.status.status === "current") {
numCurrent += 1;
} else if (n.status.staged) {
numChecked += 1;
@@ -430,7 +429,7 @@ function nodeCheckedStatus(root: CommitTreeNode): CheckboxProps['checked'] {
if (numChecked === 0) {
return false;
}
return 'indeterminate';
return "indeterminate";
}
function setCheckedAndChildren(
@@ -447,7 +446,7 @@ function setCheckedAndChildren(
next(child);
}
if (node.status.status === 'current') {
if (node.status.status === "current") {
// Nothing required
} else if (checked && !node.status.staged) {
toAdd.push(node.status.relaPath);
@@ -463,7 +462,7 @@ function setCheckedAndChildren(
}
function isNodeRelevant(node: CommitTreeNode): boolean {
if (node.status.status !== 'current') {
if (node.status.status !== "current") {
return true;
}
@@ -480,7 +479,7 @@ function DiffPanel({ entry }: { entry: GitStatusEntry }) {
<div className="text-sm text-text-subtle mb-2 px-1">
{resolvedModelName(entry.next ?? entry.prev)} ({entry.status})
</div>
<DiffViewer original={prevYaml ?? ''} modified={nextYaml ?? ''} className="flex-1 min-h-0" />
<DiffViewer original={prevYaml ?? ""} modified={nextYaml ?? ""} className="flex-1 min-h-0" />
</div>
);
}

View File

@@ -1,29 +1,29 @@
import { useGit } from '@yaakapp-internal/git';
import type { WorkspaceMeta } from '@yaakapp-internal/models';
import classNames from 'classnames';
import { useAtomValue } from 'jotai';
import type { HTMLAttributes } from 'react';
import { forwardRef } from 'react';
import { openWorkspaceSettings } from '../../commands/openWorkspaceSettings';
import { activeWorkspaceAtom, activeWorkspaceMetaAtom } from '../../hooks/useActiveWorkspace';
import { useKeyValue } from '../../hooks/useKeyValue';
import { useRandomKey } from '../../hooks/useRandomKey';
import { sync } from '../../init/sync';
import { showConfirm, showConfirmDelete } from '../../lib/confirm';
import { showDialog } from '../../lib/dialog';
import { fireAndForget } from '../../lib/fireAndForget';
import { showPrompt } from '../../lib/prompt';
import { showErrorToast, showToast } from '../../lib/toast';
import { Banner } from '../core/Banner';
import type { DropdownItem } from '../core/Dropdown';
import { Dropdown } from '../core/Dropdown';
import { Icon } from '../core/Icon';
import { InlineCode } from '../core/InlineCode';
import { gitCallbacks } from './callbacks';
import { GitCommitDialog } from './GitCommitDialog';
import { GitRemotesDialog } from './GitRemotesDialog';
import { handlePullResult, handlePushResult } from './git-util';
import { HistoryDialog } from './HistoryDialog';
import { useGit } from "@yaakapp-internal/git";
import type { WorkspaceMeta } from "@yaakapp-internal/models";
import classNames from "classnames";
import { useAtomValue } from "jotai";
import type { HTMLAttributes } from "react";
import { forwardRef } from "react";
import { openWorkspaceSettings } from "../../commands/openWorkspaceSettings";
import { activeWorkspaceAtom, activeWorkspaceMetaAtom } from "../../hooks/useActiveWorkspace";
import { useKeyValue } from "../../hooks/useKeyValue";
import { useRandomKey } from "../../hooks/useRandomKey";
import { sync } from "../../init/sync";
import { showConfirm, showConfirmDelete } from "../../lib/confirm";
import { showDialog } from "../../lib/dialog";
import { fireAndForget } from "../../lib/fireAndForget";
import { showPrompt } from "../../lib/prompt";
import { showErrorToast, showToast } from "../../lib/toast";
import { Banner } from "../core/Banner";
import type { DropdownItem } from "../core/Dropdown";
import { Dropdown } from "../core/Dropdown";
import { Icon } from "../core/Icon";
import { InlineCode } from "../core/InlineCode";
import { gitCallbacks } from "./callbacks";
import { GitCommitDialog } from "./GitCommitDialog";
import { GitRemotesDialog } from "./GitRemotesDialog";
import { handlePullResult, handlePushResult } from "./git-util";
import { HistoryDialog } from "./HistoryDialog";
export function GitDropdown() {
const workspaceMeta = useAtomValue(activeWorkspaceMetaAtom);
@@ -58,13 +58,13 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
const localBranches = status.data?.localBranches ?? [];
const remoteBranches = status.data?.remoteBranches ?? [];
const remoteOnlyBranches = remoteBranches.filter(
(b) => !localBranches.includes(b.replace(/^origin\//, '')),
(b) => !localBranches.includes(b.replace(/^origin\//, "")),
);
if (workspace == null) {
return null;
}
const noRepo = status.error?.includes('not found');
const noRepo = status.error?.includes("not found");
if (noRepo) {
return <SetupGitDropdown workspaceId={workspace.id} initRepo={init.mutate} />;
}
@@ -75,7 +75,7 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
}
const currentBranch = status.data.headRefShorthand;
const hasChanges = status.data.entries.some((e) => e.status !== 'current');
const hasChanges = status.data.entries.some((e) => e.status !== "current");
const _hasRemotes = (status.data.origins ?? []).length > 0;
const { ahead, behind } = status.data;
@@ -88,12 +88,12 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
if (!force) {
// Checkout failed so ask user if they want to force it
const forceCheckout = await showConfirm({
id: 'git-force-checkout',
title: 'Conflicts Detected',
id: "git-force-checkout",
title: "Conflicts Detected",
description:
'Your branch has conflicts. Either make a commit or force checkout to discard changes.',
confirmText: 'Force Checkout',
color: 'warning',
"Your branch has conflicts. Either make a commit or force checkout to discard changes.",
confirmText: "Force Checkout",
color: "warning",
});
if (forceCheckout) {
tryCheckout(branch, true);
@@ -101,21 +101,21 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
} else {
// Checkout failed
showErrorToast({
id: 'git-checkout-error',
title: 'Error checking out branch',
id: "git-checkout-error",
title: "Error checking out branch",
message: String(err),
});
}
},
async onSuccess(branchName) {
showToast({
id: 'git-checkout-success',
id: "git-checkout-success",
message: (
<>
Switched branch <InlineCode>{branchName}</InlineCode>
</>
),
color: 'success',
color: "success",
});
await sync({ force: true });
},
@@ -125,33 +125,33 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
const items: DropdownItem[] = [
{
label: 'View History...',
label: "View History...",
hidden: (log.data ?? []).length === 0,
leftSlot: <Icon icon="history" />,
onSelect: async () => {
showDialog({
id: 'git-history',
size: 'md',
title: 'Commit History',
id: "git-history",
size: "md",
title: "Commit History",
noPadding: true,
render: () => <HistoryDialog log={log.data ?? []} />,
});
},
},
{
label: 'Manage Remotes...',
label: "Manage Remotes...",
leftSlot: <Icon icon="hard_drive_download" />,
onSelect: () => GitRemotesDialog.show(syncDir),
},
{ type: 'separator' },
{ type: "separator" },
{
label: 'New Branch...',
label: "New Branch...",
leftSlot: <Icon icon="git_branch_plus" />,
async onSelect() {
const name = await showPrompt({
id: 'git-branch-name',
title: 'Create Branch',
label: 'Branch Name',
id: "git-branch-name",
title: "Create Branch",
label: "Branch Name",
});
if (!name) return;
@@ -161,8 +161,8 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
disableToastError: true,
onError: (err) => {
showErrorToast({
id: 'git-branch-error',
title: 'Error creating branch',
id: "git-branch-error",
title: "Error creating branch",
message: String(err),
});
},
@@ -171,9 +171,9 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
tryCheckout(name, false);
},
},
{ type: 'separator' },
{ type: "separator" },
{
label: 'Push',
label: "Push",
leftSlot: <Icon icon="arrow_up_from_line" />,
waitForOnSelect: true,
async onSelect() {
@@ -182,8 +182,8 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
onSuccess: handlePushResult,
onError(err) {
showErrorToast({
id: 'git-push-error',
title: 'Error pushing changes',
id: "git-push-error",
title: "Error pushing changes",
message: String(err),
});
},
@@ -191,7 +191,7 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
},
},
{
label: 'Pull',
label: "Pull",
leftSlot: <Icon icon="arrow_down_to_line" />,
waitForOnSelect: true,
async onSelect() {
@@ -200,8 +200,8 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
onSuccess: handlePullResult,
onError(err) {
showErrorToast({
id: 'git-pull-error',
title: 'Error pulling changes',
id: "git-pull-error",
title: "Error pulling changes",
message: String(err),
});
},
@@ -209,14 +209,14 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
},
},
{
label: 'Commit...',
label: "Commit...",
leftSlot: <Icon icon="git_commit_vertical" />,
onSelect() {
showDialog({
id: 'commit',
title: 'Commit Changes',
size: 'full',
id: "commit",
title: "Commit Changes",
size: "full",
noPadding: true,
render: ({ hide }) => (
<GitCommitDialog syncDir={syncDir} onDone={hide} workspace={workspace} />
@@ -225,17 +225,17 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
},
},
{
label: 'Reset Changes',
label: "Reset Changes",
hidden: !hasChanges,
leftSlot: <Icon icon="rotate_ccw" />,
color: 'danger',
color: "danger",
async onSelect() {
const confirmed = await showConfirm({
id: 'git-reset-changes',
title: 'Reset Changes',
description: 'This will discard all uncommitted changes. This cannot be undone.',
confirmText: 'Reset',
color: 'danger',
id: "git-reset-changes",
title: "Reset Changes",
description: "This will discard all uncommitted changes. This cannot be undone.",
confirmText: "Reset",
color: "danger",
});
if (!confirmed) return;
@@ -243,32 +243,32 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
disableToastError: true,
onSuccess() {
showToast({
id: 'git-reset-success',
message: 'Changes have been reset',
color: 'success',
id: "git-reset-success",
message: "Changes have been reset",
color: "success",
});
fireAndForget(sync({ force: true }));
},
onError(err) {
showErrorToast({
id: 'git-reset-error',
title: 'Error resetting changes',
id: "git-reset-error",
title: "Error resetting changes",
message: String(err),
});
},
});
},
},
{ type: 'separator', label: 'Branches', hidden: localBranches.length < 1 },
{ type: "separator", label: "Branches", hidden: localBranches.length < 1 },
...localBranches.map((branch) => {
const isCurrent = currentBranch === branch;
return {
label: branch,
leftSlot: <Icon icon={isCurrent ? 'check' : 'empty'} />,
leftSlot: <Icon icon={isCurrent ? "check" : "empty"} />,
submenuOpenOnClick: true,
submenu: [
{
label: 'Checkout',
label: "Checkout",
hidden: isCurrent,
onSelect: () => tryCheckout(branch, false),
},
@@ -286,10 +286,10 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
disableToastError: true,
onSuccess() {
showToast({
id: 'git-merged-branch',
id: "git-merged-branch",
message: (
<>
Merged <InlineCode>{branch}</InlineCode> into{' '}
Merged <InlineCode>{branch}</InlineCode> into{" "}
<InlineCode>{currentBranch}</InlineCode>
</>
),
@@ -298,8 +298,8 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
},
onError(err) {
showErrorToast({
id: 'git-merged-branch-error',
title: 'Error merging branch',
id: "git-merged-branch-error",
title: "Error merging branch",
message: String(err),
});
},
@@ -308,17 +308,17 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
},
},
{
label: 'New Branch...',
label: "New Branch...",
async onSelect() {
const name = await showPrompt({
id: 'git-new-branch-from',
title: 'New Branch',
id: "git-new-branch-from",
title: "New Branch",
description: (
<>
Create a new branch from <InlineCode>{branch}</InlineCode>
</>
),
label: 'Branch Name',
label: "Branch Name",
});
if (!name) return;
@@ -328,8 +328,8 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
disableToastError: true,
onError: (err) => {
showErrorToast({
id: 'git-branch-error',
title: 'Error creating branch',
id: "git-branch-error",
title: "Error creating branch",
message: String(err),
});
},
@@ -339,12 +339,12 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
},
},
{
label: 'Rename...',
label: "Rename...",
async onSelect() {
const newName = await showPrompt({
id: 'git-rename-branch',
title: 'Rename Branch',
label: 'New Branch Name',
id: "git-rename-branch",
title: "Rename Branch",
label: "New Branch Name",
defaultValue: branch,
});
if (!newName || newName === branch) return;
@@ -355,20 +355,20 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
disableToastError: true,
onSuccess() {
showToast({
id: 'git-rename-branch-success',
id: "git-rename-branch-success",
message: (
<>
Renamed <InlineCode>{branch}</InlineCode> to{' '}
Renamed <InlineCode>{branch}</InlineCode> to{" "}
<InlineCode>{newName}</InlineCode>
</>
),
color: 'success',
color: "success",
});
},
onError(err) {
showErrorToast({
id: 'git-rename-branch-error',
title: 'Error renaming branch',
id: "git-rename-branch-error",
title: "Error renaming branch",
message: String(err),
});
},
@@ -376,15 +376,15 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
);
},
},
{ type: 'separator', hidden: isCurrent },
{ type: "separator", hidden: isCurrent },
{
label: 'Delete',
color: 'danger',
label: "Delete",
color: "danger",
hidden: isCurrent,
onSelect: async () => {
const confirmed = await showConfirmDelete({
id: 'git-delete-branch',
title: 'Delete Branch',
id: "git-delete-branch",
title: "Delete Branch",
description: (
<>
Permanently delete <InlineCode>{branch}</InlineCode>?
@@ -401,18 +401,18 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
disableToastError: true,
onError(err) {
showErrorToast({
id: 'git-delete-branch-error',
title: 'Error deleting branch',
id: "git-delete-branch-error",
title: "Error deleting branch",
message: String(err),
});
},
},
);
if (result.type === 'not_fully_merged') {
if (result.type === "not_fully_merged") {
const confirmed = await showConfirm({
id: 'force-branch-delete',
title: 'Branch not fully merged',
id: "force-branch-delete",
title: "Branch not fully merged",
description: (
<>
<p>
@@ -429,8 +429,8 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
disableToastError: true,
onError(err) {
showErrorToast({
id: 'git-force-delete-branch-error',
title: 'Error force deleting branch',
id: "git-force-delete-branch-error",
title: "Error force deleting branch",
message: String(err),
});
},
@@ -447,21 +447,21 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
const isCurrent = currentBranch === branch;
return {
label: branch,
leftSlot: <Icon icon={isCurrent ? 'check' : 'empty'} />,
leftSlot: <Icon icon={isCurrent ? "check" : "empty"} />,
submenuOpenOnClick: true,
submenu: [
{
label: 'Checkout',
label: "Checkout",
hidden: isCurrent,
onSelect: () => tryCheckout(branch, false),
},
{
label: 'Delete',
color: 'danger',
label: "Delete",
color: "danger",
async onSelect() {
const confirmed = await showConfirmDelete({
id: 'git-delete-remote-branch',
title: 'Delete Remote Branch',
id: "git-delete-remote-branch",
title: "Delete Remote Branch",
description: (
<>
Permanently delete <InlineCode>{branch}</InlineCode> from the remote?
@@ -476,19 +476,19 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
disableToastError: true,
onSuccess() {
showToast({
id: 'git-delete-remote-branch-success',
id: "git-delete-remote-branch-success",
message: (
<>
Deleted remote branch <InlineCode>{branch}</InlineCode>
</>
),
color: 'success',
color: "success",
});
},
onError(err) {
showErrorToast({
id: 'git-delete-remote-branch-error',
title: 'Error deleting remote branch',
id: "git-delete-remote-branch-error",
title: "Error deleting remote branch",
message: String(err),
});
},
@@ -534,7 +534,7 @@ const GitMenuButton = forwardRef<HTMLButtonElement, HTMLAttributes<HTMLButtonEle
ref={ref}
className={classNames(
className,
'px-3 h-md border-t border-border flex items-center justify-between text-text-subtle outline-none focus-visible:bg-surface-highlight',
"px-3 h-md border-t border-border flex items-center justify-between text-text-subtle outline-none focus-visible:bg-surface-highlight",
)}
{...props}
/>
@@ -544,7 +544,7 @@ const GitMenuButton = forwardRef<HTMLButtonElement, HTMLAttributes<HTMLButtonEle
function SetupSyncDropdown({ workspaceMeta }: { workspaceMeta: WorkspaceMeta }) {
const { value: hidden, set: setHidden } = useKeyValue<Record<string, boolean>>({
key: 'setup_sync',
key: "setup_sync",
fallback: {},
});
@@ -564,24 +564,24 @@ function SetupSyncDropdown({ workspaceMeta }: { workspaceMeta: WorkspaceMeta })
fullWidth
items={[
{
type: 'content',
type: "content",
label: banner,
},
{
color: 'success',
label: 'Open Workspace Settings',
color: "success",
label: "Open Workspace Settings",
leftSlot: <Icon icon="settings" />,
onSelect: () => openWorkspaceSettings('data'),
onSelect: () => openWorkspaceSettings("data"),
},
{ type: 'separator' },
{ type: "separator" },
{
label: 'Hide This Message',
label: "Hide This Message",
leftSlot: <Icon icon="eye_closed" />,
async onSelect() {
const confirmed = await showConfirm({
id: 'hide-sync-menu-prompt',
title: 'Hide Setup Message',
description: 'You can configure filesystem sync or Git it in the workspace settings',
id: "hide-sync-menu-prompt",
title: "Hide Setup Message",
description: "You can configure filesystem sync or Git it in the workspace settings",
});
if (confirmed) {
await setHidden((prev) => ({ ...prev, [workspaceMeta.workspaceId]: true }));
@@ -608,7 +608,7 @@ function SetupGitDropdown({
initRepo: () => void;
}) {
const { value: hidden, set: setHidden } = useKeyValue<Record<string, boolean>>({
key: 'setup_git_repo',
key: "setup_git_repo",
fallback: {},
});
@@ -622,21 +622,21 @@ function SetupGitDropdown({
<Dropdown
fullWidth
items={[
{ type: 'content', label: banner },
{ type: "content", label: banner },
{
label: 'Initialize Git Repo',
label: "Initialize Git Repo",
leftSlot: <Icon icon="magic_wand" />,
onSelect: initRepo,
},
{ type: 'separator' },
{ type: "separator" },
{
label: 'Hide This Message',
label: "Hide This Message",
leftSlot: <Icon icon="eye_closed" />,
async onSelect() {
const confirmed = await showConfirm({
id: 'hide-git-init-prompt',
title: 'Hide Git Setup',
description: 'You can initialize a git repo outside of Yaak to bring this back',
id: "hide-git-init-prompt",
title: "Hide Git Setup",
description: "You can initialize a git repo outside of Yaak to bring this back",
});
if (confirmed) {
await setHidden((prev) => ({ ...prev, [workspaceId]: true }));

View File

@@ -1,10 +1,10 @@
import { useGit } from '@yaakapp-internal/git';
import { showDialog } from '../../lib/dialog';
import { Button } from '../core/Button';
import { IconButton } from '../core/IconButton';
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from '../core/Table';
import { gitCallbacks } from './callbacks';
import { addGitRemote } from './showAddRemoteDialog';
import { useGit } from "@yaakapp-internal/git";
import { showDialog } from "../../lib/dialog";
import { Button } from "../core/Button";
import { IconButton } from "../core/IconButton";
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "../core/Table";
import { gitCallbacks } from "./callbacks";
import { addGitRemote } from "./showAddRemoteDialog";
interface Props {
dir: string;
@@ -57,9 +57,9 @@ export function GitRemotesDialog({ dir }: Props) {
GitRemotesDialog.show = (dir: string) => {
showDialog({
id: 'git-remotes',
title: 'Manage Remotes',
size: 'md',
id: "git-remotes",
title: "Manage Remotes",
size: "md",
render: ({ hide }) => <GitRemotesDialog onDone={hide} dir={dir} />,
});
};

View File

@@ -1,5 +1,5 @@
import type { GitCommit } from '@yaakapp-internal/git';
import { formatDistanceToNowStrict } from 'date-fns';
import type { GitCommit } from "@yaakapp-internal/git";
import { formatDistanceToNowStrict } from "date-fns";
import {
Table,
TableBody,
@@ -8,7 +8,7 @@ import {
TableHeaderCell,
TableRow,
TruncatedWideTableCell,
} from '../core/Table';
} from "../core/Table";
interface Props {
log: GitCommit[];
@@ -27,12 +27,14 @@ export function HistoryDialog({ log }: Props) {
</TableHead>
<TableBody>
{log.map((l) => (
<TableRow key={(l.author.name ?? '') + (l.author.email ?? '') + (l.message ?? 'n/a') + l.when}>
<TableRow
key={(l.author.name ?? "") + (l.author.email ?? "") + (l.message ?? "n/a") + l.when}
>
<TruncatedWideTableCell>
{l.message || <em className="text-text-subtle">No message</em>}
</TruncatedWideTableCell>
<TableCell>
<span title={`Email: ${l.author.email}`}>{l.author.name || 'Unknown'}</span>
<span title={`Email: ${l.author.email}`}>{l.author.name || "Unknown"}</span>
</TableCell>
<TableCell className="text-text-subtle">
<span title={l.when}>{formatDistanceToNowStrict(l.when)} ago</span>

View File

@@ -1,18 +1,18 @@
import type { GitCallbacks } from '@yaakapp-internal/git';
import { sync } from '../../init/sync';
import { promptCredentials } from './credentials';
import { promptDivergedStrategy } from './diverged';
import { addGitRemote } from './showAddRemoteDialog';
import { promptUncommittedChangesStrategy } from './uncommitted';
import type { GitCallbacks } from "@yaakapp-internal/git";
import { sync } from "../../init/sync";
import { promptCredentials } from "./credentials";
import { promptDivergedStrategy } from "./diverged";
import { addGitRemote } from "./showAddRemoteDialog";
import { promptUncommittedChangesStrategy } from "./uncommitted";
export function gitCallbacks(dir: string): GitCallbacks {
return {
addRemote: async () => {
return addGitRemote(dir, 'origin');
return addGitRemote(dir, "origin");
},
promptCredentials: async ({ url, error }) => {
const creds = await promptCredentials({ url, error });
if (creds == null) throw new Error('Cancelled credentials prompt');
if (creds == null) throw new Error("Cancelled credentials prompt");
return creds;
},
promptDiverged: async ({ remote, branch }) => {

View File

@@ -1,6 +1,6 @@
import { showPromptForm } from '../../lib/prompt-form';
import { Banner } from '../core/Banner';
import { InlineCode } from '../core/InlineCode';
import { showPromptForm } from "../../lib/prompt-form";
import { Banner } from "../core/Banner";
import { InlineCode } from "../core/InlineCode";
export interface GitCredentials {
username: string;
@@ -15,15 +15,15 @@ export async function promptCredentials({
error: string | null;
}): Promise<GitCredentials | null> {
const isGitHub = /github\.com/i.test(remoteUrl);
const userLabel = isGitHub ? 'GitHub Username' : 'Username';
const passLabel = isGitHub ? 'GitHub Personal Access Token' : 'Password / Token';
const userDescription = isGitHub ? 'Use your GitHub username (not your email).' : undefined;
const userLabel = isGitHub ? "GitHub Username" : "Username";
const passLabel = isGitHub ? "GitHub Personal Access Token" : "Password / Token";
const userDescription = isGitHub ? "Use your GitHub username (not your email)." : undefined;
const passDescription = isGitHub
? 'GitHub requires a Personal Access Token (PAT) for write operations over HTTPS. Passwords are not supported.'
: 'Enter your password or access token for this Git server.';
? "GitHub requires a Personal Access Token (PAT) for write operations over HTTPS. Passwords are not supported."
: "Enter your password or access token for this Git server.";
const r = await showPromptForm({
id: 'git-credentials',
title: 'Credentials Required',
id: "git-credentials",
title: "Credentials Required",
description: error ? (
<Banner color="danger">{error}</Banner>
) : (
@@ -32,10 +32,10 @@ export async function promptCredentials({
</>
),
inputs: [
{ type: 'text', name: 'username', label: userLabel, description: userDescription },
{ type: "text", name: "username", label: userLabel, description: userDescription },
{
type: 'text',
name: 'password',
type: "text",
name: "password",
label: passLabel,
description: passDescription,
password: true,
@@ -44,7 +44,7 @@ export async function promptCredentials({
});
if (r == null) return null;
const username = String(r.username || '');
const password = String(r.password || '');
const username = String(r.username || "");
const password = String(r.password || "");
return { username, password };
}

View File

@@ -1,16 +1,16 @@
import type { DivergedStrategy } from '@yaakapp-internal/git';
import { useState } from 'react';
import { showDialog } from '../../lib/dialog';
import { Button } from '../core/Button';
import { InlineCode } from '../core/InlineCode';
import { RadioCards } from '../core/RadioCards';
import { HStack } from '../core/Stacks';
import type { DivergedStrategy } from "@yaakapp-internal/git";
import { useState } from "react";
import { showDialog } from "../../lib/dialog";
import { Button } from "../core/Button";
import { InlineCode } from "../core/InlineCode";
import { RadioCards } from "../core/RadioCards";
import { HStack } from "../core/Stacks";
type Resolution = 'force_reset' | 'merge';
type Resolution = "force_reset" | "merge";
const resolutionLabel: Record<Resolution, string> = {
force_reset: 'Force Pull',
merge: 'Merge',
force_reset: "Force Pull",
merge: "Merge",
};
interface DivergedDialogProps {
@@ -30,17 +30,18 @@ function DivergedDialog({ remote, branch, onResult, onHide }: DivergedDialogProp
};
const handleCancel = () => {
onResult('cancel');
onResult("cancel");
onHide();
};
return (
<div className="flex flex-col gap-4 mb-4">
<p className="text-text-subtle">
Your local branch has diverged from{' '}
Your local branch has diverged from{" "}
<InlineCode>
{remote}/{branch}
</InlineCode>. How would you like to resolve this?
</InlineCode>
. How would you like to resolve this?
</p>
<RadioCards
name="diverged-strategy"
@@ -48,24 +49,24 @@ function DivergedDialog({ remote, branch, onResult, onHide }: DivergedDialogProp
onChange={setSelected}
options={[
{
value: 'merge',
label: 'Merge Commit',
description: 'Combining local and remote changes into a single merge commit',
value: "merge",
label: "Merge Commit",
description: "Combining local and remote changes into a single merge commit",
},
{
value: 'force_reset',
label: 'Force Pull',
description: 'Discard local commits and reset to match the remote branch',
value: "force_reset",
label: "Force Pull",
description: "Discard local commits and reset to match the remote branch",
},
]}
/>
<HStack space={2} justifyContent="start" className="flex-row-reverse">
<Button
color={selected === 'force_reset' ? 'danger' : 'primary'}
color={selected === "force_reset" ? "danger" : "primary"}
disabled={selected == null}
onClick={handleSubmit}
>
{selected != null ? resolutionLabel[selected] : 'Select an option'}
{selected != null ? resolutionLabel[selected] : "Select an option"}
</Button>
<Button variant="border" onClick={handleCancel}>
Cancel
@@ -84,12 +85,12 @@ export async function promptDivergedStrategy({
}): Promise<DivergedStrategy> {
return new Promise((resolve) => {
showDialog({
id: 'git-diverged',
title: 'Branches Diverged',
id: "git-diverged",
title: "Branches Diverged",
hideX: true,
size: 'sm',
size: "sm",
disableBackdropClose: true,
onClose: () => resolve('cancel'),
onClose: () => resolve("cancel"),
render: ({ hide }) =>
DivergedDialog({
remote,

View File

@@ -1,35 +1,35 @@
import type { PullResult, PushResult } from '@yaakapp-internal/git';
import { showToast } from '../../lib/toast';
import type { PullResult, PushResult } from "@yaakapp-internal/git";
import { showToast } from "../../lib/toast";
export function handlePushResult(r: PushResult) {
switch (r.type) {
case 'needs_credentials':
showToast({ id: 'push-error', message: 'Credentials not found', color: 'danger' });
case "needs_credentials":
showToast({ id: "push-error", message: "Credentials not found", color: "danger" });
break;
case 'success':
showToast({ id: 'push-success', message: r.message, color: 'success' });
case "success":
showToast({ id: "push-success", message: r.message, color: "success" });
break;
case 'up_to_date':
showToast({ id: 'push-nothing', message: 'Already up-to-date', color: 'info' });
case "up_to_date":
showToast({ id: "push-nothing", message: "Already up-to-date", color: "info" });
break;
}
}
export function handlePullResult(r: PullResult) {
switch (r.type) {
case 'needs_credentials':
showToast({ id: 'pull-error', message: 'Credentials not found', color: 'danger' });
case "needs_credentials":
showToast({ id: "pull-error", message: "Credentials not found", color: "danger" });
break;
case 'success':
showToast({ id: 'pull-success', message: r.message, color: 'success' });
case "success":
showToast({ id: "pull-success", message: r.message, color: "success" });
break;
case 'up_to_date':
showToast({ id: 'pull-nothing', message: 'Already up-to-date', color: 'info' });
case "up_to_date":
showToast({ id: "pull-nothing", message: "Already up-to-date", color: "info" });
break;
case 'diverged':
case "diverged":
// Handled by mutation callback before reaching here
break;
case 'uncommitted_changes':
case "uncommitted_changes":
// Handled by mutation callback before reaching here
break;
}

View File

@@ -1,20 +1,20 @@
import type { GitRemote } from '@yaakapp-internal/git';
import { gitMutations } from '@yaakapp-internal/git';
import { showPromptForm } from '../../lib/prompt-form';
import { gitCallbacks } from './callbacks';
import type { GitRemote } from "@yaakapp-internal/git";
import { gitMutations } from "@yaakapp-internal/git";
import { showPromptForm } from "../../lib/prompt-form";
import { gitCallbacks } from "./callbacks";
export async function addGitRemote(dir: string, defaultName?: string): Promise<GitRemote> {
const r = await showPromptForm({
id: 'add-remote',
title: 'Add Remote',
id: "add-remote",
title: "Add Remote",
inputs: [
{ type: 'text', label: 'Name', name: 'name', defaultValue: defaultName },
{ type: 'text', label: 'URL', name: 'url' },
{ type: "text", label: "Name", name: "name", defaultValue: defaultName },
{ type: "text", label: "URL", name: "url" },
],
});
if (r == null) throw new Error('Cancelled remote prompt');
if (r == null) throw new Error("Cancelled remote prompt");
const name = String(r.name ?? '');
const url = String(r.url ?? '');
const name = String(r.name ?? "");
const url = String(r.url ?? "");
return gitMutations(dir, gitCallbacks(dir)).addRemote.mutateAsync({ name, url });
}

View File

@@ -1,13 +1,13 @@
import type { UncommittedChangesStrategy } from '@yaakapp-internal/git';
import { showConfirm } from '../../lib/confirm';
import type { UncommittedChangesStrategy } from "@yaakapp-internal/git";
import { showConfirm } from "../../lib/confirm";
export async function promptUncommittedChangesStrategy(): Promise<UncommittedChangesStrategy> {
const confirmed = await showConfirm({
id: 'git-uncommitted-changes',
title: 'Uncommitted Changes',
description: 'You have uncommitted changes. Commit or reset your changes before pulling.',
confirmText: 'Reset and Pull',
color: 'danger',
id: "git-uncommitted-changes",
title: "Uncommitted Changes",
description: "You have uncommitted changes. Commit or reset your changes before pulling.",
confirmText: "Reset and Pull",
color: "danger",
});
return confirmed ? 'reset' : 'cancel';
return confirmed ? "reset" : "cancel";
}