Merge pull request 'ui/integrate-clan-tags-machine-detail' (#4716) from ui/integrate-clan-tags-machine-detail into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4716
This commit is contained in:
@@ -11,7 +11,7 @@ import { Label } from "@/src/components/Form/Label";
|
|||||||
import { Orienter } from "@/src/components/Form/Orienter";
|
import { Orienter } from "@/src/components/Form/Orienter";
|
||||||
import { CollectionNode } from "@kobalte/core";
|
import { CollectionNode } from "@kobalte/core";
|
||||||
|
|
||||||
interface MachineTag {
|
export interface MachineTag {
|
||||||
value: string;
|
value: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
new?: boolean;
|
new?: boolean;
|
||||||
@@ -24,11 +24,10 @@ export type MachineTagsProps = FieldProps & {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
defaultValue?: string[];
|
defaultValue?: string[];
|
||||||
|
defaultOptions?: string[];
|
||||||
|
readonlyOptions?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// tags which are applied to all machines and cannot be removed
|
|
||||||
const staticOptions = [{ value: "all", disabled: true }];
|
|
||||||
|
|
||||||
const uniqueOptions = (options: MachineTag[]) => {
|
const uniqueOptions = (options: MachineTag[]) => {
|
||||||
const record: Record<string, MachineTag> = {};
|
const record: Record<string, MachineTag> = {};
|
||||||
options.forEach((option) => {
|
options.forEach((option) => {
|
||||||
@@ -39,13 +38,8 @@ const uniqueOptions = (options: MachineTag[]) => {
|
|||||||
return Object.values(record);
|
return Object.values(record);
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortedOptions = (options: MachineTag[]) => {
|
const sortedOptions = (options: MachineTag[]) =>
|
||||||
return options.sort((a, b) => {
|
options.sort((a, b) => a.value.localeCompare(b.value));
|
||||||
if (a.new && !b.new) return -1;
|
|
||||||
if (a.disabled && !b.disabled) return -1;
|
|
||||||
return a.value.localeCompare(b.value);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortedAndUniqueOptions = (options: MachineTag[]) =>
|
const sortedAndUniqueOptions = (options: MachineTag[]) =>
|
||||||
sortedOptions(uniqueOptions(options));
|
sortedOptions(uniqueOptions(options));
|
||||||
@@ -72,9 +66,15 @@ export const MachineTags = (props: MachineTagsProps) => {
|
|||||||
(props.defaultValue || []).map((value) => ({ value })),
|
(props.defaultValue || []).map((value) => ({ value })),
|
||||||
);
|
);
|
||||||
|
|
||||||
// todo this should be the superset of tags used across the entire clan and be passed in via a prop
|
// convert default options string[] into MachineTag[]
|
||||||
const [availableOptions, setAvailableOptions] = createSignal<MachineTag[]>(
|
const [availableOptions, setAvailableOptions] = createSignal<MachineTag[]>(
|
||||||
sortedAndUniqueOptions([...staticOptions, ...defaultValue]),
|
sortedAndUniqueOptions([
|
||||||
|
...(props.readonlyOptions || []).map((value) => ({
|
||||||
|
value,
|
||||||
|
disabled: true,
|
||||||
|
})),
|
||||||
|
...(props.defaultOptions || []).map((value) => ({ value })),
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { FieldSchema } from "@/src/hooks/queries";
|
import { SuccessData } from "@/src/hooks/api";
|
||||||
import { Maybe } from "@modular-forms/solid";
|
import { Maybe } from "@modular-forms/solid";
|
||||||
|
|
||||||
export const tooltipText = <T extends object, K extends keyof T>(
|
export const tooltipText = (
|
||||||
name: K,
|
name: string,
|
||||||
schema: FieldSchema<T>,
|
schema: SuccessData<"get_machine_fields_schema">,
|
||||||
staticValue: Maybe<string> = undefined,
|
staticValue: Maybe<string> = undefined,
|
||||||
): Maybe<string> => {
|
): Maybe<string> => {
|
||||||
const entry = schema[name];
|
const entry = schema[name];
|
||||||
|
|||||||
@@ -6,20 +6,15 @@ import { useApiClient } from "./ApiClient";
|
|||||||
export type ClanDetails = SuccessData<"get_clan_details">;
|
export type ClanDetails = SuccessData<"get_clan_details">;
|
||||||
export type ClanDetailsWithURI = ClanDetails & { uri: string };
|
export type ClanDetailsWithURI = ClanDetails & { uri: string };
|
||||||
|
|
||||||
export type FieldSchema<T> = {
|
export type Tags = SuccessData<"list_tags">;
|
||||||
[K in keyof T]: {
|
|
||||||
readonly: boolean;
|
|
||||||
reason?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Machine = SuccessData<"get_machine">;
|
export type Machine = SuccessData<"get_machine">;
|
||||||
export type ListMachines = SuccessData<"list_machines">;
|
export type ListMachines = SuccessData<"list_machines">;
|
||||||
export type MachineDetails = SuccessData<"get_machine_details">;
|
export type MachineDetails = SuccessData<"get_machine_details">;
|
||||||
|
|
||||||
export interface MachineDetail {
|
export interface MachineDetail {
|
||||||
|
tags: Tags;
|
||||||
machine: Machine;
|
machine: Machine;
|
||||||
fieldsSchema: FieldSchema<Machine>;
|
fieldsSchema: SuccessData<"get_machine_fields_schema">;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MachinesQueryResult = UseQueryResult<ListMachines>;
|
export type MachinesQueryResult = UseQueryResult<ListMachines>;
|
||||||
@@ -50,7 +45,12 @@ export const useMachineQuery = (clanURI: string, machineName: string) => {
|
|||||||
return useQuery<MachineDetail>(() => ({
|
return useQuery<MachineDetail>(() => ({
|
||||||
queryKey: ["clans", encodeBase64(clanURI), "machine", machineName],
|
queryKey: ["clans", encodeBase64(clanURI), "machine", machineName],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const [machineCall, schemaCall] = await Promise.all([
|
const [tagsCall, machineCall, schemaCall] = await Promise.all([
|
||||||
|
client.fetch("list_tags", {
|
||||||
|
flake: {
|
||||||
|
identifier: clanURI,
|
||||||
|
},
|
||||||
|
}),
|
||||||
client.fetch("get_machine", {
|
client.fetch("get_machine", {
|
||||||
name: machineName,
|
name: machineName,
|
||||||
flake: {
|
flake: {
|
||||||
@@ -67,6 +67,11 @@ export const useMachineQuery = (clanURI: string, machineName: string) => {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const tags = await tagsCall.result;
|
||||||
|
if (tags.status === "error") {
|
||||||
|
throw new Error("Error fetching tags: " + tags.errors[0].message);
|
||||||
|
}
|
||||||
|
|
||||||
const machine = await machineCall.result;
|
const machine = await machineCall.result;
|
||||||
if (machine.status === "error") {
|
if (machine.status === "error") {
|
||||||
throw new Error("Error fetching machine: " + machine.errors[0].message);
|
throw new Error("Error fetching machine: " + machine.errors[0].message);
|
||||||
@@ -81,6 +86,7 @@ export const useMachineQuery = (clanURI: string, machineName: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
tags: tags.data,
|
||||||
machine: machine.data,
|
machine: machine.data,
|
||||||
fieldsSchema: writeSchema.data,
|
fieldsSchema: writeSchema.data,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,6 +30,26 @@ export const SectionTags = (props: SectionTags) => {
|
|||||||
return pick(machineQuery.data.machine, ["tags"]) satisfies FormValues;
|
return pick(machineQuery.data.machine, ["tags"]) satisfies FormValues;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const options = () => {
|
||||||
|
if (!machineQuery.isSuccess) {
|
||||||
|
return [[], []];
|
||||||
|
}
|
||||||
|
|
||||||
|
// these are static values or values which have been configured in nix and
|
||||||
|
// cannot be modified in the UI
|
||||||
|
const readonlyOptions =
|
||||||
|
machineQuery.data.fieldsSchema.tags?.readonly_members || [];
|
||||||
|
|
||||||
|
// filter out the read-only options from the superset of clan-wide options
|
||||||
|
const readonlySet = new Set(readonlyOptions);
|
||||||
|
|
||||||
|
const defaultOptions = (machineQuery.data.tags.options || []).filter(
|
||||||
|
(tag) => !readonlySet.has(tag),
|
||||||
|
);
|
||||||
|
|
||||||
|
return [defaultOptions, readonlyOptions];
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={machineQuery.isSuccess}>
|
<Show when={machineQuery.isSuccess}>
|
||||||
<SidebarSectionForm
|
<SidebarSectionForm
|
||||||
@@ -50,6 +70,8 @@ export const SectionTags = (props: SectionTags) => {
|
|||||||
readOnly={!editing}
|
readOnly={!editing}
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
defaultValue={field.value}
|
defaultValue={field.value}
|
||||||
|
defaultOptions={options()[0]}
|
||||||
|
readonlyOptions={options()[1]}
|
||||||
input={input}
|
input={input}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user