mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-10 19:57:43 +02:00
UBERF-5603 (#4754)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
@@ -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[] = []
|
||||
|
||||
+6
-3
@@ -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>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/assets/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -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 |
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "assets"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"string": {
|
||||
"CreatePublicLink": "Создать публичную ссылку",
|
||||
"PublicLink": "Публичная ссылка",
|
||||
"Revoke": "Отозвать",
|
||||
"RevokeConfirmation": "Это приведет к тому, что ранее созданная ссылка перестанет работать. Вы уверены, что хотите отозвать эту публичную ссылку?",
|
||||
"LinkWasRevoked": "Ссылка была отозвана"
|
||||
}
|
||||
}
|
||||
@@ -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`))
|
||||
)
|
||||
@@ -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`))
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/eslint.config.json'],
|
||||
parserOptions: { tsconfigRootDir: __dirname }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "ui"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)']
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const sveltePreprocess = require('svelte-preprocess')
|
||||
|
||||
module.exports = {
|
||||
preprocess: sveltePreprocess()
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/ui/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
@@ -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'}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
+4
-10
@@ -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)
|
||||
|
||||
+1
@@ -48,6 +48,7 @@
|
||||
{size}
|
||||
{justify}
|
||||
{width}
|
||||
disabled={readonly}
|
||||
on:click={(ev) => {
|
||||
if (!shown && !readonly) {
|
||||
showPopup(EditBoxPopup, { value, format: 'number' }, eventToHTMLElement(ev), (res) => {
|
||||
|
||||
+9
-6
@@ -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;
|
||||
}
|
||||
|
||||
+3
-8
@@ -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' }}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user