Skip to content

Commit

Permalink
fix(space): display toast if space is invalid on switch (#17)
Browse files Browse the repository at this point in the history
- replace non-reactive select with dropdown component for switcher
- safe run wrapper functions for sync and async
- reduce animations
  • Loading branch information
errmayank committed Sep 18, 2024
1 parent 9b6315f commit 0b6a48f
Show file tree
Hide file tree
Showing 13 changed files with 288 additions and 159 deletions.
5 changes: 5 additions & 0 deletions .changeset/tricky-kids-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"zaku": patch
---

Display toast if space is invalid on switch
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default [
"warn",
{ varsIgnorePattern: "^\\$\\$(Props|Events|Slots)$" },
],
"@typescript-eslint/no-explicit-any": "off",
},
},
];
52 changes: 50 additions & 2 deletions src/lib/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { spaceReferenceStruct, type SpaceReference } from "$lib/store";
import { Struct } from "$lib/utils/struct";
import { invoke } from "@tauri-apps/api/core";
import { isValiError, parse as vParse } from "valibot";
import type { InvokeArgs, InvokeOptions } from "@tauri-apps/api/core";
import type { BaseIssue, BaseSchema, InferOutput } from "valibot";

import { spaceReferenceStruct, zakuErrorStruct } from "$lib/store";
import { Err, Ok, Struct } from "$lib/utils/struct";
import type { SpaceReference, ZakuError } from "$lib/store";
import type { Result } from "$lib/utils/struct";

export type OpenDirectoryDialogOptions = {
title?: string;
Expand Down Expand Up @@ -40,3 +46,45 @@ export async function getSpaceReference(path: string): Promise<SpaceReference> {

return Struct.parse(spaceReferenceStruct, spaceReferenceRawResult);
}

export function safeParse<const TSchema extends BaseSchema<unknown, unknown, BaseIssue<unknown>>>(
schema: TSchema,
input: unknown,
): Result<InferOutput<TSchema>> {
try {
const parsedResult = vParse(schema, input);

return Ok(parsedResult);
} catch (err) {
return Err(err);
}
}

export type InvokeCommand = "get_zaku_state" | "set_active_space";

export async function safeInvoke<
const TSchema extends BaseSchema<unknown, unknown, BaseIssue<unknown>>,
>(
schema: TSchema,
command: InvokeCommand,
args?: InvokeArgs,
options?: InvokeOptions,
): Promise<Result<InferOutput<TSchema>, ZakuError>> {
try {
const result = await invoke(command, args, options);
const parsedResult = vParse(schema, result);

return Ok(parsedResult);
} catch (err) {
if (isValiError(err)) {
return Err({ error: err.message, message: "Something went wrong." });
} else {
const zakuError = safeParse(zakuErrorStruct, err);
const parsedZakuError: ZakuError = zakuError.ok
? zakuError.value
: { error: "Unknown", message: "Something went wrong." };

return Err(parsedZakuError);
}
}
}
4 changes: 2 additions & 2 deletions src/lib/components/primitives/button/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type VariantProps, tv } from "tailwind-variants";
import Root from "./button.svelte";

const buttonVariants = tv({
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-small font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-small font-medium focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
variants: {
variant: {
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
Expand All @@ -20,7 +20,7 @@ const buttonVariants = tv({
default: "h-6 px-3 py-2",
sm: "h-5 rounded-md px-3 text-xs",
lg: "h-9 rounded-md px-8",
icon: "size-6",
icon: "size-6 min-h-6 min-w-6 max-h-6 max-w-6",
},
},
defaultVariants: {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/primitives/input/input.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

<input
class={cn(
"flex h-6 w-full rounded-md border border-input bg-transparent px-3 py-1 text-small shadow-sm transition-colors file:border-0 file:bg-transparent file:text-small file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
"flex h-6 w-full rounded-md border border-input bg-transparent px-3 py-1 text-small shadow-sm file:border-0 file:bg-transparent file:text-small file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
bind:value
Expand Down
114 changes: 36 additions & 78 deletions src/lib/components/sidebar/sidebar.svelte
Original file line number Diff line number Diff line change
@@ -1,52 +1,44 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { zakuState } from "$lib/store";
import { Button } from "$lib/components/primitives/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "$lib/components/primitives/dropdown-menu";
import type { PaneAPI } from "paneforge";
import { CookieIcon, SettingsIcon, Trash2Icon, PlusIcon } from "lucide-svelte";
import { SpaceCreateDialog, SpaceSwitcher } from "$lib/components/space";
import { CookieIcon, SettingsIcon, PanelLeftIcon } from "lucide-svelte";
import { SpaceSwitcher } from "$lib/components/space";
import { cn } from "$lib/utils/style";
import { dispatchNotification, getSpaceReference, openDirectoryDialog } from "$lib/commands";
export let isCollapsed = false;
let isCreateSpaceDialogOpen = false;
async function handleOpenExistingSpace() {
try {
const selectedPath = await openDirectoryDialog({ title: "Open an existing Space" });
if (selectedPath !== null) {
const spaceReference = await getSpaceReference(selectedPath);
await zakuState.set(spaceReference);
await goto("/space");
}
} catch (err) {
console.error(err);
await dispatchNotification({
title: "Doesn't look like a valid space.",
body: "Unable to parse the directory, make sure it is a valid space and try again.",
});
}
}
async function handleDelete() {
if ($zakuState.active_space) {
await zakuState.delete($zakuState.active_space.path);
}
}
export let pane: PaneAPI;
export let isCollapsed: boolean;
</script>

{#if $zakuState.active_space}
<div class="flex size-full flex-col justify-between">
<div class="flex w-full items-center justify-center border-b p-1.5">
<SpaceSwitcher {isCollapsed} />
<div class="mt-1.5 flex w-full items-center justify-center border-b p-1.5">
<div
class={cn(
"flex w-full items-center justify-between gap-1.5",
isCollapsed ? "flex-col" : "flex-row",
)}
>
<div class="flex-grow overflow-hidden text-ellipsis whitespace-nowrap">
<SpaceSwitcher isSidebarCollapsed={isCollapsed} />
</div>
<Button
variant="ghost"
size="icon"
on:click={() => {
if (isCollapsed) {
pane.expand();
pane.resize(24);
} else {
pane.collapse();
}
}}
class="flex-shrink-0"
>
<PanelLeftIcon size={14} class="min-h-[14px] min-w-[14px]" />
</Button>
</div>
</div>
<div class="flex-grow overflow-y-auto p-1.5">
{#if !isCollapsed}
Expand All @@ -61,47 +53,13 @@
isCollapsed && "flex-col-reverse",
)}
>
<DropdownMenu>
<DropdownMenuTrigger asChild let:builder>
<Button builders={[builder]} size="icon" variant="ghost-hover">
<SettingsIcon strokeWidth={1.25} size={16} />
<span class="sr-only">Settings</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
on:click={() => {
isCreateSpaceDialogOpen = true;
}}
>
<div class="flex items-center gap-1.5 text-small">
<PlusIcon strokeWidth={2.25} size={13} />
<span>Create new Space</span>
</div>
</DropdownMenuItem>
<DropdownMenuItem on:click={handleOpenExistingSpace}>
<div class="flex items-center gap-1.5 text-small">
<PlusIcon strokeWidth={2.25} size={13} />
<span>Open existing Space</span>
</div>
</DropdownMenuItem>
<DropdownMenuItem on:click={handleDelete}>
<div class="flex items-center gap-1.5 text-small text-destructive">
<Trash2Icon strokeWidth={2.25} size={13} />
<span>Delete space</span>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button size="icon" variant="ghost-hover">
<Button size="icon" variant="ghost">
<SettingsIcon strokeWidth={1.25} size={16} />
<span class="sr-only">Settings</span>
</Button>
<Button size="icon" variant="ghost">
<CookieIcon size={14} />
</Button>
</div>
</div>
<SpaceCreateDialog
bind:isOpen={isCreateSpaceDialogOpen}
onCreate={async () => {
isCreateSpaceDialogOpen = false;
}}
/>
{/if}
Loading

0 comments on commit 0b6a48f

Please sign in to comment.