layout change ui fix
Some checks failed
CI / check (push) Failing after 5m6s

This commit is contained in:
2026-09-29 16:59:13 +05:30
parent e676d259b2
commit 1ef0c6fc8a
7 changed files with 475 additions and 86 deletions

View File

@@ -1,88 +1,61 @@
import React from 'react';
import { RotateCcw, SlidersHorizontal, Undo2 } from 'lucide-react';
import { RotateCcw, Undo2 } from 'lucide-react';
import { Button } from '@/components/ds';
import { useUiEditing } from '@/components/ui-tree/UiEditingProvider';
import { inspectTree } from '@/lib/ui/inspect';
import { nodeRegistry } from '@/lib/ui/registry';
import { TreePanel } from './TreePanel';
import { NodeInspector } from './NodeInspector';
import { NodePicker } from './NodePicker';
/**
* The visual editor.
* The preview bar for a layout change.
*
* It is a *client* of the UI system, not a second implementation of it. Every
* control it draws ends in one call — `propose(op)` on the editing session —
* with an operation object of exactly the shape Owliver produces for the same
* change. From there the two are indistinguishable: same validation, same
* preview merge, same Apply, same `preferences.uiLayouts`.
* control it draws ends in one call on the editing session, with an operation
* of exactly the shape Owliver produces for the same change. From there the two
* are indistinguishable: same validation, same preview merge, same Apply, same
* `preferences.uiLayouts`.
*
* That is the whole architecture:
* Owliver → operation → propose → validate → preview
* ↓ Apply
* preferences.uiLayouts
*
* editor / Owliver → operation → propose → validate → preview
* ↓ Apply
* preferences.uiLayouts
* WHAT THIS USED TO BE, AND WHY IT IS NOT
*
* There is no page in this file, no component name, and no branch on what a
* node is. What can be done to the selected node comes from its registration;
* what can be added comes from the registry and the closed data vocabulary;
* what it is showing comes from the tree. A page that migrates tomorrow is
* editable tomorrow with nothing here changed.
* There was a `Customise layout` button here, and behind it a tree panel, a
* node inspector and a picker — a visual editor sitting beside Owliver, both
* producing the same operations. It was removed on request: two ways to
* rearrange a page is two things to explain, and the panel was on every admin
* screen whether or not anybody was arranging anything.
*
* Owliver is now the only way to propose a layout change. This component keeps
* the other half of that conversation — a person still has to SEE the change
* and decide. `TreePanel`, `NodeInspector` and `NodePicker` are left in the
* directory, unimported: they are the editor, intact, if it is ever wanted
* back.
*
* Nothing about applying moved. `useAssistant` calls `apply` and `discard` on
* the session directly, so "Apply" typed into the panel works exactly as the
* button does — see the `ui-apply` branch there.
*/
export function UiEditor({ registry = nodeRegistry }) {
export function UiEditor() {
const editing = useUiEditing();
const [open, setOpen] = React.useState(false);
const [selectedId, setSelectedId] = React.useState(null);
/* Rendered only where a page has opted into composition. A page that has not
is not broken; it simply has nothing to arrange. */
if (!editing) return null;
const {
tree, propose, discard, apply, undo, reset,
discard, apply, undo, reset,
previewing, customised, saving, problems, skipped,
/* The page this session belongs to. Handed to the picker so what can be
added here is decided by the registry rather than by the picker being
shown everything that exists. */
page,
} = editing;
const nodes = inspectTree(tree, { registry });
const selected = nodes.find((node) => node.id === selectedId) || null;
/* A node's own container, for the reorder buttons and for the picker. */
const siblings = selected ? nodes.filter((node) => node.parent === selected.parent) : [];
const parent = selected?.container ? selected : nodes.find((n) => n.id === selected?.parent) || null;
/**
* The one door out of this component.
*
* Everything the panels do arrives here as an operation and goes straight to
* the session. Nothing is applied, nothing is stored, and nothing is
* validated locally — `propose` refuses what cannot be kept and the refusal
* is shown below.
*/
const operate = (op) => {
const result = propose(op);
/* A removed node cannot stay selected; a replaced one keeps its id. */
if (result.ok && op.op === 'remove') setSelectedId(null);
};
/* Nothing proposed and nothing saved means nothing to say. Without this the
component drew an empty bar on every page, which is most of what the button
was being blamed for. */
if (!previewing && !customised && !problems.length && !skipped.length) return null;
return (
<div data-ui-controls="editor" className="space-y-2">
<div className="flex flex-wrap items-center justify-between gap-2">
<Button
size="xs"
variant={open ? 'default' : 'outline'}
shape="rounded"
onClick={() => setOpen((v) => !v)}
>
<SlidersHorizontal className="mr-1.5 h-3.5 w-3.5" aria-hidden="true" />
Customise layout
</Button>
{/* The preview bar. Unsaved and saved have to be told apart at a
glance, because the whole promise is that nothing is kept until
somebody says so. */}
<div className="flex flex-wrap items-center justify-end gap-2">
{/* Unsaved and saved have to be told apart at a glance, because the
whole promise is that nothing is kept until somebody says so. */}
{(previewing || customised) && (
<div className="flex flex-wrap items-center gap-2">
{previewing && (
@@ -122,25 +95,6 @@ export function UiEditor({ registry = nodeRegistry }) {
{skipped.length} saved change{skipped.length === 1 ? '' : 's'} no longer apply to this page.
</p>
)}
{open && (
<div className="grid gap-3 rounded-xl border border-border bg-surface-subtle p-3 lg:grid-cols-2">
<div className="space-y-3">
<TreePanel tree={tree} selectedId={selectedId} onSelect={setSelectedId} registry={registry} />
<NodePicker tree={tree} parent={parent} page={page} onAdd={operate} registry={registry} />
</div>
<div className="rounded-xl border border-border bg-surface p-3">
{selected
? <NodeInspector node={selected} siblings={siblings} page={page} onOperate={operate} registry={registry} />
: (
<p className="text-caption text-ink-3">
Choose a section on the left to see what can be changed about it.
</p>
)}
</div>
</div>
)}
</div>
);
}

View File

@@ -88,7 +88,7 @@ export class UiNodeBoundary extends React.Component<UiNodeBoundaryProps, UiNodeB
{node?.props?.title || 'This section could not be shown'}
</p>
<p className="mt-0.5 text-caption text-ink-4">
It is still on the page and can be hidden or removed from Customise layout.
It is still on the page. Ask Owliver to hide it.
</p>
</section>
);