diff --git a/packages/core/src/registry/nodes/database.ts b/packages/core/src/registry/nodes/database.ts index bd122440..69539f0a 100644 --- a/packages/core/src/registry/nodes/database.ts +++ b/packages/core/src/registry/nodes/database.ts @@ -20,6 +20,7 @@ export const databaseAttributesSchema = z.object({ parentId: z.string(), fields: z.record(z.string(), fieldAttributesSchema), nameField: databaseNameFieldAttributesSchema.nullable().optional(), + locked: z.boolean().nullable().optional(), }); export type DatabaseAttributes = z.infer; diff --git a/packages/ui/src/components/databases/boards/board-view-settings.tsx b/packages/ui/src/components/databases/boards/board-view-settings.tsx index 9599864f..bee7aeaf 100644 --- a/packages/ui/src/components/databases/boards/board-view-settings.tsx +++ b/packages/ui/src/components/databases/boards/board-view-settings.tsx @@ -1,4 +1,4 @@ -import { Trash2 } from 'lucide-react'; +import { Lock, LockOpen, Trash2 } from 'lucide-react'; import { Fragment, useState } from 'react'; import { ViewAvatarInput } from '@colanode/ui/components/databases/view-avatar-input'; @@ -35,12 +35,12 @@ export const BoardViewSettings = () => { name={view.name} avatar={view.avatar} layout={view.layout} - readOnly={!database.canEdit} + readOnly={!database.canEdit || database.isLocked} /> @@ -53,13 +53,30 @@ export const BoardViewSettings = () => {
{ - setOpenDelete(true); - setOpen(false); + database.toggleLock(); }} > - - Delete view + {database.isLocked ? ( + + ) : ( + + )} + + {database.isLocked ? 'Unlock database' : 'Lock database'} +
+ {!database.isLocked && ( +
{ + setOpenDelete(true); + setOpen(false); + }} + > + + Delete view +
+ )} )} diff --git a/packages/ui/src/components/databases/calendars/calendar-view-settings.tsx b/packages/ui/src/components/databases/calendars/calendar-view-settings.tsx index b70abf85..2adc8bb1 100644 --- a/packages/ui/src/components/databases/calendars/calendar-view-settings.tsx +++ b/packages/ui/src/components/databases/calendars/calendar-view-settings.tsx @@ -1,4 +1,4 @@ -import { Trash2 } from 'lucide-react'; +import { Lock, LockOpen, Trash2 } from 'lucide-react'; import { Fragment, useState } from 'react'; import { ViewAvatarInput } from '@colanode/ui/components/databases/view-avatar-input'; @@ -35,12 +35,12 @@ export const CalendarViewSettings = () => { name={view.name} avatar={view.avatar} layout={view.layout} - readOnly={!database.canEdit} + readOnly={!database.canEdit || database.isLocked} /> @@ -53,13 +53,30 @@ export const CalendarViewSettings = () => {
{ - setOpenDelete(true); - setOpen(false); + database.toggleLock(); }} > - - Delete view + {database.isLocked ? ( + + ) : ( + + )} + + {database.isLocked ? 'Unlock database' : 'Lock database'} +
+ {!database.isLocked && ( +
{ + setOpenDelete(true); + setOpen(false); + }} + > + + Delete view +
+ )} )} diff --git a/packages/ui/src/components/databases/database-settings.tsx b/packages/ui/src/components/databases/database-settings.tsx index 726b5418..2640a98f 100644 --- a/packages/ui/src/components/databases/database-settings.tsx +++ b/packages/ui/src/components/databases/database-settings.tsx @@ -1,5 +1,13 @@ -import { Copy, Image, LetterText, Settings, Trash2 } from 'lucide-react'; -import { Fragment, useState } from 'react'; +import { + Copy, + Image, + LetterText, + Settings, + Trash2, + Lock, + LockOpen, +} from 'lucide-react'; +import { Fragment, useCallback, useState } from 'react'; import { LocalDatabaseNode } from '@colanode/client/types'; import { NodeRole, hasNodeRole } from '@colanode/core'; @@ -14,6 +22,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@colanode/ui/components/ui/dropdown-menu'; +import { useWorkspace } from '@colanode/ui/contexts/workspace'; interface DatabaseSettingsProps { database: LocalDatabaseNode; @@ -21,11 +30,33 @@ interface DatabaseSettingsProps { } export const DatabaseSettings = ({ database, role }: DatabaseSettingsProps) => { + const workspace = useWorkspace(); const [showUpdateDialog, setShowUpdateDialog] = useState(false); const [showDeleteDialog, setShowDeleteModal] = useState(false); const canEdit = hasNodeRole(role, 'editor'); const canDelete = hasNodeRole(role, 'admin'); + const isLocked = database.locked ?? false; + + const handleLockDatabase = useCallback(() => { + if (!canEdit) { + return; + } + + const nodes = workspace.collections.nodes; + if (!nodes.has(database.id)) { + return; + } + + nodes.update(database.id, (draft) => { + if (draft.type !== 'database') { + return; + } + + const currentLocked = draft.locked ?? false; + draft.locked = !currentLocked; + }); + }, [canEdit, database.id, workspace.userId]); return ( @@ -64,6 +95,18 @@ export const DatabaseSettings = ({ database, role }: DatabaseSettingsProps) => { Update icon + + {isLocked ? ( + + ) : ( + + )} + {isLocked ? 'Unlock database' : 'Lock database'} + { + const workspace = useWorkspace(); + const canEdit = hasNodeRole(role, 'editor'); + const isLocked = database.locked ?? false; const canCreateRecord = hasNodeRole(role, 'editor'); + const toggleLock = useCallback(() => { + if (!canEdit) { + return; + } + + const nodes = workspace.collections.nodes; + nodes.update(database.id, (draft) => { + if (draft.type !== 'database') { + return; + } + + const currentLocked = draft.locked ?? false; + draft.locked = !currentLocked; + }); + }, [canEdit, database.id, workspace.userId]); + return ( { nameField: database.nameField, role, fields: Object.values(database.fields), - canEdit, + canEdit: canEdit, + isLocked, canCreateRecord, rootId: database.rootId, + toggleLock, }} > {children} diff --git a/packages/ui/src/components/databases/fields/field-create-popover.tsx b/packages/ui/src/components/databases/fields/field-create-popover.tsx index e017ff21..792de39c 100644 --- a/packages/ui/src/components/databases/fields/field-create-popover.tsx +++ b/packages/ui/src/components/databases/fields/field-create-popover.tsx @@ -161,7 +161,7 @@ export const FieldCreatePopover = ({ }, }); - if (!database.canEdit) { + if (!database.canEdit || database.isLocked) { return null; } diff --git a/packages/ui/src/components/databases/fields/field-rename-input.tsx b/packages/ui/src/components/databases/fields/field-rename-input.tsx index 457e4170..85216350 100644 --- a/packages/ui/src/components/databases/fields/field-rename-input.tsx +++ b/packages/ui/src/components/databases/fields/field-rename-input.tsx @@ -40,7 +40,7 @@ export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
{ const newValue = event.target.value; mutate(newValue); diff --git a/packages/ui/src/components/databases/tables/table-view-field-header.tsx b/packages/ui/src/components/databases/tables/table-view-field-header.tsx index 421c8b93..c5d7d2da 100644 --- a/packages/ui/src/components/databases/tables/table-view-field-header.tsx +++ b/packages/ui/src/components/databases/tables/table-view-field-header.tsx @@ -143,7 +143,7 @@ export const TableViewFieldHeader = ({ const [, dragRef] = useDrag({ type: 'table-field-header', item: viewField, - canDrag: () => database.canEdit, + canDrag: () => database.canEdit && !database.isLocked, end: (_item, monitor) => { const dropResult = monitor.getDropResult<{ after: string }>(); if (!dropResult?.after) return; @@ -190,7 +190,7 @@ export const TableViewFieldHeader = ({ bottomLeft: false, bottomRight: false, left: false, - right: database.canEdit, + right: database.canEdit && !database.isLocked, top: false, topLeft: false, topRight: false, diff --git a/packages/ui/src/components/databases/tables/table-view-header.tsx b/packages/ui/src/components/databases/tables/table-view-header.tsx index 548a82eb..26b98f73 100644 --- a/packages/ui/src/components/databases/tables/table-view-header.tsx +++ b/packages/ui/src/components/databases/tables/table-view-header.tsx @@ -17,7 +17,7 @@ export const TableViewHeader = () => { {view.fields.map((field) => { return ; })} - {database.canEdit && ( + {database.canEdit && !database.isLocked && ( } /> diff --git a/packages/ui/src/components/databases/tables/table-view-name-header.tsx b/packages/ui/src/components/databases/tables/table-view-name-header.tsx index 0da1a34f..f04ffad8 100644 --- a/packages/ui/src/components/databases/tables/table-view-name-header.tsx +++ b/packages/ui/src/components/databases/tables/table-view-name-header.tsx @@ -74,7 +74,7 @@ export const TableViewNameHeader = () => { bottomLeft: false, bottomRight: false, left: false, - right: database.canEdit, + right: database.canEdit && !database.isLocked, top: false, topLeft: false, topRight: false, @@ -112,7 +112,7 @@ export const TableViewNameHeader = () => {
{ const newName = e.target.value; if (newName === database.nameField?.name) return; @@ -128,7 +128,7 @@ export const TableViewNameHeader = () => { />
- {database.canEdit && ( + {database.canEdit && !database.isLocked && (
{ name={view.name} avatar={view.avatar} layout={view.layout} - readOnly={!database.canEdit} + readOnly={!database.canEdit || database.isLocked} />
@@ -53,13 +53,30 @@ export const TableViewSettings = () => {
{ - setOpenDelete(true); - setOpen(false); + database.toggleLock(); }} > - - Delete view + {database.isLocked ? ( + + ) : ( + + )} + + {database.isLocked ? 'Unlock database' : 'Lock database'} +
+ {!database.isLocked && ( +
{ + setOpenDelete(true); + setOpen(false); + }} + > + + Delete view +
+ )}
)} diff --git a/packages/ui/src/components/databases/view-create-dialog.tsx b/packages/ui/src/components/databases/view-create-dialog.tsx index e77fd653..20917efb 100644 --- a/packages/ui/src/components/databases/view-create-dialog.tsx +++ b/packages/ui/src/components/databases/view-create-dialog.tsx @@ -147,7 +147,7 @@ export const ViewCreateDialog = ({ onOpenChange(false); }; - if (!database.canEdit) { + if (!database.canEdit || database.isLocked) { return null; } diff --git a/packages/ui/src/components/databases/view-field-settings.tsx b/packages/ui/src/components/databases/view-field-settings.tsx index 61f5721c..30200940 100644 --- a/packages/ui/src/components/databases/view-field-settings.tsx +++ b/packages/ui/src/components/databases/view-field-settings.tsx @@ -79,10 +79,12 @@ export const ViewFieldSettings = () => { { - if (!database.canEdit) return; + if (!database.canEdit || database.isLocked) return; handleDisplayChange({ id: field.id, @@ -103,13 +105,15 @@ export const ViewFieldSettings = () => { : 'Show field in this view'} - {database.canEdit && ( + {database.canEdit && !database.isLocked && ( { setDeleteFieldId(field.id); diff --git a/packages/ui/src/components/databases/view-tabs.tsx b/packages/ui/src/components/databases/view-tabs.tsx index a5eba999..2f99627b 100644 --- a/packages/ui/src/components/databases/view-tabs.tsx +++ b/packages/ui/src/components/databases/view-tabs.tsx @@ -17,7 +17,7 @@ export const ViewTabs = () => { onClick={() => databaseViews.setActiveViewId(view.id)} /> ))} - {database.canEdit && } + {database.canEdit && !database.isLocked && } ); }; diff --git a/packages/ui/src/components/databases/view.tsx b/packages/ui/src/components/databases/view.tsx index 241c3406..3a061cdb 100644 --- a/packages/ui/src/components/databases/view.tsx +++ b/packages/ui/src/components/databases/view.tsx @@ -108,7 +108,7 @@ export const View = ({ view }: ViewProps) => { }); }, initFieldSort: async (fieldId: string, direction: SortDirection) => { - if (!database.canEdit) { + if (!database.canEdit || database.isLocked) { return; } diff --git a/packages/ui/src/components/records/record-field.tsx b/packages/ui/src/components/records/record-field.tsx index feeab01e..80b1555f 100644 --- a/packages/ui/src/components/records/record-field.tsx +++ b/packages/ui/src/components/records/record-field.tsx @@ -33,7 +33,7 @@ export const RecordField = ({ field }: RecordFieldProps) => { - {database.canEdit && ( + {database.canEdit && !database.isLocked && (
{ diff --git a/packages/ui/src/contexts/database.ts b/packages/ui/src/contexts/database.ts index 3afe27be..9fa57584 100644 --- a/packages/ui/src/contexts/database.ts +++ b/packages/ui/src/contexts/database.ts @@ -12,9 +12,11 @@ interface DatabaseContext { nameField: DatabaseNameFieldAttributes | null | undefined; fields: FieldAttributes[]; canEdit: boolean; + isLocked: boolean; canCreateRecord: boolean; role: NodeRole; rootId: string; + toggleLock: () => void; } export const DatabaseContext = createContext(