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
* [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)
135 lines
4.4 KiB
TypeScript
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>
|
|
);
|
|
} |