forked from clan/clan-core
Merge pull request 'API: init icon resolve' (#1616) from hsjobeki/clan-core:hsjobeki-main into main
This commit is contained in:
commit
83056f743d
@ -2,6 +2,7 @@ import argparse
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from clan_cli.api import API
|
||||
from clan_cli.clan.create import ClanMetaInfo
|
||||
@ -22,6 +23,7 @@ def show_clan_meta(uri: str | Path) -> ClanMetaInfo:
|
||||
]
|
||||
)
|
||||
res = "{}"
|
||||
|
||||
try:
|
||||
proc = run_no_stdout(cmd)
|
||||
res = proc.stdout.strip()
|
||||
@ -33,10 +35,36 @@ def show_clan_meta(uri: str | Path) -> ClanMetaInfo:
|
||||
)
|
||||
|
||||
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(
|
||||
name=clan_meta.get("name"),
|
||||
description=clan_meta.get("description", None),
|
||||
icon=clan_meta.get("icon", None),
|
||||
icon=icon_path,
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,13 +1,12 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
import fs from "node:fs";
|
||||
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 outputPath = path.join(distPath, "index.html");
|
||||
|
||||
const postcss = require("postcss");
|
||||
const css_url = require("postcss-url");
|
||||
|
||||
fs.readFile(manifestPath, { encoding: "utf8" }, (err, data) => {
|
||||
if (err) {
|
||||
return console.error("Failed to read manifest:", err);
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "@clan/webview-ui",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"dev": "vite",
|
||||
|
@ -1,6 +1,8 @@
|
||||
module.exports = {
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
@ -17,6 +17,8 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||
console.log(import.meta.env);
|
||||
if (import.meta.env.DEV) {
|
||||
console.log("Development mode");
|
||||
// Load the debugger in development mode
|
||||
await import("solid-devtools");
|
||||
window.webkit = window.webkit || {
|
||||
messageHandlers: {
|
||||
gtk: {
|
||||
@ -28,12 +30,11 @@ if (import.meta.env.DEV) {
|
||||
console.log("mock", { mock });
|
||||
|
||||
window.clan[method](JSON.stringify(mock));
|
||||
}, 1000);
|
||||
}, 200);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
postMessage;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
render(() => <App />, root!);
|
||||
|
@ -19,21 +19,17 @@ interface ClanDetailsProps {
|
||||
directory: string;
|
||||
}
|
||||
|
||||
interface MetaFieldsProps {
|
||||
interface ClanFormProps {
|
||||
directory?: string;
|
||||
meta: ClanMeta;
|
||||
actions: JSX.Element;
|
||||
editable?: boolean;
|
||||
directory?: string;
|
||||
}
|
||||
|
||||
const fn = (e: SubmitEvent) => {
|
||||
e.preventDefault();
|
||||
console.log("form submit", e.currentTarget);
|
||||
};
|
||||
|
||||
export default function Login() {
|
||||
const [, { Form, Field }] = createForm<ClanMeta>({
|
||||
initialValues: { name: "MyClan" },
|
||||
export const ClanForm = (props: ClanFormProps) => {
|
||||
const { meta, actions, editable = true, directory } = props;
|
||||
const [formStore, { Form, Field }] = createForm<ClanMeta>({
|
||||
initialValues: meta,
|
||||
});
|
||||
|
||||
const handleSubmit: SubmitHandler<ClanMeta> = (values, event) => {
|
||||
@ -52,8 +48,48 @@ export default function Login() {
|
||||
console.log("submit", values);
|
||||
};
|
||||
return (
|
||||
<div class="card-body">
|
||||
<div class="card card-compact w-96 bg-base-100 shadow-xl">
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Field name="icon">
|
||||
{(field, props) => (
|
||||
<>
|
||||
<figure>
|
||||
<Show
|
||||
when={field.value}
|
||||
fallback={
|
||||
<span class="material-icons aspect-square size-60 rounded-lg text-[18rem]">
|
||||
group
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{(icon) => (
|
||||
<img
|
||||
class="aspect-square size-60 rounded-lg"
|
||||
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>
|
||||
<div class="card-body">
|
||||
<div class="card-body">
|
||||
<Field
|
||||
name="name"
|
||||
validate={[required("Please enter a unique name for the clan.")]}
|
||||
@ -107,101 +143,25 @@ export default function Login() {
|
||||
</label>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="icon">
|
||||
{(field, props) => (
|
||||
<label class="form-control w-full max-w-xs">
|
||||
<div class="label">
|
||||
<span class="label-text">Select icon</span>
|
||||
{actions}
|
||||
</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>
|
||||
<div class="card-actions justify-end">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
</Form>
|
||||
</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<
|
||||
OperationResponse<"show_clan_meta">,
|
||||
{ status: "success" }
|
||||
@ -209,7 +169,7 @@ type ClanMeta = Extract<
|
||||
|
||||
export const ClanDetails = (props: ClanDetailsProps) => {
|
||||
const { directory } = props;
|
||||
const [, setLoading] = createSignal(false);
|
||||
const [loading, setLoading] = createSignal(false);
|
||||
const [errors, setErrors] = createSignal<
|
||||
| Extract<
|
||||
OperationResponse<"show_clan_meta">,
|
||||
@ -237,11 +197,16 @@ export const ClanDetails = (props: ClanDetailsProps) => {
|
||||
});
|
||||
return (
|
||||
<Switch fallback={"loading"}>
|
||||
<Match when={loading()}>
|
||||
<div>Loading</div>
|
||||
</Match>
|
||||
<Match when={data()}>
|
||||
<EditMetaFields
|
||||
{(data) => {
|
||||
const meta = data();
|
||||
return (
|
||||
<ClanForm
|
||||
directory={directory}
|
||||
// @ts-expect-error: TODO: figure out how solid allows type narrowing this
|
||||
meta={data()}
|
||||
meta={meta}
|
||||
actions={
|
||||
<div class="card-actions justify-between">
|
||||
<button class="btn btn-link" onClick={() => loadMeta()}>
|
||||
@ -251,9 +216,11 @@ export const ClanDetails = (props: ClanDetailsProps) => {
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Match>
|
||||
<Match when={errors()}>
|
||||
<button class="btn btn-link" onClick={() => loadMeta()}>
|
||||
<button class="btn btn-secondary" onClick={() => loadMeta()}>
|
||||
Retry
|
||||
</button>
|
||||
<For each={errors()}>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { pyApi } from "@/src/api";
|
||||
import { Match, Switch, createEffect, createSignal } from "solid-js";
|
||||
import toast from "solid-toast";
|
||||
import { ClanDetails, EditMetaFields } from "./clanDetails";
|
||||
import { ClanDetails, ClanForm } from "./clanDetails";
|
||||
|
||||
export const clan = () => {
|
||||
const [mode, setMode] = createSignal<"init" | "open" | "create">("init");
|
||||
@ -53,16 +53,16 @@ export const clan = () => {
|
||||
<ClanDetails directory={clanDir() || ""} />
|
||||
</Match>
|
||||
<Match when={mode() === "create"}>
|
||||
<EditMetaFields
|
||||
<ClanForm
|
||||
actions={
|
||||
<div class="card-actions justify-end">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onClick={() => {
|
||||
pyApi.open_file.dispatch({
|
||||
file_request: { mode: "save" },
|
||||
});
|
||||
}}
|
||||
// onClick={() => {
|
||||
// pyApi.open_file.dispatch({
|
||||
// file_request: { mode: "save" },
|
||||
// });
|
||||
// }}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { defineConfig } from "vite";
|
||||
import solidPlugin from "vite-plugin-solid";
|
||||
// import devtools from "solid-devtools/vite";
|
||||
import devtools from "solid-devtools/vite";
|
||||
import path from "node:path";
|
||||
|
||||
export default defineConfig({
|
||||
@ -14,7 +14,7 @@ export default defineConfig({
|
||||
Uncomment the following line to enable solid-devtools.
|
||||
For more info see https://github.com/thetarnav/solid-devtools/tree/main/packages/extension#readme
|
||||
*/
|
||||
// devtools(),
|
||||
devtools(),
|
||||
solidPlugin(),
|
||||
],
|
||||
server: {
|
||||
|
Loading…
Reference in New Issue
Block a user