Merge branch 'develop' of https://github.com/hcengineering/platform into staging-new

Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
Artem Savchenko
2025-10-07 07:33:38 +07:00
16 changed files with 157 additions and 46 deletions
+1
View File
@@ -423,6 +423,7 @@ export async function configurePlatform (onWorkbenchConnect?: () => Promise<void
addLocation(emojiId, () => import(/* webpackChunkName: "achievement" */ '@hcengineering/emoji-resources'))
addLocation(billingId, () => import(/* webpackChunkName: "billing" */ '@hcengineering/billing-resources'))
addLocation(hulyMailId, () => import(/* webpackChunkName: "huly-mail" */ '@hcengineering/huly-mail-resources'))
addLocation(aiAssistantId, () => import(/* webpackChunkName: "ai-assistant" */ '@hcengineering/ai-assistant-resources'))
setMetadata(client.metadata.FilterModel, 'ui')
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
+5 -4
View File
@@ -387,6 +387,7 @@ export async function configurePlatform() {
if (err.message.includes('Loading chunk') && i != 4) {
continue
}
console.log('reload due to loading error')
location.reload()
}
}
@@ -585,10 +586,10 @@ export async function configurePlatform() {
addLocation(processId, () => import(/* webpackChunkName: "process" */ '@hcengineering/process-resources'))
addLocation(achievementId, () => import(/* webpackChunkName: "achievement" */ '@hcengineering/achievement-resources'))
addLocation(communicationId, () => import(/* webpackChunkName: "communication" */ '@hcengineering/communication-resources'))
addLocation(emojiId, () => import(/* webpackChunkName: "achievement" */ '@hcengineering/emoji-resources'))
addLocation(billingId, () => import(/* webpackChunkName: "achievement" */ '@hcengineering/billing-resources'))
addLocation(hulyMailId, () => import(/* webpackChunkName: "achievement" */ '@hcengineering/huly-mail-resources'))
addLocation(aiAssistantId, () => import(/* webpackChunkName: "achievement" */ '@hcengineering/ai-assistant-resources'))
addLocation(emojiId, () => import(/* webpackChunkName: "emoji" */ '@hcengineering/emoji-resources'))
addLocation(billingId, () => import(/* webpackChunkName: "billing" */ '@hcengineering/billing-resources'))
addLocation(hulyMailId, () => import(/* webpackChunkName: "hulyMail" */ '@hcengineering/huly-mail-resources'))
addLocation(aiAssistantId, () => import(/* webpackChunkName: "ai-assistant" */ '@hcengineering/ai-assistant-resources'))
setMetadata(client.metadata.FilterModel, 'ui')
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
+1
View File
@@ -125,6 +125,7 @@ export type PropertyType = any
export interface UXObject extends Obj {
label: IntlString
icon?: Asset
color?: number
hidden?: boolean
readonly?: boolean
}
@@ -19,6 +19,7 @@
export let icon: number | number[] | Ref<Blob>
export let size: IconSize
export let inline: boolean = false
let value: string | undefined = parseIcon(icon)
@@ -39,7 +40,7 @@
$: value = parseIcon(icon)
</script>
<div class="emoji-{size} flex-row-center emoji">
<span class="emoji-{size} flex-row-center emoji" class:inline class:fitSize={inline}>
{#if value !== undefined}
{value}
{:else}
@@ -47,7 +48,7 @@
<img src={iconBlob.src} srcset={iconBlob.srcset} alt="icon" />
{/await}
{/if}
</div>
</span>
<style lang="scss">
.emoji {
@@ -59,6 +60,15 @@
img {
margin: 0;
}
&.inline {
display: inline;
img {
margin-bottom: -0.125rem;
vertical-align: unset;
}
}
}
.emoji-inline {
width: 1em;
@@ -70,6 +80,13 @@
height: 0.75rem;
font-size: 0.75rem;
}
.emoji-smaller {
width: 0.875rem;
height: 0.875rem;
font-size: 0.875rem;
}
.emoji-small {
width: 1rem;
height: 1rem;
@@ -89,6 +106,7 @@
width: inherit;
height: inherit;
}
.emoji-smaller,
.emoji-x-small,
.emoji-small,
.emoji-medium,
@@ -21,6 +21,7 @@
import { createQuery, getClient } from '../../utils'
import MessageBox from '../MessageBox.svelte'
import presentation from '../../plugin'
import { IconWithEmoji } from '../../index'
export let _id: Ref<Doc> | undefined = undefined
export let _class: Ref<Class<Doc>> | undefined = undefined
@@ -35,12 +36,14 @@
let broken: boolean = false
const withoutDoc: Ref<Doc>[] = [contact.mention.Here, contact.mention.Everyone]
$: clazz = _class ? hierarchy.findClass(_class) : undefined
$: icon =
_class !== undefined &&
hierarchy.hasClass(_class) &&
!withoutDoc.includes(_id as any) &&
!hierarchy.isDerived(_class, contact.class.Contact)
? hierarchy.getClass(_class).icon
? clazz?.icon
: null
$: if (_class != null && _id != null && hierarchy.hasClass(_class) && !withoutDoc.includes(_id as any)) {
@@ -64,8 +67,12 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<span class="antiMention" class:transparent class:broken on:click={onBrokenLinkClick}>
{#if icon}<Icon {icon} size="small" />{' '}{:else}@{/if}{#if _id === contact.mention.Here}<span class="lower"
><Label label={contact.string.Here} /></span
{#if icon}{#if icon === view.ids.IconWithEmoji}<IconWithEmoji
icon={clazz?.color ?? 0}
size={'smaller'}
inline
/>{:else}<Icon {icon} size="small" />{/if}{' '}{:else}@{/if}{#if _id === contact.mention.Here}<span
class="lower"><Label label={contact.string.Here} /></span
>{:else if _id === contact.mention.Everyone}<span class="lower"><Label label={contact.string.Everyone} /></span
>{:else}{title}{/if}
</span>
@@ -62,6 +62,7 @@ addTxListener((txes: Tx[]) => {
if (cud.objectClass === core.class.PluginConfiguration) {
if (cud.operations.enabled !== undefined) {
// Plugin enabled/disabled we need to refresh
console.log('reload due to changed plugin configuration')
location.reload()
}
}
+1 -1
View File
@@ -532,7 +532,7 @@
&.transparent {
background-color: transparent;
&:hover {
background-color: transparent;
}
@@ -14,10 +14,10 @@
-->
<script lang="ts">
import { getClient } from '@hcengineering/presentation'
import { type Card } from '@hcengineering/card'
import cardPlugin, { type Card } from '@hcengineering/card'
import { type ActivityMessage } from '@hcengineering/communication-types'
import view from '@hcengineering/view'
import { DocNavLink } from '@hcengineering/view-resources'
import { DocNavLink, ObjectIcon } from '@hcengineering/view-resources'
import { Icon, Label } from '@hcengineering/ui'
import communication from './../../../plugin'
@@ -34,11 +34,13 @@
</script>
<span class="container flex-gap-1 overflow-label">
{#if clazz.icon}
<span class="icon mr-1">
<Icon icon={clazz.icon} size="small" />
</span>
{/if}
<span class="icon mr-1">
{#if hierarchy.isDerived(card._class, communication.type.Direct)}
<Icon icon={clazz.icon ?? cardPlugin.icon.Card} size="small" />
{:else}
<ObjectIcon value={card} size={'small'} />
{/if}
</span>
{#if action === 'create'}
<Label label={communication.string.New} />
@@ -14,12 +14,13 @@
-->
<script lang="ts">
import { onMount } from 'svelte'
import { Icon, Label } from '@hcengineering/ui'
import { Icon, Label, tooltip } from '@hcengineering/ui'
import contact, { SocialIdentity, SocialIdentityProvider } from '@hcengineering/contact'
import { getClient } from '@hcengineering/presentation'
export let value: SocialIdentity
export let socialIdProvider: SocialIdentityProvider | undefined = undefined
export let shouldShowAvatar = true
const client = getClient()
@@ -33,12 +34,22 @@
</script>
{#if socialIdProvider != null}
<div class="flex-row-center flex-gap-2">
<div class="icon"><Icon size="full" {icon} /></div>
<div class="flex-row-center" class:flex-gap-2={shouldShowAvatar}>
<div
class="icon"
use:tooltip={{
component: Label,
props: { label: socialIdProvider.label }
}}
>
<Icon size="full" {icon} />
</div>
<div class="flex-col flex-gap-0-5">
<div>{value.displayValue ?? value.value}</div>
<div class="type"><Label label={socialIdProvider.label} /></div>
{#if shouldShowAvatar}
<div class="type"><Label label={socialIdProvider.label} /></div>
{/if}
</div>
</div>
{/if}
+2 -1
View File
@@ -402,7 +402,8 @@ export default async (): Promise<Resources> => ({
ContactNamePresenter,
PersonIdFilter,
AssigneePopup,
TranslationSettings
TranslationSettings,
SocialIdentityPresenter
},
completion: {
EmployeeQuery: async (
@@ -107,6 +107,7 @@
time={status.params.notBefore * 1000}
showHours={true}
on:timeout={() => {
console.log('reload due to token inactive')
window.location.reload()
}}
/>
@@ -99,7 +99,7 @@
dangerous: true,
action: async () => {
await doRelease()
console.log('reload due to primary social id release')
location.reload()
}
})
@@ -21,13 +21,13 @@ import { SvelteRenderer } from '../node-view'
import { ReferenceNode, type ReferenceNodeProps, type ReferenceOptions } from '@hcengineering/text'
import Suggestion, { type SuggestionKeyDownProps, type SuggestionOptions, type SuggestionProps } from './suggestion'
import { type Class, type Doc, type Ref } from '@hcengineering/core'
import { type Blob, type Class, type Doc, type Ref } from '@hcengineering/core'
import { getMetadata, getResource, translate } from '@hcengineering/platform'
import presentation, { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
import presentation, { createQuery, getBlobRef, getClient, MessageBox } from '@hcengineering/presentation'
import view from '@hcengineering/view'
import contact from '@hcengineering/contact'
import { parseLocation, showPopup, tooltip, type LabelAndProps, type Location } from '@hcengineering/ui'
import { parseLocation, showPopup, tooltip, type LabelAndProps, type Location, fromCodePoint } from '@hcengineering/ui'
import workbench, { type Application } from '@hcengineering/workbench'
export interface ReferenceExtensionOptions extends ReferenceOptions {
@@ -145,7 +145,7 @@ export const ReferenceExtension = ReferenceNode.extend<ReferenceExtensionOptions
titleSpan.innerText = `${iconUrl !== '' ? '' : options.suggestion.char}${trans}`
root.classList.add('lower')
} else {
titleSpan.innerText = `${iconUrl !== '' ? '' : options.suggestion.char}${props.label ?? props.id}`
titleSpan.innerText = `${iconUrl !== '' || emojiCode != null ? '' : options.suggestion.char}${props.label ?? props.id}`
}
if (broken) {
root.classList.add('broken')
@@ -156,10 +156,17 @@ export const ReferenceExtension = ReferenceNode.extend<ReferenceExtensionOptions
const icon =
objectclass !== undefined && !hierarchy.isDerived(objectclass, contact.class.Contact)
? hierarchy.getClass(objectclass).icon
? hierarchy.findClass(objectclass)?.icon
: undefined
const iconUrl = typeof icon === 'string' ? getMetadata(icon) ?? 'https://anticrm.org/logo.svg' : ''
let iconUrl = ''
let emojiCode: number | undefined
if (icon === view.ids.IconWithEmoji) {
emojiCode = hierarchy.findClass(objectclass)?.color ?? 0
} else if (typeof icon === 'string') {
iconUrl = getMetadata(icon) ?? 'https://anticrm.org/logo.svg'
}
if (iconUrl !== '') {
const svg = root.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'svg'))
@@ -168,6 +175,10 @@ export const ReferenceExtension = ReferenceNode.extend<ReferenceExtensionOptions
svg.setAttribute('fill', 'currentColor')
const use = svg.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'use'))
use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', iconUrl)
} else if (emojiCode !== undefined) {
const emojiEl = createEmojiElement(emojiCode)
root.appendChild(emojiEl)
root.appendChild(document.createTextNode(' '))
}
let tooltipHandle: any
@@ -575,3 +586,49 @@ function makeQuery (obj: Record<string, string | number | boolean | null | undef
})
.join('&')
}
function parseEmoji (icon: number | number[] | Ref<Blob>): string | undefined {
if (typeof icon === 'object' && '__ref' in icon) {
return undefined
}
try {
return Array.isArray(icon) ? fromCodePoint(...icon) : fromCodePoint(icon as number)
} catch (err) {}
return undefined
}
function createEmojiElement (emojiCode: number | number[] | Ref<Blob>): HTMLElement {
const root = document.createElement('span')
Object.assign(root.style, {
display: 'inline',
flexShrink: '0',
color: 'black'
})
const value = parseEmoji(emojiCode as any)
if (value !== undefined) {
root.textContent = value
return root
}
const placeholder = document.createElement('span')
placeholder.textContent = '@'
root.appendChild(placeholder)
void getBlobRef(emojiCode as Ref<Blob>).then((iconBlob) => {
if (!root.isConnected) return
const img = document.createElement('img')
img.alt = 'icon'
img.src = iconBlob.src
if (iconBlob.srcset != null) img.srcset = iconBlob.srcset
img.style.display = 'inline'
img.style.margin = '0'
img.style.maxHeight = '0.875rem'
img.style.marginBottom = '-0.125rem'
img.style.verticalAlign = 'unset'
root.replaceChild(img, placeholder)
})
return root
}
@@ -15,7 +15,7 @@
<script lang="ts">
import { Class, Doc, Ref } from '@hcengineering/core'
import { getResource, translateCB } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { createQuery, getClient, IconWithEmoji } from '@hcengineering/presentation'
import { AnyComponent, Icon, LabelAndProps, themeStore, tooltip } from '@hcengineering/ui'
import view from '@hcengineering/view'
@@ -60,6 +60,8 @@
doc = object
}
$: cl = doc?._class ?? _class
$: clazz = cl ? hierarchy.findClass(cl) : undefined
$: icon =
doc !== undefined && !hierarchy.isDerived(doc._class, contact.class.Contact) ? classIcon(client, doc._class) : null
@@ -126,7 +128,11 @@
use:tooltip={docTooltip}
>
<DocNavLink object={doc} component={docComponent} {disabled} inlineReference {onClick} {transparent}>
{#if icon}<Icon {icon} size="small" />{' '}{:else}@{/if}{displayTitle}
{#if icon}{#if icon === view.ids.IconWithEmoji}<IconWithEmoji
icon={clazz?.color ?? 0}
size={'smaller'}
inline
/>{:else}<Icon {icon} size="small" />{/if}{' '}{:else}@{/if}{displayTitle}
</DocNavLink>
</span>
{/if}
@@ -243,6 +243,7 @@ export async function connect (title: string): Promise<Client | undefined> {
localStorage.setItem(`versionUpgrade:s${serverVersion}:f${frontVersion}`, 't')
// It might have been refreshed manually and download has started - do not reload
if (!isUpgrading) {
console.log('reload due to version upgrade')
location.reload()
}
@@ -255,6 +256,7 @@ export async function connect (title: string): Promise<Client | undefined> {
// It might be possible that this callback will fire after the user has spent some time
// in the upgrade !modal! dialog and clicked upgrade - check again and do not reload
if (get(upgradeDownloadProgress) < 0) {
console.log('reload due to upgrade download')
location.reload()
}
}, 10000)
@@ -268,6 +270,7 @@ export async function connect (title: string): Promise<Client | undefined> {
return true
},
onUpgrade: () => {
console.log('reload due to upgrade')
location.reload()
},
onUnauthorized: () => {
@@ -292,6 +295,7 @@ export async function connect (title: string): Promise<Client | undefined> {
translateCB(plugin.string.WorkspaceIsMigrating, {}, get(themeStore).language, (r) => {
versionError.set(r)
setTimeout(() => {
console.log('reload due to migration')
location.reload()
}, 5000)
})
@@ -326,6 +330,7 @@ export async function connect (title: string): Promise<Client | undefined> {
}
if (event === ClientConnectEvent.Upgraded) {
console.log('reload due to upgrade')
window.location.reload()
}
@@ -344,6 +349,7 @@ export async function connect (title: string): Promise<Client | undefined> {
if (currentVersionStr !== reconnectVersionStr) {
// It seems upgrade happened
console.log('reload due to version mismatch')
location.reload()
versionError.set(`${currentVersionStr} != ${reconnectVersionStr}`)
}
@@ -357,6 +363,7 @@ export async function connect (title: string): Promise<Client | undefined> {
if (reconnectVersionStr !== '' && currentVersionStr !== reconnectVersionStr) {
if (typeof sessionStorage !== 'undefined') {
if (sessionStorage.getItem(versionStorageKey) !== reconnectVersionStr) {
console.log('reload due to version mismatch')
sessionStorage.setItem(versionStorageKey, reconnectVersionStr)
location.reload()
}
@@ -370,10 +377,12 @@ export async function connect (title: string): Promise<Client | undefined> {
try {
const frontConfig = await loadServerConfig(concatLink(frontUrl, '/config.json'))
if (frontConfig?.version !== undefined && frontConfig.version !== currentFrontVersion) {
console.log('reload due to config version mismatch')
location.reload()
}
} catch (err: any) {
// Failed to load server config, reload location
console.log('reload due to config loading error')
location.reload()
}
}
+10 -15
View File
@@ -17,7 +17,6 @@ import core, { ArrOf, checkMixinKey, Doc, getObjectValue, RefTo } from '@hcengin
import { getEmbeddedLabel, getResource } from '@hcengineering/platform'
import process, {
Execution,
ExecutionError,
parseContext,
ProcessError,
processError,
@@ -117,11 +116,7 @@ async function fillValue (
return value
}
async function getNestedValue (
control: ProcessControl,
execution: Execution,
context: SelectedNested
): Promise<any | ExecutionError> {
async function getNestedValue (control: ProcessControl, execution: Execution, context: SelectedNested): Promise<any> {
const card = control.cache.get(execution.card)
if (card === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.card }, {}, true)
const attr = control.client.getHierarchy().findAttribute(card._class, context.path)
@@ -130,8 +125,9 @@ async function getNestedValue (
if (nestedValue === undefined) throw processError(process.error.EmptyAttributeContextValue, {}, { attr: attr.label })
const parentType = attr.type._class === core.class.ArrOf ? (attr.type as ArrOf<Doc>).of : attr.type
const targetClass = parentType._class === core.class.RefTo ? (parentType as RefTo<Doc>).to : parentType._class
const refs = Array.isArray(nestedValue) ? nestedValue : [nestedValue]
const target = await control.client.findAll(targetClass, {
_id: { $in: Array.isArray(nestedValue) ? nestedValue : [nestedValue] }
_id: { $in: refs }
})
if (target.length === 0) throw processError(process.error.RelatedObjectNotFound, {}, { attr: attr.label })
const nested = control.client.getHierarchy().findAttribute(targetClass, context.key)
@@ -158,10 +154,9 @@ async function getNestedValue (
}
return val
}
const val =
Array.isArray(target) && target.length > 1
? target.map((v) => getValue(control, execution, context.key, v))
: getValue(control, execution, context.key, target[0])
const val = Array.isArray(nestedValue)
? target.map((v) => getValue(control, execution, context.key, v))
: getValue(control, execution, context.key, target[0])
if (val == null) {
throw processError(
process.error.EmptyRelatedObjectValue,
@@ -187,6 +182,7 @@ async function getRelationValue (
const ids = relations.map((it) => {
return context.direction === 'A' ? it.docA : it.docB
})
const shouldBeArray = assoc.type === 'N:N' || (assoc.type === '1:N' && context.direction === 'A')
const target = await control.client.findAll(targetClass, { _id: { $in: ids } })
if (target.length === 0) throw processError(process.error.RelatedObjectNotFound, { attr: context.name })
const attr = context.key !== '' ? control.client.getHierarchy().findAttribute(targetClass, context.key) : undefined
@@ -213,10 +209,9 @@ async function getRelationValue (
}
return val
}
const val =
Array.isArray(target) && target.length > 1
? target.map((v) => getValue(control, execution, context.key, v))
: getValue(control, execution, context.key, target[0])
const val = shouldBeArray
? target.map((v) => getValue(control, execution, context.key, v))
: getValue(control, execution, context.key, target[0])
if (val == null) {
throw processError(
process.error.EmptyRelatedObjectValue,