diff --git a/.changeset/early-dingos-care.md b/.changeset/early-dingos-care.md new file mode 100644 index 00000000..998ba05c --- /dev/null +++ b/.changeset/early-dingos-care.md @@ -0,0 +1,5 @@ +--- +'@blinkk/root-cms': patch +--- + +feat: add quick edit modal for reference fields diff --git a/packages/root-cms/ui/components/DocEditor/DocEditor.tsx b/packages/root-cms/ui/components/DocEditor/DocEditor.tsx index 92771bf3..76815ac9 100644 --- a/packages/root-cms/ui/components/DocEditor/DocEditor.tsx +++ b/packages/root-cms/ui/components/DocEditor/DocEditor.tsx @@ -115,7 +115,9 @@ import {SelectField} from './fields/SelectField.js'; import {StringField} from './fields/StringField.js'; interface DocEditorProps { + className?: string; docId: string; + hideStatusBar?: boolean; } const COLLECTION_SCHEMA_TYPES_CONTEXT = createContext< @@ -142,12 +144,12 @@ export function DocEditor(props: DocEditorProps) { value={collection?.schema?.types || {}} > -
+
- {!loading && ( + {!loading && !props.hideStatusBar && (
+ + referenceFieldEditorModal.open({docId: refId})} + > + + +
+ + + referenceFieldEditorModal.open({docId: refId}) + } + > + + + { + modals.openContextModal(MODAL_ID, { + ...modalTheme, + className: 'ReferenceFieldEditorModalWrap', + innerProps: props, + title: `Edit ${props.docId}`, + size: 'xl', + overflow: 'inside', + closeOnClickOutside: false, + closeOnEscape: false, + }); + }, + }; +} + +/** + * Modal for quickly editing a referenced doc and persisting changes on save. + */ +export function ReferenceFieldEditorModal( + modalProps: ContextModalProps +) { + const {id, context, innerProps} = modalProps; + const docId = innerProps.docId; + return ( +
+ + + context.closeModal(id)} + /> + +
+ ); +} + +ReferenceFieldEditorModal.Footer = (props: { + docId: string; + onCancel: () => void; +}) => { + const draft = useDraftDoc(); + + return ( +
+
+ +
+ + +
+ ); +}; + +ReferenceFieldEditorModal.id = MODAL_ID; diff --git a/packages/root-cms/ui/hooks/useDraftDoc.tsx b/packages/root-cms/ui/hooks/useDraftDoc.tsx index e9720c4f..880f0d4e 100644 --- a/packages/root-cms/ui/hooks/useDraftDoc.tsx +++ b/packages/root-cms/ui/hooks/useDraftDoc.tsx @@ -68,6 +68,10 @@ export class DraftDocController extends EventListener { private autolockApplied = false; /** When true, prevents any writes to the DB (e.g. user lacks edit access). */ readOnly = false; + /** When false, draft changes are only written by explicitly calling flush(). */ + autoSave = true; + /** When false, pending updates are discarded when stop()/dispose() is called. */ + flushOnStop = true; started = false; constructor(docId: string) { @@ -144,7 +148,9 @@ export class DraftDocController extends EventListener { if (this.dbUnsubscribe) { this.dbUnsubscribe(); } - this.flush(); + if (this.flushOnStop) { + this.flush(); + } this.started = false; } @@ -196,7 +202,9 @@ export class DraftDocController extends EventListener { this.pendingUpdates.set(key, value); this.store.set(key, value); this.setSaveState(SaveState.UPDATES_PENDING); - this.queueChanges(); + if (this.autoSave) { + this.queueChanges(); + } } /** @@ -219,7 +227,9 @@ export class DraftDocController extends EventListener { } this.store.update(updates); this.setSaveState(SaveState.UPDATES_PENDING); - this.queueChanges(); + if (this.autoSave) { + this.queueChanges(); + } } /** @@ -232,7 +242,9 @@ export class DraftDocController extends EventListener { this.pendingUpdates.set(key, deleteField()); this.store.set(key, undefined); this.setSaveState(SaveState.UPDATES_PENDING); - this.queueChanges(); + if (this.autoSave) { + this.queueChanges(); + } } /** @@ -392,6 +404,10 @@ export interface DraftDocProviderProps { docId: string; /** When true, prevents any writes to the DB (e.g. user lacks edit access). */ readOnly?: boolean; + /** When false, changes are only persisted when `flush()` is called. */ + autoSave?: boolean; + /** When false, pending updates are discarded when unmounting the provider. */ + flushOnStop?: boolean; children?: ComponentChildren; } @@ -415,6 +431,8 @@ export function DraftDocProvider(props: DraftDocProviderProps) { // Set readOnly mode on the controller based on the prop. controller.readOnly = props.readOnly ?? false; + controller.autoSave = props.autoSave ?? true; + controller.flushOnStop = props.flushOnStop ?? true; useEffect(() => { setLoading(true); diff --git a/packages/root-cms/ui/ui.tsx b/packages/root-cms/ui/ui.tsx index 8bc7b29a..5c6dd820 100644 --- a/packages/root-cms/ui/ui.tsx +++ b/packages/root-cms/ui/ui.tsx @@ -22,6 +22,7 @@ import {ExportSheetModal} from './components/ExportSheetModal/ExportSheetModal.j import {LocalizationModal} from './components/LocalizationModal/LocalizationModal.js'; import {LockPublishingModal} from './components/LockPublishingModal/LockPublishingModal.js'; import {PublishDocModal} from './components/PublishDocModal/PublishDocModal.js'; +import {ReferenceFieldEditorModal} from './components/ReferenceFieldEditorModal/ReferenceFieldEditorModal.js'; import {ScheduleReleaseModal} from './components/ScheduleReleaseModal/ScheduleReleaseModal.js'; import {VersionHistoryModal} from './components/VersionHistoryModal/VersionHistoryModal.js'; import {FirebaseContext, FirebaseContextObject} from './hooks/useFirebase.js'; @@ -127,6 +128,7 @@ function App() { [LocalizationModal.id]: LocalizationModal, [LockPublishingModal.id]: LockPublishingModal, [PublishDocModal.id]: PublishDocModal, + [ReferenceFieldEditorModal.id]: ReferenceFieldEditorModal, [ScheduleReleaseModal.id]: ScheduleReleaseModal, [VersionHistoryModal.id]: VersionHistoryModal, }}