UBER-526/UBER-523 (#3447)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2023-06-22 11:55:57 +07:00 committed by GitHub
parent ef8dfdc006
commit 747de753ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 289 additions and 154 deletions

View File

@ -1,6 +1,18 @@
# Changelog
## 0.6.32 (upcoming)
## 0.6.110 (upcoming)
- UBER-526: Live reload on language changes.
Function translate is updated to pass language, so be aware to use `$themeState.language` to properly use translation.
```javascript
function translate(id:string, params: Record<string, any>, language?:string): Promise<string> {
//...
}
```
## 0.6.32
Tracker:

View File

@ -212,6 +212,8 @@ module.exports = {
// https://webpack.js.org/configuration/watch/#watchoptionsignored
// don't use this pattern, if you have a monorepo with linked packages
ignored: /node_modules/,
aggregateTimeout: 500,
poll: 1000
},
devtool: prod ? false : 'inline-source-map',
devServer: {

View File

@ -47,10 +47,13 @@ export function addStringsLoader (plugin: Plugin, loader: Loader): void {
* Perform load of all internationalization sources for all plugins available.
* @public
*/
export async function loadPluginStrings (locale: string): Promise<void> {
export async function loadPluginStrings (locale: string, force: boolean = false): Promise<void> {
if (force) {
cache.clear()
}
for (const [plugin] of loaders) {
let messages = translations.get(plugin)
if (messages === undefined) {
if (messages === undefined || force) {
messages = await loadTranslationsForComponent(plugin, locale)
translations.set(plugin, messages)
}
@ -100,8 +103,12 @@ async function getTranslation (id: _IdInfo, locale: string): Promise<IntlString
* @param params -
* @returns
*/
export async function translate<P extends Record<string, any>> (message: IntlString<P>, params: P): Promise<string> {
const locale = getMetadata(platform.metadata.locale) ?? 'en'
export async function translate<P extends Record<string, any>> (
message: IntlString<P>,
params: P,
language?: string
): Promise<string> {
const locale = language ?? getMetadata(platform.metadata.locale) ?? 'en'
const compiled = cache.get(message)
if (compiled !== undefined) {

View File

@ -20,6 +20,7 @@
import { showPopup, Button } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import presentation, { SpacesMultiPopup } from '..'
import { themeStore } from '@hcengineering/ui'
export let selectedItems: Ref<Space>[] = []
export let _classes: Ref<Class<Space>>[] = []
@ -65,7 +66,7 @@
<svelte:fragment slot="content">
{#if selectedItems.length > 0}
<div class="flex-row-center flex-nowrap">
{#await translate(presentation.string.NumberSpaces, { count: selectedItems.length }) then text}
{#await translate(presentation.string.NumberSpaces, { count: selectedItems.length }, $themeStore.language) then text}
<span class="overflow-label disabled ml-1-5">{text}</span>
{/await}
</div>

View File

@ -15,8 +15,9 @@
import core, { PluginConfiguration, SortingOrder } from '@hcengineering/core'
import { Plugin, Resource, getResourcePlugin } from '@hcengineering/platform'
import { writable } from 'svelte/store'
import { get, writable } from 'svelte/store'
import { createQuery } from '.'
import { location as platformLocation } from '@hcengineering/ui'
/**
* @public
@ -41,6 +42,9 @@ export const configurationStore = writable<ConfigurationManager>(configuration)
const configQuery = createQuery(true)
let hashString = ''
let workspaceId: string = ''
/**
* @public
*/
@ -52,10 +56,15 @@ configQuery.query(
core.class.PluginConfiguration,
{},
(res) => {
if (configuration.list.length > 0) {
// Configuration
const newHash = res.map((it) => `${it.pluginId}=${it.enabled ? '+' : '-'}`).join('&')
const wsId = get(platformLocation).path[1]
if (hashString !== '' && hashString !== newHash && workspaceId !== '' && workspaceId === wsId) {
// Configuration is changed for same workspace.
location.reload()
}
workspaceId = wsId
hashString = newHash
configuration = new ConfigurationManager(res, new Map(res.map((it) => [it.pluginId, it])))
configurationStore.set(configuration)
},

View File

@ -114,7 +114,7 @@
let placeHolderStr: string = ''
$: ph = translate(placeholder, {}).then((r) => {
$: ph = translate(placeholder, {}, $themeStore.language).then((r) => {
placeHolderStr = r
})

View File

@ -25,6 +25,7 @@
import { FormatMode } from '../types'
import { defaultExtensions } from './extensions'
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
import { themeStore } from '@hcengineering/ui'
export let content: string = ''
export let placeholder: IntlString = textEditorPlugin.string.EditorPlaceholder
@ -37,7 +38,7 @@
let placeHolderStr: string = ''
$: ph = translate(placeholder, {}).then((r) => {
$: ph = translate(placeholder, {}, $themeStore.language).then((r) => {
placeHolderStr = r
})

View File

@ -21,38 +21,45 @@
const currentFontSize = getCurrentFontSize()
let currentLanguage = getCurrentLanguage()
const setRootColors = (theme: string) => {
document.documentElement.setAttribute('class', `${theme} ${getCurrentFontSize()}`)
themeOptions.set(
new ThemeOptions(getCurrentFontSize() === 'normal-font' ? 16 : 14, getCurrentTheme() === 'theme-dark')
)
const setOptions = (currentFont: string, theme: string, language: string) => {
themeOptions.set(new ThemeOptions(currentFont === 'normal-font' ? 16 : 14, theme === 'theme-dark', language))
}
const setRootFontSize = (fontsize: string) => {
const setRootColors = (theme: string, set = true) => {
if (set) {
localStorage.setItem('theme', theme)
}
document.documentElement.setAttribute('class', `${theme} ${getCurrentFontSize()}`)
setOptions(getCurrentFontSize(), theme, getCurrentLanguage())
}
const setRootFontSize = (fontsize: string, set = true) => {
if (set) {
localStorage.setItem('fontsize', fontsize)
}
document.documentElement.setAttribute('class', `${getCurrentTheme()} ${fontsize}`)
themeOptions.set(new ThemeOptions(fontsize === 'normal-font' ? 16 : 14, getCurrentTheme() === 'theme-dark'))
setOptions(fontsize, getCurrentTheme(), getCurrentLanguage())
}
const setLanguage = async (language: string, set: boolean = true) => {
currentLanguage = language
if (set) {
localStorage.setItem('lang', language)
}
setMetadata(platform.metadata.locale, currentLanguage)
await loadPluginStrings(currentLanguage, set)
setOptions(getCurrentFontSize(), getCurrentTheme(), language)
}
setContext('theme', {
currentTheme,
setTheme: (name: string) => {
localStorage.setItem('theme', name)
setRootColors(name)
}
setTheme: setRootColors
})
setContext('fontsize', {
currentFontSize,
setFontSize: (fontsize: string) => {
localStorage.setItem('fontsize', fontsize)
setRootFontSize(fontsize)
}
setFontSize: setRootFontSize
})
setContext('lang', {
currentLanguage,
setLanguage: (lang: string) => {
currentLanguage = lang
localStorage.setItem('lang', lang)
location.reload()
}
setLanguage
})
const setDocumentLanguage = (): void => {
@ -60,9 +67,9 @@
}
onMount(() => {
setRootColors(currentTheme)
setRootFontSize(currentFontSize)
setMetadata(platform.metadata.locale, currentLanguage)
setRootColors(currentTheme, false)
setRootFontSize(currentFontSize, false)
setLanguage(currentLanguage, false)
loadPluginStrings(currentLanguage)
setDocumentLanguage()
})

View File

@ -41,8 +41,12 @@ export const getCurrentFontSize = (): string => localStorage.getItem('fontsize')
export const getCurrentLanguage = (): string => localStorage.getItem('lang') ?? 'en'
export class ThemeOptions {
constructor (readonly fontSize: number, readonly dark: boolean) {}
constructor (readonly fontSize: number, readonly dark: boolean, readonly language: string) {}
}
export const themeStore = writable<ThemeOptions>(
new ThemeOptions(getCurrentFontSize() === 'normal-font' ? 16 : 14, getCurrentTheme() === 'theme-dark')
new ThemeOptions(
getCurrentFontSize() === 'normal-font' ? 16 : 14,
getCurrentTheme() === 'theme-dark',
getCurrentLanguage()
)
)

View File

@ -21,6 +21,7 @@
import type { AnySvelteComponent, ListItem } from '../types'
import Icon from './Icon.svelte'
import ListView from './ListView.svelte'
import { themeStore } from '@hcengineering/theme'
export let icon: Asset | AnySvelteComponent
export let placeholder: IntlString = plugin.string.SearchDots
@ -29,7 +30,7 @@
let search: string = ''
let phTraslate: string = ''
$: if (placeholder) {
translate(placeholder, {}).then((res) => {
translate(placeholder, {}, $themeStore.language).then((res) => {
phTraslate = res
})
}

View File

@ -23,6 +23,7 @@
import Label from './Label.svelte'
import { resizeObserver } from '../resize'
import { floorFractionDigits } from '../utils'
import { themeStore } from '@hcengineering/theme'
export let label: IntlString | undefined = undefined
export let icon: Asset | AnySvelteComponent | undefined = undefined
@ -62,7 +63,7 @@
$: style = `max-width: ${
maxWidth || (parentWidth ? (icon ? `calc(${parentWidth}px - 1.25rem)` : `${parentWidth}px`) : 'max-content')
};`
$: translate(placeholder, placeholderParam ?? {}).then((res) => {
$: translate(placeholder, placeholderParam ?? {}, $themeStore.language).then((res) => {
phTraslate = res
})

View File

@ -15,6 +15,7 @@
<script lang="ts">
import type { IntlString } from '@hcengineering/platform'
import { translate } from '@hcengineering/platform'
import { themeStore } from '@hcengineering/theme'
export let label: IntlString
export let params: Record<string, any> = {}
@ -22,7 +23,7 @@
let _value: string | undefined = undefined
$: if (label !== undefined) {
translate(label, params ?? {})
translate(label, params ?? {}, $themeStore.language)
.then((r) => {
_value = r
})

View File

@ -17,12 +17,13 @@
import { IntlString, translate } from '@hcengineering/platform'
import { replaceURLs } from '../utils'
import { themeStore } from '@hcengineering/theme'
export let text: string | undefined = undefined
export let label: IntlString | undefined = undefined
export let params: Readonly<Record<string, any>> = {}
$: label && translate(label, params).then((result) => (text = result))
$: label && translate(label, params, $themeStore.language).then((result) => (text = result))
</script>
{#if text}

View File

@ -17,6 +17,7 @@
import { translate } from '@hcengineering/platform'
import plugin from '../plugin'
import Label from './Label.svelte'
import { themeStore } from '@hcengineering/theme'
export let label: IntlString | undefined = undefined
export let width: string | undefined = undefined
@ -30,7 +31,7 @@
let input: HTMLTextAreaElement
let phTraslate: string = ''
$: translate(placeholder, placeholderParam ?? {}).then((res) => {
$: translate(placeholder, placeholderParam ?? {}, $themeStore.language).then((res) => {
phTraslate = res
})

View File

@ -15,6 +15,7 @@
<script lang="ts">
import { translate } from '@hcengineering/platform'
import ui from '../plugin'
import { themeStore } from '@hcengineering/theme'
export let value: number
@ -28,20 +29,20 @@
async function formatTime (value: number) {
if (value > 0) {
if (value < HOUR) {
time = await translate(ui.string.MinutesAfter, { minutes: Math.floor(value / MINUTE) })
time = await translate(ui.string.MinutesAfter, { minutes: Math.floor(value / MINUTE) }, $themeStore.language)
} else if (value < DAY) {
time = await translate(ui.string.HoursAfter, { hours: Math.floor(value / HOUR) })
time = await translate(ui.string.HoursAfter, { hours: Math.floor(value / HOUR) }, $themeStore.language)
} else {
time = await translate(ui.string.DaysAfter, { days: Math.floor(value / DAY) })
time = await translate(ui.string.DaysAfter, { days: Math.floor(value / DAY) }, $themeStore.language)
}
} else {
const abs = Math.abs(value)
if (abs < HOUR) {
time = await translate(ui.string.MinutesBefore, { minutes: Math.floor(abs / MINUTE) })
time = await translate(ui.string.MinutesBefore, { minutes: Math.floor(abs / MINUTE) }, $themeStore.language)
} else if (abs < DAY) {
time = await translate(ui.string.HoursBefore, { hours: Math.floor(abs / HOUR) })
time = await translate(ui.string.HoursBefore, { hours: Math.floor(abs / HOUR) }, $themeStore.language)
} else {
time = await translate(ui.string.DaysBefore, { days: Math.floor(abs / DAY) })
time = await translate(ui.string.DaysBefore, { days: Math.floor(abs / DAY) }, $themeStore.language)
}
}
}

View File

@ -27,6 +27,7 @@
import { ticker } from '..'
import ui from '../plugin'
import { tooltip } from '../tooltips'
import { themeStore } from '@hcengineering/theme'
export let value: number | undefined
@ -47,15 +48,15 @@
let passed = now - value
if (passed < 0) passed = 0
if (passed < HOUR) {
time = await translate(ui.string.Minutes, { minutes: Math.floor(passed / MINUTE) })
time = await translate(ui.string.Minutes, { minutes: Math.floor(passed / MINUTE) }, $themeStore.language)
} else if (passed < DAY) {
time = await translate(ui.string.Hours, { hours: Math.floor(passed / HOUR) })
time = await translate(ui.string.Hours, { hours: Math.floor(passed / HOUR) }, $themeStore.language)
} else if (passed < MONTH) {
time = await translate(ui.string.Days, { days: Math.floor(passed / DAY) })
time = await translate(ui.string.Days, { days: Math.floor(passed / DAY) }, $themeStore.language)
} else if (passed < YEAR) {
time = await translate(ui.string.Months, { months: calculateMonthsPassed(now, value) })
time = await translate(ui.string.Months, { months: calculateMonthsPassed(now, value) }, $themeStore.language)
} else {
time = await translate(ui.string.Years, { years: Math.floor(passed / YEAR) })
time = await translate(ui.string.Years, { years: Math.floor(passed / YEAR) }, $themeStore.language)
}
}

View File

@ -5,7 +5,7 @@
// import { applicationShortcutKey } from '../../utils'
import { getCurrentLocation, location, navigate } from '../../location'
import { Theme } from '@hcengineering/theme'
import { Theme, themeStore } from '@hcengineering/theme'
import Component from '../Component.svelte'
import StatusComponent from '../Status.svelte'
@ -108,6 +108,7 @@
<div id="ui-root">
<div class="antiStatusBar">
<div class="flex-row-center h-full content-color">
{$themeStore.language}
<div
class="status-info"
style:margin-left={(isPortrait && docWidth <= 480) || (!isPortrait && docHeight <= 480) ? '1.5rem' : '0'}

View File

@ -17,6 +17,7 @@
import { IntlString, translate } from '@hcengineering/platform'
import { afterUpdate } from 'svelte'
import { WizardItemPosition, WizardItemPositionState } from '../..'
import { themeStore } from '@hcengineering/theme'
export let label: IntlString
export let position: WizardItemPosition
@ -44,7 +45,7 @@
}
$: style = getStyle(positionState)
$: label && translate(label, {}).then((t) => (translation = t))
$: label && translate(label, {}, $themeStore.language).then((t) => (translation = t))
afterUpdate(() => {
if (text) lenght = text.clientWidth + 32 > 300 ? 300 : text.clientWidth + 32

View File

@ -5,24 +5,25 @@ import core, {
Client,
Collection,
Doc,
getObjectValue,
Obj,
Ref,
TxCUD,
TxCollectionCUD,
TxCreateDoc,
TxCUD,
TxMixin,
TxOperations,
TxProcessor,
TxUpdateDoc
TxUpdateDoc,
getObjectValue
} from '@hcengineering/core'
import { Asset, IntlString, getResource, translate } from '@hcengineering/platform'
import { AnyComponent, AnySvelteComponent, ErrorPresenter } from '@hcengineering/ui'
import { getAttributePresenterClass } from '@hcengineering/presentation'
import { AnyComponent, AnySvelteComponent, ErrorPresenter, themeStore } from '@hcengineering/ui'
import view, { AttributeModel, BuildModelKey, BuildModelOptions } from '@hcengineering/view'
import { getObjectPresenter } from '@hcengineering/view-resources'
import { get } from 'svelte/store'
import { ActivityKey, activityKey } from './activity'
import activity from './plugin'
import { getAttributePresenterClass } from '@hcengineering/presentation'
const valueTypes: ReadonlyArray<Ref<Class<Doc>>> = [
core.class.TypeString,
@ -47,11 +48,12 @@ async function createPseudoViewlet (
): Promise<TxDisplayViewlet> {
const docClass: Class<Doc> = client.getModel().getObject(dtx.tx.objectClass)
let trLabel = docClass.label !== undefined ? await translate(docClass.label, {}) : undefined
const language = get(themeStore).language
let trLabel = docClass.label !== undefined ? await translate(docClass.label, {}, language) : undefined
if (dtx.collectionAttribute !== undefined) {
const itemLabel = (dtx.collectionAttribute.type as Collection<AttachedDoc>).itemLabel
if (itemLabel !== undefined) {
trLabel = await translate(itemLabel, {})
trLabel = await translate(itemLabel, {}, language)
}
}
@ -60,7 +62,7 @@ async function createPseudoViewlet (
if (presenter !== undefined) {
let collection = ''
if (dtx.collectionAttribute?.label !== undefined) {
collection = await translate(dtx.collectionAttribute.label, {})
collection = await translate(dtx.collectionAttribute.label, {}, language)
}
return {
display,

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { Class, Obj } from '@hcengineering/core'
import { translate } from '@hcengineering/platform'
import { DropdownLabels, DropdownTextItem } from '@hcengineering/ui'
import { DropdownLabels, DropdownTextItem, themeStore } from '@hcengineering/ui'
import automation from '../../plugin'
@ -10,7 +10,7 @@
async function getClassItems () {
const classItems: DropdownTextItem[] = []
for (const cl of classes) {
const label = await translate(cl.label, {})
const label = await translate(cl.label, {}, $themeStore.language)
classItems.push({ id: cl._id, label })
}
return classItems

View File

@ -6,7 +6,7 @@
import task, { calcRank, TodoItem } from '@hcengineering/task'
import { translate } from '@hcengineering/platform'
import presentation, { Card as Popup, createQuery, getClient } from '@hcengineering/presentation'
import { Label, Dropdown, EditBox } from '@hcengineering/ui'
import { Label, Dropdown, EditBox, themeStore } from '@hcengineering/ui'
import type { ListItem } from '@hcengineering/ui'
import board from '../../plugin'
@ -27,7 +27,7 @@
const templatesQuery = createQuery()
const dispatch = createEventDispatcher()
translate(board.string.ChecklistDropdownNone, {}).then((result) => {
translate(board.string.ChecklistDropdownNone, {}, $themeStore.language).then((result) => {
noneListItem.label = result
})

View File

@ -4,7 +4,7 @@
import { IntlString, translate } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation'
import { calcRank, State } from '@hcengineering/task'
import { DropdownLabels, DropdownTextItem } from '@hcengineering/ui'
import { DropdownLabels, DropdownTextItem, themeStore } from '@hcengineering/ui'
import board from '../../plugin'
export let object: Card
@ -36,7 +36,7 @@
ranks[index] = {
id: selectedRank,
label: await translate(board.string.Current, { label: ranks[index].label })
label: await translate(board.string.Current, { label: ranks[index].label }, $themeStore.language)
}
}
}

View File

@ -3,7 +3,7 @@
import { Ref } from '@hcengineering/core'
import { IntlString, translate } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation'
import { DropdownLabels, DropdownTextItem } from '@hcengineering/ui'
import { DropdownLabels, DropdownTextItem, themeStore } from '@hcengineering/ui'
import board from '../../plugin'
export let object: Card
@ -15,7 +15,7 @@
spacesQuery.query(board.class.Board, {}, async (result) => {
spaces = result.map(({ _id, name }) => ({ id: _id, label: name }))
const index = spaces.findIndex(({ id }) => id === object.space)
spaces[index].label = await translate(board.string.Current, { label: spaces[index].label })
spaces[index].label = await translate(board.string.Current, { label: spaces[index].label }, $themeStore.language)
})
</script>

View File

@ -4,7 +4,7 @@
import { IntlString, translate } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation'
import task, { State } from '@hcengineering/task'
import { DropdownLabels, DropdownTextItem } from '@hcengineering/ui'
import { DropdownLabels, DropdownTextItem, themeStore } from '@hcengineering/ui'
import board from '../../plugin'
export let object: Card
@ -23,7 +23,11 @@
;[{ _id: selected }] = result
if (object.space === space) {
const index = states.findIndex(({ id }) => id === object.state)
states[index].label = await translate(board.string.Current, { label: states[index].label })
states[index].label = await translate(
board.string.Current,
{ label: states[index].label },
$themeStore.language
)
selected = object.state
}
},

View File

@ -16,7 +16,7 @@
import { Event } from '@hcengineering/calendar'
import { DateRangeMode } from '@hcengineering/core'
import { translate } from '@hcengineering/platform'
import { DateRangePresenter } from '@hcengineering/ui'
import { DateRangePresenter, themeStore } from '@hcengineering/ui'
import calendar from '../plugin'
export let value: Event
@ -45,11 +45,11 @@
let passed = interval
if (interval < 0) passed = 0
if (passed < HOUR) {
return await translate(calendar.string.DueMinutes, { minutes: Math.floor(passed / MINUTE) })
return await translate(calendar.string.DueMinutes, { minutes: Math.floor(passed / MINUTE) }, $themeStore.language)
} else if (passed < DAY) {
return await translate(calendar.string.DueHours, { hours: Math.floor(passed / HOUR) })
return await translate(calendar.string.DueHours, { hours: Math.floor(passed / HOUR) }, $themeStore.language)
} else {
return await translate(calendar.string.DueDays, { days: Math.floor(passed / DAY) })
return await translate(calendar.string.DueDays, { days: Math.floor(passed / DAY) }, $themeStore.language)
}
}
</script>

View File

@ -16,7 +16,7 @@
import type { IntlString } from '@hcengineering/platform'
import { translate } from '@hcengineering/platform'
import { copyTextToClipboard } from '@hcengineering/presentation'
import type { PopupOptions } from '@hcengineering/ui'
import { PopupOptions, themeStore } from '@hcengineering/ui'
import {
Button,
createFocusManager,
@ -39,10 +39,10 @@
const dispatch = createEventDispatcher()
let input: HTMLInputElement
let phTraslate: string
$: translate(placeholder, {}).then((tr) => (phTraslate = tr))
$: translate(placeholder, {}, $themeStore.language).then((tr) => (phTraslate = tr))
let label: IntlString = plugin.string.CopyToClipboard
let lTraslate: string
$: translate(label, {}).then((tr) => (lTraslate = tr))
$: translate(label, {}, $themeStore.language).then((tr) => (lTraslate = tr))
let show: boolean = false
const copyChannel = (): void => {

View File

@ -25,7 +25,8 @@
IconCheck,
IconSearch,
Loading,
resizeObserver
resizeObserver,
themeStore
} from '@hcengineering/ui'
import { Filter } from '@hcengineering/view'
import { FILTER_DEBOUNCE_MS, FilterRemovedNotification, sortFilterValues } from '@hcengineering/view-resources'
@ -88,9 +89,14 @@
const removed = oldSize - (filter.value.length ?? 0)
if (removed > 0) {
onChange(filter)
addNotification(await translate(view.string.FilterUpdated, {}), filter.key.label, FilterRemovedNotification, {
description: await translate(view.string.FilterRemoved, { count: removed })
})
addNotification(
await translate(view.string.FilterUpdated, {}, $themeStore.language),
filter.key.label,
FilterRemovedNotification,
{
description: await translate(view.string.FilterRemoved, { count: removed }, $themeStore.language)
}
)
}
}
values = sortFilterValues(values, (v) => isSelected(v, filter.value))

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { Doc, Ref } from '@hcengineering/core'
import { Button, showPopup, eventToHTMLElement } from '@hcengineering/ui'
import { Button, showPopup, eventToHTMLElement, themeStore } from '@hcengineering/ui'
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
import contact, { Employee } from '@hcengineering/contact'
import { getClient } from '@hcengineering/presentation'
@ -36,7 +36,7 @@
let buttonTitle = ''
$: members = retrieveMembers(value)
$: translate(intlTitle, {}).then((res) => {
$: translate(intlTitle, {}, $themeStore.language).then((res) => {
buttonTitle = res
})

View File

@ -17,7 +17,7 @@
import { Account, AccountRole, DocumentQuery, getCurrentAccount, Ref, SortingOrder, Space } from '@hcengineering/core'
import { translate } from '@hcengineering/platform'
import presentation, { getClient } from '@hcengineering/presentation'
import { ActionIcon, IconAdd, IconClose, Label, SearchEdit, showPopup } from '@hcengineering/ui'
import { ActionIcon, IconAdd, IconClose, Label, SearchEdit, showPopup, themeStore } from '@hcengineering/ui'
import AddMembersPopup from './AddMembersPopup.svelte'
import UserInfo from './UserInfo.svelte'
@ -29,7 +29,7 @@
$: label = hierarchy.getClass(space._class).label
let spaceClass = ''
$: {
translate(label, {}).then((p) => (spaceClass = p.toLowerCase()))
translate(label, {}, $themeStore.language).then((p) => (spaceClass = p.toLowerCase()))
}
let search: string = ''
$: isSearch = search.trim().length

View File

@ -33,6 +33,7 @@
import tags from '@hcengineering/tags'
import { CollaborationDiffViewer } from '@hcengineering/text-editor'
import view from '@hcengineering/view'
import { themeStore } from '@hcengineering/ui'
import {
Button,
@ -174,14 +175,14 @@
[DocumentVersionState.Rejected]: ''
}
async function updateLabels (): Promise<void> {
async function updateLabels (lang: string): Promise<void> {
labels = {
[DocumentVersionState.Draft]: await translate(document.string.Draft, {}),
[DocumentVersionState.Approved]: await translate(document.string.Approved, {}),
[DocumentVersionState.Rejected]: await translate(document.string.Rejected, {})
[DocumentVersionState.Draft]: await translate(document.string.Draft, {}, lang),
[DocumentVersionState.Approved]: await translate(document.string.Approved, {}, lang),
[DocumentVersionState.Rejected]: await translate(document.string.Rejected, {}, lang)
}
}
updateLabels()
updateLabels($themeStore.language)
$: {
const ifo: any = [

View File

@ -21,7 +21,14 @@
import { translate } from '@hcengineering/platform'
import { Card, createQuery, getClient } from '@hcengineering/presentation'
import { EmployeeBox } from '@hcengineering/contact-resources'
import ui, { Button, DateRangePresenter, DropdownLabelsIntl, IconAttachment, Label } from '@hcengineering/ui'
import ui, {
Button,
DateRangePresenter,
DropdownLabelsIntl,
IconAttachment,
Label,
themeStore
} from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import hr from '../plugin'
import { getRequests } from '../utils'
@ -45,7 +52,7 @@
let types: RequestType[] = []
let type: RequestType | undefined = undefined
let typeLabel = ''
$: type && translate(type.label, {}).then((p) => (typeLabel = p))
$: type && translate(type.label, {}, $themeStore.language).then((p) => (typeLabel = p))
typesQuery.query(hr.class.RequestType, {}, (res) => {
types = res

View File

@ -26,6 +26,7 @@
import { OK, Status, Severity } from '@hcengineering/platform'
import type { IntlString } from '@hcengineering/platform'
import { translate } from '@hcengineering/platform'
import { themeStore } from '@hcengineering/ui'
import login from '../plugin'
import { onMount } from 'svelte'
@ -62,13 +63,15 @@
export let object: any
export let ignoreInitialValidation: boolean = false
async function validate () {
async function validate (language: string) {
if (ignoreInitialValidation) return
for (const field of fields) {
const v = object[field.name]
const f = field
if (!f.optional && (!v || v === '')) {
status = new Status(Severity.INFO, login.status.RequiredField, { field: await translate(field.i18n, {}) })
status = new Status(Severity.INFO, login.status.RequiredField, {
field: await translate(field.i18n, {}, language)
})
return
}
if (f.id !== undefined) {
@ -77,8 +80,8 @@
const v = object[field.name]
if (v !== object[f.name]) {
status = new Status(Severity.INFO, login.status.FieldsDoNotMatch, {
field: await translate(field.i18n, {}),
field2: await translate(f.i18n, {})
field: await translate(field.i18n, {}, language),
field2: await translate(f.i18n, {}, language)
})
return
}
@ -88,7 +91,7 @@
if (!f.rule.test(v)) {
status = new Status(Severity.INFO, login.status.IncorrectValue, {
field: await translate(field.i18n, {}),
descr: field.ruleDescr ? await translate(field.ruleDescr, {}) : ''
descr: field.ruleDescr ? await translate(field.ruleDescr, {}, language) : ''
})
return
}
@ -96,7 +99,7 @@
}
status = OK
}
validate()
validate($themeStore.language)
let inAction = false

View File

@ -15,14 +15,14 @@
-->
<script lang="ts">
import { translate } from '@hcengineering/platform'
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
import { deviceOptionsStore as deviceInfo, themeStore } from '@hcengineering/ui'
import plugin from '../plugin'
export let landscape: boolean = false
export let mini: boolean = false
let slogan = ''
translate(plugin.string.Slogan, {})
translate(plugin.string.Slogan, {}, $themeStore.language)
.then((r) => {
slogan = r
})

View File

@ -18,7 +18,7 @@
import { getMetadata } from '@hcengineering/platform'
import presentation from '@hcengineering/presentation'
import { themeStore } from '@hcengineering/theme'
import { themeStore } from '@hcengineering/ui'
import workbench from '@hcengineering/workbench'
import { onDestroy } from 'svelte'
import Confirmation from './Confirmation.svelte'

View File

@ -17,7 +17,15 @@
import { getEmbeddedLabel, translate } from '@hcengineering/platform'
import presentation, { Card, getClient } from '@hcengineering/presentation'
import setting from '../plugin'
import { AnyComponent, Component, DropdownIntlItem, DropdownLabelsIntl, EditBox, Label } from '@hcengineering/ui'
import {
AnyComponent,
Component,
DropdownIntlItem,
DropdownLabelsIntl,
EditBox,
Label,
themeStore
} from '@hcengineering/ui'
import view from '@hcengineering/view-resources/src/plugin'
import { createEventDispatcher } from 'svelte'
@ -33,7 +41,7 @@
const hierarchy = client.getHierarchy()
const dispatch = createEventDispatcher()
translate(attribute.label, {}).then((p) => (name = p))
translate(attribute.label, {}, $themeStore.language).then((p) => (name = p))
async function save (): Promise<void> {
const update: DocumentUpdate<AnyAttribute> = {}

View File

@ -20,6 +20,7 @@
import setting from '../plugin'
import PluginCard from './PluginCard.svelte'
import { translate } from '@hcengineering/platform'
import { themeStore } from '@hcengineering/ui'
export let _id: Ref<Integration>
export let _class: Ref<Class<Integration>>
@ -49,7 +50,7 @@
)
let title: string = ''
translate(setting.string.Integrations, {}).then((res) => (title = res))
translate(setting.string.Integrations, {}, $themeStore.language).then((res) => (title = res))
</script>
{#if integration}

View File

@ -47,7 +47,7 @@
let phTraslate: string = ''
$: if (placeholder) {
translate(placeholder, {}).then((res) => {
translate(placeholder, {}, $themeStore.language).then((res) => {
phTraslate = res
})
}

View File

@ -18,7 +18,7 @@
import { KeyedAttribute } from '@hcengineering/presentation'
import { TagElement, TagReference } from '@hcengineering/tags'
import type { ButtonKind, ButtonSize, TooltipAlignment } from '@hcengineering/ui'
import { Button, showPopup } from '@hcengineering/ui'
import { Button, showPopup, themeStore } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tags from '../plugin'
import TagsPopup from './TagsPopup.svelte'
@ -43,7 +43,7 @@
$: itemLabel = (key.attr.type as Collection<AttachedDoc>).itemLabel
$: translate(itemLabel ?? key.attr.label, {}).then((v) => {
$: translate(itemLabel ?? key.attr.label, {}, $themeStore.language).then((v) => {
keyLabel = v
})
@ -90,7 +90,7 @@
<svelte:fragment slot="content">
{#if items.length > 0}
<span class="flex-row-center flex-nowrap overflow-label disabled">
{#await translate(countLabel, { count: items.length }) then text}
{#await translate(countLabel, { count: items.length }, $themeStore.language) then text}
{text}
{/await}
</span>

View File

@ -24,7 +24,8 @@
IconClose,
Label,
ShowMore,
showPopup
showPopup,
themeStore
} from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tags from '../plugin'
@ -45,7 +46,7 @@
$: itemLabel = (key.attr.type as Collection<AttachedDoc>).itemLabel
$: translate(itemLabel ?? key.attr.label, {}).then((v) => {
$: translate(itemLabel ?? key.attr.label, {}, $themeStore.language).then((v) => {
keyLabel = v
})

View File

@ -17,7 +17,7 @@
import { Asset, IntlString, translate } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation'
import { TagCategory, TagElement } from '@hcengineering/tags'
import { AnySvelteComponent, Button, Label, SearchEdit, showPopup, IconAdd } from '@hcengineering/ui'
import { AnySvelteComponent, Button, Label, SearchEdit, showPopup, IconAdd, themeStore } from '@hcengineering/ui'
import { TableBrowser } from '@hcengineering/view-resources'
import tags from '../plugin'
import CategoryBar from './CategoryBar.svelte'
@ -32,7 +32,7 @@
export let onTag: ((tag: TagElement) => void) | undefined = undefined
let keyTitle: string
$: translate(item, {}).then((t) => {
$: translate(item, {}, $themeStore.language).then((t) => {
keyTitle = t.toLowerCase()
})

View File

@ -18,7 +18,7 @@
import { createQuery } from '@hcengineering/presentation'
import { Component, Project } from '@hcengineering/tracker'
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
import { Button, ButtonShape, Label, SelectPopup, eventToHTMLElement, showPopup } from '@hcengineering/ui'
import { Button, ButtonShape, Label, SelectPopup, eventToHTMLElement, showPopup, themeStore } from '@hcengineering/ui'
import tracker from '../plugin'
export let value: Ref<Component> | null | undefined
@ -57,7 +57,7 @@
$: handleSelectedComponentIdUpdated(value, rawComponents)
$: translate(tracker.string.NoComponent, {}).then((result) => (defaultComponentLabel = result))
$: translate(tracker.string.NoComponent, {}, $themeStore.language).then((result) => (defaultComponentLabel = result))
$: componentText = shouldShowLabel ? selectedComponent?.label ?? defaultComponentLabel : undefined
const handleSelectedComponentIdUpdated = async (

View File

@ -50,7 +50,8 @@
FocusHandler,
IconAttachment,
Label,
showPopup
showPopup,
themeStore
} from '@hcengineering/ui'
import { Attachment } from '@hcengineering/attachment'
import { AttachmentPresenter } from '@hcengineering/attachment-resources'
@ -417,11 +418,16 @@
]
: [{ parentId: _id, parentTitle: value.title }]
await subIssuesComponent.save(parents, _id)
addNotification(await translate(tracker.string.IssueCreated, {}), getTitle(object.title), IssueNotification, {
issueId: _id,
subTitlePostfix: (await translate(tracker.string.CreatedOne, {})).toLowerCase(),
issueUrl: currentProject && generateIssueShortLink(getIssueId(currentProject, value as Issue))
})
addNotification(
await translate(tracker.string.IssueCreated, {}, $themeStore.language),
getTitle(object.title),
IssueNotification,
{
issueId: _id,
subTitlePostfix: (await translate(tracker.string.CreatedOne, {}, $themeStore.language)).toLowerCase(),
issueUrl: currentProject && generateIssueShortLink(getIssueId(currentProject, value as Issue))
}
)
// Create an backlink to document
await createBacklinks(client, _id, tracker.class.Issue, _id, object.description)

View File

@ -15,7 +15,7 @@
<script lang="ts">
import { WithLookup } from '@hcengineering/core'
import { Component } from '@hcengineering/tracker'
import { Icon, tooltip } from '@hcengineering/ui'
import { Icon, tooltip, themeStore } from '@hcengineering/ui'
import tracker from '../../plugin'
import view from '@hcengineering/view'
import { DocNavLink } from '@hcengineering/view-resources'
@ -35,7 +35,7 @@
$: if (value !== undefined) {
label = value.label
} else {
translate(tracker.string.NoComponent, {})
translate(tracker.string.NoComponent, {}, $themeStore.language)
.then((r) => {
label = r
})

View File

@ -24,6 +24,7 @@
<script lang="ts">
import { translate } from '@hcengineering/platform'
import tracker from '../../plugin'
import { themeStore } from '@hcengineering/ui'
export let value: number
@ -32,15 +33,15 @@
async function formatTime (passed: number) {
if (passed < 0) passed = 0
if (passed < HOUR) {
time = await translate(tracker.string.DurMinutes, { minutes: Math.floor(passed / MINUTE) })
time = await translate(tracker.string.DurMinutes, { minutes: Math.floor(passed / MINUTE) }, $themeStore.language)
} else if (passed < DAY) {
time = await translate(tracker.string.DurHours, { hours: Math.floor(passed / HOUR) })
time = await translate(tracker.string.DurHours, { hours: Math.floor(passed / HOUR) }, $themeStore.language)
} else if (passed < MONTH) {
time = await translate(tracker.string.DurDays, { days: Math.floor(passed / DAY) })
time = await translate(tracker.string.DurDays, { days: Math.floor(passed / DAY) }, $themeStore.language)
} else if (passed < YEAR) {
time = await translate(tracker.string.DurMonths, { months: Math.floor(passed / MONTH) })
time = await translate(tracker.string.DurMonths, { months: Math.floor(passed / MONTH) }, $themeStore.language)
} else {
time = await translate(tracker.string.DurYears, { years: Math.floor(passed / YEAR) })
time = await translate(tracker.string.DurYears, { years: Math.floor(passed / YEAR) }, $themeStore.language)
}
}

View File

@ -3,7 +3,14 @@
import { IntlString, translate } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation'
import { Issue } from '@hcengineering/tracker'
import { Button, IconDetails, IconDetailsFilled, IModeSelector, resolvedLocationStore } from '@hcengineering/ui'
import {
Button,
IconDetails,
IconDetailsFilled,
IModeSelector,
resolvedLocationStore,
themeStore
} from '@hcengineering/ui'
import view, { Viewlet } from '@hcengineering/view'
import {
FilterBar,
@ -61,7 +68,7 @@
$: active = $activeViewlet[key]
$: if (!label && title) {
translate(title, {}).then((res) => {
translate(title, {}, $themeStore.language).then((res) => {
label = res
})
}

View File

@ -18,7 +18,7 @@
import { createQuery } from '@hcengineering/presentation'
import { Component, Milestone } from '@hcengineering/tracker'
import type { ButtonKind, ButtonSize, LabelAndProps } from '@hcengineering/ui'
import { Button, ButtonShape, eventToHTMLElement, SelectPopup, showPopup, Label } from '@hcengineering/ui'
import { Button, ButtonShape, eventToHTMLElement, SelectPopup, showPopup, Label, themeStore } from '@hcengineering/ui'
import tracker from '../../plugin'
import { milestoneStatusAssets } from '../../types'
@ -59,7 +59,7 @@
$: handleSelectedMilestoneIdUpdated(value, rawMilestones)
$: translate(tracker.string.Milestone, {}).then((result) => (defaultMilestoneLabel = result))
$: translate(tracker.string.Milestone, {}, $themeStore.language).then((result) => (defaultMilestoneLabel = result))
const milestoneIcon = tracker.icon.Milestone
$: milestoneText = shouldShowLabel ? selectedMilestone?.label ?? defaultMilestoneLabel : undefined

View File

@ -20,6 +20,7 @@
import { translate } from '@hcengineering/platform'
import MilestonePopup from './MilestonePopup.svelte'
import { Milestone } from '@hcengineering/tracker'
import { themeStore } from '@hcengineering/ui'
export let milestones: Milestone[]
export let moveAndDeleteMilestone: (selectedMilestone?: Milestone) => Promise<void>
@ -27,7 +28,7 @@
let selectedMilestone: Milestone | undefined
let noMilestoneLabel: string
$: translate(tracker.string.NoMilestone, {}).then((label) => (noMilestoneLabel = label))
$: translate(tracker.string.NoMilestone, {}, $themeStore.language).then((label) => (noMilestoneLabel = label))
$: selectedMilestoneLabel = selectedMilestone?.label ?? noMilestoneLabel
</script>

View File

@ -3,7 +3,15 @@
import { IntlString, translate } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation'
import { IssueTemplate } from '@hcengineering/tracker'
import { Button, IconAdd, IconDetails, IconDetailsFilled, resolvedLocationStore, showPopup } from '@hcengineering/ui'
import {
Button,
IconAdd,
IconDetails,
IconDetailsFilled,
resolvedLocationStore,
showPopup,
themeStore
} from '@hcengineering/ui'
import view, { Viewlet } from '@hcengineering/view'
import {
FilterBar,
@ -59,7 +67,7 @@
$: active = $activeViewlet[key]
$: if (!label && title) {
translate(title, {}).then((res) => {
translate(title, {}, $themeStore.language).then((res) => {
label = res
})
}

View File

@ -15,6 +15,7 @@
<script lang="ts">
import { IntlString, translate } from '@hcengineering/platform'
import { onMount } from 'svelte'
import { themeStore } from '@hcengineering/ui'
export let value: string | undefined = undefined
export let placeholder: IntlString | undefined
@ -26,7 +27,7 @@
async function updatePlaceholderTranslation (ph: IntlString | undefined) {
if (ph) {
placeholderTranslation = await translate(ph, {})
placeholderTranslation = await translate(ph, {}, $themeStore.language)
}
}

View File

@ -27,7 +27,7 @@ import {
import { Resources, translate } from '@hcengineering/platform'
import { getClient, MessageBox, ObjectSearchResult } from '@hcengineering/presentation'
import { Issue, Milestone, Project } from '@hcengineering/tracker'
import { showPopup } from '@hcengineering/ui'
import { showPopup, themeStore } from '@hcengineering/ui'
import ComponentEditor from './components/components/ComponentEditor.svelte'
import ComponentPresenter from './components/components/ComponentPresenter.svelte'
import ComponentRefPresenter from './components/components/ComponentRefPresenter.svelte'
@ -136,6 +136,8 @@ import CreateProject from './components/projects/CreateProject.svelte'
import ProjectPresenter from './components/projects/ProjectPresenter.svelte'
import ProjectSpacePresenter from './components/projects/ProjectSpacePresenter.svelte'
import { get } from 'svelte/store'
export { default as SubIssueList } from './components/issues/edit/SubIssueList.svelte'
export async function queryIssue<D extends Issue> (
@ -286,7 +288,7 @@ async function moveAndDeleteMilestones (
oldMilestones: Milestone[],
newMilestone?: Milestone
): Promise<void> {
const noMilestoneLabel = await translate(tracker.string.NoMilestone, {})
const noMilestoneLabel = await translate(tracker.string.NoMilestone, {}, get(themeStore).language)
showPopup(
MessageBox,
@ -302,8 +304,8 @@ async function moveAndDeleteMilestones (
(result?: boolean) => {
if (result === true) {
for (const oldMilestone of oldMilestones) {
void moveIssuesToAnotherMilestone(client, oldMilestone, newMilestone).then((succes) => {
if (succes) {
void moveIssuesToAnotherMilestone(client, oldMilestone, newMilestone).then((success) => {
if (success) {
void deleteObject(client, oldMilestone)
}
})

View File

@ -27,7 +27,8 @@
IconSearch,
deviceOptionsStore,
capitalizeFirstLetter,
formatKey
formatKey,
themeStore
} from '@hcengineering/ui'
import { Action, ViewContext } from '@hcengineering/view'
import { filterActions, getSelection } from '../actions'
@ -111,7 +112,7 @@
search = search.trim().toLowerCase()
if (search.length > 0) {
for (const a of actions) {
const tr = await translate(a.label, {})
const tr = await translate(a.label, {}, $themeStore.language)
if (tr.toLowerCase().indexOf(search) !== -1) {
res.push(a)
}

View File

@ -30,7 +30,7 @@
getClient,
hasResource
} from '@hcengineering/presentation'
import { AnyComponent, Button, Component, IconMixin, IconMoreH, showPopup } from '@hcengineering/ui'
import { AnyComponent, Button, Component, IconMixin, IconMoreH, showPopup, themeStore } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { createEventDispatcher, onDestroy } from 'svelte'
import { ContextMenu, ParentsNavigator } from '..'
@ -230,7 +230,7 @@
return name
}
const label = hierarchy.getClass(object._class).label
return await translate(label, {})
return await translate(label, {}, $themeStore.language)
}
async function getHeaderEditor (_class: Ref<Class<Doc>>): Promise<AnyComponent | undefined> {

View File

@ -15,7 +15,7 @@
-->
<script lang="ts">
import { getClient } from '@hcengineering/presentation'
import { Button, Label, Status as StatusControl } from '@hcengineering/ui'
import { Button, Label, Status as StatusControl, themeStore } from '@hcengineering/ui'
import core, { Class, Client, Doc, Ref, SortingOrder, Space } from '@hcengineering/core'
import { getResource, OK, Resource, Status, translate } from '@hcengineering/platform'
@ -41,9 +41,11 @@
$: {
const doc = docs[0]
if (space === undefined) space = doc.space
translate(hierarchy.getClass(doc._class).label, {}).then((res) => (label = res.toLocaleLowerCase()))
translate(hierarchy.getClass(doc._class).label, {}, $themeStore.language).then(
(res) => (label = res.toLocaleLowerCase())
)
}
$: _class && translate(_class, {}).then((res) => (classLabel = res.toLocaleLowerCase()))
$: _class && translate(_class, {}, $themeStore.language).then((res) => (classLabel = res.toLocaleLowerCase()))
async function move (doc: Doc): Promise<void> {
const needStates = currentSpace ? hierarchy.isDerived(currentSpace._class, task.class.SpaceWithStates) : false

View File

@ -18,7 +18,16 @@
import { getAttributePresenterClass, getClient } from '@hcengineering/presentation'
import type { State } from '@hcengineering/task'
import task from '@hcengineering/task'
import { AnyComponent, Component, eventToHTMLElement, Icon, IconClose, Label, showPopup } from '@hcengineering/ui'
import {
AnyComponent,
Component,
eventToHTMLElement,
Icon,
IconClose,
Label,
showPopup,
themeStore
} from '@hcengineering/ui'
import { Filter, FilterMode } from '@hcengineering/view'
import { createEventDispatcher, onDestroy } from 'svelte'
import view from '../../plugin'
@ -55,7 +64,7 @@
let countLabel: string = ''
async function getLabel (): Promise<void> {
const count = isState ? await getCountStates(currentFilter.value) : currentFilter.value.length
countLabel = await translate(view.string.FilterStatesCount, { value: count })
countLabel = await translate(view.string.FilterStatesCount, { value: count }, $themeStore.language)
}
let valueComponent: AnyComponent | undefined

View File

@ -25,7 +25,8 @@
IconSearch,
Label,
Loading,
resizeObserver
resizeObserver,
themeStore
} from '@hcengineering/ui'
import { Filter, GrouppingManager } from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
@ -106,9 +107,14 @@
const removed = oldSize - (filter.value.length ?? 0)
if (removed > 0) {
onChange(filter)
addNotification(await translate(view.string.FilterUpdated, {}), filter.key.label, FilterRemovedNotification, {
description: await translate(view.string.FilterRemoved, { count: removed })
})
addNotification(
await translate(view.string.FilterUpdated, {}, $themeStore.language),
filter.key.label,
FilterRemovedNotification,
{
description: await translate(view.string.FilterRemoved, { count: removed }, $themeStore.language)
}
)
}
}
values = sortFilterValues(values, (v) => isSelected(v, filter.value))

View File

@ -170,8 +170,14 @@
}
)
const workspaceId = $location.path[1]
onDestroy(
location.subscribe(async (loc) => {
if (workspaceId !== $location.path[1]) {
// Switch of workspace
return
}
closeTooltip()
closePopup()