Webview: init 'open clan' workflow
All checks were successful
buildbot/nix-build .#checks.aarch64-darwin.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-archlinux Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-deb Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-e2fsprogs Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-rpm Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-fakeroot Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-iso-installer Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-flash-installer Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-app-no-breakpoints Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-flash-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.check-for-breakpoints Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-age Build done.
buildbot/nix-build .#checks.x86_64-linux.package-default Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-cli-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-ts-api Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-bash Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-apk Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-test-backup Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-default Build done.
buildbot/nix-build .#checks.x86_64-linux.container Build done.
buildbot/nix-build .#checks.x86_64-linux.deltachat Build done.
buildbot/nix-build .#checks.x86_64-linux.package-editor Build done.
buildbot/nix-build .#checks.x86_64-linux.matrix-synapse Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-app-pytest Build done.
buildbot/nix-build .#checks.x86_64-linux.package-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.treefmt Build done.
buildbot/nix-build .#checks.x86_64-linux.package-tea-create-pr Build done.
buildbot/nix-build .#checks.x86_64-linux.lib-jsonschema-nix-unit-tests Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zerotier-members Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zerotierone Build done.
buildbot/nix-build .#checks.x86_64-linux.package-deploy-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.package-module-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-git Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-nix Build done.
buildbot/nix-build .#checks.x86_64-linux.renderClanOptions Build done.
buildbot/nix-build .#checks.x86_64-linux."clan-dep-python3.11-qemu" Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-rsync Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-webview-ui Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-sops Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-test-backup Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-sshpass Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-cli Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-tor Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-zbar Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-iso-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-openssh Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zt-tcp-relay Build done.
buildbot/nix-build .#checks.x86_64-linux."clan-dep-python3.11-mypy" Build done.
buildbot/nix-build .#checks.x86_64-linux.lib-jsonschema-example-valid Build done.
buildbot/nix-build .#checks.x86_64-linux.borgbackup Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-clan-cli Build done.
buildbot/nix-build .#checks.x86_64-linux.module-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.package-impure-checks Build done.
buildbot/nix-build .#checks.x86_64-linux.package-merge-after-ci Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-test-backup Build done.
buildbot/nix-build .#checks.x86_64-linux.package-moonlight-sunshine-accept Build done.
buildbot/nix-build .#checks.x86_64-linux.package-pending-reviews Build done.
buildbot/nix-build .#checks.x86_64-linux.secrets Build done.
buildbot/nix-build .#checks.x86_64-linux.wayland-proxy-virtwl Build done.
buildbot/nix-build .#checks.x86_64-linux.syncthing Build done.
buildbot/nix-build .#checks.x86_64-linux.package-function-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.postgresql Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-flash-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.package-module-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-iso-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.template-minimal Build done.
buildbot/nix-build .#checks.x86_64-linux.flash Build done.
buildbot/nix-build .#checks.x86_64-linux.test-installation Build done.
buildbot/nix-eval Build done.
buildbot/nix-build .#checks.x86_64-linux.zt-tcp-relay Build done.
buildbot/nix-build .#checks.x86_64-linux.package-webview-ui Build done.
buildbot/nix-build .#checks.x86_64-linux.package-iso-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-clan-app Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-pytest-without-core Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-app Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-install-test-ubuntu-22-04 Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-pytest-with-core Build done.
buildbot/nix-build .#checks.x86_64-linux.test-backups Build done.
checks / checks-impure (pull_request) Successful in 2m16s

This commit is contained in:
Johannes Kirschbauer 2024-06-11 16:28:02 +02:00
parent be868ee107
commit 913ab4627c
Signed by: hsjobeki
SSH Key Fingerprint: SHA256:vX3utDqig7Ph5L0JPv87ZTPb/w7cMzREKVZzzLFg9qU
9 changed files with 288 additions and 15 deletions

View File

@ -29,7 +29,6 @@ let
++ lib.optional (synapseCfg.settings ? user_directory) "user-search"
++ lib.optional (synapseCfg.settings.url_preview_enabled) "url-preview"
++ lib.optional (synapseCfg.settings.database.name == "psycopg2") "postgres";
in
{
options.services.matrix-synapse.package = lib.mkOption { readOnly = false; };

View File

@ -71,6 +71,19 @@ def open_file(file_request: FileRequest) -> str | None:
finally:
main_loop.quit()
def on_save_finish(
file_dialog: Gtk.FileDialog, task: Gio.Task, main_loop: GLib.MainLoop
) -> None:
try:
gfile = file_dialog.save_finish(task)
if gfile:
nonlocal selected_path
selected_path = gfile.get_path()
except Exception as e:
print(f"Error getting selected file: {e}")
finally:
main_loop.quit()
dialog = Gtk.FileDialog()
if file_request.title:
@ -105,12 +118,16 @@ def open_file(file_request: FileRequest) -> str | None:
# if select_folder
if file_request.mode == "select_folder":
dialog.select_folder(
callback=lambda dialog, task: on_folder_select(dialog, task, main_loop)
callback=lambda dialog, task: on_folder_select(dialog, task, main_loop),
)
elif file_request.mode == "open_file":
dialog.open(
callback=lambda dialog, task: on_file_select(dialog, task, main_loop)
)
elif file_request.mode == "save":
dialog.save(
callback=lambda dialog, task: on_save_finish(dialog, task, main_loop)
)
# Wait for the user to select a file or directory
main_loop.run() # type: ignore

View File

@ -19,7 +19,7 @@ class FileFilter:
@dataclass
class FileRequest:
# Mode of the os dialog window
mode: Literal["open_file", "select_folder"]
mode: Literal["open_file", "select_folder", "save"]
# Title of the os dialog window
title: str | None = None
# Pre-applied filters for the file dialog

View File

@ -1,6 +1,7 @@
# !/usr/bin/env python3
import argparse
import json
import os
from dataclasses import dataclass, fields
from pathlib import Path
@ -47,11 +48,16 @@ def create_clan(options: CreateOptions) -> CreateClanResponse:
if not directory.exists():
directory.mkdir()
else:
raise ClanError(
location=f"{directory.resolve()}",
msg="Cannot create clan",
description="Directory already exists",
)
# Directory already exists
# Check if it is empty
# Throw error otherwise
dir_content = os.listdir(directory)
if len(dir_content) != 0:
raise ClanError(
location=f"{directory.resolve()}",
msg="Cannot create clan",
description="Directory already exists and is not empty.",
)
cmd_responses = {}
command = nix_command(

View File

@ -6,10 +6,19 @@ const faker = JSONSchemaFaker;
faker.option({
alwaysFakeOptionals: true,
useExamplesValue: true,
});
const getFakeResponse = (method: OperationNames, data: any) => {
const fakeData = faker.generate(schema.properties[method].properties.return);
if (method === "open_file") {
return {
status: "success",
data: "/path/to/clan",
};
}
return fakeData;
};

View File

@ -1,10 +1,16 @@
import { Accessor, For, Match, Switch } from "solid-js";
import { MachineListView } from "./routes/machines/view";
import { colors } from "./routes/colors/view";
import { clan } from "./routes/clan/view";
export type Route = keyof typeof routes;
export const routes = {
clan: {
child: clan,
label: "Clan",
icon: "groups",
},
machines: {
child: MachineListView,
label: "Machines",

View File

@ -87,11 +87,4 @@ operationNames.forEach((opName) => {
pyApi[name] = createFunctions(name);
});
pyApi.open_file.receive((r) => {
const { status } = r;
if (status === "error") return console.error(r.errors);
console.log(r.data);
alert(r.data);
});
export { pyApi };

View File

@ -0,0 +1,161 @@
import { OperationResponse, pyApi } from "@/src/api";
import {
For,
JSX,
Match,
Show,
Switch,
createEffect,
createSignal,
} from "solid-js";
import cx from "classnames";
interface ClanDetailsProps {
directory: string;
}
interface MetaFieldsProps {
meta: ClanMeta;
actions: JSX.Element;
editable?: boolean;
directory?: string;
}
const fn = (e: SubmitEvent) => {
e.preventDefault();
console.log(e.currentTarget);
};
export const EditMetaFields = (props: MetaFieldsProps) => {
const { meta, editable, actions, directory } = props;
const [editing, setEditing] = createSignal<
keyof MetaFieldsProps["meta"] | null
>(null);
return (
<div class="card card-compact w-96 bg-base-100 shadow-xl">
<figure>
<img
src="https://www.shutterstock.com/image-vector/modern-professional-ninja-mascot-logo-260nw-1729854862.jpg"
alt="Clan Logo"
/>
</figure>
<div class="card-body">
<form onSubmit={fn}>
<h2 class="card-title justify-between">
<input
classList={{
[cx("text-slate-600")]: editing() !== "name",
}}
readOnly={editing() !== "name"}
class="w-full"
autofocus
onBlur={() => setEditing(null)}
type="text"
value={meta?.name}
onInput={(e) => {
console.log(e.currentTarget.value);
}}
/>
<Show when={editable}>
<button class="btn btn-square btn-ghost btn-sm">
<span
class="material-icons"
onClick={() => {
if (editing() !== "name") setEditing("name");
else {
setEditing(null);
}
}}
>
<Show when={editing() !== "name"} fallback="check">
edit
</Show>
</span>
</button>
</Show>
</h2>
<div class="flex gap-1 align-middle leading-8">
<i class="material-icons">description</i>
<span>{meta?.description || "No description"}</span>
</div>
<Show when={directory}>
<div class="flex gap-1 align-middle leading-8">
<i class="material-icons">folder</i>
<span>{directory}</span>
</div>
</Show>
{actions}
</form>
</div>
</div>
);
};
type ClanMeta = Extract<
OperationResponse<"show_clan_meta">,
{ status: "success" }
>["data"];
export const ClanDetails = (props: ClanDetailsProps) => {
const { directory } = props;
const [, setLoading] = createSignal(false);
const [errors, setErrors] = createSignal<
| Extract<
OperationResponse<"show_clan_meta">,
{ status: "error" }
>["errors"]
| null
>(null);
const [data, setData] = createSignal<ClanMeta>();
const loadMeta = () => {
pyApi.show_clan_meta.dispatch({ uri: directory });
setLoading(true);
};
createEffect(() => {
loadMeta();
pyApi.show_clan_meta.receive((response) => {
setLoading(false);
if (response.status === "error") {
setErrors(response.errors);
return console.error(response.errors);
}
setData(response.data);
});
});
return (
<Switch fallback={"loading"}>
<Match when={data()}>
<EditMetaFields
directory={directory}
// @ts-expect-error: TODO: figure out how solid allows type narrowing this
meta={data()}
actions={
<div class="card-actions justify-between">
<button class="btn btn-link" onClick={() => loadMeta()}>
Refresh
</button>
<button class="btn btn-primary">Open</button>
</div>
}
/>
</Match>
<Match when={errors()}>
<button class="btn btn-link" onClick={() => loadMeta()}>
Retry
</button>
<For each={errors()}>
{(item) => (
<div class="flex flex-col gap-3">
<span class="bg-red-400 text-white">{item.message}</span>
<span class="bg-red-400 text-white">{item.description}</span>
<span class="bg-red-400 text-white">{item.location}</span>
</div>
)}
</For>
</Match>
</Switch>
);
};

View File

@ -0,0 +1,82 @@
import { pyApi } from "@/src/api";
import { Match, Switch, createEffect, createSignal } from "solid-js";
import toast from "solid-toast";
import { ClanDetails, EditMetaFields } from "./clanDetails";
export const clan = () => {
const [mode, setMode] = createSignal<"init" | "open" | "create">("init");
const [clanDir, setClanDir] = createSignal<string | null>(null);
createEffect(() => {
console.log(mode());
});
return (
<div>
<Switch fallback={"invalid"}>
<Match when={mode() === "init"}>
<div class="flex gap-2">
<button class="btn btn-square" onclick={() => setMode("create")}>
<span class="material-icons">add</span>
</button>
<button
class="btn btn-square"
onclick={() => {
pyApi.open_file.dispatch({
file_request: {
mode: "select_folder",
title: "Open Clan",
},
});
pyApi.open_file.receive((r) => {
// There are two error cases to handle
if (r.status !== "success") {
console.error(r.errors);
toast.error("Error opening clan");
return;
}
// User didn't select anything
if (!r.data) {
setMode("init");
return;
}
setClanDir(r.data);
setMode("open");
});
}}
>
<span class="material-icons">folder_open</span>
</button>
</div>
</Match>
<Match when={mode() === "open"}>
<ClanDetails directory={clanDir() || ""} />
</Match>
<Match when={mode() === "create"}>
<EditMetaFields
actions={
<div class="card-actions justify-end">
<button
class="btn btn-primary"
onClick={() => {
pyApi.open_file.dispatch({
file_request: { mode: "save" },
});
}}
>
Save
</button>
</div>
}
meta={{
name: "New Clan",
description: "nice description",
icon: "select icon",
}}
editable
/>
</Match>
</Switch>
</div>
);
};