mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-28 20:45:00 +02:00
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:
Vendored
+1
-1
@@ -97,7 +97,7 @@
|
||||
"MODEL_JSON": "${workspaceRoot}/models/all/bundle/model.json",
|
||||
// "SERVER_PROVIDER":"uweb"
|
||||
"SERVER_PROVIDER": "ws",
|
||||
"MODEL_VERSION": "0.7.186",
|
||||
"MODEL_VERSION": "0.7.230",
|
||||
// "VERSION": "0.6.289",
|
||||
"COMMUNICATION_API_ENABLED": "true",
|
||||
"ELASTIC_INDEX_NAME": "local_storage_index",
|
||||
|
||||
Generated
+37
-1
@@ -1675,6 +1675,9 @@ importers:
|
||||
'@types/express-form-data':
|
||||
specifier: ^2.0.5
|
||||
version: 2.0.5
|
||||
'@types/heic-decode':
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2
|
||||
'@types/html-to-text':
|
||||
specifier: ^8.1.1
|
||||
version: 8.1.1
|
||||
@@ -1813,6 +1816,9 @@ importers:
|
||||
blurhash:
|
||||
specifier: ^2.0.5
|
||||
version: 2.0.5
|
||||
bmp-ts:
|
||||
specifier: ^1.0.9
|
||||
version: 1.0.9
|
||||
body-parser:
|
||||
specifier: ^1.20.3
|
||||
version: 1.20.3
|
||||
@@ -2020,6 +2026,9 @@ importers:
|
||||
graphql-binding:
|
||||
specifier: ^2.5.2
|
||||
version: 2.5.2
|
||||
heic-decode:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
highlight.js:
|
||||
specifier: ^11.11.1
|
||||
version: 11.11.1
|
||||
@@ -5569,7 +5578,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/pod-preview@file:projects/pod-preview.tgz':
|
||||
resolution: {integrity: sha512-APb+wu9FGvuc2FCTz9nGyrGZwA4+o5uL6WWPdRhg1zeC9UniDdSA2InqNP03CxK8fayAwTMkTN9BF3C2g3w2lQ==, tarball: file:projects/pod-preview.tgz}
|
||||
resolution: {integrity: sha512-67MM8HopF5FhImVD91kHzl4UuyYk8WXHYXG+4Wz+jO1py2l/nvW9kNj9A1vPHXfy6clmsAsUMdAlLLLJE7ilmg==, tarball: file:projects/pod-preview.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/pod-print@file:projects/pod-print.tgz':
|
||||
@@ -7162,6 +7171,9 @@ packages:
|
||||
'@types/hast@3.0.4':
|
||||
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
||||
|
||||
'@types/heic-decode@1.1.2':
|
||||
resolution: {integrity: sha512-E1Dw+gpIuiHJcTpFBHm+V/6eYsPhtWook1Z6MIG0H1ONWBp9Z6l3FKydUxFw9GL9JqrWB1qa/uQHdbn/KUeXYw==}
|
||||
|
||||
'@types/html-minifier-terser@6.1.0':
|
||||
resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==}
|
||||
|
||||
@@ -8082,6 +8094,9 @@ packages:
|
||||
bmp-js@0.1.0:
|
||||
resolution: {integrity: sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==}
|
||||
|
||||
bmp-ts@1.0.9:
|
||||
resolution: {integrity: sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==}
|
||||
|
||||
bn.js@4.12.0:
|
||||
resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==}
|
||||
|
||||
@@ -10297,6 +10312,10 @@ packages:
|
||||
resolution: {integrity: sha512-trFMIq3PATiFRiQmNNeHtsrkwYRByIXUbYNbotiY9RLVfMkdwZdd2eQ38mGt7BRiCKBaj1DyBAIHmm7mmXPuuw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
heic-decode@2.1.0:
|
||||
resolution: {integrity: sha512-0fB3O3WMk38+PScbHLVp66jcNhsZ/ErtQ6u2lMYu/YxXgbBtl+oKOhGQHa4RpvE68k8IzbWkABzHnyAIjR758A==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
hexoid@1.0.0:
|
||||
resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -11285,6 +11304,10 @@ packages:
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
|
||||
libheif-js@1.19.8:
|
||||
resolution: {integrity: sha512-vQJWusIxO7wavpON1dusciL8Go9jsIQ+EUrckauFYAiSTjcmLAsuJh3SszLpvkwPci3JcL41ek2n+LUZGFpPIQ==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
libphonenumber-js@1.10.56:
|
||||
resolution: {integrity: sha512-d0GdKshNnyfl5gM7kZ9rXjGiAbxT/zCXp0k+EAzh8H4zrb2R7GXtMCrULrX7UQxtfx6CLy/vz/lomvW79FAFdA==}
|
||||
|
||||
@@ -25626,12 +25649,14 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/cors': 2.8.17
|
||||
'@types/express': 4.17.21
|
||||
'@types/heic-decode': 1.1.2
|
||||
'@types/jest': 29.5.12
|
||||
'@types/morgan': 1.9.9
|
||||
'@types/node': 22.15.29
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint@8.56.0)(typescript@5.8.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.8.3)
|
||||
blurhash: 2.0.5
|
||||
bmp-ts: 1.0.9
|
||||
cors: 2.8.5
|
||||
cross-env: 7.0.3
|
||||
dotenv: 16.0.3
|
||||
@@ -25643,6 +25668,7 @@ snapshots:
|
||||
eslint-plugin-node: 11.1.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
express: 4.21.2
|
||||
heic-decode: 2.1.0
|
||||
jest: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.8.3))
|
||||
lru-cache: 11.1.0
|
||||
morgan: 1.10.0
|
||||
@@ -32109,6 +32135,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/unist': 2.0.10
|
||||
|
||||
'@types/heic-decode@1.1.2': {}
|
||||
|
||||
'@types/html-minifier-terser@6.1.0': {}
|
||||
|
||||
'@types/html-to-text@8.1.1': {}
|
||||
@@ -33246,6 +33274,8 @@ snapshots:
|
||||
|
||||
bmp-js@0.1.0: {}
|
||||
|
||||
bmp-ts@1.0.9: {}
|
||||
|
||||
bn.js@4.12.0: {}
|
||||
|
||||
body-parser@1.20.3:
|
||||
@@ -35944,6 +35974,10 @@ snapshots:
|
||||
|
||||
heap-js@2.6.0: {}
|
||||
|
||||
heic-decode@2.1.0:
|
||||
dependencies:
|
||||
libheif-js: 1.19.8
|
||||
|
||||
hexoid@1.0.0: {}
|
||||
|
||||
highlight.js@11.11.1: {}
|
||||
@@ -37214,6 +37248,8 @@ snapshots:
|
||||
dependencies:
|
||||
isomorphic.js: 0.2.5
|
||||
|
||||
libheif-js@1.19.8: {}
|
||||
|
||||
libphonenumber-js@1.10.56: {}
|
||||
|
||||
lie@3.3.0:
|
||||
|
||||
@@ -189,6 +189,10 @@ function defineTraining (builder: Builder): void {
|
||||
component: training.component.TrainingPanel
|
||||
})
|
||||
|
||||
builder.mixin(training.class.Training, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: training.component.TrainingPresenter
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
attachTo: training.class.Training,
|
||||
descriptor: view.viewlet.Table,
|
||||
@@ -201,7 +205,8 @@ function defineTraining (builder: Builder): void {
|
||||
},
|
||||
{
|
||||
...columns.trainingTitle,
|
||||
key: 'title'
|
||||
key: '',
|
||||
presenter: training.component.TrainingTitlePresenter
|
||||
},
|
||||
{
|
||||
...columns.trainingRevision,
|
||||
@@ -890,8 +895,7 @@ const columns = {
|
||||
trainingTitle: {
|
||||
key: 'title',
|
||||
label: training.string.TrainingTitle,
|
||||
props: { accent: true },
|
||||
displayProps: { grow: true, align: 'left' }
|
||||
displayProps: { align: 'left' }
|
||||
},
|
||||
trainingRevision: {
|
||||
key: 'revision',
|
||||
|
||||
@@ -17,7 +17,7 @@ import type { Account, AccountRole, AccountUuid, Doc, Domain, PersonId, Ref } fr
|
||||
import { type MeasureContext } from '@hcengineering/measurements'
|
||||
import { type DocumentQuery, type FindOptions } from './storage'
|
||||
import type { DocumentUpdate, Tx } from './tx'
|
||||
import { type WorkspaceIds } from './utils'
|
||||
import { PermissionsGrant, type WorkspaceIds } from './utils'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -72,6 +72,7 @@ export interface SessionData {
|
||||
role: AccountRole
|
||||
}
|
||||
>
|
||||
grant?: PermissionsGrant
|
||||
|
||||
asyncRequests?: ((ctx: MeasureContext, id?: string) => Promise<void>)[]
|
||||
}
|
||||
|
||||
@@ -990,3 +990,7 @@ export function uniqueNotEmpty<T extends NonNullable<unknown>> (arr: Array<T | u
|
||||
return unique(arr).filter(notEmpty)
|
||||
}
|
||||
export { platformNow, platformNowDiff } from '@hcengineering/measurements'
|
||||
|
||||
export interface PermissionsGrant {
|
||||
spaces?: Ref<Space>[]
|
||||
}
|
||||
|
||||
@@ -155,6 +155,10 @@
|
||||
>
|
||||
<Component is={presence.component.Presence} props={{ object }} />
|
||||
|
||||
<svelte:fragment slot="beforeTitle">
|
||||
<slot name="beforeTitle" />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="title">
|
||||
{#if !withoutTitle}
|
||||
{#if $$slots.title}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ui from '../plugin'
|
||||
import { showPopup } from '../popups'
|
||||
import type { ButtonKind, DropdownIntlItem } from '../types'
|
||||
import type { ButtonKind, ButtonSize, DropdownIntlItem } from '../types'
|
||||
import Button from './Button.svelte'
|
||||
import DropdownIcon from './icons/Dropdown.svelte'
|
||||
import NestedMenu from './NestedMenu.svelte'
|
||||
@@ -30,6 +30,9 @@
|
||||
|
||||
export let width: string | undefined = undefined
|
||||
export let kind: ButtonKind = 'regular'
|
||||
export let size: ButtonSize | undefined = undefined
|
||||
export let withIcon: boolean = false
|
||||
export let withSelectIcon: boolean = true
|
||||
|
||||
let container: HTMLElement
|
||||
let opened: boolean = false
|
||||
@@ -39,7 +42,7 @@
|
||||
function openPopup (): void {
|
||||
if (!opened) {
|
||||
opened = true
|
||||
showPopup(NestedMenu, { items }, container, (result) => {
|
||||
showPopup(NestedMenu, { items, withIcon }, container, (result) => {
|
||||
if (result !== undefined) {
|
||||
selected = result
|
||||
dispatch('selected', result.id)
|
||||
@@ -51,12 +54,22 @@
|
||||
</script>
|
||||
|
||||
<div bind:this={container}>
|
||||
<Button width={width ?? 'min-content'} {kind} {disabled} on:click={openPopup}>
|
||||
<Button
|
||||
width={width ?? 'min-content'}
|
||||
{kind}
|
||||
{size}
|
||||
{disabled}
|
||||
on:click={openPopup}
|
||||
icon={withIcon ? selected?.icon : undefined}
|
||||
iconProps={selected?.iconProps}
|
||||
>
|
||||
<span slot="content" class="overflow-label disabled flex-grow text-left mr-2">
|
||||
<Label label={selected !== undefined ? selected.label : label} />
|
||||
</span>
|
||||
<svelte:fragment slot="iconRight">
|
||||
<DropdownIcon size={'small'} fill={!disabled ? 'var(--theme-content-color)' : 'var(--theme-dark-color)'} />
|
||||
{#if withSelectIcon}
|
||||
<DropdownIcon size={'small'} fill={!disabled ? 'var(--theme-content-color)' : 'var(--theme-dark-color)'} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -18,10 +18,12 @@
|
||||
import { resizeObserver } from '../resize'
|
||||
import { DropdownIntlItem } from '../types'
|
||||
import NestedMenu from './NestedMenu.svelte'
|
||||
import Icon from './Icon.svelte'
|
||||
|
||||
export let items: [DropdownIntlItem, DropdownIntlItem[]][]
|
||||
export let nestedFrom: DropdownIntlItem | undefined = undefined
|
||||
export let onSelect: ((val: DropdownIntlItem) => void) | undefined = undefined
|
||||
export let withIcon: boolean = false
|
||||
|
||||
const elements: HTMLElement[] = []
|
||||
|
||||
@@ -72,6 +74,11 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if withIcon && nestedFrom.icon}
|
||||
<div class="icon">
|
||||
<Icon icon={nestedFrom.icon} iconProps={nestedFrom.iconProps} size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="overflow-label pr-1"><Label label={nestedFrom.label} /></div>
|
||||
</button>
|
||||
<div class="divider" />
|
||||
@@ -87,12 +94,15 @@
|
||||
elements[i]?.focus()
|
||||
}}
|
||||
label={item[0].label}
|
||||
icon={withIcon ? item[0].icon : undefined}
|
||||
iconProps={item[0].iconProps}
|
||||
props={{
|
||||
items: item[1].map((p) => {
|
||||
return [p, []]
|
||||
}),
|
||||
nestedFrom: item[0],
|
||||
onSelect: onNestedSelect
|
||||
onSelect: onNestedSelect,
|
||||
withIcon
|
||||
}}
|
||||
options={{ component: NestedMenu }}
|
||||
/>
|
||||
@@ -110,6 +120,11 @@
|
||||
click(item[0])
|
||||
}}
|
||||
>
|
||||
{#if withIcon && item[0].icon}
|
||||
<div class="icon">
|
||||
<Icon icon={item[0].icon} iconProps={item[0].iconProps} size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="overflow-label pr-1"><Label label={item[0].label} /></div>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
export let focusIndex = -1
|
||||
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let iconProps: Record<string, any> = {}
|
||||
export let text: string | undefined = undefined
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let labelProps: Record<string, any> = {}
|
||||
@@ -58,7 +59,7 @@
|
||||
{/if}
|
||||
{:else}
|
||||
{#if icon}
|
||||
<div class="icon"><Icon {icon} size={'small'} /></div>
|
||||
<div class="icon"><Icon {icon} {iconProps} size={'small'} /></div>
|
||||
{/if}
|
||||
<span class="overflow-label pr-1">
|
||||
{#if label}<Label {label} params={labelProps} />
|
||||
|
||||
@@ -52,4 +52,8 @@
|
||||
<symbol id="space" viewBox="0 0 32 32">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 9C2 6.23858 4.23858 4 7 4H10.6716C11.4672 4 12.2303 4.31607 12.7929 4.87868L15.6213 7.70711C15.8089 7.89464 16.0632 8 16.3284 8H25C27.7614 8 30 10.2386 30 13V23C30 25.7614 27.7614 28 25 28H7C4.23858 28 2 25.7614 2 23V9ZM7 6C5.34315 6 4 7.34315 4 9V23C4 24.6569 5.34315 26 7 26H25C26.6569 26 28 24.6569 28 23V13C28 11.3431 26.6569 10 25 10H16.3284C15.5328 10 14.7697 9.68393 14.2071 9.12132L11.3787 6.29289C11.1911 6.10536 10.9368 6 10.6716 6H7Z" fill="currentColor"/>
|
||||
</symbol>
|
||||
<symbol id="expand" viewBox="0 0 32 32">
|
||||
<path d="M20 2V4H26.586L18.293 12.2929C17.9024 12.6834 17.9024 13.3166 18.293 13.7071C18.6835 14.0976 19.3167 14.0976 19.7072 13.7071L28 5.414V12H30V2H20Z"/>
|
||||
<path d="M13.7073 19.7071C14.0978 19.3166 14.0978 18.6834 13.7073 18.2929C13.3167 17.9024 12.6836 17.9024 12.293 18.2929L4 26.586V20H2V30H12V28H5.414L13.7073 19.7071Z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 8.0 KiB |
@@ -42,10 +42,11 @@
|
||||
"ConfigDescription": "Rozšíření pro správu znalostí ve stylu databáze.",
|
||||
"AddCollaborators": "Přidat spolupracovníky",
|
||||
"Home": "Domů",
|
||||
"WhatDoYouWantToShare": "Čím chcete sdílet?",
|
||||
"Share": "Sdílet",
|
||||
"CardTitle": "Zadejte název karty...",
|
||||
"Post": "Přidat",
|
||||
"Compact": "Kompaktní",
|
||||
"Comfortable": "Komfortní",
|
||||
"Comfortable2": "Komfortní 2"
|
||||
"Comfortable2": "Komfortní 2",
|
||||
"AdvancedCreateCard": "Přepnout na pokročilé vytváření karet"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
"ConfigDescription": "Erweiterung für Datenbank-basiertes Wissensmanagement.",
|
||||
"AddCollaborators": "Mitarbeiter hinzufügen",
|
||||
"Home": "Startseite",
|
||||
"WhatDoYouWantToShare": "Was möchten Sie teilen?",
|
||||
"Share": "Teilen",
|
||||
"CardTitle": "Geben Sie einen Kartentitel ein...",
|
||||
"Post": "Beitrag",
|
||||
"Compact": "Kompakt",
|
||||
"Comfortable": "Komfortabel",
|
||||
"Comfortable2": "Komfortabel 2"
|
||||
"Comfortable2": "Komfortabel 2",
|
||||
"AdvancedCreateCard": "Zum erweiterten Kartenerstellungsmodus wechseln"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
"ConfigDescription": "Extension for database-style knowledge management.",
|
||||
"AddCollaborators": "Add collaborators",
|
||||
"Home": "Home",
|
||||
"WhatDoYouWantToShare": "What do you want to share?",
|
||||
"Share": "Share",
|
||||
"CardTitle": "Enter a card title...",
|
||||
"Post": "Post",
|
||||
"Compact": "Compact",
|
||||
"Comfortable": "Comfortable",
|
||||
"Comfortable2": "Comfortable 2"
|
||||
"Comfortable2": "Comfortable 2",
|
||||
"AdvancedCreateCard": "Switch to advanced card creation"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
"ConfigDescription": "Extensión para la gestión del conocimiento al estilo de una base de datos.",
|
||||
"AddCollaborators": "Agregar colaboradores",
|
||||
"Home": "Inicio",
|
||||
"WhatDoYouWantToShare": "¿Qué quieres compartir?",
|
||||
"Share": "Compartir",
|
||||
"CardTitle": "Introduce un título para la tarjeta...",
|
||||
"Post": "Publicar",
|
||||
"Compact": "Compacto",
|
||||
"Comfortable": "Confortable",
|
||||
"Comfortable2": "Confortable 2"
|
||||
"Comfortable2": "Confortable 2",
|
||||
"AdvancedCreateCard": "Cambiar a creación avanzada de tarjetas"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
"ConfigDescription": "Extension pour la gestion des connaissances de style base de données.",
|
||||
"AddCollaborators": "Ajouter des collaborateurs",
|
||||
"Home": "Accueil",
|
||||
"WhatDoYouWantToShare": "Que voulez-vous partager ?",
|
||||
"Share": "Partager",
|
||||
"CardTitle": "Entrez un titre de carte...",
|
||||
"Post": "Publier",
|
||||
"Compact": "Compact",
|
||||
"Comfortable": "Confortable",
|
||||
"Comfortable2": "Confortable 2"
|
||||
"Comfortable2": "Confortable 2",
|
||||
"AdvancedCreateCard": "Passer à la création avancée de cartes"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
"ConfigDescription": "Estensione per la gestione della conoscenza in stile database.",
|
||||
"AddCollaborators": "Aggiungi collaboratori",
|
||||
"Home": "Home",
|
||||
"WhatDoYouWantToShare": "Cosa vuoi condividere?",
|
||||
"Share": "Condividi",
|
||||
"CardTitle": "Inserisci un titolo per la carta...",
|
||||
"Post": "Pubblica",
|
||||
"Compact": "Compatto",
|
||||
"Comfortable": "Confortabile",
|
||||
"Comfortable2": "Confortabile 2"
|
||||
"Comfortable2": "Confortabile 2",
|
||||
"AdvancedCreateCard": "Passa alla creazione avanzata della carta"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
"ConfigDescription": "データベーススタイルのナレッジマネジメント用の拡張機能。",
|
||||
"AddCollaborators": "コラボレーターを追加",
|
||||
"Home": "ホーム",
|
||||
"WhatDoYouWantToShare": "どうしますかを共有しますか?",
|
||||
"Share": "共有",
|
||||
"CardTitle": "カードのタイトルを入力...",
|
||||
"Post": "投稿",
|
||||
"Compact": "コンパクト",
|
||||
"Comfortable": "コンファンティブ",
|
||||
"Comfortable2": "コンファンティブ 2"
|
||||
"Comfortable2": "コンファンティブ 2",
|
||||
"AdvancedCreateCard": "高度なカード作成に切り替え"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
"ConfigDescription": "Extensão para gerenciamento de conhecimento no estilo de banco de dados.",
|
||||
"AddCollaborators": "Adicionar colaboradores",
|
||||
"Home": "Início",
|
||||
"WhatDoYouWantToShare": "O que você deseja compartilhar?",
|
||||
"Share": "Compartilhar",
|
||||
"CardTitle": "Digite um título para o cartão...",
|
||||
"Post": "Publicar",
|
||||
"Compact": "Compacto",
|
||||
"Comfortable": "Confortável",
|
||||
"Comfortable2": "Confortável 2"
|
||||
"Comfortable2": "Confortável 2",
|
||||
"AdvancedCreateCard": "Mudar para criação avançada de cartão"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
"ConfigDescription": "Расширение для управления знаниями в стиле базы данных.",
|
||||
"AddCollaborators": "Добавить сотрудников",
|
||||
"Home": "Дом",
|
||||
"WhatDoYouWantToShare": "Чем вы хотите поделиться?",
|
||||
"Share": "Поделиться",
|
||||
"CardTitle": "Введите название карты...",
|
||||
"Post": "Опубликовать",
|
||||
"Compact": "Компактный",
|
||||
"Comfortable": "Комфортный",
|
||||
"Comfortable2": "Комфортный 2"
|
||||
"Comfortable2": "Комфортный 2",
|
||||
"AdvancedCreateCard": "Переключиться на расширенное создание карты"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
"ConfigDescription": "用于数据库风格知识管理的扩展。",
|
||||
"AddCollaborators": "添加协作者",
|
||||
"Home": "主页",
|
||||
"WhatDoYouWantToShare": "您想分享什么?",
|
||||
"Share": "分享",
|
||||
"CardTitle": "输入卡片标题...",
|
||||
"Post": "发布",
|
||||
"Compact": "紧凑",
|
||||
"Comfortable": "舒适",
|
||||
"Comfortable2": "舒适 2"
|
||||
"Comfortable2": "舒适 2",
|
||||
"AdvancedCreateCard": "切换到高级卡片创建"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,5 +25,6 @@ loadMetadata(card.icon, {
|
||||
View: `${icons}#view`,
|
||||
Document: `${icons}#document`,
|
||||
Home: `${icons}#home`,
|
||||
Space: `${icons}#space`
|
||||
Space: `${icons}#space`,
|
||||
Expand: `${icons}#expand`
|
||||
})
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
export let type: Ref<MasterTag> = 'chat:masterTag:Thread' as Ref<MasterTag>
|
||||
export let space: CardSpace | undefined = undefined
|
||||
export let changeType: boolean = false
|
||||
export let allowChangeSpace: boolean = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
@@ -183,7 +184,7 @@
|
||||
<TypeSelector bind:value={type} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if space == null && !(extension?.hideSpace ?? false)}
|
||||
{#if (space == null || allowChangeSpace) && !(extension?.hideSpace ?? false)}
|
||||
<div class="hulyModal-content__settingsSet-line">
|
||||
<span class="label"><Label label={core.string.Space} /></span>
|
||||
<SpaceSelector
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
import { NotificationContext } from '@hcengineering/communication-types'
|
||||
|
||||
import card from '../plugin'
|
||||
import CardIcon from './CardIcon.svelte'
|
||||
import TagsEditor from './TagsEditor.svelte'
|
||||
import EditCardNewContent from './EditCardNewContent.svelte'
|
||||
import { openCardInSidebar } from '../utils'
|
||||
@@ -165,6 +166,10 @@
|
||||
<EditCardNewContent {_id} {doc} readonly={_readonly} {context} {isContextLoaded} />
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="beforeTitle">
|
||||
<CardIcon value={doc} />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="title">
|
||||
{#if showParents}
|
||||
<ParentsNavigator element={doc} maxWidth={'10rem'} />
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
import card from '../plugin'
|
||||
import CardAttributeEditor from './CardAttributeEditor.svelte'
|
||||
import CardIcon from './CardIcon.svelte'
|
||||
import CardPresenter from './CardPresenter.svelte'
|
||||
import Childs from './Childs.svelte'
|
||||
import Content from './Content.svelte'
|
||||
@@ -139,6 +140,10 @@
|
||||
on:open
|
||||
on:close={() => dispatch('close')}
|
||||
>
|
||||
<svelte:fragment slot="beforeTitle">
|
||||
<CardIcon value={doc} />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="title">
|
||||
<ParentsNavigator element={doc} />
|
||||
<CardPresenter value={doc} noUnderline />
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<!-- See the License for the specific language governing permissions and -->
|
||||
<!-- limitations under the License. -->
|
||||
<script lang="ts">
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Card } from '@hcengineering/card'
|
||||
import core, { SortingOrder } from '@hcengineering/core'
|
||||
import ui, {
|
||||
@@ -19,25 +19,21 @@
|
||||
IconSettings,
|
||||
Label,
|
||||
ModernButton,
|
||||
ModernEditbox,
|
||||
Scroller,
|
||||
SearchInput,
|
||||
Loading,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { FilterBar, FilterButton } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import HomeCardPresenter from './HomeCardPresenter.svelte'
|
||||
import HomeSettings from './HomeSettings.svelte'
|
||||
import CreateCardPopup from './CreateCardPopup.svelte'
|
||||
import NewCardForm from './NewCardForm.svelte'
|
||||
import card from '../plugin'
|
||||
|
||||
const cardsQuery = createQuery()
|
||||
const limitStep = 50
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let divScroll: HTMLDivElement
|
||||
let limit = limitStep
|
||||
let cards: Card[] = []
|
||||
@@ -111,19 +107,6 @@
|
||||
function onSettings (e: MouseEvent): void {
|
||||
showPopup(HomeSettings, {}, eventToHTMLElement(e))
|
||||
}
|
||||
|
||||
function onShare (): void {
|
||||
showPopup(CreateCardPopup, { title, changeType: true }, 'center', async (result) => {
|
||||
if (result !== undefined) {
|
||||
const doc = await getClient().findOne(card.class.Card, { _id: result })
|
||||
if (doc === undefined) return
|
||||
dispatch('selectCard', doc)
|
||||
}
|
||||
})
|
||||
title = ''
|
||||
}
|
||||
|
||||
let title: string = ''
|
||||
</script>
|
||||
|
||||
<Scroller bind:divScroll {onScroll} padding="2rem 4rem">
|
||||
@@ -145,28 +128,7 @@
|
||||
space={undefined}
|
||||
on:change={({ detail }) => (resultQuery = detail)}
|
||||
/>
|
||||
<div class="create-card">
|
||||
<ModernEditbox
|
||||
bind:value={title}
|
||||
label={card.string.WhatDoYouWantToShare}
|
||||
size="large"
|
||||
kind="secondary"
|
||||
width="100%"
|
||||
disabled={false}
|
||||
autoFocus={true}
|
||||
>
|
||||
<svelte:fragment slot="after">
|
||||
<ModernButton
|
||||
label={card.string.Share}
|
||||
size="small"
|
||||
kind="primary"
|
||||
disabled={title.trim() === ''}
|
||||
on:click={onShare}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</ModernEditbox>
|
||||
</div>
|
||||
|
||||
<NewCardForm />
|
||||
<div class="body flex-gap-2">
|
||||
{#each cards as card, index}
|
||||
{@const previousCard = cards[index - 1]}
|
||||
@@ -211,12 +173,6 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.create-card {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
<script lang="ts">
|
||||
import { getClient, SpaceSelector } from '@hcengineering/presentation'
|
||||
import { ModernButton, ModernEditbox, ButtonIcon, IconSend, showPopup } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import CreateCardPopup from './CreateCardPopup.svelte'
|
||||
import card from '../plugin'
|
||||
import { TypeSelector } from '../index'
|
||||
import core, { Data, Ref } from '@hcengineering/core'
|
||||
import { Card, type CardSpace, MasterTag } from '@hcengineering/card'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { EmptyMarkup } from '@hcengineering/text'
|
||||
import { createCard } from '../utils'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let title: string = ''
|
||||
let space: Ref<CardSpace> | undefined = undefined
|
||||
let type: Ref<MasterTag> = 'chat:masterTag:Thread' as Ref<MasterTag>
|
||||
|
||||
let creating = false
|
||||
$: applyDisabled = title.trim() === '' || space == null || type == null || creating
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
$: extension =
|
||||
type != null
|
||||
? client
|
||||
.getModel()
|
||||
.findAllSync(card.mixin.CreateCardExtension, {})
|
||||
.find((it) => hierarchy.isDerived(type, it._id))
|
||||
: undefined
|
||||
|
||||
function onExtend (): void {
|
||||
showPopup(
|
||||
CreateCardPopup,
|
||||
{ title, type, space, changeType: true, allowChangeSpace: true },
|
||||
'center',
|
||||
async (result) => {
|
||||
if (result !== undefined) {
|
||||
const doc = await getClient().findOne(card.class.Card, { _id: result })
|
||||
if (doc === undefined) return
|
||||
dispatch('selectCard', doc)
|
||||
}
|
||||
}
|
||||
)
|
||||
title = ''
|
||||
}
|
||||
|
||||
async function okAction (): Promise<void> {
|
||||
if (space === undefined || type == null || title.trim() === '') return
|
||||
|
||||
try {
|
||||
creating = true
|
||||
|
||||
const data: Partial<Data<Card>> = {
|
||||
title
|
||||
}
|
||||
|
||||
if (extension?.canCreate !== undefined) {
|
||||
const fn = await getResource(extension.canCreate)
|
||||
const res = await fn(space, data)
|
||||
if (res === false) {
|
||||
return
|
||||
} else if (typeof res === 'string') {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const createdCard = await createCard(type, space, data, EmptyMarkup)
|
||||
if (createdCard != null) {
|
||||
dispatch('selectCard', createdCard)
|
||||
}
|
||||
title = ''
|
||||
} finally {
|
||||
creating = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="create-card rounded-form">
|
||||
<div class="corner-container">
|
||||
<ButtonIcon
|
||||
icon={card.icon.Expand}
|
||||
size="extra-small"
|
||||
on:click={onExtend}
|
||||
kind="tertiary"
|
||||
disabled={creating}
|
||||
tooltip={{ label: card.string.AdvancedCreateCard }}
|
||||
/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<ModernEditbox
|
||||
bind:value={title}
|
||||
label={card.string.CardTitle}
|
||||
size="medium"
|
||||
kind="ghost"
|
||||
width="100%"
|
||||
disabled={creating}
|
||||
autoFocus={true}
|
||||
on:keydown={(evt) => {
|
||||
if (evt.key === 'Enter') {
|
||||
void okAction()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="form-row form-row-bottom">
|
||||
<div class="form-dropdowns">
|
||||
<SpaceSelector
|
||||
_class={card.class.CardSpace}
|
||||
query={{ archived: false }}
|
||||
label={core.string.Space}
|
||||
bind:space
|
||||
focus={false}
|
||||
readonly={creating}
|
||||
kind={'regular'}
|
||||
size={'small'}
|
||||
/>
|
||||
<TypeSelector size={'small'} bind:value={type} disabled={creating} />
|
||||
</div>
|
||||
<ModernButton
|
||||
label={card.string.Post}
|
||||
icon={IconSend}
|
||||
iconSize="small"
|
||||
size="small"
|
||||
kind="primary"
|
||||
disabled={applyDisabled}
|
||||
loading={creating}
|
||||
on:click={okAction}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.corner-container {
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
right: 1rem;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.create-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
border-radius: 1rem;
|
||||
border: 1px solid var(--theme-divider-color);
|
||||
background: var(--theme-surface-color);
|
||||
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.04);
|
||||
padding: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
.rounded-form {
|
||||
border-radius: 1rem;
|
||||
}
|
||||
.form-row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.form-row-bottom {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0;
|
||||
gap: 1rem;
|
||||
}
|
||||
.form-dropdowns {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -15,13 +15,17 @@
|
||||
<script lang="ts">
|
||||
import { MasterTag } from '@hcengineering/card'
|
||||
import { Class, ClassifierKind, Doc, Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { DropdownIntlItem, NestedDropdown } from '@hcengineering/ui'
|
||||
import { getClient, IconWithEmoji } from '@hcengineering/presentation'
|
||||
import { type ButtonKind, type ButtonSize, DropdownIntlItem, NestedDropdown } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import card from '../plugin'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
export let value: Ref<MasterTag>
|
||||
export let width: string | undefined = undefined
|
||||
export let kind: ButtonKind | undefined = undefined
|
||||
export let size: ButtonSize | undefined = undefined
|
||||
export let disabled: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@@ -55,21 +59,31 @@
|
||||
try {
|
||||
const clazz = hierarchy.getClass(key)
|
||||
result.push([
|
||||
{ id: key, label: clazz.label },
|
||||
value.map((it) => ({ id: it._id, label: it.label })).sort((a, b) => a.label.localeCompare(b.label))
|
||||
{ id: key, label: clazz.label, ...getIconProps(clazz) },
|
||||
value
|
||||
.map((it) => ({ id: it._id, label: it.label, ...getIconProps(it) }))
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
])
|
||||
} catch {}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function getIconProps (tag: MasterTag): Pick<DropdownIntlItem, 'icon' | 'iconProps'> {
|
||||
return {
|
||||
icon: tag.icon === view.ids.IconWithEmoji ? IconWithEmoji : tag.icon ?? card.icon.MasterTag,
|
||||
iconProps: tag.icon === view.ids.IconWithEmoji ? { icon: tag.color } : {}
|
||||
}
|
||||
}
|
||||
|
||||
const classes = filterClasses()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: selectedClass = hierarchy.getClass(value)
|
||||
$: selected = {
|
||||
id: selectedClass._id,
|
||||
label: selectedClass.label
|
||||
label: selectedClass.label,
|
||||
...getIconProps(selectedClass)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -77,6 +91,11 @@
|
||||
items={classes}
|
||||
{width}
|
||||
{selected}
|
||||
{kind}
|
||||
{size}
|
||||
{disabled}
|
||||
withIcon={true}
|
||||
withSelectIcon={false}
|
||||
on:selected={(e) => {
|
||||
value = e.detail
|
||||
dispatch('change', value)
|
||||
|
||||
@@ -102,10 +102,11 @@ export default mergeIds(cardId, card, {
|
||||
NoChildren: '' as IntlString,
|
||||
AddCollaborators: '' as IntlString,
|
||||
Home: '' as IntlString,
|
||||
WhatDoYouWantToShare: '' as IntlString,
|
||||
Share: '' as IntlString,
|
||||
CardTitle: '' as IntlString,
|
||||
Post: '' as IntlString,
|
||||
Compact: '' as IntlString,
|
||||
Comfortable: '' as IntlString,
|
||||
Comfortable2: '' as IntlString
|
||||
Comfortable2: '' as IntlString,
|
||||
AdvancedCreateCard: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -151,7 +151,8 @@ const cardPlugin = plugin(cardId, {
|
||||
View: '' as Asset,
|
||||
Document: '' as Asset,
|
||||
Home: '' as Asset,
|
||||
Space: '' as Asset
|
||||
Space: '' as Asset,
|
||||
Expand: '' as Asset
|
||||
},
|
||||
extensions: {
|
||||
EditCardExtension: '' as ComponentExtensionId
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
<Card
|
||||
okAction={save}
|
||||
canSave={to !== undefined && trigger !== undefined && to !== from}
|
||||
canSave={to !== undefined && trigger !== undefined}
|
||||
label={plugin.string.AddTransition}
|
||||
width={'medium'}
|
||||
on:close
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<!--
|
||||
Copyright © 2023 Hardcore Engineering Inc.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { type Training } from '@hcengineering/training'
|
||||
import { DocNavLink } from '@hcengineering/view-resources'
|
||||
|
||||
export let value: Training
|
||||
export let disabled: boolean = false
|
||||
export let accent: boolean = false
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<div class="content-halfcontent-color">
|
||||
<DocNavLink object={value} {disabled} noOverflow accent>
|
||||
<span class="whitespace-nowrap" class:fs-bold={accent}>
|
||||
{value.title}
|
||||
</span>
|
||||
</DocNavLink>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -28,6 +28,7 @@ import TrainingCodePresenter from './components/TrainingCodePresenter.svelte'
|
||||
import TrainingCreator from './components/TrainingCreator.svelte'
|
||||
import TrainingPanel from './components/TrainingPanel.svelte'
|
||||
import TrainingPassingScorePresenter from './components/TrainingPassingScorePresenter.svelte'
|
||||
import TrainingPresenter from './components/TrainingPresenter.svelte'
|
||||
import TrainingRequestDueDateEditor from './components/TrainingRequestDueDateEditor.svelte'
|
||||
import TrainingRequestMaxAttemptsEditor from './components/TrainingRequestMaxAttemptsEditor.svelte'
|
||||
import TrainingRequestMaxAttemptsPresenter from './components/TrainingRequestMaxAttemptsPresenter.svelte'
|
||||
@@ -36,6 +37,7 @@ import TrainingRequestPanel from './components/TrainingRequestPanel.svelte'
|
||||
import TrainingRequestTraineesEditor from './components/TrainingRequestTraineesEditor.svelte'
|
||||
import TrainingStateFilterPresenter from './components/TrainingStateFilterPresenter.svelte'
|
||||
import TrainingStatePresenter from './components/TrainingStatePresenter.svelte'
|
||||
import TrainingTitlePresenter from './components/TrainingTitlePresenter.svelte'
|
||||
import ViewAllTrainings from './components/ViewAllTrainings.svelte'
|
||||
import ViewIncomingRequests from './components/ViewIncomingRequests.svelte'
|
||||
import ViewMyResults from './components/ViewMyResults.svelte'
|
||||
@@ -76,6 +78,7 @@ export default async (): Promise<Resources> => ({
|
||||
TrainingCreator,
|
||||
TrainingPanel,
|
||||
TrainingPassingScorePresenter,
|
||||
TrainingPresenter,
|
||||
TrainingRequestDueDateEditor,
|
||||
TrainingRequestMaxAttemptsEditor,
|
||||
TrainingRequestMaxAttemptsPresenter,
|
||||
@@ -84,6 +87,7 @@ export default async (): Promise<Resources> => ({
|
||||
TrainingRequestTraineesEditor,
|
||||
TrainingStateFilterPresenter,
|
||||
TrainingStatePresenter,
|
||||
TrainingTitlePresenter,
|
||||
ViewAllTrainings,
|
||||
ViewIncomingRequests,
|
||||
ViewMyTrainings,
|
||||
|
||||
@@ -21,6 +21,7 @@ import type IncomingRequestStatePresenter from './components/IncomingRequestStat
|
||||
import type SentRequestCompletionPresenter from './components/SentRequestCompletionPresenter.svelte'
|
||||
import type SentRequestPresenter from './components/SentRequestPresenter.svelte'
|
||||
import type SentRequestStatePresenter from './components/SentRequestStatePresenter.svelte'
|
||||
import type TrainingPresenter from './components/TrainingPresenter.svelte'
|
||||
import type TrainingAttemptNumberPresenter from './components/TrainingAttemptNumberPresenter.svelte'
|
||||
import type TrainingAttemptPanel from './components/TrainingAttemptPanel.svelte'
|
||||
import type TrainingAttemptPresenter from './components/TrainingAttemptPresenter.svelte'
|
||||
@@ -40,6 +41,7 @@ import type TrainingRequestPanel from './components/TrainingRequestPanel.svelte'
|
||||
import type TrainingRequestTraineesEditor from './components/TrainingRequestTraineesEditor.svelte'
|
||||
import type TrainingStateFilterPresenter from './components/TrainingStateFilterPresenter.svelte'
|
||||
import type TrainingStatePresenter from './components/TrainingStatePresenter.svelte'
|
||||
import type TrainingTitlePresenter from './components/TrainingTitlePresenter.svelte'
|
||||
import type ViewAllTrainings from './components/ViewAllTrainings.svelte'
|
||||
import type ViewIncomingRequests from './components/ViewIncomingRequests.svelte'
|
||||
import type ViewMyResults from './components/ViewMyResults.svelte'
|
||||
@@ -56,6 +58,7 @@ export default mergeIds(trainingId, training, {
|
||||
SentRequestCompletionPresenter: '' as Resource<ComponentType<SentRequestCompletionPresenter>>,
|
||||
SentRequestPresenter: '' as Resource<ComponentType<SentRequestPresenter>>,
|
||||
SentRequestStatePresenter: '' as Resource<ComponentType<SentRequestStatePresenter>>,
|
||||
TrainingPresenter: '' as Resource<ComponentType<TrainingPresenter>>,
|
||||
TrainingAttemptNumberPresenter: '' as Resource<ComponentType<TrainingAttemptNumberPresenter>>,
|
||||
TrainingAttemptPanel: '' as Resource<ComponentType<TrainingAttemptPanel>>,
|
||||
TrainingAttemptPresenter: '' as Resource<ComponentType<TrainingAttemptPresenter>>,
|
||||
@@ -74,6 +77,7 @@ export default mergeIds(trainingId, training, {
|
||||
TrainingRequestTraineesEditor: '' as Resource<ComponentType<TrainingRequestTraineesEditor>>,
|
||||
TrainingStateFilterPresenter: '' as Resource<ComponentType<TrainingStateFilterPresenter>>,
|
||||
TrainingStatePresenter: '' as Resource<ComponentType<TrainingStatePresenter>>,
|
||||
TrainingTitlePresenter: '' as Resource<ComponentType<TrainingTitlePresenter>>,
|
||||
ViewAllTrainings: '' as Resource<ComponentType<ViewAllTrainings>>,
|
||||
ViewIncomingRequests: '' as Resource<ComponentType<ViewIncomingRequests>>,
|
||||
ViewMyTrainings: '' as Resource<ComponentType<ViewMyTrainings>>,
|
||||
|
||||
@@ -51,7 +51,8 @@
|
||||
"typescript": "^5.8.3",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/morgan": "~1.9.9"
|
||||
"@types/morgan": "~1.9.9",
|
||||
"@types/heic-decode": "^1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
@@ -69,6 +70,8 @@
|
||||
"cors": "^2.8.5",
|
||||
"morgan": "^1.10.0",
|
||||
"node-poppler": "^8.0.4",
|
||||
"lru-cache": "^11.1.0"
|
||||
"lru-cache": "^11.1.0",
|
||||
"heic-decode": "^2.1.0",
|
||||
"bmp-ts": "^1.0.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import { pipeline } from 'stream/promises'
|
||||
import { getImageMetadata } from '../metadata'
|
||||
import { TemporaryDir } from '../tempdir'
|
||||
import { type PreviewFile, type PreviewMetadata, type PreviewProvider } from '../types'
|
||||
import { bmpToPng } from '../utils/bmp'
|
||||
import { heicToPng } from '../utils/heic'
|
||||
|
||||
export class ImageProvider implements PreviewProvider {
|
||||
constructor (
|
||||
@@ -41,6 +43,20 @@ export class ImageProvider implements PreviewProvider {
|
||||
await pipeline(stream, createWriteStream(path))
|
||||
})
|
||||
|
||||
// Handle HEIC conversion
|
||||
if (contentType === 'image/heic' || contentType === 'image/heif') {
|
||||
const pngFile = await heicToPng(ctx, path)
|
||||
this.tempDir.rm(path)
|
||||
return { mimeType: 'image/png', filePath: pngFile }
|
||||
}
|
||||
|
||||
// Handle BMP conversion
|
||||
if (contentType === 'image/bmp') {
|
||||
const pngFile = await bmpToPng(ctx, path)
|
||||
this.tempDir.rm(path)
|
||||
return { mimeType: 'image/png', filePath: pngFile }
|
||||
}
|
||||
|
||||
return { mimeType: contentType, filePath: path }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type MeasureContext } from '@hcengineering/core'
|
||||
import { readFile } from 'fs/promises'
|
||||
import { decode } from 'bmp-ts'
|
||||
import sharp from 'sharp'
|
||||
|
||||
export async function bmpToPng (ctx: MeasureContext, bmpFile: string): Promise<string> {
|
||||
const pngFile: string = bmpFile + '.png'
|
||||
|
||||
const buffer = await readFile(bmpFile)
|
||||
|
||||
const data = await ctx.with('bmp-decode', {}, () => {
|
||||
return decode(Buffer.from(buffer), { toRGBA: false })
|
||||
})
|
||||
|
||||
// Manually convert to RGBA as library does not seem to handle this properly
|
||||
for (let i = 0; i < data.data.length; i += 4) {
|
||||
// const a = data.data[i + 0]
|
||||
const b = data.data[i + 1]
|
||||
const g = data.data[i + 2]
|
||||
const r = data.data[i + 3]
|
||||
data.data[i + 0] = r
|
||||
data.data[i + 1] = g
|
||||
data.data[i + 2] = b
|
||||
data.data[i + 3] = 0xff
|
||||
}
|
||||
|
||||
await ctx.with('sharp', {}, async () => {
|
||||
await sharp(data.data, {
|
||||
raw: {
|
||||
width: data.width,
|
||||
height: data.height,
|
||||
channels: 4
|
||||
}
|
||||
})
|
||||
.png()
|
||||
.toFile(pngFile)
|
||||
})
|
||||
|
||||
return pngFile
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type MeasureContext } from '@hcengineering/core'
|
||||
import { readFile } from 'fs/promises'
|
||||
import decodeHeic from 'heic-decode'
|
||||
import sharp from 'sharp'
|
||||
|
||||
export async function heicToPng (ctx: MeasureContext, heicFile: string): Promise<string> {
|
||||
const pngFile: string = heicFile + '.png'
|
||||
|
||||
const buffer = await readFile(heicFile)
|
||||
|
||||
const { data, width, height } = await ctx.with('heic-decode', {}, () =>
|
||||
decodeHeic({
|
||||
// @ts-expect-error wrong types
|
||||
buffer: new Uint8Array(buffer)
|
||||
})
|
||||
)
|
||||
|
||||
await ctx.with('sharp', {}, async () => {
|
||||
await sharp(data, {
|
||||
raw: {
|
||||
width,
|
||||
height,
|
||||
channels: 4
|
||||
}
|
||||
})
|
||||
.png()
|
||||
.toFile(pngFile)
|
||||
})
|
||||
|
||||
return pngFile
|
||||
}
|
||||
@@ -111,16 +111,26 @@ export async function OnEmployeeCreate (_txes: Tx[], control: TriggerControl): P
|
||||
|
||||
const emp = control.hierarchy.as(person, contact.mixin.Employee)
|
||||
if (emp.role === 'GUEST') {
|
||||
let readOnlyGuestSpaces: Space[] = []
|
||||
const readonlyEmployees = await control.findAll(control.ctx, contact.mixin.Employee, {
|
||||
personUuid: readOnlyGuestAccountUuid
|
||||
})
|
||||
if (readonlyEmployees.length === 0) continue
|
||||
if (readonlyEmployees.length !== 0) {
|
||||
const readonlyEmployee = readonlyEmployees[0]
|
||||
if (readonlyEmployee.active) {
|
||||
readOnlyGuestSpaces = await control.findAll(control.ctx, core.class.Space, {
|
||||
members: readOnlyGuestAccountUuid
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const readonlyEmployee = readonlyEmployees[0]
|
||||
if (!readonlyEmployee.active) continue
|
||||
const grantSpacesRefs = control.ctx.contextData.grant?.spaces
|
||||
const grantSpaces =
|
||||
grantSpacesRefs !== undefined
|
||||
? await control.findAll(control.ctx, core.class.Space, { _id: { $in: grantSpacesRefs } })
|
||||
: []
|
||||
|
||||
const spaces = await control.findAll(control.ctx, core.class.Space, { members: readOnlyGuestAccountUuid })
|
||||
for (const space of spaces) {
|
||||
for (const space of [...readOnlyGuestSpaces, ...grantSpaces]) {
|
||||
if (space._class === contact.class.PersonSpace || space.members.includes(account)) continue
|
||||
|
||||
const pushTx = systemTxFactory.createTxUpdateDoc(space._class, space.space, space._id, {
|
||||
@@ -149,7 +159,12 @@ export async function OnEmployeeCreate (_txes: Tx[], control: TriggerControl): P
|
||||
}
|
||||
|
||||
const spaces = await control.findAll(control.ctx, core.class.Space, { autoJoin: true })
|
||||
for (const space of spaces) {
|
||||
const grantSpacesRefs = control.ctx.contextData.grant?.spaces
|
||||
const grantSpaces =
|
||||
grantSpacesRefs !== undefined
|
||||
? await control.findAll(control.ctx, core.class.Space, { _id: { $in: grantSpacesRefs } })
|
||||
: []
|
||||
for (const space of [...spaces, ...grantSpaces]) {
|
||||
if (space.members.includes(account)) continue
|
||||
|
||||
const pushTx = systemTxFactory.createTxUpdateDoc(space._class, space.space, space._id, {
|
||||
|
||||
@@ -611,6 +611,7 @@ export async function selectWorkspace (
|
||||
|
||||
let accountUuid: AccountUuid
|
||||
let extra: Record<string, any> | undefined
|
||||
let grant: PermissionsGrant | undefined
|
||||
try {
|
||||
const decodedToken = decodeTokenVerbose(ctx, token ?? '')
|
||||
accountUuid = decodedToken.account
|
||||
@@ -618,6 +619,7 @@ export async function selectWorkspace (
|
||||
workspace = await getWorkspaceById(db, decodedToken.workspace)
|
||||
}
|
||||
extra = decodedToken.extra
|
||||
grant = decodedToken.grant
|
||||
} catch (e) {
|
||||
if (workspace?.allowReadOnlyGuest === true) {
|
||||
accountUuid = readOnlyGuestAccountUuid
|
||||
@@ -666,7 +668,7 @@ export async function selectWorkspace (
|
||||
if (accountUuid === systemAccountUuid) {
|
||||
return {
|
||||
account: accountUuid,
|
||||
token: generateToken(accountUuid, workspace.uuid, extra),
|
||||
token: generateToken(accountUuid, workspace.uuid, extra, undefined, grant),
|
||||
endpoint: getEndpoint(workspace.uuid, workspace.region, getKind(workspace.region)),
|
||||
workspace: workspace.uuid,
|
||||
workspaceUrl: workspace.url,
|
||||
@@ -723,7 +725,7 @@ export async function selectWorkspace (
|
||||
|
||||
return {
|
||||
account: accountUuid,
|
||||
token: generateToken(accountUuid, workspace.uuid, extra),
|
||||
token: generateToken(accountUuid, workspace.uuid, extra, undefined, grant),
|
||||
endpoint: getEndpoint(workspace.uuid, workspace.region, getKind(workspace.region)),
|
||||
workspace: workspace.uuid,
|
||||
workspaceUrl: workspace.url,
|
||||
|
||||
@@ -101,8 +101,7 @@ export async function loadCollabJson (
|
||||
}
|
||||
|
||||
if (!blob.contentType.includes('application/json')) {
|
||||
ctx.error('invalid content type', { contentType: blob.contentType })
|
||||
return undefined
|
||||
ctx.warn('invalid content type', { contentType: blob.contentType })
|
||||
}
|
||||
|
||||
const buffer = await storageAdapter.read(ctx, wsIds, blobId)
|
||||
|
||||
@@ -31,7 +31,8 @@ import core, {
|
||||
type Tx,
|
||||
type TxResult,
|
||||
type TxWorkspaceEvent,
|
||||
type WorkspaceIds
|
||||
type WorkspaceIds,
|
||||
type PermissionsGrant
|
||||
} from '@hcengineering/core'
|
||||
import { PlatformError, unknownError } from '@hcengineering/platform'
|
||||
import { createHash, type Hash } from 'crypto'
|
||||
@@ -180,7 +181,8 @@ export class SessionDataImpl implements SessionData {
|
||||
role: AccountRole
|
||||
}
|
||||
>,
|
||||
readonly service: string
|
||||
readonly service: string,
|
||||
readonly grant?: PermissionsGrant
|
||||
) {
|
||||
this._removedMap = _removedMap
|
||||
this._contextCache = _contextCache
|
||||
|
||||
@@ -234,7 +234,8 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
||||
sctx.contextCache,
|
||||
this.context.modelDb,
|
||||
sctx.socialStringsToUsers,
|
||||
sctx.service
|
||||
sctx.service,
|
||||
sctx.grant
|
||||
)
|
||||
ctx.contextData = asyncContextData
|
||||
const aresult = await this.triggers.apply(
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import type { LoginInfoWithWorkspaces } from '@hcengineering/account-client'
|
||||
import {
|
||||
generateId,
|
||||
type PermissionsGrant,
|
||||
TxProcessor,
|
||||
type Account,
|
||||
type AccountUuid,
|
||||
@@ -141,6 +142,16 @@ export class ClientSession implements Session {
|
||||
return await ctx.ctx.with('load-model', {}, (_ctx) => ctx.pipeline.loadModel(_ctx, lastModelTx, hash))
|
||||
}
|
||||
|
||||
private getPermissionsGrant (): PermissionsGrant | undefined {
|
||||
if (this.token.grant == null) {
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
spaces: this.token.grant?.spaces
|
||||
}
|
||||
}
|
||||
|
||||
includeSessionContext (ctx: ClientSessionCtx): void {
|
||||
const dataId = this.workspace.dataId ?? (this.workspace.uuid as unknown as WorkspaceDataId)
|
||||
const contextData = new SessionDataImpl(
|
||||
@@ -157,7 +168,8 @@ export class ClientSession implements Session {
|
||||
undefined,
|
||||
ctx.pipeline.context.modelDb,
|
||||
ctx.socialStringsToUsers,
|
||||
this.token.extra?.service ?? '🤦♂️user'
|
||||
this.token.extra?.service ?? '🤦♂️user',
|
||||
this.getPermissionsGrant()
|
||||
)
|
||||
ctx.ctx.contextData = contextData
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AccountRole, AccountUuid, MeasureContext, PersonUuid, WorkspaceUuid } from '@hcengineering/core'
|
||||
import { AccountRole, AccountUuid, MeasureContext, PersonUuid, Ref, Space, WorkspaceUuid } from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { decode, encode } from 'jwt-simple'
|
||||
import { validate } from 'uuid'
|
||||
@@ -22,6 +22,8 @@ export interface PermissionsGrant {
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
|
||||
spaces?: Ref<Space>[]
|
||||
|
||||
extra?: Record<string, any>
|
||||
}
|
||||
|
||||
@@ -71,6 +73,7 @@ export function generateToken (
|
||||
role: grant.role,
|
||||
firstName: grant.firstName,
|
||||
lastName: grant.lastName,
|
||||
spaces: grant.spaces,
|
||||
extra: grant.extra
|
||||
}
|
||||
: undefined
|
||||
|
||||
Reference in New Issue
Block a user