From 913ab4627c8488b874c784943afdeb7d8cbd405f Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 11 Jun 2024 16:28:02 +0200 Subject: [PATCH] Webview: init 'open clan' workflow --- clanModules/matrix-synapse/default.nix | 1 - pkgs/clan-app/clan_app/views/webview.py | 19 ++- pkgs/clan-cli/clan_cli/api/directory.py | 2 +- pkgs/clan-cli/clan_cli/clan/create.py | 16 +- pkgs/webview-ui/app/mock.ts | 9 + pkgs/webview-ui/app/src/Routes.tsx | 6 + pkgs/webview-ui/app/src/api.ts | 7 - .../app/src/routes/clan/clanDetails.tsx | 161 ++++++++++++++++++ pkgs/webview-ui/app/src/routes/clan/view.tsx | 82 +++++++++ 9 files changed, 288 insertions(+), 15 deletions(-) create mode 100644 pkgs/webview-ui/app/src/routes/clan/clanDetails.tsx create mode 100644 pkgs/webview-ui/app/src/routes/clan/view.tsx diff --git a/clanModules/matrix-synapse/default.nix b/clanModules/matrix-synapse/default.nix index c6a1051f..3de37c61 100644 --- a/clanModules/matrix-synapse/default.nix +++ b/clanModules/matrix-synapse/default.nix @@ -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; }; diff --git a/pkgs/clan-app/clan_app/views/webview.py b/pkgs/clan-app/clan_app/views/webview.py index e5348a1e..054c6303 100644 --- a/pkgs/clan-app/clan_app/views/webview.py +++ b/pkgs/clan-app/clan_app/views/webview.py @@ -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 diff --git a/pkgs/clan-cli/clan_cli/api/directory.py b/pkgs/clan-cli/clan_cli/api/directory.py index d797f739..f08e34c0 100644 --- a/pkgs/clan-cli/clan_cli/api/directory.py +++ b/pkgs/clan-cli/clan_cli/api/directory.py @@ -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 diff --git a/pkgs/clan-cli/clan_cli/clan/create.py b/pkgs/clan-cli/clan_cli/clan/create.py index 5a35e58f..2b2f244a 100644 --- a/pkgs/clan-cli/clan_cli/clan/create.py +++ b/pkgs/clan-cli/clan_cli/clan/create.py @@ -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( diff --git a/pkgs/webview-ui/app/mock.ts b/pkgs/webview-ui/app/mock.ts index 1fa5399e..bcd1b6f8 100644 --- a/pkgs/webview-ui/app/mock.ts +++ b/pkgs/webview-ui/app/mock.ts @@ -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; }; diff --git a/pkgs/webview-ui/app/src/Routes.tsx b/pkgs/webview-ui/app/src/Routes.tsx index a726ed4e..feee4144 100644 --- a/pkgs/webview-ui/app/src/Routes.tsx +++ b/pkgs/webview-ui/app/src/Routes.tsx @@ -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", diff --git a/pkgs/webview-ui/app/src/api.ts b/pkgs/webview-ui/app/src/api.ts index 8ef28fb1..805fb3a3 100644 --- a/pkgs/webview-ui/app/src/api.ts +++ b/pkgs/webview-ui/app/src/api.ts @@ -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 }; diff --git a/pkgs/webview-ui/app/src/routes/clan/clanDetails.tsx b/pkgs/webview-ui/app/src/routes/clan/clanDetails.tsx new file mode 100644 index 00000000..50746c1b --- /dev/null +++ b/pkgs/webview-ui/app/src/routes/clan/clanDetails.tsx @@ -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 ( +
+
+ Clan Logo +
+
+
+

+ setEditing(null)} + type="text" + value={meta?.name} + onInput={(e) => { + console.log(e.currentTarget.value); + }} + /> + + + +

+
+ description + {meta?.description || "No description"} +
+ +
+ folder + {directory} +
+
+ {actions} +
+
+
+ ); +}; + +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(); + + 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 ( + + + + + + + } + /> + + + + + {(item) => ( +
+ {item.message} + {item.description} + {item.location} +
+ )} +
+
+
+ ); +}; diff --git a/pkgs/webview-ui/app/src/routes/clan/view.tsx b/pkgs/webview-ui/app/src/routes/clan/view.tsx new file mode 100644 index 00000000..e703e4c7 --- /dev/null +++ b/pkgs/webview-ui/app/src/routes/clan/view.tsx @@ -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(null); + + createEffect(() => { + console.log(mode()); + }); + return ( +
+ + +
+ + +
+
+ + + + + + +
+ } + meta={{ + name: "New Clan", + description: "nice description", + icon: "select icon", + }} + editable + /> + + + + ); +};