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
@@ -13,17 +13,16 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import activity, { ActivityMessage, ActivityMessageExtension } from '@hcengineering/activity'
import { Action, IconMoreV, showPopup } from '@hcengineering/ui'
import { Menu } from '@hcengineering/view-resources'
import activity, { ActivityMessage, ActivityMessageExtension } from '@hcengineering/activity'
import { createEventDispatcher } from 'svelte'
import AddReactionAction from './reactions/AddReactionAction.svelte'
import SaveMessageAction from './SaveMessageAction.svelte'
import PinMessageAction from './PinMessageAction.svelte'
import ActivityMessageExtensionComponent from './activity-message/ActivityMessageExtension.svelte'
import ActivityMessageAction from './ActivityMessageAction.svelte'
import { isReactionMessage } from '../activityMessagesUtils'
import PinMessageAction from './PinMessageAction.svelte'
import SaveMessageAction from './SaveMessageAction.svelte'
import ActivityMessageExtensionComponent from './activity-message/ActivityMessageExtension.svelte'
import AddReactionAction from './reactions/AddReactionAction.svelte'
export let message: ActivityMessage | undefined
export let extensions: ActivityMessageExtension[] = []
@@ -23,7 +23,7 @@
import core, { getDisplayTime } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Action, Label } from '@hcengineering/ui'
import { getActions } from '@hcengineering/view-resources'
import { getActions, restrictionStore } from '@hcengineering/view-resources'
import ReactionsPresenter from '../reactions/ReactionsPresenter.svelte'
import ActivityMessageExtensionComponent from './ActivityMessageExtension.svelte'
@@ -104,6 +104,9 @@
$: isHidden = !!viewlet?.onlyWithParent && parentMessage === undefined
$: withActionMenu =
withActions && !embedded && (actions.length > 0 || allActionIds.some((id) => !excludedActions.includes(id)))
let readonly: boolean = false
$: readonly = $restrictionStore.disableComments
</script>
{#if !isHidden}
@@ -170,14 +173,14 @@
props={{ object: message, embedded, onReply }}
/>
{/if}
<ReactionsPresenter object={message} />
<ReactionsPresenter object={message} {readonly} />
{#if parentMessage && showEmbedded}
<div class="mt-2" />
<ActivityMessagePresenter value={parentMessage} embedded hideFooter withActions={false} />
{/if}
</div>
{#if withActions}
{#if withActions && !readonly}
<div class="actions" class:opened={isActionsOpened}>
<ActivityMessageActions
message={isReactionMessage(message) ? parentMessage : message}
@@ -24,6 +24,7 @@
export let reactions: Reaction[] = []
export let object: Doc | undefined = undefined
export let readonly: boolean = false
const dispatch = createEventDispatcher()
const client = getClient()
@@ -39,6 +40,7 @@
reactionsAccounts = reactionsAccounts
}
function getClickHandler (emoji: string) {
if (readonly) return
return (e: CustomEvent) => {
e.stopPropagation()
e.preventDefault()
@@ -47,6 +49,7 @@
}
function openEmojiPalette (ev: Event) {
if (readonly) return
ev.preventDefault()
ev.stopPropagation()
showPopup(EmojiPopup, {}, ev.target as HTMLElement, (emoji: string) => {
@@ -61,6 +64,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="item border-radius-1"
class:cursor-pointer={!readonly}
use:tooltip={{ component: ReactionsTooltip, props: { reactionAccounts: accounts } }}
on:click={getClickHandler(emoji)}
>
@@ -70,7 +74,7 @@
</div>
</div>
{/each}
{#if object && reactionsAccounts.size > 0}
{#if object && reactionsAccounts.size > 0 && !readonly}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="item flex-row-center border-radius-1" class:withoutBackground={true} on:click={openEmojiPalette}>
@@ -102,7 +106,6 @@
height: 1.5rem;
background: var(--secondary-button-disabled);
border: none;
cursor: pointer;
&:hover {
border: 1px solid var(--theme-darker-color);
@@ -13,13 +13,14 @@
// limitations under the License.
-->
<script lang="ts">
import { createQuery, getClient } from '@hcengineering/presentation'
import activity, { ActivityMessage, Reaction } from '@hcengineering/activity'
import { createQuery, getClient } from '@hcengineering/presentation'
import Reactions from './Reactions.svelte'
import { updateDocReactions } from '../../utils'
import Reactions from './Reactions.svelte'
export let object: ActivityMessage | undefined
export let readonly = false
const client = getClient()
const reactionsQuery = createQuery()
@@ -35,12 +36,13 @@
}
const handleClick = (ev: CustomEvent) => {
if (readonly) return
updateDocReactions(client, reactions, object, ev.detail)
}
</script>
{#if object && hasReactions}
<div class="footer flex-col p-inline contrast mt-2 min-h-6">
<Reactions {reactions} {object} on:click={handleClick} />
<Reactions {reactions} {object} {readonly} on:click={handleClick} />
</div>
{/if}
@@ -16,7 +16,7 @@
import type { Attachment } from '@hcengineering/attachment'
import { getResource } from '@hcengineering/platform'
import { PDFViewer, getFileUrl } from '@hcengineering/presentation'
import { ActionIcon, IconMoreH, IconOpen, Menu, closeTooltip, showPopup } from '@hcengineering/ui'
import { Action as UIAction, ActionIcon, IconMoreH, IconOpen, Menu, closeTooltip, showPopup } from '@hcengineering/ui'
import view, { Action } from '@hcengineering/view'
import attachmentPlugin from '../plugin'
@@ -24,6 +24,7 @@
export let attachment: Attachment
export let isSaved = false
export let removable = false
let download: HTMLAnchorElement
@@ -56,38 +57,40 @@
action: attachmentPlugin.actionImpl.AddAttachmentToSaved
} as unknown as Action)
const openAction: UIAction = {
label: view.string.Open,
icon: IconOpen,
action: async (props: any, evt: Event) => {
showPreview(evt as MouseEvent)
}
}
const showMenu = (ev: Event) => {
const actions: UIAction[] = []
if (openable) {
actions.push(openAction)
}
actions.push({
label: saveAttachmentAction.label,
icon: saveAttachmentAction.icon,
action: async (props: any, evt: Event) => {
const impl = await getResource(saveAttachmentAction.action)
await impl(attachment, evt)
}
})
if (removable) {
actions.push({
label: attachmentPlugin.string.DeleteFile,
action: async (props: any, evt: Event) => {
const impl = await getResource(attachmentPlugin.actionImpl.DeleteAttachment)
await impl(attachment, evt)
}
})
}
showPopup(
Menu,
{
actions: [
...(openable
? [
{
label: view.string.Open,
icon: IconOpen,
action: async (props: any, evt: MouseEvent) => {
showPreview(evt)
}
}
]
: []),
{
label: saveAttachmentAction.label,
icon: saveAttachmentAction.icon,
action: async (props: any, evt: MouseEvent) => {
const impl = await getResource(saveAttachmentAction.action)
await impl(attachment, evt)
}
},
{
label: attachmentPlugin.string.DeleteFile,
action: async (props: any, evt: MouseEvent) => {
const impl = await getResource(attachmentPlugin.actionImpl.DeleteAttachment)
await impl(attachment, evt)
}
}
]
actions
},
ev.target as HTMLElement
)
@@ -27,6 +27,7 @@
export let value: Attachment
export let isSaved: boolean = false
export let listProvider: ListSelectionProvider | undefined = undefined
export let removable: boolean = false
const dispatch = createEventDispatcher()
$: type = getType(value.type)
@@ -58,14 +59,14 @@
alt={value.name}
/>
<div class="actions conner">
<AttachmentActions attachment={value} {isSaved} />
<AttachmentActions attachment={value} {isSaved} {removable} />
</div>
</div>
{:else if type === 'audio'}
<div class="buttonContainer">
<AudioPlayer {value} />
<div class="actions conner" style:padding={'0.125rem 0.25rem'}>
<AttachmentActions attachment={value} {isSaved} />
<AttachmentActions attachment={value} {isSaved} {removable} />
</div>
</div>
{:else if type === 'video'}
@@ -78,14 +79,14 @@
</div>
</video>
<div class="actions conner">
<AttachmentActions attachment={value} {isSaved} />
<AttachmentActions attachment={value} {isSaved} {removable} />
</div>
</div>
{:else}
<div class="flex buttonContainer extraWidth">
<AttachmentPresenter {value} />
<div class="actions conner">
<AttachmentActions attachment={value} {isSaved} />
<AttachmentActions attachment={value} {isSaved} {removable} />
</div>
</div>
{/if}
@@ -34,6 +34,7 @@
export let enableAttachments: boolean = true
export let useAttachmentPreview: boolean = false
export let readonly: boolean = false
const client = getClient()
@@ -43,7 +44,7 @@
let extraActions: RefAction[] = []
let modelRefActions: RefAction[] = []
$: if (enableAttachments) {
$: if (enableAttachments && !readonly) {
extraActions = [
{
label: textEditor.string.Attach,
@@ -59,10 +60,12 @@
void getModelRefActions().then((actions) => {
modelRefActions = actions
})
$: refActions = defaultRefActions
.concat(extraActions)
.concat(modelRefActions)
.sort((a, b) => a.order - b.order)
$: refActions = readonly
? []
: defaultRefActions
.concat(extraActions)
.concat(modelRefActions)
.sort((a, b) => a.order - b.order)
let progress = false
let attachments: Attachment[] = []
@@ -89,6 +92,7 @@
}
async function fileSelected (): Promise<void> {
if (readonly) return
progress = true
const list = inputFile.files
if (list === null || list.length === 0) return
@@ -155,6 +159,7 @@
}
export async function pasteAction (evt: ClipboardEvent): Promise<void> {
if (readonly) return
if (!isAllowedPaste(evt)) {
return
}
@@ -172,6 +177,7 @@
}
export async function fileDrop (e: DragEvent): Promise<void> {
if (readonly) return
progress = true
const list = e.dataTransfer?.files
if (list !== undefined && list.length !== 0) {
@@ -231,6 +237,7 @@
{placeholder}
{boundary}
{refActions}
{readonly}
attachFile={async (file) => {
return await createAttachment(file)
}}
@@ -248,6 +255,7 @@
{#if (attachments.length > 0 && enableAttachments) || progress}
<AttachmentsGrid
{attachments}
{readonly}
{progress}
{progressItems}
{useAttachmentPreview}
@@ -24,6 +24,7 @@
export let useAttachmentPreview = false
export let progress = false
export let progressItems: Ref<Doc>[] = []
export let readonly: boolean = false
let element: HTMLElement
let attachmentPopupId: string = ''
@@ -46,11 +47,16 @@
<div class="attachment-grid">
{#each attachments as attachment (attachment._id)}
{#if useAttachmentPreview}
<AttachmentPreview value={attachment} {listProvider} on:open={(res) => (attachmentPopupId = res.detail)} />
<AttachmentPreview
value={attachment}
removable={!readonly}
{listProvider}
on:open={(res) => (attachmentPopupId = res.detail)}
/>
{:else}
<AttachmentPresenter
value={attachment}
removable
removable={!readonly}
showPreview
progress={progressItems.includes(attachment._id)}
on:remove
@@ -20,17 +20,8 @@
import { Panel } from '@hcengineering/panel'
import { createQuery, getClient } from '@hcengineering/presentation'
import { StyledTextBox } from '@hcengineering/text-editor'
import {
Button,
CircleButton,
EditBox,
IconAdd,
IconMoreH,
Label,
getEventPopupPositionElement,
showPopup
} from '@hcengineering/ui'
import { ContextMenu, DocAttributeBar, ParentsNavigator, invokeAction } from '@hcengineering/view-resources'
import { Button, EditBox, IconMoreH } from '@hcengineering/ui'
import { DocAttributeBar, ParentsNavigator, invokeAction, showMenu } from '@hcengineering/view-resources'
import { createEventDispatcher, onMount } from 'svelte'
import board from '../plugin'
import { getCardActions } from '../utils/CardActionUtils'
@@ -113,11 +104,7 @@
kind="ghost"
size="medium"
on:click={(e) => {
showPopup(
ContextMenu,
{ object, baseMenuClass: board.class.Card, mode: 'editor' },
getEventPopupPositionElement(e)
)
showMenu(e, { object, baseMenuClass: board.class.Card, mode: 'editor' })
}}
/>
</svelte:fragment>
@@ -22,19 +22,9 @@
import { ChatMessagesPresenter } from '@hcengineering/notification-resources'
import { getClient } from '@hcengineering/presentation'
import tags from '@hcengineering/tags'
import {
Button,
Component,
EditBox,
Icon,
IconMoreV,
Label,
getPopupPositionElement,
numberToHexColor,
showPopup
} from '@hcengineering/ui'
import { Button, Component, EditBox, Icon, IconMoreV, Label, numberToHexColor } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { ContextMenu } from '@hcengineering/view-resources'
import { showMenu } from '@hcengineering/view-resources'
import board from '../plugin'
import { hasDate, openCardPanel, updateCard, updateCardMembers } from '../utils/CardUtils'
import DatePresenter from './presenters/DatePresenter.svelte'
@@ -53,9 +43,9 @@
isEditMode = false
}
function enterEditMode (): void {
function enterEditMode (e: MouseEvent): void {
isEditMode = true
showPopup(ContextMenu, { object }, getPopupPositionElement(ref, { h: 'right', v: 'top' }), exitEditMode)
showMenu(e, { object }, exitEditMode)
}
function showCard () {
@@ -14,7 +14,7 @@
// limitations under the License.
-->
<script lang="ts">
import board, { Card } from '@hcengineering/board'
import { Card } from '@hcengineering/board'
import {
CategoryType,
Class,
@@ -30,10 +30,8 @@
import { ActionContext, createQuery } from '@hcengineering/presentation'
import type { DocWithRank, Project } from '@hcengineering/task'
import task, { getStates } from '@hcengineering/task'
import { getEventPositionElement, showPopup } from '@hcengineering/ui'
import { typeStore } from '@hcengineering/task-resources'
import {
ContextMenu,
ListSelectionProvider,
SelectDirection,
focusStore,
@@ -41,6 +39,7 @@
getGroupByValues,
groupBy,
setGroupByValues,
showMenu,
statusStore
} from '@hcengineering/view-resources'
import { onMount } from 'svelte'
@@ -77,15 +76,6 @@
const selection = listProvider.selection
const showMenu = async (ev: MouseEvent, object: Doc): Promise<void> => {
ev.preventDefault()
if (object._class !== board.class.Card) {
return
}
showPopup(ContextMenu, { object }, getEventPositionElement(ev))
}
let resultQuery: DocumentQuery<DocWithRank>
$: resultQuery = { ...query, isArchived: { $nin: [true] }, space }
@@ -144,7 +134,9 @@
on:check={(evt) => {
listProvider.updateSelection(evt.detail.docs, evt.detail.value)
}}
on:contextmenu={(evt) => showMenu(evt.detail.evt, evt.detail.objects)}
on:contextmenu={(evt) => {
showMenu(evt.detail.evt, { object: evt.detail.objects })
}}
selection={listProvider.current($focusStore)}
>
<svelte:fragment slot="card" let:object>
@@ -1,21 +1,8 @@
<script lang="ts">
import { State } from '@hcengineering/task'
import {
Button,
getEventPositionElement,
getPlatformColorDef,
getPlatformColorForTextDef,
IconMoreV,
showPopup,
themeStore
} from '@hcengineering/ui'
import { ContextMenu } from '@hcengineering/view-resources'
import { Button, getPlatformColorDef, getPlatformColorForTextDef, IconMoreV, themeStore } from '@hcengineering/ui'
import { showMenu } from '@hcengineering/view-resources'
export let state: State
const showMenu = async (ev: MouseEvent): Promise<void> => {
ev.preventDefault()
showPopup(ContextMenu, { object: state }, getEventPositionElement(ev))
}
</script>
{#if state}
@@ -29,7 +16,13 @@
<div class="flex-between h-full font-medium pr-2 pl-4">
<span class="lines-limit-2">{state.name}</span>
<div class="flex">
<Button icon={IconMoreV} kind="ghost" on:click={showMenu} />
<Button
icon={IconMoreV}
kind="ghost"
on:click={(e) => {
showMenu(e, { object: state })
}}
/>
</div>
</div>
</div>
@@ -37,7 +37,7 @@
ticker,
isWeekend
} from '@hcengineering/ui'
import { Menu } from '@hcengineering/view-resources'
import { Menu, showMenu } from '@hcengineering/view-resources'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
import type {
CalendarADGrid,
@@ -759,12 +759,6 @@
dispatch('dragEnter', { date: new Date(new Date(newTime).setMinutes(stickyMinutes, 0, 0)) })
}
}
function showMenu (ev: MouseEvent, event: Event) {
ev.preventDefault()
closeTooltip()
showPopup(Menu, { object: event }, getEventPositionElement(ev))
}
</script>
<Scroller
@@ -1011,7 +1005,7 @@
mouseDownElement(e, ev, 'top')
}}
on:contextmenu={(e) => {
showMenu(e, ev)
showMenu(e, { object: ev })
}}
/>
<div
@@ -1022,7 +1016,7 @@
mouseDownElement(e, ev, 'bottom')
}}
on:contextmenu={(e) => {
showMenu(e, ev)
showMenu(e, { object: ev })
}}
/>
<EventElement
@@ -16,16 +16,9 @@
import calendar, { CalendarEventPresenter, Event } from '@hcengineering/calendar'
import { Doc } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import {
Component,
MILLISECONDS_IN_MINUTE,
closeTooltip,
getEventPositionElement,
showPopup,
tooltip
} from '@hcengineering/ui'
import { Component, MILLISECONDS_IN_MINUTE, showPopup, tooltip } from '@hcengineering/ui'
import view, { ObjectEditor } from '@hcengineering/view'
import { Menu } from '@hcengineering/view-resources'
import { showMenu } from '@hcengineering/view-resources'
import { calendarStore, isVisible } from '../utils'
import EventPresenter from './EventPresenter.svelte'
@@ -55,13 +48,11 @@
let div: HTMLDivElement
function showMenu (ev: MouseEvent) {
ev.preventDefault()
closeTooltip()
showPopup(Menu, { object: event }, getEventPositionElement(ev))
}
$: visible = isVisible(event, $calendarStore)
function contextMenu (e: MouseEvent): void {
showMenu(e, { object: event })
}
</script>
{#if event}
@@ -74,7 +65,7 @@
class:empty
use:tooltip={{ component: EventPresenter, props: { value: event, hideDetails: !visible } }}
on:click|stopPropagation={click}
on:contextmenu={showMenu}
on:contextmenu={contextMenu}
>
{#if !empty && presenter?.presenter}
<Component is={presenter.presenter} props={{ event, narrow, oneRow, hideDetails: !visible }} />
@@ -18,6 +18,7 @@
import { Button, ButtonKind, ButtonSize, IconThread } from '@hcengineering/ui'
import ChatMessagePopup from './ChatMessagePopup.svelte'
import { restrictionStore } from '@hcengineering/view-resources'
export let value: number | undefined
export let object: Doc
@@ -26,6 +27,8 @@
export let showCounter: boolean = true
export let compactMode: boolean = false
export let withInput: boolean = true
$: disabled = $restrictionStore.disableComments
</script>
{#if value && value > 0}
@@ -34,7 +37,7 @@
{size}
showTooltip={{
component: ChatMessagePopup,
props: { objectId: object._id, object, withInput }
props: { objectId: object._id, object, withInput: withInput && !disabled }
}}
>
<div slot="icon">
@@ -35,11 +35,11 @@
import { openDoc } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import { PersonLabelTooltip, personByIdStore } from '..'
import { AssigneeCategory } from '../assignee'
import AssigneePopup from './AssigneePopup.svelte'
import EmployeePresenter from './EmployeePresenter.svelte'
import UserInfo from './UserInfo.svelte'
import IconPerson from './icons/Person.svelte'
import { AssigneeCategory } from '../assignee'
export let _class: Ref<Class<Employee>> = contact.mixin.Employee
export let excluded: Ref<Contact>[] | undefined = undefined
@@ -156,7 +156,16 @@
onEmployeeEdit={_click}
/>
{:else}
<Button {focusIndex} width={width ?? 'min-content'} {size} {kind} {justify} {showTooltip} on:click={_click}>
<Button
{focusIndex}
disabled={readonly}
width={width ?? 'min-content'}
{size}
{kind}
{justify}
{showTooltip}
on:click={_click}
>
<span
slot="content"
class="overflow-label flex-grow"
@@ -19,6 +19,7 @@
import { ButtonKind, ButtonSize, closeTooltip, showPopup } from '@hcengineering/ui'
import { Channel, ChannelProvider } from '@hcengineering/contact'
import { restrictionStore } from '@hcengineering/view-resources'
import contact from '../plugin'
import ChannelsDropdown from './ChannelsDropdown.svelte'
@@ -26,7 +27,7 @@
export let attachedClass: Ref<Class<Doc>>
export let integrations: Set<Ref<Doc>> | undefined = undefined
export let editable: boolean = true
export let allowOpen = true
export let allowOpen: boolean = !$restrictionStore.disableNavigation
export let focusIndex = -1
export let kind: ButtonKind = 'link-bordered'
@@ -25,6 +25,7 @@
import Company from './icons/Company.svelte'
export let object: Organization
export let readonly: boolean = false
const client = getClient()
@@ -58,6 +59,7 @@
<div class="flex-grow flex-col">
<div class="name">
<EditBox
disabled={readonly}
placeholder={contact.string.PersonFirstNamePlaceholder}
bind:value={object.name}
on:change={nameChange}
@@ -66,7 +68,14 @@
</div>
<div class="separator" />
<Scroller contentDirection={'horizontal'} padding={'.125rem .125rem .5rem'} stickedScrollBars thinScrollBars>
<ChannelsEditor attachedTo={object._id} attachedClass={object._class} {integrations} focusIndex={10} on:click />
<ChannelsEditor
editable={!readonly}
attachedTo={object._id}
attachedClass={object._class}
{integrations}
focusIndex={10}
on:click
/>
</Scroller>
</div>
</div>
@@ -14,17 +14,18 @@
// limitations under the License.
-->
<script lang="ts">
import { combineName, PersonAccount, getFirstName, getLastName, Person } from '@hcengineering/contact'
import { getCurrentAccount, Ref } from '@hcengineering/core'
import { Person, PersonAccount, combineName, getFirstName, getLastName } from '@hcengineering/contact'
import { Ref, getCurrentAccount } from '@hcengineering/core'
import { AttributeEditor, createQuery, getClient } from '@hcengineering/presentation'
import setting, { IntegrationType } from '@hcengineering/setting'
import { createFocusManager, EditBox, FocusHandler, Scroller } from '@hcengineering/ui'
import { EditBox, FocusHandler, Scroller, createFocusManager } from '@hcengineering/ui'
import { createEventDispatcher, onMount } from 'svelte'
import contact from '../plugin'
import ChannelsEditor from './ChannelsEditor.svelte'
import EditableAvatar from './EditableAvatar.svelte'
export let object: Person
export let readonly: boolean = false
const client = getClient()
const account = getCurrentAccount() as PersonAccount
@@ -84,6 +85,7 @@
<div class="flex-no-shrink mr-8">
{#key object}
<EditableAvatar
disabled={readonly}
avatar={object.avatar}
size={'x-large'}
name={object.name}
@@ -95,6 +97,7 @@
<div class="flex-grow flex-col">
<div class="name">
<EditBox
disabled={readonly}
placeholder={contact.string.PersonFirstNamePlaceholder}
bind:value={firstName}
on:change={firstNameChange}
@@ -103,6 +106,7 @@
</div>
<div class="name">
<EditBox
disabled={readonly}
placeholder={contact.string.PersonLastNamePlaceholder}
bind:value={lastName}
on:change={lastNameChange}
@@ -110,7 +114,14 @@
/>
</div>
<div class="location">
<AttributeEditor maxWidth="20rem" _class={contact.class.Person} {object} key="city" focusIndex={3} />
<AttributeEditor
maxWidth="20rem"
_class={contact.class.Person}
{object}
editable={!readonly}
key="city"
focusIndex={3}
/>
</div>
<div class="separator" />
@@ -124,6 +135,7 @@
<ChannelsEditor
attachedTo={object._id}
attachedClass={object._class}
editable={!readonly}
bind:integrations
shape={'circle'}
focusIndex={10}
@@ -26,6 +26,7 @@
export let objectId: Ref<Doc>
export let space: Ref<Space>
export let _class: Ref<Class<Doc>>
export let readonly: boolean = false
export let members: number
@@ -40,6 +41,7 @@
let loading = true
const createApp = async (ev: MouseEvent): Promise<void> => {
if (readonly) return
showPopup(
UsersPopup,
{
@@ -77,7 +79,9 @@
viewletQuery={{ _id: contact.viewlet.TableMember }}
/>
<ViewletSettingButton kind={'ghost'} bind:viewlet />
<Button id={contact.string.AddMember} icon={IconAdd} kind={'ghost'} on:click={createApp} />
{#if !readonly}
<Button id={contact.string.AddMember} icon={IconAdd} kind={'ghost'} on:click={createApp} />
{/if}
</div>
</svelte:fragment>
@@ -89,6 +93,7 @@
options={viewlet.options}
query={{ attachedTo: objectId }}
loadingProps={{ length: members }}
{readonly}
/>
{:else}
<div class="antiSection-empty solid flex-col mt-3">
@@ -97,9 +102,11 @@
</span>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<span class="over-underline content-color" on:click={createApp}>
<Label label={contact.string.AddMember} />
</span>
{#if !readonly}
<span class="over-underline content-color" on:click={createApp}>
<Label label={contact.string.AddMember} />
</span>
{/if}
</div>
{/if}
</svelte:fragment>
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/assets/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="link" viewBox="0 0 32 32">
<path d="M29.25 6.75997C28.6926 6.20061 28.0302 5.75679 27.3009 5.45396C26.5716 5.15112 25.7897 4.99524 25 4.99524C24.2103 4.99524 23.4284 5.15112 22.6991 5.45396C22.278 5.62881 21.8792 5.85065 21.5101 6.1146C21.0614 6.43545 21.0693 7.07931 21.4594 7.46935C21.8499 7.85988 22.4827 7.84994 22.9575 7.5679C23.1218 7.47029 23.2933 7.38434 23.4707 7.31086C23.9571 7.10938 24.4785 7.00567 25.005 7.00567C25.5315 7.00567 26.0528 7.10938 26.5393 7.31086C27.0257 7.51235 27.4677 7.80767 27.84 8.17997C28.2123 8.55227 28.5076 8.99425 28.7091 9.48068C28.9106 9.96711 29.0143 10.4885 29.0143 11.015C29.0143 11.5415 28.9106 12.0628 28.7091 12.5493C28.5076 13.0357 28.2123 13.4777 27.84 13.85L19.84 21.85C19.0894 22.6019 18.0709 23.0248 17.0085 23.0257C15.9461 23.0267 14.9269 22.6055 14.175 21.855C13.4231 21.1044 13.0001 20.0859 12.9992 19.0235C12.9983 17.9611 13.4194 16.9419 14.17 16.19L14.8803 15.4746C15.2675 15.0846 15.2659 14.4537 14.8787 14.0637C14.4886 13.6709 13.8519 13.6681 13.4604 14.0596L12.75 14.77C12.1906 15.3274 11.7468 15.9897 11.444 16.7191C11.1411 17.4484 10.9852 18.2303 10.9852 19.02C10.9852 19.8097 11.1411 20.5916 11.444 21.3209C11.7468 22.0502 12.1906 22.7125 12.75 23.27C13.8815 24.387 15.41 25.0092 17 25C17.7927 25.0032 18.5782 24.8494 19.3111 24.5473C20.044 24.2452 20.7098 23.8009 21.27 23.24L29.27 15.24C30.3909 14.1123 31.0184 12.5858 31.0147 10.9958C31.0109 9.40582 30.3762 7.88232 29.25 6.75997Z" />
<path d="M4.18997 24.82C3.81656 24.4483 3.52026 24.0065 3.31807 23.52C3.11589 23.0335 3.01181 22.5118 3.01181 21.985C3.01181 21.4581 3.11589 20.9365 3.31807 20.4499C3.52026 19.9634 3.81656 19.5216 4.18997 19.15L12.19 11.15C12.5616 10.7766 13.0034 10.4803 13.4899 10.2781C13.9765 10.0759 14.4981 9.97181 15.025 9.97181C15.5518 9.97181 16.0735 10.0759 16.56 10.2781C17.0465 10.4803 17.4883 10.7766 17.86 11.15C18.231 11.5246 18.5231 11.9698 18.7189 12.4594C18.9147 12.9489 19.0103 13.4728 19 14C19.003 14.5288 18.9012 15.0529 18.7004 15.5421C18.4995 16.0313 18.2037 16.4758 17.83 16.85L16.4072 18.2929C16.0213 18.6842 16.0289 19.3189 16.4175 19.7075C16.808 20.098 17.4466 20.1034 17.8371 19.7129L19.25 18.3C20.3785 17.1715 21.0124 15.6409 21.0124 14.045C21.0124 12.449 20.3785 10.9185 19.25 9.78997C18.1215 8.66147 16.5909 8.02749 14.995 8.02749C13.399 8.02749 11.8685 8.66147 10.74 9.78997L2.73997 17.79C2.17911 18.3476 1.73401 19.0106 1.43029 19.7408C1.12657 20.471 0.970215 21.2541 0.970215 22.045C0.970215 22.8358 1.12657 23.6189 1.43029 24.3492C1.73401 25.0794 2.17911 25.7424 2.73997 26.3C3.87879 27.4084 5.41087 28.0198 6.99997 28C8.26594 28.0012 9.49169 27.6069 10.5118 26.885C10.964 26.5651 10.961 25.921 10.5693 25.5293C10.1781 25.1381 9.54699 25.1518 9.0717 25.4348C8.90786 25.5324 8.73689 25.6184 8.56 25.6919C8.07349 25.8941 7.55182 25.9981 7.02497 25.9981C6.49812 25.9981 5.97645 25.8941 5.48994 25.6919C5.00342 25.4897 4.56164 25.1934 4.18997 24.82Z" />
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "assets"
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
+9
View File
@@ -0,0 +1,9 @@
{
"string": {
"CreatePublicLink": "Create public link",
"PublicLink": "Public link",
"Revoke": "Revoke",
"RevokeConfirmation": "This will cause previously shared link to stop working. Are you sure you want to revoke this public link?",
"LinkWasRevoked": "Public link was revoked"
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"string": {
"CreatePublicLink": "Создать публичную ссылку",
"PublicLink": "Публичная ссылка",
"Revoke": "Отозвать",
"RevokeConfirmation": "Это приведет к тому, что ранее созданная ссылка перестанет работать. Вы уверены, что хотите отозвать эту публичную ссылку?",
"LinkWasRevoked": "Ссылка была отозвана"
}
}
+44
View File
@@ -0,0 +1,44 @@
{
"name": "@hcengineering/guest-assets",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"template": "@hcengineering/assets-package",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"test": "jest --passWithNoTests --silent",
"build:docs": "",
"format": "format src",
"build:watch": "compile",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.4.0",
"eslint-plugin-promise": "^6.1.1",
"eslint": "^8.54.0",
"prettier": "^3.1.0",
"@types/node": "~20.11.16",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"prettier-plugin-svelte": "^3.1.0",
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/platform": "^0.6.9",
"@hcengineering/guest": "^0.6.0"
},
"repository": "https://github.com/hcenginneing/anticrm",
"publishConfig": {
"registry": "https://npm.pkg.github.com"
}
}
@@ -0,0 +1,6 @@
import { makeLocalesTest } from '@hcengineering/platform'
it(
'Locales are equale',
makeLocalesTest((lang) => import(`../../lang/${lang}.json`))
)
+23
View File
@@ -0,0 +1,23 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { addStringsLoader, loadMetadata } from '@hcengineering/platform'
import guest, { guestId } from '@hcengineering/guest'
const icons = require('../assets/icons.svg') as string // eslint-disable-line
loadMetadata(guest.icon, {
Link: `${icons}#link`
})
addStringsLoader(guestId, async (lang: string) => await import(`../lang/${lang}.json`))
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/assets/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"types": ["node", "jest"],
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/eslint.config.json'],
parserOptions: { tsconfigRootDir: __dirname }
}
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "ui"
}
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)']
}
+54
View File
@@ -0,0 +1,54 @@
{
"name": "@hcengineering/guest-resources",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "compile ui",
"build:docs": "api-extractor run --local",
"format": "format src",
"build:watch": "compile ui",
"_phase:build": "compile ui",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"svelte-loader": "^3.1.9",
"sass": "^1.53.0",
"svelte-preprocess": "^5.1.0",
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.4.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-svelte": "^2.34.0",
"prettier-plugin-svelte": "^3.1.0",
"eslint": "^8.54.0",
"prettier": "^3.1.0",
"svelte-check": "^3.6.0",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"svelte-eslint-parser": "^0.33.1"
},
"dependencies": {
"svelte": "^4.2.5",
"@hcengineering/platform": "^0.6.9",
"@hcengineering/core": "^0.6.28",
"@hcengineering/client": "^0.6.14",
"@hcengineering/workbench": "^0.6.9",
"@hcengineering/workbench-resources": "^0.6.1",
"@hcengineering/ui": "^0.6.11",
"@hcengineering/view": "^0.6.9",
"@hcengineering/presentation": "^0.6.2",
"@hcengineering/guest": "^0.6.0",
"@hcengineering/login": "^0.6.8",
"@hcengineering/view-resources": "^0.6.0",
"@hcengineering/analytics": "^0.6.0",
"fast-copy": "~3.0.1"
}
}
@@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}
@@ -0,0 +1,119 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Doc } from '@hcengineering/core'
import presentaion, {
Card,
MessageBox,
copyTextToClipboard,
createQuery,
getClient
} from '@hcengineering/presentation'
import guest from '../plugin'
import { Button, Loading, Location, showPopup } from '@hcengineering/ui'
import { getObjectLinkFragment } from '@hcengineering/view-resources'
import view from '@hcengineering/view'
import { PublicLink } from '@hcengineering/guest'
import { createEventDispatcher } from 'svelte'
export let value: Doc
const client = getClient()
const query = createQuery()
let loading = true
let link: PublicLink | undefined
const dispatch = createEventDispatcher()
async function createLink (location: Location, revokable: boolean = true): Promise<void> {
await client.createDoc(guest.class.PublicLink, guest.space.Links, {
attachedTo: value._id,
location,
revokable,
restrictions: {
readonly: true,
disableNavigation: true,
disableActions: true,
disableComments: true
},
url: ''
})
}
async function generate (object: Doc): Promise<void> {
const panelComponent = client.getHierarchy().classHierarchyMixin(object._class, view.mixin.ObjectPanel)
const comp = panelComponent?.component ?? view.component.EditDoc
const loc = await getObjectLinkFragment(client.getHierarchy(), object, {}, comp)
await createLink(loc)
}
async function checkNeedGenerate (value: Doc, loading: boolean, link: PublicLink | undefined): Promise<void> {
if (!loading || link !== undefined) return
await generate(value)
}
query.query(
guest.class.PublicLink,
{ attachedTo: value._id },
(res) => {
link = res[0]
void checkNeedGenerate(value, loading, link)
loading = false
},
{ limit: 1 }
)
function close () {
dispatch('close')
}
function copy (): void {
if (link?.url === undefined || link.url === '') return
copyTextToClipboard(link.url)
}
async function revoke (): Promise<void> {
if (!revokable || link === undefined) return
showPopup(
MessageBox,
{
label: guest.string.Revoke,
message: guest.string.RevokeConfirmation
},
'top',
(res) => {
if (res === true && link !== undefined) {
client.remove(link)
}
dispatch('close')
}
)
}
$: revokable = link?.revokable ?? false
</script>
<Card label={guest.string.PublicLink} canSave={true} okLabel={presentaion.string.Close} on:close okAction={close}>
{#if link?.url === undefined || link.url === ''}
<Loading />
{:else}
<div class="over-underline link w-full overflow-label" on:click={copy}>{link.url}</div>
{/if}
<svelte:fragment slot="buttons">
{#if revokable}
<Button label={guest.string.Revoke} kind={'dangerous'} size={'large'} on:click={revoke} />
{/if}
</svelte:fragment>
</Card>
@@ -0,0 +1,337 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Analytics } from '@hcengineering/analytics'
import core, { Class, Doc, Ref, Space } from '@hcengineering/core'
import { getMetadata, getResource } from '@hcengineering/platform'
import { ActionContext, getClient } from '@hcengineering/presentation'
import {
AnyComponent,
Component,
Label,
Location,
PanelInstance,
Popup,
PopupAlignment,
ResolvedLocation,
Separator,
TooltipInstance,
areLocationsEqual,
closePanel,
getCurrentLocation,
getLocation,
navigate,
openPanel,
setResolvedLocation
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import { ListSelectionProvider, restrictionStore, updateFocus } from '@hcengineering/view-resources'
import workbench, { Application, NavigatorModel, SpecialNavModel, ViewConfiguration } from '@hcengineering/workbench'
import { buildNavModel, SpaceView } from '@hcengineering/workbench-resources'
import guest from '../plugin'
import { decodeTokenPayload } from '../utils'
const excludedApps = getMetadata(workbench.metadata.ExcludedApplications) ?? []
const client = getClient()
async function load (): Promise<boolean> {
const loc = getCurrentLocation()
const token = loc.query?.token
if (token == null) return false
const decoded = decodeTokenPayload(token)
const link = await client.findOne(guest.class.PublicLink, { _id: decoded.linkId })
if (link == null) return false
restrictionStore.set(link.restrictions)
await doSyncLoc(link.location)
return true
}
let contentPanel: HTMLElement
let panelInstance: PanelInstance
let popupInstance: Popup
const apps: Application[] = client
.getModel()
.findAllSync<Application>(workbench.class.Application, { hidden: false, _id: { $nin: excludedApps } })
async function resolveShortLink (loc: Location): Promise<ResolvedLocation | undefined> {
if (loc.path[2] !== undefined && loc.path[2].trim().length > 0) {
const app = apps.find((p) => p.alias === loc.path[2])
if (app?.locationResolver) {
const resolver = await getResource(app.locationResolver)
return await resolver(loc)
}
}
}
function mergeLoc (loc: Location, resolved: ResolvedLocation): Location {
const resolvedApp = resolved.loc.path[2]
const resolvedSpace = resolved.loc.path[3]
const resolvedSpecial = resolved.loc.path[4]
if (resolvedApp === undefined) {
loc.path = [loc.path[0], loc.path[1], ...resolved.defaultLocation.path.splice(2)]
} else {
loc.path[2] = resolvedApp
if (resolvedSpace === undefined) {
loc.path[3] = currentSpace ?? (currentSpecial as string) ?? resolved.defaultLocation.path[3]
loc.path[4] = (currentSpecial as string) ?? resolved.defaultLocation.path[4]
} else {
loc.path[3] = resolvedSpace
loc.path[4] = resolvedSpecial ?? currentSpecial ?? resolved.defaultLocation.path[4]
}
}
for (let index = 0; index < loc.path.length; index++) {
const path = loc.path[index]
if (path === undefined) {
loc.path.length = index
break
}
}
loc.fragment = resolved.loc.fragment ?? loc.fragment ?? resolved.defaultLocation.fragment
return loc
}
let currentSpace: Ref<Space> | undefined
let currentSpecial: string | undefined
let specialComponent: SpecialNavModel | undefined
let asideId: string | undefined
let currentFragment: string | undefined = ''
let currentApplication: Application | undefined
let navigatorModel: NavigatorModel | undefined
let currentView: ViewConfiguration | undefined
function setSpaceSpecial (spaceSpecial: string | undefined): void {
if (currentSpecial !== undefined && spaceSpecial === currentSpecial) return
if (asideId !== undefined && spaceSpecial === asideId) return
if (spaceSpecial === undefined) return
specialComponent = getSpecialComponent(spaceSpecial)
if (specialComponent !== undefined) {
currentSpecial = spaceSpecial
} else if (navigatorModel?.aside !== undefined || currentApplication?.aside !== undefined) {
asideId = spaceSpecial
}
}
function getSpecialComponent (id: string): SpecialNavModel | undefined {
const sp = navigatorModel?.specials?.find((x) => x.id === id)
if (sp !== undefined) {
return sp
}
for (const s of navigatorModel?.spaces ?? []) {
const sp = s.specials?.find((x) => x.id === id)
if (sp !== undefined) {
return sp
}
}
}
async function syncLoc (loc: Location): Promise<void> {
if (loc.path.length > 3 && getSpecialComponent(loc.path[3]) === undefined) {
// resolve short links
const resolvedLoc = await resolveShortLink(loc)
if (resolvedLoc !== undefined && !areLocationsEqual(loc, resolvedLoc.loc)) {
loc = mergeLoc(loc, resolvedLoc)
}
}
setResolvedLocation(loc)
const app = loc.path[2]
const space = loc.path[3] as Ref<Space>
const special = loc.path[4]
const fragment = loc.fragment
currentApplication = await client.findOne<Application>(workbench.class.Application, { alias: app })
navigatorModel = await buildNavModel(client, currentApplication)
if (currentSpecial === undefined || currentSpecial !== space) {
const newSpecial = space !== undefined ? getSpecialComponent(space) : undefined
if (newSpecial !== undefined) {
specialComponent = newSpecial
currentSpecial = space
} else {
await updateSpace(space)
setSpaceSpecial(special)
}
}
if (special !== currentSpecial && (navigatorModel?.aside || currentApplication?.aside)) {
asideId = special
}
if (fragment !== currentFragment) {
currentFragment = fragment
if (fragment !== undefined && fragment.trim().length > 0) {
await setOpenPanelFocus(fragment)
} else {
closePanel()
}
}
}
async function setOpenPanelFocus (fragment: string): Promise<void> {
const props = decodeURIComponent(fragment).split('|')
if (props.length >= 3) {
const doc = await client.findOne<Doc>(props[2] as Ref<Class<Doc>>, { _id: props[1] as Ref<Doc> })
if (doc !== undefined) {
const provider = ListSelectionProvider.Find(doc._id)
updateFocus({
provider,
focus: doc
})
openPanel(
props[0] as AnyComponent,
props[1],
props[2],
(props[3] ?? undefined) as PopupAlignment,
(props[4] ?? undefined) as AnyComponent
)
} else {
closePanel(false)
}
} else {
closePanel(false)
}
}
async function doSyncLoc (loc: Location): Promise<void> {
await syncLoc(loc)
await updateWindowTitle(loc)
}
async function updateSpace (spaceId?: Ref<Space>): Promise<void> {
if (spaceId === currentSpace) return
if (spaceId === undefined) return
const space = await client.findOne<Space>(core.class.Space, { _id: spaceId })
if (space === undefined) return
currentSpace = spaceId
const spaceClass = client.getHierarchy().getClass(space._class)
const view = client.getHierarchy().as(spaceClass, workbench.mixin.SpaceView)
currentView = view.view
}
async function updateWindowTitle (loc: Location): Promise<void> {
const ws = loc.path[1]
const docTitle = await getWindowTitle(loc)
if (docTitle !== undefined && docTitle !== '') {
document.title = ws == null ? docTitle : `${docTitle} - ${ws}`
} else {
const title = getMetadata(workbench.metadata.PlatformTitle) ?? 'Platform'
document.title = ws == null ? title : `${ws} - ${title}`
}
}
function closeAside (): void {
const loc = getLocation()
loc.path.length = 4
asideId = undefined
navigate(loc)
}
async function getWindowTitle (loc: Location): Promise<string | undefined> {
if (loc.fragment == null) return
const hierarchy = client.getHierarchy()
const [, _id, _class] = decodeURIComponent(loc.fragment).split('|')
if (_class == null) return
const mixin = hierarchy.classHierarchyMixin(_class as Ref<Class<Doc>>, view.mixin.ObjectTitle)
if (mixin === undefined) return
const titleProvider = await getResource(mixin.titleProvider)
try {
return await titleProvider(client, _id as Ref<Doc>)
} catch (err: any) {
Analytics.handleError(err)
console.error(err)
}
}
let aside: HTMLElement
let cover: HTMLElement
</script>
{#await load() then res}
{#if res}
<div class="workbench-container" style:flex-direction={'row'}>
<div class="workbench-container inner">
<div class="antiPanel-component antiComponent" bind:this={contentPanel}>
{#if currentApplication && currentApplication.component}
<Component is={currentApplication.component} props={{ currentSpace, visibleNav: false }} />
{:else if specialComponent}
<Component
is={specialComponent.component}
props={{
model: navigatorModel,
...specialComponent.componentProps,
currentSpace,
visibleNav: false
}}
/>
{:else if currentView?.component !== undefined}
<Component
is={currentView.component}
props={{ ...currentView.componentProps, currentView, visibleNav: false }}
/>
{:else}
<SpaceView {currentSpace} {currentView} />
{/if}
</div>
{#if asideId}
{@const asideComponent = navigatorModel?.aside ?? currentApplication?.aside}
{#if asideComponent !== undefined}
<Separator name={'workbench'} index={1} />
<div class="antiPanel-component antiComponent aside" bind:this={aside}>
<Component is={asideComponent} props={{ currentSpace, _id: asideId }} on:close={closeAside} />
</div>
{/if}
{/if}
</div>
</div>
<div bind:this={cover} class="cover" />
<TooltipInstance />
<PanelInstance bind:this={panelInstance} {contentPanel} readonly={$restrictionStore.readonly} embedded>
<svelte:fragment slot="panel-header">
<ActionContext context={{ mode: 'panel' }} />
</svelte:fragment>
</PanelInstance>
<Popup bind:this={popupInstance} {contentPanel}>
<svelte:fragment slot="popup-header">
<ActionContext context={{ mode: 'popup' }} />
</svelte:fragment>
</Popup>
{:else}
<div class="version-wrapper">
<div class="antiPopup version-popup">
<h1><Label label={guest.string.LinkWasRevoked} /></h1>
</div>
</div>
{/if}
{/await}
<style lang="scss">
.version-wrapper {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.version-popup {
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
</style>
@@ -0,0 +1,59 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { getMetadata } from '@hcengineering/platform'
import { Label, Loading, Notifications, location } from '@hcengineering/ui'
import { connect, versionError } from '../connect'
import { guestId } from '@hcengineering/guest'
import workbench from '@hcengineering/workbench'
import Guest from './Guest.svelte'
</script>
{#if $location.path[0] === guestId}
{#await connect(getMetadata(workbench.metadata.PlatformTitle) ?? 'Platform')}
<Loading />
{:then client}
{#if !client && versionError}
<div class="version-wrapper">
<div class="antiPopup version-popup">
<h1><Label label={workbench.string.ServerUnderMaintenance} /></h1>
{versionError}
</div>
</div>
{:else if client}
<Notifications>
<Guest />
</Notifications>
{/if}
{:catch error}
<div>{error} -- {error.stack}</div>
{/await}
{/if}
<style lang="scss">
.version-wrapper {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.version-popup {
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
</style>
+188
View File
@@ -0,0 +1,188 @@
import { Analytics } from '@hcengineering/analytics'
import client from '@hcengineering/client'
import core, {
ClientConnectEvent,
versionToString,
type AccountClient,
type Client,
type Version,
setCurrentAccount
} from '@hcengineering/core'
import login, { loginId } from '@hcengineering/login'
import { getMetadata, getResource, setMetadata } from '@hcengineering/platform'
import presentation, { closeClient, refreshClient, setClient } from '@hcengineering/presentation'
import { fetchMetadataLocalStorage, getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
export let versionError: string | undefined = ''
let _token: string | undefined
let _client: AccountClient | undefined
let _clientSet: boolean = false
export async function connect (title: string): Promise<Client | undefined> {
const loc = getCurrentLocation()
const token = loc.query?.token
const ws = loc.path[1]
if (ws === undefined || token == null) {
navigate({
path: [loginId]
})
return
}
setMetadata(presentation.metadata.Token, token)
document.cookie =
encodeURIComponent(presentation.metadata.Token.replaceAll(':', '-')) + '=' + encodeURIComponent(token) + '; path=/'
const getEndpoint = await getResource(login.function.GetEndpoint)
const endpoint = await getEndpoint()
if (endpoint == null) {
navigate({
path: [loginId]
})
return
}
if (_token !== token && _client !== undefined) {
await _client.close()
_client = undefined
}
if (_client !== undefined) {
return _client
}
_token = token
let version: Version | undefined
let serverEndpoint = endpoint.replace(/^ws/g, 'http')
if (serverEndpoint.endsWith('/')) {
serverEndpoint = serverEndpoint.substring(0, serverEndpoint.length - 1)
}
const clientFactory = await getResource(client.function.GetClient)
_client = await clientFactory(
token,
endpoint,
() => {
location.reload()
},
() => {
clearMetadata(ws)
navigate({
path: [loginId],
query: {}
})
},
// We need to refresh all active live queries and clear old queries.
(event: ClientConnectEvent) => {
console.log('WorkbenchClient: onConnect', event)
try {
if ((_clientSet && event === ClientConnectEvent.Connected) || event === ClientConnectEvent.Refresh) {
void refreshClient()
}
if (event === ClientConnectEvent.Upgraded) {
window.location.reload()
}
void (async () => {
if (_client !== undefined) {
const newVersion = await _client.findOne<Version>(core.class.Version, {})
console.log('Reconnect Model version', newVersion)
const currentVersionStr = versionToString(version as Version)
const reconnectVersionStr = versionToString(newVersion as Version)
if (currentVersionStr !== reconnectVersionStr) {
// It seems upgrade happened
// location.reload()
versionError = `${currentVersionStr} != ${reconnectVersionStr}`
}
const serverVersion: { version: string } = await (
await fetch(serverEndpoint + '/api/v1/version', {})
).json()
console.log('Server version', serverVersion.version)
if (serverVersion.version !== '' && serverVersion.version !== currentVersionStr) {
versionError = `${currentVersionStr} => ${serverVersion.version}`
}
}
})()
} catch (err) {
console.error(err)
}
}
)
console.log('logging in as guest')
Analytics.handleEvent('GUEST LOGIN')
Analytics.setTag('workspace', ws)
const me = await _client?.getAccount()
if (me !== undefined) {
Analytics.setUser(me.email)
Analytics.setTag('workspace', ws)
console.log('login: employee account', me)
setCurrentAccount(me)
}
try {
version = await _client.findOne<Version>(core.class.Version, {})
console.log('Model version', version)
const requiredVersion = getMetadata(presentation.metadata.RequiredVersion)
if (requiredVersion !== undefined && version !== undefined) {
console.log('checking min model version', requiredVersion)
const versionStr = versionToString(version)
if (version === undefined || requiredVersion !== versionStr) {
versionError = `${versionStr} => ${requiredVersion}`
return undefined
}
}
try {
const serverVersion: { version: string } = await (await fetch(serverEndpoint + '/api/v1/version', {})).json()
console.log('Server version', serverVersion.version)
if (
serverVersion.version !== '' &&
(version === undefined || serverVersion.version !== versionToString(version))
) {
const versionStr = version !== undefined ? versionToString(version) : 'unknown'
versionError = `${versionStr} => ${serverVersion.version}`
return
}
} catch (err: any) {
versionError = 'server version not available'
return
}
} catch (err: any) {
console.log(err)
const requirdVersion = getMetadata(presentation.metadata.RequiredVersion)
console.log('checking min model version', requirdVersion)
if (requirdVersion !== undefined) {
versionError = `'unknown' => ${requirdVersion}`
return undefined
}
}
// Update window title
document.title = [ws, title].filter((it) => it).join(' - ')
_clientSet = true
await setClient(_client)
return _client
}
function clearMetadata (ws: string): void {
const tokens = fetchMetadataLocalStorage(login.metadata.LoginTokens)
if (tokens !== null) {
const loc = getCurrentLocation()
// eslint-disable-next-line
delete tokens[loc.path[1]]
setMetadataLocalStorage(login.metadata.LoginTokens, tokens)
}
setMetadata(presentation.metadata.Token, null)
setMetadataLocalStorage(login.metadata.LastToken, null)
document.cookie =
encodeURIComponent(presentation.metadata.Token.replaceAll(':', '-')) + '=' + encodeURIComponent('') + '; path=/'
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
setMetadataLocalStorage(login.metadata.LoginEmail, null)
void closeClient()
}
+10
View File
@@ -0,0 +1,10 @@
import { type Resources } from '@hcengineering/platform'
import CreatePublicLink from './components/CreatePublicLink.svelte'
import GuestApp from './components/GuestApp.svelte'
export default async (): Promise<Resources> => ({
component: {
GuestApp,
CreatePublicLink
}
})
+12
View File
@@ -0,0 +1,12 @@
import guest, { guestId } from '@hcengineering/guest'
import { type IntlString, mergeIds } from '@hcengineering/platform'
export default mergeIds(guestId, guest, {
string: {
CreatePublicLink: '' as IntlString,
PublicLink: '' as IntlString,
Revoke: '' as IntlString,
RevokeConfirmation: '' as IntlString,
LinkWasRevoked: '' as IntlString
}
})
+11
View File
@@ -0,0 +1,11 @@
export function decodeTokenPayload (token: string): any {
const parts = token.split('.')
const payload = parts[1]
const decodedPayload = atob(payload)
const parsedPayload = JSON.parse(decodedPayload)
return parsedPayload
}
+5
View File
@@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/ui/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib"
}
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
+44
View File
@@ -0,0 +1,44 @@
{
"name": "@hcengineering/guest",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"test": "jest --passWithNoTests --silent",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"prettier-plugin-svelte": "^3.1.0"
},
"dependencies": {
"@hcengineering/core": "^0.6.28",
"@hcengineering/platform": "^0.6.9",
"@hcengineering/ui": "^0.6.11"
},
"repository": "https://github.com/hcenginneing/anticrm",
"publishConfig": {
"registry": "https://npm.pkg.github.com"
}
}
+37
View File
@@ -0,0 +1,37 @@
import { Class, Doc, Ref, Space } from '@hcengineering/core'
import type { Asset, Plugin } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import { AnyComponent, Location } from '@hcengineering/ui'
export interface PublicLink extends Doc {
attachedTo: Ref<Doc>
url: string
location: Location
restrictions: Restrictions
revokable: boolean
}
export interface Restrictions {
readonly: boolean
disableComments: boolean
disableNavigation: boolean
disableActions: boolean
}
export const guestAccountEmail = '#guest@hc.engineering'
export const guestId = 'guest' as Plugin
export default plugin(guestId, {
class: {
PublicLink: '' as Ref<Class<PublicLink>>
},
icon: {
Link: '' as Asset
},
space: {
Links: '' as Ref<Space>
},
component: {
GuestApp: '' as AnyComponent
}
})
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
@@ -27,7 +27,7 @@
getEventPositionElement,
showPopup
} from '@hcengineering/ui'
import { Menu, openDoc } from '@hcengineering/view-resources'
import { Menu, openDoc, showMenu } from '@hcengineering/view-resources'
import hr from '../plugin'
import { addMember } from '../utils'
import CreateDepartment from './CreateDepartment.svelte'
@@ -76,10 +76,6 @@
showPopup(CreateDepartment, { space: value._id }, eventToHTMLElement(e))
}
function showMenu (e: MouseEvent) {
showPopup(Menu, { object: value, baseMenuClass: value._class }, getEventPositionElement(e))
}
function edit (e: MouseEvent): void {
openDoc(client.getHierarchy(), value)
}
@@ -92,6 +88,10 @@
$: values = allEmployees.filter((it) => it.department === value._id && it._id !== dragPersonId)
$: dragging = value._id === dragOver?._id && dragPersonId !== undefined
function onContext (e: MouseEvent): void {
showMenu(e, { object: value, baseMenuClass: value._class })
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
@@ -99,7 +99,7 @@
class="w-full mt-2 mb-4 container flex clear-mins flex-no-shrink"
class:cursor-pointer={currentDescendants.length}
on:click|stopPropagation={edit}
on:contextmenu|preventDefault={showMenu}
on:contextmenu={onContext}
class:dragging
>
<div
@@ -50,7 +50,7 @@
}
</script>
{#if object && type && type.label}
{#if object && type?.label}
<Card
label={hr.string.EditRequestType}
labelProps={{ type: type.label }}
@@ -18,8 +18,8 @@
import { EmployeePresenter } from '@hcengineering/contact-resources'
import { WithLookup } from '@hcengineering/core'
import { Staff } from '@hcengineering/hr'
import { closeTooltip, getEventPositionElement, showPopup } from '@hcengineering/ui'
import { ContextMenu } from '@hcengineering/view-resources'
import { closeTooltip } from '@hcengineering/ui'
import { showMenu } from '@hcengineering/view-resources'
import hr from '../plugin'
import { flip } from 'svelte/animate'
@@ -34,10 +34,6 @@
function ondrag (p: Employee): void {
dragPerson = p as WithLookup<Staff>
}
function showContextMenu (ev: MouseEvent, object: Employee) {
showPopup(ContextMenu, { object }, getEventPositionElement(ev))
}
</script>
{#if value}
@@ -54,8 +50,8 @@
dragPerson = undefined
closeTooltip()
}}
on:contextmenu|stopPropagation|preventDefault={(evt) => {
showContextMenu(evt, p)
on:contextmenu={(evt) => {
showMenu(evt, { object: p })
}}
>
<EmployeePresenter
@@ -15,8 +15,8 @@
<script lang="ts">
import hr, { Request, RequestType } from '@hcengineering/hr'
import { getClient } from '@hcengineering/presentation'
import { closeTooltip, Icon, Label, showPopup } from '@hcengineering/ui'
import { ContextMenu } from '@hcengineering/view-resources'
import { Icon, Label, closeTooltip } from '@hcengineering/ui'
import { showMenu } from '@hcengineering/view-resources'
export let request: Request
export let editable: boolean = false
@@ -36,10 +36,8 @@
function click (e: MouseEvent, request: Request) {
if (!editable) return
e.stopPropagation()
e.preventDefault()
closeTooltip()
showPopup(ContextMenu, { object: request }, e.target as HTMLElement)
showMenu(e, { object: request })
}
$: description = shouldShowDescription ? request.description.replace(/<[^>]*>/g, '').trim() : ''
@@ -15,13 +15,13 @@
-->
<script lang="ts">
import { Doc, Ref, getObjectValue } from '@hcengineering/core'
import { IconMoreV, showPopup } from '@hcengineering/ui'
import { AttributeModel } from '@hcengineering/view'
import inventory, { Category } from '@hcengineering/inventory'
import { IconMoreV } from '@hcengineering/ui'
import { AttributeModel } from '@hcengineering/view'
import { showMenu } from '@hcengineering/view-resources'
import HierarchyElement from './HierarchyElement.svelte'
import { ContextMenu } from '@hcengineering/view-resources'
import Expand from './icons/Expand.svelte'
import Collapse from './icons/Collapse.svelte'
import Expand from './icons/Expand.svelte'
export let descendants: Map<Ref<Doc>, Category[]>
export let level: number = 1
@@ -29,10 +29,6 @@
export let parent: Ref<Doc> = inventory.global.Category
let expanded: Set<Ref<Category>> = new Set<Ref<Category>>()
const showMenu = async (ev: MouseEvent, object: Category): Promise<void> => {
showPopup(ContextMenu, { object }, ev.target as HTMLElement)
}
function click (id: Ref<Category>): void {
if (!descendants.has(id)) return
if (expanded.has(id)) {
@@ -75,7 +71,14 @@
/>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="menuRow" on:click={(ev) => showMenu(ev, object)}><IconMoreV size={'small'} /></div>
<div
class="menuRow"
on:click={(ev) => {
showMenu(ev, { object })
}}
>
<IconMoreV size={'small'} />
</div>
</div>
</td>
{:else}
@@ -21,8 +21,10 @@
import contact from '@hcengineering/contact'
import { createEventDispatcher, onMount } from 'svelte'
import lead from '../plugin'
import { restrictionStore } from '@hcengineering/view-resources'
export let object: Lead
export let readonly: boolean = false
const dispatch = createEventDispatcher()
const client = getClient()
@@ -49,6 +51,7 @@
bind:value={rawTitle}
placeholder={lead.string.LeadPlaceholder}
kind={'large-style'}
disabled={readonly}
focusable
on:blur={() => {
if (rawTitle !== object.title) change('title', rawTitle)
@@ -58,6 +61,8 @@
_class={contact.class.Contact}
label={lead.string.Customer}
placeholder={lead.string.SelectCustomer}
showNavigate={!$restrictionStore.disableNavigation}
{readonly}
kind={'link'}
size={'x-large'}
justify={'left'}
@@ -15,18 +15,18 @@
-->
<script lang="ts">
import { AttachmentsPresenter } from '@hcengineering/attachment-resources'
import { ChatMessagesPresenter } from '@hcengineering/chunter-resources'
import contact from '@hcengineering/contact'
import { ContactPresenter } from '@hcengineering/contact-resources'
import type { WithLookup } from '@hcengineering/core'
import type { Lead } from '@hcengineering/lead'
import notification from '@hcengineering/notification'
import { getClient } from '@hcengineering/presentation'
import { AssigneePresenter } from '@hcengineering/task-resources'
import { ActionIcon, Component, DueDatePresenter, IconMoreH, showPopup } from '@hcengineering/ui'
import { BuildModelKey } from '@hcengineering/view'
import { ContextMenu, enabledConfig, openDoc, statusStore } from '@hcengineering/view-resources'
import { ChatMessagesPresenter } from '@hcengineering/chunter-resources'
import task from '@hcengineering/task'
import { AssigneePresenter } from '@hcengineering/task-resources'
import { ActionIcon, Component, DueDatePresenter, IconMoreH } from '@hcengineering/ui'
import { BuildModelKey } from '@hcengineering/view'
import { enabledConfig, openDoc, showMenu, statusStore } from '@hcengineering/view-resources'
import lead from '../plugin'
import LeadPresenter from './LeadPresenter.svelte'
@@ -37,10 +37,6 @@
const client = getClient()
const assigneeAttribute = client.getHierarchy().getAttribute(lead.class.Lead, 'assignee')
function showMenu (ev?: Event): void {
showPopup(ContextMenu, { object }, (ev as MouseEvent).target as HTMLElement)
}
function showLead () {
openDoc(client.getHierarchy(), object)
}
@@ -61,7 +57,7 @@
<ActionIcon
label={lead.string.More}
action={(evt) => {
showMenu(evt)
showMenu(evt, { object })
}}
icon={IconMoreH}
size={'small'}
+3 -2
View File
@@ -17,7 +17,7 @@
import { type IntlString } from '@hcengineering/platform'
import InviteLink from './components/InviteLink.svelte'
import LoginApp from './components/LoginApp.svelte'
import { changePassword, getWorkspaces, leaveWorkspace, selectWorkspace, sendInvite } from './utils'
import { changePassword, getWorkspaces, leaveWorkspace, selectWorkspace, sendInvite, getEnpoint } from './utils'
/*!
* Anticrm Platform Login Plugin
* © 2020, 2021 Anticrm Platform Contributors.
@@ -35,7 +35,8 @@ export default async () => ({
ChangePassword: changePassword,
SelectWorkspace: selectWorkspace,
GetWorkspaces: getWorkspaces,
SendInvite: sendInvite
SendInvite: sendInvite,
GetEndpoint: getEnpoint
}
})
+36
View File
@@ -816,3 +816,39 @@ export async function afterConfirm (): Promise<void> {
goTo('selectWorkspace')
}
}
export async function getEnpoint (): Promise<string | undefined> {
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
if (accountsUrl === undefined) {
throw new Error('accounts url not specified')
}
const endpoint = getMetadata(login.metadata.OverrideEndpoint)
if (endpoint !== undefined) {
return endpoint
}
const params: [] = []
const request = {
method: 'getEndpoint',
params
}
try {
const response = await fetch(accountsUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
})
const result = await response.json()
return result
} catch (err: any) {
console.log('get endpoint error', err)
Analytics.handleError(err)
}
}
+2 -1
View File
@@ -75,6 +75,7 @@ export default plugin(loginId, {
LeaveWorkspace: '' as Resource<(email: string) => Promise<void>>,
ChangePassword: '' as Resource<(oldPassword: string, password: string) => Promise<void>>,
SelectWorkspace: '' as Resource<(workspace: string) => Promise<[Status, WorkspaceLoginInfo | undefined]>>,
GetWorkspaces: '' as Resource<() => Promise<Workspace[]>>
GetWorkspaces: '' as Resource<() => Promise<Workspace[]>>,
GetEndpoint: '' as Resource<() => Promise<string>>
}
})
@@ -26,6 +26,7 @@
import Reviews from './review/Reviews.svelte'
export let object: Applicant
export let readonly: boolean = false
let candidate: Candidate
let vacancy: Vacancy
@@ -63,9 +64,9 @@
{#if object !== undefined && candidate !== undefined}
<Scroller horizontal stickedScrollBars>
<div class="flex-between min-w-min">
<div class="card"><CandidateCard {candidate} on:click /></div>
<div class="card"><CandidateCard {candidate} disabled={readonly} on:click /></div>
<div class="flex-center arrows"><ExpandRightDouble /></div>
<div class="card"><VacancyCard {vacancy} /></div>
<div class="card"><VacancyCard {vacancy} disabled={readonly} /></div>
</div>
</Scroller>
<div class="mt-6">
@@ -75,6 +76,7 @@
label={recruit.string.TalentReviews}
application={object?._id}
company={vacancy?.company}
{readonly}
/>
</div>
{/if}
@@ -22,9 +22,9 @@
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import { Vacancy } from '@hcengineering/recruit'
import tracker from '@hcengineering/tracker'
import { Button, Component, EditBox, IconMixin, IconMoreH, Label } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { Button, Component, EditBox, IconMixin, IconMoreH, Label, showPopup } from '@hcengineering/ui'
import { ContextMenu, DocAttributeBar, DocNavLink } from '@hcengineering/view-resources'
import { DocAttributeBar, DocNavLink, showMenu } from '@hcengineering/view-resources'
import { createEventDispatcher, onDestroy } from 'svelte'
import recruit from '../plugin'
import VacancyApplications from './VacancyApplications.svelte'
@@ -70,12 +70,6 @@
$: updateObject(_id)
function showMenu (ev?: Event): void {
if (object !== undefined) {
showPopup(ContextMenu, { object, excludedActions: [view.action.Open] }, (ev as MouseEvent).target as HTMLElement)
}
}
const ignoreMixins: Set<Ref<Mixin<Doc>>> = new Set<Ref<Mixin<Doc>>>()
const hierarchy = client.getHierarchy()
let mixins: Mixin<Doc>[] = []
@@ -164,7 +158,14 @@
{/if}
</svelte:fragment>
<svelte:fragment slot="utils">
<Button icon={IconMoreH} iconProps={{ size: 'medium' }} kind={'icon'} on:click={showMenu} />
<Button
icon={IconMoreH}
iconProps={{ size: 'medium' }}
kind={'icon'}
on:click={(e) => {
showMenu(e, { object, excludedActions: [view.action.Open] })
}}
/>
<Button
icon={IconMixin}
kind={'icon'}
@@ -25,8 +25,10 @@
export let objectId: Ref<Doc>
export let vacancies: number | undefined
export let readonly: boolean = false
const createApp = (ev: MouseEvent): void => {
if (readonly) return
showPopup(CreateVacancy, { company: objectId, preserveCompany: true }, ev.target as HTMLElement)
}
const config: (BuildModelKey | string)[] = [
@@ -54,7 +56,9 @@
<span class="antiSection-header__title">
<Label label={recruit.string.Vacancies} />
</span>
<Button id="appls.add" icon={IconAdd} kind={'ghost'} on:click={createApp} />
{#if !readonly}
<Button id="appls.add" icon={IconAdd} kind={'ghost'} on:click={createApp} />
{/if}
</div>
{#if (vacancies ?? 0) > 0}
<Scroller horizontal>
@@ -62,6 +66,7 @@
_class={recruit.class.Vacancy}
{config}
query={{ company: objectId }}
{readonly}
loadingProps={{ length: vacancies ?? 0 }}
/>
</Scroller>
@@ -73,9 +78,11 @@
<span class="content-dark-color">
<Label label={getEmbeddedLabel('No Vacancies')} />
</span>
<span class="over-underline content-color" on:click={createApp}>
<Label label={recruit.string.CreateVacancy} />
</span>
{#if !readonly}
<span class="over-underline content-color" on:click={createApp}>
<Label label={recruit.string.CreateVacancy} />
</span>
{/if}
</div>
{/if}
</div>
@@ -18,7 +18,7 @@
import { DateRangeMode, Doc, FindOptions, Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { Applicant, Review } from '@hcengineering/recruit'
import { Button, DatePresenter, IconAdd, Label, showPopup, Scroller } from '@hcengineering/ui'
import { Button, DatePresenter, IconAdd, Label, Scroller, showPopup } from '@hcengineering/ui'
import { Table } from '@hcengineering/view-resources'
import recruit from '../../plugin'
import FileDuo from '../icons/FileDuo.svelte'
@@ -29,8 +29,10 @@
export let label: IntlString = recruit.string.Reviews
export let application: Ref<Applicant> | undefined
export let company: Ref<Organization> | undefined
export let readonly: boolean = false
const createApp = (): void => {
if (readonly) return
showPopup(
CreateReview,
{
@@ -54,7 +56,9 @@
<span class="antiSection-header__title">
<Label {label} />
</span>
<Button icon={IconAdd} kind={'ghost'} on:click={createApp} />
{#if !readonly}
<Button icon={IconAdd} kind={'ghost'} on:click={createApp} />
{/if}
</div>
{#if reviews > 0}
<Scroller horizontal>
@@ -83,6 +87,7 @@
}
]}
{options}
{readonly}
query={{ attachedTo: objectId }}
loadingProps={{ length: reviews }}
/>
@@ -95,10 +100,12 @@
<span class="content-dark-color mt-2">
<Label label={recruit.string.NoReviewForCandidate} />
</span>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span class="over-underline content-color" on:click={createApp}>
<Label label={recruit.string.CreateAnReview} />
</span>
{#if !readonly}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span class="over-underline content-color" on:click={createApp}>
<Label label={recruit.string.CreateAnReview} />
</span>
{/if}
</div>
{/if}
</div>
@@ -163,7 +163,6 @@
void showMenu(event, attr, i)
}}
on:contextmenu={async (event) => {
event.preventDefault()
void showMenu(event, attr, i)
}}
on:click={async () => {
@@ -15,8 +15,8 @@
<script lang="ts">
import { Class, ClassifierKind, Doc, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { getEventPositionElement, showPopup, NavItem } from '@hcengineering/ui'
import { ContextMenu } from '@hcengineering/view-resources'
import { NavItem } from '@hcengineering/ui'
import { showMenu } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import settings from '../plugin'
@@ -50,9 +50,6 @@
}
return result
}
function showContextMenu (evt: MouseEvent, clazz: Class<Doc>): void {
showPopup(ContextMenu, { object: clazz }, getEventPositionElement(evt))
}
</script>
{#each classes as cl}
@@ -68,7 +65,7 @@
dispatch('select', cl)
}}
on:contextmenu={(evt) => {
showContextMenu(evt, clazz)
showMenu(evt, { object: clazz })
}}
/>
{#if desc.length}
@@ -13,26 +13,25 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import core, { Enum } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import {
eventToHTMLElement,
Breadcrumb,
ButtonIcon,
Header,
IconAdd,
IconMoreH,
IconTableOfContents,
Label,
showPopup,
Header,
Breadcrumb,
ModernButton,
Scroller,
Separator,
defineSeparators,
settingsSeparators,
Separator,
Scroller,
ButtonIcon,
IconTableOfContents,
ModernButton
showPopup
} from '@hcengineering/ui'
import { ContextMenu } from '@hcengineering/view-resources'
import { showMenu } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import setting from '../plugin'
import EnumValues from './EnumValues.svelte'
@@ -106,7 +105,7 @@
pressed={hovered === i}
on:click={(ev) => {
hovered = i
showPopup(ContextMenu, { object: value }, eventToHTMLElement(ev), () => {
showMenu(ev, { object: value }, () => {
hovered = null
})
}}
@@ -16,7 +16,7 @@
import { createEventDispatcher } from 'svelte'
import contact, { PersonAccount } from '@hcengineering/contact'
import { EmployeePresenter, personByIdStore } from '@hcengineering/contact-resources'
import { AccountRole, SortingOrder, getCurrentAccount } from '@hcengineering/core'
import { AccountRole, SortingOrder, getCurrentAccount, roleOrder } from '@hcengineering/core'
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import { DropdownIntlItem, DropdownLabelsIntl, EditBox, Header, Breadcrumb, Scroller } from '@hcengineering/ui'
import setting from '../plugin'
@@ -32,24 +32,17 @@
const currentRole = getCurrentAccount().role
const items: DropdownIntlItem[] = [
{ id: AccountRole.User.toString(), label: setting.string.User },
{ id: AccountRole.Maintainer.toString(), label: setting.string.Maintainer },
{ id: AccountRole.Owner.toString(), label: setting.string.Owner }
{ id: AccountRole.User, label: setting.string.User },
{ id: AccountRole.Maintainer, label: setting.string.Maintainer },
{ id: AccountRole.Owner, label: setting.string.Owner }
]
let accounts: PersonAccount[] = []
$: owners = accounts.filter((p) => p.role === AccountRole.Owner)
query.query(
contact.class.PersonAccount,
{},
(res) => {
accounts = res
},
{
sort: { name: SortingOrder.Descending }
}
)
query.query(contact.class.PersonAccount, {}, (res) => {
accounts = res
})
async function change (account: PersonAccount, value: AccountRole): Promise<void> {
await client.update(account, {
@@ -80,13 +73,14 @@
</div>
<DropdownLabelsIntl
label={setting.string.Role}
disabled={account.role > currentRole || (account.role === AccountRole.Owner && owners.length === 1)}
disabled={roleOrder[account.role] > roleOrder[currentRole] ||
(account.role === AccountRole.Owner && owners.length === 1)}
kind={'primary'}
size={'medium'}
{items}
selected={account.role?.toString()}
selected={account.role}
on:selected={(e) => {
void change(account, Number(e.detail))
void change(account, e.detail)
}}
/>
</div>
@@ -28,6 +28,7 @@
export let ignoreKeys: string[]
export let vertical: boolean = false
export let allowedCollections: string[] = []
export let readonly: boolean = false
const client = getClient()
const hierarchy = client.getHierarchy()
@@ -70,16 +71,17 @@
bind:value={object.assignee}
on:change={change}
allowDeselect
{readonly}
titleDeselect={task.string.TaskUnAssign}
/>
<div class="column">
<AttributesBar {object} _class={object._class} keys={filtredKeys} />
<AttributesBar {object} _class={object._class} keys={filtredKeys} {readonly} />
</div>
</div>
<AttributesBar {object} _class={object._class} keys={['status']} showHeader={false} />
<AttributesBar {object} _class={object._class} keys={['status']} showHeader={false} {readonly} />
</div>
{:else}
<DocAttributeBar {object} {ignoreKeys} {mixins} {allowedCollections} on:update />
<DocAttributeBar {object} {ignoreKeys} {mixins} {allowedCollections} {readonly} on:update />
{/if}
<style lang="scss">
@@ -31,14 +31,7 @@
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation'
import tags from '@hcengineering/tags'
import { Project, Task, TaskOrdering } from '@hcengineering/task'
import {
ColorDefinition,
defaultBackground,
getEventPositionElement,
Label,
showPopup,
themeStore
} from '@hcengineering/ui'
import { ColorDefinition, defaultBackground, Label, themeStore } from '@hcengineering/ui'
import view, { AttributeModel, BuildModelKey, Viewlet, ViewOptionModel, ViewOptions } from '@hcengineering/view'
import {
focusStore,
@@ -49,10 +42,10 @@
getPresenter,
groupBy,
ListSelectionProvider,
Menu,
noCategory,
SelectDirection,
setGroupByValues
setGroupByValues,
showMenu
} from '@hcengineering/view-resources'
import { onMount } from 'svelte'
import task from '../../plugin'
@@ -110,9 +103,8 @@
;(document.activeElement as HTMLElement)?.blur()
})
const showMenu = async (ev: MouseEvent, items: Doc[]): Promise<void> => {
ev.preventDefault()
showPopup(Menu, { object: items, baseMenuClass }, getEventPositionElement(ev))
const showContextMenu = async (ev: MouseEvent, items: Doc[]): Promise<void> => {
showMenu(ev, { object: items, baseMenuClass })
}
// Category information only
let tasks: DocWithRank[] = []
@@ -267,7 +259,7 @@
on:check={(evt) => {
listProvider.updateSelection(evt.detail.docs, evt.detail.value)
}}
on:contextmenu={(evt) => showMenu(evt.detail.evt, evt.detail.objects)}
on:contextmenu={(evt) => showContextMenu(evt.detail.evt, evt.detail.objects)}
>
<svelte:fragment slot="header" let:state let:count let:index>
{@const color = accentColors.get(`${index}${$themeStore.dark}${groupByKey}`)}
@@ -43,14 +43,12 @@
Separator,
TextArea,
defineSeparators,
eventToHTMLElement,
getCurrentResolvedLocation,
navigate,
resolvedLocationStore,
secondNavSeparators,
showPopup
secondNavSeparators
} from '@hcengineering/ui'
import { ContextMenu } from '@hcengineering/view-resources'
import { showMenu } from '@hcengineering/view-resources'
import plugin from '../../plugin'
import IconLayers from '../icons/Layers.svelte'
import CreateTaskType from '../taskTypes/CreateTaskType.svelte'
@@ -200,7 +198,7 @@
size={'small'}
kind={'secondary'}
on:click={(ev) => {
showPopup(ContextMenu, { object: type }, eventToHTMLElement(ev), () => {})
showMenu(ev, { object: type })
}}
/>
<Breadcrumbs
@@ -63,7 +63,7 @@
projectType={$selectedTypeStore}
/>
{:else}
<Button {kind} {size} {width} {justify} {shrink} on:click={handleClick}>
<Button {kind} {size} {width} {justify} {shrink} {disabled} on:click={handleClick}>
<svelte:fragment slot="content">
{#if state}
<div class="pointer-events-none clear-mins">
@@ -39,6 +39,7 @@
export let shouldShowName = true
export let shrink: number = 0
export let isAction: boolean = false
export let readonly: boolean = false
$: _object =
(typeof object !== 'string' ? object : undefined) ?? (typeof value !== 'string' ? value : undefined) ?? []
@@ -186,6 +187,7 @@
{width}
{short}
{shrink}
{readonly}
{shouldShowName}
showNavigate={false}
justify={'left'}
@@ -21,6 +21,7 @@
export let value: WithLookup<Issue>
export let width: string | undefined = undefined
export let editable: boolean = true
const client = getClient()
$: shouldIgnoreOverdue =
@@ -49,7 +50,7 @@
kind={'link'}
value={value.dueDate}
{width}
editable
{editable}
onChange={(e) => handleDueDateChanged(e)}
{shouldIgnoreOverdue}
/>
@@ -52,7 +52,15 @@
{/if}
{:else if value}
<div class="flex-row-center">
<DocNavLink object={value} {onClick} {disabled} {noUnderline} component={tracker.component.EditIssue} shrink={0}>
<DocNavLink
object={value}
{onClick}
{disabled}
{noUnderline}
{inline}
component={tracker.component.EditIssue}
shrink={0}
>
<span class="issuePresenterRoot" class:list={kind === 'list'} class:cursor-pointer={!disabled}>
{#if shouldShowAvatar}
<div class="icon" use:tooltip={{ label: tracker.string.Issue }}>
@@ -62,6 +62,7 @@
openDoc,
SelectDirection,
setGroupByValues,
showMenu,
statusStore
} from '@hcengineering/view-resources'
import { ChatMessagesPresenter } from '@hcengineering/chunter-resources'
@@ -138,10 +139,6 @@
;(document.activeElement as HTMLElement)?.blur()
})
const showMenu = async (ev: MouseEvent, items: Doc[]): Promise<void> => {
ev.preventDefault()
showPopup(Menu, { object: items, baseMenuClass }, getEventPositionElement(ev))
}
// Category information only
let tasks: DocWithRank[] = []
@@ -337,7 +334,9 @@
on:check={(evt) => {
listProvider.updateSelection(evt.detail.docs, evt.detail.value)
}}
on:contextmenu={(evt) => showMenu(evt.detail.evt, evt.detail.objects)}
on:contextmenu={(evt) => {
showMenu(evt.detail.evt, { object: evt.detail.objects, baseMenuClass })
}}
>
<svelte:fragment slot="header" let:state let:count let:index>
{@const color = accentColors.get(`${index}${$themeStore.dark}${groupByKey}`)}
@@ -6,6 +6,8 @@
export let value: Issue
export let type: 'isBlocking' | 'blockedBy' | 'relations'
export let blockedBy: Doc[] | undefined = undefined
export let disabled: boolean = false
export let readonly: boolean = false
$: valueGroup = (type === 'isBlocking' ? blockedBy ?? [] : value[type] ?? []).reduce<
Map<Ref<Class<Doc>>, Ref<Doc>[]>
@@ -25,7 +27,7 @@
{#each classes as classCategory}
{@const vals = valueGroup.get(classCategory)}
{#if vals}
<RelationEditorPart {value} _class={classCategory} documentIds={vals} {type} />
<RelationEditorPart {value} _class={classCategory} documentIds={vals} {type} {disabled} {readonly} />
{/if}
{/each}
</div>
@@ -13,6 +13,8 @@
export let _class: Ref<Class<Doc>>
export let documentIds: Ref<Doc>[]
export let type: 'isBlocking' | 'blockedBy' | 'relations'
export let disabled: boolean = false
export let readonly: boolean = false
const client = getClient()
const issuesQuery = createQuery()
@@ -29,6 +31,7 @@
})
async function handleClick (issue: RelatedDocument) {
if (readonly || disabled) return
const prop = type === 'isBlocking' ? 'blockedBy' : type
const isBlockingValue = await client.findAll(value._class, { 'blockedBy._id': value._id })
@@ -67,6 +70,7 @@
}
async function handleRedirect (issue: Issue) {
if (disabled) return
const loc = await issueLinkFragmentProvider(issue)
navigate(loc)
}
@@ -80,13 +84,15 @@
<div class="tag-container">
<Icon {icon} size={'small'} />
<div class="flex-grow">
<button on:click|stopPropagation={() => handleRedirect(issue)}>
<button {disabled} on:click|stopPropagation={() => handleRedirect(issue)}>
<span class="overflow-label ml-1-5 content-color">{issue.identifier}</span>
</button>
</div>
<button class="btn-close" on:click|stopPropagation={() => handleClick(issue)}>
<Icon icon={IconClose} size={'x-small'} />
</button>
{#if !readonly}
<button class="btn-close" on:click|stopPropagation={() => handleClick(issue)}>
<Icon icon={IconClose} size={'x-small'} />
</button>
{/if}
</div>
{:else}
<div class="tag-container between">
@@ -94,9 +100,11 @@
is={view.component.ObjectPresenter}
props={{ objectId: doc._id, _class: doc._class, value: doc, noUnderline: true, props: { avatarSize: 'card' } }}
/>
<button class="btn-close" on:click|stopPropagation={() => handleClick(doc)}>
<Icon icon={IconClose} size={'x-small'} />
</button>
{#if !readonly}
<button class="btn-close" on:click|stopPropagation={() => handleClick(doc)}>
<Icon icon={IconClose} size={'x-small'} />
</button>
{/if}
</div>
{/if}
{/each}
@@ -53,16 +53,6 @@
align-items: center;
flex-shrink: 1;
min-width: 1rem;
&.list {
color: var(--theme-caption-color);
}
&:hover {
text-decoration: underline;
}
&:active {
color: var(--theme-content-color);
}
}
.with-margin {
@@ -21,7 +21,7 @@
import tags from '@hcengineering/tags'
import type { Issue } from '@hcengineering/tracker'
import { Component, Label } from '@hcengineering/ui'
import { getFiltredKeys, isCollectionAttr, ObjectBox } from '@hcengineering/view-resources'
import { getFiltredKeys, isCollectionAttr, ObjectBox, restrictionStore } from '@hcengineering/view-resources'
import tracker from '../../../plugin'
import ComponentEditor from '../../components/ComponentEditor.svelte'
import MilestoneEditor from '../../milestones/MilestoneEditor.svelte'
@@ -33,6 +33,7 @@
export let issue: Issue
export let showAllMixins: boolean = false
export let readonly = false
const query = createQuery()
let showIsBlocking = false
@@ -125,31 +126,37 @@
<Label label={tracker.string.Status} />
</span>
<StatusEditor value={issue} size={'medium'} iconSize={'small'} shouldShowLabel />
<StatusEditor value={issue} size={'medium'} iconSize={'small'} shouldShowLabel isEditable={!readonly} />
{#if issue.blockedBy?.length}
<span class="labelTop">
<Label label={tracker.string.BlockedBy} />
</span>
<RelationEditor value={issue} type="blockedBy" />
<RelationEditor value={issue} type="blockedBy" {readonly} disabled={$restrictionStore.disableNavigation} />
{/if}
{#if showIsBlocking}
<span class="labelTop">
<Label label={tracker.string.Blocks} />
</span>
<RelationEditor value={issue} type="isBlocking" {blockedBy} />
<RelationEditor
value={issue}
type="isBlocking"
{blockedBy}
{readonly}
disabled={$restrictionStore.disableNavigation}
/>
{/if}
{#if issue.relations?.length}
<span class="labelTop">
<Label label={tracker.string.Related} />
</span>
<RelationEditor value={issue} type="relations" />
<RelationEditor value={issue} type="relations" {readonly} disabled={$restrictionStore.disableNavigation} />
{/if}
<span class="labelOnPanel">
<Label label={tracker.string.Priority} />
</span>
<PriorityEditor value={issue} size={'medium'} shouldShowLabel />
<PriorityEditor value={issue} size={'medium'} shouldShowLabel isEditable={!readonly} />
<span class="labelOnPanel">
<Label label={core.string.CreatedBy} />
@@ -168,24 +175,27 @@
<span class="labelOnPanel">
<Label label={tracker.string.Assignee} />
</span>
<AssigneeEditor object={issue} size={'medium'} avatarSize={'card'} width="100%" />
<AssigneeEditor object={issue} size={'medium'} avatarSize={'card'} width="100%" {readonly} />
<span class="labelTop">
<Label label={tracker.string.Labels} />
</span>
<Component is={tags.component.TagsAttributeEditor} props={{ object: issue, label: tracker.string.AddLabel }} />
<Component
is={tags.component.TagsAttributeEditor}
props={{ object: issue, label: tracker.string.AddLabel, readonly }}
/>
<div class="divider" />
<span class="labelOnPanel">
<Label label={tracker.string.Component} />
</span>
<ComponentEditor value={issue} space={issue.space} size={'medium'} />
<ComponentEditor value={issue} space={issue.space} size={'medium'} isEditable={!readonly} />
<span class="labelOnPanel">
<Label label={tracker.string.Milestone} />
</span>
<MilestoneEditor value={issue} space={issue.space} size={'medium'} />
<MilestoneEditor value={issue} space={issue.space} size={'medium'} isEditable={!readonly} />
{#if issue.dueDate !== null}
<div class="divider" />
@@ -193,13 +203,13 @@
<span class="labelOnPanel">
<Label label={tracker.string.DueDate} />
</span>
<DueDateEditor value={issue} width={'100%'} />
<DueDateEditor value={issue} width={'100%'} editable={!readonly} />
{/if}
{#if keys.length > 0}
<div class="divider" />
{#each keys as key (typeof key === 'string' ? key : key.key)}
<AttributeBarEditor {key} _class={issue._class} object={issue} showHeader={true} size={'medium'} />
<AttributeBarEditor {readonly} {key} _class={issue._class} object={issue} showHeader={true} size={'medium'} />
{/each}
{/if}
@@ -211,6 +221,7 @@
<AttributeBarEditor
{key}
_class={mixin._id}
{readonly}
object={hierarchy.as(issue, mixin._id)}
showHeader={true}
size={'medium'}
@@ -40,11 +40,10 @@
Spinner,
createFocusManager,
getCurrentResolvedLocation,
navigate,
showPopup
navigate
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import { ContextMenu, DocNavLink, ParentsNavigator } from '@hcengineering/view-resources'
import { DocNavLink, ParentsNavigator, showMenu } from '@hcengineering/view-resources'
import { createEventDispatcher, onDestroy } from 'svelte'
import { generateIssueShortLink } from '../../../issues'
import tracker from '../../../plugin'
@@ -58,6 +57,7 @@
export let _class: Ref<Class<Issue>>
export let embedded: boolean = false
export let kind: 'default' | 'modern' = 'default'
export let readonly: boolean = false
let lastId: Ref<Doc> = _id
const queryClient = createQuery()
@@ -121,13 +121,9 @@
}
}
function showMenu (ev?: Event): void {
function showContextMenu (ev: MouseEvent): void {
if (issue !== undefined) {
showPopup(
ContextMenu,
{ object: issue, excludedActions: [view.action.Open] },
(ev as MouseEvent).target as HTMLElement
)
showMenu(ev, { object: issue, excludedActions: [view.action.Open] })
}
}
@@ -181,6 +177,8 @@
<Panel
object={issue}
isHeader={false}
withoutInput={readonly}
allowClose={!embedded}
isAside={true}
isSub={false}
{embedded}
@@ -223,25 +221,27 @@
</svelte:fragment>
<svelte:fragment slot="utils">
<Button icon={IconMoreH} iconProps={{ size: 'medium' }} kind={'icon'} on:click={showMenu} />
<CopyToClipboard issueUrl={generateIssueShortLink(issue.identifier)} />
<Button
icon={setting.icon.Setting}
kind={'icon'}
iconProps={{ 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 !readonly}
<Button icon={IconMoreH} iconProps={{ size: 'medium' }} kind={'icon'} on:click={showContextMenu} />
<CopyToClipboard issueUrl={generateIssueShortLink(issue.identifier)} />
<Button
icon={setting.icon.Setting}
kind={'icon'}
iconProps={{ 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}
<Button
icon={IconMixin}
iconProps={{ size: 'medium' }}
@@ -265,6 +265,7 @@
<EditBox
focusIndex={1}
bind:value={title}
disabled={readonly}
placeholder={tracker.string.IssueTitlePlaceholder}
kind="large-style"
on:blur={save}
@@ -273,6 +274,7 @@
<AttachmentStyleBoxCollabEditor
focusIndex={30}
object={issue}
{readonly}
key={{ key: 'description', attr: descriptionKey }}
bind:this={descriptionBox}
placeholder={tracker.string.IssueDescriptionPlaceholder}
@@ -292,7 +294,7 @@
{#if editorFooter}
<div class="step-tb-6">
<Component is={editorFooter.footer} props={{ object: issue, _class, ...editorFooter.props }} />
<Component is={editorFooter.footer} props={{ object: issue, _class, ...editorFooter.props, readonly }} />
</div>
{/if}
@@ -303,7 +305,7 @@
<svelte:fragment slot="custom-attributes">
{#if issue !== undefined && currentProject}
<div class="space-divider" />
<ControlPanel {issue} {showAllMixins} />
<ControlPanel {issue} {showAllMixins} {readonly} />
{/if}
<div class="popupPanel-body__aside-grid">
@@ -18,7 +18,7 @@
import { Issue } from '@hcengineering/tracker'
import { Button, Chevron, ExpandCollapse, IconAdd, closeTooltip, resizeObserver, showPopup } from '@hcengineering/ui'
import view, { ViewOptions, Viewlet, ViewletPreference } from '@hcengineering/view'
import { ViewletsSettingButton } from '@hcengineering/view-resources'
import { ViewletsSettingButton, restrictionStore } from '@hcengineering/view-resources'
import { afterUpdate } from 'svelte'
import tracker from '../../../plugin'
import CreateIssue from '../../CreateIssue.svelte'
@@ -139,19 +139,21 @@
{#if hasSubIssues}
<slot name="buttons" />
{/if}
<Button
id="add-sub-issue"
icon={IconAdd}
label={hasSubIssues ? undefined : createLabel}
labelParams={{ subIssues: 0 }}
kind={'ghost'}
showTooltip={{ label: createLabel, direction: 'bottom' }}
on:click={() => {
isCollapsed = false
closeTooltip()
openNewIssueDialog()
}}
/>
{#if !$restrictionStore.readonly}
<Button
id="add-sub-issue"
icon={IconAdd}
label={hasSubIssues ? undefined : createLabel}
labelParams={{ subIssues: 0 }}
kind={'ghost'}
showTooltip={{ label: createLabel, direction: 'bottom' }}
on:click={() => {
isCollapsed = false
closeTooltip()
openNewIssueDialog()
}}
/>
{/if}
</div>
</div>
{#if hasSubIssues && viewOptions && viewlet}
@@ -165,7 +167,7 @@
}}
>
<SubIssueList
createItemDialog={CreateIssue}
createItemDialog={!$restrictionStore.readonly ? CreateIssue : undefined}
createItemLabel={tracker.string.AddIssueTooltip}
createItemDialogProps={{ space: object.space, ...createParams, shouldSaveDraft }}
focusIndex={focusIndex === -1 ? -1 : focusIndex + 1}
@@ -30,6 +30,9 @@
export let compactMode: boolean = false
export let configurations: Record<Ref<Class<Doc>>, Viewlet['config']> = {}
export let preference: ViewletPreference[] = []
export let createItemDialog: AnySvelteComponent | AnyComponent | undefined = undefined
export let createItemLabel: IntlString | undefined = undefined
export let createItemDialogProps: Record<string, any> | undefined = undefined
let list: List
@@ -62,10 +65,6 @@
isFocus: () => focused
})
export let createItemDialog: AnySvelteComponent | AnyComponent | undefined = undefined
export let createItemLabel: IntlString | undefined = undefined
export let createItemDialogProps: Record<string, any> | undefined = undefined
const dispatch = createEventDispatcher()
</script>
@@ -15,7 +15,7 @@
<script lang="ts">
import { Issue, trackerId } from '@hcengineering/tracker'
import { Button, IconScaleFull, Label, closeTooltip, getCurrentResolvedLocation, navigate } from '@hcengineering/ui'
import { createFilter, setFilters } from '@hcengineering/view-resources'
import { createFilter, restrictionStore, setFilters } from '@hcengineering/view-resources'
import tracker from '../../../plugin'
import QueryIssuesList from './QueryIssuesList.svelte'
@@ -44,24 +44,26 @@
<Label label={tracker.string.SubIssuesList} params={{ subIssues: size }} />
</svelte:fragment>
<svelte:fragment slot="buttons">
<Button
icon={IconScaleFull}
kind={'ghost'}
showTooltip={{ label: tracker.string.OpenSubIssues, direction: 'bottom' }}
on:click={() => {
const filter = createFilter(tracker.class.Issue, 'attachedTo', [issue._id])
if (filter !== undefined) {
closeTooltip()
const loc = getCurrentResolvedLocation()
loc.fragment = undefined
loc.query = undefined
loc.path[2] = trackerId
loc.path[3] = issue.space
loc.path[4] = 'issues'
navigate(loc)
setFilters([filter])
}
}}
/>
{#if !$restrictionStore.disableNavigation}
<Button
icon={IconScaleFull}
kind={'ghost'}
showTooltip={{ label: tracker.string.OpenSubIssues, direction: 'bottom' }}
on:click={() => {
const filter = createFilter(tracker.class.Issue, 'attachedTo', [issue._id])
if (filter !== undefined) {
closeTooltip()
const loc = getCurrentResolvedLocation()
loc.fragment = undefined
loc.query = undefined
loc.path[2] = trackerId
loc.path[3] = issue.space
loc.path[4] = 'issues'
navigate(loc)
setFilters([filter])
}
}}
/>
{/if}
</svelte:fragment>
</QueryIssuesList>
@@ -16,18 +16,13 @@
import contact from '@hcengineering/contact'
import { AssigneeBox } from '@hcengineering/contact-resources'
import { Issue } from '@hcengineering/tracker'
import { ListView, deviceOptionsStore as deviceInfo, getEventPositionElement, showPopup } from '@hcengineering/ui'
import { ContextMenu, FixedColumn, ListSelectionProvider } from '@hcengineering/view-resources'
import { ListView, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
import { FixedColumn, ListSelectionProvider, showMenu } from '@hcengineering/view-resources'
import tracker from '../../../plugin'
import { activeProjects } from '../../../utils'
import EstimationEditor from './EstimationEditor.svelte'
export let issues: Issue[]
function showContextMenu (ev: MouseEvent, object: Issue) {
showPopup(ContextMenu, { object }, $deviceInfo.isMobile ? 'top' : getEventPositionElement(ev))
}
const listProvider = new ListSelectionProvider(() => {})
$: twoRows = $deviceInfo.twoRows
</script>
@@ -36,11 +31,10 @@
<ListView count={issues.length} addClass={'step-tb-2-accent'}>
<svelte:fragment slot="item" let:item>
{@const issue = issues[item]}
{@const currentProject = $activeProjects.get(issue.space)}
<div
class="{twoRows ? 'flex-col' : 'flex-between'} p-text-2 clear-mins"
on:contextmenu|preventDefault={(ev) => {
showContextMenu(ev, issue)
on:contextmenu={(ev) => {
showMenu(ev, { object: issue })
}}
on:mouseover={() => {
listProvider.updateFocus(issue)
@@ -48,6 +48,7 @@
{size}
{justify}
{width}
disabled={readonly}
on:click={(ev) => {
if (!shown && !readonly) {
showPopup(EditBoxPopup, { value, format: 'number' }, eventToHTMLElement(ev), (res) => {
@@ -29,6 +29,7 @@
export let kind: 'no-border' | 'link' = 'no-border'
export let size: 'small' | 'medium' | 'large' = 'large'
export let currentProject: Project | undefined
export let readonly: boolean = false
$: if (currentProject === undefined) {
currentProject = $activeProjects.get(object.space)
@@ -37,6 +38,7 @@
$: defaultTimeReportDay = currentProject?.defaultTimeReportDay
function addTimeReport (event: MouseEvent): void {
if (readonly) return
showPopup(
TimeSpendReportPopup,
{
@@ -52,6 +54,7 @@
)
}
function showReports (event: MouseEvent): void {
if (readonly) return
showPopup(ReportsPopup, { issue: object }, eventToHTMLElement(event))
}
$: childTime = floorFractionDigits(
@@ -66,6 +69,7 @@
<div
id="ReportedTimeEditor"
class="link-container antiButton link {size} flex-grow flex-between"
class:readonly
on:click={showReports}
>
{#if value !== undefined}
@@ -78,9 +82,11 @@
{:else}
<span class="content-dark-color"><Label label={placeholder} /></span>
{/if}
<div class="add-action">
<ActionIcon icon={IconAdd} size={'small'} action={addTimeReport} />
</div>
{#if !readonly}
<div class="add-action">
<ActionIcon icon={IconAdd} size={'small'} action={addTimeReport} />
</div>
{/if}
</div>
{:else if value !== undefined}
<span class="flex-row-center">
@@ -101,9 +107,6 @@
}
&:hover {
background-color: var(--theme-bg-color);
border-color: var(--theme-divider-color);
.add-action {
visibility: visible;
}
@@ -22,10 +22,9 @@
ListView,
deviceOptionsStore as deviceInfo,
eventToHTMLElement,
getEventPositionElement,
showPopup
} from '@hcengineering/ui'
import { ContextMenu, FixedColumn, ListSelectionProvider } from '@hcengineering/view-resources'
import { FixedColumn, ListSelectionProvider, showMenu } from '@hcengineering/view-resources'
import tracker from '../../../plugin'
import { activeProjects } from '../../../utils'
import TimePresenter from './TimePresenter.svelte'
@@ -33,10 +32,6 @@
export let reports: WithLookup<TimeSpendReport>[]
function showContextMenu (ev: MouseEvent, object: TimeSpendReport) {
showPopup(ContextMenu, { object }, getEventPositionElement(ev))
}
const listProvider = new ListSelectionProvider(() => {})
const toProjectId = (ref: Ref<Space>) => ref as Ref<Project>
@@ -70,8 +65,8 @@
{@const currentProject = $activeProjects.get(toProjectId(report.space))}
<div
class="{twoRows ? 'flex-col' : 'flex-between'} p-text-2 clear-mins"
on:contextmenu|preventDefault={(ev) => {
showContextMenu(ev, report)
on:contextmenu={(ev) => {
showMenu(ev, { object: report })
}}
on:mouseenter={() => {
listProvider.updateFocus(report)
@@ -22,9 +22,9 @@
import setting, { settingId } from '@hcengineering/setting'
import tags from '@hcengineering/tags'
import { IssueTemplate, IssueTemplateChild, Project } from '@hcengineering/tracker'
import { Button, EditBox, IconMoreH, Label, getCurrentResolvedLocation, navigate, showPopup } from '@hcengineering/ui'
import { ContextMenu } from '@hcengineering/view-resources'
import { Button, EditBox, IconMoreH, Label, getCurrentResolvedLocation, navigate } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { showMenu } from '@hcengineering/view-resources'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
import tracker from '../../plugin'
@@ -91,13 +91,9 @@
}
}
function showMenu (ev?: Event): void {
function showContextMenu (ev: MouseEvent): void {
if (template !== undefined) {
showPopup(
ContextMenu,
{ object: template, excludedActions: [view.action.Open] },
(ev as MouseEvent).target as HTMLElement
)
showMenu(ev, { object: template, excludedActions: [view.action.Open] })
}
}
@@ -183,7 +179,7 @@
{/if}
</svelte:fragment>
<svelte:fragment slot="utils">
<Button icon={IconMoreH} iconProps={{ size: 'medium' }} kind={'icon'} on:click={showMenu} />
<Button icon={IconMoreH} iconProps={{ size: 'medium' }} kind={'icon'} on:click={showContextMenu} />
<Button
icon={setting.icon.Setting}
iconProps={{ size: 'medium' }}
+1
View File
@@ -43,6 +43,7 @@
"@hcengineering/contact": "^0.6.20",
"@hcengineering/model": "^0.6.7",
"@hcengineering/panel": "^0.6.15",
"@hcengineering/guest": "^0.6.0",
"@hcengineering/core": "^0.6.28",
"@hcengineering/view": "^0.6.9",
"@hcengineering/ui": "^0.6.11",
+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)

Some files were not shown because too many files have changed in this diff Show More