API: init icon resolve
All checks were successful
buildbot/nix-build .#checks.aarch64-darwin.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-flash-installer Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-iso-installer Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-flash-installer Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-iso-installer 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-app-no-breakpoints Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-deb Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-rpm Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-age Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-archlinux Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-bash Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-e2fsprogs Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-fakeroot Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-test-backup Build done.
buildbot/nix-build .#checks.x86_64-linux.package-module-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.renderClanOptions 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.devShell-webview-ui Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-openssh Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-cli Build done.
buildbot/nix-build .#checks.x86_64-linux.check-for-breakpoints Build done.
buildbot/nix-build .#checks.x86_64-linux."clan-dep-python3.11-mypy" Build done.
buildbot/nix-build .#checks.x86_64-linux.package-default Build done.
buildbot/nix-build .#checks.x86_64-linux."clan-dep-python3.11-qemu" 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.clan-dep-sops Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-zbar 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.clan-app-pytest Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.x86_64-linux.treefmt Build done.
buildbot/nix-build .#checks.x86_64-linux.package-editor Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-rsync Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-sshpass Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-tor Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-default Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-clan-cli 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.aarch64-linux.nixos-test-backup Build done.
buildbot/nix-build .#checks.x86_64-linux.matrix-synapse Build done.
buildbot/nix-build .#checks.x86_64-linux.module-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.package-docs 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.lib-jsonschema-nix-unit-tests Build done.
buildbot/nix-build .#checks.x86_64-linux.package-moonlight-sunshine-accept Build done.
buildbot/nix-build .#checks.x86_64-linux.package-tea-create-pr Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zerotier-members Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zt-tcp-relay Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zerotierone Build done.
buildbot/nix-build .#checks.x86_64-linux.postgresql Build done.
buildbot/nix-build .#checks.x86_64-linux.package-pending-reviews Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-flash-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-iso-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.package-module-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.package-function-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.template-minimal Build done.
buildbot/nix-build .#checks.x86_64-linux.package-iso-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.package-deploy-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.secrets Build done.
buildbot/nix-build .#checks.x86_64-linux.zt-tcp-relay 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.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-webview-ui 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 2m14s
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.

This commit is contained in:
Johannes Kirschbauer 2024-06-12 13:21:25 +02:00
parent 1f3c4f4ac3
commit 6743ff96a9
Signed by: hsjobeki
SSH Key Fingerprint: SHA256:vX3utDqig7Ph5L0JPv87ZTPb/w7cMzREKVZzzLFg9qU
8 changed files with 181 additions and 183 deletions

View File

@ -2,6 +2,7 @@ import argparse
import json import json
import logging import logging
from pathlib import Path from pathlib import Path
from urllib.parse import urlparse
from clan_cli.api import API from clan_cli.api import API
from clan_cli.clan.create import ClanMetaInfo from clan_cli.clan.create import ClanMetaInfo
@ -22,6 +23,7 @@ def show_clan_meta(uri: str | Path) -> ClanMetaInfo:
] ]
) )
res = "{}" res = "{}"
try: try:
proc = run_no_stdout(cmd) proc = run_no_stdout(cmd)
res = proc.stdout.strip() res = proc.stdout.strip()
@ -33,10 +35,36 @@ def show_clan_meta(uri: str | Path) -> ClanMetaInfo:
) )
clan_meta = json.loads(res) clan_meta = json.loads(res)
# Check if icon is a URL such as http:// or https://
# Check if icon is an relative path
# All other schemas such as file://, ftp:// are not yet supported.
icon_path: str | None = None
if meta_icon := clan_meta.get("icon"):
scheme = urlparse(meta_icon).scheme
if scheme in ["http", "https"]:
icon_path = meta_icon
elif scheme in [""]:
if Path(meta_icon).is_absolute():
raise ClanError(
"Invalid absolute path",
location=f"show_clan {uri}",
description="Icon path must be a URL or a relative path.",
)
else:
icon_path = str((Path(uri) / meta_icon).resolve())
else:
raise ClanError(
"Invalid schema",
location=f"show_clan {uri}",
description="Icon path must be a URL or a relative path.",
)
return ClanMetaInfo( return ClanMetaInfo(
name=clan_meta.get("name"), name=clan_meta.get("name"),
description=clan_meta.get("description", None), description=clan_meta.get("description", None),
icon=clan_meta.get("icon", None), icon=icon_path,
) )

View File

@ -1,13 +1,12 @@
const fs = require("fs"); import fs from "node:fs";
const path = require("path"); import postcss from "postcss";
import path from "node:path";
import css_url from "postcss-url";
const distPath = path.resolve(__dirname, "dist"); const distPath = path.resolve("dist");
const manifestPath = path.join(distPath, ".vite/manifest.json"); const manifestPath = path.join(distPath, ".vite/manifest.json");
const outputPath = path.join(distPath, "index.html"); const outputPath = path.join(distPath, "index.html");
const postcss = require("postcss");
const css_url = require("postcss-url");
fs.readFile(manifestPath, { encoding: "utf8" }, (err, data) => { fs.readFile(manifestPath, { encoding: "utf8" }, (err, data) => {
if (err) { if (err) {
return console.error("Failed to read manifest:", err); return console.error("Failed to read manifest:", err);

View File

@ -2,6 +2,7 @@
"name": "@clan/webview-ui", "name": "@clan/webview-ui",
"version": "0.0.1", "version": "0.0.1",
"description": "", "description": "",
"type": "module",
"scripts": { "scripts": {
"start": "vite", "start": "vite",
"dev": "vite", "dev": "vite",

View File

@ -1,6 +1,8 @@
module.exports = { const config = {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
}; };
export default config;

View File

@ -17,6 +17,8 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
console.log(import.meta.env); console.log(import.meta.env);
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.log("Development mode"); console.log("Development mode");
// Load the debugger in development mode
await import("solid-devtools");
window.webkit = window.webkit || { window.webkit = window.webkit || {
messageHandlers: { messageHandlers: {
gtk: { gtk: {
@ -28,12 +30,11 @@ if (import.meta.env.DEV) {
console.log("mock", { mock }); console.log("mock", { mock });
window.clan[method](JSON.stringify(mock)); window.clan[method](JSON.stringify(mock));
}, 1000); }, 200);
}, },
}, },
}, },
}; };
} }
postMessage;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
render(() => <App />, root!); render(() => <App />, root!);

View File

@ -19,21 +19,17 @@ interface ClanDetailsProps {
directory: string; directory: string;
} }
interface MetaFieldsProps { interface ClanFormProps {
directory?: string;
meta: ClanMeta; meta: ClanMeta;
actions: JSX.Element; actions: JSX.Element;
editable?: boolean; editable?: boolean;
directory?: string;
} }
const fn = (e: SubmitEvent) => { export const ClanForm = (props: ClanFormProps) => {
e.preventDefault(); const { meta, actions, editable = true, directory } = props;
console.log("form submit", e.currentTarget); const [formStore, { Form, Field }] = createForm<ClanMeta>({
}; initialValues: meta,
export default function Login() {
const [, { Form, Field }] = createForm<ClanMeta>({
initialValues: { name: "MyClan" },
}); });
const handleSubmit: SubmitHandler<ClanMeta> = (values, event) => { const handleSubmit: SubmitHandler<ClanMeta> = (values, event) => {
@ -52,156 +48,120 @@ export default function Login() {
console.log("submit", values); console.log("submit", values);
}; };
return ( return (
<div class="card-body"> <div class="card card-compact w-96 bg-base-100 shadow-xl">
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
<Field
name="name"
validate={[required("Please enter a unique name for the clan.")]}
>
{(field, props) => (
<label class="form-control w-full max-w-xs">
<div class="label">
<span class="label-text block after:ml-0.5 after:text-primary after:content-['*']">
Name
</span>
</div>
<input
{...props}
type="email"
required
placeholder="your.mail@example.com"
class="input w-full max-w-xs"
classList={{ "input-error": !!field.error }}
value={field.value}
/>
<div class="label">
{field.error && (
<span class="label-text-alt">{field.error}</span>
)}
</div>
</label>
)}
</Field>
<Field name="description">
{(field, props) => (
<label class="form-control w-full max-w-xs">
<div class="label">
<span class="label-text">Description</span>
</div>
<input
{...props}
required
type="text"
placeholder="Some words about your clan"
class="input w-full max-w-xs"
classList={{ "input-error": !!field.error }}
value={field.value || ""}
/>
<div class="label">
{field.error && (
<span class="label-text-alt">{field.error}</span>
)}
</div>
</label>
)}
</Field>
<Field name="icon"> <Field name="icon">
{(field, props) => ( {(field, props) => (
<label class="form-control w-full max-w-xs"> <>
<div class="label"> <figure>
<span class="label-text">Select icon</span> <Show
</div> when={field.value}
<input fallback={
type="file" <span class="material-icons aspect-square size-60 rounded-lg text-[18rem]">
class="file-input file-input-bordered w-full max-w-xs" group
/> </span>
<div class="label"> }
{field.error && ( >
<span class="label-text-alt">{field.error}</span> {(icon) => (
)} <img
</div> class="aspect-square size-60 rounded-lg"
</label> src={icon()}
alt="Clan Logo"
/>
)}
</Show>
</figure>
<label class="form-control w-full max-w-xs">
<div class="label">
<span class="label-text">Select icon</span>
</div>
<input
type="file"
class="file-input file-input-bordered w-full max-w-xs"
/>
<div class="label">
{field.error && (
<span class="label-text-alt">{field.error}</span>
)}
</div>
</label>
</>
)} )}
</Field> </Field>
<div class="card-actions justify-end"> <div class="card-body">
<button class="btn btn-primary" type="submit"> <div class="card-body">
Create <Field
</button> name="name"
validate={[required("Please enter a unique name for the clan.")]}
>
{(field, props) => (
<label class="form-control w-full max-w-xs">
<div class="label">
<span class="label-text block after:ml-0.5 after:text-primary after:content-['*']">
Name
</span>
</div>
<input
{...props}
type="email"
required
placeholder="your.mail@example.com"
class="input w-full max-w-xs"
classList={{ "input-error": !!field.error }}
value={field.value}
/>
<div class="label">
{field.error && (
<span class="label-text-alt">{field.error}</span>
)}
</div>
</label>
)}
</Field>
<Field name="description">
{(field, props) => (
<label class="form-control w-full max-w-xs">
<div class="label">
<span class="label-text">Description</span>
</div>
<input
{...props}
required
type="text"
placeholder="Some words about your clan"
class="input w-full max-w-xs"
classList={{ "input-error": !!field.error }}
value={field.value || ""}
/>
<div class="label">
{field.error && (
<span class="label-text-alt">{field.error}</span>
)}
</div>
</label>
)}
</Field>
{actions}
</div>
</div> </div>
</Form> </Form>
</div> </div>
); );
}
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">
<Login />
{/* <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>
);
}; };
// export const EditMetaFields = (props: MetaFieldsProps) => {
// const { meta, editable, actions, directory } = props;
// const [editing, setEditing] = createSignal<
// keyof MetaFieldsProps["meta"] | null
// >(null);
// return (
// );
// };
type ClanMeta = Extract< type ClanMeta = Extract<
OperationResponse<"show_clan_meta">, OperationResponse<"show_clan_meta">,
{ status: "success" } { status: "success" }
@ -209,7 +169,7 @@ type ClanMeta = Extract<
export const ClanDetails = (props: ClanDetailsProps) => { export const ClanDetails = (props: ClanDetailsProps) => {
const { directory } = props; const { directory } = props;
const [, setLoading] = createSignal(false); const [loading, setLoading] = createSignal(false);
const [errors, setErrors] = createSignal< const [errors, setErrors] = createSignal<
| Extract< | Extract<
OperationResponse<"show_clan_meta">, OperationResponse<"show_clan_meta">,
@ -237,23 +197,30 @@ export const ClanDetails = (props: ClanDetailsProps) => {
}); });
return ( return (
<Switch fallback={"loading"}> <Switch fallback={"loading"}>
<Match when={loading()}>
<div>Loading</div>
</Match>
<Match when={data()}> <Match when={data()}>
<EditMetaFields {(data) => {
directory={directory} const meta = data();
// @ts-expect-error: TODO: figure out how solid allows type narrowing this return (
meta={data()} <ClanForm
actions={ directory={directory}
<div class="card-actions justify-between"> meta={meta}
<button class="btn btn-link" onClick={() => loadMeta()}> actions={
Refresh <div class="card-actions justify-between">
</button> <button class="btn btn-link" onClick={() => loadMeta()}>
<button class="btn btn-primary">Open</button> Refresh
</div> </button>
} <button class="btn btn-primary">Open</button>
/> </div>
}
/>
);
}}
</Match> </Match>
<Match when={errors()}> <Match when={errors()}>
<button class="btn btn-link" onClick={() => loadMeta()}> <button class="btn btn-secondary" onClick={() => loadMeta()}>
Retry Retry
</button> </button>
<For each={errors()}> <For each={errors()}>

View File

@ -1,7 +1,7 @@
import { pyApi } from "@/src/api"; import { pyApi } from "@/src/api";
import { Match, Switch, createEffect, createSignal } from "solid-js"; import { Match, Switch, createEffect, createSignal } from "solid-js";
import toast from "solid-toast"; import toast from "solid-toast";
import { ClanDetails, EditMetaFields } from "./clanDetails"; import { ClanDetails, ClanForm } from "./clanDetails";
export const clan = () => { export const clan = () => {
const [mode, setMode] = createSignal<"init" | "open" | "create">("init"); const [mode, setMode] = createSignal<"init" | "open" | "create">("init");
@ -53,16 +53,16 @@ export const clan = () => {
<ClanDetails directory={clanDir() || ""} /> <ClanDetails directory={clanDir() || ""} />
</Match> </Match>
<Match when={mode() === "create"}> <Match when={mode() === "create"}>
<EditMetaFields <ClanForm
actions={ actions={
<div class="card-actions justify-end"> <div class="card-actions justify-end">
<button <button
class="btn btn-primary" class="btn btn-primary"
onClick={() => { // onClick={() => {
pyApi.open_file.dispatch({ // pyApi.open_file.dispatch({
file_request: { mode: "save" }, // file_request: { mode: "save" },
}); // });
}} // }}
> >
Save Save
</button> </button>

View File

@ -1,6 +1,6 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid"; import solidPlugin from "vite-plugin-solid";
// import devtools from "solid-devtools/vite"; import devtools from "solid-devtools/vite";
import path from "node:path"; import path from "node:path";
export default defineConfig({ export default defineConfig({
@ -14,7 +14,7 @@ export default defineConfig({
Uncomment the following line to enable solid-devtools. Uncomment the following line to enable solid-devtools.
For more info see https://github.com/thetarnav/solid-devtools/tree/main/packages/extension#readme For more info see https://github.com/thetarnav/solid-devtools/tree/main/packages/extension#readme
*/ */
// devtools(), devtools(),
solidPlugin(), solidPlugin(),
], ],
server: { server: {