working fileSelect component
This commit is contained in:
@@ -35,6 +35,15 @@ class FileRequest:
|
|||||||
@API.register_abstract
|
@API.register_abstract
|
||||||
def cancel_task(task_id: str) -> None:
|
def cancel_task(task_id: str) -> None:
|
||||||
"""Cancel a task by its op_key."""
|
"""Cancel a task by its op_key."""
|
||||||
|
msg = "cancel_task() is not implemented"
|
||||||
|
raise NotImplementedError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
@API.register_abstract
|
||||||
|
def list_tasks() -> list[str]:
|
||||||
|
"""List all tasks."""
|
||||||
|
msg = "list_tasks() is not implemented"
|
||||||
|
raise NotImplementedError(msg)
|
||||||
|
|
||||||
|
|
||||||
@API.register_abstract
|
@API.register_abstract
|
||||||
@@ -43,6 +52,8 @@ def open_file(file_request: FileRequest) -> list[str] | None:
|
|||||||
Abstract api method to open a file dialog window.
|
Abstract api method to open a file dialog window.
|
||||||
It must return the name of the selected file or None if no file was selected.
|
It must return the name of the selected file or None if no file was selected.
|
||||||
"""
|
"""
|
||||||
|
msg = "open_file() is not implemented"
|
||||||
|
raise NotImplementedError(msg)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -56,8 +56,9 @@ const _callApi = <K extends OperationNames>(
|
|||||||
) => Promise<OperationResponse<OperationNames>>
|
) => Promise<OperationResponse<OperationNames>>
|
||||||
>
|
>
|
||||||
)[method](args) as Promise<OperationResponse<K>>;
|
)[method](args) as Promise<OperationResponse<K>>;
|
||||||
const op_key = (promise as any)._webviewMessageId as string;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
debugger;
|
const op_key = (promise as any)._webviewMessageId as string;
|
||||||
|
|
||||||
return { promise, op_key };
|
return { promise, op_key };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,7 @@ import { Typography } from "@/src/components/Typography";
|
|||||||
import Fieldset from "@/src/Form/fieldset";
|
import Fieldset from "@/src/Form/fieldset";
|
||||||
import Icon from "@/src/components/icon"; // For displaying file icons
|
import Icon from "@/src/components/icon"; // For displaying file icons
|
||||||
import { callApi } from "@/src/api";
|
import { callApi } from "@/src/api";
|
||||||
import type {
|
import type { FieldValues } from "@modular-forms/solid";
|
||||||
FieldComponent,
|
|
||||||
FieldValues,
|
|
||||||
FieldName,
|
|
||||||
} from "@modular-forms/solid";
|
|
||||||
import { Show, For, type Component, type JSX } from "solid-js";
|
import { Show, For, type Component, type JSX } from "solid-js";
|
||||||
|
|
||||||
// Types for the file dialog options passed to callApi
|
// Types for the file dialog options passed to callApi
|
||||||
@@ -23,24 +19,23 @@ export interface FileDialogOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Props for the CustomFileField component
|
// Props for the CustomFileField component
|
||||||
interface FileSelectorOpts<
|
interface FileSelectorOpts<TFieldName extends string> {
|
||||||
TForm extends FieldValues,
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
TFieldName extends FieldName<TForm>,
|
Field: any; // The Field component from createForm
|
||||||
> {
|
|
||||||
Field: FieldComponent<TForm>; // The Field component from createForm
|
|
||||||
name: TFieldName; // Name of the form field (e.g., "sshKeys", "profilePicture")
|
name: TFieldName; // Name of the form field (e.g., "sshKeys", "profilePicture")
|
||||||
label: string; // Legend for Fieldset or main label for the input
|
label: string; // Legend for Fieldset or main label for the input
|
||||||
description?: string | JSX.Element; // Optional description text
|
description?: string | JSX.Element; // Optional description text
|
||||||
multiple?: boolean; // True if multiple files can be selected, false for single file
|
multiple?: boolean; // True if multiple files can be selected, false for single file
|
||||||
fileDialogOptions: FileDialogOptions; // Configuration for the custom file dialog
|
fileDialogOptions: FileDialogOptions; // Configuration for the custom file dialog
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
of: any;
|
||||||
// Optional props for styling
|
// Optional props for styling
|
||||||
inputClass?: string;
|
inputClass?: string;
|
||||||
fileListClass?: string;
|
fileListClass?: string;
|
||||||
// You can add more specific props like `validate` if you want to pass them to Field
|
// You can add more specific props like `validate` if you want to pass them to Field
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FileSelectorField: Component<FileSelectorOpts<any, any>> = (
|
export const FileSelectorField: Component<FileSelectorOpts<string>> = (
|
||||||
props,
|
props,
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
@@ -57,7 +52,7 @@ export const FileSelectorField: Component<FileSelectorOpts<any, any>> = (
|
|||||||
// Ref to the underlying HTMLInputElement (assuming FileInput forwards refs or is simple)
|
// Ref to the underlying HTMLInputElement (assuming FileInput forwards refs or is simple)
|
||||||
let actualInputElement: HTMLInputElement | undefined;
|
let actualInputElement: HTMLInputElement | undefined;
|
||||||
|
|
||||||
const openAndSetFiles = async (event: MouseEvent) => {
|
const openAndSetFiles = async (event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!actualInputElement) {
|
if (!actualInputElement) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -127,7 +122,10 @@ export const FileSelectorField: Component<FileSelectorOpts<any, any>> = (
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<Field name={name} type={multiple ? "File[]" : "File"}>
|
<Field name={name} type={multiple ? "File[]" : "File"}>
|
||||||
{(field, fieldProps) => (
|
{(
|
||||||
|
field: { value: File | File[]; error?: string },
|
||||||
|
fieldProps: Record<string, unknown>,
|
||||||
|
) => (
|
||||||
<>
|
<>
|
||||||
{/*
|
{/*
|
||||||
This FileInput component should be clickable.
|
This FileInput component should be clickable.
|
||||||
@@ -135,8 +133,9 @@ export const FileSelectorField: Component<FileSelectorOpts<any, any>> = (
|
|||||||
If FileInput is complex, it might need an 'inputRef' prop or similar.
|
If FileInput is complex, it might need an 'inputRef' prop or similar.
|
||||||
*/}
|
*/}
|
||||||
<FileInput
|
<FileInput
|
||||||
{...(fieldProps as FileInputProps)} // Spread modular-forms props
|
{...(fieldProps as unknown as FileInputProps)} // Spread modular-forms props
|
||||||
ref={(el: HTMLInputElement) => {
|
ref={(el: HTMLInputElement) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(fieldProps as any).ref(el); // Pass ref to modular-forms
|
(fieldProps as any).ref(el); // Pass ref to modular-forms
|
||||||
actualInputElement = el; // Capture for local use
|
actualInputElement = el; // Capture for local use
|
||||||
}}
|
}}
|
||||||
@@ -150,7 +149,7 @@ export const FileSelectorField: Component<FileSelectorOpts<any, any>> = (
|
|||||||
error={field.error} // Display error from modular-forms
|
error={field.error} // Display error from modular-forms
|
||||||
/>
|
/>
|
||||||
{field.error && (
|
{field.error && (
|
||||||
<Typography color="error" hierarchy="body" size="xs" class="mt-1">
|
<Typography hierarchy="body" size="xs" class="mt-1">
|
||||||
{field.error}
|
{field.error}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
@@ -175,9 +174,8 @@ export const FileSelectorField: Component<FileSelectorOpts<any, any>> = (
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(file) => (
|
{(file) => (
|
||||||
<div class="flex items-center justify-between rounded border border-def-1 bg-bg-2 p-2 text-sm">
|
<div class="flex items-center justify-between rounded border p-2 text-sm border-def-1">
|
||||||
<span class="truncate" title={file.name}>
|
<span class="truncate" title={file.name}>
|
||||||
<Icon icon="File" class="mr-2 inline-block" size={14} />
|
|
||||||
{file.name}
|
{file.name}
|
||||||
</span>
|
</span>
|
||||||
{/* A remove button per file is complex with FileList & modular-forms.
|
{/* A remove button per file is complex with FileList & modular-forms.
|
||||||
|
|||||||
@@ -107,8 +107,7 @@ const closeButtonStyle: JSX.CSSProperties = {
|
|||||||
// --- Toast Component Definitions ---
|
// --- Toast Component Definitions ---
|
||||||
|
|
||||||
// Error Toast
|
// Error Toast
|
||||||
export interface ErrorToastProps extends BaseToastProps {}
|
export const ErrorToastComponent: Component<BaseToastProps> = (props) => {
|
||||||
export const ErrorToastComponent: Component<ErrorToastProps> = (props) => {
|
|
||||||
const handleCancelClick = () => {
|
const handleCancelClick = () => {
|
||||||
if (props.onCancel) {
|
if (props.onCancel) {
|
||||||
props.onCancel();
|
props.onCancel();
|
||||||
@@ -136,8 +135,7 @@ export const ErrorToastComponent: Component<ErrorToastProps> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Info Toast
|
// Info Toast
|
||||||
export interface InfoToastProps extends BaseToastProps {}
|
export const InfoToastComponent: Component<BaseToastProps> = (props) => {
|
||||||
export const InfoToastComponent: Component<InfoToastProps> = (props) => {
|
|
||||||
const handleCancelClick = () => {
|
const handleCancelClick = () => {
|
||||||
if (props.onCancel) {
|
if (props.onCancel) {
|
||||||
props.onCancel();
|
props.onCancel();
|
||||||
@@ -165,8 +163,7 @@ export const InfoToastComponent: Component<InfoToastProps> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Warning Toast
|
// Warning Toast
|
||||||
export interface WarningToastProps extends BaseToastProps {}
|
export const WarningToastComponent: Component<BaseToastProps> = (props) => {
|
||||||
export const WarningToastComponent: Component<WarningToastProps> = (props) => {
|
|
||||||
const handleCancelClick = () => {
|
const handleCancelClick = () => {
|
||||||
if (props.onCancel) {
|
if (props.onCancel) {
|
||||||
props.onCancel();
|
props.onCancel();
|
||||||
|
|||||||
@@ -243,6 +243,7 @@ export const Flash = () => {
|
|||||||
description="Provide your SSH public key(s) for secure, passwordless connections. (.pub files)"
|
description="Provide your SSH public key(s) for secure, passwordless connections. (.pub files)"
|
||||||
multiple={true} // Allow multiple SSH keys
|
multiple={true} // Allow multiple SSH keys
|
||||||
fileDialogOptions={sshKeyDialogOptions}
|
fileDialogOptions={sshKeyDialogOptions}
|
||||||
|
of={Array<File>}
|
||||||
// You could add custom validation via modular-forms 'validate' prop on CustomFileField if needed
|
// You could add custom validation via modular-forms 'validate' prop on CustomFileField if needed
|
||||||
// e.g. validate={[required("At least one SSH key is required.")]}
|
// e.g. validate={[required("At least one SSH key is required.")]}
|
||||||
// This would require CustomFileField to accept and pass `validate` to its internal `Field`.
|
// This would require CustomFileField to accept and pass `validate` to its internal `Field`.
|
||||||
|
|||||||
@@ -623,10 +623,11 @@ const MachineForm = (props: MachineDetailsProps) => {
|
|||||||
</Field>
|
</Field>
|
||||||
<FileSelectorField
|
<FileSelectorField
|
||||||
Field={Field}
|
Field={Field}
|
||||||
|
of={Array<File>}
|
||||||
|
multiple={true}
|
||||||
name="sshKeys" // Corresponds to FlashFormValues.sshKeys
|
name="sshKeys" // Corresponds to FlashFormValues.sshKeys
|
||||||
label="SSH Private Key"
|
label="SSH Private Key"
|
||||||
description="Provide your SSH private key for secure, passwordless connections."
|
description="Provide your SSH private key for secure, passwordless connections."
|
||||||
multiple={false}
|
|
||||||
fileDialogOptions={
|
fileDialogOptions={
|
||||||
{
|
{
|
||||||
title: "Select SSH Keys",
|
title: "Select SSH Keys",
|
||||||
|
|||||||
@@ -127,10 +127,11 @@ export const HWStep = (props: StepProps<HardwareValues>) => {
|
|||||||
</Field>
|
</Field>
|
||||||
<FileSelectorField
|
<FileSelectorField
|
||||||
Field={Field}
|
Field={Field}
|
||||||
|
of={File}
|
||||||
|
multiple={false}
|
||||||
name="sshKey" // Corresponds to FlashFormValues.sshKeys
|
name="sshKey" // Corresponds to FlashFormValues.sshKeys
|
||||||
label="SSH Private Key"
|
label="SSH Private Key"
|
||||||
description="Provide your SSH private key for secure, passwordless connections."
|
description="Provide your SSH private key for secure, passwordless connections."
|
||||||
multiple={false}
|
|
||||||
fileDialogOptions={
|
fileDialogOptions={
|
||||||
{
|
{
|
||||||
title: "Select SSH Keys",
|
title: "Select SSH Keys",
|
||||||
|
|||||||
Reference in New Issue
Block a user