mirror of
https://github.com/hcengineering/platform.git
synced 2025-06-09 09:20:54 +00:00
UBERF-6330: Fix race conditions in UI (#5184)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
d3fa4908ef
commit
0d1d1a8b8d
@ -27,12 +27,21 @@ export class MeasureMetricsContext implements MeasureContext {
|
|||||||
this.logger.logOperation(this.name, spend, { ...params, ...fullParams })
|
this.logger.logOperation(this.name, spend, { ...params, ...fullParams })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const errorPrinter = ({ message, stack, ...rest }: Error): object => ({
|
||||||
|
message,
|
||||||
|
stack,
|
||||||
|
...rest
|
||||||
|
})
|
||||||
|
function replacer (value: any): any {
|
||||||
|
return value instanceof Error ? errorPrinter(value) : value
|
||||||
|
}
|
||||||
|
|
||||||
this.logger = logger ?? {
|
this.logger = logger ?? {
|
||||||
info: (msg, args) => {
|
info: (msg, args) => {
|
||||||
console.info(msg, ...Object.entries(args ?? {}).map((it) => `${it[0]}=${JSON.stringify(it[1])}`))
|
console.info(msg, ...Object.entries(args ?? {}).map((it) => `${it[0]}=${JSON.stringify(replacer(it[1]))}`))
|
||||||
},
|
},
|
||||||
error: (msg, args) => {
|
error: (msg, args) => {
|
||||||
console.error(msg, ...Object.entries(args ?? {}).map((it) => `${it[0]}=${JSON.stringify(it[1])}`))
|
console.error(msg, ...Object.entries(args ?? {}).map((it) => `${it[0]}=${JSON.stringify(replacer(it[1]))}`))
|
||||||
},
|
},
|
||||||
close: async () => {},
|
close: async () => {},
|
||||||
logOperation: (operation, time, params) => {}
|
logOperation: (operation, time, params) => {}
|
||||||
|
@ -60,6 +60,15 @@ export interface ModelLogger {
|
|||||||
error: (msg: string, err: any) => void
|
error: (msg: string, err: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const errorPrinter = ({ message, stack, ...rest }: Error): object => ({
|
||||||
|
message,
|
||||||
|
stack,
|
||||||
|
...rest
|
||||||
|
})
|
||||||
|
function replacer (value: any): any {
|
||||||
|
return value instanceof Error ? errorPrinter(value) : value
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -68,6 +77,6 @@ export const consoleModelLogger: ModelLogger = {
|
|||||||
console.log(msg, data)
|
console.log(msg, data)
|
||||||
},
|
},
|
||||||
error (msg: string, data: any): void {
|
error (msg: string, data: any): void {
|
||||||
console.error(msg, data)
|
console.error(msg, replacer(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@
|
|||||||
if (key.code === 'Enter') {
|
if (key.code === 'Enter') {
|
||||||
key.preventDefault()
|
key.preventDefault()
|
||||||
key.stopPropagation()
|
key.stopPropagation()
|
||||||
handleSelection(key, objects, selection)
|
void handleSelection(key, objects, selection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const manager = createFocusManager()
|
const manager = createFocusManager()
|
||||||
@ -217,7 +217,7 @@
|
|||||||
class="menu-item withList w-full flex-row-center"
|
class="menu-item withList w-full flex-row-center"
|
||||||
disabled={readonly || isDeselectDisabled || loading}
|
disabled={readonly || isDeselectDisabled || loading}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
handleSelection(undefined, objects, item)
|
void handleSelection(undefined, objects, item)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span class="label" class:disabled={readonly || isDeselectDisabled || loading}>
|
<span class="label" class:disabled={readonly || isDeselectDisabled || loading}>
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
import presentation from '../plugin'
|
import presentation from '../plugin'
|
||||||
import { ObjectSearchCategory, ObjectSearchResult } from '../types'
|
import { ObjectSearchCategory, ObjectSearchResult } from '../types'
|
||||||
import { getClient } from '../utils'
|
import { getClient } from '../utils'
|
||||||
import { hasResource } from '..'
|
import { hasResource, reduceCalls } from '..'
|
||||||
|
|
||||||
export let query: string = ''
|
export let query: string = ''
|
||||||
export let label: IntlString | undefined = undefined
|
export let label: IntlString | undefined = undefined
|
||||||
@ -108,7 +108,7 @@
|
|||||||
|
|
||||||
export function done () {}
|
export function done () {}
|
||||||
|
|
||||||
async function updateItems (
|
const updateItems = reduceCalls(async function updateItems (
|
||||||
cat: ObjectSearchCategory | undefined,
|
cat: ObjectSearchCategory | undefined,
|
||||||
query: string,
|
query: string,
|
||||||
relatedDocuments?: RelatedDocument[]
|
relatedDocuments?: RelatedDocument[]
|
||||||
@ -148,8 +148,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
$: updateItems(category, query, relatedDocuments)
|
$: void updateItems(category, query, relatedDocuments)
|
||||||
|
|
||||||
const manager = createFocusManager()
|
const manager = createFocusManager()
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
import view, { IconProps } from '@hcengineering/view'
|
import view, { IconProps } from '@hcengineering/view'
|
||||||
|
|
||||||
import { ObjectCreate } from '../types'
|
import { ObjectCreate } from '../types'
|
||||||
import { getClient } from '../utils'
|
import { getClient, reduceCalls } from '../utils'
|
||||||
import SpacesPopup from './SpacesPopup.svelte'
|
import SpacesPopup from './SpacesPopup.svelte'
|
||||||
|
|
||||||
export let _class: Ref<Class<Space>>
|
export let _class: Ref<Class<Space>>
|
||||||
@ -73,19 +73,24 @@
|
|||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const mgr = getFocusManager()
|
const mgr = getFocusManager()
|
||||||
async function updateSelected (_value: Ref<Space> | undefined, spaceQuery: DocumentQuery<Space> | undefined) {
|
const updateSelected = reduceCalls(async function (
|
||||||
selected = _value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: _value }) : undefined
|
_value: Ref<Space> | undefined,
|
||||||
|
spaceQuery: DocumentQuery<Space> | undefined
|
||||||
|
) {
|
||||||
|
let v = _value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: _value }) : undefined
|
||||||
|
selected = v
|
||||||
|
|
||||||
if (selected === undefined && autoSelect) {
|
if (selected === undefined && autoSelect) {
|
||||||
selected = (await findDefaultSpace?.()) ?? (await client.findOne(_class, { ...(spaceQuery ?? {}) }))
|
v = (await findDefaultSpace?.()) ?? (await client.findOne(_class, { ...(spaceQuery ?? {}) }))
|
||||||
|
selected = v
|
||||||
if (selected !== undefined) {
|
if (selected !== undefined) {
|
||||||
value = selected._id ?? undefined
|
value = selected._id ?? undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dispatch('object', selected)
|
dispatch('object', selected)
|
||||||
}
|
})
|
||||||
|
|
||||||
$: updateSelected(value, spaceQuery)
|
$: void updateSelected(value, spaceQuery)
|
||||||
|
|
||||||
const showSpacesPopup = (ev: MouseEvent) => {
|
const showSpacesPopup = (ev: MouseEvent) => {
|
||||||
if (readonly) {
|
if (readonly) {
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
import core, {
|
import core, {
|
||||||
TxOperations,
|
TxOperations,
|
||||||
getCurrentAccount,
|
getCurrentAccount,
|
||||||
@ -51,7 +52,6 @@ import { onDestroy } from 'svelte'
|
|||||||
import { type KeyedAttribute } from '..'
|
import { type KeyedAttribute } from '..'
|
||||||
import { OptimizeQueryMiddleware, PresentationPipelineImpl, type PresentationPipeline } from './pipeline'
|
import { OptimizeQueryMiddleware, PresentationPipelineImpl, type PresentationPipeline } from './pipeline'
|
||||||
import plugin from './plugin'
|
import plugin from './plugin'
|
||||||
import { Analytics } from '@hcengineering/analytics'
|
|
||||||
|
|
||||||
let liveQuery: LQ
|
let liveQuery: LQ
|
||||||
let client: TxOperations & MeasureClient
|
let client: TxOperations & MeasureClient
|
||||||
@ -552,3 +552,42 @@ export function decodeTokenPayload (token: string): any {
|
|||||||
export function isAdminUser (): boolean {
|
export function isAdminUser (): boolean {
|
||||||
return decodeTokenPayload(getMetadata(plugin.metadata.Token) ?? '').admin === 'true'
|
return decodeTokenPayload(getMetadata(plugin.metadata.Token) ?? '').admin === 'true'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ReduceParameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
|
||||||
|
|
||||||
|
interface NextCall {
|
||||||
|
op: () => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to skip middle update calls, optimistically if update function is called multiple times with few different parameters, only the last variant will be executed.
|
||||||
|
* The last invocation is executed after a few cycles, allowing to skip middle ones.
|
||||||
|
*
|
||||||
|
* This method can be used inside Svelte components to collapse complex update logic and handle interactions.
|
||||||
|
*/
|
||||||
|
export function reduceCalls<T extends (...args: ReduceParameters<T>) => Promise<void>> (
|
||||||
|
operation: T
|
||||||
|
): (...args: ReduceParameters<T>) => Promise<void> {
|
||||||
|
let nextCall: NextCall | undefined
|
||||||
|
let currentCall: NextCall | undefined
|
||||||
|
|
||||||
|
const next = (): void => {
|
||||||
|
currentCall = nextCall
|
||||||
|
nextCall = undefined
|
||||||
|
if (currentCall !== undefined) {
|
||||||
|
void currentCall.op()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return async function (...args: ReduceParameters<T>): Promise<void> {
|
||||||
|
const myOp = async (): Promise<void> => {
|
||||||
|
await operation(...args)
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
nextCall = { op: myOp }
|
||||||
|
await Promise.resolve()
|
||||||
|
if (currentCall === undefined) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,10 +14,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { SearchResultDoc } from '@hcengineering/core'
|
||||||
|
import presentation, { SearchResult, reduceCalls, searchFor, type SearchItem } from '@hcengineering/presentation'
|
||||||
import { Label, ListView, resizeObserver } from '@hcengineering/ui'
|
import { Label, ListView, resizeObserver } from '@hcengineering/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import presentation, { type SearchItem, SearchResult, searchFor } from '@hcengineering/presentation'
|
|
||||||
import { SearchResultDoc } from '@hcengineering/core'
|
|
||||||
|
|
||||||
export let query: string = ''
|
export let query: string = ''
|
||||||
|
|
||||||
@ -67,12 +67,12 @@
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateItems (localQuery: string): Promise<void> {
|
const updateItems = reduceCalls(async function (localQuery: string): Promise<void> {
|
||||||
const r = await searchFor('mention', localQuery)
|
const r = await searchFor('mention', localQuery)
|
||||||
if (r.query === query) {
|
if (r.query === query) {
|
||||||
items = r.items
|
items = r.items
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
$: void updateItems(query)
|
$: void updateItems(query)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -43,5 +43,6 @@
|
|||||||
close={popup.close}
|
close={popup.close}
|
||||||
{contentPanel}
|
{contentPanel}
|
||||||
overlay={popup.options.overlay}
|
overlay={popup.options.overlay}
|
||||||
|
{popup}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -16,11 +16,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { deviceOptionsStore as deviceInfo, resizeObserver, testing } from '..'
|
import { deviceOptionsStore as deviceInfo, resizeObserver, testing } from '..'
|
||||||
import { fitPopupElement } from '../popups'
|
import { CompAndProps, fitPopupElement } from '../popups'
|
||||||
import type { AnySvelteComponent, PopupAlignment, PopupOptions, PopupPositionElement, DeviceOptions } from '../types'
|
import type { AnySvelteComponent, DeviceOptions, PopupAlignment, PopupOptions, PopupPositionElement } from '../types'
|
||||||
|
|
||||||
export let is: AnySvelteComponent
|
export let is: AnySvelteComponent
|
||||||
export let props: object
|
export let props: Record<string, any>
|
||||||
export let element: PopupAlignment | undefined
|
export let element: PopupAlignment | undefined
|
||||||
export let onClose: ((result: any) => void) | undefined
|
export let onClose: ((result: any) => void) | undefined
|
||||||
export let onUpdate: ((result: any) => void) | undefined
|
export let onUpdate: ((result: any) => void) | undefined
|
||||||
@ -29,11 +29,16 @@
|
|||||||
export let top: boolean
|
export let top: boolean
|
||||||
export let close: () => void
|
export let close: () => void
|
||||||
export let contentPanel: HTMLElement | undefined
|
export let contentPanel: HTMLElement | undefined
|
||||||
|
export let popup: CompAndProps
|
||||||
|
|
||||||
// We should not update props after popup is created,
|
// We should not update props after popup is created using standard mechanism,
|
||||||
// since they could be used, and any show will update them
|
// since they could be used, and any show will update them
|
||||||
const initialProps = props
|
// So special update callback should be used.
|
||||||
|
let initialProps: Record<string, any> = props
|
||||||
|
|
||||||
|
$: popup.update = (props) => {
|
||||||
|
initialProps = Object.assign(initialProps, props)
|
||||||
|
}
|
||||||
const WINDOW_PADDING = 1
|
const WINDOW_PADDING = 1
|
||||||
|
|
||||||
interface PopupParams {
|
interface PopupParams {
|
||||||
@ -81,7 +86,7 @@
|
|||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeClose () {
|
function escapeClose (): void {
|
||||||
if (componentInstance?.canClose) {
|
if (componentInstance?.canClose) {
|
||||||
if (!componentInstance.canClose()) return
|
if (!componentInstance.canClose()) return
|
||||||
}
|
}
|
||||||
@ -109,6 +114,8 @@
|
|||||||
|
|
||||||
function handleKeydown (ev: KeyboardEvent) {
|
function handleKeydown (ev: KeyboardEvent) {
|
||||||
if (ev.key === 'Escape' && is && top) {
|
if (ev.key === 'Escape' && is && top) {
|
||||||
|
ev.preventDefault()
|
||||||
|
ev.stopPropagation()
|
||||||
escapeClose()
|
escapeClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -268,6 +275,7 @@
|
|||||||
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div
|
<div
|
||||||
|
id={popup.options.refId}
|
||||||
class="popup {testing ? 'endShow' : showing === undefined ? 'endShow' : !showing ? 'preShow' : 'startShow'}"
|
class="popup {testing ? 'endShow' : showing === undefined ? 'endShow' : !showing ? 'preShow' : 'startShow'}"
|
||||||
class:testing
|
class:testing
|
||||||
class:anim={(element === 'float' || element === 'centered') && !testing && !drag}
|
class:anim={(element === 'float' || element === 'centered') && !testing && !drag}
|
||||||
|
@ -22,16 +22,19 @@ export interface CompAndProps {
|
|||||||
onClose?: (result: any) => void
|
onClose?: (result: any) => void
|
||||||
onUpdate?: (result: any) => void
|
onUpdate?: (result: any) => void
|
||||||
close: () => void
|
close: () => void
|
||||||
|
update?: (props: Record<string, any>) => void
|
||||||
options: {
|
options: {
|
||||||
category: string
|
category: string
|
||||||
overlay: boolean
|
overlay: boolean
|
||||||
fixed?: boolean
|
fixed?: boolean
|
||||||
|
refId?: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PopupResult {
|
export interface PopupResult {
|
||||||
id: string
|
id: string
|
||||||
close: () => void
|
close: () => void
|
||||||
|
update: (props: Record<string, any>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const popupstore = writable<CompAndProps[]>([])
|
export const popupstore = writable<CompAndProps[]>([])
|
||||||
@ -40,7 +43,7 @@ export function updatePopup (id: string, props: Partial<CompAndProps>): void {
|
|||||||
popupstore.update((popups) => {
|
popupstore.update((popups) => {
|
||||||
const popupIndex = popups.findIndex((p) => p.id === id)
|
const popupIndex = popups.findIndex((p) => p.id === id)
|
||||||
if (popupIndex !== -1) {
|
if (popupIndex !== -1) {
|
||||||
popups[popupIndex] = { ...popups[popupIndex], ...props }
|
popups[popupIndex].update?.(props)
|
||||||
}
|
}
|
||||||
return popups
|
return popups
|
||||||
})
|
})
|
||||||
@ -63,7 +66,11 @@ export function showPopup (
|
|||||||
category: string
|
category: string
|
||||||
overlay: boolean
|
overlay: boolean
|
||||||
fixed?: boolean
|
fixed?: boolean
|
||||||
} = { category: 'popup', overlay: true }
|
refId?: string
|
||||||
|
} = {
|
||||||
|
category: 'popup',
|
||||||
|
overlay: true
|
||||||
|
}
|
||||||
): PopupResult {
|
): PopupResult {
|
||||||
const id = `${popupId++}`
|
const id = `${popupId++}`
|
||||||
const closePopupOp = (): void => {
|
const closePopupOp = (): void => {
|
||||||
@ -90,7 +97,10 @@ export function showPopup (
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
close: closePopupOp
|
close: closePopupOp,
|
||||||
|
update: (props) => {
|
||||||
|
updatePopup(id, props)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: updateFilterActions(
|
$: void updateFilterActions(
|
||||||
messages,
|
messages,
|
||||||
filters,
|
filters,
|
||||||
selectedFiltersRefs,
|
selectedFiltersRefs,
|
||||||
|
@ -124,12 +124,12 @@
|
|||||||
|
|
||||||
const q = createQuery()
|
const q = createQuery()
|
||||||
|
|
||||||
async function update (
|
function update (
|
||||||
_class: Ref<Class<Event>>,
|
_class: Ref<Class<Event>>,
|
||||||
query: DocumentQuery<Event> | undefined,
|
query: DocumentQuery<Event> | undefined,
|
||||||
calendars: Calendar[],
|
calendars: Calendar[],
|
||||||
options?: FindOptions<Event>
|
options?: FindOptions<Event>
|
||||||
) {
|
): void {
|
||||||
q.query<Event>(
|
q.query<Event>(
|
||||||
_class,
|
_class,
|
||||||
query ?? { space: { $in: calendars.map((p) => p._id) } },
|
query ?? { space: { $in: calendars.map((p) => p._id) } },
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
$: updateResultQuery(search)
|
$: updateResultQuery(search)
|
||||||
|
|
||||||
function showCreateDialog () {
|
function showCreateDialog (): void {
|
||||||
if (createComponent === undefined) {
|
if (createComponent === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
_class: Ref<Class<ActivityMessage>>,
|
_class: Ref<Class<ActivityMessage>>,
|
||||||
lastViewedTimestamp?: Timestamp,
|
lastViewedTimestamp?: Timestamp,
|
||||||
selectedMessageId?: Ref<ActivityMessage>
|
selectedMessageId?: Ref<ActivityMessage>
|
||||||
) {
|
): void {
|
||||||
if (dataProvider === undefined) {
|
if (dataProvider === undefined) {
|
||||||
// For now loading all messages for documents with activity. Need to correct handle aggregation with pagination.
|
// For now loading all messages for documents with activity. Need to correct handle aggregation with pagination.
|
||||||
// Perhaps we should load all activity messages once, and keep loading in chunks only for ChatMessages then merge them correctly with activity messages
|
// Perhaps we should load all activity messages once, and keep loading in chunks only for ChatMessages then merge them correctly with activity messages
|
||||||
|
@ -41,13 +41,13 @@
|
|||||||
let title: string | undefined = undefined
|
let title: string | undefined = undefined
|
||||||
let description: string | undefined = undefined
|
let description: string | undefined = undefined
|
||||||
|
|
||||||
$: updateDescription(_id, _class, object)
|
$: void updateDescription(_id, _class, object)
|
||||||
|
|
||||||
$: getChannelName(_id, _class, object).then((res) => {
|
$: void getChannelName(_id, _class, object).then((res) => {
|
||||||
title = res
|
title = res
|
||||||
})
|
})
|
||||||
|
|
||||||
async function updateDescription (_id: Ref<Doc>, _class: Ref<Class<Doc>>, object?: Doc) {
|
async function updateDescription (_id: Ref<Doc>, _class: Ref<Class<Doc>>, object?: Doc): Promise<void> {
|
||||||
if (hierarchy.isDerived(_class, chunter.class.DirectMessage)) {
|
if (hierarchy.isDerived(_class, chunter.class.DirectMessage)) {
|
||||||
description = undefined
|
description = undefined
|
||||||
} else if (hierarchy.isDerived(_class, chunter.class.Channel)) {
|
} else if (hierarchy.isDerived(_class, chunter.class.Channel)) {
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
$: updatePersons(ids)
|
$: updatePersons(ids)
|
||||||
|
|
||||||
function updatePersons (ids: Ref<Person>[]) {
|
function updatePersons (ids: Ref<Person>[]): void {
|
||||||
persons = ids.map((_id) => $personByIdStore.get(_id)).filter((person): person is Person => !!person)
|
persons = ids.map((_id) => $personByIdStore.get(_id)).filter((person): person is Person => !!person)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -13,11 +13,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Person } from '@hcengineering/contact'
|
|
||||||
import { personByIdStore, Avatar } from '@hcengineering/contact-resources'
|
|
||||||
import { Doc, IdMap, Ref, WithLookup } from '@hcengineering/core'
|
|
||||||
import { getLocation, Label, navigate, TimeSince } from '@hcengineering/ui'
|
|
||||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||||
|
import { Person } from '@hcengineering/contact'
|
||||||
|
import { Avatar, personByIdStore } from '@hcengineering/contact-resources'
|
||||||
|
import { Doc, IdMap, Ref, WithLookup } from '@hcengineering/core'
|
||||||
import notification, {
|
import notification, {
|
||||||
ActivityInboxNotification,
|
ActivityInboxNotification,
|
||||||
DocNotifyContext,
|
DocNotifyContext,
|
||||||
@ -25,6 +24,7 @@
|
|||||||
InboxNotificationsClient
|
InboxNotificationsClient
|
||||||
} from '@hcengineering/notification'
|
} from '@hcengineering/notification'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
|
import { Label, TimeSince, getLocation, navigate } from '@hcengineering/ui'
|
||||||
import { get } from 'svelte/store'
|
import { get } from 'svelte/store'
|
||||||
|
|
||||||
import { buildThreadLink } from '../utils'
|
import { buildThreadLink } from '../utils'
|
||||||
@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
let inboxClient: InboxNotificationsClient | undefined = undefined
|
let inboxClient: InboxNotificationsClient | undefined = undefined
|
||||||
|
|
||||||
getResource(notification.function.GetInboxNotificationsClient).then((getClientFn) => {
|
void getResource(notification.function.GetInboxNotificationsClient).then((getClientFn) => {
|
||||||
inboxClient = getClientFn()
|
inboxClient = getClientFn()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
$: disabledRemoveFor = currentAccount._id !== object?.createdBy && creatorPersonRef ? [creatorPersonRef] : []
|
$: disabledRemoveFor = currentAccount._id !== object?.createdBy && creatorPersonRef ? [creatorPersonRef] : []
|
||||||
$: updateMembers(object)
|
$: updateMembers(object)
|
||||||
|
|
||||||
function updateMembers (object: Channel | undefined) {
|
function updateMembers (object: Channel | undefined): void {
|
||||||
if (object === undefined) {
|
if (object === undefined) {
|
||||||
members = new Set()
|
members = new Set()
|
||||||
return
|
return
|
||||||
@ -58,7 +58,7 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function changeMembers (personRefs: Ref<Person>[], object?: Channel) {
|
async function changeMembers (personRefs: Ref<Person>[], object?: Channel): Promise<void> {
|
||||||
if (object === undefined) {
|
if (object === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@
|
|||||||
await Promise.all([leaveChannel(object, toLeave), joinChannel(object, toJoin)])
|
await Promise.all([leaveChannel(object, toLeave), joinChannel(object, toJoin)])
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeMember (ev: CustomEvent) {
|
async function removeMember (ev: CustomEvent): Promise<void> {
|
||||||
if (object === undefined) {
|
if (object === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -97,7 +97,7 @@
|
|||||||
await leaveChannel(object, accounts)
|
await leaveChannel(object, accounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSelectUsersPopup () {
|
function openSelectUsersPopup (): void {
|
||||||
showPopup(
|
showPopup(
|
||||||
SelectUsersPopup,
|
SelectUsersPopup,
|
||||||
{
|
{
|
||||||
@ -108,7 +108,7 @@
|
|||||||
'top',
|
'top',
|
||||||
(result?: Ref<Person>[]) => {
|
(result?: Ref<Person>[]) => {
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
changeMembers(result, object)
|
void changeMembers(result, object)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
import contact, { Contact, Employee, Person, getName } from '@hcengineering/contact'
|
import contact, { Contact, Employee, Person, getName } from '@hcengineering/contact'
|
||||||
import { Class, DocumentQuery, FindOptions, Ref } from '@hcengineering/core'
|
import { Class, DocumentQuery, FindOptions, Ref } from '@hcengineering/core'
|
||||||
import { IntlString, getEmbeddedLabel } from '@hcengineering/platform'
|
import { IntlString, getEmbeddedLabel } from '@hcengineering/platform'
|
||||||
import presentation, { getClient } from '@hcengineering/presentation'
|
import presentation, { getClient, reduceCalls } from '@hcengineering/presentation'
|
||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
Button,
|
Button,
|
||||||
@ -76,13 +76,13 @@
|
|||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
async function updateSelected (value: Ref<Person> | null | undefined) {
|
const updateSelected = reduceCalls(async function (value: Ref<Person> | null | undefined) {
|
||||||
selected = value
|
selected = value
|
||||||
? $personByIdStore.get(value) ?? (await client.findOne(contact.class.Person, { _id: value }))
|
? $personByIdStore.get(value) ?? (await client.findOne(contact.class.Person, { _id: value }))
|
||||||
: undefined
|
: undefined
|
||||||
}
|
})
|
||||||
|
|
||||||
$: updateSelected(value)
|
$: void updateSelected(value)
|
||||||
|
|
||||||
const mgr = getFocusManager()
|
const mgr = getFocusManager()
|
||||||
|
|
||||||
|
@ -13,7 +13,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
|
import contact, { AvatarProvider } from '@hcengineering/contact'
|
||||||
import { Client, Ref } from '@hcengineering/core'
|
import { Client, Ref } from '@hcengineering/core'
|
||||||
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
|
||||||
const providers = new Map<string, AvatarProvider | null>()
|
const providers = new Map<string, AvatarProvider | null>()
|
||||||
|
|
||||||
@ -29,15 +31,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import contact, {
|
import { AvatarType, getAvatarProviderId, getFirstName, getLastName } from '@hcengineering/contact'
|
||||||
AvatarProvider,
|
|
||||||
AvatarType,
|
|
||||||
getFirstName,
|
|
||||||
getLastName,
|
|
||||||
getAvatarProviderId
|
|
||||||
} from '@hcengineering/contact'
|
|
||||||
import { Asset, getMetadata, getResource } from '@hcengineering/platform'
|
import { Asset, getMetadata, getResource } from '@hcengineering/platform'
|
||||||
import { getBlobURL, getClient } from '@hcengineering/presentation'
|
import { getBlobURL, reduceCalls } from '@hcengineering/presentation'
|
||||||
import {
|
import {
|
||||||
AnySvelteComponent,
|
AnySvelteComponent,
|
||||||
ColorDefinition,
|
ColorDefinition,
|
||||||
@ -46,11 +42,11 @@
|
|||||||
getPlatformAvatarColorByName,
|
getPlatformAvatarColorByName,
|
||||||
getPlatformAvatarColorForTextDef,
|
getPlatformAvatarColorForTextDef,
|
||||||
getPlatformColor,
|
getPlatformColor,
|
||||||
themeStore,
|
resizeObserver,
|
||||||
resizeObserver
|
themeStore
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import AvatarIcon from './icons/Avatar.svelte'
|
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
|
import AvatarIcon from './icons/Avatar.svelte'
|
||||||
|
|
||||||
export let avatar: string | null | undefined = undefined
|
export let avatar: string | null | undefined = undefined
|
||||||
export let name: string | null | undefined = undefined
|
export let name: string | null | undefined = undefined
|
||||||
@ -89,12 +85,16 @@
|
|||||||
return lastFirst ? lname + fname : fname + lname
|
return lastFirst ? lname + fname : fname + lname
|
||||||
}
|
}
|
||||||
|
|
||||||
async function update (size: IconSize, avatar?: string | null, direct?: Blob, name?: string | null) {
|
const update = reduceCalls(async function (
|
||||||
|
size: IconSize,
|
||||||
|
avatar?: string | null,
|
||||||
|
direct?: Blob,
|
||||||
|
name?: string | null
|
||||||
|
) {
|
||||||
if (direct !== undefined) {
|
if (direct !== undefined) {
|
||||||
getBlobURL(direct).then((blobURL) => {
|
const blobURL = await getBlobURL(direct)
|
||||||
url = [blobURL]
|
url = [blobURL]
|
||||||
avatarProvider = undefined
|
avatarProvider = undefined
|
||||||
})
|
|
||||||
} else if (avatar) {
|
} else if (avatar) {
|
||||||
const avatarProviderId = getAvatarProviderId(avatar)
|
const avatarProviderId = getAvatarProviderId(avatar)
|
||||||
avatarProvider = avatarProviderId && (await getProvider(getClient(), avatarProviderId))
|
avatarProvider = avatarProviderId && (await getProvider(getClient(), avatarProviderId))
|
||||||
@ -116,8 +116,8 @@
|
|||||||
url = undefined
|
url = undefined
|
||||||
avatarProvider = undefined
|
avatarProvider = undefined
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
$: update(size, avatar, direct, name)
|
$: void update(size, avatar, direct, name)
|
||||||
|
|
||||||
$: srcset = url?.slice(1)?.join(', ')
|
$: srcset = url?.slice(1)?.join(', ')
|
||||||
|
|
||||||
|
@ -74,11 +74,11 @@
|
|||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
async function updateSelected (value: Ref<Contact> | null | undefined) {
|
async function updateSelected (value: Ref<Contact> | null | undefined): Promise<void> {
|
||||||
selected = value ? await client.findOne(_previewClass, { _id: value }) : undefined
|
selected = value ? await client.findOne(_previewClass, { _id: value }) : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
$: updateSelected(value)
|
$: void updateSelected(value)
|
||||||
|
|
||||||
const mgr = getFocusManager()
|
const mgr = getFocusManager()
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@
|
|||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<Button
|
<Button
|
||||||
id={'new-document'}
|
id={'new-teamspace'}
|
||||||
icon={IconAdd}
|
icon={IconAdd}
|
||||||
label={document.string.CreateTeamspace}
|
label={document.string.CreateTeamspace}
|
||||||
justify={'left'}
|
justify={'left'}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Person } from '@hcengineering/contact'
|
import { Person } from '@hcengineering/contact'
|
||||||
import { AssigneePopup, EmployeePresenter } from '@hcengineering/contact-resources'
|
import { AssigneePopup, EmployeePresenter } from '@hcengineering/contact-resources'
|
||||||
import { Doc, Ref, SortingOrder } from '@hcengineering/core'
|
import { Doc, Ref, SortingOrder, generateId } from '@hcengineering/core'
|
||||||
import { MessageBox, createQuery, getClient } from '@hcengineering/presentation'
|
import { MessageBox, createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { NodeViewContent, NodeViewProps, NodeViewWrapper } from '@hcengineering/text-editor'
|
import { NodeViewContent, NodeViewProps, NodeViewWrapper } from '@hcengineering/text-editor'
|
||||||
import { CheckBox, getEventPositionElement, showPopup } from '@hcengineering/ui'
|
import { CheckBox, getEventPositionElement, showPopup } from '@hcengineering/ui'
|
||||||
@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
const title = node.textBetween(0, node.content.size, undefined, ' ')
|
const title = node.textBetween(0, node.content.size, undefined, ' ')
|
||||||
|
|
||||||
const ops = client.apply('todo')
|
const ops = client.apply('todo' + generateId())
|
||||||
|
|
||||||
if (todo !== undefined) {
|
if (todo !== undefined) {
|
||||||
await ops.remove(todo)
|
await ops.remove(todo)
|
||||||
|
@ -42,9 +42,20 @@
|
|||||||
|
|
||||||
async function click (evt: MouseEvent): Promise<void> {
|
async function click (evt: MouseEvent): Promise<void> {
|
||||||
pressed = true
|
pressed = true
|
||||||
showPopup(TagsEditorPopup, { object, targetClass }, getEventPopupPositionElement(evt), () => {
|
showPopup(
|
||||||
|
TagsEditorPopup,
|
||||||
|
{ object, targetClass },
|
||||||
|
getEventPopupPositionElement(evt),
|
||||||
|
() => {
|
||||||
pressed = false
|
pressed = false
|
||||||
})
|
},
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
refId: 'TagsPopup',
|
||||||
|
category: 'popup',
|
||||||
|
overlay: true
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeTag (tag: TagReference): Promise<void> {
|
async function removeTag (tag: TagReference): Promise<void> {
|
||||||
|
@ -34,10 +34,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function click (evt: MouseEvent) {
|
function click (evt: MouseEvent) {
|
||||||
showPopup(DraftTagsPopup, { targetClass, tags }, getEventPopupPositionElement(evt), undefined, (res) => {
|
showPopup(
|
||||||
|
DraftTagsPopup,
|
||||||
|
{ targetClass, tags },
|
||||||
|
getEventPopupPositionElement(evt),
|
||||||
|
undefined,
|
||||||
|
(res) => {
|
||||||
tags = res
|
tags = res
|
||||||
dispatch('change', tags)
|
dispatch('change', tags)
|
||||||
})
|
},
|
||||||
|
{
|
||||||
|
refId: 'TagsPopup',
|
||||||
|
category: 'popup',
|
||||||
|
overlay: true
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -25,18 +25,19 @@
|
|||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
async function addRef ({ title, color, _id: tag }: TagElement): Promise<void> {
|
async function addRef ({ title, color, _id: tag }: TagElement): Promise<void> {
|
||||||
tags.push({
|
tags = [
|
||||||
|
...tags,
|
||||||
|
{
|
||||||
tag,
|
tag,
|
||||||
title,
|
title,
|
||||||
color
|
color
|
||||||
})
|
}
|
||||||
tags = tags
|
]
|
||||||
dispatch('update', tags)
|
dispatch('update', tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeTag (tag: TagElement): Promise<void> {
|
async function removeTag (tag: TagElement): Promise<void> {
|
||||||
tags = tags.filter((t) => t.tag !== tag._id)
|
tags = tags.filter((t) => t.tag !== tag._id)
|
||||||
tags = tags
|
|
||||||
dispatch('update', tags)
|
dispatch('update', tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function tagsHandler (evt: MouseEvent): Promise<void> {
|
async function tagsHandler (evt: MouseEvent): Promise<void> {
|
||||||
showPopup(TagsEditorPopup, { object }, getEventPopupPositionElement(evt))
|
showPopup(TagsEditorPopup, { object }, getEventPopupPositionElement(evt), undefined, undefined, {
|
||||||
|
refId: 'TagsPopup',
|
||||||
|
category: 'popup',
|
||||||
|
overlay: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let allWidth: number
|
let allWidth: number
|
||||||
|
@ -24,7 +24,11 @@
|
|||||||
})
|
})
|
||||||
async function tagsHandler (evt: MouseEvent): Promise<void> {
|
async function tagsHandler (evt: MouseEvent): Promise<void> {
|
||||||
if (readonly) return
|
if (readonly) return
|
||||||
showPopup(TagsEditorPopup, { object, targetClass }, getEventPopupPositionElement(evt))
|
showPopup(TagsEditorPopup, { object, targetClass }, getEventPopupPositionElement(evt), undefined, undefined, {
|
||||||
|
refId: 'TagsPopup',
|
||||||
|
category: 'popup',
|
||||||
|
overlay: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
async function removeTag (tag: TagReference): Promise<void> {
|
async function removeTag (tag: TagReference): Promise<void> {
|
||||||
if (tag !== undefined) await client.remove(tag)
|
if (tag !== undefined) await client.remove(tag)
|
||||||
|
@ -73,6 +73,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
refId: 'TagsPopup',
|
||||||
|
category: 'popup',
|
||||||
|
overlay: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,11 @@
|
|||||||
if (result.action === 'add') addRef(result.tag)
|
if (result.action === 'add') addRef(result.tag)
|
||||||
else if (result.action === 'remove') removeTag(items.filter((it) => it.tag === result.tag._id)[0]._id)
|
else if (result.action === 'remove') removeTag(items.filter((it) => it.tag === result.tag._id)[0]._id)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
refId: 'TagsPopup',
|
||||||
|
category: 'popup',
|
||||||
|
overlay: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||||
import type { IntlString } from '@hcengineering/platform'
|
import type { IntlString } from '@hcengineering/platform'
|
||||||
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { findTagCategory, TagCategory, TagElement } from '@hcengineering/tags'
|
import { TagCategory, TagElement, findTagCategory } from '@hcengineering/tags'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
EditWithIcon,
|
EditWithIcon,
|
||||||
@ -33,10 +33,10 @@
|
|||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import tags from '../plugin'
|
import tags from '../plugin'
|
||||||
|
import { createTagElement } from '../utils'
|
||||||
import CreateTagElement from './CreateTagElement.svelte'
|
import CreateTagElement from './CreateTagElement.svelte'
|
||||||
import IconView from './icons/View.svelte'
|
import IconView from './icons/View.svelte'
|
||||||
import IconViewHide from './icons/ViewHide.svelte'
|
import IconViewHide from './icons/ViewHide.svelte'
|
||||||
import { createTagElement } from '../utils'
|
|
||||||
|
|
||||||
export let newElements: TagElement[] = []
|
export let newElements: TagElement[] = []
|
||||||
export let targetClass: Ref<Class<Doc>>
|
export let targetClass: Ref<Class<Doc>>
|
||||||
@ -69,15 +69,12 @@
|
|||||||
objects = newElements.concat(result)
|
objects = newElements.concat(result)
|
||||||
})
|
})
|
||||||
|
|
||||||
async function onCreateTagElement (res: any): Promise<void> {
|
async function onCreateTagElement (res: Ref<TagElement> | undefined | null): Promise<void> {
|
||||||
if (res === null) return
|
if (res == null) return
|
||||||
setTimeout(() => {
|
const tag = await getClient().findOne(tags.class.TagElement, { _id: res })
|
||||||
const tag = objects.findLast((e) => e._id === res)
|
|
||||||
if (tag === undefined) return
|
|
||||||
selected = [...selected, tag._id]
|
|
||||||
dispatch('update', { action: 'add', tag })
|
dispatch('update', { action: 'add', tag })
|
||||||
|
selected = [...selected, res]
|
||||||
inProcess = false
|
inProcess = false
|
||||||
}, 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTagElementPopup (): Promise<void> {
|
async function createTagElementPopup (): Promise<void> {
|
||||||
|
@ -1,22 +1,14 @@
|
|||||||
// Copyright © 2022 Hardcore Engineering Inc.
|
// Copyright © 2022 Hardcore Engineering Inc.
|
||||||
|
|
||||||
import {
|
import { type Class, type Data, type Doc, type DocumentQuery, type FindResult, type Ref } from '@hcengineering/core'
|
||||||
type Class,
|
|
||||||
type Data,
|
|
||||||
type Doc,
|
|
||||||
type DocumentQuery,
|
|
||||||
type FindResult,
|
|
||||||
generateId,
|
|
||||||
type Ref
|
|
||||||
} from '@hcengineering/core'
|
|
||||||
import { type Asset } from '@hcengineering/platform'
|
import { type Asset } from '@hcengineering/platform'
|
||||||
import { type TagElement, type InitialKnowledge, type TagReference, type TagCategory } from '@hcengineering/tags'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
import { type InitialKnowledge, type TagCategory, type TagElement, type TagReference } from '@hcengineering/tags'
|
||||||
import { type ColorDefinition, getColorNumberByText } from '@hcengineering/ui'
|
import { type ColorDefinition, getColorNumberByText } from '@hcengineering/ui'
|
||||||
import { type Filter } from '@hcengineering/view'
|
import { type Filter } from '@hcengineering/view'
|
||||||
import { FilterQuery } from '@hcengineering/view-resources'
|
import { FilterQuery } from '@hcengineering/view-resources'
|
||||||
import tags from './plugin'
|
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import tags from './plugin'
|
||||||
|
|
||||||
export function getTagStyle (color: ColorDefinition, selected = false): string {
|
export function getTagStyle (color: ColorDefinition, selected = false): string {
|
||||||
return `
|
return `
|
||||||
@ -79,7 +71,7 @@ export async function createTagElement (
|
|||||||
category?: Ref<TagCategory> | null,
|
category?: Ref<TagCategory> | null,
|
||||||
description?: string | null,
|
description?: string | null,
|
||||||
color?: number | null
|
color?: number | null
|
||||||
): Promise<any> {
|
): Promise<Ref<TagElement>> {
|
||||||
const tagElement: Data<TagElement> = {
|
const tagElement: Data<TagElement> = {
|
||||||
title,
|
title,
|
||||||
description: description ?? '',
|
description: description ?? '',
|
||||||
@ -89,6 +81,5 @@ export async function createTagElement (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const tagElementId = generateId()
|
return await client.createDoc<TagElement>(tags.class.TagElement, tags.space.Tags, tagElement)
|
||||||
return await client.createDoc(tags.class.TagElement, tags.space.Tags, tagElement, tagElementId)
|
|
||||||
}
|
}
|
||||||
|
@ -72,8 +72,15 @@
|
|||||||
let preference: ViewletPreference | undefined
|
let preference: ViewletPreference | undefined
|
||||||
|
|
||||||
let documentIds: Ref<Task>[] = []
|
let documentIds: Ref<Task>[] = []
|
||||||
function updateResultQuery (search: string, documentIds: Ref<Task>[], doneStates: Status[]): void {
|
function updateResultQuery (
|
||||||
|
search: string,
|
||||||
|
documentIds: Ref<Task>[],
|
||||||
|
doneStates: Status[],
|
||||||
|
mode: string | undefined
|
||||||
|
): void {
|
||||||
|
if (mode === 'assigned') {
|
||||||
resultQuery.status = { $nin: doneStates.map((it) => it._id) }
|
resultQuery.status = { $nin: doneStates.map((it) => it._id) }
|
||||||
|
}
|
||||||
if (documentIds.length > 0) {
|
if (documentIds.length > 0) {
|
||||||
resultQuery._id = { $in: documentIds }
|
resultQuery._id = { $in: documentIds }
|
||||||
}
|
}
|
||||||
@ -123,7 +130,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: updateResultQuery(search, documentIds, doneStates)
|
$: updateResultQuery(search, documentIds, doneStates, mode)
|
||||||
|
|
||||||
let viewlet: Viewlet | undefined
|
let viewlet: Viewlet | undefined
|
||||||
|
|
||||||
@ -155,7 +162,7 @@
|
|||||||
<SearchEdit
|
<SearchEdit
|
||||||
bind:value={search}
|
bind:value={search}
|
||||||
on:change={() => {
|
on:change={() => {
|
||||||
updateResultQuery(search, documentIds, doneStates)
|
updateResultQuery(search, documentIds, doneStates, mode)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div class="buttons-divider" />
|
<div class="buttons-divider" />
|
||||||
|
@ -83,7 +83,7 @@
|
|||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
async function updateQuery (query: DocumentQuery<Task>, selectedDoneStates: Set<Ref<Status>>): Promise<void> {
|
function updateQuery (query: DocumentQuery<Task>, selectedDoneStates: Set<Ref<Status>>): void {
|
||||||
resConfig = updateConfig(config)
|
resConfig = updateConfig(config)
|
||||||
const result = client.getHierarchy().clone(query)
|
const result = client.getHierarchy().clone(query)
|
||||||
if (state) {
|
if (state) {
|
||||||
@ -164,7 +164,14 @@
|
|||||||
<TabList items={itemsDS} bind:selected={selectedDS} multiselect on:select={handleDoneSelect} />
|
<TabList items={itemsDS} bind:selected={selectedDS} multiselect on:select={handleDoneSelect} />
|
||||||
</ScrollerBar>
|
</ScrollerBar>
|
||||||
{:else}
|
{:else}
|
||||||
<StatesBar bind:state {space} gap={'none'} on:change={() => updateQuery(query, selectedDoneStates)} />
|
<StatesBar
|
||||||
|
bind:state
|
||||||
|
{space}
|
||||||
|
gap={'none'}
|
||||||
|
on:change={() => {
|
||||||
|
updateQuery(query, selectedDoneStates)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="statustableview-container">
|
<div class="statustableview-container">
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import core, { IdMap, Ref, Status, StatusCategory } from '@hcengineering/core'
|
import core, { IdMap, Ref, Status, StatusCategory } from '@hcengineering/core'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient, reduceCalls } from '@hcengineering/presentation'
|
||||||
import task, { Project, ProjectType } from '@hcengineering/task'
|
import task, { Project, ProjectType } from '@hcengineering/task'
|
||||||
import {
|
import {
|
||||||
ColorDefinition,
|
ColorDefinition,
|
||||||
@ -23,8 +23,8 @@
|
|||||||
getPlatformColorDef,
|
getPlatformColorDef,
|
||||||
themeStore
|
themeStore
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { typeStore } from '../..'
|
|
||||||
import { createEventDispatcher, onMount } from 'svelte'
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
|
import { typeStore } from '../..'
|
||||||
|
|
||||||
export let value: Status | undefined
|
export let value: Status | undefined
|
||||||
export let shouldShowTooltip: boolean = false
|
export let shouldShowTooltip: boolean = false
|
||||||
@ -36,8 +36,16 @@
|
|||||||
let category: StatusCategory | undefined
|
let category: StatusCategory | undefined
|
||||||
let type: ProjectType | undefined = undefined
|
let type: ProjectType | undefined = undefined
|
||||||
|
|
||||||
$: updateCategory(value)
|
const update = reduceCalls(async function (
|
||||||
$: getType(space, $typeStore)
|
value: Status | undefined,
|
||||||
|
space: Ref<Project>,
|
||||||
|
typeStore: IdMap<ProjectType>
|
||||||
|
) {
|
||||||
|
await updateCategory(value)
|
||||||
|
await getType(space, typeStore)
|
||||||
|
})
|
||||||
|
|
||||||
|
$: void update(value, space, $typeStore)
|
||||||
|
|
||||||
$: viewState = getViewState(type, value)
|
$: viewState = getViewState(type, value)
|
||||||
|
|
||||||
@ -56,7 +64,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateCategory (value: Status | undefined) {
|
async function updateCategory (value: Status | undefined): Promise<void> {
|
||||||
if (value === undefined) return
|
if (value === undefined) return
|
||||||
category = await client.findOne(core.class.StatusCategory, { _id: value.category })
|
category = await client.findOne(core.class.StatusCategory, { _id: value.category })
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import core, { IdMap, Ref, Status, StatusCategory } from '@hcengineering/core'
|
import core, { IdMap, Ref, Status, StatusCategory } from '@hcengineering/core'
|
||||||
import { Asset } from '@hcengineering/platform'
|
import { Asset } from '@hcengineering/platform'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient, reduceCalls } from '@hcengineering/presentation'
|
||||||
import task, { Project, ProjectType, TaskType } from '@hcengineering/task'
|
import task, { Project, ProjectType, TaskType } from '@hcengineering/task'
|
||||||
import {
|
import {
|
||||||
ColorDefinition,
|
ColorDefinition,
|
||||||
@ -91,13 +91,13 @@
|
|||||||
)
|
)
|
||||||
$: void updateCategory(value)
|
$: void updateCategory(value)
|
||||||
|
|
||||||
async function updateCategory (value: Status | undefined): Promise<void> {
|
const updateCategory = reduceCalls(async function (value: Status | undefined): Promise<void> {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
category = undefined
|
category = undefined
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
category = await client.findOne(core.class.StatusCategory, { _id: value.category })
|
category = await client.findOne(core.class.StatusCategory, { _id: value.category })
|
||||||
}
|
})
|
||||||
|
|
||||||
const categoryIcons = {
|
const categoryIcons = {
|
||||||
[task.statusCategory.UnStarted]: IconBacklog,
|
[task.statusCategory.UnStarted]: IconBacklog,
|
||||||
|
@ -35,13 +35,17 @@
|
|||||||
const spaceQ = createQuery()
|
const spaceQ = createQuery()
|
||||||
let templates: MessageTemplate[] = []
|
let templates: MessageTemplate[] = []
|
||||||
let selected: Ref<MessageTemplate> | undefined
|
let selected: Ref<MessageTemplate> | undefined
|
||||||
let newTemplate: Data<MessageTemplate> | undefined = undefined
|
|
||||||
|
let title: string = ''
|
||||||
|
let message: string = ''
|
||||||
|
|
||||||
|
let newTemplate: boolean = false
|
||||||
|
|
||||||
query.query(templatesPlugin.class.MessageTemplate, {}, (t) => {
|
query.query(templatesPlugin.class.MessageTemplate, {}, (t) => {
|
||||||
templates = t
|
templates = t
|
||||||
if (templates.findIndex((t) => t._id === selected) === -1) {
|
if (selected !== undefined && templates.findIndex((t) => t._id === selected) === -1) {
|
||||||
selected = undefined
|
selected = undefined
|
||||||
newTemplate = undefined
|
newTemplate = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -60,28 +64,28 @@
|
|||||||
let mode = Mode.View
|
let mode = Mode.View
|
||||||
|
|
||||||
async function addTemplate (): Promise<void> {
|
async function addTemplate (): Promise<void> {
|
||||||
newTemplate = {
|
title = ''
|
||||||
title: '',
|
message = ''
|
||||||
message: ''
|
newTemplate = true
|
||||||
}
|
|
||||||
mode = Mode.Create
|
mode = Mode.Create
|
||||||
}
|
}
|
||||||
async function saveNewTemplate (): Promise<void> {
|
async function saveNewTemplate (): Promise<void> {
|
||||||
if (newTemplate === undefined) {
|
if (!newTemplate) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (mode === Mode.Create) {
|
if (mode === Mode.Create) {
|
||||||
if (newTemplate.title.trim().length > 0 && space !== undefined) {
|
if (title.trim().length > 0 && space !== undefined) {
|
||||||
const ref = await client.createDoc(templatesPlugin.class.MessageTemplate, space, newTemplate)
|
const ref = await client.createDoc(templatesPlugin.class.MessageTemplate, space, {
|
||||||
|
title,
|
||||||
|
message
|
||||||
|
})
|
||||||
selected = ref
|
selected = ref
|
||||||
}
|
}
|
||||||
} else if (selected !== undefined) {
|
} else if (selected !== undefined) {
|
||||||
await client.updateDoc(
|
await client.updateDoc(templatesPlugin.class.MessageTemplate, templatesPlugin.space.Templates, selected, {
|
||||||
templatesPlugin.class.MessageTemplate,
|
title,
|
||||||
templatesPlugin.space.Templates,
|
message
|
||||||
selected,
|
})
|
||||||
newTemplate
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
mode = Mode.View
|
mode = Mode.View
|
||||||
}
|
}
|
||||||
@ -92,7 +96,7 @@
|
|||||||
textEditor.submit()
|
textEditor.submit()
|
||||||
}
|
}
|
||||||
const updateTemplate = (evt: any) => {
|
const updateTemplate = (evt: any) => {
|
||||||
newTemplate = { title: newTemplate?.title ?? '', message: evt.detail }
|
message = evt.detail
|
||||||
}
|
}
|
||||||
|
|
||||||
function addField (ev: MouseEvent) {
|
function addField (ev: MouseEvent) {
|
||||||
@ -184,7 +188,9 @@
|
|||||||
indent
|
indent
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
selected = t._id
|
selected = t._id
|
||||||
newTemplate = { title: t.title, message: t.message }
|
title = t.title
|
||||||
|
message = t.message
|
||||||
|
newTemplate = true
|
||||||
mode = Mode.View
|
mode = Mode.View
|
||||||
}}
|
}}
|
||||||
selected={selected === t._id}
|
selected={selected === t._id}
|
||||||
@ -223,18 +229,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="text-lg caption-color">
|
<div class="text-lg caption-color">
|
||||||
{#if mode !== Mode.View}
|
{#if mode !== Mode.View}
|
||||||
<EditBox bind:value={newTemplate.title} placeholder={templatesPlugin.string.TemplatePlaceholder} />
|
<EditBox bind:value={title} placeholder={templatesPlugin.string.TemplatePlaceholder} />
|
||||||
{:else}
|
{:else}
|
||||||
{newTemplate.title}
|
{title}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="separator" />
|
<div class="separator" />
|
||||||
{#if mode !== Mode.View}
|
{#if mode !== Mode.View}
|
||||||
<StyledTextEditor bind:content={newTemplate.message} bind:this={textEditor} on:value={updateTemplate}>
|
<StyledTextEditor bind:content={message} bind:this={textEditor} on:value={updateTemplate}>
|
||||||
<div class="flex flex-reverse flex-grow">
|
<div class="flex flex-reverse flex-grow">
|
||||||
<div class="ml-2">
|
<div class="ml-2">
|
||||||
<Button
|
<Button
|
||||||
disabled={newTemplate.title.trim().length === 0}
|
disabled={title.trim().length === 0}
|
||||||
kind={'primary'}
|
kind={'primary'}
|
||||||
label={templatesPlugin.string.SaveTemplate}
|
label={templatesPlugin.string.SaveTemplate}
|
||||||
on:click={saveNewTemplate}
|
on:click={saveNewTemplate}
|
||||||
@ -245,7 +251,7 @@
|
|||||||
label={templatesPlugin.string.Cancel}
|
label={templatesPlugin.string.Cancel}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
if (mode === Mode.Create) {
|
if (mode === Mode.Create) {
|
||||||
newTemplate = undefined
|
newTemplate = false
|
||||||
}
|
}
|
||||||
mode = Mode.View
|
mode = Mode.View
|
||||||
}}
|
}}
|
||||||
@ -256,7 +262,7 @@
|
|||||||
</StyledTextEditor>
|
</StyledTextEditor>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<MessageViewer message={newTemplate.message} />
|
<MessageViewer {message} />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-reverse">
|
<div class="flex flex-reverse">
|
||||||
<Button
|
<Button
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ActionIcon, IconAdd, showPopup, ModernEditbox } from '@hcengineering/ui'
|
import { ActionIcon, IconAdd, showPopup, ModernEditbox } from '@hcengineering/ui'
|
||||||
import { SortingOrder, getCurrentAccount } from '@hcengineering/core'
|
import { SortingOrder, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||||
import { PersonAccount } from '@hcengineering/contact'
|
import { PersonAccount } from '@hcengineering/contact'
|
||||||
import { ToDoPriority } from '@hcengineering/time'
|
import { ToDoPriority } from '@hcengineering/time'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
@ -19,7 +19,7 @@
|
|||||||
name = name.trim()
|
name = name.trim()
|
||||||
if (name.length === 0) return
|
if (name.length === 0) return
|
||||||
description = description?.trim() ?? ''
|
description = description?.trim() ?? ''
|
||||||
const ops = client.apply('todo')
|
const ops = client.apply('todo' + generateId())
|
||||||
const latestTodo = await ops.findOne(
|
const latestTodo = await ops.findOne(
|
||||||
time.class.ToDo,
|
time.class.ToDo,
|
||||||
{
|
{
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
async function saveToDo (): Promise<void> {
|
async function saveToDo (): Promise<void> {
|
||||||
loading = true
|
loading = true
|
||||||
const ops = client.apply('todo')
|
const ops = client.apply('todo-' + generateId())
|
||||||
const latestTodo = await ops.findOne(
|
const latestTodo = await ops.findOne(
|
||||||
time.class.ToDo,
|
time.class.ToDo,
|
||||||
{
|
{
|
||||||
@ -239,8 +239,8 @@
|
|||||||
<Component
|
<Component
|
||||||
is={tagsPlugin.component.DraftTagsEditor}
|
is={tagsPlugin.component.DraftTagsEditor}
|
||||||
props={{ tags, targetClass: time.class.ToDo }}
|
props={{ tags, targetClass: time.class.ToDo }}
|
||||||
on:change={(e) => {
|
on:update={(e) => {
|
||||||
tags = e.detail
|
tags = [...e.detail]
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
$: from = getFrom(currentDate)
|
$: from = getFrom(currentDate)
|
||||||
$: to = getTo(currentDate)
|
$: to = getTo(currentDate)
|
||||||
|
|
||||||
function update (calendars: Calendar[]) {
|
function update (calendars: Calendar[]): void {
|
||||||
q.query<Event>(
|
q.query<Event>(
|
||||||
calendar.class.Event,
|
calendar.class.Event,
|
||||||
{ space: { $in: calendars.map((p) => p._id) } },
|
{ space: { $in: calendars.map((p) => p._id) } },
|
||||||
|
@ -41,7 +41,8 @@
|
|||||||
MultipleDraftController,
|
MultipleDraftController,
|
||||||
SpaceSelector,
|
SpaceSelector,
|
||||||
createQuery,
|
createQuery,
|
||||||
getClient
|
getClient,
|
||||||
|
reduceCalls
|
||||||
} from '@hcengineering/presentation'
|
} from '@hcengineering/presentation'
|
||||||
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
||||||
import { TaskType, makeRank } from '@hcengineering/task'
|
import { TaskType, makeRank } from '@hcengineering/task'
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import { Component } from '@hcengineering/tracker'
|
import { Component } from '@hcengineering/tracker'
|
||||||
import type { ButtonKind, ButtonSize, LabelAndProps, SelectPopupValueType } from '@hcengineering/ui'
|
import type { ButtonKind, ButtonSize, LabelAndProps, SelectPopupValueType } from '@hcengineering/ui'
|
||||||
import { Button, ButtonShape, SelectPopup, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
import { Button, ButtonShape, SelectPopup, eventToHTMLElement, showPopup, PopupResult } from '@hcengineering/ui'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import ComponentPresenter from './ComponentPresenter.svelte'
|
import ComponentPresenter from './ComponentPresenter.svelte'
|
||||||
|
|
||||||
@ -57,12 +57,10 @@
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
$: handleSelectedComponentIdUpdated(value, rawComponents)
|
const handleSelectedComponentIdUpdated = (
|
||||||
|
|
||||||
const handleSelectedComponentIdUpdated = async (
|
|
||||||
newComponentId: Ref<Component> | null | undefined,
|
newComponentId: Ref<Component> | null | undefined,
|
||||||
components: Component[]
|
components: Component[]
|
||||||
) => {
|
): void => {
|
||||||
if (newComponentId === null || newComponentId === undefined) {
|
if (newComponentId === null || newComponentId === undefined) {
|
||||||
selectedComponent = undefined
|
selectedComponent = undefined
|
||||||
|
|
||||||
@ -72,6 +70,8 @@
|
|||||||
selectedComponent = components.find((it) => it._id === newComponentId)
|
selectedComponent = components.find((it) => it._id === newComponentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: handleSelectedComponentIdUpdated(value, rawComponents)
|
||||||
|
|
||||||
function getComponentInfo (rawComponents: Component[], sp: Component | undefined): SelectPopupValueType[] {
|
function getComponentInfo (rawComponents: Component[], sp: Component | undefined): SelectPopupValueType[] {
|
||||||
return [
|
return [
|
||||||
...(isAllowUnset
|
...(isAllowUnset
|
||||||
@ -100,17 +100,25 @@
|
|||||||
let components: SelectPopupValueType[] = []
|
let components: SelectPopupValueType[] = []
|
||||||
$: components = getComponentInfo(rawComponents, selectedComponent)
|
$: components = getComponentInfo(rawComponents, selectedComponent)
|
||||||
|
|
||||||
|
let selectPopupResult: PopupResult | undefined
|
||||||
|
|
||||||
|
// We need update SelectPopup when it active
|
||||||
|
$: selectPopupResult?.update({ value: components })
|
||||||
|
|
||||||
const handleComponentEditorOpened = async (event: MouseEvent): Promise<void> => {
|
const handleComponentEditorOpened = async (event: MouseEvent): Promise<void> => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
if (!isEditable) {
|
if (!isEditable) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
showPopup(
|
selectPopupResult = showPopup(
|
||||||
SelectPopup,
|
SelectPopup,
|
||||||
{ value: components, placeholder: popupPlaceholder, searchable: true },
|
{ value: components, placeholder: popupPlaceholder, searchable: true },
|
||||||
eventToHTMLElement(event),
|
eventToHTMLElement(event),
|
||||||
onChange
|
(result: any) => {
|
||||||
|
onChange?.(result)
|
||||||
|
selectPopupResult = undefined
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import { Issue, IssueStatus } from '@hcengineering/tracker'
|
import { Issue, IssueStatus } from '@hcengineering/tracker'
|
||||||
import { Label, ticker, Row } from '@hcengineering/ui'
|
import { Label, Row, ticker } from '@hcengineering/ui'
|
||||||
import { statusStore } from '@hcengineering/view-resources'
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import Duration from './Duration.svelte'
|
import Duration from './Duration.svelte'
|
||||||
@ -39,7 +39,7 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
let displaySt: WithTime[] = []
|
let displaySt: WithTime[] = []
|
||||||
async function updateStatus (txes: Tx[], statuses: IdMap<IssueStatus>, _: number): Promise<void> {
|
function updateStatus (txes: Tx[], statuses: IdMap<IssueStatus>, _: number): void {
|
||||||
const result: WithTime[] = []
|
const result: WithTime[] = []
|
||||||
|
|
||||||
let current: Ref<IssueStatus> | undefined
|
let current: Ref<IssueStatus> | undefined
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import { IntlString, getEmbeddedLabel, translate } from '@hcengineering/platform'
|
import { IntlString, getEmbeddedLabel, translate } from '@hcengineering/platform'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import { Milestone } from '@hcengineering/tracker'
|
import { Milestone } from '@hcengineering/tracker'
|
||||||
import type { ButtonKind, ButtonSize, LabelAndProps } from '@hcengineering/ui'
|
import type { ButtonKind, ButtonSize, LabelAndProps, PopupResult } from '@hcengineering/ui'
|
||||||
import { Button, ButtonShape, Label, SelectPopup, eventToHTMLElement, showPopup, themeStore } from '@hcengineering/ui'
|
import { Button, ButtonShape, Label, SelectPopup, eventToHTMLElement, showPopup, themeStore } from '@hcengineering/ui'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import { milestoneStatusAssets } from '../../types'
|
import { milestoneStatusAssets } from '../../types'
|
||||||
@ -96,19 +96,25 @@
|
|||||||
|
|
||||||
$: milestones = getMilestoneInfo(rawMilestones, selectedMilestone)
|
$: milestones = getMilestoneInfo(rawMilestones, selectedMilestone)
|
||||||
|
|
||||||
|
let milestonePopup: PopupResult | undefined
|
||||||
const handleMilestoneEditorOpened = async (event: MouseEvent): Promise<void> => {
|
const handleMilestoneEditorOpened = async (event: MouseEvent): Promise<void> => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
if (!isEditable) {
|
if (!isEditable) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
showPopup(
|
milestonePopup = showPopup(
|
||||||
SelectPopup,
|
SelectPopup,
|
||||||
{ value: milestones, placeholder: popupPlaceholder, searchable: true },
|
{ value: milestones, placeholder: popupPlaceholder, searchable: true },
|
||||||
eventToHTMLElement(event),
|
eventToHTMLElement(event),
|
||||||
onChange
|
(evt) => {
|
||||||
|
onChange?.(evt)
|
||||||
|
milestonePopup = undefined
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: milestonePopup?.update({ value: milestones })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isAction}
|
{#if isAction}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
import { Analytics } from '@hcengineering/analytics'
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
import core, { Doc, Hierarchy, Ref, Space, TxRemoveDoc } from '@hcengineering/core'
|
import core, { Doc, Hierarchy, Ref, Space, TxRemoveDoc } from '@hcengineering/core'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
import { addTxListener, contextStore, getClient } from '@hcengineering/presentation'
|
import { addTxListener, contextStore, getClient, reduceCalls } from '@hcengineering/presentation'
|
||||||
import { AnyComponent, Component } from '@hcengineering/ui'
|
import { AnyComponent, Component } from '@hcengineering/ui'
|
||||||
import { Action, ViewContextType } from '@hcengineering/view'
|
import { Action, ViewContextType } from '@hcengineering/view'
|
||||||
import { fly } from 'svelte/transition'
|
import { fly } from 'svelte/transition'
|
||||||
@ -237,11 +237,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let presenter: AnyComponent | undefined
|
let presenter: AnyComponent | undefined
|
||||||
async function updatePreviewPresenter (doc?: Doc): Promise<void> {
|
const updatePreviewPresenter = reduceCalls(async function (doc?: Doc): Promise<void> {
|
||||||
presenter = doc !== undefined ? await getObjectPreview(client, Hierarchy.mixinOrClass(doc)) : undefined
|
const r = doc !== undefined ? await getObjectPreview(client, Hierarchy.mixinOrClass(doc)) : undefined
|
||||||
}
|
presenter = r
|
||||||
|
})
|
||||||
|
|
||||||
$: updatePreviewPresenter($previewDocument)
|
$: void updatePreviewPresenter($previewDocument)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={handleKeys} />
|
<svelte:window on:keydown={handleKeys} />
|
||||||
|
@ -13,42 +13,55 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { WithLookup, Doc, Ref, SearchResultDoc } from '@hcengineering/core'
|
import core, {
|
||||||
|
Doc,
|
||||||
|
Ref,
|
||||||
|
SearchResultDoc,
|
||||||
|
Tx,
|
||||||
|
TxBuilder,
|
||||||
|
TxWorkspaceEvent,
|
||||||
|
WithLookup,
|
||||||
|
WorkspaceEvent,
|
||||||
|
coreId
|
||||||
|
} from '@hcengineering/core'
|
||||||
import { getResource, translate } from '@hcengineering/platform'
|
import { getResource, translate } from '@hcengineering/platform'
|
||||||
import {
|
import {
|
||||||
type SearchItem,
|
ActionContext,
|
||||||
type ObjectSearchCategory,
|
|
||||||
SearchResult,
|
SearchResult,
|
||||||
|
addTxListener,
|
||||||
createQuery,
|
createQuery,
|
||||||
getClient,
|
getClient,
|
||||||
ActionContext,
|
reduceCalls,
|
||||||
searchFor
|
removeTxListener,
|
||||||
|
searchFor,
|
||||||
|
type ObjectSearchCategory,
|
||||||
|
type SearchItem
|
||||||
} from '@hcengineering/presentation'
|
} from '@hcengineering/presentation'
|
||||||
import ui, {
|
import ui, {
|
||||||
Button,
|
Button,
|
||||||
closePopup,
|
|
||||||
Component,
|
Component,
|
||||||
Icon,
|
Icon,
|
||||||
IconArrowLeft,
|
IconArrowLeft,
|
||||||
Label,
|
Label,
|
||||||
deviceOptionsStore,
|
|
||||||
capitalizeFirstLetter,
|
|
||||||
formatKey,
|
|
||||||
themeStore,
|
|
||||||
ListView,
|
ListView,
|
||||||
resizeObserver
|
capitalizeFirstLetter,
|
||||||
|
closePopup,
|
||||||
|
deviceOptionsStore,
|
||||||
|
formatKey,
|
||||||
|
resizeObserver,
|
||||||
|
themeStore
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { Action, ActionCategory, ViewContext } from '@hcengineering/view'
|
import { Action, ActionCategory, ViewContext } from '@hcengineering/view'
|
||||||
|
import { createEventDispatcher, onMount, tick } from 'svelte'
|
||||||
import { filterActions, getSelection } from '../actions'
|
import { filterActions, getSelection } from '../actions'
|
||||||
import view from '../plugin'
|
import view from '../plugin'
|
||||||
import { focusStore, selectionStore } from '../selection'
|
import { focusStore, selectionStore } from '../selection'
|
||||||
import { openDoc } from '../utils'
|
import { openDoc } from '../utils'
|
||||||
import ObjectPresenter from './ObjectPresenter.svelte'
|
import ObjectPresenter from './ObjectPresenter.svelte'
|
||||||
import { createEventDispatcher, tick } from 'svelte'
|
|
||||||
|
|
||||||
|
import { contextStore } from '@hcengineering/presentation'
|
||||||
import ChevronDown from './icons/ChevronDown.svelte'
|
import ChevronDown from './icons/ChevronDown.svelte'
|
||||||
import ChevronUp from './icons/ChevronUp.svelte'
|
import ChevronUp from './icons/ChevronUp.svelte'
|
||||||
import { contextStore } from '@hcengineering/presentation'
|
|
||||||
|
|
||||||
export let viewContext: ViewContext | undefined = $contextStore.getLastContext()
|
export let viewContext: ViewContext | undefined = $contextStore.getLastContext()
|
||||||
|
|
||||||
@ -98,7 +111,7 @@
|
|||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
async function getSupportedActions (actions: Array<WithLookup<Action>>): Promise<void> {
|
const getSupportedActions = reduceCalls(async (actions: Array<WithLookup<Action>>): Promise<void> => {
|
||||||
const docs = getSelection($focusStore, $selectionStore)
|
const docs = getSelection($focusStore, $selectionStore)
|
||||||
let fActions: Array<WithLookup<Action>> = actions
|
let fActions: Array<WithLookup<Action>> = actions
|
||||||
|
|
||||||
@ -125,11 +138,11 @@
|
|||||||
fActions = await filterVisibleActions(fActions, docs)
|
fActions = await filterVisibleActions(fActions, docs)
|
||||||
// Sort by category.
|
// Sort by category.
|
||||||
supportedActions = fActions.sort((a, b) => a.category.localeCompare(b.category))
|
supportedActions = fActions.sort((a, b) => a.category.localeCompare(b.category))
|
||||||
}
|
})
|
||||||
|
|
||||||
$: void getSupportedActions(actions)
|
$: void getSupportedActions(actions)
|
||||||
|
|
||||||
async function filterSearchActions (actions: Array<WithLookup<Action>>, search: string): Promise<void> {
|
const filterSearchActions = reduceCalls(async (actions: Array<WithLookup<Action>>, search: string): Promise<void> => {
|
||||||
const res: Array<WithLookup<Action>> = []
|
const res: Array<WithLookup<Action>> = []
|
||||||
let preparedSearch = search.trim().toLowerCase()
|
let preparedSearch = search.trim().toLowerCase()
|
||||||
if (preparedSearch.charAt(0) === '/') {
|
if (preparedSearch.charAt(0) === '/') {
|
||||||
@ -146,7 +159,7 @@
|
|||||||
} else {
|
} else {
|
||||||
filteredActions = actions
|
filteredActions = actions
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
$: void filterSearchActions(supportedActions, search)
|
$: void filterSearchActions(supportedActions, search)
|
||||||
|
|
||||||
let selection = 0
|
let selection = 0
|
||||||
@ -241,16 +254,32 @@
|
|||||||
|
|
||||||
let items: SearchActionItem[] = []
|
let items: SearchActionItem[] = []
|
||||||
|
|
||||||
async function updateItems (query: string, filteredActions: Array<WithLookup<Action>>): Promise<void> {
|
const updateItems = reduceCalls(async (query: string, filteredActions: Array<WithLookup<Action>>): Promise<void> => {
|
||||||
let searchItems: SearchItem[] = []
|
let searchItems: SearchItem[] = []
|
||||||
if (query !== '' && query.indexOf('/') !== 0) {
|
if (query !== '' && query.indexOf('/') !== 0) {
|
||||||
searchItems = (await searchFor('spotlight', query)).items
|
searchItems = (await searchFor('spotlight', query)).items
|
||||||
}
|
}
|
||||||
items = packSearchAndActions(searchItems, filteredActions)
|
items = packSearchAndActions(searchItems, filteredActions)
|
||||||
}
|
})
|
||||||
|
|
||||||
$: void updateItems(search, filteredActions)
|
$: void updateItems(search, filteredActions)
|
||||||
|
|
||||||
|
function txListener (tx: Tx): void {
|
||||||
|
if (tx._class === core.class.TxWorkspaceEvent) {
|
||||||
|
const evt = tx as TxWorkspaceEvent
|
||||||
|
if (evt.event === WorkspaceEvent.IndexingUpdate) {
|
||||||
|
void updateItems(search, filteredActions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
addTxListener(txListener)
|
||||||
|
return () => {
|
||||||
|
removeTxListener(txListener)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
let textHTML: HTMLInputElement
|
let textHTML: HTMLInputElement
|
||||||
let phTraslate: string = ''
|
let phTraslate: string = ''
|
||||||
let autoFocus = !$deviceOptionsStore.isMobile
|
let autoFocus = !$deviceOptionsStore.isMobile
|
||||||
|
@ -25,13 +25,14 @@
|
|||||||
KeyedAttribute,
|
KeyedAttribute,
|
||||||
createQuery,
|
createQuery,
|
||||||
getClient,
|
getClient,
|
||||||
hasResource
|
hasResource,
|
||||||
|
reduceCalls
|
||||||
} from '@hcengineering/presentation'
|
} from '@hcengineering/presentation'
|
||||||
import { AnyComponent, Button, Component, IconMixin, IconMoreH } from '@hcengineering/ui'
|
import { AnyComponent, Button, Component, IconMixin, IconMoreH } from '@hcengineering/ui'
|
||||||
import view from '@hcengineering/view'
|
import view from '@hcengineering/view'
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||||
|
|
||||||
import { DocNavLink, ParentsNavigator, getDocLabel, getDocMixins, showMenu, getDocAttrsInfo } from '..'
|
import { DocNavLink, ParentsNavigator, getDocAttrsInfo, getDocLabel, getDocMixins, showMenu } from '..'
|
||||||
import { getCollectionCounter } from '../utils'
|
import { getCollectionCounter } from '../utils'
|
||||||
import DocAttributeBar from './DocAttributeBar.svelte'
|
import DocAttributeBar from './DocAttributeBar.svelte'
|
||||||
|
|
||||||
@ -68,7 +69,7 @@
|
|||||||
const query = createQuery()
|
const query = createQuery()
|
||||||
$: updateQuery(_id, _class)
|
$: updateQuery(_id, _class)
|
||||||
|
|
||||||
function updateQuery (_id: Ref<Doc>, _class: Ref<Class<Doc>>) {
|
function updateQuery (_id: Ref<Doc>, _class: Ref<Class<Doc>>): void {
|
||||||
if (_id && _class) {
|
if (_id && _class) {
|
||||||
query.query(_class, { _id }, (result) => {
|
query.query(_class, { _id }, (result) => {
|
||||||
object = result[0]
|
object = result[0]
|
||||||
@ -146,12 +147,12 @@
|
|||||||
|
|
||||||
$: editorFooter = getEditorFooter(_class, object)
|
$: editorFooter = getEditorFooter(_class, object)
|
||||||
|
|
||||||
$: void getEditorOrDefault(realObjectClass, _id)
|
const getEditorOrDefault = reduceCalls(async function (_class: Ref<Class<Doc>>, _id: Ref<Doc>): Promise<void> {
|
||||||
|
|
||||||
async function getEditorOrDefault (_class: Ref<Class<Doc>>, _id: Ref<Doc>): Promise<void> {
|
|
||||||
await updateKeys()
|
await updateKeys()
|
||||||
mainEditor = getEditor(_class)
|
mainEditor = getEditor(_class)
|
||||||
}
|
})
|
||||||
|
|
||||||
|
$: void getEditorOrDefault(realObjectClass, _id)
|
||||||
|
|
||||||
let title: string | undefined = undefined
|
let title: string | undefined = undefined
|
||||||
let rawTitle: string = ''
|
let rawTitle: string = ''
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
getObjectValue
|
getObjectValue
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import notification from '@hcengineering/notification'
|
import notification from '@hcengineering/notification'
|
||||||
import { createQuery, getClient, updateAttribute } from '@hcengineering/presentation'
|
import { createQuery, getClient, reduceCalls, updateAttribute } from '@hcengineering/presentation'
|
||||||
import ui, {
|
import ui, {
|
||||||
Button,
|
Button,
|
||||||
CheckBox,
|
CheckBox,
|
||||||
@ -117,7 +117,7 @@
|
|||||||
: { ...(options?.sort ?? {}), [sortKey]: sortOrder }
|
: { ...(options?.sort ?? {}), [sortKey]: sortOrder }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function update (
|
const update = reduceCalls(async function (
|
||||||
_class: Ref<Class<Doc>>,
|
_class: Ref<Class<Doc>>,
|
||||||
query: DocumentQuery<Doc>,
|
query: DocumentQuery<Doc>,
|
||||||
sortKey: string | string[],
|
sortKey: string | string[],
|
||||||
@ -143,8 +143,8 @@
|
|||||||
)
|
)
|
||||||
? 1
|
? 1
|
||||||
: 0
|
: 0
|
||||||
}
|
})
|
||||||
$: update(_class, query, _sortKey, sortOrder, lookup, limit, options)
|
$: void update(_class, query, _sortKey, sortOrder, lookup, limit, options)
|
||||||
|
|
||||||
$: dispatch('content', objects)
|
$: dispatch('content', objects)
|
||||||
|
|
||||||
@ -276,36 +276,31 @@
|
|||||||
let model: AttributeModel[] | undefined
|
let model: AttributeModel[] | undefined
|
||||||
let modelOptions: BuildModelOptions | undefined
|
let modelOptions: BuildModelOptions | undefined
|
||||||
|
|
||||||
$: updateModelOptions(client, _class, config, lookup)
|
const updateModelOptions = reduceCalls(async function updateModelOptions (
|
||||||
async function updateModelOptions (
|
|
||||||
client: TxOperations,
|
client: TxOperations,
|
||||||
_class: Ref<Class<Doc>>,
|
_class: Ref<Class<Doc>>,
|
||||||
config: Array<string | BuildModelKey>,
|
config: Array<string | BuildModelKey>,
|
||||||
lookup?: Lookup<Doc>
|
lookup?: Lookup<Doc>
|
||||||
) {
|
): Promise<void> {
|
||||||
const newModelOpts = { client, _class, keys: config, lookup }
|
const newModelOpts = { client, _class, keys: config, lookup }
|
||||||
if (modelOptions == null || !deepEqual(modelOptions, newModelOpts)) {
|
if (modelOptions == null || !deepEqual(modelOptions, newModelOpts)) {
|
||||||
modelOptions = newModelOpts
|
modelOptions = newModelOpts
|
||||||
await build(modelOptions)
|
await build(modelOptions)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
$: void updateModelOptions(client, _class, config, lookup)
|
||||||
|
|
||||||
let buildIndex = 0
|
async function build (modelOptions: BuildModelOptions): Promise<void> {
|
||||||
|
|
||||||
async function build (modelOptions: BuildModelOptions) {
|
|
||||||
isBuildingModel = true
|
isBuildingModel = true
|
||||||
const idx = ++buildIndex
|
|
||||||
const res = await buildModel(modelOptions)
|
const res = await buildModel(modelOptions)
|
||||||
if (buildIndex === idx) {
|
|
||||||
model = res
|
model = res
|
||||||
}
|
|
||||||
isBuildingModel = false
|
isBuildingModel = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function contextHandler (object: Doc, row: number): (ev: MouseEvent) => void {
|
function contextHandler (object: Doc, row: number): (ev: MouseEvent) => void {
|
||||||
return (ev) => {
|
return (ev) => {
|
||||||
if (!readonly) {
|
if (!readonly) {
|
||||||
showContextMenu(ev, object, row)
|
void showContextMenu(ev, object, row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Class, Doc, DocumentQuery, Ref, Space, getCurrentAccount } from '@hcengineering/core'
|
import { Class, Doc, DocumentQuery, Ref, Space, getCurrentAccount } from '@hcengineering/core'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient, reduceCalls } from '@hcengineering/presentation'
|
||||||
import { Button, IconAdd, eventToHTMLElement, getCurrentLocation, showPopup } from '@hcengineering/ui'
|
import { Button, IconAdd, eventToHTMLElement, getCurrentLocation, showPopup } from '@hcengineering/ui'
|
||||||
import { Filter, FilteredView, ViewOptions, Viewlet } from '@hcengineering/view'
|
import { Filter, FilteredView, ViewOptions, Viewlet } from '@hcengineering/view'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
@ -80,7 +80,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function makeQuery (query: DocumentQuery<Doc>, filters: Filter[]): Promise<void> {
|
const makeQuery = reduceCalls(async (query: DocumentQuery<Doc>, filters: Filter[]): Promise<void> => {
|
||||||
const newQuery = hierarchy.clone(query)
|
const newQuery = hierarchy.clone(query)
|
||||||
for (let i = 0; i < filters.length; i++) {
|
for (let i = 0; i < filters.length; i++) {
|
||||||
const filter = filters[i]
|
const filter = filters[i]
|
||||||
@ -141,7 +141,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dispatch('change', newQuery)
|
dispatch('change', newQuery)
|
||||||
}
|
})
|
||||||
|
|
||||||
$: makeQuery(query, $filterStore)
|
$: makeQuery(query, $filterStore)
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function load (_class: Ref<Class<Doc>> | undefined) {
|
function load (_class: Ref<Class<Doc>> | undefined): void {
|
||||||
const key = getFilterKey(_class)
|
const key = getFilterKey(_class)
|
||||||
const items = localStorage.getItem(key)
|
const items = localStorage.getItem(key)
|
||||||
if (items !== null) {
|
if (items !== null) {
|
||||||
@ -43,7 +43,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function save (_class: Ref<Class<Doc>> | undefined, p: Filter[]) {
|
function save (_class: Ref<Class<Doc>> | undefined, p: Filter[]): void {
|
||||||
const key = getFilterKey(_class)
|
const key = getFilterKey(_class)
|
||||||
localStorage.setItem(key, JSON.stringify(p))
|
localStorage.setItem(key, JSON.stringify(p))
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@
|
|||||||
save(_class, p)
|
save(_class, p)
|
||||||
})
|
})
|
||||||
|
|
||||||
function onChange (e: Filter | undefined) {
|
function onChange (e: Filter | undefined): void {
|
||||||
if (e !== undefined) setFilters([e])
|
if (e !== undefined) setFilters([e])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +60,7 @@
|
|||||||
_class = undefined
|
_class = undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
function add (e: MouseEvent) {
|
function add (e: MouseEvent): void {
|
||||||
const target = eventToHTMLElement(e)
|
const target = eventToHTMLElement(e)
|
||||||
showPopup(
|
showPopup(
|
||||||
FilterTypePopup,
|
FilterTypePopup,
|
||||||
|
@ -18,13 +18,13 @@
|
|||||||
Doc,
|
Doc,
|
||||||
DocumentQuery,
|
DocumentQuery,
|
||||||
FindOptions,
|
FindOptions,
|
||||||
|
RateLimiter,
|
||||||
Ref,
|
Ref,
|
||||||
Space,
|
Space,
|
||||||
RateLimiter,
|
|
||||||
mergeQueries
|
mergeQueries
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { IntlString, getResource } from '@hcengineering/platform'
|
import { IntlString, getResource } from '@hcengineering/platform'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient, reduceCalls } from '@hcengineering/presentation'
|
||||||
import { AnyComponent, AnySvelteComponent } from '@hcengineering/ui'
|
import { AnyComponent, AnySvelteComponent } from '@hcengineering/ui'
|
||||||
import { BuildModelKey, ViewOptionModel, ViewOptions, ViewQueryOption, Viewlet } from '@hcengineering/view'
|
import { BuildModelKey, ViewOptionModel, ViewOptions, ViewQueryOption, Viewlet } from '@hcengineering/view'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
@ -70,9 +70,12 @@
|
|||||||
$: resultOptions = { ...options, lookup, ...(orderBy !== undefined ? { sort: { [orderBy[0]]: orderBy[1] } } : {}) }
|
$: resultOptions = { ...options, lookup, ...(orderBy !== undefined ? { sort: { [orderBy[0]]: orderBy[1] } } : {}) }
|
||||||
|
|
||||||
let resultQuery: DocumentQuery<Doc> = query
|
let resultQuery: DocumentQuery<Doc> = query
|
||||||
$: void getResultQuery(query, viewOptionsConfig, viewOptions).then((p) => {
|
|
||||||
|
const update = reduceCalls(async function (query: DocumentQuery<Doc>, viewOptions: ViewOptions) {
|
||||||
|
const p = await getResultQuery(query, viewOptionsConfig, viewOptions)
|
||||||
resultQuery = mergeQueries(p, query)
|
resultQuery = mergeQueries(p, query)
|
||||||
})
|
})
|
||||||
|
$: void update(query, viewOptions)
|
||||||
|
|
||||||
$: queryNoLookup = noLookup(resultQuery)
|
$: queryNoLookup = noLookup(resultQuery)
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
Space
|
Space
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { getResource, IntlString } from '@hcengineering/platform'
|
import { getResource, IntlString } from '@hcengineering/platform'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient, reduceCalls } from '@hcengineering/presentation'
|
||||||
import { AnyComponent, AnySvelteComponent } from '@hcengineering/ui'
|
import { AnyComponent, AnySvelteComponent } from '@hcengineering/ui'
|
||||||
import {
|
import {
|
||||||
AttributeModel,
|
AttributeModel,
|
||||||
@ -100,18 +100,15 @@
|
|||||||
CategoryQuery.remove(queryId)
|
CategoryQuery.remove(queryId)
|
||||||
})
|
})
|
||||||
|
|
||||||
function update (): void {
|
const updateCategories = reduceCalls(
|
||||||
void updateCategories(_class, space, docs, groupByKey, viewOptions, viewOptionsConfig)
|
async (
|
||||||
}
|
|
||||||
|
|
||||||
async function updateCategories (
|
|
||||||
_class: Ref<Class<Doc>>,
|
_class: Ref<Class<Doc>>,
|
||||||
space: Ref<Space> | undefined,
|
space: Ref<Space> | undefined,
|
||||||
docs: Doc[],
|
docs: Doc[],
|
||||||
groupByKey: string,
|
groupByKey: string,
|
||||||
viewOptions: ViewOptions,
|
viewOptions: ViewOptions,
|
||||||
viewOptionsModel: ViewOptionModel[] | undefined
|
viewOptionsModel: ViewOptionModel[] | undefined
|
||||||
): Promise<void> {
|
): Promise<void> => {
|
||||||
categories = await getCategories(client, _class, space, docs, groupByKey)
|
categories = await getCategories(client, _class, space, docs, groupByKey)
|
||||||
if (level === 0) {
|
if (level === 0) {
|
||||||
for (const viewOption of viewOptionsModel ?? []) {
|
for (const viewOption of viewOptionsModel ?? []) {
|
||||||
@ -128,29 +125,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function update (): void {
|
||||||
|
void updateCategories(_class, space, docs, groupByKey, viewOptions, viewOptionsConfig)
|
||||||
|
}
|
||||||
|
|
||||||
let itemModels = new Map<Ref<Class<Doc>>, AttributeModel[]>()
|
let itemModels = new Map<Ref<Class<Doc>>, AttributeModel[]>()
|
||||||
|
|
||||||
function getHeader (_class: Ref<Class<Doc>>, groupByKey: string): void {
|
const getHeader = reduceCalls(async function (_class: Ref<Class<Doc>>, groupByKey: string): Promise<void> {
|
||||||
if (groupByKey === noCategory) {
|
if (groupByKey === noCategory) {
|
||||||
headerComponent = undefined
|
headerComponent = undefined
|
||||||
} else {
|
} else {
|
||||||
getPresenter(client, _class, { key: groupByKey }, { key: groupByKey }).then((p) => (headerComponent = p))
|
await getPresenter(client, _class, { key: groupByKey }, { key: groupByKey }).then((p) => (headerComponent = p))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
let headerComponent: AttributeModel | undefined
|
let headerComponent: AttributeModel | undefined
|
||||||
$: getHeader(_class, groupByKey)
|
$: void getHeader(_class, groupByKey)
|
||||||
|
|
||||||
let updateCounter = 0
|
|
||||||
let configurationsVersion = 0
|
let configurationsVersion = 0
|
||||||
async function buildModels (
|
const buildModels = reduceCalls(async function (
|
||||||
_class: Ref<Class<Doc>>,
|
_class: Ref<Class<Doc>>,
|
||||||
config: Array<string | BuildModelKey>,
|
config: Array<string | BuildModelKey>,
|
||||||
configurations?: Record<Ref<Class<Doc>>, Viewlet['config']> | undefined
|
configurations?: Record<Ref<Class<Doc>>, Viewlet['config']> | undefined
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const id = ++updateCounter
|
|
||||||
updateCounter = id
|
|
||||||
const newItemModels = new Map<Ref<Class<Doc>>, AttributeModel[]>()
|
const newItemModels = new Map<Ref<Class<Doc>>, AttributeModel[]>()
|
||||||
const entries = Object.entries(configurations ?? [])
|
const entries = Object.entries(configurations ?? [])
|
||||||
for (const [k, v] of entries) {
|
for (const [k, v] of entries) {
|
||||||
@ -164,9 +163,7 @@
|
|||||||
newItemModels.set(_class, res)
|
newItemModels.set(_class, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id === updateCounter) {
|
|
||||||
itemModels = newItemModels
|
itemModels = newItemModels
|
||||||
configurationsVersion = updateCounter
|
|
||||||
for (const [, v] of Object.entries(newItemModels)) {
|
for (const [, v] of Object.entries(newItemModels)) {
|
||||||
// itemModels = itemModels
|
// itemModels = itemModels
|
||||||
;(v as AttributeModel[]).forEach((m: AttributeModel) => {
|
;(v as AttributeModel[]).forEach((m: AttributeModel) => {
|
||||||
@ -178,10 +175,10 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
configurationsVersion = configurationsVersion + 1
|
||||||
}
|
})
|
||||||
|
|
||||||
$: buildModels(_class, config, configurations)
|
$: void buildModels(_class, config, configurations)
|
||||||
|
|
||||||
function getInitIndex (categories: any, i: number): number {
|
function getInitIndex (categories: any, i: number): number {
|
||||||
let res = initIndex
|
let res = initIndex
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import contact from '@hcengineering/contact'
|
||||||
import { Ref, getCurrentAccount, toIdMap } from '@hcengineering/core'
|
import { Ref, getCurrentAccount, toIdMap } from '@hcengineering/core'
|
||||||
import { copyTextToClipboard, createQuery, getClient } from '@hcengineering/presentation'
|
import { copyTextToClipboard, createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import setting from '@hcengineering/setting'
|
import setting from '@hcengineering/setting'
|
||||||
@ -6,18 +7,19 @@
|
|||||||
Action,
|
Action,
|
||||||
IconAdd,
|
IconAdd,
|
||||||
Location,
|
Location,
|
||||||
eventToHTMLElement,
|
|
||||||
location,
|
|
||||||
navigate,
|
|
||||||
showPopup,
|
|
||||||
SelectPopup,
|
SelectPopup,
|
||||||
|
eventToHTMLElement,
|
||||||
getEventPopupPositionElement,
|
getEventPopupPositionElement,
|
||||||
|
getLocation,
|
||||||
getPopupPositionElement,
|
getPopupPositionElement,
|
||||||
|
location,
|
||||||
locationToUrl,
|
locationToUrl,
|
||||||
getLocation
|
navigate,
|
||||||
|
showPopup
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import view, { Filter, FilteredView, ViewOptions, Viewlet } from '@hcengineering/view'
|
import view, { Filter, FilteredView, ViewOptions, Viewlet } from '@hcengineering/view'
|
||||||
import {
|
import {
|
||||||
|
EditBoxPopup,
|
||||||
TreeItem,
|
TreeItem,
|
||||||
TreeNode,
|
TreeNode,
|
||||||
activeViewlet,
|
activeViewlet,
|
||||||
@ -28,14 +30,13 @@
|
|||||||
setActiveViewletId,
|
setActiveViewletId,
|
||||||
setFilters,
|
setFilters,
|
||||||
setViewOptions,
|
setViewOptions,
|
||||||
viewOptionStore,
|
viewOptionStore
|
||||||
EditBoxPopup
|
|
||||||
} from '@hcengineering/view-resources'
|
} from '@hcengineering/view-resources'
|
||||||
import { Application } from '@hcengineering/workbench'
|
import { Application } from '@hcengineering/workbench'
|
||||||
import copy from 'fast-copy'
|
import copy from 'fast-copy'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import contact from '@hcengineering/contact'
|
import TodoCheck from './icons/TodoCheck.svelte'
|
||||||
import task from '../../../task/lib'
|
import TodoUncheck from './icons/TodoUncheck.svelte'
|
||||||
|
|
||||||
export let currentApplication: Application | undefined
|
export let currentApplication: Application | undefined
|
||||||
|
|
||||||
@ -99,7 +100,7 @@
|
|||||||
async function switchPublicAction (object: FilteredView, originalEvent: MouseEvent | undefined): Promise<Action[]> {
|
async function switchPublicAction (object: FilteredView, originalEvent: MouseEvent | undefined): Promise<Action[]> {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
icon: object.sharable ? task.icon.TodoCheck : task.icon.TodoUnCheck,
|
icon: object.sharable ? TodoCheck : TodoUncheck,
|
||||||
label: view.string.PublicView,
|
label: view.string.PublicView,
|
||||||
action: async (ctx: any, evt: Event) => {
|
action: async (ctx: any, evt: Event) => {
|
||||||
await client.update(object, { sharable: !object.sharable })
|
await client.update(object, { sharable: !object.sharable })
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import core, { Class, Doc, Ref, Space, WithLookup } from '@hcengineering/core'
|
import core, { Class, Doc, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient, reduceCalls } from '@hcengineering/presentation'
|
||||||
import { AnyComponent, Component, resolvedLocationStore } from '@hcengineering/ui'
|
import { AnyComponent, Component, resolvedLocationStore } from '@hcengineering/ui'
|
||||||
import view, { ViewOptions, Viewlet } from '@hcengineering/view'
|
import view, { ViewOptions, Viewlet } from '@hcengineering/view'
|
||||||
import {
|
import {
|
||||||
@ -55,9 +55,7 @@
|
|||||||
|
|
||||||
$: active = $activeViewlet[key]
|
$: active = $activeViewlet[key]
|
||||||
|
|
||||||
$: update(active, currentSpace, currentView?.class)
|
const update = reduceCalls(async function update (
|
||||||
|
|
||||||
async function update (
|
|
||||||
active: Ref<Viewlet> | null,
|
active: Ref<Viewlet> | null,
|
||||||
currentSpace?: Ref<Space>,
|
currentSpace?: Ref<Space>,
|
||||||
attachTo?: Ref<Class<Doc>>
|
attachTo?: Ref<Class<Doc>>
|
||||||
@ -88,7 +86,9 @@
|
|||||||
}
|
}
|
||||||
_class = attachTo
|
_class = attachTo
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
|
$: void update(active, currentSpace, currentView?.class)
|
||||||
|
|
||||||
const hierarchy = client.getHierarchy()
|
const hierarchy = client.getHierarchy()
|
||||||
async function getHeader (_class: Ref<Class<Space>>): Promise<AnyComponent | undefined> {
|
async function getHeader (_class: Ref<Class<Space>>): Promise<AnyComponent | undefined> {
|
||||||
@ -97,7 +97,7 @@
|
|||||||
if (headerMixin?.header == null && clazz.extends != null) return await getHeader(clazz.extends)
|
if (headerMixin?.header == null && clazz.extends != null) return await getHeader(clazz.extends)
|
||||||
return headerMixin.header
|
return headerMixin.header
|
||||||
}
|
}
|
||||||
function setViewlet (e: CustomEvent<WithLookup<Viewlet>>) {
|
function setViewlet (e: CustomEvent<WithLookup<Viewlet>>): void {
|
||||||
viewlet = e.detail
|
viewlet = e.detail
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -20,7 +20,14 @@
|
|||||||
import notification, { DocNotifyContext, InboxNotification, notificationId } from '@hcengineering/notification'
|
import notification, { DocNotifyContext, InboxNotification, notificationId } from '@hcengineering/notification'
|
||||||
import { BrowserNotificatator, InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
import { BrowserNotificatator, InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||||
import { IntlString, broadcastEvent, getMetadata, getResource } from '@hcengineering/platform'
|
import { IntlString, broadcastEvent, getMetadata, getResource } from '@hcengineering/platform'
|
||||||
import { ActionContext, ComponentExtensions, createQuery, getClient, isAdminUser } from '@hcengineering/presentation'
|
import {
|
||||||
|
ActionContext,
|
||||||
|
ComponentExtensions,
|
||||||
|
createQuery,
|
||||||
|
getClient,
|
||||||
|
isAdminUser,
|
||||||
|
reduceCalls
|
||||||
|
} from '@hcengineering/presentation'
|
||||||
import setting from '@hcengineering/setting'
|
import setting from '@hcengineering/setting'
|
||||||
import support, { SupportStatus, supportLink } from '@hcengineering/support'
|
import support, { SupportStatus, supportLink } from '@hcengineering/support'
|
||||||
import {
|
import {
|
||||||
@ -163,18 +170,17 @@
|
|||||||
let hasNotificationsFn: ((data: Map<Ref<DocNotifyContext>, InboxNotification[]>) => Promise<boolean>) | undefined =
|
let hasNotificationsFn: ((data: Map<Ref<DocNotifyContext>, InboxNotification[]>) => Promise<boolean>) | undefined =
|
||||||
undefined
|
undefined
|
||||||
let hasInboxNotifications = false
|
let hasInboxNotifications = false
|
||||||
let syncPromise: Promise<void> | undefined = undefined
|
|
||||||
let locUpdate = 0
|
|
||||||
|
|
||||||
getResource(notification.function.HasInboxNotifications).then((f) => {
|
void getResource(notification.function.HasInboxNotifications).then((f) => {
|
||||||
hasNotificationsFn = f
|
hasNotificationsFn = f
|
||||||
})
|
})
|
||||||
|
|
||||||
$: hasNotificationsFn?.($inboxNotificationsByContextStore).then((res) => {
|
$: void hasNotificationsFn?.($inboxNotificationsByContextStore).then((res) => {
|
||||||
hasInboxNotifications = res
|
hasInboxNotifications = res
|
||||||
})
|
})
|
||||||
|
|
||||||
const doSyncLoc = async (loc: Location, iteration: number): Promise<void> => {
|
const doSyncLoc = reduceCalls(async (loc: Location): Promise<void> => {
|
||||||
|
console.log('do sync', JSON.stringify(loc), $location.path)
|
||||||
if (workspaceId !== $location.path[1]) {
|
if (workspaceId !== $location.path[1]) {
|
||||||
// Switch of workspace
|
// Switch of workspace
|
||||||
return
|
return
|
||||||
@ -182,19 +188,15 @@
|
|||||||
closeTooltip()
|
closeTooltip()
|
||||||
closePopup()
|
closePopup()
|
||||||
|
|
||||||
await syncLoc(loc, iteration)
|
await syncLoc(loc)
|
||||||
await updateWindowTitle(loc)
|
await updateWindowTitle(loc)
|
||||||
checkOnHide()
|
checkOnHide()
|
||||||
syncPromise = undefined
|
console.log('do sync-end', JSON.stringify(loc), $location.path)
|
||||||
}
|
})
|
||||||
|
|
||||||
onDestroy(
|
onDestroy(
|
||||||
location.subscribe((loc) => {
|
location.subscribe((loc) => {
|
||||||
locUpdate++
|
void doSyncLoc(loc)
|
||||||
if (syncPromise !== undefined) {
|
|
||||||
void syncPromise.then(() => doSyncLoc(loc, locUpdate))
|
|
||||||
} else {
|
|
||||||
syncPromise = doSyncLoc(loc, locUpdate)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -294,15 +296,12 @@
|
|||||||
return loc
|
return loc
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncLoc (loc: Location, iteration: number): Promise<void> {
|
async function syncLoc (loc: Location): Promise<void> {
|
||||||
const originalLoc = JSON.stringify(loc)
|
const originalLoc = JSON.stringify(loc)
|
||||||
|
|
||||||
if (loc.path.length > 3 && getSpecialComponent(loc.path[3]) === undefined) {
|
if (loc.path.length > 3 && getSpecialComponent(loc.path[3]) === undefined) {
|
||||||
// resolve short links
|
// resolve short links
|
||||||
const resolvedLoc = await resolveShortLink(loc)
|
const resolvedLoc = await resolveShortLink(loc)
|
||||||
if (locUpdate !== iteration) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (resolvedLoc !== undefined && !areLocationsEqual(loc, resolvedLoc.loc)) {
|
if (resolvedLoc !== undefined && !areLocationsEqual(loc, resolvedLoc.loc)) {
|
||||||
loc = mergeLoc(loc, resolvedLoc)
|
loc = mergeLoc(loc, resolvedLoc)
|
||||||
}
|
}
|
||||||
@ -334,9 +333,6 @@
|
|||||||
let len = 3
|
let len = 3
|
||||||
if (spaceRef !== undefined && specialRef !== undefined) {
|
if (spaceRef !== undefined && specialRef !== undefined) {
|
||||||
const spaceObj = await client.findOne<Space>(core.class.Space, { _id: spaceRef })
|
const spaceObj = await client.findOne<Space>(core.class.Space, { _id: spaceRef })
|
||||||
if (locUpdate !== iteration) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (spaceObj !== undefined) {
|
if (spaceObj !== undefined) {
|
||||||
loc.path[3] = spaceRef
|
loc.path[3] = spaceRef
|
||||||
loc.path[4] = specialRef
|
loc.path[4] = specialRef
|
||||||
@ -355,13 +351,7 @@
|
|||||||
clear(1)
|
clear(1)
|
||||||
currentAppAlias = app
|
currentAppAlias = app
|
||||||
currentApplication = await client.findOne<Application>(workbench.class.Application, { alias: app })
|
currentApplication = await client.findOne<Application>(workbench.class.Application, { alias: app })
|
||||||
if (locUpdate !== iteration) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
navigatorModel = await buildNavModel(client, currentApplication)
|
navigatorModel = await buildNavModel(client, currentApplication)
|
||||||
if (locUpdate !== iteration) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -395,9 +385,6 @@
|
|||||||
currentSpecial = space
|
currentSpecial = space
|
||||||
} else {
|
} else {
|
||||||
await updateSpace(space)
|
await updateSpace(space)
|
||||||
if (locUpdate !== iteration) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setSpaceSpecial(special)
|
setSpaceSpecial(special)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -425,6 +412,7 @@
|
|||||||
|
|
||||||
if (props.length >= 3) {
|
if (props.length >= 3) {
|
||||||
const doc = await client.findOne<Doc>(props[2] as Ref<Class<Doc>>, { _id: props[1] as Ref<Doc> })
|
const doc = await client.findOne<Doc>(props[2] as Ref<Class<Doc>>, { _id: props[1] as Ref<Doc> })
|
||||||
|
|
||||||
if (doc !== undefined) {
|
if (doc !== undefined) {
|
||||||
const provider = ListSelectionProvider.Find(doc._id)
|
const provider = ListSelectionProvider.Find(doc._id)
|
||||||
updateFocus({
|
updateFocus({
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let size: 'small' | 'medium' | 'large'
|
||||||
|
const fill: string = 'currentColor'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M8,14.5c-3.6,0-6.5-2.9-6.5-6.5S4.4,1.5,8,1.5s6.5,2.9,6.5,6.5S11.6,14.5,8,14.5z M8,2.5C5,2.5,2.5,5,2.5,8 c0,3,2.5,5.5,5.5,5.5c3,0,5.5-2.5,5.5-5.5C13.5,5,11,2.5,8,2.5z"
|
||||||
|
/>
|
||||||
|
<polygon points="7.4,10.7 5,8.4 5.7,7.6 7.3,9.3 10.3,5.7 11,6.3 " />
|
||||||
|
</svg>
|
@ -0,0 +1,10 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let size: 'small' | 'medium' | 'large'
|
||||||
|
const fill: string = 'currentColor'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M8,14.5c-3.6,0-6.5-2.9-6.5-6.5S4.4,1.5,8,1.5s6.5,2.9,6.5,6.5S11.6,14.5,8,14.5z M8,2.5C5,2.5,2.5,5,2.5,8 c0,3,2.5,5.5,5.5,5.5c3,0,5.5-2.5,5.5-5.5C13.5,5,11,2.5,8,2.5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
@ -877,7 +877,13 @@ export async function createWorkspace (
|
|||||||
try {
|
try {
|
||||||
const initWS = getMetadata(toolPlugin.metadata.InitWorkspace)
|
const initWS = getMetadata(toolPlugin.metadata.InitWorkspace)
|
||||||
const wsId = getWorkspaceId(workspaceInfo.workspace, productId)
|
const wsId = getWorkspaceId(workspaceInfo.workspace, productId)
|
||||||
if (initWS !== undefined && (await getWorkspaceById(db, productId, initWS)) !== null) {
|
|
||||||
|
// We should not try to clone INIT_WS into INIT_WS during it's creation.
|
||||||
|
if (
|
||||||
|
initWS !== undefined &&
|
||||||
|
(await getWorkspaceById(db, productId, initWS)) !== null &&
|
||||||
|
initWS !== workspaceInfo.workspace
|
||||||
|
) {
|
||||||
// Just any valid model for transactor to be able to function
|
// Just any valid model for transactor to be able to function
|
||||||
await (
|
await (
|
||||||
await initModel(ctx, getTransactor(), wsId, txes, [], ctxModellogger, async (value) => {
|
await initModel(ctx, getTransactor(), wsId, txes, [], ctxModellogger, async (value) => {
|
||||||
|
@ -207,10 +207,19 @@ export class FullTextIndex implements WithFind {
|
|||||||
const indexedDocMap = new Map<Ref<Doc>, IndexedDoc>()
|
const indexedDocMap = new Map<Ref<Doc>, IndexedDoc>()
|
||||||
|
|
||||||
for (const doc of docs) {
|
for (const doc of docs) {
|
||||||
if (doc._class.some((cl) => this.hierarchy.isDerived(cl, baseClass))) {
|
if (
|
||||||
|
doc._class != null &&
|
||||||
|
Array.isArray(doc._class) &&
|
||||||
|
doc._class.some((cl) => this.hierarchy.isDerived(cl, baseClass))
|
||||||
|
) {
|
||||||
ids.add(doc.id)
|
ids.add(doc.id)
|
||||||
indexedDocMap.set(doc.id, doc)
|
indexedDocMap.set(doc.id, doc)
|
||||||
}
|
}
|
||||||
|
if (doc._class !== null && !Array.isArray(doc._class) && this.hierarchy.isDerived(doc._class, baseClass)) {
|
||||||
|
ids.add(doc.id)
|
||||||
|
indexedDocMap.set(doc.id, doc)
|
||||||
|
}
|
||||||
|
|
||||||
if (doc.attachedTo != null) {
|
if (doc.attachedTo != null) {
|
||||||
if (doc.attachedToClass != null && this.hierarchy.isDerived(doc.attachedToClass, baseClass)) {
|
if (doc.attachedToClass != null && this.hierarchy.isDerived(doc.attachedToClass, baseClass)) {
|
||||||
if (this.hierarchy.isDerived(doc.attachedToClass, baseClass)) {
|
if (this.hierarchy.isDerived(doc.attachedToClass, baseClass)) {
|
||||||
|
@ -245,19 +245,20 @@ export async function upgradeModel (
|
|||||||
const migrateClient = new MigrateClientImpl(db, hierarchy, modelDb, logger)
|
const migrateClient = new MigrateClientImpl(db, hierarchy, modelDb, logger)
|
||||||
|
|
||||||
const states = await migrateClient.find<MigrationState>(DOMAIN_MIGRATION, { _class: core.class.MigrationState })
|
const states = await migrateClient.find<MigrationState>(DOMAIN_MIGRATION, { _class: core.class.MigrationState })
|
||||||
const migrateState = new Map(
|
const sts = Array.from(groupByArray(states, (it) => it.plugin).entries())
|
||||||
Array.from(groupByArray(states, (it) => it.plugin).entries()).map((it) => [
|
const migrateState = new Map(sts.map((it) => [it[0], new Set(it[1].map((q) => q.state))]))
|
||||||
it[0],
|
|
||||||
new Set(it[1].map((q) => q.state))
|
|
||||||
])
|
|
||||||
)
|
|
||||||
migrateClient.migrateState = migrateState
|
migrateClient.migrateState = migrateState
|
||||||
|
|
||||||
await ctx.with('migrate', {}, async () => {
|
await ctx.with('migrate', {}, async () => {
|
||||||
let i = 0
|
let i = 0
|
||||||
for (const op of migrateOperations) {
|
for (const op of migrateOperations) {
|
||||||
const t = Date.now()
|
const t = Date.now()
|
||||||
|
try {
|
||||||
await op[1].migrate(migrateClient, logger)
|
await op[1].migrate(migrateClient, logger)
|
||||||
|
} catch (err: any) {
|
||||||
|
logger.error(`error during migrate: ${op[0]} ${err.message}`, err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
logger.log('migrate:', { workspaceId: workspaceId.name, operation: op[0], time: Date.now() - t })
|
logger.log('migrate:', { workspaceId: workspaceId.name, operation: op[0], time: Date.now() - t })
|
||||||
await progress(20 + ((100 / migrateOperations.length) * i * 20) / 100)
|
await progress(20 + ((100 / migrateOperations.length) * i * 20) / 100)
|
||||||
i++
|
i++
|
||||||
|
@ -19,8 +19,9 @@ test.describe('Collaborative tests for Application', () => {
|
|||||||
const vacancyName = 'Software Engineer'
|
const vacancyName = 'Software Engineer'
|
||||||
let talentName: TalentName
|
let talentName: TalentName
|
||||||
// open second page
|
// open second page
|
||||||
const userSecondPage = await getSecondPage(browser)
|
const { page: userSecondPage, context } = await getSecondPage(browser)
|
||||||
|
|
||||||
|
try {
|
||||||
await test.step('User1. Add collaborators and comment from user1', async () => {
|
await test.step('User1. Add collaborators and comment from user1', async () => {
|
||||||
const navigationMenuPage = new NavigationMenuPage(page)
|
const navigationMenuPage = new NavigationMenuPage(page)
|
||||||
await navigationMenuPage.buttonApplications.click()
|
await navigationMenuPage.buttonApplications.click()
|
||||||
@ -87,5 +88,9 @@ test.describe('Collaborative tests for Application', () => {
|
|||||||
const applicationsDetailsPage = new ApplicationsDetailsPage(page)
|
const applicationsDetailsPage = new ApplicationsDetailsPage(page)
|
||||||
await applicationsDetailsPage.checkCommentExist('Test Comment from user2')
|
await applicationsDetailsPage.checkCommentExist('Test Comment from user2')
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
await userSecondPage.close()
|
||||||
|
await context.close()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { test } from '@playwright/test'
|
import { test } from '@playwright/test'
|
||||||
import { generateId, getSecondPage, PlatformSetting, PlatformURI } from '../utils'
|
|
||||||
import { NewIssue } from '../model/tracker/types'
|
|
||||||
import { IssuesPage } from '../model/tracker/issues-page'
|
|
||||||
import { LeftSideMenuPage } from '../model/left-side-menu-page'
|
import { LeftSideMenuPage } from '../model/left-side-menu-page'
|
||||||
import { IssuesDetailsPage } from '../model/tracker/issues-details-page'
|
import { IssuesDetailsPage } from '../model/tracker/issues-details-page'
|
||||||
|
import { IssuesPage } from '../model/tracker/issues-page'
|
||||||
|
import { NewIssue } from '../model/tracker/types'
|
||||||
|
import { generateId, getSecondPage, PlatformSetting, PlatformURI } from '../utils'
|
||||||
|
|
||||||
test.use({
|
test.use({
|
||||||
storageState: PlatformSetting
|
storageState: PlatformSetting
|
||||||
@ -22,7 +22,7 @@ test.describe('Collaborative test for issue', () => {
|
|||||||
priority: 'Urgent',
|
priority: 'Urgent',
|
||||||
assignee: 'Appleseed John',
|
assignee: 'Appleseed John',
|
||||||
createLabel: true,
|
createLabel: true,
|
||||||
labels: `CREATE-ISSUE-${generateId()}`,
|
labels: `CREATE-ISSUE-${generateId(5)}`,
|
||||||
component: 'No component',
|
component: 'No component',
|
||||||
estimation: '2',
|
estimation: '2',
|
||||||
milestone: 'No Milestone',
|
milestone: 'No Milestone',
|
||||||
@ -31,7 +31,8 @@ test.describe('Collaborative test for issue', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// open second page
|
// open second page
|
||||||
const userSecondPage = await getSecondPage(browser)
|
const { page: userSecondPage, context } = await getSecondPage(browser)
|
||||||
|
try {
|
||||||
await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished()
|
await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished()
|
||||||
|
|
||||||
// create a new issue by first user
|
// create a new issue by first user
|
||||||
@ -62,7 +63,10 @@ test.describe('Collaborative test for issue', () => {
|
|||||||
milestone: 'Milestone',
|
milestone: 'Milestone',
|
||||||
estimation: '2h'
|
estimation: '2h'
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
await userSecondPage.close()
|
await userSecondPage.close()
|
||||||
|
await context.close()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Issues status can be changed by another users', async ({ page, browser }) => {
|
test('Issues status can be changed by another users', async ({ page, browser }) => {
|
||||||
@ -72,15 +76,18 @@ test.describe('Collaborative test for issue', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// open second page
|
// open second page
|
||||||
const userSecondPage = await getSecondPage(browser)
|
const { page: userSecondPage, context } = await getSecondPage(browser)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (userSecondPage.url() !== `${PlatformURI}/workbench/sanity-ws/tracker/`) {
|
||||||
await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished()
|
await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished()
|
||||||
|
}
|
||||||
|
|
||||||
const issuesPageSecond = new IssuesPage(userSecondPage)
|
const issuesPageSecond = new IssuesPage(userSecondPage)
|
||||||
await issuesPageSecond.linkSidebarAll.click()
|
await issuesPageSecond.linkSidebarAll.click()
|
||||||
await issuesPageSecond.modelSelectorAll.click()
|
await issuesPageSecond.modelSelectorAll.click()
|
||||||
|
|
||||||
// change status
|
// change status
|
||||||
await (await page.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished()
|
|
||||||
const issuesPage = new IssuesPage(page)
|
const issuesPage = new IssuesPage(page)
|
||||||
await issuesPage.linkSidebarAll.click()
|
await issuesPage.linkSidebarAll.click()
|
||||||
await issuesPage.modelSelectorBacklog.click()
|
await issuesPage.modelSelectorBacklog.click()
|
||||||
@ -107,7 +114,10 @@ test.describe('Collaborative test for issue', () => {
|
|||||||
...issue,
|
...issue,
|
||||||
status: 'In Progress'
|
status: 'In Progress'
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
await userSecondPage.close()
|
await userSecondPage.close()
|
||||||
|
await context.close()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test('First user change assignee, second user should see assigned issue', async ({ page, browser }) => {
|
test('First user change assignee, second user should see assigned issue', async ({ page, browser }) => {
|
||||||
@ -118,7 +128,8 @@ test.describe('Collaborative test for issue', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// open second page
|
// open second page
|
||||||
const userSecondPage = await getSecondPage(browser)
|
const { page: userSecondPage, context } = await getSecondPage(browser)
|
||||||
|
try {
|
||||||
await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished()
|
await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished()
|
||||||
|
|
||||||
await test.step(`user1. change assignee to ${newAssignee}`, async () => {
|
await test.step(`user1. change assignee to ${newAssignee}`, async () => {
|
||||||
@ -156,6 +167,9 @@ test.describe('Collaborative test for issue', () => {
|
|||||||
const issuesDetailsPageSecond = new IssuesDetailsPage(userSecondPage)
|
const issuesDetailsPageSecond = new IssuesDetailsPage(userSecondPage)
|
||||||
await issuesDetailsPageSecond.checkIssue({ ...issue })
|
await issuesDetailsPageSecond.checkIssue({ ...issue })
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
await userSecondPage.close()
|
await userSecondPage.close()
|
||||||
|
await context.close()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { expect, test } from '@playwright/test'
|
import { expect, test } from '@playwright/test'
|
||||||
import { generateId, PlatformSetting, PlatformURI } from '../utils'
|
import { DocumentContentPage } from '../model/documents/document-content-page'
|
||||||
|
import { DocumentsPage } from '../model/documents/documents-page'
|
||||||
import { NewDocument } from '../model/documents/types'
|
import { NewDocument } from '../model/documents/types'
|
||||||
import { LeftSideMenuPage } from '../model/left-side-menu-page'
|
import { LeftSideMenuPage } from '../model/left-side-menu-page'
|
||||||
import { DocumentsPage } from '../model/documents/documents-page'
|
|
||||||
import { DocumentContentPage } from '../model/documents/document-content-page'
|
|
||||||
import { PublicLinkPopup } from '../model/tracker/public-link-popup'
|
import { PublicLinkPopup } from '../model/tracker/public-link-popup'
|
||||||
|
import { generateId, PlatformSetting, PlatformURI } from '../utils'
|
||||||
|
|
||||||
test.describe('Documents link tests', () => {
|
test.describe('Documents link tests', () => {
|
||||||
test('Document public link revoke', async ({ browser }) => {
|
test('Document public link revoke', async ({ browser }) => {
|
||||||
@ -15,6 +15,7 @@ test.describe('Documents link tests', () => {
|
|||||||
|
|
||||||
const newContext = await browser.newContext({ storageState: PlatformSetting })
|
const newContext = await browser.newContext({ storageState: PlatformSetting })
|
||||||
const page = await newContext.newPage()
|
const page = await newContext.newPage()
|
||||||
|
try {
|
||||||
await (await page.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished()
|
await (await page.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished()
|
||||||
|
|
||||||
const leftSideMenuPage = new LeftSideMenuPage(page)
|
const leftSideMenuPage = new LeftSideMenuPage(page)
|
||||||
@ -39,6 +40,7 @@ test.describe('Documents link tests', () => {
|
|||||||
|
|
||||||
const clearSession = await browser.newContext()
|
const clearSession = await browser.newContext()
|
||||||
const clearPage = await clearSession.newPage()
|
const clearPage = await clearSession.newPage()
|
||||||
|
try {
|
||||||
await test.step('Check guest access to the document', async () => {
|
await test.step('Check guest access to the document', async () => {
|
||||||
await clearPage.goto(link)
|
await clearPage.goto(link)
|
||||||
|
|
||||||
@ -56,5 +58,13 @@ test.describe('Documents link tests', () => {
|
|||||||
await clearPage.goto(link)
|
await clearPage.goto(link)
|
||||||
await expect(clearPage.locator('div.antiPopup > h1')).toHaveText('Public link was revoked')
|
await expect(clearPage.locator('div.antiPopup > h1')).toHaveText('Public link was revoked')
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
await clearPage.close()
|
||||||
|
await clearSession.close()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await page.close()
|
||||||
|
await newContext.close()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -130,7 +130,8 @@ test.describe('Documents tests', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('User2. Add content second user', async () => {
|
await test.step('User2. Add content second user', async () => {
|
||||||
const userSecondPage = await getSecondPage(browser)
|
const { page: userSecondPage, context } = await getSecondPage(browser)
|
||||||
|
try {
|
||||||
await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished()
|
await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished()
|
||||||
|
|
||||||
const leftSideMenuPageSecond = new LeftSideMenuPage(userSecondPage)
|
const leftSideMenuPageSecond = new LeftSideMenuPage(userSecondPage)
|
||||||
@ -146,6 +147,10 @@ test.describe('Documents tests', () => {
|
|||||||
|
|
||||||
content = await documentContentPageSecond.addContentToTheNewLine(contentSecondUser)
|
content = await documentContentPageSecond.addContentToTheNewLine(contentSecondUser)
|
||||||
await documentContentPageSecond.checkContent(content)
|
await documentContentPageSecond.checkContent(content)
|
||||||
|
} finally {
|
||||||
|
await userSecondPage.close()
|
||||||
|
await context.close()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('User1. Check final content', async () => {
|
await test.step('User1. Check final content', async () => {
|
||||||
|
@ -52,6 +52,7 @@ export class CommonPage {
|
|||||||
.locator('div.popup form[id="tags:string:AddTag"] input[placeholder="Please type description here"]')
|
.locator('div.popup form[id="tags:string:AddTag"] input[placeholder="Please type description here"]')
|
||||||
.fill(description)
|
.fill(description)
|
||||||
await page.locator('div.popup form[id="tags:string:AddTag"] button[type="submit"]').click()
|
await page.locator('div.popup form[id="tags:string:AddTag"] button[type="submit"]').click()
|
||||||
|
await page.locator('div.popup form[id="tags:string:AddTag"]').waitFor({ state: 'hidden' })
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectAssignee (page: Page, name: string): Promise<void> {
|
async selectAssignee (page: Page, name: string): Promise<void> {
|
||||||
|
@ -27,6 +27,7 @@ export class DocumentContentPage extends CommonPage {
|
|||||||
|
|
||||||
async addContentToTheNewLine (newContent: string): Promise<string> {
|
async addContentToTheNewLine (newContent: string): Promise<string> {
|
||||||
await expect(this.inputContent).toBeVisible()
|
await expect(this.inputContent).toBeVisible()
|
||||||
|
await expect(this.inputContent).toHaveJSProperty('contentEditable', 'true')
|
||||||
await this.inputContent.pressSequentially(`\n${newContent}`)
|
await this.inputContent.pressSequentially(`\n${newContent}`)
|
||||||
const endContent = await this.inputContent.textContent()
|
const endContent = await this.inputContent.textContent()
|
||||||
if (endContent == null) {
|
if (endContent == null) {
|
||||||
@ -42,6 +43,7 @@ export class DocumentContentPage extends CommonPage {
|
|||||||
|
|
||||||
async updateDocumentTitle (title: string): Promise<void> {
|
async updateDocumentTitle (title: string): Promise<void> {
|
||||||
await this.buttonDocumentTitle.fill(title)
|
await this.buttonDocumentTitle.fill(title)
|
||||||
|
await this.buttonDocumentTitle.blur()
|
||||||
}
|
}
|
||||||
|
|
||||||
async addRandomLines (count: number, lineLength: number = 36): Promise<void> {
|
async addRandomLines (count: number, lineLength: number = 36): Promise<void> {
|
||||||
|
@ -117,9 +117,10 @@ export class PlanningPage extends CalendarPage {
|
|||||||
if (data.createLabel) {
|
if (data.createLabel) {
|
||||||
await this.pressCreateButtonSelectPopup(this.page)
|
await this.pressCreateButtonSelectPopup(this.page)
|
||||||
await this.addNewTagPopup(this.page, data.labels, 'Tag from createNewIssue')
|
await this.addNewTagPopup(this.page, data.labels, 'Tag from createNewIssue')
|
||||||
}
|
await this.page.locator('.popup#TagsPopup').press('Escape')
|
||||||
|
} else {
|
||||||
await this.checkFromDropdownWithSearch(this.page, data.labels)
|
await this.checkFromDropdownWithSearch(this.page, data.labels)
|
||||||
await (popup ? this.buttonPopupCreateAddLabel.press('Escape') : this.buttonPanelCreateAddLabel.press('Escape'))
|
}
|
||||||
}
|
}
|
||||||
if (data.slots != null) {
|
if (data.slots != null) {
|
||||||
let index = 0
|
let index = 0
|
||||||
|
@ -31,7 +31,7 @@ export class IssuesDetailsPage extends CommonTrackerPage {
|
|||||||
super(page)
|
super(page)
|
||||||
this.page = page
|
this.page = page
|
||||||
this.inputTitle = page.locator('div.popupPanel-body input[type="text"]')
|
this.inputTitle = page.locator('div.popupPanel-body input[type="text"]')
|
||||||
this.inputDescription = page.locator('div.popupPanel-body div.textInput p')
|
this.inputDescription = page.locator('div.popupPanel-body div.textInput div.tiptap')
|
||||||
this.buttonStatus = page.locator('//span[text()="Status"]/../button[1]//span')
|
this.buttonStatus = page.locator('//span[text()="Status"]/../button[1]//span')
|
||||||
this.buttonPriority = page.locator('//span[text()="Priority"]/../button[2]//span')
|
this.buttonPriority = page.locator('//span[text()="Priority"]/../button[2]//span')
|
||||||
this.buttonAssignee = page.locator('(//span[text()="Assignee"]/../div/button)[2]')
|
this.buttonAssignee = page.locator('(//span[text()="Assignee"]/../div/button)[2]')
|
||||||
@ -170,6 +170,7 @@ export class IssuesDetailsPage extends CommonTrackerPage {
|
|||||||
|
|
||||||
async addToDescription (description: string): Promise<void> {
|
async addToDescription (description: string): Promise<void> {
|
||||||
const existDescription = await this.inputDescription.textContent()
|
const existDescription = await this.inputDescription.textContent()
|
||||||
|
await expect(this.inputDescription).toHaveJSProperty('contentEditable', 'true')
|
||||||
await this.inputDescription.fill(`${existDescription}\n${description}`)
|
await this.inputDescription.fill(`${existDescription}\n${description}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,11 +110,10 @@ export class IssuesPage extends CommonTrackerPage {
|
|||||||
if (data.createLabel) {
|
if (data.createLabel) {
|
||||||
await this.pressCreateButtonSelectPopup(this.page)
|
await this.pressCreateButtonSelectPopup(this.page)
|
||||||
await this.addNewTagPopup(this.page, data.labels, 'Tag from createNewIssue')
|
await this.addNewTagPopup(this.page, data.labels, 'Tag from createNewIssue')
|
||||||
|
await this.page.locator('.popup#TagsPopup').press('Escape')
|
||||||
} else {
|
} else {
|
||||||
await this.checkFromDropdown(this.page, data.labels)
|
await this.checkFromDropdown(this.page, data.labels)
|
||||||
}
|
}
|
||||||
await this.inputPopupCreateNewIssueTitle.press('Escape')
|
|
||||||
await this.inputPopupCreateNewIssueTitle.click({ force: true })
|
|
||||||
}
|
}
|
||||||
if (data.component != null) {
|
if (data.component != null) {
|
||||||
await this.buttonPopupCreateNewIssueComponent.click()
|
await this.buttonPopupCreateNewIssueComponent.click()
|
||||||
@ -211,10 +210,14 @@ export class IssuesPage extends CommonTrackerPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkAllIssuesByPriority (priorityName: string): Promise<void> {
|
async checkAllIssuesByPriority (priorityName: string): Promise<void> {
|
||||||
|
await expect(async () => {
|
||||||
for await (const locator of iterateLocator(this.issuesList)) {
|
for await (const locator of iterateLocator(this.issuesList)) {
|
||||||
const href = await locator.locator('div.priority-container use').getAttribute('href')
|
const href = await locator.locator('div.priority-container use').getAttribute('href')
|
||||||
expect(href).toContain(priorityName)
|
expect(href, { message: `Should contain ${priorityName} but it is ${href}` }).toContain(priorityName)
|
||||||
}
|
}
|
||||||
|
}).toPass({
|
||||||
|
timeout: 15000
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async getIssueId (issueLabel: string, position: number = 0): Promise<string> {
|
async getIssueId (issueLabel: string, position: number = 0): Promise<string> {
|
||||||
|
@ -16,8 +16,8 @@ export async function prepareNewIssueStep (page: Page, issue: NewIssue): Promise
|
|||||||
export async function prepareNewIssueWithOpenStep (page: Page, issue: NewIssue): Promise<string> {
|
export async function prepareNewIssueWithOpenStep (page: Page, issue: NewIssue): Promise<string> {
|
||||||
return await test.step('Prepare document', async () => {
|
return await test.step('Prepare document', async () => {
|
||||||
const issuesPage = new IssuesPage(page)
|
const issuesPage = new IssuesPage(page)
|
||||||
|
await issuesPage.linkSidebarAll.click()
|
||||||
await issuesPage.modelSelectorAll.click()
|
await issuesPage.modelSelectorAll.click()
|
||||||
|
|
||||||
await issuesPage.createNewIssue(issue)
|
await issuesPage.createNewIssue(issue)
|
||||||
await issuesPage.searchIssueByName(issue.title)
|
await issuesPage.searchIssueByName(issue.title)
|
||||||
await issuesPage.openIssueByName(issue.title)
|
await issuesPage.openIssueByName(issue.title)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { expect, test } from '@playwright/test'
|
import { expect, test } from '@playwright/test'
|
||||||
import { navigate } from './tracker.utils'
|
|
||||||
import { generateId, PlatformSetting, PlatformURI, fillSearch } from '../utils'
|
import { generateId, PlatformSetting, PlatformURI, fillSearch } from '../utils'
|
||||||
import { LeftSideMenuPage } from '../model/left-side-menu-page'
|
import { LeftSideMenuPage } from '../model/left-side-menu-page'
|
||||||
import { TrackerNavigationMenuPage } from '../model/tracker/tracker-navigation-menu-page'
|
import { TrackerNavigationMenuPage } from '../model/tracker/tracker-navigation-menu-page'
|
||||||
@ -18,8 +17,6 @@ test.describe('Tracker component tests', () => {
|
|||||||
|
|
||||||
test('create-component-issue', async ({ page }) => {
|
test('create-component-issue', async ({ page }) => {
|
||||||
await page.click('[id="app-tracker\\:string\\:TrackerApplication"]')
|
await page.click('[id="app-tracker\\:string\\:TrackerApplication"]')
|
||||||
|
|
||||||
await navigate(page)
|
|
||||||
await page.click('text=Components')
|
await page.click('text=Components')
|
||||||
await expect(page).toHaveURL(
|
await expect(page).toHaveURL(
|
||||||
`${PlatformURI}/workbench/sanity-ws/tracker/tracker%3Aproject%3ADefaultProject/components`
|
`${PlatformURI}/workbench/sanity-ws/tracker/tracker%3Aproject%3ADefaultProject/components`
|
||||||
|
@ -235,6 +235,7 @@ test.describe('Tracker filters tests', () => {
|
|||||||
await leftSideMenuPage.buttonTracker.click()
|
await leftSideMenuPage.buttonTracker.click()
|
||||||
|
|
||||||
const issuesPage = new IssuesPage(page)
|
const issuesPage = new IssuesPage(page)
|
||||||
|
await issuesPage.linkSidebarAll.click()
|
||||||
await issuesPage.modelSelectorAll.click()
|
await issuesPage.modelSelectorAll.click()
|
||||||
|
|
||||||
for (const status of DEFAULT_STATUSES) {
|
for (const status of DEFAULT_STATUSES) {
|
||||||
|
@ -317,6 +317,7 @@ test.describe('Tracker issue tests', () => {
|
|||||||
await trackerNavigationMenuPage.openIssuesForProject('Default')
|
await trackerNavigationMenuPage.openIssuesForProject('Default')
|
||||||
|
|
||||||
const issuesPage = new IssuesPage(page)
|
const issuesPage = new IssuesPage(page)
|
||||||
|
await issuesPage.searchIssueByName(commentIssue.title)
|
||||||
await issuesPage.checkCommentsCount(commentIssue.title, '1')
|
await issuesPage.checkCommentsCount(commentIssue.title, '1')
|
||||||
await issuesPage.openCommentPopupForIssueByName(commentIssue.title)
|
await issuesPage.openCommentPopupForIssueByName(commentIssue.title)
|
||||||
|
|
||||||
@ -327,6 +328,7 @@ test.describe('Tracker issue tests', () => {
|
|||||||
|
|
||||||
await issuesPage.modelSelectorAll.click()
|
await issuesPage.modelSelectorAll.click()
|
||||||
|
|
||||||
|
await issuesPage.searchIssueByName(commentIssue.title)
|
||||||
await issuesPage.checkCommentsCount(commentIssue.title, '2')
|
await issuesPage.checkCommentsCount(commentIssue.title, '2')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -17,6 +17,7 @@ test.describe('Tracker public link issues tests', () => {
|
|||||||
await test.step('Get public link from popup', async () => {
|
await test.step('Get public link from popup', async () => {
|
||||||
const newContext = await browser.newContext({ storageState: PlatformSetting })
|
const newContext = await browser.newContext({ storageState: PlatformSetting })
|
||||||
const page = await newContext.newPage()
|
const page = await newContext.newPage()
|
||||||
|
try {
|
||||||
await (await page.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished()
|
await (await page.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished()
|
||||||
|
|
||||||
const leftSideMenuPage = new LeftSideMenuPage(page)
|
const leftSideMenuPage = new LeftSideMenuPage(page)
|
||||||
@ -28,6 +29,10 @@ test.describe('Tracker public link issues tests', () => {
|
|||||||
|
|
||||||
const publicLinkPopup = new PublicLinkPopup(page)
|
const publicLinkPopup = new PublicLinkPopup(page)
|
||||||
link = await publicLinkPopup.getPublicLink()
|
link = await publicLinkPopup.getPublicLink()
|
||||||
|
} finally {
|
||||||
|
await page.close()
|
||||||
|
await newContext.close()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Check guest access to the issue', async () => {
|
await test.step('Check guest access to the issue', async () => {
|
||||||
@ -36,6 +41,7 @@ test.describe('Tracker public link issues tests', () => {
|
|||||||
await clearSession.clearPermissions()
|
await clearSession.clearPermissions()
|
||||||
|
|
||||||
const clearPage = await clearSession.newPage()
|
const clearPage = await clearSession.newPage()
|
||||||
|
try {
|
||||||
await clearPage.goto(link)
|
await clearPage.goto(link)
|
||||||
|
|
||||||
const clearIssuesDetailsPage = new IssuesDetailsPage(clearPage)
|
const clearIssuesDetailsPage = new IssuesDetailsPage(clearPage)
|
||||||
@ -45,6 +51,10 @@ test.describe('Tracker public link issues tests', () => {
|
|||||||
status: 'Backlog'
|
status: 'Backlog'
|
||||||
})
|
})
|
||||||
expect(clearPage.url()).toContain('guest')
|
expect(clearPage.url()).toContain('guest')
|
||||||
|
} finally {
|
||||||
|
await clearPage.close()
|
||||||
|
await clearSession.close()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -57,6 +67,7 @@ test.describe('Tracker public link issues tests', () => {
|
|||||||
const newContext = await browser.newContext({ storageState: PlatformSetting })
|
const newContext = await browser.newContext({ storageState: PlatformSetting })
|
||||||
const page = await newContext.newPage()
|
const page = await newContext.newPage()
|
||||||
let link: string
|
let link: string
|
||||||
|
try {
|
||||||
await test.step('Get public link from popup', async () => {
|
await test.step('Get public link from popup', async () => {
|
||||||
await (await page.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished()
|
await (await page.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished()
|
||||||
|
|
||||||
@ -73,6 +84,7 @@ test.describe('Tracker public link issues tests', () => {
|
|||||||
|
|
||||||
const clearSession = await browser.newContext()
|
const clearSession = await browser.newContext()
|
||||||
const clearPage = await clearSession.newPage()
|
const clearPage = await clearSession.newPage()
|
||||||
|
try {
|
||||||
await test.step('Check guest access to the issue', async () => {
|
await test.step('Check guest access to the issue', async () => {
|
||||||
await clearPage.goto(link)
|
await clearPage.goto(link)
|
||||||
|
|
||||||
@ -94,5 +106,13 @@ test.describe('Tracker public link issues tests', () => {
|
|||||||
await clearPage.goto(link)
|
await clearPage.goto(link)
|
||||||
await expect(clearPage.locator('div.antiPopup > h1')).toHaveText('Public link was revoked')
|
await expect(clearPage.locator('div.antiPopup > h1')).toHaveText('Public link was revoked')
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
await clearPage.close()
|
||||||
|
await clearSession.close()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await page.close()
|
||||||
|
await newContext.close()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -23,7 +23,7 @@ test.describe('Tracker sub-issues tests', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('create sub-issue', async ({ page }) => {
|
test('create sub-issue', async ({ page }) => {
|
||||||
await navigate(page)
|
await page.click('[id="app-tracker\\:string\\:TrackerApplication"]')
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
name: `issue-${generateId(5)}`,
|
name: `issue-${generateId(5)}`,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Browser, Locator, Page, expect } from '@playwright/test'
|
import { Browser, BrowserContext, Locator, Page, expect } from '@playwright/test'
|
||||||
import { allure } from 'allure-playwright'
|
import { allure } from 'allure-playwright'
|
||||||
|
|
||||||
export const PlatformURI = process.env.PLATFORM_URI as string
|
export const PlatformURI = process.env.PLATFORM_URI as string
|
||||||
@ -62,9 +62,9 @@ export async function fillSearch (page: Page, search: string): Promise<Locator>
|
|||||||
return searchBox
|
return searchBox
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSecondPage (browser: Browser): Promise<Page> {
|
export async function getSecondPage (browser: Browser): Promise<{ page: Page, context: BrowserContext }> {
|
||||||
const userSecondContext = await browser.newContext({ storageState: PlatformSettingSecond })
|
const userSecondContext = await browser.newContext({ storageState: PlatformSettingSecond })
|
||||||
return await userSecondContext.newPage()
|
return { page: await userSecondContext.newPage(), context: userSecondContext }
|
||||||
}
|
}
|
||||||
export function expectToContainsOrdered (val: Locator, text: string[], timeout?: number): Promise<void> {
|
export function expectToContainsOrdered (val: Locator, text: string[], timeout?: number): Promise<void> {
|
||||||
const origIssuesExp = new RegExp('.*' + text.join('.*') + '.*')
|
const origIssuesExp = new RegExp('.*' + text.join('.*') + '.*')
|
||||||
|
Loading…
Reference in New Issue
Block a user