Files
gridline/src/components/db-viewer/objects/IndexForm.tsx
T
adrianbonpin 0c74d77e75
Release / publish (, ubuntu-22.04) (push) Failing after 14m42s
Release / publish (, windows-latest) (push) Canceled after 0s
Release / publish (--target aarch64-apple-darwin, macos-latest) (push) Canceled after 0s
Release / publish (--target x86_64-apple-darwin, macos-15-intel) (push) Canceled after 0s
v0.7.6: PostgreSQL object management, keychain toggle, tabbed Objects workspace (#12)
* [P1-T1] Change::Ddl Rust variant + execute_change arm

* [P1-T2] Frontend ddl change type + objectCrud capability

* [P1-T3] use_keychain data model + migration v8

* [P2-T1] object_crud skeleton + validators + build_ddl dispatch

* [P2-T2] Sequence builders

* [P2-T3] Enum builders (no value removal)

* [P2-T4] View / matview / extension builders

* [P2-T5] Index + constraint builders

* [P2-T6] Function / procedure + trigger builders

* [P2-T7] build_object_ddl + get_available_extensions commands + wrappers

* [P3-T1] conditional keychain + session passwords

* [P3-T2] keychain-off password prompt on connect

* [P3-T3] default-ON keychain opt-out + tooltip + modal conditional

* [P3-T4] ddl queue card + after-commit refetch

* [P4-T1] ObjectCrudDialog shell

* [P4-T2] SequenceForm

* [P4-T3] EnumForm with no-removal note

* [P4-T4] ExtensionForm with available-extensions picker

* [P4-T5] ViewForm (view + materialized view)

* [P5-T1] IndexForm with column picker

* [P5-T2] ConstraintForm (check/unique/pk/fk) + ColumnPicker

* [P5-T3] FunctionForm (function + procedure)

* [P5-T4] TriggerForm with trigger-function picker

* [P5-T5] ObjectContextMenu + Explorer/TableOverflowMenu CRUD wiring

* [P6-T1] object tab type + openObjectTab dedup

* [P6-T2] extract ObjectDetail for object tabs

* [P6-T3] Objects view two-pane sidebar + workspace

* [P6-T4] object-tab content + per-type tab icons

* [P7-T1] Version bump 0.7.5 -> 0.7.6

* [P7-T2] docs sync README/ROADMAP/AGENTS for v0.7.6

* [P7-T3] chore: Cargo.lock version sync 0.7.5 -> 0.7.6

* fix(ui): object tab icon stacks above name (preflight svg block)

* fix(ui): optically center object tab icon with name

* fix(ui): object tab icon matches query/table icon handling

* [UI-POLISH-1] objectForm tab type + openFormTab store action

* [UI-POLISH-2] ObjectFormTab + KindForm with Visual/SQL toggle

* [UI-POLISH-3] route create/edit through form tabs; remove modal

* docs: create/edit now open as form tabs (AGENTS sync)

* [FB-1] follow app styling patterns + schema dropdown in forms

* [FB-2] Monaco editor for function body + view definition

* [FB-3] form tabs styled like viewers + in-cell editing

* [FB-5] focus outline scoped to input area (label excluded)

* [FB-6] no amber focus outline on Monaco body/definition rows

* [FB-7] header dedupe + schema default + full edit prefill

* [FB-8] SQL view in read-only Monaco editor

* docs: roadmap — table create/edit + relationships (next)

* docs: roadmap — Admin follow-up is 0.7.7 (next after 0.7.6)
2026-08-06 22:56:49 +08:00

135 lines
4.4 KiB
TypeScript

import { useEffect, useState } from "react";
import type { DdlParams } from "../../../lib/objectCrud";
import * as cmd from "../../../lib/commands";
import type { SchemaGraph } from "../../../lib/types";
import { ColumnPicker } from "./ColumnPicker";
import { FormRow, inputClass, controlClass } from "./formRow";
interface Props {
connectionId: string;
params: DdlParams;
schemas?: string[];
onChange: (p: DdlParams) => void;
}
const METHODS = ["", "btree", "hash", "gist", "gin", "brin"];
function patchAction(params: DdlParams, patch: Record<string, unknown>): DdlParams {
const action = (params.action ?? {}) as Record<string, unknown>;
return { ...params, action: { ...action, ...patch } };
}
export function IndexForm({ connectionId, params, schemas, onChange }: Props) {
const p = params as Record<string, unknown>;
const action = (p.action ?? {}) as Record<string, unknown>;
const [cols, setCols] = useState<string[]>([]);
useEffect(() => {
cmd
.getSchemaGraph(connectionId, (p.schema as string) || undefined)
.then((g: SchemaGraph) => {
const t = g.tables.find(
(t) => t.name === (p.table as string) && t.schema === (p.schema as string),
);
setCols(t ? t.columns.map((c) => c.name) : []);
})
.catch(() => setCols([]));
}, [connectionId, p.schema, p.table]);
const selected: string[] = (action.columns as string[]) ?? [];
const setAction = (patch: Record<string, unknown>) => onChange(patchAction(params, patch));
return (
<div>
<FormRow label="Schema">
{schemas && schemas.length > 0 ? (
<select
value={(p.schema as string) ?? ""}
onChange={(e) => onChange({ ...p, schema: e.target.value })}
aria-label="Schema"
className={controlClass}
>
<option value="" disabled>Schema</option>
{schemas.map((s) => <option key={s} value={s}>{s}</option>)}
</select>
) : (
<input
type="text"
placeholder="Schema"
value={(p.schema as string) ?? ""}
onChange={(e) => onChange({ ...p, schema: e.target.value })}
className={inputClass}
/>
)}
</FormRow>
<FormRow label="Table">
<input
type="text"
placeholder="Table"
value={(p.table as string) ?? ""}
onChange={(e) => onChange({ ...p, table: e.target.value })}
className={inputClass}
/>
</FormRow>
<FormRow label="Name">
<input
type="text"
placeholder="Index name"
value={(p.name as string) ?? ""}
onChange={(e) => onChange({ ...p, name: e.target.value })}
className={inputClass}
/>
</FormRow>
<FormRow label="Unique">
<label className="min-w-0 flex-1 flex items-center gap-2 px-3 font-heading text-xs text-text cursor-pointer">
<input
type="checkbox"
checked={!!action.unique}
onChange={(e) => setAction({ unique: e.target.checked })}
aria-label="Unique"
className="rounded border-border bg-surface text-accent focus:ring-accent"
/>
<span>Unique</span>
</label>
</FormRow>
<FormRow label="Method">
<select
aria-label="Method"
value={(action.method as string) ?? ""}
onChange={(e) => setAction({ method: e.target.value })}
className={controlClass}
>
{METHODS.map((m) => (
<option key={m} value={m}>
{m || "(default btree)"}
</option>
))}
</select>
</FormRow>
<FormRow label="Columns" className="items-stretch">
<div className="min-w-0 flex-1 flex flex-col gap-1 px-4 py-2">
<ColumnPicker
cols={cols}
selected={selected}
onToggle={(c) =>
setAction({
columns: selected.includes(c)
? selected.filter((x) => x !== c)
: [...selected, c],
})
}
/>
</div>
</FormRow>
<FormRow label="Predicate">
<input
type="text"
placeholder="WHERE predicate (optional)"
value={(action.predicate as string) ?? ""}
onChange={(e) => setAction({ predicate: e.target.value || null })}
className={inputClass}
/>
</FormRow>
</div>
);
}