Merge remote-tracking branch 'origin/develop' into staging

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2024-11-19 14:37:42 +07:00
230 changed files with 3579 additions and 2551 deletions
+92 -6
View File
@@ -29,6 +29,14 @@ services:
ports:
- 5432:5432
restart: unless-stopped
cockroach:
image: cockroachdb/cockroach:latest-v24.2
ports:
- "26257:26257"
- "8089:8080"
command: start-single-node --insecure
volumes:
- cockroach_db:/cockroach/cockroach-data"
minio:
image: 'minio/minio'
command: server /data --address ":9000" --console-address ":9001"
@@ -78,8 +86,8 @@ services:
# - DB_URL=postgresql://postgres:example@postgres:5432
- DB_URL=${MONGO_URL}
# - DB_NS=account-2
- REGION_INFO=|Mongo;pg|Postgres
- TRANSACTOR_URL=ws://host.docker.internal:3333,ws://host.docker.internal:3331;;pg
- REGION_INFO=|Mongo;pg|Postgres;cockroach|CockroachDB
- TRANSACTOR_URL=ws://host.docker.internal:3333,ws://host.docker.internal:3331;;pg,ws://host.docker.internal:3332;;cockroach,
- SES_URL=
- STORAGE_CONFIG=${STORAGE_CONFIG}
- FRONT_URL=http://host.docker.internal:8087
@@ -124,7 +132,6 @@ services:
# - DB_URL=postgresql://postgres:example@postgres:5432
- SES_URL=
- STORAGE_CONFIG=${STORAGE_CONFIG}
- FRONT_URL=http://host.docker.internal:8087
- RESERVED_DB_NAMES=telegram,gmail,github
- MODEL_ENABLED=*
- ACCOUNTS_URL=http://host.docker.internal:3000
@@ -160,6 +167,32 @@ services:
- INIT_SCRIPT_URL=https://raw.githubusercontent.com/hcengineering/init/main/script.yaml
# - INIT_WORKSPACE=onboarding
restart: unless-stopped
workspace_cockroach:
image: hardcoreeng/workspace
extra_hosts:
- "host.docker.internal:host-gateway"
links:
- cockroach
- minio
- stats
volumes:
- ./branding.json:/var/cfg/branding.json
environment:
# - WS_OPERATION=create
- SERVER_SECRET=secret
- DB_URL=postgresql://root@host.docker.internal:26257/defaultdb?sslmode=disable
- STATS_URL=http://host.docker.internal:4900
- SES_URL=
- REGION=cockroach
- STORAGE_CONFIG=${STORAGE_CONFIG}
- RESERVED_DB_NAMES=telegram,gmail,github
- MODEL_ENABLED=*
- ACCOUNTS_URL=http://host.docker.internal:3000
- BRANDING_PATH=/var/cfg/branding.json
# - PARALLEL=2
- INIT_SCRIPT_URL=https://raw.githubusercontent.com/hcengineering/init/main/script.yaml
# - INIT_WORKSPACE=onboarding
restart: unless-stopped
collaborator:
image: hardcoreeng/collaborator
extra_hosts:
@@ -270,15 +303,47 @@ services:
environment:
# - SERVER_PROVIDER=uweb
# - UWS_HTTP_MAX_HEADERS_SIZE="32768"
- UV_THREADPOOL_SIZE=10
# - UV_THREADPOOL_SIZE=10
- SERVER_PORT=3331
- SERVER_SECRET=secret
- ENABLE_COMPRESSION=true
- FULLTEXT_URL=http://host.docker.internal:4701
- STATS_URL=http://host.docker.internal:4900
- DB_URL=postgresql://postgres:example@postgres:5432
- MONGO_URL=${MONGO_URL}
- 'MONGO_OPTIONS={"appName": "transactor-pg", "maxPoolSize": 1}'
- METRICS_CONSOLE=false
- METRICS_FILE=metrics.txt
- STORAGE_CONFIG=${STORAGE_CONFIG}
- FRONT_URL=http://host.docker.internal:8087
# - APM_SERVER_URL=http://apm-server:8200
- SES_URL=''
- ACCOUNTS_URL=http://host.docker.internal:3000
- LAST_NAME_FIRST=true
- BRANDING_PATH=/var/cfg/branding.json
restart: unless-stopped
transactor_cockroach:
image: hardcoreeng/transactor
extra_hosts:
- "host.docker.internal:host-gateway"
links:
- cockroach
- minio
- account
- stats
# - apm-server
ports:
- 3332:3332
volumes:
- ./branding.json:/var/cfg/branding.json
environment:
# - SERVER_PROVIDER=uweb
# - UWS_HTTP_MAX_HEADERS_SIZE="32768"
# - UV_THREADPOOL_SIZE=10
- SERVER_PORT=3332
- SERVER_SECRET=secret
- ENABLE_COMPRESSION=true
- FULLTEXT_URL=http://host.docker.internal:4702
- STATS_URL=http://host.docker.internal:4900
- DB_URL=postgresql://root@host.docker.internal:26257/defaultdb?sslmode=disable
- METRICS_CONSOLE=false
- METRICS_FILE=metrics.txt
- STORAGE_CONFIG=${STORAGE_CONFIG}
@@ -333,6 +398,26 @@ services:
- STATS_URL=http://host.docker.internal:4900
- REKONI_URL=http://host.docker.internal:4004
- ACCOUNTS_URL=http://host.docker.internal:3000
fulltext_cockroach:
image: hardcoreeng/fulltext
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
links:
- elastic
- postgres
ports:
- 4702:4702
environment:
- PORT=4702
- SERVER_SECRET=secret
- DB_URL=postgresql://root@host.docker.internal:26257/defaultdb?sslmode=disable
- FULLTEXT_DB_URL=http://host.docker.internal:9200
- ELASTIC_INDEX_NAME=local_storage_index
- STORAGE_CONFIG=${STORAGE_CONFIG}
- STATS_URL=http://host.docker.internal:4900
- REKONI_URL=http://host.docker.internal:4004
- ACCOUNTS_URL=http://host.docker.internal:3000
print:
image: hardcoreeng/print
extra_hosts:
@@ -420,3 +505,4 @@ volumes:
dbpg:
files:
elastic:
cockroach_db:
+12 -31
View File
@@ -20,7 +20,6 @@ import { loadCollaborativeDoc, saveCollaborativeDoc, yDocToBuffer } from '@hceng
import contact from '@hcengineering/contact'
import core, {
type ArrOf,
type AttachedDoc,
type BackupClient,
type Class,
ClassifierKind,
@@ -47,7 +46,6 @@ import core, {
type StatusCategory,
type Tx,
type TxCUD,
type TxCollectionCUD,
type TxCreateDoc,
type TxMixin,
TxOperations,
@@ -158,16 +156,6 @@ export async function cleanWorkspace (
await db.collection(DOMAIN_TX).deleteMany({ objectId: (tx as TxRemoveDoc<Doc>).objectId })
processed++
}
if (
tx._class === core.class.TxCollectionCUD &&
(tx as TxCollectionCUD<Doc, AttachedDoc>).tx._class === core.class.TxRemoveDoc
) {
// We need to remove all update and create operations for document
await db.collection(DOMAIN_TX).deleteMany({
'tx.objectId': ((tx as TxCollectionCUD<Doc, AttachedDoc>).tx as TxRemoveDoc<Doc>).objectId
})
processed++
}
}
if (processed % 1000 === 0) {
console.log('processed', processed)
@@ -219,18 +207,13 @@ export async function cleanRemovedTransactions (workspaceId: WorkspaceId, transa
try {
let count = 0
while (true) {
const removedDocs = await connection.findAll(
core.class.TxCollectionCUD,
{ 'tx._class': core.class.TxRemoveDoc },
{ limit: 1000 }
)
const removedDocs = await connection.findAll(core.class.TxRemoveDoc, {}, { limit: 1000 })
if (removedDocs.length === 0) {
break
}
const toRemove = await connection.findAll(core.class.TxCollectionCUD, {
'tx._class': { $in: [core.class.TxCreateDoc, core.class.TxRemoveDoc, core.class.TxUpdateDoc] },
'tx.objectId': { $in: removedDocs.map((it) => it.tx.objectId) }
const toRemove = await connection.findAll(core.class.TxCUD, {
objectId: { $in: removedDocs.map((it) => it.objectId) }
})
await connection.clean(
DOMAIN_TX,
@@ -368,20 +351,18 @@ export async function fixCommentDoubleIdCreate (workspaceId: WorkspaceId, transa
mode: 'backup'
})) as unknown as CoreClient & BackupClient
try {
const commentTxes = await connection.findAll(core.class.TxCollectionCUD, {
'tx._class': core.class.TxCreateDoc,
'tx.objectClass': chunter.class.ChatMessage
const commentTxes = await connection.findAll(core.class.TxCreateDoc, {
objectClass: chunter.class.ChatMessage
})
const commentTxesRemoved = await connection.findAll(core.class.TxCollectionCUD, {
'tx._class': core.class.TxRemoveDoc,
'tx.objectClass': chunter.class.ChatMessage
const commentTxesRemoved = await connection.findAll(core.class.TxRemoveDoc, {
objectClass: chunter.class.ChatMessage
})
const removed = new Map(commentTxesRemoved.map((it) => [it.tx.objectId, it]))
const removed = new Map(commentTxesRemoved.map((it) => [it.objectId, it]))
// Do not checked removed
const objSet = new Set<Ref<Doc>>()
const oldValue = new Map<Ref<Doc>, string>()
for (const c of commentTxes) {
const cid = c.tx.objectId
const cid = c.objectId
if (removed.has(cid)) {
continue
}
@@ -389,7 +370,7 @@ export async function fixCommentDoubleIdCreate (workspaceId: WorkspaceId, transa
objSet.add(cid)
if (has) {
// We have found duplicate one, let's rename it.
const doc = TxProcessor.createDoc2Doc<ChatMessage>(c.tx as unknown as TxCreateDoc<ChatMessage>)
const doc = TxProcessor.createDoc2Doc<ChatMessage>(c as unknown as TxCreateDoc<ChatMessage>)
if (doc.message !== '' && doc.message.trim() !== '<p></p>') {
await connection.clean(DOMAIN_TX, [c._id])
if (oldValue.get(cid) === doc.message.trim()) {
@@ -398,8 +379,8 @@ export async function fixCommentDoubleIdCreate (workspaceId: WorkspaceId, transa
oldValue.set(doc._id, doc.message)
console.log('renaming', cid, doc.message)
// Remove previous transaction.
c.tx.objectId = generateId()
doc._id = c.tx.objectId as Ref<ChatMessage>
c.objectId = generateId()
doc._id = c.objectId as Ref<ChatMessage>
await connection.upload(DOMAIN_TX, [c])
// Also we need to create snapsot
await connection.upload(DOMAIN_ACTIVITY, [doc])
+1 -11
View File
@@ -215,17 +215,7 @@ async function getMixinWithForeignProperties (
{ limit: 1, sort: { modifiedOn: SortingOrder.Descending } }
)
const collectionTx = await connection.findAll(
core.class.TxCollectionCUD,
{
'tx._class': core.class.TxUpdateDoc,
'tx.objectId': doc._id,
[`tx.operations.${property}`]: { $exists: true }
},
{ limit: 1, sort: { modifiedOn: SortingOrder.Descending } }
)
const cModifiedOn = Math.max(updateDocTx[0]?.modifiedOn ?? 0, collectionTx[0]?.modifiedOn ?? 0)
const cModifiedOn = updateDocTx[0]?.modifiedOn ?? 0
const mModifiedOn = mixinTx[0]?.modifiedOn ?? 0
properties.push({ name: property, cValue, mValue, cModifiedOn, mModifiedOn })
+2 -5
View File
@@ -201,11 +201,8 @@ export class TDocUpdateMessageViewlet extends TDoc implements DocUpdateMessageVi
@Model(activity.class.ActivityExtension, core.class.Doc, DOMAIN_MODEL)
export class TActivityExtension extends TDoc implements ActivityExtension {
@Prop(TypeRef(core.class.Class), core.string.Class)
@Index(IndexKind.Indexed)
ofClass!: Ref<Class<Doc>>
components!: Record<ActivityExtensionKind, AnyComponent>
ofClass!: Ref<Class<Doc>>
components!: Record<ActivityExtensionKind, { component: AnyComponent, props?: Record<string, any> }>
}
@Model(activity.class.ActivityMessagesFilter, core.class.Doc, DOMAIN_MODEL)
+1 -1
View File
@@ -85,7 +85,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: analyticsCollector.class.OnboardingChannel,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc<ActivityMessageControl<OnboardingChannel>>(
+5 -5
View File
@@ -273,27 +273,27 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: chunter.class.Channel,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: chunter.class.DirectMessage,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: activity.class.DocUpdateMessage,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: chunter.class.ChatMessage,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: activity.class.ActivityReference,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
// Indexing
+4 -4
View File
@@ -263,22 +263,22 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: contact.class.Contact,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: contact.class.Person,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: contact.class.Organization,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: contact.class.Member,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.mixin(contact.mixin.Employee, core.class.Class, view.mixin.ObjectFactory, {
+2 -2
View File
@@ -514,7 +514,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: documents.class.DocumentCategory,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.mixin(documents.class.DocumentCategory, core.class.Class, view.mixin.ObjectPresenter, {
@@ -768,7 +768,7 @@ export function defineNotifications (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: documents.class.DocumentComment,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.mixin(documents.class.ControlledDocument, core.class.Class, notification.mixin.ClassCollaborators, {
+2 -26
View File
@@ -24,12 +24,7 @@ import {
DOMAIN_STATUS,
DOMAIN_TRANSIENT,
DOMAIN_TX,
systemAccountEmail,
type AttachedDoc,
type Class,
type Doc,
type IndexingConfiguration,
type TxCollectionCUD
systemAccountEmail
} from '@hcengineering/core'
import { type Builder } from '@hcengineering/model'
import { TBenchmarkDoc } from './benchmark'
@@ -91,17 +86,7 @@ import {
import { defineSpaceType } from './spaceType'
import { TDomainStatusPlaceholder, TStatus, TStatusCategory } from './status'
import { TUserStatus } from './transient'
import {
TTx,
TTxApplyIf,
TTxCollectionCUD,
TTxCreateDoc,
TTxCUD,
TTxMixin,
TTxRemoveDoc,
TTxUpdateDoc,
TTxWorkspaceEvent
} from './tx'
import { TTx, TTxApplyIf, TTxCreateDoc, TTxCUD, TTxMixin, TTxRemoveDoc, TTxUpdateDoc, TTxWorkspaceEvent } from './tx'
export { coreId, DOMAIN_SPACE } from '@hcengineering/core'
export * from './core'
@@ -122,7 +107,6 @@ export function createModel (builder: Builder): void {
TTxCUD,
TTxCreateDoc,
TAttachedDoc,
TTxCollectionCUD,
TTxMixin,
TTxUpdateDoc,
TTxRemoveDoc,
@@ -188,14 +172,6 @@ export function createModel (builder: Builder): void {
core.account.System
)
builder.mixin<Class<TxCollectionCUD<Doc, AttachedDoc>>, IndexingConfiguration<TxCollectionCUD<Doc, AttachedDoc>>>(
core.class.TxCollectionCUD,
core.class.Class,
core.mixin.IndexConfiguration,
{
indexes: ['tx.objectId', 'tx.operations.attachedTo']
}
)
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
domain: DOMAIN_TX,
disabled: [
+70 -5
View File
@@ -15,22 +15,26 @@
import { saveCollaborativeDoc } from '@hcengineering/collaboration'
import core, {
collaborativeDocParse,
coreId,
DOMAIN_MODEL_TX,
DOMAIN_SPACE,
DOMAIN_STATUS,
DOMAIN_TX,
MeasureMetricsContext,
RateLimiter,
collaborativeDocParse,
coreId,
generateId,
makeCollaborativeDoc,
MeasureMetricsContext,
RateLimiter,
type AnyAttribute,
type Class,
type Doc,
type Domain,
type MeasureContext,
type Ref,
type Space,
type Status,
type TxCreateDoc
type TxCreateDoc,
type TxCUD
} from '@hcengineering/core'
import {
createDefaultSpace,
@@ -285,6 +289,67 @@ export const coreOperation: MigrateOperation = {
await client.update(DOMAIN_TX, { '%hash%': { $exists: true } }, { $set: { '%hash%': null } })
await client.update(DOMAIN_SPACE, { '%hash%': { $exists: true } }, { $set: { '%hash%': null } })
}
},
{
state: 'remove-github-patches',
func: async (client) => {
await client.update(
DOMAIN_TX,
{
objectClass: 'tracker:class:Issue',
collection: 'pullRequests',
'tx.attributes.patch': { $exists: true }
},
{ $unset: { 'tx.attributes.patch': 1 } }
)
}
},
{
state: 'remove-collection-txes',
func: async (client) => {
let processed = 0
const iterator = await client.traverse<TxCUD<Doc>>(DOMAIN_TX, {
_class: 'core:class:TxCollectionCUD' as Ref<Class<Doc>>
})
while (true) {
const txes = await iterator.next(1000)
if (txes === null || txes.length === 0) break
processed += txes.length
try {
await client.deleteMany(DOMAIN_TX, {
_id: { $in: txes.map((it) => it._id) }
})
await client.create(
DOMAIN_TX,
txes.map((tx) => {
const { collection, objectId, objectClass } = tx
return {
collection,
attachedTo: objectId,
attachedToClass: objectClass,
...(tx as any).tx
}
})
)
} catch (err: any) {
console.error(err)
}
console.log('processed', processed)
}
await iterator.close()
}
},
{
state: 'move-model-txes',
func: async (client) => {
await client.move(
DOMAIN_TX,
{
objectSpace: core.space.Model
},
DOMAIN_MODEL_TX
)
}
}
])
},
-8
View File
@@ -14,7 +14,6 @@
//
import {
type AttachedDoc,
type Class,
type Data,
type Doc,
@@ -28,7 +27,6 @@ import {
type Space,
type Tx,
type TxApplyIf,
type TxCollectionCUD,
type TxCreateDoc,
type TxCUD,
type TxMixin,
@@ -72,12 +70,6 @@ export class TTxCreateDoc<T extends Doc> extends TTxCUD<T> implements TxCreateDo
attributes!: Data<T>
}
@Model(core.class.TxCollectionCUD, core.class.TxCUD)
export class TTxCollectionCUD<T extends Doc, P extends AttachedDoc> extends TTxCUD<T> implements TxCollectionCUD<T, P> {
collection!: string
tx!: TxCUD<P>
}
@Model(core.class.TxMixin, core.class.TxCUD)
export class TTxMixin<D extends Doc, M extends D> extends TTxCUD<D> implements TxMixin<D, M> {
mixin!: Ref<Mixin<M>>
+1 -1
View File
@@ -492,7 +492,7 @@ function defineDocument (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: document.class.Document,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
// Search
+4 -4
View File
@@ -13,11 +13,11 @@
// limitations under the License.
//
import { type CollaborativeDoc, DOMAIN_TX, MeasureMetricsContext, SortingOrder } from '@hcengineering/core'
import { type DocumentSnapshot, type Document, type Teamspace } from '@hcengineering/document'
import { DOMAIN_MODEL_TX, MeasureMetricsContext, SortingOrder, type CollaborativeDoc } from '@hcengineering/core'
import { type Document, type DocumentSnapshot, type Teamspace } from '@hcengineering/document'
import {
tryMigrate,
migrateSpaceRanks,
tryMigrate,
type MigrateOperation,
type MigrateUpdate,
type MigrationClient,
@@ -75,7 +75,7 @@ async function migrateTeamspacesMixins (client: MigrationClient): Promise<void>
const newSpaceTypeMixin = document.mixin.DefaultTeamspaceTypeData
await client.update(
DOMAIN_TX,
DOMAIN_MODEL_TX,
{
objectClass: core.class.Attribute,
'attributes.attributeOf': oldSpaceTypeMixin
+1 -1
View File
@@ -595,7 +595,7 @@ function defineFile (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: drive.class.File,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
// Search
+6 -6
View File
@@ -1,9 +1,9 @@
import {
AccountRole,
DOMAIN_TX,
DOMAIN_MODEL_TX,
type Account,
type Ref,
type Space,
type Account,
type TxCreateDoc,
type TxUpdateDoc
} from '@hcengineering/core'
@@ -30,13 +30,13 @@ export const guestOperation: MigrateOperation = {
1: AccountRole.Maintainer,
2: AccountRole.Owner
}
const createTxes = await client.find<TxCreateDoc<Account>>(DOMAIN_TX, {
const createTxes = await client.find<TxCreateDoc<Account>>(DOMAIN_MODEL_TX, {
_class: core.class.TxCreateDoc,
'attributes.role': { $in: [0, 1, 2] }
})
for (const tx of createTxes) {
await client.update(
DOMAIN_TX,
DOMAIN_MODEL_TX,
{
_id: tx._id
},
@@ -47,13 +47,13 @@ export const guestOperation: MigrateOperation = {
}
)
}
const updateTxes = await client.find<TxUpdateDoc<Account>>(DOMAIN_TX, {
const updateTxes = await client.find<TxUpdateDoc<Account>>(DOMAIN_MODEL_TX, {
_class: core.class.TxUpdateDoc,
'operations.role': { $in: [0, 1, 2] }
})
for (const tx of updateTxes) {
await client.update(
DOMAIN_TX,
DOMAIN_MODEL_TX,
{
_id: tx._id
},
+3 -3
View File
@@ -85,17 +85,17 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: inventory.class.Product,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: inventory.class.Category,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: inventory.class.Variant,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.mixin(inventory.class.Category, core.class.Class, view.mixin.ObjectPresenter, {
+2 -2
View File
@@ -51,12 +51,12 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: lead.class.Lead,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: lead.class.Funnel,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.mixin(lead.class.Funnel, core.class.Class, workbench.mixin.SpaceView, {
+10 -3
View File
@@ -13,7 +13,14 @@
// limitations under the License.
//
import { AccountRole, DOMAIN_TX, makeCollaborativeDoc, TxOperations, type Ref, type Status } from '@hcengineering/core'
import {
AccountRole,
DOMAIN_MODEL_TX,
makeCollaborativeDoc,
TxOperations,
type Ref,
type Status
} from '@hcengineering/core'
import { leadId, type Lead } from '@hcengineering/lead'
import {
tryMigrate,
@@ -26,7 +33,7 @@ import {
import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
import contact, { DOMAIN_CONTACT } from '@hcengineering/model-contact'
import task, { DOMAIN_TASK, createSequence, migrateDefaultStatusesBase } from '@hcengineering/model-task'
import task, { createSequence, DOMAIN_TASK, migrateDefaultStatusesBase } from '@hcengineering/model-task'
import lead from './plugin'
import { defaultLeadStatuses } from './spaceType'
@@ -110,7 +117,7 @@ async function migrateDefaultTypeMixins (client: MigrationClient): Promise<void>
const newTaskTypeMixin = lead.mixin.LeadTypeData
await client.update(
DOMAIN_TX,
DOMAIN_MODEL_TX,
{
objectClass: core.class.Attribute,
'attributes.attributeOf': oldSpaceTypeMixin
+2 -1
View File
@@ -28,8 +28,8 @@
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/attachment": "^0.6.14",
"@hcengineering/activity": "^0.6.0",
"@hcengineering/attachment": "^0.6.14",
"@hcengineering/chunter": "^0.6.20",
"@hcengineering/contact": "^0.6.24",
"@hcengineering/core": "^0.6.32",
@@ -46,6 +46,7 @@
"@hcengineering/notification": "^0.6.23",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/setting": "^0.6.17",
"@hcengineering/time": "^0.6.0",
"@hcengineering/ui": "^0.6.15",
"@hcengineering/view": "^0.6.13",
"@hcengineering/workbench": "^0.6.16"
+73 -11
View File
@@ -20,7 +20,10 @@ import {
DOMAIN_TRANSIENT,
IndexKind,
type Ref,
type CollaborativeDoc
type CollaborativeDoc,
type Doc,
type Timestamp,
type CollectionSize
} from '@hcengineering/core'
import {
type DevicesPreference,
@@ -37,7 +40,8 @@ import {
type RoomInfo,
type RoomType,
type RoomLanguage,
type MeetingMinutes
type MeetingMinutes,
type MeetingStatus
} from '@hcengineering/love'
import {
type Builder,
@@ -52,7 +56,9 @@ import {
TypeCollaborativeDoc,
TypeRef,
TypeString,
UX
TypeTimestamp,
UX,
TypeAny
} from '@hcengineering/model'
import calendar, { TEvent } from '@hcengineering/model-calendar'
import core, { TAttachedDoc, TDoc } from '@hcengineering/model-core'
@@ -66,6 +72,7 @@ import workbench, { WidgetType } from '@hcengineering/workbench'
import activity from '@hcengineering/activity'
import chunter from '@hcengineering/chunter'
import attachment from '@hcengineering/attachment'
import time, { type ToDo, type Todoable } from '@hcengineering/time'
import love from './plugin'
@@ -107,6 +114,9 @@ export class TRoom extends TDoc implements Room {
@Prop(PropCollection(love.class.MeetingMinutes), love.string.MeetingMinutes)
meetings?: number
@Prop(PropCollection(chunter.class.ChatMessage), activity.string.Messages)
messages?: number
}
@Model(love.class.Office, love.class.Room)
@@ -184,8 +194,13 @@ export class TMeeting extends TEvent implements Meeting {
}
@Model(love.class.MeetingMinutes, core.class.Doc, DOMAIN_MEETING_MINUTES)
@UX(love.string.MeetingMinutes, love.icon.Cam)
export class TMeetingMinutes extends TAttachedDoc implements MeetingMinutes {
@UX(love.string.MeetingMinutes, love.icon.Cam, undefined, 'createdOn', undefined, love.string.MeetingsMinutes)
export class TMeetingMinutes extends TAttachedDoc implements MeetingMinutes, Todoable {
@Prop(TypeRef(core.class.Doc), love.string.Room, { editor: love.component.MeetingMinutesDocEditor })
@Index(IndexKind.Indexed)
@ReadOnly()
declare attachedTo: Ref<Doc>
@Hidden()
sid!: string
@@ -197,6 +212,12 @@ export class TMeetingMinutes extends TAttachedDoc implements MeetingMinutes {
@Index(IndexKind.FullText)
description!: CollaborativeDoc
@Prop(TypeAny(love.component.MeetingMinutesStatusPresenter, love.string.Status), love.string.Status, {
editor: love.component.MeetingMinutesStatusPresenter
})
@ReadOnly()
status!: MeetingStatus
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
attachments?: number
@@ -205,6 +226,18 @@ export class TMeetingMinutes extends TAttachedDoc implements MeetingMinutes {
@Prop(PropCollection(chunter.class.ChatMessage), activity.string.Messages)
messages?: number
@Prop(TypeTimestamp(), love.string.MeetingStart, { editor: view.component.TimestampPresenter })
@ReadOnly()
@Index(IndexKind.IndexedDsc)
declare createdOn: Timestamp
@Prop(TypeTimestamp(), love.string.MeetingEnd)
@ReadOnly()
meetingEnd?: Timestamp
@Prop(Collection(time.class.ToDo), getEmbeddedLabel('Action Items'))
todos?: CollectionSize<ToDo>
}
export default love
@@ -418,17 +451,17 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: love.class.Room,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput, props: { collection: 'messages' } } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: love.class.Office,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput, props: { collection: 'messages' } } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: love.class.MeetingMinutes,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput, props: { collection: 'messages' } } }
})
builder.mixin(love.class.MeetingMinutes, core.class.Class, activity.mixin.ActivityDoc, {})
@@ -439,6 +472,10 @@ export function createModel (builder: Builder): void {
presenter: love.component.MeetingMinutesPresenter
})
builder.mixin(love.class.Room, core.class.Class, view.mixin.ObjectPresenter, {
presenter: love.component.RoomPresenter
})
builder.mixin(love.class.MeetingMinutes, core.class.Class, view.mixin.CollectionEditor, {
editor: love.component.MeetingMinutesSection
})
@@ -467,10 +504,11 @@ export function createModel (builder: Builder): void {
descriptor: view.viewlet.Table,
config: [
'',
{ key: 'status', presenter: love.component.MeetingMinutesStatusPresenter, label: love.string.Status },
'createdOn',
'meetingEnd',
{ key: 'messages', displayProps: { key: 'messages', suffix: true } },
{ key: 'transcription', displayProps: { key: 'transcription', suffix: true } },
'modifiedOn',
'modifiedBy'
{ key: 'transcription', displayProps: { key: 'transcription', suffix: true } }
],
configOptions: {
hiddenKeys: ['description'],
@@ -481,6 +519,26 @@ export function createModel (builder: Builder): void {
love.viewlet.TableMeetingMinutes
)
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: love.class.MeetingMinutes,
descriptor: view.viewlet.Table,
config: [
'',
{ key: 'status', presenter: love.component.MeetingMinutesStatusPresenter, label: love.string.Status },
'createdOn',
'meetingEnd'
],
configOptions: {
sortable: true
},
variant: 'embedded'
},
love.viewlet.TableMeetingMinutesEmbedded
)
builder.createDoc(
view.class.ViewletDescriptor,
core.space.Model,
@@ -574,4 +632,8 @@ export function createModel (builder: Builder): void {
indexes: [],
searchDisabled: true
})
builder.mixin(love.class.MeetingMinutes, core.class.Class, view.mixin.ObjectPanelFooter, {
editor: love.component.PanelControlBar
})
}
+20 -1
View File
@@ -16,7 +16,16 @@
import contact from '@hcengineering/contact'
import { type Space, TxOperations, type Ref, makeCollaborativeDoc } from '@hcengineering/core'
import drive from '@hcengineering/drive'
import { RoomAccess, RoomType, createDefaultRooms, isOffice, loveId, type Floor, type Room } from '@hcengineering/love'
import {
MeetingStatus,
RoomAccess,
RoomType,
createDefaultRooms,
isOffice,
loveId,
type Floor,
type Room
} from '@hcengineering/love'
import {
createDefaultSpace,
migrateSpace,
@@ -142,6 +151,16 @@ export const loveOperation: MigrateOperation = {
}
}
}
},
{
state: 'default-meeting-minutes-status',
func: async (client) => {
await client.update(
DOMAIN_MEETING_MINUTES,
{ status: { $exists: false } },
{ status: MeetingStatus.Finished }
)
}
}
])
},
+9 -3
View File
@@ -13,6 +13,8 @@
// limitations under the License.
//
import chunter from '@hcengineering/chunter'
import contact, { type PersonSpace } from '@hcengineering/contact'
import core, { DOMAIN_TX, type Class, type Doc, type DocumentQuery, type Ref, type Space } from '@hcengineering/core'
import {
migrateSpace,
@@ -28,11 +30,9 @@ import notification, {
type InboxNotification
} from '@hcengineering/notification'
import { DOMAIN_PREFERENCE } from '@hcengineering/preference'
import contact, { type PersonSpace } from '@hcengineering/contact'
import chunter from '@hcengineering/chunter'
import { DOMAIN_DOC_NOTIFY, DOMAIN_NOTIFICATION, DOMAIN_USER_NOTIFY } from './index'
import { DOMAIN_SPACE } from '@hcengineering/model-core'
import { DOMAIN_DOC_NOTIFY, DOMAIN_NOTIFICATION, DOMAIN_USER_NOTIFY } from './index'
export async function removeNotifications (
client: MigrationClient,
@@ -429,6 +429,12 @@ export const notificationOperation: MigrateOperation = {
objectClass: notification.class.BrowserNotification
})
}
},
{
state: 'migrate-dnc-space',
func: async (client) => {
await client.update(DOMAIN_DOC_NOTIFY, { space: core.space.Space }, { space: core.space.Workspace })
}
}
])
},
+2 -2
View File
@@ -159,7 +159,7 @@ function defineProduct (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: products.class.Product,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.mixin(products.class.Product, core.class.Class, view.mixin.ObjectEditor, {
@@ -299,7 +299,7 @@ function defineProductVersion (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: products.class.ProductVersion,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.mixin(products.class.ProductVersion, core.class.Class, view.mixin.ObjectEditor, {
+3 -3
View File
@@ -56,17 +56,17 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: recruit.class.Vacancy,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: recruit.class.Applicant,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: recruit.class.Review,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.mixin(recruit.class.Vacancy, core.class.Class, workbench.mixin.SpaceView, {
+2 -2
View File
@@ -15,7 +15,7 @@
import { getCategories } from '@anticrm/skillset'
import core, {
DOMAIN_TX,
DOMAIN_MODEL_TX,
toIdMap,
TxOperations,
type Doc,
@@ -149,7 +149,7 @@ async function migrateDefaultTypeMixins (client: MigrationClient): Promise<void>
const newTaskTypeMixin = recruit.mixin.ApplicantTypeData
await client.update(
DOMAIN_TX,
DOMAIN_MODEL_TX,
{
objectClass: core.class.Attribute,
'attributes.attributeOf': oldSpaceTypeMixin
+24 -14
View File
@@ -18,7 +18,7 @@ import {
Account,
IndexKind,
type CollaborativeDoc,
type Collection as Array,
type Collection,
type Domain,
type Markup,
type Ref,
@@ -28,7 +28,7 @@ import {
type Timestamp
} from '@hcengineering/core'
import {
Collection,
Collection as TypeCollection,
Hidden,
Index,
Mixin,
@@ -70,7 +70,9 @@ export class TVacancy extends TProject implements Vacancy {
@Index(IndexKind.FullText)
fullDescription!: CollaborativeDoc
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
@Prop(TypeCollection(attachment.class.Attachment), attachment.string.Attachments, {
shortLabel: attachment.string.Files
})
attachments?: number
@Prop(TypeDate(), recruit.string.Due, recruit.icon.Calendar)
@@ -83,13 +85,16 @@ export class TVacancy extends TProject implements Vacancy {
@Prop(TypeRef(contact.class.Organization), recruit.string.Company, { icon: contact.icon.Company })
company?: Ref<Organization>
@Prop(Collection(chunter.class.ChatMessage), chunter.string.Comments)
@Prop(TypeCollection(chunter.class.ChatMessage), chunter.string.Comments)
comments?: number
@Prop(TypeString(), recruit.string.Vacancy)
@Index(IndexKind.FullText)
@Hidden()
number!: number
@Prop(TypeCollection(survey.class.Poll), survey.string.Polls)
polls?: Collection<Poll>
}
@Mixin(recruit.mixin.Candidate, contact.class.Person)
@@ -99,7 +104,7 @@ export class TCandidate extends TPerson implements Candidate {
@Index(IndexKind.FullText)
title?: string
@Prop(Collection(recruit.class.Applicant), recruit.string.Applications, {
@Prop(TypeCollection(recruit.class.Applicant), recruit.string.Applications, {
shortLabel: recruit.string.ApplicationsShort
})
applications?: number
@@ -114,26 +119,29 @@ export class TCandidate extends TPerson implements Candidate {
@Index(IndexKind.FullText)
source?: string
@Prop(Collection(tags.class.TagReference, recruit.string.SkillLabel), recruit.string.SkillsLabel, {
@Prop(TypeCollection(tags.class.TagReference, recruit.string.SkillLabel), recruit.string.SkillsLabel, {
icon: recruit.icon.Skills,
schema: '3'
})
skills?: number
@Prop(Collection(recruit.class.Review, recruit.string.Review), recruit.string.Reviews)
@Prop(TypeCollection(recruit.class.Review, recruit.string.Review), recruit.string.Reviews)
reviews?: number
@Prop(
Collection(recruit.class.ApplicantMatch, getEmbeddedLabel('Vacancy match')),
TypeCollection(recruit.class.ApplicantMatch, getEmbeddedLabel('Vacancy match')),
getEmbeddedLabel('Vacancy Matches')
)
vacancyMatch?: number
@Prop(TypeCollection(survey.class.Poll), survey.string.Polls)
polls?: Collection<Poll>
}
@Mixin(recruit.mixin.VacancyList, contact.class.Organization)
@UX(recruit.string.VacancyList, recruit.icon.RecruitApplication, 'CM', 'name')
export class TVacancyList extends TOrganization implements VacancyList {
@Prop(Collection(recruit.class.Vacancy), recruit.string.Vacancies)
@Prop(TypeCollection(recruit.class.Vacancy), recruit.string.Vacancies)
vacancies!: number
}
@@ -162,8 +170,8 @@ export class TApplicant extends TTask implements Applicant {
@Index(IndexKind.Indexed)
declare status: Ref<Status>
@Prop(Collection(survey.class.Poll), survey.string.Polls)
polls?: Array<Poll>
@Prop(TypeCollection(survey.class.Poll), survey.string.Polls)
polls?: Collection<Poll>
}
@Model(recruit.class.ApplicantMatch, core.class.AttachedDoc, DOMAIN_TASK)
@@ -211,7 +219,7 @@ export class TReview extends TEvent implements Review {
@Prop(TypeRef(contact.class.Organization), recruit.string.Company, { icon: contact.icon.Company })
company?: Ref<Organization>
@Prop(Collection(recruit.class.Opinion), recruit.string.Opinions)
@Prop(TypeCollection(recruit.class.Opinion), recruit.string.Opinions)
opinions?: number
}
@@ -225,10 +233,12 @@ export class TOpinion extends TAttachedDoc implements Opinion {
@Prop(TypeRef(recruit.class.Review), recruit.string.Review)
declare attachedTo: Ref<Review>
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
@Prop(TypeCollection(attachment.class.Attachment), attachment.string.Attachments, {
shortLabel: attachment.string.Files
})
attachments?: number
@Prop(Collection(chunter.class.ChatMessage), chunter.string.Comments)
@Prop(TypeCollection(chunter.class.ChatMessage), chunter.string.Comments)
comments?: number
@Prop(TypeMarkup(), recruit.string.Description)
+6 -6
View File
@@ -12,18 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
import core, { DOMAIN_TX, type Ref, type TxCreateDoc } from '@hcengineering/core'
import request, { requestId, type Request } from '@hcengineering/request'
import contact, { type Person, type PersonAccount } from '@hcengineering/contact'
import core, { DOMAIN_MODEL_TX, type Ref, type TxCreateDoc } from '@hcengineering/core'
import {
type MigrateUpdate,
type MigrationDocumentQuery,
tryMigrate,
type MigrateOperation,
type MigrateUpdate,
type MigrationClient,
type MigrationDocumentQuery,
type MigrationUpgradeClient,
type ModelLogger
} from '@hcengineering/model'
import contact, { type Person, type PersonAccount } from '@hcengineering/contact'
import request, { requestId, type Request } from '@hcengineering/request'
import { DOMAIN_REQUEST } from '.'
@@ -32,7 +32,7 @@ async function migrateRequestPersonAccounts (client: MigrationClient): Promise<v
const requests = await client.find<Request>(DOMAIN_REQUEST, {
_class: { $in: descendants }
})
const personAccountsCreateTxes = await client.find(DOMAIN_TX, {
const personAccountsCreateTxes = await client.find(DOMAIN_MODEL_TX, {
_class: core.class.TxCreateDoc,
objectClass: contact.class.PersonAccount
})
+6 -4
View File
@@ -36,14 +36,16 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverActivity.trigger.OnReactionChanged,
txMatch: {
collection: 'reactions',
_class: core.class.TxCollectionCUD
collection: 'reactions'
},
isAsync: true
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverActivity.trigger.ActivityMessagesHandler,
txMatch: {
objectClass: { $nin: [activity.class.ActivityMessage, notification.class.DocNotifyContext] }
},
isAsync: true
})
@@ -54,8 +56,8 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverActivity.trigger.ReferenceTrigger,
txMatch: {
'tx.objectClass': { $ne: activity.class.ActivityReference },
objectClass: {
objectClass: { $ne: activity.class.ActivityReference },
attachedToClass: {
$nin: [
notification.class.InboxNotification,
notification.class.DocNotifyContext,
+8 -16
View File
@@ -18,16 +18,13 @@ import core, {
MeasureMetricsContext,
SortingOrder,
TxFactory,
TxProcessor,
toFindResult,
toIdMap,
type AttachedDoc,
type Class,
type Doc,
type Ref,
type Tx,
type TxCUD,
type TxCollectionCUD,
type TxCreateDoc
} from '@hcengineering/core'
import {
@@ -85,12 +82,11 @@ async function generateDocUpdateMessageByTx (
tx,
control,
undefined,
undefined,
objectCache
)
for (const collectionTx of createCollectionCUDTxes) {
const createTx = collectionTx.tx as TxCreateDoc<DocUpdateMessage>
const createTx = collectionTx as TxCreateDoc<DocUpdateMessage>
const domain = client.hierarchy.getDomain(createTx.objectClass)
await client.create<DocUpdateMessage>(domain, {
@@ -152,15 +148,14 @@ async function createDocUpdateMessages (client: MigrationClient): Promise<void>
continue
}
if (v._class === core.class.TxCollectionCUD) {
if (v.attachedToClass !== undefined && v.attachedTo !== undefined) {
try {
const vcol = v as TxCollectionCUD<Doc, AttachedDoc>
const _cl = client.hierarchy.getBaseClass(vcol.tx.objectClass)
const _cl = client.hierarchy.getBaseClass(v.attachedToClass)
const s = byClass.get(_cl) ?? new Set()
s.add(vcol.tx.objectId)
s.add(v.attachedTo)
byClass.set(_cl, s)
} catch {
const objClass = (v as TxCollectionCUD<Doc, AttachedDoc>).tx.objectClass
const objClass = v.attachedToClass
const has = classNotFound.has(objClass)
if (!has) {
classNotFound.add(objClass)
@@ -198,8 +193,7 @@ async function createDocUpdateMessages (client: MigrationClient): Promise<void>
}
const transactions = allTransactions.get(d._id) ?? []
for (const tx of transactions) {
const innerTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
txIds.add(innerTx._id)
txIds.add(tx._id)
}
}
@@ -220,10 +214,8 @@ async function createDocUpdateMessages (client: MigrationClient): Promise<void>
}
const transactions = allTransactions.get(d._id) ?? []
for (const tx of transactions) {
const innerTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
if (!client.hierarchy.hasClass(innerTx.objectClass)) {
const objClass = innerTx.objectClass
if (!client.hierarchy.hasClass(tx.objectClass)) {
const objClass = tx.objectClass
const has = classNotFound.has(objClass)
if (!has) {
classNotFound.add(objClass)
+2 -3
View File
@@ -27,9 +27,8 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverAttachment.trigger.OnAttachmentDelete,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx.objectClass': attachment.class.Attachment,
'tx._class': core.class.TxRemoveDoc
_class: core.class.TxRemoveDoc,
objectClass: attachment.class.Attachment
}
})
}
+1 -1
View File
@@ -44,7 +44,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverCalendar.trigger.OnEvent,
txMatch: {
'tx.objectClass': calendar.class.Event
objectClass: calendar.class.Event
}
})
+4 -6
View File
@@ -78,18 +78,16 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverChunter.trigger.OnChatMessageRemoved,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx._class': core.class.TxRemoveDoc,
'tx.objectClass': chunter.class.ChatMessage
_class: core.class.TxRemoveDoc,
objectClass: chunter.class.ChatMessage
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverChunter.trigger.ChatNotificationsHandler,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx._class': core.class.TxCreateDoc,
'tx.objectClass': chunter.class.ChatMessage
_class: core.class.TxCreateDoc,
objectClass: chunter.class.ChatMessage
},
isAsync: true
})
+4 -1
View File
@@ -23,6 +23,9 @@ export { serverCollaborationId } from '@hcengineering/server-collaboration'
export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverCollaboration.trigger.OnDelete
trigger: serverCollaboration.trigger.OnDelete,
txMatch: {
_class: core.class.TxRemoveDoc
}
})
}
@@ -33,11 +33,10 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverDocuments.trigger.OnDocApprovalRequestApproved,
txMatch: {
_class: core.class.TxCollectionCUD,
objectClass: documents.class.ControlledDocument,
'tx._class': core.class.TxUpdateDoc,
'tx.objectClass': documents.class.DocumentApprovalRequest,
'tx.operations.status': RequestStatus.Completed
_class: core.class.TxUpdateDoc,
attachedToClass: documents.class.ControlledDocument,
objectClass: documents.class.DocumentApprovalRequest,
'operations.status': RequestStatus.Completed
}
})
+2 -3
View File
@@ -42,9 +42,8 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverGmail.trigger.OnMessageCreate,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx.objectClass': gmail.class.Message,
'tx._class': core.class.TxCreateDoc
_class: core.class.TxCreateDoc,
objectClass: gmail.class.Message
}
})
+8 -12
View File
@@ -44,27 +44,24 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverHr.trigger.OnRequestCreate,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx.objectClass': hr.class.Request,
'tx._class': core.class.TxCreateDoc
_class: core.class.TxCreateDoc,
objectClass: hr.class.Request
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverHr.trigger.OnRequestUpdate,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx.objectClass': hr.class.Request,
'tx._class': core.class.TxUpdateDoc
_class: core.class.TxUpdateDoc,
objectClass: hr.class.Request
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverHr.trigger.OnRequestRemove,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx.objectClass': hr.class.Request,
'tx._class': core.class.TxRemoveDoc
_class: core.class.TxRemoveDoc,
objectClass: hr.class.Request
}
})
@@ -88,9 +85,8 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverHr.trigger.OnPublicHolidayCreate,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx.objectClass': hr.class.PublicHoliday,
'tx._class': core.class.TxCreateDoc
_class: core.class.TxCreateDoc,
objectClass: hr.class.PublicHoliday
}
})
+4 -1
View File
@@ -89,7 +89,10 @@ export function createModel (builder: Builder): void {
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverNotification.trigger.OnDocRemove
trigger: serverNotification.trigger.OnDocRemove,
txMatch: {
_class: core.class.TxRemoveDoc
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
+4 -1
View File
@@ -25,7 +25,10 @@ export { serverRequestId } from '@hcengineering/server-request'
export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverRequest.trigger.OnRequest
trigger: serverRequest.trigger.OnRequest,
txMatch: {
objectClass: request.class.Request
}
})
builder.mixin(request.class.Request, core.class.Class, serverNotification.mixin.TextPresenter, {
+2 -5
View File
@@ -68,11 +68,8 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverSetting.trigger.OnRoleNameUpdate,
txMatch: {
_class: core.class.TxCollectionCUD,
objectSpace: core.space.Model,
collection: 'roles',
'tx._class': core.class.TxUpdateDoc,
'tx.objectClass': core.class.Role
_class: core.class.TxUpdateDoc,
objectClass: core.class.Role
}
})
}
+5 -1
View File
@@ -23,7 +23,11 @@ export { serverTagsId } from '@hcengineering/server-tags'
export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTags.trigger.onTagReference
trigger: serverTags.trigger.onTagReference,
txMatch: {
_class: { $in: [core.class.TxCreateDoc, core.class.TxRemoveDoc] },
objectClass: tags.class.TagReference
}
})
builder.mixin<Class<Doc>, ObjectDDParticipant>(
+5 -1
View File
@@ -15,6 +15,7 @@
import core from '@hcengineering/core/src/component'
import { type Builder } from '@hcengineering/model'
import task from '@hcengineering/task'
import serverCore from '@hcengineering/server-core'
import serverTask from '@hcengineering/server-task'
@@ -22,6 +23,9 @@ export { serverTaskId } from '@hcengineering/server-task'
export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTask.trigger.OnStateUpdate
trigger: serverTask.trigger.OnStateUpdate,
txMatch: {
objectClass: task.class.Task
}
})
}
+2 -3
View File
@@ -45,9 +45,8 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTelegram.trigger.OnMessageCreate,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx.objectClass': telegram.class.Message,
'tx._class': core.class.TxCreateDoc
_class: core.class.TxCreateDoc,
objectClass: telegram.class.Message
}
})
+10 -15
View File
@@ -44,27 +44,24 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTime.trigger.OnToDoUpdate,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx.objectClass': time.class.ToDo,
'tx._class': core.class.TxUpdateDoc
_class: core.class.TxUpdateDoc,
objectClass: time.class.ToDo
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTime.trigger.OnToDoRemove,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx.objectClass': time.class.ToDo,
'tx._class': core.class.TxRemoveDoc
_class: core.class.TxRemoveDoc,
objectClass: time.class.ToDo
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTime.trigger.OnToDoCreate,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx.objectClass': time.class.ToDo,
'tx._class': core.class.TxCreateDoc
_class: core.class.TxCreateDoc,
objectClass: time.class.ToDo
},
isAsync: true
})
@@ -72,18 +69,16 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTime.trigger.OnWorkSlotCreate,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx.objectClass': time.class.WorkSlot,
'tx._class': core.class.TxCreateDoc
_class: core.class.TxCreateDoc,
objectClass: time.class.WorkSlot
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTime.trigger.OnWorkSlotUpdate,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx.objectClass': time.class.WorkSlot,
'tx._class': core.class.TxUpdateDoc
_class: core.class.TxUpdateDoc,
objectClass: time.class.WorkSlot
}
})
+2 -1
View File
@@ -38,6 +38,7 @@
"@hcengineering/platform": "^0.6.11",
"@hcengineering/survey": "^0.6.0",
"@hcengineering/survey-resources": "^0.6.0",
"@hcengineering/ui": "^0.6.15"
"@hcengineering/ui": "^0.6.15",
"@hcengineering/view": "^0.6.13"
}
}
+50 -4
View File
@@ -18,7 +18,7 @@ import { AccountRole } from '@hcengineering/core'
import { type Builder } from '@hcengineering/model'
import core from '@hcengineering/model-core'
import chunter from '@hcengineering/model-chunter'
import view, { type Viewlet } from '@hcengineering/model-view'
import view, { createAction, type Viewlet } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import { surveyId } from '@hcengineering/survey'
import { TPoll, TSurvey } from './types'
@@ -79,6 +79,14 @@ export function createModel (builder: Builder): void {
survey.viewlet.TableSurvey
)
builder.mixin(survey.class.Survey, core.class.Class, view.mixin.ObjectTitle, {
titleProvider: survey.function.SurveyTitleProvider
})
builder.mixin(survey.class.Survey, core.class.Class, view.mixin.LinkProvider, {
encode: survey.function.GetSurveyLink
})
builder.mixin(survey.class.Survey, core.class.Class, view.mixin.ObjectPanel, {
component: survey.component.EditSurveyPanel
})
@@ -91,7 +99,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: survey.class.Survey,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc<Viewlet>(
@@ -100,20 +108,58 @@ export function createModel (builder: Builder): void {
{
attachTo: survey.class.Poll,
descriptor: view.viewlet.Table,
config: ['', 'modifiedOn'],
config: ['', 'isCompleted', 'modifiedOn'],
configOptions: {
hiddenKeys: ['name', 'survey', 'results'],
hiddenKeys: ['name', 'survey', 'questions'],
sortable: true
}
},
survey.viewlet.TablePoll
)
builder.mixin(survey.class.Poll, core.class.Class, view.mixin.ObjectTitle, {
titleProvider: survey.function.PollTitleProvider
})
builder.mixin(survey.class.Poll, core.class.Class, view.mixin.LinkProvider, {
encode: survey.function.GetPollLink
})
builder.mixin(survey.class.Poll, core.class.Class, view.mixin.ObjectPanel, {
component: survey.component.EditPollPanel
})
builder.mixin(survey.class.Poll, core.class.Class, view.mixin.ObjectPresenter, {
presenter: survey.component.PollPresenter
})
builder.mixin(survey.class.Poll, core.class.Class, activity.mixin.ActivityDoc, {})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: survey.class.Poll,
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.mixin(survey.class.Poll, core.class.Class, view.mixin.CollectionEditor, {
editor: survey.component.PollCollection
})
createAction(
builder,
{
action: survey.actionImpl.DeletePoll,
label: workbench.string.Delete,
icon: view.icon.Delete,
input: 'any',
category: survey.category.Survey,
target: survey.class.Poll,
context: {
mode: ['context', 'browser'],
group: 'remove'
},
visibilityTester: view.function.CanDeleteObject,
override: [view.action.Delete]
},
survey.action.DeletePoll
)
}
+1 -3
View File
@@ -13,8 +13,6 @@
// limitations under the License.
//
import { mergeIds } from '@hcengineering/platform'
import { surveyId } from '@hcengineering/survey'
import survey from '@hcengineering/survey-resources/src/plugin'
export default mergeIds(surveyId, survey, {})
export default survey
+17 -3
View File
@@ -14,7 +14,18 @@
//
import { IndexKind, type Domain, type Ref } from '@hcengineering/core'
import { ArrOf, Hidden, Index, Model, Prop, TypeRecord, TypeRef, TypeString, UX } from '@hcengineering/model'
import {
ArrOf,
Hidden,
Index,
Model,
Prop,
TypeBoolean,
TypeRecord,
TypeRef,
TypeString,
UX
} from '@hcengineering/model'
import core, { TAttachedDoc, TDoc } from '@hcengineering/model-core'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { type Poll, type Question, type Survey } from '@hcengineering/survey'
@@ -48,7 +59,10 @@ export class TPoll extends TAttachedDoc implements Poll {
@Prop(TypeString(), survey.string.Prompt)
prompt!: string
@Prop(ArrOf(TypeRecord()), getEmbeddedLabel('Answers'))
@Prop(ArrOf(TypeRecord()), getEmbeddedLabel('Questions'))
@Hidden()
results?: { question: string, answer: string[] }[]
questions?: Question[]
@Prop(TypeBoolean(), survey.string.Completed)
isCompleted?: boolean
}
+32 -60
View File
@@ -15,6 +15,7 @@
import activity, { type DocUpdateMessage } from '@hcengineering/activity'
import {
DOMAIN_MODEL_TX,
DOMAIN_STATUS,
DOMAIN_TX,
TxOperations,
@@ -25,7 +26,6 @@ import {
type Ref,
type Space,
type Status,
type TxCollectionCUD,
type TxCreateDoc,
type TxUpdateDoc
} from '@hcengineering/core'
@@ -95,7 +95,7 @@ export async function migrateDefaultStatusesBase<T extends Task> (
// 3. More than one type (one system and one custom) - the tool is running after the WS upgrade.
// Not supported for now. Alternatively - Proceed with (2) scenario for the custom one. Delete it in the end.
const defaultTypes = await client.find<TxCreateDoc<ProjectType>>(DOMAIN_TX, {
const defaultTypes = await client.find<TxCreateDoc<ProjectType>>(DOMAIN_MODEL_TX, {
_class: core.class.TxCreateDoc,
objectId: defaultTypeId,
objectSpace: core.space.Model,
@@ -121,7 +121,7 @@ export async function migrateDefaultStatusesBase<T extends Task> (
// and not modified by user
if (defaultType.attributes.tasks.length === 1 && defaultType.attributes.tasks[0] === defaultTaskTypeId) {
const defaultTaskType = (
await client.find<TxCreateDoc<TaskType>>(DOMAIN_TX, {
await client.find<TxCreateDoc<TaskType>>(DOMAIN_MODEL_TX, {
_class: core.class.TxCreateDoc,
objectId: defaultTaskTypeId,
objectSpace: core.space.Model,
@@ -133,7 +133,7 @@ export async function migrateDefaultStatusesBase<T extends Task> (
logger.log('Moving the existing default type created by ConfigUser to a system one', '')
logger.log('Moving the existing default task type created by ConfigUser to a system one', '')
await client.update(
DOMAIN_TX,
DOMAIN_MODEL_TX,
{ _id: defaultTaskType._id },
{
$set: {
@@ -143,7 +143,7 @@ export async function migrateDefaultStatusesBase<T extends Task> (
)
await client.update(
DOMAIN_TX,
DOMAIN_MODEL_TX,
{ _id: defaultType._id },
{
$set: {
@@ -173,7 +173,7 @@ export async function migrateDefaultStatusesBase<T extends Task> (
logger.log('Moving the existing default type to a custom one', '')
const newId = defaultType.objectId + '-custom'
await client.update(
DOMAIN_TX,
DOMAIN_MODEL_TX,
{ _id: defaultType._id },
{
$set: {
@@ -183,7 +183,7 @@ export async function migrateDefaultStatusesBase<T extends Task> (
}
)
await client.update(
DOMAIN_TX,
DOMAIN_MODEL_TX,
{
objectId: defaultType.objectId,
objectSpace: core.space.Model
@@ -195,7 +195,7 @@ export async function migrateDefaultStatusesBase<T extends Task> (
}
)
await client.update(
DOMAIN_TX,
DOMAIN_MODEL_TX,
{
objectId: { $in: defaultType.attributes.tasks },
objectSpace: core.space.Model,
@@ -295,7 +295,7 @@ export async function migrateDefaultStatusesBase<T extends Task> (
// 1. Update all update TXes with statuses
// 2. Update all push TXes with statuses
const projectTypeStatusesCreates = await client.find<TxCreateDoc<ProjectType>>(DOMAIN_TX, {
const projectTypeStatusesCreates = await client.find<TxCreateDoc<ProjectType>>(DOMAIN_MODEL_TX, {
_class: core.class.TxCreateDoc,
objectClass: task.class.ProjectType,
objectSpace: core.space.Model,
@@ -313,11 +313,11 @@ export async function migrateDefaultStatusesBase<T extends Task> (
}
counter++
await client.update(DOMAIN_TX, { _id: ptsCreate._id }, { $set: { 'attributes.statuses': newUpdateStatuses } })
await client.update(DOMAIN_MODEL_TX, { _id: ptsCreate._id }, { $set: { 'attributes.statuses': newUpdateStatuses } })
}
logger.log('projectTypeStatusesCreates updated: ', counter)
const projectTypeStatusesUpdates = await client.find<TxUpdateDoc<ProjectType>>(DOMAIN_TX, {
const projectTypeStatusesUpdates = await client.find<TxUpdateDoc<ProjectType>>(DOMAIN_MODEL_TX, {
_class: core.class.TxUpdateDoc,
objectId: { $in: projectTypeStatusesCreates.map((sc) => sc.objectId) },
objectClass: task.class.ProjectType,
@@ -335,11 +335,11 @@ export async function migrateDefaultStatusesBase<T extends Task> (
}
counter++
await client.update(DOMAIN_TX, { _id: ptsUpdate._id }, { $set: { 'operations.statuses': newUpdateStatuses } })
await client.update(DOMAIN_MODEL_TX, { _id: ptsUpdate._id }, { $set: { 'operations.statuses': newUpdateStatuses } })
}
logger.log('projectTypeStatusesUpdates updated: ', counter)
const projectTypeStatusesPushes = await client.find<TxUpdateDoc<ProjectType>>(DOMAIN_TX, {
const projectTypeStatusesPushes = await client.find<TxUpdateDoc<ProjectType>>(DOMAIN_MODEL_TX, {
_class: core.class.TxUpdateDoc,
objectId: { $in: projectTypeStatusesCreates.map((sc) => sc.objectId) },
objectClass: task.class.ProjectType,
@@ -363,7 +363,11 @@ export async function migrateDefaultStatusesBase<T extends Task> (
}
counter++
await client.update(DOMAIN_TX, { _id: ptsUpdate._id }, { $set: { 'operations.$push.statuses': newPushStatus } })
await client.update(
DOMAIN_MODEL_TX,
{ _id: ptsUpdate._id },
{ $set: { 'operations.$push.statuses': newPushStatus } }
)
}
logger.log('projectTypeStatusesPushes updated: ', counter)
@@ -371,7 +375,7 @@ export async function migrateDefaultStatusesBase<T extends Task> (
// 1. Update create TX
// 2. Update all update TXes with statuses
const allTaskTypes = await client.find<TxCreateDoc<TaskType>>(DOMAIN_TX, {
const allTaskTypes = await client.find<TxCreateDoc<TaskType>>(DOMAIN_MODEL_TX, {
_class: core.class.TxCreateDoc,
objectClass: taskTypeClass,
'attributes.ofClass': { $in: baseTaskClasses }
@@ -388,11 +392,15 @@ export async function migrateDefaultStatusesBase<T extends Task> (
}
counter++
await client.update(DOMAIN_TX, { _id: taskType._id }, { $set: { 'attributes.statuses': newTaskTypeStatuses } })
await client.update(
DOMAIN_MODEL_TX,
{ _id: taskType._id },
{ $set: { 'attributes.statuses': newTaskTypeStatuses } }
)
}
logger.log('allTaskTypes updated: ', counter)
const allTaskTypeStatusesUpdates = await client.find<TxUpdateDoc<TaskType>>(DOMAIN_TX, {
const allTaskTypeStatusesUpdates = await client.find<TxUpdateDoc<TaskType>>(DOMAIN_MODEL_TX, {
_class: core.class.TxUpdateDoc,
objectClass: taskTypeClass,
objectId: { $in: allTaskTypes.map((tt) => tt.objectId) },
@@ -414,7 +422,7 @@ export async function migrateDefaultStatusesBase<T extends Task> (
counter++
await client.update(
DOMAIN_TX,
DOMAIN_MODEL_TX,
{ _id: ttsUpdate._id },
{ $set: { 'operations.statuses': newTaskTypeUpdateStatuses } }
)
@@ -447,48 +455,6 @@ export async function migrateDefaultStatusesBase<T extends Task> (
}
logger.log('affectedBaseTasks updated: ', counter)
const baseTaskCreateTxes = await client.find<TxCollectionCUD<T, T>>(DOMAIN_TX, {
_class: core.class.TxCollectionCUD,
'tx._class': core.class.TxCreateDoc,
'tx.objectClass': { $in: baseTaskClasses },
'tx.attributes.status': { $in: statusIdsBeingMigrated }
})
logger.log('Base task create TXes: ', baseTaskCreateTxes.length)
counter = 0
for (const baseTaskCreateTx of baseTaskCreateTxes) {
const tx = baseTaskCreateTx.tx as TxCreateDoc<T>
const newStatus = getNewStatus(tx.attributes.status)
if (newStatus !== tx.attributes.status) {
counter++
await client.update(DOMAIN_TX, { _id: baseTaskCreateTx._id }, { $set: { 'tx.attributes.status': newStatus } })
}
}
logger.log('Base task create TXes updated: ', counter)
const baseTaskUpdateTxes = await client.find<TxCollectionCUD<T, T>>(DOMAIN_TX, {
_class: core.class.TxCollectionCUD,
'tx._class': core.class.TxUpdateDoc,
'tx.objectClass': { $in: baseTaskClasses },
'tx.operations.status': { $in: statusIdsBeingMigrated }
})
logger.log('Base task update TXes: ', baseTaskUpdateTxes.length)
counter = 0
for (const baseTaskUpdateTx of baseTaskUpdateTxes) {
const tx = baseTaskUpdateTx.tx as TxUpdateDoc<T>
const newStatus = tx.operations.status !== undefined ? getNewStatus(tx.operations.status) : undefined
if (newStatus !== tx.operations.status) {
counter++
await client.update(DOMAIN_TX, { _id: baseTaskUpdateTx._id }, { $set: { 'tx.operations.status': newStatus } })
}
}
logger.log('Base task update TXes updated: ', counter)
const baseTaskUpdateMessages = await client.find<DocUpdateMessage>(DOMAIN_ACTIVITY, {
_class: activity.class.DocUpdateMessage,
action: 'update',
@@ -588,6 +554,12 @@ export const taskOperation: MigrateOperation = {
{ objectId: { $in: missing }, objectSpace: 'task:space:Statuses' },
{ $set: { objectSpace: core.space.Model } }
)
await client.update(
DOMAIN_MODEL_TX,
{ objectId: { $in: missing }, objectSpace: 'task:space:Statuses' },
{ $set: { objectSpace: core.space.Model } }
)
await client.move(DOMAIN_TX, { objectId: { $in: missing }, objectSpace: core.space.Model }, DOMAIN_MODEL_TX)
}
}
},
+5 -5
View File
@@ -119,7 +119,7 @@ function defineApplication (builder: Builder): void {
}
]
},
navHeaderComponent: testManagement.component.NewTestCaseHeader
navHeaderComponent: testManagement.component.TestManagementSpaceHeader
},
testManagement.app.TestManagement
)
@@ -143,7 +143,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: testManagement.class.TestProject,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
defineTestSuite(builder)
@@ -218,7 +218,7 @@ function defineTestSuite (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: testManagement.class.TestSuite,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.mixin(testManagement.class.TestSuite, core.class.Class, view.mixin.ObjectEditor, {
@@ -283,7 +283,7 @@ function defineTestCase (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: testManagement.class.TestCase,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.mixin(testManagement.class.TestCase, core.class.Class, view.mixin.ObjectEditor, {
@@ -388,7 +388,7 @@ function defineTestRun (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: testManagement.class.TestRun,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.mixin(testManagement.class.TestRun, core.class.Class, view.mixin.ObjectEditor, {
+4 -4
View File
@@ -461,22 +461,22 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: tracker.class.Issue,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: tracker.class.Milestone,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: tracker.class.Component,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: tracker.class.IssueTemplate,
components: { input: chunter.component.ChatMessageInput }
components: { input: { component: chunter.component.ChatMessageInput } }
})
defineViewlets(builder)
+17 -17
View File
@@ -13,37 +13,37 @@
// limitations under the License.
//
import activity, { type DocUpdateMessage } from '@hcengineering/activity'
import core, {
AccountRole,
DOMAIN_MODEL_TX,
DOMAIN_STATUS,
type Ref,
type Status,
type TxCreateDoc,
TxOperations,
generateId,
toIdMap,
DOMAIN_TX,
type Status,
type Ref,
AccountRole
toIdMap
} from '@hcengineering/core'
import {
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient,
type ModelLogger,
createOrUpdate,
tryMigrate,
tryUpgrade,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient
tryUpgrade
} from '@hcengineering/model'
import { DOMAIN_SPACE } from '@hcengineering/model-core'
import activity, { type DocUpdateMessage } from '@hcengineering/activity'
import { DOMAIN_ACTIVITY } from '@hcengineering/model-activity'
import { DOMAIN_SPACE } from '@hcengineering/model-core'
import { DOMAIN_TASK, migrateDefaultStatusesBase } from '@hcengineering/model-task'
import tags from '@hcengineering/tags'
import task from '@hcengineering/task'
import { type IssueStatus, TimeReportDayType, trackerId, type Issue, type Project } from '@hcengineering/tracker'
import { type Issue, type IssueStatus, type Project, TimeReportDayType, trackerId } from '@hcengineering/tracker'
import tracker from './plugin'
import contact from '@hcengineering/model-contact'
import { classicIssueTaskStatuses } from '.'
import tracker from './plugin'
async function createDefaultProject (tx: TxOperations): Promise<void> {
const current = await tx.findOne(tracker.class.Project, {
@@ -280,7 +280,7 @@ async function migrateStatusesToModel (client: MigrationClient): Promise<void> {
modifiedBy
}
await client.create(DOMAIN_TX, tx)
await client.create(DOMAIN_MODEL_TX, tx)
}
}
@@ -291,7 +291,7 @@ async function migrateDefaultTypeMixins (client: MigrationClient): Promise<void>
const newTaskTypeMixin = tracker.mixin.IssueTypeData
await client.update(
DOMAIN_TX,
DOMAIN_MODEL_TX,
{
objectClass: core.class.Attribute,
'attributes.attributeOf': oldSpaceTypeMixin
@@ -350,7 +350,7 @@ async function migrateDefaultProjectOwners (client: MigrationClient): Promise<vo
async function migrateIssueStatuses (client: MigrationClient): Promise<void> {
await client.update(
DOMAIN_TX,
DOMAIN_MODEL_TX,
{
objectClass: task.class.TaskType,
'attributes.ofClass': tracker.class.Issue,
@@ -363,7 +363,7 @@ async function migrateIssueStatuses (client: MigrationClient): Promise<void> {
}
)
await client.update(
DOMAIN_TX,
DOMAIN_MODEL_TX,
{
objectClass: core.class.Status,
'attributes.ofAttribute': tracker.attribute.IssueStatus
+7
View File
@@ -67,6 +67,7 @@ import {
type ListItemPresenter,
type ObjectEditor,
type ObjectEditorFooter,
type ObjectPanelFooter,
type ObjectEditorHeader,
type ObjectFactory,
type ObjectPanel,
@@ -216,6 +217,11 @@ export class TObjectEditorFooter extends TClass implements ObjectEditorFooter {
editor!: AnyComponent
}
@Mixin(view.mixin.ObjectPanelFooter, core.class.Class)
export class TObjectPanelFooter extends TClass implements ObjectPanelFooter {
editor!: AnyComponent
}
@Mixin(view.mixin.SpaceHeader, core.class.Class)
export class TSpaceHeader extends TClass implements SpaceHeader {
header!: AnyComponent
@@ -445,6 +451,7 @@ export function createModel (builder: Builder): void {
TObjectTitle,
TObjectEditorHeader,
TObjectEditorFooter,
TObjectPanelFooter,
TSpaceHeader,
TSpaceName,
TSpacePresenter,
+1 -5
View File
@@ -7,8 +7,6 @@ import { basename, dirname, join } from 'path'
import winston from 'winston'
import DailyRotateFile from 'winston-daily-rotate-file'
const PLATFORM_OPERATION_LOGGING = process.env.PLATFORM_OPERATION_LOGGING === 'true'
export class SplitLogger implements MeasureLogger {
logger: winston.Logger
@@ -110,9 +108,7 @@ export class SplitLogger implements MeasureLogger {
}
logOperation (operation: string, time: number, params: ParamsType): void {
if (PLATFORM_OPERATION_LOGGING) {
this.logger.info({ operation, time, ...params })
}
this.logger.info(operation, { time, ...params })
}
childLogger (name: string, params: Record<string, string>): MeasureLogger {
-7
View File
@@ -189,13 +189,6 @@ export function genMinModel (): TxCUD<Doc>[] {
kind: ClassifierKind.CLASS
})
)
txes.push(
createClass(core.class.TxCollectionCUD, {
label: 'TxCollectionCUD' as IntlString,
extends: core.class.TxCUD,
kind: ClassifierKind.CLASS
})
)
txes.push(
createClass(core.class.Blob, {
+5
View File
@@ -326,6 +326,11 @@ export interface TypeAny<AnyComponent = any> extends Type<any> {
*/
export const DOMAIN_MODEL = 'model' as Domain
/**
* @public
*/
export const DOMAIN_MODEL_TX = 'model_tx' as Domain
/**
* @public
*/
+4 -5
View File
@@ -15,14 +15,14 @@
import { Analytics } from '@hcengineering/analytics'
import { BackupClient, DocChunk } from './backup'
import { Account, AttachedDoc, Class, DOMAIN_MODEL, Doc, Domain, Ref, Timestamp } from './classes'
import { Account, Class, DOMAIN_MODEL, Doc, Domain, Ref, Timestamp } from './classes'
import core from './component'
import { Hierarchy } from './hierarchy'
import { MeasureContext, MeasureMetricsContext } from './measurements'
import { ModelDb } from './memdb'
import type { DocumentQuery, FindOptions, FindResult, FulltextStorage, Storage, TxResult, WithLookup } from './storage'
import { SearchOptions, SearchQuery, SearchResult, SortingOrder } from './storage'
import { Tx, TxCUD, TxCollectionCUD } from './tx'
import { Tx, TxCUD } from './tx'
import { toFindResult } from './utils'
const transactionThreshold = 500
@@ -307,9 +307,8 @@ export async function createClient (
// if we have attachment document create/delete we need to full refresh, since some derived data could be missing
for (const tx of atxes) {
if (
tx._class === core.class.TxCollectionCUD &&
((tx as TxCollectionCUD<Doc, AttachedDoc>).tx._class === core.class.TxCreateDoc ||
(tx as TxCollectionCUD<Doc, AttachedDoc>).tx._class === core.class.TxRemoveDoc)
(tx as TxCUD<Doc>).attachedTo !== undefined &&
(tx._class === core.class.TxCreateDoc || tx._class === core.class.TxRemoveDoc)
) {
needFullRefresh = true
break
-2
View File
@@ -60,7 +60,6 @@ import type {
Tx,
TxApplyIf,
TxCUD,
TxCollectionCUD,
TxCreateDoc,
TxMixin,
TxModelUpgrade,
@@ -97,7 +96,6 @@ export default plugin(coreId, {
TxApplyIf: '' as Ref<Class<TxApplyIf>>,
TxCUD: '' as Ref<Class<TxCUD<Doc>>>,
TxCreateDoc: '' as Ref<Class<TxCreateDoc<Doc>>>,
TxCollectionCUD: '' as Ref<Class<TxCollectionCUD<Doc, AttachedDoc>>>,
TxMixin: '' as Ref<Class<TxMixin<Doc, Doc>>>,
TxUpdateDoc: '' as Ref<Class<TxUpdateDoc<Doc>>>,
TxRemoveDoc: '' as Ref<Class<TxRemoveDoc<Doc>>>,
+2 -17
View File
@@ -64,22 +64,7 @@ export class MeasureMetricsContext implements MeasureContext {
st = Date.now()
contextData: object = {}
private done (value?: number, override?: boolean): void {
updateMeasure(
this.metrics,
this.st,
this.params,
this.fullParams,
(spend) => {
this.logger.logOperation(this.name, spend, {
...this.params,
...(typeof this.fullParams === 'function' ? this.fullParams() : this.fullParams),
...this.fullParams,
...(this.logParams ?? {})
})
},
value,
override
)
updateMeasure(this.metrics, this.st, this.params, this.fullParams, (spend) => {}, value, override)
}
constructor (
@@ -144,7 +129,7 @@ export class MeasureMetricsContext implements MeasureContext {
let needFinally = true
try {
const value = op(c)
if (value != null && value instanceof Promise) {
if (value instanceof Promise) {
needFinally = false
return value.finally(() => {
c.end()
+2 -21
View File
@@ -15,12 +15,12 @@
import { PlatformError, Severity, Status } from '@hcengineering/platform'
import { Lookup, MeasureContext, ReverseLookups, getObjectValue } from '.'
import type { Account, AttachedDoc, Class, Doc, Ref } from './classes'
import type { Account, Class, Doc, Ref } from './classes'
import core from './component'
import { Hierarchy } from './hierarchy'
import { checkMixinKey, matchQuery, resultSort } from './query'
import type { DocumentQuery, FindOptions, FindResult, LookupData, Storage, TxResult, WithLookup } from './storage'
import type { Tx, TxCollectionCUD, TxCreateDoc, TxMixin, TxRemoveDoc, TxUpdateDoc } from './tx'
import type { Tx, TxCreateDoc, TxMixin, TxRemoveDoc, TxUpdateDoc } from './tx'
import { TxProcessor } from './tx'
import { toFindResult } from './utils'
@@ -363,25 +363,6 @@ export class ModelDb extends MemDb {
case core.class.TxCreateDoc:
this.addDoc(TxProcessor.createDoc2Doc(tx as TxCreateDoc<Doc>, clone))
break
case core.class.TxCollectionCUD: {
// We need update only create transactions to contain attached, attachedToClass.
const cud = tx as TxCollectionCUD<Doc, AttachedDoc<Doc>>
if (cud.tx._class === core.class.TxCreateDoc) {
const createTx = cud.tx as TxCreateDoc<AttachedDoc>
const d: TxCreateDoc<AttachedDoc> = {
...createTx,
attributes: {
...createTx.attributes,
attachedTo: cud.objectId,
attachedToClass: cud.objectClass,
collection: cud.collection
}
}
this.addDoc(TxProcessor.createDoc2Doc(d as TxCreateDoc<Doc>, clone))
}
this.addTxes(ctx, [cud.tx], clone)
break
}
case core.class.TxUpdateDoc: {
const cud = tx as TxUpdateDoc<Doc>
const doc = this.findObject(cud.objectId)
+3 -3
View File
@@ -123,7 +123,7 @@ export class TxOperations implements Omit<Client, 'notify'> {
modifiedBy
)
await this.tx(tx)
return tx.tx.objectId as unknown as Ref<P>
return tx.objectId as unknown as Ref<P>
}
async updateCollection<T extends Doc, P extends AttachedDoc>(
@@ -148,7 +148,7 @@ export class TxOperations implements Omit<Client, 'notify'> {
modifiedBy
)
await this.tx(tx)
return tx.objectId
return attachedTo
}
async removeCollection<T extends Doc, P extends AttachedDoc>(
@@ -171,7 +171,7 @@ export class TxOperations implements Omit<Client, 'notify'> {
modifiedBy
)
await this.tx(tx)
return tx.objectId
return attachedTo
}
updateDoc<T extends Doc>(
+21 -73
View File
@@ -88,6 +88,9 @@ export interface TxModelUpgrade extends Tx {}
export interface TxCUD<T extends Doc> extends Tx {
objectId: Ref<T>
objectClass: Ref<Class<T>>
attachedTo?: Ref<Doc>
attachedToClass?: Ref<Class<Doc>>
collection?: string
}
/**
@@ -97,17 +100,6 @@ export interface TxCreateDoc<T extends Doc> extends TxCUD<T> {
attributes: Data<T>
}
/**
*
* Will perform create/update/delete of attached documents.
*
* @public
*/
export interface TxCollectionCUD<T extends Doc, P extends AttachedDoc> extends TxCUD<T> {
collection: string
tx: TxCUD<P>
}
/**
* @public
*/
@@ -327,9 +319,6 @@ export abstract class TxProcessor implements WithTx {
case core.class.TxCreateDoc:
result.push(await this.txCreateDoc(tx as TxCreateDoc<Doc>))
break
case core.class.TxCollectionCUD:
result.push(await this.txCollectionCUD(tx as TxCollectionCUD<Doc, AttachedDoc>))
break
case core.class.TxUpdateDoc:
result.push(await this.txUpdateDoc(tx as TxUpdateDoc<Doc>))
break
@@ -348,9 +337,19 @@ export abstract class TxProcessor implements WithTx {
}
static createDoc2Doc<T extends Doc>(tx: TxCreateDoc<T>, doClone = true): T {
const attached =
tx.attachedTo !== undefined
? {
attachedTo: tx.attachedTo,
attachedToClass: tx.attachedToClass,
collection: tx.collection
}
: {}
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
...(doClone ? clone(tx.attributes) : tx.attributes),
...attached,
_id: tx.objectId,
_class: tx.objectClass,
space: tx.objectSpace,
@@ -400,22 +399,10 @@ export abstract class TxProcessor implements WithTx {
static buildDoc2Doc<D extends Doc>(txes: Tx[]): D | undefined {
let doc: Doc
let createTx = txes.find((tx) => tx._class === core.class.TxCreateDoc)
if (createTx === undefined) {
const collectionTxes = txes.filter((tx) => tx._class === core.class.TxCollectionCUD) as Array<
TxCollectionCUD<Doc, AttachedDoc>
>
const collectionCreateTx = collectionTxes.find((p) => p.tx._class === core.class.TxCreateDoc)
if (collectionCreateTx === undefined) return
createTx = TxProcessor.extractTx(collectionCreateTx)
}
const createTx = txes.find((tx) => tx._class === core.class.TxCreateDoc)
if (createTx === undefined) return
const objectId = (createTx as TxCreateDoc<D>).objectId
doc = TxProcessor.createDoc2Doc(createTx as TxCreateDoc<Doc>)
for (let tx of txes) {
if ((tx as TxCUD<D>).objectId !== objectId && tx._class === core.class.TxCollectionCUD) {
tx = TxProcessor.extractTx(tx)
}
for (const tx of txes) {
if (tx._class === core.class.TxUpdateDoc) {
doc = TxProcessor.updateDoc2Doc(doc, tx as TxUpdateDoc<Doc>)
} else if (tx._class === core.class.TxMixin) {
@@ -431,27 +418,10 @@ export abstract class TxProcessor implements WithTx {
_class === core.class.TxCreateDoc ||
_class === core.class.TxUpdateDoc ||
_class === core.class.TxRemoveDoc ||
_class === core.class.TxCollectionCUD ||
_class === core.class.TxMixin
)
}
static extractTx (tx: Tx): Tx {
if (tx._class === core.class.TxCollectionCUD) {
const ctx = tx as TxCollectionCUD<Doc, AttachedDoc>
if (ctx.tx._class === core.class.TxCreateDoc) {
const create = ctx.tx as TxCreateDoc<AttachedDoc>
create.attributes.attachedTo = ctx.objectId
create.attributes.attachedToClass = ctx.objectClass
create.attributes.collection = ctx.collection
return create
}
return ctx.tx
}
return tx
}
static txHasUpdate<T extends Doc>(tx: TxUpdateDoc<T>, attribute: string): boolean {
const ops = tx.operations
if ((ops as any)[attribute] !== undefined) return true
@@ -472,24 +442,6 @@ export abstract class TxProcessor implements WithTx {
protected abstract txUpdateDoc (tx: TxUpdateDoc<Doc>): Promise<TxResult>
protected abstract txRemoveDoc (tx: TxRemoveDoc<Doc>): Promise<TxResult>
protected abstract txMixin (tx: TxMixin<Doc, Doc>): Promise<TxResult>
protected txCollectionCUD (tx: TxCollectionCUD<Doc, AttachedDoc>): Promise<TxResult> {
// We need update only create transactions to contain attached, attachedToClass.
if (tx.tx._class === core.class.TxCreateDoc) {
const createTx = tx.tx as TxCreateDoc<AttachedDoc>
const d: TxCreateDoc<AttachedDoc> = {
...createTx,
attributes: {
...createTx.attributes,
attachedTo: tx.objectId,
attachedToClass: tx.objectClass,
collection: tx.collection
}
}
return this.txCreateDoc(d)
}
return this.tx(tx.tx)
}
}
/**
@@ -534,18 +486,14 @@ export class TxFactory {
tx: TxCUD<P>,
modifiedOn?: Timestamp,
modifiedBy?: Ref<Account>
): TxCollectionCUD<T, P> {
): TxCUD<P> {
return {
_id: generateId(),
_class: core.class.TxCollectionCUD,
space: this.txSpace,
objectId,
objectClass: _class,
objectSpace: space,
modifiedOn: modifiedOn ?? Date.now(),
modifiedBy: modifiedBy ?? this.account,
...tx,
collection,
tx
attachedTo: objectId,
attachedToClass: _class,
modifiedOn: modifiedOn ?? Date.now(),
modifiedBy: modifiedBy ?? this.account
}
}
@@ -158,6 +158,11 @@
<svelte:fragment slot="pre-utils">
<slot name="pre-utils" />
</svelte:fragment>
<svelte:fragment slot="panel-footer">
<slot name="panel-footer" />
</svelte:fragment>
<svelte:fragment slot="utils">
{#if isUtils && $$slots.utils}
<slot name="utils" />
@@ -93,6 +93,11 @@
user-select: none;
box-shadow: var(--theme-popup-shadow);
@media screen and (max-width: 480px) {
width: 100%;
max-width: 100%;
}
.message {
margin-bottom: 1.75rem;
color: var(--theme-content-color);
+3 -3
View File
@@ -119,7 +119,7 @@ class UIClient extends TxOperations implements Client, OptimisticTxes {
this.pendingTxes.delete(t._id)
// Only CUD tx can be pending now
const innerTx = TxProcessor.extractTx(t) as TxCUD<Doc>
const innerTx = t as TxCUD<Doc>
if (innerTx._class === core.class.TxCreateDoc) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
@@ -206,7 +206,7 @@ class UIClient extends TxOperations implements Client, OptimisticTxes {
return
}
const innerTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
const innerTx = tx as TxCUD<Doc>
// Can pre-build some configuration later from the model if this will be too slow.
const instantTxes = this.getHierarchy().classHierarchyMixin(innerTx.objectClass, plugin.mixin.InstantTransactions)
if (instantTxes?.txClasses.includes(innerTx._class) !== true) {
@@ -626,7 +626,7 @@ export async function getAttributeEditor (
function filterKeys (hierarchy: Hierarchy, keys: KeyedAttribute[], ignoreKeys: string[]): KeyedAttribute[] {
const docKeys: Set<string> = new Set<string>(hierarchy.getAllAttributes(core.class.AttachedDoc).keys())
keys = keys.filter((k) => !docKeys.has(k.key))
keys = keys.filter((k) => !docKeys.has(k.key) || k.attr.editor !== undefined)
keys = keys.filter((k) => !ignoreKeys.includes(k.key))
return keys
}
-7
View File
@@ -188,13 +188,6 @@ export function genMinModel (): TxCUD<Doc>[] {
kind: ClassifierKind.CLASS
})
)
txes.push(
createClass(core.class.TxCollectionCUD, {
label: 'TxCollectionCUD' as IntlString,
extends: core.class.TxCUD,
kind: ClassifierKind.CLASS
})
)
txes.push(
createClass(core.class.TxMixin, {
label: 'TxMixin' as IntlString,
+26 -17
View File
@@ -677,24 +677,33 @@ describe('query', () => {
)
)
}
const pp = new Promise((resolve) => {
liveQuery.query<AttachedComment>(
test.class.TestComment,
{ _id: parentComment },
(result) => {
attempt++
const comment = result[0]
if (comment !== undefined) {
expect((comment.$lookup as any)?.comments).toHaveLength(childLength - attempt)
}
if (attempt === childLength) {
resolve(null)
}
},
{ lookup: { _id: { comments: test.class.TestComment } } }
)
let secondPromise: Promise<void> | undefined
const firstCallback = new Promise<void>((resolve) => {
secondPromise = new Promise<void>((_resolve) => {
liveQuery.query<AttachedComment>(
test.class.TestComment,
{ _id: parentComment },
(result) => {
attempt++
if (attempt === 0) {
resolve()
}
const comment = result[0]
if (comment !== undefined) {
expect((comment.$lookup as any)?.comments).toHaveLength(childLength - attempt)
}
if (attempt === childLength) {
_resolve()
}
},
{ lookup: { _id: { comments: test.class.TestComment } } }
)
})
})
await firstCallback
for (const child of childs) {
await factory.removeCollection(
test.class.TestComment,
@@ -705,7 +714,7 @@ describe('query', () => {
'comments'
)
}
await pp
await secondPromise
})
it('lookup query update doc', async () => {
-39
View File
@@ -15,7 +15,6 @@
import { Analytics } from '@hcengineering/analytics'
import core, {
AttachedDoc,
BulkUpdateEvent,
Class,
Client,
@@ -38,7 +37,6 @@ import core, {
Space,
Timestamp,
Tx,
TxCollectionCUD,
TxCreateDoc,
TxMixin,
TxProcessor,
@@ -672,41 +670,6 @@ export class LiveQuery implements WithTx, Client {
return {}
}
protected async txCollectionCUD (
tx: TxCollectionCUD<Doc, AttachedDoc>,
docCache: Map<string, Doc>
): Promise<TxResult> {
for (const queries of this.queries) {
const isTx = this.client.getHierarchy().isDerived(queries[0], core.class.Tx)
for (const q of queries[1]) {
if (isTx) {
// handle add since Txes are immutable
await this.handleDocAdd(q, tx, true, docCache)
continue
}
if (tx.tx._class === core.class.TxCreateDoc) {
const createTx = tx.tx as TxCreateDoc<AttachedDoc>
const d: TxCreateDoc<AttachedDoc> = {
...createTx,
attributes: {
...createTx.attributes,
attachedTo: tx.objectId,
attachedToClass: tx.objectClass,
collection: tx.collection
}
}
await this.handleDocAdd(q, TxProcessor.createDoc2Doc(d), true, docCache)
} else if (tx.tx._class === core.class.TxUpdateDoc) {
await this.handleDocUpdate(q, tx.tx as unknown as TxUpdateDoc<Doc>, docCache)
} else if (tx.tx._class === core.class.TxRemoveDoc) {
await this.handleDocRemove(q, tx.tx as unknown as TxRemoveDoc<Doc>)
}
}
}
return {}
}
async txUpdateDoc (tx: TxUpdateDoc<Doc>, docCache: Map<string, Doc>): Promise<TxResult> {
for (const queries of this.queries) {
const isTx = this.client.getHierarchy().isDerived(queries[0], core.class.Tx)
@@ -1278,8 +1241,6 @@ export class LiveQuery implements WithTx, Client {
switch (tx._class) {
case core.class.TxCreateDoc:
return await this.txCreateDoc(tx as TxCreateDoc<Doc>, docCache)
case core.class.TxCollectionCUD:
return await this.txCollectionCUD(tx as TxCollectionCUD<Doc, AttachedDoc>, docCache)
case core.class.TxUpdateDoc:
return await this.txUpdateDoc(tx as TxUpdateDoc<Doc>, docCache)
case core.class.TxRemoveDoc:
+1
View File
@@ -900,6 +900,7 @@ a.no-line {
.leading-3 { line-height: .75rem; }
.tracking--05px { letter-spacing: -.5px; }
.tracking-1px { letter-spacing: 1px; }
.text-balance { text-wrap: balance; }
.over-underline {
cursor: pointer;
+2
View File
@@ -31,6 +31,8 @@
--tag-on-accent-PorpoiseText: #FFFFFF;
--tag-accent-SunshineBackground: #FFBD2E;
--border-color-global-error-border-color: #fb6863;
}
/* Dark Theme */
+9 -1
View File
@@ -444,4 +444,12 @@
&.delete {
background: var(--text-editor-highlighted-node-delete-background-color);
}
}
}
// Hiding the selection of an empty line
.select-text p > br.ProseMirror-trailingBreak::selection {
background: transparent;
}
.select-text .text-editor-image-container {
user-select: all;
}
+1
View File
@@ -33,6 +33,7 @@
--spacing-4: 2rem;
--spacing-4_5: 2.25rem;
--spacing-5: 2.5rem;
--spacing-5_5: 2.75rem;
--spacing-6: 3rem;
--spacing-6_5: 3.5rem;
--spacing-7: 4rem;
+5
View File
@@ -543,6 +543,11 @@
.hulyModal-footer {
padding: var(--spacing-1_5);
}
@media screen and (max-width: 480px) {
width: 100vw;
height: var(--app-height);
min-width: 20rem;
}
}
&.type-aside,
&.type-popup {
+7
View File
@@ -50,6 +50,7 @@
}
&__title-wrap {
overflow-x: auto;
display: flex;
align-items: center;
min-width: 0;
@@ -277,6 +278,12 @@
max-width: 90vw;
max-height: 90vh;
@media screen and (max-width: 480px) {
width: 100%;
max-width: 100%;
max-height: 100%;
}
.antiCard-header {
padding: .75rem .75rem .375rem;
@@ -0,0 +1,123 @@
<script lang="ts">
//
// © 2023 Hardcore Engineering, Inc. All Rights Reserved.
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
//
import type { IntlString } from '@hcengineering/platform'
import { generateId } from '@hcengineering/core'
import { Label } from '../..'
export let id: string = generateId()
export let label: string | undefined = undefined
export let labelIntl: IntlString | undefined = undefined
export let labelParams: Record<string, any> | undefined = undefined
export let checked: boolean = false
export let indeterminate: boolean = false
export let disabled: boolean = false
export let error: boolean = false
export let required: boolean = false
</script>
<label class="checkbox-container" class:disabled>
<input type="checkbox" class="checkbox" {id} bind:checked {disabled} {indeterminate} {required} on:change />
<div class="checkbox-element" class:disabled class:error />
{#if label !== undefined || labelIntl !== undefined || $$slots.default !== undefined}
<div class="checkbox-label">
{#if labelIntl}<Label label={labelIntl} params={labelParams} />{:else}{label}{/if}
<slot />
</div>
{/if}
<slot name="after" />
</label>
<style lang="scss">
.checkbox-element {
position: relative;
flex-shrink: 0;
width: var(--spacing-2);
height: var(--spacing-2);
background-color: var(--selector-BackgroundColor);
border: 1px solid var(--selector-BorderColor);
border-radius: var(--extra-small-BorderRadius);
}
.checkbox-label {
color: var(--global-primary-TextColor);
user-select: none;
}
.checkbox {
overflow: hidden;
position: absolute;
margin: -1px;
padding: 0;
width: 1px;
height: 1px;
border: 0;
clip: rect(0 0 0 0);
&:checked + .checkbox-element,
&:indeterminate + .checkbox-element {
background-color: var(--selector-active-BackgroundColor);
border-color: var(--selector-active-BackgroundColor);
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0.625rem;
height: 0.5rem;
background-color: var(--selector-IconColor);
transform: translate(-50%, -50%);
}
}
&:checked + .checkbox-element::after {
clip-path: path(
'M9.7,0.5c0.4,0.4,0.4,1,0,1.4L4.1,7.8L0.3,4.2c-0.4-0.4-0.4-1,0-1.4c0.4-0.4,1-0.4,1.4,0L4,5l4.3-4.5C8.6,0.1,9.3,0.1,9.7,0.5z'
);
}
&:indeterminate + .checkbox-element::after {
clip-path: path('M0,4c0-0.6,0.4-1,1-1h8c0.6,0,1,0.4,1,1c0,0.6-0.4,1-1,1H1C0.4,5,0,4.6,0,4z');
}
&:disabled + .checkbox-element {
box-shadow: none;
background-color: var(--selector-disabled-BackgroundColor);
border-color: var(--selector-disabled-BorderColor);
& + .checkbox-label {
color: var(--global-disabled-TextColor);
}
}
& + .checkbox-element.error {
border-color: var(--border-color-global-error-border-color);
}
&:focus + .checkbox-element {
outline: 2px solid var(--global-focus-BorderColor);
outline-offset: 2px;
}
&:required + .checkbox-element + .checkbox-label::after {
content: '*';
position: relative;
top: -0.125rem;
left: 0.125rem;
color: var(--global-error-TextColor);
}
}
.checkbox-container {
display: inline-flex;
gap: var(--spacing-1_5);
min-width: var(--spacing-2);
min-height: var(--spacing-2);
&:not(.disabled) {
cursor: pointer;
&:active .checkbox-element {
outline: 2px solid var(--global-focus-BorderColor);
outline-offset: 2px;
}
&:hover .checkbox-element {
box-shadow: 0 0 0 4px var(--selector-hover-overlay-BackgroundColor);
}
}
}
</style>
@@ -0,0 +1,111 @@
<script lang="ts">
//
// © 2023 Hardcore Engineering, Inc. All Rights Reserved.
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
//
import type { IntlString } from '@hcengineering/platform'
import { generateId } from '@hcengineering/core'
import { Label } from '../..'
export let id: string = generateId()
export let group: any
export let value: any
export let label: string | undefined = undefined
export let labelIntl: IntlString | undefined = undefined
export let labelParams: Record<string, any> | undefined = undefined
export let checked: boolean = false
export let disabled: boolean = false
export let error: boolean = false
</script>
<label class="radioButton-container" class:disabled>
<input type="radio" class="radioButton" {id} bind:group {value} {checked} {disabled} on:change />
<div class="radioButton-element" class:disabled class:error />
{#if label !== undefined || labelIntl !== undefined || $$slots.default !== undefined}
<div class="radioButton-label">
{#if labelIntl}<Label label={labelIntl} params={labelParams} />{:else}{label}{/if}
<slot />
</div>
{/if}
<slot name="after" />
</label>
<style lang="scss">
.radioButton-element {
position: relative;
flex-shrink: 0;
width: var(--spacing-2);
height: var(--spacing-2);
background-color: var(--selector-BackgroundColor);
border: 1px solid var(--selector-BorderColor);
border-radius: var(--large-BorderRadius);
}
.radioButton-label {
color: var(--global-primary-TextColor);
user-select: none;
}
.radioButton {
overflow: hidden;
position: absolute;
margin: -1px;
padding: 0;
width: 1px;
height: 1px;
border: 0;
clip: rect(0 0 0 0);
&:checked + .radioButton-element {
background-color: var(--selector-active-BackgroundColor);
border-color: var(--selector-active-BackgroundColor);
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: var(--spacing-0_75);
height: var(--spacing-0_75);
background-color: var(--selector-IconColor);
border-radius: 50%;
transform: translate(-50%, -50%);
}
}
&:disabled + .radioButton-element {
box-shadow: none;
background-color: var(--selector-disabled-BackgroundColor);
border-color: var(--selector-disabled-BorderColor);
&::after {
background-color: var(--selector-disabled-IconColor);
}
& + .radioButton-label {
color: var(--global-disabled-TextColor);
}
}
& + .radioButton-element.error {
border-color: var(--border-color-global-error-border-color);
}
&:focus + .radioButton-element {
outline: 2px solid var(--global-focus-BorderColor);
outline-offset: 2px;
}
}
.radioButton-container {
display: inline-flex;
gap: var(--spacing-2);
min-width: var(--spacing-2);
min-height: var(--spacing-2);
&:not(.disabled) {
cursor: pointer;
&:active .radioButton-element {
outline: 2px solid var(--global-focus-BorderColor);
outline-offset: 2px;
}
&:hover .radioButton-element {
box-shadow: 0 0 0 4px var(--selector-hover-overlay-BackgroundColor);
}
}
}
</style>
+5
View File
@@ -309,6 +309,11 @@
</div>
{/if}
</div>
{#if $$slots['panel-footer']}
<div class="popupPanel-footer">
<slot name="panel-footer" />
</div>
{/if}
<div class="popupPanel-pageHeader only-print" id="page-header">
<slot name="page-header" />
</div>
@@ -76,6 +76,12 @@
$: document.body.style.cursor = drag ? 'all-scroll' : 'default'
$: docSize = checkAdaptiveMatching($deviceInfo.size, 'md')
$: isFullMobile =
$deviceInfo.isMobile &&
$deviceInfo.isPortrait &&
['right', 'top', 'float', 'full', 'content', 'middle', 'centered', 'center', 'full-centered'].some(
(el) => element === el
)
function _update (result: any): void {
if (onUpdate !== undefined) onUpdate(result)
@@ -100,7 +106,7 @@
contentPanel: HTMLElement | undefined
): void => {
const device: DeviceOptions = $deviceInfo
if ((fullSize || docSize) && (element === 'float' || element === 'centered')) {
if (((fullSize || docSize) && (element === 'float' || element === 'centered')) || isFullMobile) {
options = fitPopupElement(modalHTML, device, 'full', contentPanel, clientWidth, clientHeight)
options.props.maxHeight = '100vh'
if (!modalHTML.classList.contains('fullsize')) modalHTML.classList.add('fullsize')
@@ -48,6 +48,8 @@
export let disablePointerEventsOnScroll = false
export let onScroll: ((params: ScrollParams) => void) | undefined = undefined
export let onResize: (() => void) | undefined = undefined
export let containerName: string | undefined = undefined
export let containerType: 'size' | 'inline-size' | undefined = containerName !== undefined ? 'inline-size' : undefined
export function scroll (top: number, left?: number, behavior: 'auto' | 'smooth' = 'auto') {
if (divScroll) {
@@ -598,6 +600,8 @@
: 'row'}
style:height={contentDirection === 'vertical-reverse' ? 'max-content' : noStretch ? 'auto' : '100%'}
style:align-items={align}
style:container-name={containerName}
style:container-type={containerType}
class:disableEvents={isScrolling && disablePointerEventsOnScroll}
use:resizeObserver={() => {
checkAutoScroll()
+2
View File
@@ -158,6 +158,8 @@ export { default as HotkeyGroup } from './components/HotkeyGroup.svelte'
export { default as ModernWizardDialog } from './components/wizard/ModernWizardDialog.svelte'
export { default as ModernWizardBar } from './components/wizard/ModernWizardBar.svelte'
export { default as ModernTab } from './components/ModernTab.svelte'
export { default as ModernCheckbox } from './components/ModernCheckbox.svelte'
export { default as ModernRadioButton } from './components/ModernRadioButton.svelte'
export { default as IconAdd } from './components/icons/Add.svelte'
export { default as IconCircleAdd } from './components/icons/CircleAdd.svelte'
+2 -1
View File
@@ -372,7 +372,8 @@ export function fitPopupElement (
newProps.right = '1px'
show = true
} else if (element === 'full-centered') {
newProps.top = '20px'
const rect = contentPanel !== undefined ? contentPanel.getBoundingClientRect() : { top: 0 }
newProps.top = `${Math.max(20, rect.top + 1)}px`
newProps.bottom = '20px'
newProps.left = '20px'
newProps.right = '20px'
@@ -24,5 +24,11 @@
</script>
{#if extension}
<Component is={extension.components[kind]} {props} on:close on:open on:submit />
<Component
is={extension.components[kind].component}
props={{ ...extension.components[kind].props, ...props }}
on:close
on:open
on:submit
/>
{/if}
@@ -30,7 +30,7 @@
$: attrViewletConfig = viewlet?.config?.[attributeModel.key]
$: attributeIcon = attrViewletConfig?.icon ?? attributeModel.icon ?? IconEdit
$: isUnset = values.length > 0 && !values.some((value) => value !== null && value !== '')
$: isUnset = values.length > 0 && !values.some((value) => value != null && value !== '')
$: isTextType = getIsTextType(attributeModel)
@@ -42,7 +42,7 @@
</script>
{#if isUnset}
<div class="row overflow-label">
<div class="unset row overflow-label">
<span class="mr-1"><Icon icon={attributeIcon} size="small" /></span>
<Label label={activity.string.Unset} />
<span class="lower"><Label label={attributeModel.label} /></span>
@@ -89,6 +89,7 @@
display: flex;
align-items: center;
gap: 0.25rem;
color: var(--global-primary-TextColor);
}
.showMore {
+1 -1
View File
@@ -210,7 +210,7 @@ export type ActivityExtensionKind = 'input'
*/
export interface ActivityExtension extends Doc {
ofClass: Ref<Class<Doc>>
components: Record<ActivityExtensionKind, AnyComponent>
components: Record<ActivityExtensionKind, { component: AnyComponent, props?: Record<string, any> }>
}
/**
@@ -39,7 +39,7 @@
}
} else {
let targetState: Status | undefined = undefined
const txes = await client.findAll(core.class.Tx, { 'tx.objectId': value._id })
const txes = await client.findAll(core.class.Tx, { objectId: value._id })
while (txes.length) {
const doc = TxProcessor.buildDoc2Doc<Card>(txes)
if (doc === undefined) {
@@ -72,9 +72,6 @@
async function updateDataProvider (attachedTo: Ref<Doc>, selectedMessageId?: Ref<ActivityMessage>): Promise<void> {
if (dataProvider === undefined) {
// For now loading all messages for documents with activity. Need to correct handle aggregation with pagination.
// Perhaps we should load all activity messages once, and keep loading in chunks only for ChatMessages then merge them correctly with activity messages
const loadAll = isDocChannel
const ctx =
context ??
(await client.findOne(notification.class.DocNotifyContext, {
@@ -90,7 +87,7 @@
attachedTo,
activity.class.ActivityMessage,
selectedMessageId,
loadAll,
false,
hasRefs,
collection
)
@@ -112,7 +109,7 @@
provider={dataProvider}
{freeze}
{autofocus}
loadMoreAllowed={!isDocChannel}
loadMoreAllowed
{withInput}
{readonly}
{onReply}
@@ -21,7 +21,7 @@
Space,
Timestamp,
Tx,
TxCollectionCUD,
TxCUD,
TxProcessor
} from '@hcengineering/core'
import activity, { ActivityMessage } from '@hcengineering/activity'
@@ -546,11 +546,9 @@
}
const newMessageTxListener = (tx: Tx): void => {
if (tx._class !== core.class.TxCollectionCUD) return
const ctx = tx as TxCollectionCUD<Doc, ActivityMessage>
if (ctx.objectId !== doc._id) return
const etx = TxProcessor.extractTx(tx)
if (etx._class !== core.class.TxCreateDoc) return
const ctx = tx as TxCUD<ActivityMessage>
if (ctx.attachedTo !== doc._id) return
if (ctx._class !== core.class.TxCreateDoc) return
if (shouldScrollToNew) {
void wait().then(scrollToNewMessages)
}
@@ -70,6 +70,9 @@
$: locked = doc?.lockedBy != null
$: readonly = $restrictionStore.readonly || locked
let useMaxWidth = getUseMaxWidth()
$: saveUseMaxWidth(useMaxWidth)
export function canClose (): boolean {
return false
}
@@ -179,6 +182,15 @@
}
}
function getUseMaxWidth (): boolean {
const useMaxWidth = localStorage.getItem('document.useMaxWidth')
return useMaxWidth === 'true'
}
function saveUseMaxWidth (useMaxWidth: boolean): void {
localStorage.setItem('document.useMaxWidth', useMaxWidth.toString())
}
onMount(() => {
dispatch('open', { ignoreKeys: ['comments', 'name'] })
})
@@ -245,7 +257,7 @@
isHeader={false}
isCustomAttr={false}
isSub={false}
useMaxWidth={false}
bind:useMaxWidth
printHeader={false}
{embedded}
adaptive={'default'}
+7 -1
View File
@@ -68,9 +68,15 @@
"Transcription": "Transcription",
"StartWithTranscription": "Start with transcription",
"MeetingMinutes": "Meeting minutes",
"MeetingsMinutes": "Meetings minutes",
"StartMeeting": "Start meeting",
"Video": "Video",
"NoMeetingMinutes": "No meeting minutes",
"JoinMeeting": "Join meeting"
"JoinMeeting": "Join meeting",
"MeetingStart": "Meeting start",
"MeetingEnd": "Meeting end",
"Status": "Status",
"Active": "Active",
"Finished": "Finished"
}
}
+7 -1
View File
@@ -68,9 +68,15 @@
"Transcription": "Transcripción",
"StartWithTranscription": "Iniciar con transcripción",
"MeetingMinutes": "Minutos de la reunión",
"MeetingsMinutes": "Minutos de las reuniones",
"StartMeeting": "Iniciar reunión",
"Video": "Video",
"NoMeetingMinutes": "Sin minutos de reunión",
"JoinMeeting": "Unirse a la reunión"
"JoinMeeting": "Unirse a la reunión",
"MeetingStart": "Inicio de la reunión",
"MeetingEnd": "Fin de la reunión",
"Status": "Estado",
"Active": "Activo",
"Finished": "Terminado"
}
}
+7 -1
View File
@@ -68,9 +68,15 @@
"Transcription": "Transcription",
"StartWithTranscription": "Démarrer avec la transcription",
"MeetingMinutes": "Minutes de la réunion",
"MeetingsMinutes": "Minutes des réunions",
"StartMeeting": "Démarrer la réunion",
"Video": "Vidéo",
"NoMeetingMinutes": "Pas de minutes de réunion",
"JoinMeeting": "Rejoindre la réunion"
"JoinMeeting": "Rejoindre la réunion",
"MeetingStart": "Début de la réunion",
"MeetingEnd": "Fin de la réunion",
"Status": "Statut",
"Active": "Actif",
"Finished": "Terminé"
}
}
+7 -1
View File
@@ -68,9 +68,15 @@
"Transcription": "Trascrizione",
"StartWithTranscription": "Inizia con la trascrizione",
"MeetingMinutes": "Verbale della riunione",
"MeetingsMinutes": "Verbali delle riunioni",
"StartMeeting": "Inizia riunione",
"Video": "Video",
"NoMeetingMinutes": "Nessun verbale della riunione",
"JoinMeeting": "Unisciti alla riunione"
"JoinMeeting": "Unisciti alla riunione",
"MeetingStart": "Inizio riunione",
"MeetingEnd": "Fine riunione",
"Status": "Stato",
"Active": "Attivo",
"Finished": "Finito"
}
}
+7 -1
View File
@@ -68,9 +68,15 @@
"Transcription": "Transcrição",
"StartWithTranscription": "Começar com transcrição",
"MeetingMinutes": "Minutos da reunião",
"MeetingsMinutes": "Minutos das reuniões",
"StartMeeting": "Iniciar reunião",
"Video": "Vídeo",
"NoMeetingMinutes": "Sem minutos de reunião",
"JoinMeeting": "Participar na reunião"
"JoinMeeting": "Participar na reunião",
"MeetingStart": "Início da reunião",
"MeetingEnd": "Fim da reunião",
"Status": "Estado",
"Active": "Ativo",
"Finished": "Finalizado"
}
}
+8 -2
View File
@@ -67,10 +67,16 @@
"Meeting": "Встреча",
"Transcription": "Транскрипция",
"StartWithTranscription": "Начинать с транскрипцией",
"MeetingMinutes": "Результаты встреч",
"MeetingMinutes": "Протокол встречи",
"MeetingsMinutes": "Протоколы встреч",
"StartMeeting": "Начать встречу",
"Video": "Видео",
"NoMeetingMinutes": "Нет результатов встреч",
"JoinMeeting": "Присоединиться к встрече"
"JoinMeeting": "Присоединиться к встрече",
"MeetingStart": "Начало встречи",
"MeetingEnd": "Конец встречи",
"Status": "Статус",
"Active": "Активно",
"Finished": "Завершено"
}
}
+7 -1
View File
@@ -68,9 +68,15 @@
"Transcription": "转录",
"StartWithTranscription": "开始转录",
"MeetingMinutes": "会议记录",
"MeetingsMinutes": "会议记录",
"StartMeeting": "开始会议",
"Video": "视频",
"NoMeetingMinutes": "无会议记录",
"JoinMeeting": "加入会议"
"JoinMeeting": "加入会议",
"MeetingStart": "会议开始",
"MeetingEnd": "会议结束",
"Status": "状态",
"Active": "活动",
"Finished": "已完成"
}
}
@@ -1,90 +0,0 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { personByIdStore } from '@hcengineering/contact-resources'
import { Room as TypeRoom } from '@hcengineering/love'
import { getMetadata } from '@hcengineering/platform'
import { Label, Loading, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
import { onDestroy, onMount } from 'svelte'
import presentation, { getClient } from '@hcengineering/presentation'
import { EditDoc } from '@hcengineering/view-resources'
import { subscribeDoc } from '@hcengineering/notification-resources'
import love from '../plugin'
import { storePromise, currentRoom, infos, invites, myInfo, myRequests, meetingMinutesStore } from '../stores'
import { awaitConnect, isConnected, isCurrentInstanceConnected, isFullScreen, tryConnect } from '../utils'
import ControlBar from './ControlBar.svelte'
export let room: TypeRoom
let loading: boolean = false
let configured: boolean = false
onMount(async () => {
loading = true
const wsURL = getMetadata(love.metadata.WebSocketURL)
if (wsURL === undefined) {
return
}
configured = true
await $storePromise
if (
!$isConnected &&
!$isCurrentInstanceConnected &&
$myInfo?.sessionId === getMetadata(presentation.metadata.SessionId)
) {
const info = $infos.filter((p) => p.room === room._id)
await tryConnect($personByIdStore, $myInfo, room, info, $myRequests, $invites)
}
await awaitConnect()
loading = false
})
let replacedPanel: HTMLElement
$: $deviceInfo.replacedPanel = replacedPanel
onDestroy(() => ($deviceInfo.replacedPanel = undefined))
$: if ($meetingMinutesStore) {
void subscribeDoc(getClient(), $meetingMinutesStore._class, $meetingMinutesStore._id, 'add', $meetingMinutesStore)
}
</script>
<div class="antiPanel-component filledNav" bind:this={replacedPanel}>
<div class="hulyComponent">
{#if $isConnected && !$isCurrentInstanceConnected}
<div class="flex-center justify-center error h-full w-full clear-mins">
<Label label={love.string.AnotherWindowError} />
</div>
{:else if !configured}
<div class="flex-center justify-center error h-full w-full clear-mins">
<Label label={love.string.ServiceNotConfigured} />
</div>
{:else if loading || !$currentRoom || !$meetingMinutesStore}
<Loading />
{:else}
<EditDoc _id={$meetingMinutesStore._id} _class={$meetingMinutesStore._class} embedded selectedAside={false} />
{/if}
{#if $currentRoom}
<div class="flex-grow flex-shrink" />
<ControlBar room={$currentRoom} fullScreen={$isFullScreen} />
{/if}
</div>
</div>
@@ -29,7 +29,8 @@
type CompAndProps,
IconMoreV,
ButtonMenu,
DropdownIntlItem
DropdownIntlItem,
IconMaximize
} from '@hcengineering/ui'
import view, { Action } from '@hcengineering/view'
import { getActions } from '@hcengineering/view-resources'
@@ -60,8 +61,10 @@
import RoomAccessPopup from './RoomAccessPopup.svelte'
import RoomLanguageSelector from './RoomLanguageSelector.svelte'
import ControlBarContainer from './ControlBarContainer.svelte'
import RoomModal from './RoomModal.svelte'
export let room: Room
export let canMaximize: boolean = true
export let fullScreen: boolean = false
export let onFullScreen: (() => void) | undefined = undefined
@@ -159,6 +162,10 @@
await fn(room)
}
$: withVideo = $screenSharing || room.type === RoomType.Video
function maximize (): void {
showPopup(RoomModal, { room }, 'full-centered')
}
</script>
<ControlBarContainer bind:noLabel>
@@ -253,6 +260,20 @@
}}
/>
{/if}
{#if ($screenSharing || room.type === RoomType.Video) && $isConnected && canMaximize}
<ModernButton
icon={IconMaximize}
tooltip={{
label: love.string.FullscreenMode,
direction: 'top'
}}
kind={'secondary'}
iconSize="medium"
size={'large'}
on:click={maximize}
/>
{/if}
{#if $isConnected && moreItems.length > 0}
<ButtonMenu
items={moreItems}
@@ -16,6 +16,7 @@
import { getClient } from '@hcengineering/presentation'
import { EditBox } from '@hcengineering/ui'
import { MeetingMinutes } from '@hcengineering/love'
import { createEventDispatcher, onMount } from 'svelte'
import love from '../plugin'
@@ -23,10 +24,15 @@
export let readonly: boolean = false
const client = getClient()
const dispatch = createEventDispatcher()
async function changeTitle (): Promise<void> {
await client.diffUpdate(object, { title: object.title })
await client.update(object, { title: object.title })
}
onMount(() => {
dispatch('open', { ignoreKeys: ['title'] })
})
</script>
<div class="flex-row-stretch">
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import { getClient } from '@hcengineering/presentation'
import { EditBox, ModernButton, closePanel } from '@hcengineering/ui'
import { EditBox, ModernButton } from '@hcengineering/ui'
import { Room, isOffice } from '@hcengineering/love'
import { createEventDispatcher, onMount } from 'svelte'
import { personByIdStore } from '@hcengineering/contact-resources'
@@ -22,7 +22,7 @@
import love from '../plugin'
import { getRoomName, tryConnect } from '../utils'
import { infos, invites, myInfo, myRequests, selectedRoomPlace } from '../stores'
import { infos, invites, myInfo, myRequests, selectedRoomPlace, myOffice, currentRoom } from '../stores'
export let object: Room
export let readonly: boolean = false
@@ -59,8 +59,6 @@
)
connecting = false
selectedRoomPlace.set(undefined)
closePanel()
dispatch('close')
}
let connectLabel: IntlString = love.string.StartMeeting
@@ -83,7 +81,9 @@
focusIndex={1}
/>
</div>
<ModernButton label={connectLabel} size="large" kind={'primary'} on:click={connect} loading={connecting} />
{#if object._id !== $myOffice?._id && ($currentRoom?._id !== object._id || connecting)}
<ModernButton label={connectLabel} size="large" kind={'primary'} on:click={connect} loading={connecting} />
{/if}
</div>
</div>
@@ -14,12 +14,15 @@
-->
<script lang="ts">
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import { onDestroy, onMount } from 'svelte'
import presentation from '@hcengineering/presentation'
import { personByIdStore } from '@hcengineering/contact-resources'
import Hall from './Hall.svelte'
import { currentRoom } from '../stores'
import { isConnected } from '../utils'
import ActiveMeeting from './ActiveMeeting.svelte'
import { getMetadata } from '@hcengineering/platform'
import love from '../plugin'
import { tryConnect, isConnected, isCurrentInstanceConnected } from '../utils'
import { infos, invites, myInfo, myRequests, storePromise, currentRoom } from '../stores'
const localNav: boolean = $deviceInfo.navigator.visible
const savedNav = localStorage.getItem('love-visibleNav')
@@ -29,12 +32,30 @@
onDestroy(() => {
$deviceInfo.navigator.visible = localNav
})
onMount(async () => {
const wsURL = getMetadata(love.metadata.WebSocketURL)
if (wsURL === undefined) {
return
}
await $storePromise
const room = $currentRoom
if (room === undefined) return
if (
!$isConnected &&
!$isCurrentInstanceConnected &&
$myInfo?.sessionId === getMetadata(presentation.metadata.SessionId)
) {
const info = $infos.filter((p) => p.room === room._id)
await tryConnect($personByIdStore, $myInfo, room, info, $myRequests, $invites)
}
})
</script>
<div class="hulyPanels-container">
{#if $currentRoom && $isConnected}
<ActiveMeeting room={$currentRoom} />
{:else}
<Hall />
{/if}
<Hall />
</div>
@@ -0,0 +1,54 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { MeetingMinutes } from '@hcengineering/love'
import { ObjectPresenter, openDoc } from '@hcengineering/view-resources'
import view from '@hcengineering/view'
import { ActionIcon } from '@hcengineering/ui'
import { createQuery, getClient } from '@hcengineering/presentation'
import type { Doc } from '@hcengineering/core'
export let object: MeetingMinutes
const client = getClient()
const docQuery = createQuery()
let doc: Doc | undefined
$: docQuery.query(object.attachedToClass, { _id: object.attachedTo }, (r) => {
doc = r.shift()
})
</script>
{#if doc}
<span class="label flex-row-center flex-gap-4 ml-3 no-word-wrap">
<ObjectPresenter
objectId={doc._id}
_class={doc._class}
value={doc}
shouldShowAvatar={false}
disabled
props={{ type: 'text' }}
/>
<ActionIcon
icon={view.icon.Open}
size={'small'}
action={async () => {
if (doc === undefined) return
await openDoc(client.getHierarchy(), doc)
}}
/>
</span>
{/if}

Some files were not shown because too many files have changed in this diff Show More