Merge pull request 'Clan-app: devshell add schemas for gtk4; gsettings' (#1803) from hsjobeki/clan-core:hsjobeki-main into main
This commit is contained in:
@@ -4,11 +4,10 @@ import logging
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
from clan_cli.api import MethodRegistry
|
from clan_cli.api import MethodRegistry, dataclass_to_dict, from_dict
|
||||||
|
|
||||||
from clan_app.api import GObjApi, GResult, ImplFunc
|
from clan_app.api import GObjApi, GResult, ImplFunc
|
||||||
from clan_app.api.file import open_file
|
from clan_app.api.file import open_file
|
||||||
from clan_app.components.serializer import dataclass_to_dict, from_dict
|
|
||||||
|
|
||||||
gi.require_version("WebKit", "6.0")
|
gi.require_version("WebKit", "6.0")
|
||||||
from gi.repository import GLib, GObject, WebKit
|
from gi.repository import GLib, GObject, WebKit
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
setuptools,
|
setuptools,
|
||||||
copyDesktopItems,
|
copyDesktopItems,
|
||||||
pygobject3,
|
pygobject3,
|
||||||
wrapGAppsHook,
|
wrapGAppsHook4,
|
||||||
gtk4,
|
gtk4,
|
||||||
adwaita-icon-theme,
|
adwaita-icon-theme,
|
||||||
pygobject-stubs,
|
pygobject-stubs,
|
||||||
@@ -84,7 +84,7 @@ python3.pkgs.buildPythonApplication rec {
|
|||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
setuptools
|
setuptools
|
||||||
copyDesktopItems
|
copyDesktopItems
|
||||||
wrapGAppsHook
|
wrapGAppsHook4
|
||||||
|
|
||||||
gobject-introspection
|
gobject-introspection
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
|
glib,
|
||||||
|
gsettings-desktop-schemas,
|
||||||
stdenv,
|
stdenv,
|
||||||
clan-app,
|
clan-app,
|
||||||
mkShell,
|
mkShell,
|
||||||
@@ -33,7 +35,9 @@ mkShell {
|
|||||||
|
|
||||||
buildInputs =
|
buildInputs =
|
||||||
[
|
[
|
||||||
|
glib
|
||||||
ruff
|
ruff
|
||||||
|
gtk4
|
||||||
gtk4.dev # has the demo called 'gtk4-widget-factory'
|
gtk4.dev # has the demo called 'gtk4-widget-factory'
|
||||||
libadwaita.devdoc # has the demo called 'adwaita-1-demo'
|
libadwaita.devdoc # has the demo called 'adwaita-1-demo'
|
||||||
]
|
]
|
||||||
@@ -60,6 +64,9 @@ mkShell {
|
|||||||
# Add clan-cli to the python path so that we can import it without building it in nix first
|
# Add clan-cli to the python path so that we can import it without building it in nix first
|
||||||
export PYTHONPATH="$GIT_ROOT/pkgs/clan-cli":"$PYTHONPATH"
|
export PYTHONPATH="$GIT_ROOT/pkgs/clan-cli":"$PYTHONPATH"
|
||||||
|
|
||||||
|
export XDG_DATA_DIRS=${gtk4}/share/gsettings-schemas/gtk4-4.14.4:$XDG_DATA_DIRS
|
||||||
|
export XDG_DATA_DIRS=${gsettings-desktop-schemas}/share/gsettings-schemas/gsettings-desktop-schemas-46.0:$XDG_DATA_DIRS
|
||||||
|
|
||||||
# Add the webview-ui to the .webui directory
|
# Add the webview-ui to the .webui directory
|
||||||
ln -nsf ${webview-ui}/lib/node_modules/@clan/webview-ui/dist/ ./clan_app/.webui
|
ln -nsf ${webview-ui}/lib/node_modules/@clan/webview-ui/dist/ ./clan_app/.webui
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,141 @@
|
|||||||
|
import dataclasses
|
||||||
|
import json
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, fields, is_dataclass
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from inspect import Parameter, Signature, signature
|
from inspect import Parameter, Signature, signature
|
||||||
from typing import Annotated, Any, Generic, Literal, TypeVar, get_type_hints
|
from pathlib import Path
|
||||||
|
from types import UnionType
|
||||||
|
from typing import (
|
||||||
|
Annotated,
|
||||||
|
Any,
|
||||||
|
Generic,
|
||||||
|
Literal,
|
||||||
|
TypeVar,
|
||||||
|
get_args,
|
||||||
|
get_origin,
|
||||||
|
get_type_hints,
|
||||||
|
)
|
||||||
|
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_string(s: str) -> str:
|
||||||
|
# Using the native string sanitizer to handle all edge cases
|
||||||
|
# Remove the outer quotes '"string"'
|
||||||
|
return json.dumps(s)[1:-1]
|
||||||
|
|
||||||
|
|
||||||
|
def dataclass_to_dict(obj: Any) -> Any:
|
||||||
|
"""
|
||||||
|
Utility function to convert dataclasses to dictionaries
|
||||||
|
It converts all nested dataclasses, lists, tuples, and dictionaries to dictionaries
|
||||||
|
|
||||||
|
It does NOT convert member functions.
|
||||||
|
"""
|
||||||
|
if is_dataclass(obj):
|
||||||
|
return {
|
||||||
|
# Use either the original name or name
|
||||||
|
sanitize_string(
|
||||||
|
field.metadata.get("original_name", field.name)
|
||||||
|
): dataclass_to_dict(getattr(obj, field.name))
|
||||||
|
for field in fields(obj) # type: ignore
|
||||||
|
}
|
||||||
|
elif isinstance(obj, list | tuple):
|
||||||
|
return [dataclass_to_dict(item) for item in obj]
|
||||||
|
elif isinstance(obj, dict):
|
||||||
|
return {sanitize_string(k): dataclass_to_dict(v) for k, v in obj.items()}
|
||||||
|
elif isinstance(obj, Path):
|
||||||
|
return sanitize_string(str(obj))
|
||||||
|
elif isinstance(obj, str):
|
||||||
|
return sanitize_string(obj)
|
||||||
|
else:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def is_union_type(type_hint: type) -> bool:
|
||||||
|
return type(type_hint) is UnionType
|
||||||
|
|
||||||
|
|
||||||
|
def get_inner_type(type_hint: type) -> type:
|
||||||
|
if is_union_type(type_hint):
|
||||||
|
# Return the first non-None type
|
||||||
|
return next(t for t in get_args(type_hint) if t is not type(None))
|
||||||
|
return type_hint
|
||||||
|
|
||||||
|
|
||||||
|
def get_second_type(type_hint: type[dict]) -> type:
|
||||||
|
"""
|
||||||
|
Get the value type of a dictionary type hint
|
||||||
|
"""
|
||||||
|
args = get_args(type_hint)
|
||||||
|
if len(args) == 2:
|
||||||
|
# Return the second argument, which should be the value type (Machine)
|
||||||
|
return args[1]
|
||||||
|
|
||||||
|
raise ValueError(f"Invalid type hint for dict: {type_hint}")
|
||||||
|
|
||||||
|
|
||||||
|
def from_dict(t: type, data: dict[str, Any] | None) -> Any:
|
||||||
|
"""
|
||||||
|
Dynamically instantiate a data class from a dictionary, handling nested data classes.
|
||||||
|
"""
|
||||||
|
if data is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Attempt to create an instance of the data_class
|
||||||
|
field_values = {}
|
||||||
|
for field in fields(t):
|
||||||
|
original_name = field.metadata.get("original_name", field.name)
|
||||||
|
|
||||||
|
field_value = data.get(original_name)
|
||||||
|
|
||||||
|
field_type = get_inner_type(field.type) # type: ignore
|
||||||
|
|
||||||
|
if original_name in data:
|
||||||
|
# If the field is another dataclass, recursively instantiate it
|
||||||
|
if is_dataclass(field_type):
|
||||||
|
field_value = from_dict(field_type, field_value)
|
||||||
|
elif isinstance(field_type, Path | str) and isinstance(
|
||||||
|
field_value, str
|
||||||
|
):
|
||||||
|
field_value = (
|
||||||
|
Path(field_value) if field_type == Path else field_value
|
||||||
|
)
|
||||||
|
elif get_origin(field_type) is dict and isinstance(field_value, dict):
|
||||||
|
# The field is a dictionary with a specific type
|
||||||
|
inner_type = get_second_type(field_type)
|
||||||
|
field_value = {
|
||||||
|
k: from_dict(inner_type, v) for k, v in field_value.items()
|
||||||
|
}
|
||||||
|
elif get_origin is list and isinstance(field_value, list):
|
||||||
|
# The field is a list with a specific type
|
||||||
|
inner_type = get_args(field_type)[0]
|
||||||
|
field_value = [from_dict(inner_type, v) for v in field_value]
|
||||||
|
|
||||||
|
# Set the value
|
||||||
|
if (
|
||||||
|
field.default is not dataclasses.MISSING
|
||||||
|
or field.default_factory is not dataclasses.MISSING
|
||||||
|
):
|
||||||
|
# Fields with default value
|
||||||
|
# a: Int = 1
|
||||||
|
# b: list = Field(default_factory=list)
|
||||||
|
if original_name in data or field_value is not None:
|
||||||
|
field_values[field.name] = field_value
|
||||||
|
else:
|
||||||
|
# Fields without default value
|
||||||
|
# a: Int
|
||||||
|
field_values[field.name] = field_value
|
||||||
|
|
||||||
|
return t(**field_values)
|
||||||
|
|
||||||
|
except (TypeError, ValueError) as e:
|
||||||
|
print(f"Failed to instantiate {t.__name__}: {e} {data}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
ResponseDataType = TypeVar("ResponseDataType")
|
ResponseDataType = TypeVar("ResponseDataType")
|
||||||
|
|||||||
@@ -12,14 +12,10 @@ Operate on the returned inventory to make changes
|
|||||||
- save_inventory: To persist changes.
|
- save_inventory: To persist changes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import dataclasses
|
|
||||||
import json
|
import json
|
||||||
from dataclasses import fields, is_dataclass
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import UnionType
|
|
||||||
from typing import Any, get_args, get_origin
|
|
||||||
|
|
||||||
from clan_cli.api import API
|
from clan_cli.api import API, dataclass_to_dict, from_dict
|
||||||
from clan_cli.errors import ClanCmdError, ClanError
|
from clan_cli.errors import ClanCmdError, ClanError
|
||||||
from clan_cli.git import commit_file
|
from clan_cli.git import commit_file
|
||||||
|
|
||||||
@@ -41,6 +37,8 @@ from .classes import (
|
|||||||
# Re export classes here
|
# Re export classes here
|
||||||
# This allows to rename classes in the generated code
|
# This allows to rename classes in the generated code
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"from_dict",
|
||||||
|
"dataclass_to_dict",
|
||||||
"Service",
|
"Service",
|
||||||
"Machine",
|
"Machine",
|
||||||
"Meta",
|
"Meta",
|
||||||
@@ -54,121 +52,6 @@ __all__ = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def sanitize_string(s: str) -> str:
|
|
||||||
return s.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
|
|
||||||
|
|
||||||
|
|
||||||
def dataclass_to_dict(obj: Any) -> Any:
|
|
||||||
"""
|
|
||||||
Utility function to convert dataclasses to dictionaries
|
|
||||||
It converts all nested dataclasses, lists, tuples, and dictionaries to dictionaries
|
|
||||||
|
|
||||||
It does NOT convert member functions.
|
|
||||||
"""
|
|
||||||
if is_dataclass(obj):
|
|
||||||
return {
|
|
||||||
# Use either the original name or name
|
|
||||||
sanitize_string(
|
|
||||||
field.metadata.get("original_name", field.name)
|
|
||||||
): dataclass_to_dict(getattr(obj, field.name))
|
|
||||||
for field in fields(obj) # type: ignore
|
|
||||||
}
|
|
||||||
elif isinstance(obj, list | tuple):
|
|
||||||
return [dataclass_to_dict(item) for item in obj]
|
|
||||||
elif isinstance(obj, dict):
|
|
||||||
return {sanitize_string(k): dataclass_to_dict(v) for k, v in obj.items()}
|
|
||||||
elif isinstance(obj, Path):
|
|
||||||
return str(obj)
|
|
||||||
elif isinstance(obj, str):
|
|
||||||
return sanitize_string(obj)
|
|
||||||
else:
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def is_union_type(type_hint: type) -> bool:
|
|
||||||
return type(type_hint) is UnionType
|
|
||||||
|
|
||||||
|
|
||||||
def get_inner_type(type_hint: type) -> type:
|
|
||||||
if is_union_type(type_hint):
|
|
||||||
# Return the first non-None type
|
|
||||||
return next(t for t in get_args(type_hint) if t is not type(None))
|
|
||||||
return type_hint
|
|
||||||
|
|
||||||
|
|
||||||
def get_second_type(type_hint: type[dict]) -> type:
|
|
||||||
"""
|
|
||||||
Get the value type of a dictionary type hint
|
|
||||||
"""
|
|
||||||
args = get_args(type_hint)
|
|
||||||
if len(args) == 2:
|
|
||||||
# Return the second argument, which should be the value type (Machine)
|
|
||||||
return args[1]
|
|
||||||
|
|
||||||
raise ValueError(f"Invalid type hint for dict: {type_hint}")
|
|
||||||
|
|
||||||
|
|
||||||
def from_dict(t: type, data: dict[str, Any] | None) -> Any:
|
|
||||||
"""
|
|
||||||
Dynamically instantiate a data class from a dictionary, handling nested data classes.
|
|
||||||
"""
|
|
||||||
if data is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Attempt to create an instance of the data_class
|
|
||||||
field_values = {}
|
|
||||||
for field in fields(t):
|
|
||||||
original_name = field.metadata.get("original_name", field.name)
|
|
||||||
|
|
||||||
field_value = data.get(original_name)
|
|
||||||
|
|
||||||
field_type = get_inner_type(field.type) # type: ignore
|
|
||||||
|
|
||||||
if original_name in data:
|
|
||||||
# If the field is another dataclass, recursively instantiate it
|
|
||||||
if is_dataclass(field_type):
|
|
||||||
field_value = from_dict(field_type, field_value)
|
|
||||||
elif isinstance(field_type, Path | str) and isinstance(
|
|
||||||
field_value, str
|
|
||||||
):
|
|
||||||
field_value = (
|
|
||||||
Path(field_value) if field_type == Path else field_value
|
|
||||||
)
|
|
||||||
elif get_origin(field_type) is dict and isinstance(field_value, dict):
|
|
||||||
# The field is a dictionary with a specific type
|
|
||||||
inner_type = get_second_type(field_type)
|
|
||||||
field_value = {
|
|
||||||
k: from_dict(inner_type, v) for k, v in field_value.items()
|
|
||||||
}
|
|
||||||
elif get_origin is list and isinstance(field_value, list):
|
|
||||||
# The field is a list with a specific type
|
|
||||||
inner_type = get_args(field_type)[0]
|
|
||||||
field_value = [from_dict(inner_type, v) for v in field_value]
|
|
||||||
|
|
||||||
# Set the value
|
|
||||||
if (
|
|
||||||
field.default is not dataclasses.MISSING
|
|
||||||
or field.default_factory is not dataclasses.MISSING
|
|
||||||
):
|
|
||||||
# Fields with default value
|
|
||||||
# a: Int = 1
|
|
||||||
# b: list = Field(default_factory=list)
|
|
||||||
if original_name in data or field_value is not None:
|
|
||||||
field_values[field.name] = field_value
|
|
||||||
else:
|
|
||||||
# Fields without default value
|
|
||||||
# a: Int
|
|
||||||
field_values[field.name] = field_value
|
|
||||||
|
|
||||||
return t(**field_values)
|
|
||||||
|
|
||||||
except (TypeError, ValueError) as e:
|
|
||||||
print(f"Failed to instantiate {t.__name__}: {e} {data}")
|
|
||||||
return None
|
|
||||||
# raise ClanError(f"Failed to instantiate {t.__name__}: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def get_path(flake_dir: str | Path) -> Path:
|
def get_path(flake_dir: str | Path) -> Path:
|
||||||
"""
|
"""
|
||||||
Get the path to the inventory file in the flake directory
|
Get the path to the inventory file in the flake directory
|
||||||
|
|||||||
@@ -143,8 +143,9 @@ const deserialize =
|
|||||||
fn(r);
|
fn(r);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Error parsing JSON: ", e);
|
console.log("Error parsing JSON: ", e);
|
||||||
console.log({ download: () => download("error.json", str) });
|
window.localStorage.setItem("error", str);
|
||||||
console.error(str);
|
console.error(str);
|
||||||
|
console.error("See localStorage 'error'");
|
||||||
alert(`Error parsing JSON: ${e}`);
|
alert(`Error parsing JSON: ${e}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import {
|
|||||||
setRoute,
|
setRoute,
|
||||||
clanList,
|
clanList,
|
||||||
} from "@/src/App";
|
} from "@/src/App";
|
||||||
import { For } from "solid-js";
|
import { For, Show } from "solid-js";
|
||||||
|
import { createQuery } from "@tanstack/solid-query";
|
||||||
|
|
||||||
export const registerClan = async () => {
|
export const registerClan = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -26,6 +27,7 @@ export const registerClan = async () => {
|
|||||||
const res = new Set([...s, loc.data]);
|
const res = new Set([...s, loc.data]);
|
||||||
return Array.from(res);
|
return Array.from(res);
|
||||||
});
|
});
|
||||||
|
setActiveURI(loc.data);
|
||||||
setRoute((r) => {
|
setRoute((r) => {
|
||||||
if (r === "welcome") return "machines";
|
if (r === "welcome") return "machines";
|
||||||
return r;
|
return r;
|
||||||
@@ -37,6 +39,87 @@ export const registerClan = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface ClanDetailsProps {
|
||||||
|
clan_dir: string;
|
||||||
|
}
|
||||||
|
const ClanDetails = (props: ClanDetailsProps) => {
|
||||||
|
const { clan_dir } = props;
|
||||||
|
|
||||||
|
const details = createQuery(() => ({
|
||||||
|
queryKey: [clan_dir, "meta"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const result = await callApi("show_clan_meta", { uri: clan_dir });
|
||||||
|
if (result.status === "error") throw new Error("Failed to fetch data");
|
||||||
|
return result.data;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-figure text-primary">
|
||||||
|
<div class="join">
|
||||||
|
<button
|
||||||
|
class=" join-item btn-sm"
|
||||||
|
classList={{
|
||||||
|
"btn btn-ghost btn-outline": activeURI() !== clan_dir,
|
||||||
|
"badge badge-primary": activeURI() === clan_dir,
|
||||||
|
}}
|
||||||
|
disabled={activeURI() === clan_dir}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveURI(clan_dir);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{activeURI() === clan_dir ? "active" : "select"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-ghost btn-outline join-item btn-sm"
|
||||||
|
onClick={() => {
|
||||||
|
setClanList((s) =>
|
||||||
|
s.filter((v, idx) => {
|
||||||
|
if (v == clan_dir) {
|
||||||
|
setActiveURI(
|
||||||
|
clanList()[idx - 1] || clanList()[idx + 1] || null
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-title">Clan URI</div>
|
||||||
|
|
||||||
|
<Show when={details.isSuccess}>
|
||||||
|
<div
|
||||||
|
class="stat-value"
|
||||||
|
// classList={{
|
||||||
|
// "text-primary": activeURI() === clan_dir,
|
||||||
|
// }}
|
||||||
|
>
|
||||||
|
{details.data?.name}
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
<Show
|
||||||
|
when={details.isSuccess && details.data?.description}
|
||||||
|
fallback={<div class="stat-desc text-lg">{clan_dir}</div>}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="stat-desc text-lg"
|
||||||
|
// classList={{
|
||||||
|
// "text-primary": activeURI() === clan_dir,
|
||||||
|
// }}
|
||||||
|
>
|
||||||
|
{details.data?.description}
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const Settings = () => {
|
export const Settings = () => {
|
||||||
return (
|
return (
|
||||||
<div class="card card-normal">
|
<div class="card card-normal">
|
||||||
@@ -54,60 +137,7 @@ export const Settings = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="stats stats-vertical shadow">
|
<div class="stats stats-vertical shadow">
|
||||||
<For each={clanList()}>
|
<For each={clanList()}>
|
||||||
{(value) => (
|
{(value) => <ClanDetails clan_dir={value} />}
|
||||||
<div class="stat">
|
|
||||||
<div class="stat-figure text-primary">
|
|
||||||
<div class="join">
|
|
||||||
<button
|
|
||||||
class=" join-item btn-sm"
|
|
||||||
classList={{
|
|
||||||
"btn btn-ghost btn-outline": activeURI() !== value,
|
|
||||||
"badge badge-primary": activeURI() === value,
|
|
||||||
}}
|
|
||||||
disabled={activeURI() === value}
|
|
||||||
onClick={() => {
|
|
||||||
setActiveURI(value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{activeURI() === value ? "active" : "select"}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn btn-ghost btn-outline join-item btn-sm"
|
|
||||||
onClick={() => {
|
|
||||||
setClanList((s) =>
|
|
||||||
s.filter((v, idx) => {
|
|
||||||
if (v == value) {
|
|
||||||
setActiveURI(
|
|
||||||
clanList()[idx - 1] ||
|
|
||||||
clanList()[idx + 1] ||
|
|
||||||
null
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
// if (activeURI() === value) {
|
|
||||||
// setActiveURI();
|
|
||||||
// }
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Remove URI
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-title">Clan URI</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="stat-desc text-lg"
|
|
||||||
classList={{
|
|
||||||
"text-primary": activeURI() === value,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user