UBERF-5603 (#4754)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2024-02-23 15:39:49 +07:00
committed by GitHub
parent cc8a1d549a
commit ffc8d1dfac
184 changed files with 2755 additions and 561 deletions
+3 -1
View File
@@ -31,9 +31,10 @@ import {
selectionStore,
selectionLimit
} from './selection'
import { deleteObjects, getObjectLinkFragment } from './utils'
import { deleteObjects, getObjectLinkFragment, restrictionStore } from './utils'
import contact from '@hcengineering/contact'
import { locationToUrl } from '@hcengineering/ui'
import { get } from 'svelte/store'
/**
* Action to be used for copying text to clipboard.
@@ -206,6 +207,7 @@ const MoveRight = (doc: Doc | undefined, evt: Event): void => {
}
function ShowActions (doc: Doc | Doc[] | undefined, evt: Event): void {
if (get(restrictionStore).disableActions) return
evt.preventDefault()
showPopup(view.component.ActionsPopup, { viewContext: $contextStore.getLastContext() }, 'top')
+16
View File
@@ -27,6 +27,7 @@ import core, {
} from '@hcengineering/core'
import { getResource } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { getEventPositionElement, showPopup } from '@hcengineering/ui'
import {
type Action,
type ActionGroup,
@@ -34,8 +35,10 @@ import {
type ViewActionInput,
type ViewContextType
} from '@hcengineering/view'
import Menu from './components/Menu.svelte'
import view from './plugin'
import { type FocusSelection, type SelectionStore } from './selection'
import { restrictionStore } from './utils'
/**
* @public
@@ -226,3 +229,16 @@ export function filterActions (
}
return result
}
let disableActions: boolean = false
restrictionStore.subscribe((v) => {
disableActions = v.disableActions
})
export function showMenu (ev: MouseEvent, props: any, onClose?: (result: any) => void | Promise<void>): void {
ev.stopPropagation()
ev.preventDefault()
if (disableActions) return
showPopup(Menu, props, getEventPositionElement(ev), onClose)
}
@@ -22,7 +22,7 @@
import { fly } from 'svelte/transition'
import { getContextActions, getSelection } from '../actions'
import { ListSelectionProvider, SelectionStore, focusStore, previewDocument, selectionStore } from '../selection'
import { getObjectPreview } from '../utils'
import { getObjectPreview, restrictionStore } from '../utils'
const client = getClient()
@@ -128,7 +128,10 @@
docs: selectionDocs
})
$: disableActions = $restrictionStore.disableActions
async function handleKeys (evt: KeyboardEvent): Promise<void> {
if (disableActions) return
const targetTagName = (evt.target as any)?.tagName?.toLowerCase()
let elm = evt.target as HTMLElement
@@ -18,7 +18,7 @@
import { AttributesBar, KeyedAttribute, getAttribute, getClient } from '@hcengineering/presentation'
import setting, { settingId } from '@hcengineering/setting'
import { Button, Label, getCurrentResolvedLocation, navigate } from '@hcengineering/ui'
import { getFiltredKeys, isCollectionAttr } from '../utils'
import { getFiltredKeys, isCollectionAttr, restrictionStore } from '../utils'
export let object: Doc | Record<string, any>
export let _class: Ref<Class<Doc>>
@@ -75,23 +75,25 @@
</div>
</div>
<div class="tool">
<Button
icon={setting.icon.Setting}
kind={'link'}
size={'medium'}
showTooltip={{ label: setting.string.ClassSetting }}
on:click={(ev) => {
ev.stopPropagation()
const loc = getCurrentResolvedLocation()
loc.path[2] = settingId
loc.path[3] = 'setting'
loc.path[4] = 'classes'
loc.path.length = 5
loc.query = { _class }
loc.fragment = undefined
navigate(loc)
}}
/>
{#if !$restrictionStore.disableNavigation}
<Button
icon={setting.icon.Setting}
kind={'link'}
size={'medium'}
showTooltip={{ label: setting.string.ClassSetting }}
on:click={(ev) => {
ev.stopPropagation()
const loc = getCurrentResolvedLocation()
loc.path[2] = settingId
loc.path[3] = 'setting'
loc.path[4] = 'classes'
loc.path.length = 5
loc.query = { _class }
loc.fragment = undefined
navigate(loc)
}}
/>
{/if}
</div>
</div>
{/if}
@@ -24,6 +24,7 @@
export let ignoreKeys: string[]
export let allowedCollections: string[] = []
export let showHeader: boolean = true
export let readonly: boolean = false
const client = getClient()
const hierarchy = client.getHierarchy()
@@ -36,6 +37,7 @@
to={undefined}
{allowedCollections}
{showHeader}
{readonly}
on:update
/>
{#each mixins as mixin}
@@ -47,6 +49,7 @@
object={hierarchy.as(object, mixin._id)}
{ignoreKeys}
{to}
{readonly}
{allowedCollections}
{showHeader}
on:update
@@ -17,10 +17,10 @@
import { NavLink, getClient } from '@hcengineering/presentation'
import { AnyComponent, getPanelURI, locationToUrl } from '@hcengineering/ui'
import view from '../plugin'
import { getObjectLinkFragment } from '../utils'
import { getObjectLinkFragment, restrictionStore } from '../utils'
export let object: Doc | undefined
export let disabled = false
export let disabled: boolean = false
export let onClick: ((event: MouseEvent) => void) | undefined = undefined
export let noUnderline = disabled
export let inline = false
@@ -31,6 +31,9 @@
export let accent: boolean = false
export let noOverflow: boolean = false
let _disabled = disabled || $restrictionStore.disableNavigation
$: _disabled = disabled || $restrictionStore.disableNavigation
const client = getClient()
const hierarchy = client.getHierarchy()
@@ -40,7 +43,7 @@
: undefined
async function getHref (object: Doc): Promise<void> {
if (disabled) {
if (_disabled) {
href = undefined
return
}
@@ -53,6 +56,6 @@
$: if (object !== undefined) getHref(object)
</script>
<NavLink {disabled} {onClick} {noUnderline} {inline} {shrink} {href} {colorInherit} {accent} {noOverflow}>
<NavLink disabled={_disabled} {onClick} {noUnderline} {inline} {shrink} {href} {colorInherit} {accent} {noOverflow}>
<slot />
</NavLink>
@@ -14,32 +14,33 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher, onDestroy } from 'svelte'
import { Class, Doc, Mixin, Ref } from '@hcengineering/core'
import notification from '@hcengineering/notification'
import { Panel } from '@hcengineering/panel'
import { getResource } from '@hcengineering/platform'
import {
ActionContext,
AttributeCategory,
AttributeCategoryOrder,
AttributesBar,
KeyedAttribute,
ActionContext,
createQuery,
getAttributePresenterClass,
getClient,
hasResource
} from '@hcengineering/presentation'
import { AnyComponent, Button, Component, IconMixin, IconMoreH, showPopup } from '@hcengineering/ui'
import { AnyComponent, Button, Component, IconMixin, IconMoreH } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { createEventDispatcher, onDestroy } from 'svelte'
import { ContextMenu, ParentsNavigator, DocNavLink, getDocLabel, getDocMixins } from '..'
import { DocNavLink, ParentsNavigator, getDocLabel, getDocMixins, showMenu } from '..'
import { categorizeFields, getCollectionCounter, getFiltredKeys } from '../utils'
import DocAttributeBar from './DocAttributeBar.svelte'
export let _id: Ref<Doc>
export let _class: Ref<Class<Doc>>
export let embedded: boolean = false
export let readonly: boolean = false
let realObjectClass: Ref<Class<Doc>> = _class
let lastId: Ref<Doc> = _id
@@ -227,11 +228,6 @@
let panelWidth: number = 0
let innerWidth: number = 0
function showMenu (ev?: Event): void {
if (object !== undefined) {
showPopup(ContextMenu, { object, excludedActions: [view.action.Open] }, (ev as MouseEvent).target as HTMLElement)
}
}
function handleOpen (ev: CustomEvent): void {
ignoreKeys = ev.detail.ignoreKeys
activityOptions = ev.detail.activityOptions ?? activityOptions
@@ -259,6 +255,7 @@
<Panel
{object}
isHeader={mainEditor?.pinned ?? false}
allowClose={!embedded}
isAside={true}
{embedded}
bind:content
@@ -272,7 +269,7 @@
dispatch('close')
}}
withoutActivity={!activityOptions.enabled}
withoutInput={!activityOptions.showInput}
withoutInput={!activityOptions.showInput || readonly}
>
<svelte:fragment slot="title">
{#if !embedded}<ParentsNavigator element={object} />{/if}
@@ -284,7 +281,16 @@
</svelte:fragment>
<svelte:fragment slot="utils">
<Button icon={IconMoreH} iconProps={{ size: 'medium' }} kind={'icon'} on:click={showMenu} />
{#if !readonly}
<Button
icon={IconMoreH}
iconProps={{ size: 'medium' }}
kind={'icon'}
on:click={(e) => {
showMenu(e, { object, excludedActions: [view.action.Open] })
}}
/>
{/if}
<Button
icon={IconMixin}
iconProps={{ size: 'medium' }}
@@ -301,19 +307,29 @@
{#if headerEditor !== undefined}
<Component
is={headerEditor}
props={{ object, keys, mixins, ignoreKeys, vertical: dir === 'column', allowedCollections, embedded }}
props={{
object,
keys,
mixins,
ignoreKeys,
vertical: dir === 'column',
allowedCollections,
embedded,
readonly
}}
on:update={updateKeys}
/>
{:else if dir === 'column'}
<DocAttributeBar
{object}
{mixins}
{readonly}
ignoreKeys={[...ignoreKeys, ...collectionArrays, ...inplaceAttributes]}
{allowedCollections}
on:update={updateKeys}
/>
{:else}
<AttributesBar {object} _class={realObjectClass} {keys} />
<AttributesBar {object} _class={realObjectClass} {keys} {readonly} />
{/if}
{/if}
</svelte:fragment>
@@ -321,14 +337,14 @@
<svelte:fragment slot="header">
{#if mainEditor && mainEditor.editor && mainEditor.pinned}
<div class="flex-col flex-grow my-4">
<Component is={mainEditor.editor} props={{ object }} on:open={handleOpen} />
<Component is={mainEditor.editor} props={{ object, readonly }} on:open={handleOpen} />
</div>
{/if}
</svelte:fragment>
{#if mainEditor && mainEditor.editor && !mainEditor.pinned}
<div class="flex-col flex-grow flex-no-shrink step-tb-6">
<Component is={mainEditor.editor} props={{ object }} on:open={handleOpen} />
<Component is={mainEditor.editor} props={{ object, readonly }} on:open={handleOpen} />
</div>
{/if}
@@ -343,6 +359,7 @@
object,
space: object.space,
key: collection.key,
readonly,
[collection.key.key]: getCollectionCounter(hierarchy, object, collection.key)
}}
/>
@@ -352,7 +369,7 @@
{#if editorFooter}
<div class="step-tb-6">
<Component is={editorFooter.footer} props={{ object, _class, ...editorFooter.props }} />
<Component is={editorFooter.footer} props={{ object, _class, ...editorFooter.props, readonly }} />
</div>
{/if}
</Panel>
@@ -26,6 +26,7 @@
export let kind: ButtonKind = 'link'
export let size: ButtonSize = 'large'
export let allowDeselect: boolean = false
export let readonly: boolean = false
let items: DropdownTextItem[] = []
@@ -56,6 +57,7 @@
{kind}
width={'100%'}
{allowDeselect}
disabled={readonly}
autoSelect={false}
on:selected={(e) => {
onChange(e.detail)
@@ -20,7 +20,7 @@
import { createEventDispatcher, onMount } from 'svelte'
import view from '../plugin'
export let value: string = ''
export let value: string | undefined = ''
export let editable: boolean | undefined = true
const placeholder: IntlString = view.string.HyperlinkPlaceholder
@@ -33,7 +33,9 @@
if (input) input.focus()
})
const copyLink = (): void => {
copyTextToClipboard(value)
if (value !== undefined) {
copyTextToClipboard(value)
}
}
</script>
@@ -92,6 +94,7 @@
kind={'ghost'}
size={'small'}
icon={IconCopy}
disabled={value === undefined || value === ''}
showTooltip={{ label: view.string.CopyToClipboard }}
on:click={() => {
copyLink()
@@ -102,6 +105,7 @@
kind={'ghost'}
size={'small'}
icon={IconArrowRight}
disabled={value === undefined || value === ''}
showTooltip={{ label: view.string.Open }}
on:click={() => {
dispatch('update', value)
@@ -15,7 +15,7 @@
<script lang="ts">
import { AttachedDoc, Doc } from '@hcengineering/core'
import { Breadcrumbs, BreadcrumbsModel, getClient } from '@hcengineering/presentation'
import { getObjectPresenter, isAttachedDoc } from '../utils'
import { getObjectPresenter, isAttachedDoc, restrictionStore } from '../utils'
import { AttributeModel } from '@hcengineering/view'
export let element: Doc | AttachedDoc
@@ -77,6 +77,6 @@
{#await getBreadcrumbsModels(element) then models}
{#if models.length > 0}
<Breadcrumbs {models} />
<Breadcrumbs {models} disabled={$restrictionStore.disableNavigation} />
{/if}
{/await}
@@ -14,8 +14,6 @@
// limitations under the License.
-->
<script lang="ts">
import { deepEqual } from 'fast-equals'
import { createEventDispatcher } from 'svelte'
import core, {
AnyAttribute,
Class,
@@ -24,9 +22,9 @@
FindOptions,
Lookup,
Ref,
SortingOrder,
TxOperations,
getObjectValue,
SortingOrder
getObjectValue
} from '@hcengineering/core'
import notification from '@hcengineering/notification'
import { createQuery, getClient, updateAttribute } from '@hcengineering/presentation'
@@ -34,19 +32,19 @@
Button,
CheckBox,
Component,
getEventPositionElement,
Label,
Loading,
mouseAttractor,
resizeObserver,
showPopup,
Spinner,
lazyObserver
lazyObserver,
mouseAttractor,
resizeObserver
} from '@hcengineering/ui'
import { AttributeModel, BuildModelKey, BuildModelOptions } from '@hcengineering/view'
import { deepEqual } from 'fast-equals'
import { createEventDispatcher } from 'svelte'
import { showMenu } from '../actions'
import view from '../plugin'
import { buildConfigLookup, buildModel, LoadingProps } from '../utils'
import Menu from './Menu.svelte'
import { LoadingProps, buildConfigLookup, buildModel, restrictionStore } from '../utils'
import IconUpDown from './icons/UpDown.svelte'
export let _class: Ref<Class<Doc>>
@@ -160,14 +158,14 @@
{ sort: getSort(_sortKey), limit: 1, ...options, lookup, total: true }
)
const showMenu = async (ev: MouseEvent, object: Doc, row: number): Promise<void> => {
const showContextMenu = async (ev: MouseEvent, object: Doc, row: number): Promise<void> => {
selection = row
if (!checkedSet.has(object._id)) {
check(objects, false)
checked = []
}
const items = checked.length > 0 ? checked : object
showPopup(Menu, { object: items, baseMenuClass }, getEventPositionElement(ev))
showMenu(ev, { object: items, baseMenuClass })
}
function changeSorting (key: string | string[]): void {
@@ -217,14 +215,21 @@
}
}
const joinProps = (attribute: AttributeModel, object: Doc) => {
const joinProps = (attribute: AttributeModel, object: Doc, readonly: boolean) => {
const readonlyParams = readonly
? {
readonly: true,
editable: false,
disabled: true
}
: {}
if (attribute.collectionAttr) {
return { object, ...attribute.props }
return { object, ...attribute.props, ...readonlyParams }
}
if (attribute.attribute?.type._class === core.class.EnumOf) {
return { ...attribute.props, type: attribute.attribute.type }
return { ...attribute.props, type: attribute.attribute.type, ...readonlyParams }
}
return { ...attribute.props, space: object.space }
return { ...attribute.props, space: object.space, ...readonlyParams }
}
function getValue (attribute: AttributeModel, object: Doc): any {
if (attribute.castRequest) {
@@ -300,7 +305,7 @@
function contextHandler (object: Doc, row: number): (ev: MouseEvent) => void {
return (ev) => {
if (!readonly) {
showMenu(ev, object, row)
showContextMenu(ev, object, row)
}
}
}
@@ -379,7 +384,7 @@
})}
on:focus={() => {}}
bind:this={refs[row]}
on:contextmenu|preventDefault={contextHandler(object, row)}
on:contextmenu={contextHandler(object, row)}
use:lazyObserver={(val) => {
if (val && row >= rowLimit) {
rowLimit = row + 10
@@ -430,7 +435,7 @@
this={attribute.presenter}
value={getValue(attribute, object)}
onChange={getOnChange(object, attribute)}
{...joinProps(attribute, object)}
{...joinProps(attribute, object, $restrictionStore.readonly)}
/>
</div>
</td>
@@ -26,6 +26,7 @@
export let viewlet: Viewlet | undefined
export let kind: ButtonKind = 'regular'
export let viewOptions: ViewOptions
export let disabled: boolean = false
const dispatch = createEventDispatcher()
const client = getClient()
@@ -110,6 +111,7 @@
<Button
icon={view.icon.ViewButton}
label={view.string.View}
{disabled}
{kind}
adaptiveShrink={'sm'}
showTooltip={{ label: view.string.CustomizeView, direction: 'bottom' }}
@@ -19,10 +19,12 @@
import { getViewOptions, viewOptionStore } from '../viewOptions'
import ViewOptionsButton from './ViewOptionsButton.svelte'
import ViewletSetting from './ViewletSetting.svelte'
import { restrictionStore } from '../utils'
export let kind: ButtonKind = 'regular'
export let viewOptions: ViewOptions | undefined = undefined
export let viewlet: Viewlet | undefined = undefined
export let disabled: boolean = false
let btn: HTMLButtonElement
@@ -31,6 +33,8 @@
}
$: viewOptions = getViewOptions(viewlet, $viewOptionStore)
$: disabled = $restrictionStore.readonly
</script>
{#if viewlet}
@@ -41,6 +45,7 @@
<Button
icon={view.icon.Configure}
label={view.string.Show}
{disabled}
{kind}
shrink={1}
adaptiveShrink={'sm'}
@@ -22,6 +22,7 @@
import { getViewOptions, viewOptionStore } from '../viewOptions'
import ViewOptionsButton from './ViewOptionsButton.svelte'
import ViewletSetting from './ViewletSetting.svelte'
import { restrictionStore } from '../utils'
export let viewletQuery: DocumentQuery<Viewlet> = {}
export let kind: ButtonKind = 'regular'
@@ -31,6 +32,7 @@
export let viewlets: Array<WithLookup<Viewlet>> = []
export let preference: ViewletPreference | undefined = undefined
export let loading = true
export let disabled: boolean = false
const dispatch = createEventDispatcher()
@@ -76,18 +78,21 @@
} else {
preferenceQuery.unsubscribe()
}
$: disabled = $restrictionStore.readonly
</script>
{#if viewlet}
<div class="flex-row-center gap-2 reverse">
{#if viewOptions}
<ViewOptionsButton {viewlet} {kind} {viewOptions} />
<ViewOptionsButton {viewlet} {kind} {viewOptions} {disabled} />
{/if}
<Button
icon={view.icon.Configure}
label={view.string.Show}
{kind}
shrink={1}
{disabled}
adaptiveShrink={'sm'}
showTooltip={{ label: view.string.CustomizeView, direction: 'bottom' }}
bind:input={btn}
@@ -30,21 +30,14 @@
import { IntlString } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { DocWithRank, calcRank } from '@hcengineering/task'
import {
AnyComponent,
AnySvelteComponent,
ExpandCollapse,
getEventPositionElement,
mouseAttractor,
showPopup
} from '@hcengineering/ui'
import { AnyComponent, AnySvelteComponent, ExpandCollapse, mouseAttractor } from '@hcengineering/ui'
import { AttributeModel, BuildModelKey, ViewOptionModel, ViewOptions, Viewlet } from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
import { fade } from 'svelte/transition'
import { FocusSelection, SelectionFocusProvider, focusStore } from '../../selection'
import Menu from '../Menu.svelte'
import ListHeader from './ListHeader.svelte'
import ListItem from './ListItem.svelte'
import { showMenu } from '../../actions'
export let category: PrimitiveType | AggregateValue
export let headerComponent: AttributeModel | undefined
@@ -169,7 +162,6 @@
$: selectedObjectIdsSet = new Set<Ref<Doc>>(selectedObjectIds.map((it) => it._id))
const handleMenuOpened = async (event: MouseEvent, object: Doc) => {
event.preventDefault()
handleRowFocused(object)
if (!selectedObjectIdsSet.has(object._id)) {
@@ -177,8 +169,7 @@
}
const items = selectedObjectIds.length > 0 ? selectedObjectIds : object
showPopup(Menu, { object: items, baseMenuClass }, getEventPositionElement(event))
showMenu(event, { object: items, baseMenuClass })
}
$: _newObjectProps = (doc: Doc | undefined): Record<string, any> | undefined => {
@@ -22,6 +22,7 @@
import view from '../../plugin'
import GrowPresenter from './GrowPresenter.svelte'
import ListPresenter from './ListPresenter.svelte'
import { restrictionStore } from '../../utils'
export let docObject: Doc
export let model: AttributeModel[]
@@ -65,6 +66,19 @@
}
}
function getProps (props: Record<string, any>, readonly: boolean): Record<string, any> {
if (readonly) {
return {
...props,
readonly: true,
disabled: true,
editable: false,
isEditable: false
}
}
return props
}
$: mobile = $deviceInfo.isMobile
onMount(() => {
@@ -124,7 +138,7 @@
<ListPresenter
{docObject}
attributeModel={attrModel}
{props}
props={getProps(props, $restrictionStore.readonly)}
{compactMode}
value={getObjectValue(attrModel.key, docObject)}
onChange={getOnChange(docObject, attrModel)}
@@ -138,7 +152,7 @@
<ListPresenter
{docObject}
attributeModel={attrModel}
{props}
props={getProps(props, $restrictionStore.readonly)}
value={getObjectValue(attrModel.key, docObject)}
onChange={getOnChange(docObject, attrModel)}
/>
@@ -148,7 +162,7 @@
<ListPresenter
{docObject}
attributeModel={attrModel}
{props}
props={getProps(props, $restrictionStore.readonly)}
value={getObjectValue(attrModel.key, docObject)}
onChange={getOnChange(docObject, attrModel)}
/>
@@ -158,7 +172,7 @@
<ListPresenter
{docObject}
{attributeModel}
{props}
props={getProps(props, $restrictionStore.readonly)}
value={getObjectValue(attributeModel.key, docObject)}
onChange={getOnChange(docObject, attributeModel)}
hideDivider={i === 0}
@@ -187,7 +201,7 @@
<ListPresenter
{docObject}
attributeModel={attrModel}
{props}
props={getProps(props, $restrictionStore.readonly)}
{compactMode}
value={getObjectValue(attrModel.key, docObject)}
onChange={getOnChange(docObject, attrModel)}
@@ -202,7 +216,7 @@
<ListPresenter
{docObject}
{attributeModel}
{props}
props={getProps(props, $restrictionStore.readonly)}
value={getObjectValue(attributeModel.key, docObject)}
onChange={getOnChange(docObject, attributeModel)}
hideDivider={j === 0}
@@ -220,7 +234,7 @@
<ListPresenter
{docObject}
{attributeModel}
{props}
props={getProps(props, $restrictionStore.readonly)}
value={getObjectValue(attributeModel.key, docObject)}
onChange={getOnChange(docObject, attributeModel)}
/>
+1 -1
View File
@@ -116,7 +116,7 @@ import {
import { IndexedDocumentPreview } from '@hcengineering/presentation'
import { showEmptyGroups } from './viewOptions'
import { AggregationMiddleware, AnalyticsMiddleware } from './middleware'
export { getActions, invokeAction, getContextActions } from './actions'
export { getActions, invokeAction, getContextActions, showMenu } from './actions'
export { default as ActionButton } from './components/ActionButton.svelte'
export { default as ActionHandler } from './components/ActionHandler.svelte'
export { default as FilterButton } from './components/filter/FilterButton.svelte'
+8
View File
@@ -57,6 +57,7 @@ import {
hasResource,
type KeyedAttribute
} from '@hcengineering/presentation'
import { type Restrictions } from '@hcengineering/guest'
import {
type AnyComponent,
type AnySvelteComponent,
@@ -1223,3 +1224,10 @@ export function getDocMixins (
export function classIcon (client: Client, _class: Ref<Class<Obj>>): Asset | undefined {
return client.getHierarchy().getClass(_class).icon
}
export const restrictionStore = writable<Restrictions>({
readonly: false,
disableComments: false,
disableNavigation: false,
disableActions: false
})