Shift/cmd/ctrl-click multi-select via DefaultEventHandlers, same-type bulk styling as one undo step, mixed-type Delete/Duplicate/Move block, grouped undo for array-aware edit.* commands. Refs #50 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
85 lines
3.1 KiB
TypeScript
85 lines
3.1 KiB
TypeScript
import React from 'react';
|
|
import { Editor, DefaultEventHandlers } from '@craftjs/core';
|
|
import { EditorShell } from './editor/EditorShell';
|
|
import { RenderNode } from './editor/RenderNode';
|
|
import { componentResolver } from './components/resolver';
|
|
import { WhpConfig } from './types';
|
|
import { EditorConfigProvider } from './state/EditorConfigContext';
|
|
import { SiteDesignProvider } from './state/SiteDesignContext';
|
|
import { CanvasTokenStyle } from './editor/CanvasTokenStyle';
|
|
import { SaveGuardProvider, useSaveGuard } from './state/SaveGuardContext';
|
|
import { NoticeProvider } from './state/NoticeContext';
|
|
import { ProjectExtrasProvider } from './state/ProjectExtrasContext';
|
|
import { CanvasOverlays } from './editor/CanvasOverlays';
|
|
import { PageProvider } from './state/PageContext';
|
|
import { SitesmithProvider, useSitesmithModal } from './state/SitesmithContext';
|
|
import { SitesmithModal } from './panels/sitesmith/SitesmithModal';
|
|
|
|
interface AppProps {
|
|
whpConfig: WhpConfig | null;
|
|
}
|
|
|
|
const SitesmithModalMount: React.FC = () => {
|
|
const { isOpen, target, close } = useSitesmithModal();
|
|
if (!isOpen) return null;
|
|
return <SitesmithModal target={target} onClose={close} />;
|
|
};
|
|
|
|
// #50: shift / cmd / ctrl-click adds to the selection (Craft's default is cmd only).
|
|
// In Craft 0.2.12 `isMultiSelectEnabled` is an option of DefaultEventHandlers, not
|
|
// of <Editor>, so it is set by supplying the handlers; the other options keep
|
|
// Craft's own defaults.
|
|
export const isMultiSelectClick = (e: MouseEvent) => e.shiftKey || e.metaKey || e.ctrlKey;
|
|
export const craftHandlers = (store: any) =>
|
|
new DefaultEventHandlers({ store, removeHoverOnMouseleave: false, isMultiSelectEnabled: isMultiSelectClick });
|
|
|
|
// Craft fires onNodesChange only when the serialized node tree changes (not on
|
|
// selection, hover or DOM registration), which is exactly "the canvas was edited".
|
|
export const SaveAwareEditor: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
const { markDirty } = useSaveGuard();
|
|
return (
|
|
<Editor
|
|
resolver={componentResolver}
|
|
enabled={true}
|
|
onRender={RenderNode}
|
|
handlers={craftHandlers}
|
|
onNodesChange={markDirty}
|
|
indicator={{
|
|
className: 'wsb-drop-line',
|
|
thickness: 3,
|
|
success: 'var(--color-accent)',
|
|
error: 'var(--color-danger)',
|
|
transition: '0.08s ease-out',
|
|
style: { borderRadius: 2, boxShadow: '0 0 0 1px rgba(0,0,0,.25)' },
|
|
}}
|
|
>
|
|
{children}
|
|
</Editor>
|
|
);
|
|
};
|
|
|
|
export const App: React.FC<AppProps> = ({ whpConfig }) => {
|
|
return (
|
|
<EditorConfigProvider config={whpConfig}>
|
|
<SaveGuardProvider>
|
|
<NoticeProvider>
|
|
<ProjectExtrasProvider>
|
|
<SiteDesignProvider>
|
|
<CanvasTokenStyle />
|
|
<SaveAwareEditor>
|
|
<PageProvider>
|
|
<SitesmithProvider>
|
|
<EditorShell />
|
|
<CanvasOverlays />
|
|
<SitesmithModalMount />
|
|
</SitesmithProvider>
|
|
</PageProvider>
|
|
</SaveAwareEditor>
|
|
</SiteDesignProvider>
|
|
</ProjectExtrasProvider>
|
|
</NoticeProvider>
|
|
</SaveGuardProvider>
|
|
</EditorConfigProvider>
|
|
);
|
|
};
|