mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Support for rating system (#10124)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Vendored
+39
-16
@@ -97,7 +97,7 @@
|
||||
"MODEL_JSON": "${workspaceRoot}/models/all/bundle/model.json",
|
||||
// "SERVER_PROVIDER":"uweb"
|
||||
"SERVER_PROVIDER": "ws",
|
||||
"MODEL_VERSION": "0.7.230",
|
||||
"MODEL_VERSION": "0.7.278",
|
||||
// "version": "0.7.0",
|
||||
"COMMUNICATION_API_ENABLED": "true",
|
||||
"ELASTIC_INDEX_NAME": "local_storage_index",
|
||||
@@ -408,27 +408,23 @@
|
||||
"protocol": "inspector"
|
||||
},
|
||||
{
|
||||
"name": "Debug backup tool",
|
||||
"name": "Debug tool",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"src/__start.ts",
|
||||
"backup-restore",
|
||||
"/Users/haiodo/Develop/private/platform/tests/sanity-ws",
|
||||
"sanity-ws",
|
||||
"--upgrade"
|
||||
],
|
||||
"args": ["src/__start.ts", "calculate-ratings", "2d790fe8-faba-4c2e-afb1-5610445966bb"],
|
||||
"env": {
|
||||
"MODEL_VERSION": "0.7.153",
|
||||
"STORAGE_CONFIG": "datalake|http://huly.local:4030",
|
||||
"ACCOUNTS_URL": "http://localhost:3003",
|
||||
"TRANSACTOR_URL": "ws://localhost:3334",
|
||||
"ACCOUNT_DB_URL": "postgresql://root@localhost:26258/defaultdb?sslmode=disable",
|
||||
"MONGO_URL": "mongodb://localhost:27018",
|
||||
"ELASTIC_URL": "http://localhost:9201",
|
||||
"ACCOUNTS_URL": "http://huly.local:3000",
|
||||
"TRANSACTOR_URL": "ws://localhost:3332",
|
||||
"ACCOUNT_DB_URL": "postgresql://root@localhost:26257/defaultdb?sslmode=disable",
|
||||
"MONGO_URL": "mongodb://localhost:27017",
|
||||
"ELASTIC_URL": "http://localhost:9200",
|
||||
"SERVER_SECRET": "secret",
|
||||
"DB_URL": "postgresql://root@localhost:26258/defaultdb?sslmode=disable",
|
||||
"QUEUE_CONFIG": "localhost:19093;-staging"
|
||||
"DB_URL": "postgresql://root@localhost:26257/defaultdb?sslmode=disable",
|
||||
"QUEUE_CONFIG": "huly.local:19092",
|
||||
"REGION": "cockroach",
|
||||
"QUEUE_REGION": "cockroach"
|
||||
},
|
||||
"smartStep": false,
|
||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||
@@ -625,6 +621,33 @@
|
||||
"protocol": "inspector",
|
||||
"outputCapture": "std"
|
||||
},
|
||||
{
|
||||
"name": "Debug Rating service",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"args": ["src/__start__.ts"],
|
||||
"env": {
|
||||
"SERVER_SECRET": "secret",
|
||||
"MODEL_VERSION": "0.7.278",
|
||||
"STORAGE_CONFIG": "datalake|http://huly.local:4030",
|
||||
"ACCOUNTS_URL": "http://localhost:3000",
|
||||
"COLLABORATOR_URL": "ws://huly.local:3078",
|
||||
"PLATFORM_OPERATION_LOGGING": "true",
|
||||
"STATS_URL": "http://huly.local:4900",
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://huly.local:4318/v1/traces",
|
||||
"QUEUE_CONFIG": "huly.local:19092",
|
||||
"MODEL_JSON": "${workspaceRoot}/models/all/bundle/model.json",
|
||||
"REGION": "cockroach",
|
||||
"QUEUE_REGION": "cockroach",
|
||||
"DB_URL": "postgresql://root@localhost:26257/defaultdb?sslmode=disable"
|
||||
},
|
||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||
"sourceMaps": true,
|
||||
"nodeVersionHint": 22,
|
||||
"cwd": "${workspaceRoot}/services/rating",
|
||||
"protocol": "inspector",
|
||||
"outputCapture": "std"
|
||||
},
|
||||
{
|
||||
"name": "Debug qms workers",
|
||||
"type": "node",
|
||||
|
||||
@@ -253,7 +253,7 @@
|
||||
"summary": "Build docker with platform",
|
||||
"description": "use to build all docker containers required for platform",
|
||||
"safeForSimultaneousRushProcesses": true,
|
||||
"shellCommand": "./common/scripts/docker-server.sh && rush docker:up"
|
||||
"shellCommand": "./common/scripts/docker-server.sh && rush docker:up:server"
|
||||
},
|
||||
{
|
||||
"commandKind": "global",
|
||||
@@ -263,6 +263,14 @@
|
||||
"safeForSimultaneousRushProcesses": true,
|
||||
"shellCommand": "cd ./dev && docker compose up -d --force-recreate"
|
||||
},
|
||||
{
|
||||
"commandKind": "global",
|
||||
"name": "docker:up:server",
|
||||
"summary": "Up development build",
|
||||
"description": "Up development build",
|
||||
"safeForSimultaneousRushProcesses": true,
|
||||
"shellCommand": "cd ./dev && docker compose up -d --force-recreate transactor_cockroach"
|
||||
},
|
||||
{
|
||||
"commandKind": "global",
|
||||
"name": "docker:local",
|
||||
|
||||
Generated
+739
-453
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
||||
rush docker:build -p 20 \
|
||||
--to @hcengineering/pod-server \
|
||||
--to @hcengineering/tool
|
||||
--to @hcengineering/tool || true
|
||||
|
||||
@@ -25,4 +25,5 @@ rush docker:build -p 20 \
|
||||
--to @hcengineering/pod-backup \
|
||||
--to @hcengineering/backup-api-pod \
|
||||
--to @hcengineering/pod-billing \
|
||||
--to @hcengineering/pod-process
|
||||
--to @hcengineering/pod-process \
|
||||
--to @hcengineering/pod-rating
|
||||
|
||||
@@ -269,6 +269,9 @@
|
||||
"@hcengineering/inbox": "^0.7.0",
|
||||
"@hcengineering/inbox-assets": "^0.7.0",
|
||||
"@hcengineering/inbox-resources": "^0.7.0",
|
||||
"@hcengineering/rating": "^0.7.0",
|
||||
"@hcengineering/rating-assets": "^0.7.0",
|
||||
"@hcengineering/rating-resources": "^0.7.0",
|
||||
"electron-squirrel-startup": "~1.0.0",
|
||||
"dotenv": "~16.0.0",
|
||||
"electron-context-menu": "^4.0.4",
|
||||
|
||||
@@ -82,6 +82,7 @@ import communication, { communicationId } from '@hcengineering/communication'
|
||||
import { emojiId } from '@hcengineering/emoji'
|
||||
import { hulyMailId } from '@hcengineering/huly-mail'
|
||||
import { aiAssistantId } from '@hcengineering/ai-assistant'
|
||||
import { ratingId } from '@hcengineering/rating'
|
||||
import billingPlugin, { billingId } from '@hcengineering/billing'
|
||||
|
||||
import '@hcengineering/activity-assets'
|
||||
@@ -142,6 +143,7 @@ import '@hcengineering/communication-assets'
|
||||
import '@hcengineering/billing-assets'
|
||||
import '@hcengineering/huly-mail-assets'
|
||||
import '@hcengineering/ai-assistant-assets'
|
||||
import '@hcengineering/rating-assets'
|
||||
|
||||
import analyticsCollector, { analyticsCollectorId } from '@hcengineering/analytics-collector'
|
||||
import { coreId } from '@hcengineering/core'
|
||||
@@ -266,6 +268,7 @@ function configureI18n (): void {
|
||||
addStringsLoader(billingId, async (lang: string) => await import(`@hcengineering/billing-assets/lang/${lang}.json`))
|
||||
addStringsLoader(hulyMailId, async (lang: string) => await import(`@hcengineering/huly-mail-assets/lang/${lang}.json`))
|
||||
addStringsLoader(aiAssistantId, async (lang: string) => await import(`@hcengineering/ai-assistant-assets/lang/${lang}.json`))
|
||||
addStringsLoader(ratingId, async (lang: string) => await import(`@hcengineering/rating-assets/lang/${lang}.json`))
|
||||
}
|
||||
|
||||
export class PlatformBranding {
|
||||
@@ -444,6 +447,7 @@ export async function configurePlatform (onWorkbenchConnect?: () => Promise<void
|
||||
addLocation(billingId, () => import(/* webpackChunkName: "billing" */ '@hcengineering/billing-resources'))
|
||||
addLocation(hulyMailId, () => import(/* webpackChunkName: "huly-mail" */ '@hcengineering/huly-mail-resources'))
|
||||
addLocation(aiAssistantId, () => import(/* webpackChunkName: "ai-assistant" */ '@hcengineering/ai-assistant-resources'))
|
||||
addLocation(ratingId, async () => await import(/* webpackChunkName: "rating" */ '@hcengineering/rating-resources'))
|
||||
|
||||
setMetadata(client.metadata.FilterModel, 'ui')
|
||||
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
|
||||
|
||||
+43
-17
@@ -562,23 +562,23 @@ services:
|
||||
- QUEUE_CONFIG=${QUEUE_CONFIG}
|
||||
- QUEUE_REGION=cockroach
|
||||
restart: unless-stopped
|
||||
# translate:
|
||||
# image: hardcoreeng/translate
|
||||
# extra_hosts:
|
||||
# - 'huly.local:host-gateway'
|
||||
# depends_on:
|
||||
# redpanda:
|
||||
# condition: service_started
|
||||
# account:
|
||||
# condition: service_started
|
||||
# environment:
|
||||
# - SECRET=secret
|
||||
# - ACCOUNTS_URL=http://huly.local:3000
|
||||
# - HULYLAKE_URL=http://huly.local:8096
|
||||
# - QUEUE_CONFIG=${QUEUE_CONFIG}
|
||||
# - QUEUE_REGION=cockroach
|
||||
# - OPENAI_API_KEY=
|
||||
# restart: unless-stopped
|
||||
# translate:
|
||||
# image: hardcoreeng/translate
|
||||
# extra_hosts:
|
||||
# - 'huly.local:host-gateway'
|
||||
# depends_on:
|
||||
# redpanda:
|
||||
# condition: service_started
|
||||
# account:
|
||||
# condition: service_started
|
||||
# environment:
|
||||
# - SECRET=secret
|
||||
# - ACCOUNTS_URL=http://huly.local:3000
|
||||
# - HULYLAKE_URL=http://huly.local:8096
|
||||
# - QUEUE_CONFIG=${QUEUE_CONFIG}
|
||||
# - QUEUE_REGION=cockroach
|
||||
# - OPENAI_API_KEY=
|
||||
# restart: unless-stopped
|
||||
backup-cockroach:
|
||||
image: hardcoreeng/backup
|
||||
extra_hosts:
|
||||
@@ -629,6 +629,32 @@ services:
|
||||
- STORAGE=minio|minio?accessKey=minioadmin&secretKey=minioadmin
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318/v1/traces
|
||||
restart: unless-stopped
|
||||
|
||||
rating_cockroach:
|
||||
image: hardcoreeng/rating
|
||||
extra_hosts:
|
||||
- 'huly.local:host-gateway'
|
||||
links:
|
||||
- cockroach
|
||||
- minio
|
||||
- account
|
||||
- stats
|
||||
depends_on:
|
||||
redpanda:
|
||||
condition: service_started
|
||||
environment:
|
||||
- QUEUE_CONFIG=${QUEUE_CONFIG}
|
||||
- REGION=cockroach
|
||||
- SERVER_SECRET=secret
|
||||
- STATS_URL=http://huly.local:4900
|
||||
- DB_URL=${DB_CR_URL}
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
- ACCOUNTS_URL=http://huly.local:3000
|
||||
- COMMUNICATION_TIME_LOGGING_ENABLED=true
|
||||
- COMMUNICATION_API_ENABLED=true
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318/v1/traces
|
||||
- HYLYLAKE_URL=http://huly.local:8096
|
||||
restart: unless-stopped
|
||||
jaeger:
|
||||
image: jaegertracing/all-in-one:latest
|
||||
extra_hosts:
|
||||
|
||||
@@ -289,6 +289,9 @@
|
||||
"@hcengineering/inbox": "^0.7.0",
|
||||
"@hcengineering/inbox-assets": "^0.7.0",
|
||||
"@hcengineering/inbox-resources": "^0.7.0",
|
||||
"@hcengineering/rating": "^0.7.0",
|
||||
"@hcengineering/rating-assets": "^0.7.0",
|
||||
"@hcengineering/rating-resources": "^0.7.0",
|
||||
"readable-stream": "^4.7.0",
|
||||
"svelte": "^4.2.20"
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ import { emojiId } from '@hcengineering/emoji'
|
||||
import billingPlugin, { billingId } from '@hcengineering/billing'
|
||||
import { hulyMailId } from '@hcengineering/huly-mail'
|
||||
import { aiAssistantId } from '@hcengineering/ai-assistant'
|
||||
import { ratingId } from '@hcengineering/rating'
|
||||
|
||||
import '@hcengineering/activity-assets'
|
||||
import '@hcengineering/analytics-collector-assets'
|
||||
@@ -139,6 +140,7 @@ import '@hcengineering/emoji-assets'
|
||||
import '@hcengineering/billing-assets'
|
||||
import '@hcengineering/huly-mail-assets'
|
||||
import '@hcengineering/ai-assistant-assets'
|
||||
import '@hcengineering/rating-assets'
|
||||
|
||||
import { coreId } from '@hcengineering/core'
|
||||
import presentation, {
|
||||
@@ -400,6 +402,7 @@ function configureI18n (): void {
|
||||
aiAssistantId,
|
||||
async (lang: string) => await import(`@hcengineering/ai-assistant-assets/lang/${lang}.json`)
|
||||
)
|
||||
addStringsLoader(ratingId, async (lang: string) => await import(`@hcengineering/rating-assets/lang/${lang}.json`))
|
||||
}
|
||||
|
||||
export async function configurePlatform () {
|
||||
@@ -680,6 +683,10 @@ export async function configurePlatform () {
|
||||
inboxId,
|
||||
async () => await import(/* webpackChunkName: "inbox" */ '@hcengineering/inbox-resources')
|
||||
)
|
||||
addLocation(
|
||||
ratingId,
|
||||
async () => await import(/* webpackChunkName: "rating" */ '@hcengineering/rating-resources')
|
||||
)
|
||||
|
||||
setMetadata(client.metadata.FilterModel, 'ui')
|
||||
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
"docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/tool staging",
|
||||
"docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/tool",
|
||||
"run-local-mongo": "rush bundle --to @hcengineering/tool >/dev/null && cross-env SERVER_SECRET=secret FULLTEXT_URL=http://localhost:4700 ACCOUNTS_URL=http://localhost:3000 TRANSACTOR_URL=ws://localhost:3333 MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_ENDPOINT=localhost ACCOUNT_DB_URL=mongodb://localhost:27017 DB_URL=mongodb://localhost:27017 TELEGRAM_DATABASE=telegram-service REKONI_URL=http://localhost:4004 MODEL_VERSION=$(node ../../common/scripts/show_version.js) GIT_REVISION=$(git describe --all --long) QUEUE_CONFIG='localhost:19092' node --expose-gc --max-old-space-size=18000 ./bundle/bundle.js",
|
||||
"run-local": "rush bundle --to @hcengineering/tool >/dev/null && cross-env SERVER_SECRET=secret FULLTEXT_URL=http://localhost:4702 ACCOUNTS_URL=http://localhost:3000 TRANSACTOR_URL=ws://localhost:3332 STORAGE_CONFIG='datalake|http://huly.local:4030' HULYLAKE_URL=http://huly.local:8096 ACCOUNT_DB_URL=postgresql://root@huly.local:26257/defaultdb?sslmode=disable DB_URL=postgresql://root@huly.local:26257/defaultdb?sslmode=disable TELEGRAM_DATABASE=telegram-service REKONI_URL=http://localhost:4004 REGION_INFO='cockroach|CockroachDB' MODEL_VERSION=$(node ../../common/scripts/show_version.js) GIT_REVISION=$(git describe --all --long) QUEUE_CONFIG='localhost:19092' node --expose-gc --max-old-space-size=18000 $TOOL_OPT ./bundle/bundle.js",
|
||||
"run-local": "(rush bundle --to @hcengineering/tool >/dev/null || true) && cross-env SERVER_SECRET=secret FULLTEXT_URL=http://localhost:4702 ACCOUNTS_URL=http://localhost:3000 TRANSACTOR_URL=ws://localhost:3332 STORAGE_CONFIG='datalake|http://huly.local:4030' HULYLAKE_URL=http://huly.local:8096 ACCOUNT_DB_URL=postgresql://root@huly.local:26257/defaultdb?sslmode=disable DB_URL=postgresql://root@huly.local:26257/defaultdb?sslmode=disable TELEGRAM_DATABASE=telegram-service REKONI_URL=http://localhost:4004 REGION_INFO='cockroach|CockroachDB' MODEL_VERSION=$(node ../../common/scripts/show_version.js) GIT_REVISION=$(git describe --all --long) QUEUE_CONFIG='localhost:19092' node --expose-gc --max-old-space-size=18000 $TOOL_OPT ./bundle/bundle.js",
|
||||
"run-local-brk": "rush bundle --to @hcengineering/tool >/dev/null && cross-env SERVER_SECRET=secret ACCOUNTS_URL=http://localhost:3000 TRANSACTOR_URL=ws://localhost:3333 MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_ENDPOINT=localhost ACCOUNT_DB_URL=mongodb://localhost:27017 DB_URL=mongodb://localhost:27017 TELEGRAM_DATABASE=telegram-service REKONI_URL=http://localhost:4004 MODEL_VERSION=$(node ../../common/scripts/show_version.js) GIT_REVISION=$(git describe --all --long) node --inspect-brk --enable-source-maps --max-old-space-size=18000 ./bundle/bundle.js",
|
||||
"run": "rush bundle --to @hcengineering/tool >/dev/null && cross-env node --max-old-space-size=8000 ./bundle/bundle.js",
|
||||
"run": "(rush bundle --to @hcengineering/tool >/dev/null || true ) && cross-env node --max-old-space-size=8000 ./bundle/bundle.js",
|
||||
"upgrade-mongo": "rushx run-local-mongo upgrade-workspace -- $1",
|
||||
"upgrade": "rushx run-local upgrade-workspace -- $1",
|
||||
"upgrade-brk": "rushx run-local-brk upgrade-workspace -- $1",
|
||||
@@ -183,6 +183,7 @@
|
||||
"@hcengineering/api-client": "^0.7.5",
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@hcengineering/hulylake-client": "^0.7.6",
|
||||
"@hcengineering/communication-types": "^0.7.7"
|
||||
"@hcengineering/communication-types": "^0.7.7",
|
||||
"@hcengineering/pod-rating": "^0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
+56
-20
@@ -27,16 +27,29 @@ import accountPlugin, {
|
||||
type AccountDB,
|
||||
type Workspace
|
||||
} from '@hcengineering/account'
|
||||
import {
|
||||
getMongoAccountDB,
|
||||
type Account as OldAccount,
|
||||
type Workspace as OldWorkspace
|
||||
} from '@hcengineering/account-service'
|
||||
import { getWorkspaceClient as getHulylakeClient } from '@hcengineering/hulylake-client'
|
||||
import { setMetadata } from '@hcengineering/platform'
|
||||
import {
|
||||
createPostgreeDestroyAdapter,
|
||||
createPostgresAdapter,
|
||||
createPostgresTxAdapter,
|
||||
getDBClient,
|
||||
shutdownPostgres
|
||||
} from '@hcengineering/postgres'
|
||||
import {
|
||||
backup,
|
||||
backupDownload,
|
||||
backupFind,
|
||||
checkBackupIntegrity,
|
||||
compactBackup,
|
||||
createFileBackupStorage,
|
||||
createStorageBackupStorage,
|
||||
restore,
|
||||
backupDownload
|
||||
restore
|
||||
} from '@hcengineering/server-backup'
|
||||
import serverClientPlugin, { getAccountClient, getTransactorEndpoint } from '@hcengineering/server-client'
|
||||
import {
|
||||
@@ -51,19 +64,6 @@ import {
|
||||
} from '@hcengineering/server-pipeline'
|
||||
import serverToken, { decodeToken, generateToken } from '@hcengineering/server-token'
|
||||
import { createWorkspace, upgradeWorkspace } from '@hcengineering/workspace-service'
|
||||
import {
|
||||
getMongoAccountDB,
|
||||
type Account as OldAccount,
|
||||
type Workspace as OldWorkspace
|
||||
} from '@hcengineering/account-service'
|
||||
import { getWorkspaceClient as getHulylakeClient } from '@hcengineering/hulylake-client'
|
||||
import {
|
||||
getDBClient,
|
||||
createPostgreeDestroyAdapter,
|
||||
createPostgresAdapter,
|
||||
createPostgresTxAdapter,
|
||||
shutdownPostgres
|
||||
} from '@hcengineering/postgres'
|
||||
|
||||
import { faker } from '@faker-js/faker'
|
||||
import { getPlatformQueue } from '@hcengineering/kafka'
|
||||
@@ -71,13 +71,14 @@ import { buildStorageFromConfig, createStorageFromConfig, storageConfigFromEnv }
|
||||
import { program, type Command } from 'commander'
|
||||
import { updateField } from './workspace'
|
||||
|
||||
import { RatingCalculator, ratingEvents, type QueueRatingMessage } from '@hcengineering/pod-rating'
|
||||
|
||||
import {
|
||||
AccountRole,
|
||||
isArchivingMode,
|
||||
isDeletingMode,
|
||||
MeasureMetricsContext,
|
||||
metricsToString,
|
||||
SocialId,
|
||||
SocialIdType,
|
||||
systemAccountEmail,
|
||||
systemAccountUuid,
|
||||
@@ -85,12 +86,12 @@ import {
|
||||
type Data,
|
||||
type Doc,
|
||||
type PersonId,
|
||||
type PersonUuid,
|
||||
type Ref,
|
||||
type Tx,
|
||||
type Version,
|
||||
type WorkspaceDataId,
|
||||
type WorkspaceUuid,
|
||||
type PersonUuid
|
||||
type WorkspaceUuid
|
||||
} from '@hcengineering/core'
|
||||
import { consoleModelLogger, type MigrateOperation } from '@hcengineering/model'
|
||||
import {
|
||||
@@ -129,14 +130,14 @@ import { performGmailAccountMigrations } from './gmail'
|
||||
import { getToolToken, getWorkspace, getWorkspaceTransactorEndpoint } from './utils'
|
||||
|
||||
import { createRestClient } from '@hcengineering/api-client'
|
||||
import { type CardID } from '@hcengineering/communication-types'
|
||||
import { sendTransactorEvent } from '@hcengineering/server-tool'
|
||||
import { existsSync } from 'fs'
|
||||
import { mkdir, writeFile } from 'fs/promises'
|
||||
import { dirname } from 'path'
|
||||
import { migrateWorkspaceMessages } from './communication'
|
||||
import { restoreMarkupRefs } from './markup'
|
||||
import { restoreGithubIntegrations } from './restoreGithub'
|
||||
import { migrateWorkspaceMessages } from './communication'
|
||||
import { type CardID } from '@hcengineering/communication-types'
|
||||
|
||||
const colorConstants = {
|
||||
colorRed: '\u001b[31m',
|
||||
@@ -2899,6 +2900,41 @@ export function devTool (
|
||||
}, dbUrl)
|
||||
})
|
||||
|
||||
program
|
||||
.command('calculate-ratings <workspace>')
|
||||
.description('Perform a rating re-calculation')
|
||||
.option('-q <queue>', 'Send to queue', false)
|
||||
.option('-r, --region <region>', 'Region')
|
||||
.action(async (workspace: string, cmd: { queue: boolean | undefined, region: string | undefined }) => {
|
||||
await withAccountDatabase(async (db) => {
|
||||
const { txes, dbUrl } = prepareTools()
|
||||
const ws = await getWorkspace(db, workspace)
|
||||
if (ws === null) {
|
||||
throw new Error(`workspace ${workspace} not found`)
|
||||
}
|
||||
|
||||
if (cmd.queue === true) {
|
||||
const queue = getPlatformQueue('tool', cmd.region ?? '')
|
||||
const ratingQueue = queue.getProducer<QueueRatingMessage>(toolCtx, 'rating')
|
||||
|
||||
await ratingQueue.send(toolCtx, ws.uuid, [ratingEvents.reindex()])
|
||||
|
||||
await queue.shutdown()
|
||||
} else {
|
||||
const wsIds = {
|
||||
uuid: ws.uuid,
|
||||
dataId: ws.dataId,
|
||||
url: ws.url
|
||||
}
|
||||
const calculator = await RatingCalculator.create(toolCtx, txes, wsIds, dbUrl, async () => '')
|
||||
|
||||
await calculator.recalculateAll(toolCtx)
|
||||
|
||||
await calculator.close()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
extendProgram?.(program)
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
"@hcengineering/model-billing": "^0.7.0",
|
||||
"@hcengineering/model-huly-mail": "^0.7.0",
|
||||
"@hcengineering/model-ai-assistant": "^0.7.0",
|
||||
"@hcengineering/model-inbox": "^0.7.0"
|
||||
"@hcengineering/model-inbox": "^0.7.0",
|
||||
"@hcengineering/model-rating": "^0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
+41
-26
@@ -17,28 +17,36 @@ import core, { coreId, type Data, type PluginConfiguration, type Ref, type Tx, t
|
||||
|
||||
import { Builder } from '@hcengineering/model'
|
||||
import { activityId, createModel as activityModel } from '@hcengineering/model-activity'
|
||||
import { aiBotId, createModel as aiBotModel } from '@hcengineering/model-ai-bot'
|
||||
import { attachmentId, createModel as attachmentModel } from '@hcengineering/model-attachment'
|
||||
import bitrix, { bitrixId, createModel as bitrixModel } from '@hcengineering/model-bitrix'
|
||||
import board, { boardId, createModel as boardModel } from '@hcengineering/model-board'
|
||||
import calendar, { calendarId, createModel as calendarModel } from '@hcengineering/model-calendar'
|
||||
import card, { cardId, createModel as cardModel } from '@hcengineering/model-card'
|
||||
import chunter, { chunterId, createModel as chunterModel } from '@hcengineering/model-chunter'
|
||||
import contact, { contactId, createModel as contactModel } from '@hcengineering/model-contact'
|
||||
import { createModel as coreModel } from '@hcengineering/model-core'
|
||||
import { desktopDownloadsId, createModel as desktopDownloadsModel } from '@hcengineering/model-desktop-downloads'
|
||||
import { desktopPreferencesId, createModel as desktopPreferencesModel } from '@hcengineering/model-desktop-preferences'
|
||||
import { driveId, createModel as driveModel } from '@hcengineering/model-drive'
|
||||
import gmail, { gmailId, createModel as gmailModel } from '@hcengineering/model-gmail'
|
||||
import { guestId, createModel as guestModel } from '@hcengineering/model-guest'
|
||||
import hr, { hrId, createModel as hrModel } from '@hcengineering/model-hr'
|
||||
import inventory, { inventoryId, createModel as inventoryModel } from '@hcengineering/model-inventory'
|
||||
import lead, { leadId, createModel as leadModel } from '@hcengineering/model-lead'
|
||||
import { mediaId, createModel as mediaModel } from '@hcengineering/model-media'
|
||||
import notification, { notificationId, createModel as notificationModel } from '@hcengineering/model-notification'
|
||||
import { preferenceId, createModel as preferenceModel } from '@hcengineering/model-preference'
|
||||
import presentation, { presentationId, createModel as presentationModel } from '@hcengineering/model-presentation'
|
||||
import rating, { ratingId, createModel as ratingModel } from '@hcengineering/model-rating'
|
||||
import { recorderId, createModel as recorderModel } from '@hcengineering/model-recorder'
|
||||
import recruit, { recruitId, createModel as recruitModel } from '@hcengineering/model-recruit'
|
||||
import { requestId, createModel as requestModel } from '@hcengineering/model-request'
|
||||
import { aiBotId, createModel as aiBotModel } from '@hcengineering/model-ai-bot'
|
||||
import { serverActivityId, createModel as serverActivityModel } from '@hcengineering/model-server-activity'
|
||||
import { serverAiBotId, createModel as serverAiBotModel } from '@hcengineering/model-server-ai-bot'
|
||||
import { serverAttachmentId, createModel as serverAttachmentModel } from '@hcengineering/model-server-attachment'
|
||||
import { serverCardId, createModel as serverCardModel } from '@hcengineering/model-server-card'
|
||||
import { serverCalendarId, createModel as serverCalendarModel } from '@hcengineering/model-server-calendar'
|
||||
import { serverCardId, createModel as serverCardModel } from '@hcengineering/model-server-card'
|
||||
import { serverChunterId, createModel as serverChunterModel } from '@hcengineering/model-server-chunter'
|
||||
import {
|
||||
serverCollaborationId,
|
||||
@@ -62,9 +70,7 @@ import { serverTelegramId, createModel as serverTelegramModel } from '@hcenginee
|
||||
import { serverTemplatesId, createModel as serverTemplatesModel } from '@hcengineering/model-server-templates'
|
||||
import { serverTrackerId, createModel as serverTrackerModel } from '@hcengineering/model-server-tracker'
|
||||
import { serverViewId, createModel as serverViewModel } from '@hcengineering/model-server-view'
|
||||
import { serverAiBotId, createModel as serverAiBotModel } from '@hcengineering/model-server-ai-bot'
|
||||
import setting, { settingId, createModel as settingModel } from '@hcengineering/model-setting'
|
||||
import { driveId, createModel as driveModel } from '@hcengineering/model-drive'
|
||||
import { supportId, createModel as supportModel } from '@hcengineering/model-support'
|
||||
import { tagsId, createModel as tagsModel } from '@hcengineering/model-tags'
|
||||
import { taskId, createModel as taskModel } from '@hcengineering/model-task'
|
||||
@@ -74,13 +80,8 @@ import { textEditorId, createModel as textEditorModel } from '@hcengineering/mod
|
||||
import { timeId, createModel as timeModel } from '@hcengineering/model-time'
|
||||
import tracker, { trackerId, createModel as trackerModel } from '@hcengineering/model-tracker'
|
||||
import { uploaderId, createModel as uploaderModel } from '@hcengineering/model-uploader'
|
||||
import { recorderId, createModel as recorderModel } from '@hcengineering/model-recorder'
|
||||
import { mediaId, createModel as mediaModel } from '@hcengineering/model-media'
|
||||
import view, { viewId, createModel as viewModel } from '@hcengineering/model-view'
|
||||
import workbench, { workbenchId, createModel as workbenchModel } from '@hcengineering/model-workbench'
|
||||
import card, { cardId, createModel as cardModel } from '@hcengineering/model-card'
|
||||
import { desktopPreferencesId, createModel as desktopPreferencesModel } from '@hcengineering/model-desktop-preferences'
|
||||
import { desktopDownloadsId, createModel as desktopDownloadsModel } from '@hcengineering/model-desktop-downloads'
|
||||
|
||||
import document, { documentId, createModel as documentModel } from '@hcengineering/model-document'
|
||||
import { serverDocumentId, createModel as serverDocumentModel } from '@hcengineering/model-server-document'
|
||||
@@ -88,41 +89,41 @@ import { serverDocumentId, createModel as serverDocumentModel } from '@hcenginee
|
||||
import github, { githubId, createModel as githubModel } from '@hcengineering/model-github'
|
||||
import { serverGithubId, createModel as serverGithubModel } from '@hcengineering/server-github-model'
|
||||
|
||||
import { serverTimeId, createModel as serverTimeModel } from '@hcengineering/model-server-time'
|
||||
import { analyticsCollectorId, createModel as analyticsCollectorModel } from '@hcengineering/model-analytics-collector'
|
||||
import { exportId, createModel as exportModel } from '@hcengineering/model-export'
|
||||
import love, { loveId, createModel as loveModel } from '@hcengineering/model-love'
|
||||
import { printId, createModel as printModel } from '@hcengineering/model-print'
|
||||
import { exportId, createModel as exportModel } from '@hcengineering/model-export'
|
||||
import { analyticsCollectorId, createModel as analyticsCollectorModel } from '@hcengineering/model-analytics-collector'
|
||||
import { serverLoveId, createModel as serverLoveModel } from '@hcengineering/model-server-love'
|
||||
import { serverProcessId, createModel as serverProcessModel } from '@hcengineering/model-server-process'
|
||||
import { serverTimeId, createModel as serverTimeModel } from '@hcengineering/model-server-time'
|
||||
|
||||
import { questionsId, createModel as questionsModel } from '@hcengineering/model-questions'
|
||||
import trainings, { trainingId, createModel as trainingModel } from '@hcengineering/model-training'
|
||||
import aiAssistant, { aiAssistantId, createModel as aiAssistantModel } from '@hcengineering/model-ai-assistant'
|
||||
import documents, { documentsId, createModel as documentsModel } from '@hcengineering/model-controlled-documents'
|
||||
import { hulyMailId, createModel as hulyMailModel } from '@hcengineering/model-huly-mail'
|
||||
import { mailId, createModel as mailModel } from '@hcengineering/model-mail'
|
||||
import products, { productsId, createModel as productsModel } from '@hcengineering/model-products'
|
||||
import { questionsId, createModel as questionsModel } from '@hcengineering/model-questions'
|
||||
import { serverProductsId, createModel as serverProductsModel } from '@hcengineering/model-server-products'
|
||||
import { serverTrainingId, createModel as serverTrainingModel } from '@hcengineering/model-server-training'
|
||||
import testManagement, {
|
||||
testManagementId,
|
||||
createModel as testManagementModel
|
||||
} from '@hcengineering/model-test-management'
|
||||
import { mailId, createModel as mailModel } from '@hcengineering/model-mail'
|
||||
import { hulyMailId, createModel as hulyMailModel } from '@hcengineering/model-huly-mail'
|
||||
import aiAssistant, { aiAssistantId, createModel as aiAssistantModel } from '@hcengineering/model-ai-assistant'
|
||||
import trainings, { trainingId, createModel as trainingModel } from '@hcengineering/model-training'
|
||||
|
||||
import { achievementId, createModel as achievementModel } from '@hcengineering/model-achievement'
|
||||
import { billingId, createModel as billingModel } from '@hcengineering/model-billing'
|
||||
import chat, { chatId, createModel as chatModel } from '@hcengineering/model-chat'
|
||||
import { communicationId, createModel as communicationModel } from '@hcengineering/model-communication'
|
||||
import { emojiId, createModel as emojiModel } from '@hcengineering/model-emoji'
|
||||
import inbox, { inboxId, createModel as inboxModel } from '@hcengineering/model-inbox'
|
||||
import { presenceId, createModel as presenceModel } from '@hcengineering/model-presence'
|
||||
import processes, { processId, createModel as processModel } from '@hcengineering/model-process'
|
||||
import {
|
||||
serverDocumentsId,
|
||||
createModel as serverDocumentsModel
|
||||
} from '@hcengineering/model-server-controlled-documents'
|
||||
import survey, { surveyId, createModel as surveyModel } from '@hcengineering/model-survey'
|
||||
import { presenceId, createModel as presenceModel } from '@hcengineering/model-presence'
|
||||
import chat, { chatId, createModel as chatModel } from '@hcengineering/model-chat'
|
||||
import processes, { processId, createModel as processModel } from '@hcengineering/model-process'
|
||||
import inbox, { createModel as inboxModel, inboxId } from '@hcengineering/model-inbox'
|
||||
import { achievementId, createModel as achievementModel } from '@hcengineering/model-achievement'
|
||||
import { emojiId, createModel as emojiModel } from '@hcengineering/model-emoji'
|
||||
import { billingId, createModel as billingModel } from '@hcengineering/model-billing'
|
||||
import { communicationId, createModel as communicationModel } from '@hcengineering/model-communication'
|
||||
import { type Plugin } from '@hcengineering/platform'
|
||||
|
||||
interface ConfigurablePlugin extends Omit<Data<PluginConfiguration>, 'pluginId' | 'transactions'> {}
|
||||
@@ -161,7 +162,8 @@ export default function buildModel (): Builder {
|
||||
contact.class.ChannelProvider,
|
||||
setting.class.IntegrationType,
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
setting.class.SettingsCategory
|
||||
setting.class.SettingsCategory,
|
||||
workbench.class.Widget
|
||||
]
|
||||
|
||||
const builders: BuilderConfig[] = [
|
||||
@@ -513,6 +515,19 @@ export default function buildModel (): Builder {
|
||||
classFilter: defaultFilter
|
||||
}
|
||||
],
|
||||
[
|
||||
ratingModel,
|
||||
ratingId,
|
||||
{
|
||||
label: rating.string.Rating,
|
||||
description: rating.string.Rating,
|
||||
icon: rating.icon.Rating,
|
||||
hidden: false,
|
||||
enabled: false,
|
||||
beta: true,
|
||||
classFilter: defaultFilter
|
||||
}
|
||||
],
|
||||
|
||||
[serverCoreModel, serverCoreId],
|
||||
[serverAttachmentModel, serverAttachmentId],
|
||||
|
||||
+20
-18
@@ -16,54 +16,56 @@
|
||||
// Import migrate operations.
|
||||
import { type MigrateOperation } from '@hcengineering/model'
|
||||
import { activityOperation } from '@hcengineering/model-activity'
|
||||
import { aiBotId, aiBotOperation } from '@hcengineering/model-ai-bot'
|
||||
import { analyticsCollectorOperation } from '@hcengineering/model-analytics-collector'
|
||||
import { attachmentOperation } from '@hcengineering/model-attachment'
|
||||
import { bitrixOperation } from '@hcengineering/model-bitrix'
|
||||
import { boardOperation } from '@hcengineering/model-board'
|
||||
import { calendarOperation } from '@hcengineering/model-calendar'
|
||||
import { cardOperation } from '@hcengineering/model-card'
|
||||
import { chatId, chatOperation } from '@hcengineering/model-chat'
|
||||
import { chunterOperation } from '@hcengineering/model-chunter'
|
||||
import { communicationId, communicationOperation } from '@hcengineering/model-communication'
|
||||
import { contactOperation } from '@hcengineering/model-contact'
|
||||
import { documentsOperation } from '@hcengineering/model-controlled-documents'
|
||||
import { coreOperation } from '@hcengineering/model-core'
|
||||
import { documentOperation } from '@hcengineering/model-document'
|
||||
import { driveOperation } from '@hcengineering/model-drive'
|
||||
import { githubOperation, githubOperationPreTime } from '@hcengineering/model-github'
|
||||
import { gmailOperation } from '@hcengineering/model-gmail'
|
||||
import { guestOperation } from '@hcengineering/model-guest'
|
||||
import { hrOperation } from '@hcengineering/model-hr'
|
||||
import { inboxId, inboxOperation } from '@hcengineering/model-inbox'
|
||||
import { inventoryOperation } from '@hcengineering/model-inventory'
|
||||
import { leadOperation } from '@hcengineering/model-lead'
|
||||
import { loveId, loveOperation } from '@hcengineering/model-love'
|
||||
import { notificationOperation } from '@hcengineering/model-notification'
|
||||
import { preferenceOperation } from '@hcengineering/model-preference'
|
||||
import { processId, processOperation } from '@hcengineering/model-process'
|
||||
import { productsOperation } from '@hcengineering/model-products'
|
||||
import { questionsOperation } from '@hcengineering/model-questions'
|
||||
import { ratingOperation } from '@hcengineering/model-rating'
|
||||
import { recorderId, recorderOperation } from '@hcengineering/model-recorder'
|
||||
import { recruitOperation } from '@hcengineering/model-recruit'
|
||||
import { requestOperation } from '@hcengineering/model-request'
|
||||
import { activityServerOperation } from '@hcengineering/model-server-activity'
|
||||
import { settingOperation } from '@hcengineering/model-setting'
|
||||
import { surveyOperation } from '@hcengineering/model-survey'
|
||||
import { tagsOperation } from '@hcengineering/model-tags'
|
||||
import { taskOperation } from '@hcengineering/model-task'
|
||||
import { telegramOperation } from '@hcengineering/model-telegram'
|
||||
import { templatesOperation } from '@hcengineering/model-templates'
|
||||
import { testManagementOperation } from '@hcengineering/model-test-management'
|
||||
import { textEditorOperation } from '@hcengineering/model-text-editor'
|
||||
import { timeOperation } from '@hcengineering/model-time'
|
||||
import { trackerOperation } from '@hcengineering/model-tracker'
|
||||
import { viewOperation } from '@hcengineering/model-view'
|
||||
import { textEditorOperation } from '@hcengineering/model-text-editor'
|
||||
import { githubOperation, githubOperationPreTime } from '@hcengineering/model-github'
|
||||
import { loveId, loveOperation } from '@hcengineering/model-love'
|
||||
import { questionsOperation } from '@hcengineering/model-questions'
|
||||
import { trainingOperation } from '@hcengineering/model-training'
|
||||
import { documentsOperation } from '@hcengineering/model-controlled-documents'
|
||||
import { productsOperation } from '@hcengineering/model-products'
|
||||
import { requestOperation } from '@hcengineering/model-request'
|
||||
import { analyticsCollectorOperation } from '@hcengineering/model-analytics-collector'
|
||||
import { viewOperation } from '@hcengineering/model-view'
|
||||
import { workbenchOperation } from '@hcengineering/model-workbench'
|
||||
import { testManagementOperation } from '@hcengineering/model-test-management'
|
||||
import { surveyOperation } from '@hcengineering/model-survey'
|
||||
import { cardOperation } from '@hcengineering/model-card'
|
||||
import { aiBotId, aiBotOperation } from '@hcengineering/model-ai-bot'
|
||||
import { chatId, chatOperation } from '@hcengineering/model-chat'
|
||||
import { inboxId, inboxOperation } from '@hcengineering/model-inbox'
|
||||
import { processId, processOperation } from '@hcengineering/model-process'
|
||||
import { communicationId, communicationOperation } from '@hcengineering/model-communication'
|
||||
import { recorderId, recorderOperation } from '@hcengineering/model-recorder'
|
||||
|
||||
export const migrateOperations: [string, MigrateOperation][] = [
|
||||
['core', coreOperation],
|
||||
['rating', ratingOperation],
|
||||
['activity', activityOperation],
|
||||
['card', cardOperation],
|
||||
['chunter', chunterOperation],
|
||||
|
||||
@@ -15,23 +15,17 @@
|
||||
|
||||
import activity from '@hcengineering/activity'
|
||||
import type {
|
||||
ClassCollaborators,
|
||||
CollectionSize,
|
||||
MarkupBlobRef,
|
||||
Domain,
|
||||
MarkupBlobRef,
|
||||
Rank,
|
||||
Ref,
|
||||
Role,
|
||||
RolesAssignment,
|
||||
ClassCollaborators
|
||||
RolesAssignment
|
||||
} from '@hcengineering/core'
|
||||
import { AccountUuid, AccountRole, IndexKind } from '@hcengineering/core'
|
||||
import {
|
||||
type Document,
|
||||
type DocumentSnapshot,
|
||||
type SavedDocument,
|
||||
type Teamspace,
|
||||
documentId
|
||||
} from '@hcengineering/document'
|
||||
import { AccountRole, AccountUuid, IndexKind } from '@hcengineering/core'
|
||||
import { type Document, type DocumentSnapshot, type Teamspace, documentId } from '@hcengineering/document'
|
||||
import {
|
||||
type Builder,
|
||||
Collection,
|
||||
@@ -41,19 +35,18 @@ import {
|
||||
Model,
|
||||
Prop,
|
||||
ReadOnly,
|
||||
TypeAccountUuid,
|
||||
TypeCollaborativeDoc,
|
||||
TypeNumber,
|
||||
TypeRef,
|
||||
TypeString,
|
||||
UX,
|
||||
TypeAccountUuid
|
||||
UX
|
||||
} from '@hcengineering/model'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
import chunter from '@hcengineering/model-chunter'
|
||||
import core, { TDoc, TTypedSpace } from '@hcengineering/model-core'
|
||||
import { createPublicLinkAction } from '@hcengineering/model-guest'
|
||||
import { generateClassNotificationTypes } from '@hcengineering/model-notification'
|
||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||
import presentation from '@hcengineering/model-presentation'
|
||||
import tracker from '@hcengineering/model-tracker'
|
||||
import view, { actionTemplates, createAction } from '@hcengineering/model-view'
|
||||
@@ -62,8 +55,8 @@ import notification from '@hcengineering/notification'
|
||||
import { type Asset, getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import tags from '@hcengineering/tags'
|
||||
import time, { type ToDo, type Todoable } from '@hcengineering/time'
|
||||
import document from './plugin'
|
||||
import { definePermissions } from './permissions'
|
||||
import document from './plugin'
|
||||
|
||||
export { documentId } from '@hcengineering/document'
|
||||
|
||||
@@ -148,12 +141,6 @@ export class TDocumentSnapshot extends TDoc implements DocumentSnapshot {
|
||||
parent!: Ref<Document>
|
||||
}
|
||||
|
||||
@Model(document.class.SavedDocument, preference.class.Preference)
|
||||
export class TSavedDocument extends TPreference implements SavedDocument {
|
||||
@Prop(TypeRef(document.class.Document), document.string.SavedDocuments)
|
||||
declare attachedTo: Ref<Document>
|
||||
}
|
||||
|
||||
@Model(document.class.Teamspace, core.class.TypedSpace)
|
||||
@UX(document.string.Teamspace, document.icon.Teamspace, 'Teamspace', 'name')
|
||||
export class TTeamspace extends TTypedSpace implements Teamspace {}
|
||||
@@ -281,7 +268,7 @@ function defineTeamspace (builder: Builder): void {
|
||||
}
|
||||
|
||||
function defineDocument (builder: Builder): void {
|
||||
builder.createModel(TDocument, TDocumentSnapshot, TSavedDocument, TDefaultTeamspaceTypeData)
|
||||
builder.createModel(TDocument, TDocumentSnapshot, TDefaultTeamspaceTypeData)
|
||||
|
||||
builder.mixin(document.class.Document, core.class.Class, time.mixin.ItemPresenter, {
|
||||
presenter: document.component.DocumentToDoPresenter
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/model/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "model"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "@hcengineering/model-rating",
|
||||
"version": "0.7.0",
|
||||
"main": "lib/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"author": "Copyright © Hardcore Engineering Inc.",
|
||||
"template": "@hcengineering/model-package",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"format": "format src",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate",
|
||||
"_phase:test": "jest --passWithNoTests --silent --forceExit",
|
||||
"test": "jest --passWithNoTests --silent --forceExit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.7.19",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"@types/node": "^22.15.29",
|
||||
"jest": "^29.7.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"ts-jest": "^29.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.7.0",
|
||||
"@hcengineering/contact": "^0.7.0",
|
||||
"@hcengineering/core": "^0.7.10",
|
||||
"@hcengineering/model": "^0.7.6",
|
||||
"@hcengineering/platform": "^0.7.5",
|
||||
"@hcengineering/rating": "^0.7.0",
|
||||
"@hcengineering/model-core": "^0.7.0",
|
||||
"@hcengineering/model-presentation": "^0.7.0",
|
||||
"@hcengineering/presentation": "^0.7.0",
|
||||
"@hcengineering/view": "^0.7.0",
|
||||
"@hcengineering/ui": "^0.7.0",
|
||||
"@hcengineering/workbench": "^0.7.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { type Builder } from '@hcengineering/model'
|
||||
|
||||
export function createActions (builder: Builder): void {}
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import core, { type AccountUuid, type PersonId } from '@hcengineering/core'
|
||||
import { type Builder, Model } from '@hcengineering/model'
|
||||
import { TAttachedDoc, TDoc } from '@hcengineering/model-core'
|
||||
import presentation from '@hcengineering/model-presentation'
|
||||
import {
|
||||
DOMAIN_PERSON_RATING,
|
||||
DOMAIN_RATING_REACTION,
|
||||
type DateCUD,
|
||||
type DocReaction,
|
||||
type PersonRating,
|
||||
type ReactionKind
|
||||
} from '@hcengineering/rating'
|
||||
import view from '@hcengineering/view'
|
||||
import workbench, { WidgetType } from '@hcengineering/workbench'
|
||||
import { createActions } from './actions'
|
||||
import rating from './plugin'
|
||||
|
||||
export { ratingId } from '@hcengineering/rating'
|
||||
export { ratingOperation } from './migration'
|
||||
|
||||
@Model(rating.class.DocReaction, core.class.Doc, DOMAIN_RATING_REACTION)
|
||||
export class TDocReaction extends TAttachedDoc implements DocReaction {
|
||||
// A reaction type identifier
|
||||
reactionType!: ReactionKind
|
||||
value!: number // +1, -1, 0..10 etc.
|
||||
}
|
||||
|
||||
@Model(rating.class.PersonRating, core.class.Doc, DOMAIN_PERSON_RATING)
|
||||
export class TPersonRating extends TDoc implements PersonRating {
|
||||
accountId!: AccountUuid
|
||||
|
||||
// A current person rating
|
||||
rating!: number // Could be re calculated from values.
|
||||
|
||||
// Document authoring statistics
|
||||
months!: DateCUD[] // Hold per month authoring stats
|
||||
days!: DateCUD[] // Hold per day authoring stats
|
||||
hours!: DateCUD[] // Hold per hour authoring stats
|
||||
|
||||
stats!: Record<string, [number, number, number]> // create/update/delete Hold per domain authoring stats
|
||||
socialIds!: Record<PersonId, number>
|
||||
|
||||
rageOperations!: number
|
||||
|
||||
// A special stats, stars on account and stars on documents, commentas authored by person
|
||||
starsEarned!: number // All author document stars.
|
||||
reactionsEarned!: number // All author document reactions
|
||||
|
||||
// How many messages person created
|
||||
messages!: number
|
||||
|
||||
stars!: number // Stars spend
|
||||
reactions!: number // Reactions created
|
||||
}
|
||||
|
||||
export * from './migration'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(
|
||||
// TDocRating,
|
||||
TPersonRating,
|
||||
TDocReaction
|
||||
)
|
||||
|
||||
createActions(builder)
|
||||
|
||||
builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, {
|
||||
extension: view.extensions.EditDocTitleExtension,
|
||||
component: rating.component.RatingEditor
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
workbench.class.Widget,
|
||||
core.space.Model,
|
||||
{
|
||||
label: rating.string.RatingWidget,
|
||||
type: WidgetType.Fixed,
|
||||
icon: rating.icon.Rating,
|
||||
component: rating.component.RatingWidget
|
||||
},
|
||||
rating.ids.RatingWidget
|
||||
)
|
||||
builder.mixin(rating.class.DocReaction, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: rating.component.DocReactionPresenter
|
||||
})
|
||||
}
|
||||
|
||||
export default rating
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import {
|
||||
type MigrateOperation,
|
||||
type MigrationClient,
|
||||
type MigrationUpgradeClient,
|
||||
tryMigrate,
|
||||
tryUpgrade
|
||||
} from '@hcengineering/model'
|
||||
import { ratingId } from '@hcengineering/rating'
|
||||
|
||||
export const ratingOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient, mode): Promise<void> {
|
||||
await tryMigrate(mode, client, ratingId, [])
|
||||
},
|
||||
async upgrade (state: Map<string, Set<string>>, client: () => Promise<MigrationUpgradeClient>, mode): Promise<void> {
|
||||
await tryUpgrade(mode, state, client, ratingId, [])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import {} from '@hcengineering/core'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import rating, { ratingId } from '@hcengineering/rating'
|
||||
import type { AnyComponent } from '@hcengineering/ui/src/types'
|
||||
|
||||
export default mergeIds(ratingId, rating, {
|
||||
component: {
|
||||
RatingEditor: '' as AnyComponent
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/model/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "lib", "dist", "types", "bundle"]
|
||||
}
|
||||
@@ -171,6 +171,7 @@ export default plugin(presentationId, {
|
||||
Token: '' as Metadata<string>,
|
||||
Endpoint: '' as Metadata<string>,
|
||||
WorkspaceUuid: '' as Metadata<WorkspaceUuid>,
|
||||
WorkspaceName: '' as Metadata<string>,
|
||||
WorkspaceDataId: '' as Metadata<WorkspaceDataId>,
|
||||
FrontUrl: '' as Asset,
|
||||
LinkPreviewUrl: '' as Metadata<string>,
|
||||
|
||||
@@ -32,7 +32,12 @@
|
||||
deviceOptionsStore as deviceInfo
|
||||
} from '@hcengineering/ui'
|
||||
import presence from '@hcengineering/presence'
|
||||
import { createQuery, createNotificationContextsQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
createQuery,
|
||||
createNotificationContextsQuery,
|
||||
getClient,
|
||||
ComponentExtensions
|
||||
} from '@hcengineering/presentation'
|
||||
import { showMenu } from '@hcengineering/view-resources'
|
||||
import view from '@hcengineering/view'
|
||||
import { NotificationContext } from '@hcengineering/communication-types'
|
||||
@@ -188,6 +193,10 @@
|
||||
{doc.title}
|
||||
{/if}
|
||||
</div>
|
||||
<ComponentExtensions
|
||||
extension={view.extensions.EditDocTitleExtension}
|
||||
props={{ size: 'medium', kind: 'ghost', _id, _class: doc._class, value: doc, readonly }}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="presence">
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
"@hcengineering/account-client": "^0.7.7",
|
||||
"@hcengineering/achievement": "^0.7.0",
|
||||
"svelte": "^4.2.20",
|
||||
"crypto-js": "^4.2.0"
|
||||
"crypto-js": "^4.2.0",
|
||||
"@hcengineering/rating": "^0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,18 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Channel, Person, combineName, getCurrentEmployee, getFirstName, getLastName } from '@hcengineering/contact'
|
||||
import { AccountRole, Ref, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
|
||||
import { AttributeEditor, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { AccountRole, Ref, getCurrentAccount, hasAccountRole, type AccountUuid } from '@hcengineering/core'
|
||||
import { AttributeEditor, createQuery, getClient, hasResource } from '@hcengineering/presentation'
|
||||
import type { PersonRating } from '@hcengineering/rating'
|
||||
import ratingPlugin from '@hcengineering/rating'
|
||||
import setting, { IntegrationType } from '@hcengineering/setting'
|
||||
import { EditBox, FocusHandler, Scroller, createFocusManager } from '@hcengineering/ui'
|
||||
import { Component, EditBox, FocusHandler, Scroller, createFocusManager } from '@hcengineering/ui'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import contact from '../plugin'
|
||||
import ChannelsEditor from './ChannelsEditor.svelte'
|
||||
import EditableAvatar from './EditableAvatar.svelte'
|
||||
import Avatar from './Avatar.svelte'
|
||||
import ChannelsDropdown from './ChannelsDropdown.svelte'
|
||||
import ChannelsEditor from './ChannelsEditor.svelte'
|
||||
import EditableAvatar from './EditableAvatar.svelte'
|
||||
|
||||
export let object: Person
|
||||
export let readonly: boolean = false
|
||||
@@ -92,13 +94,26 @@
|
||||
}
|
||||
|
||||
const manager = createFocusManager()
|
||||
|
||||
const levelQuery = createQuery()
|
||||
|
||||
let personRating: PersonRating | undefined
|
||||
|
||||
$: if (object.personUuid !== undefined) {
|
||||
levelQuery.query(ratingPlugin.class.PersonRating, { accountId: object.personUuid as AccountUuid }, (res) => {
|
||||
personRating = res[0]
|
||||
})
|
||||
} else {
|
||||
levelQuery.unsubscribe()
|
||||
personRating = undefined
|
||||
}
|
||||
</script>
|
||||
|
||||
<FocusHandler {manager} />
|
||||
|
||||
{#if object !== undefined}
|
||||
<div class="flex-row-stretch flex-grow">
|
||||
<div class="flex-no-shrink mr-8">
|
||||
<div class="flex-no-shrink mr-8 flex-col flex-row-center">
|
||||
{#key object}
|
||||
{#if editable}
|
||||
<EditableAvatar
|
||||
@@ -111,6 +126,14 @@
|
||||
{:else}
|
||||
<Avatar person={object} size={'x-large'} name={object.name} />
|
||||
{/if}
|
||||
{#if personRating != null && hasResource(ratingPlugin.component.RatingRing)}
|
||||
<div class="flex-row-center mt-2">
|
||||
<Component
|
||||
is={ratingPlugin.component.RatingRing}
|
||||
props={{ rating: personRating.rating, showValues: true }}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/key}
|
||||
</div>
|
||||
<div class="flex-grow flex-col">
|
||||
@@ -166,6 +189,11 @@
|
||||
</Scroller>
|
||||
</div>
|
||||
</div>
|
||||
{#if personRating != null && hasResource(ratingPlugin.component.RatingRing)}
|
||||
<div class="flex-row-center mt-2">
|
||||
<Component is={ratingPlugin.component.RatingActivities} props={{ rating: personRating }} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -15,23 +15,26 @@
|
||||
<script lang="ts">
|
||||
import { Employee, Person } from '@hcengineering/contact'
|
||||
import { AccountUuid, Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { ButtonIcon, navigate } from '@hcengineering/ui'
|
||||
import { ComponentExtensions, createQuery, getClient, hasResource } from '@hcengineering/presentation'
|
||||
import { ButtonIcon, Component, navigate } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { getObjectLinkFragment } from '@hcengineering/view-resources'
|
||||
import { ComponentExtensions, getClient } from '@hcengineering/presentation'
|
||||
|
||||
import ModernProfilePopup from './ModernProfilePopup.svelte'
|
||||
import contact from '../../plugin'
|
||||
import Avatar from '../Avatar.svelte'
|
||||
import { employeeByIdStore } from '../../utils'
|
||||
import { getPersonTimezone } from './utils'
|
||||
import rating, { type PersonRating } from '@hcengineering/rating'
|
||||
import { EmployeePresenter, getPersonByPersonRefStore } from '../../index'
|
||||
import TimePresenter from './TimePresenter.svelte'
|
||||
import contact from '../../plugin'
|
||||
import { employeeByIdStore } from '../../utils'
|
||||
import Avatar from '../Avatar.svelte'
|
||||
import DeactivatedHeader from './DeactivatedHeader.svelte'
|
||||
import ModernProfilePopup from './ModernProfilePopup.svelte'
|
||||
import TimePresenter from './TimePresenter.svelte'
|
||||
import { getPersonTimezone } from './utils'
|
||||
|
||||
export let _id: Ref<Employee>
|
||||
export let disabled: boolean = false
|
||||
|
||||
const hasRating = hasResource(rating.component.RatingRing)
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
@@ -44,6 +47,19 @@
|
||||
$: isEmployee = $employeeByIdStore.has(_id)
|
||||
$: void loadPersonTimezone(employee)
|
||||
|
||||
const levelQuery = createQuery()
|
||||
|
||||
let personRating: PersonRating | undefined
|
||||
|
||||
$: if (employee?.personUuid != null) {
|
||||
levelQuery.query(rating.class.PersonRating, { accountId: employee?.personUuid as AccountUuid }, (res) => {
|
||||
personRating = res[0]
|
||||
})
|
||||
} else {
|
||||
personRating = undefined
|
||||
levelQuery.unsubscribe()
|
||||
}
|
||||
|
||||
async function viewProfile (): Promise<void> {
|
||||
if (employee === undefined) return
|
||||
const panelComponent = hierarchy.classHierarchyMixin(employee._class as Ref<Class<Doc>>, view.mixin.ObjectPanel)
|
||||
@@ -94,6 +110,14 @@
|
||||
<div class="flex-col flex-gap-0-5 pl-1">
|
||||
<div class="status-container" />
|
||||
<EmployeePresenter value={employee} shouldShowAvatar={false} showPopup={false} compact accent />
|
||||
{#if hasRating}
|
||||
<div class="flex-row-center text-sm">
|
||||
<Component
|
||||
is={rating.component.RatingRing}
|
||||
props={{ rating: personRating?.rating ?? 0, showValues: false }}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<span class="flex-presenter cursor-default">
|
||||
<TimePresenter {timezone} />
|
||||
</span>
|
||||
|
||||
@@ -22,7 +22,13 @@
|
||||
import notification from '@hcengineering/notification'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { IconWithEmoji, copyTextToClipboard, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
ComponentExtensions,
|
||||
IconWithEmoji,
|
||||
copyTextToClipboard,
|
||||
createQuery,
|
||||
getClient
|
||||
} from '@hcengineering/presentation'
|
||||
import tags from '@hcengineering/tags'
|
||||
import { Heading } from '@hcengineering/text-editor'
|
||||
import { TableOfContents } from '@hcengineering/text-editor-resources'
|
||||
@@ -52,7 +58,7 @@
|
||||
} from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
|
||||
import { starDocument, unlockContent, unstarDocument } from '..'
|
||||
import { unlockContent } from '..'
|
||||
import document from '../plugin'
|
||||
import { getDocumentUrl } from '../utils'
|
||||
import DocumentEditor from './DocumentEditor.svelte'
|
||||
@@ -108,17 +114,6 @@
|
||||
clearTimeout(copyTimeout)
|
||||
})
|
||||
|
||||
const starredQuery = createQuery()
|
||||
let isStarred = false
|
||||
$: starredQuery.query(
|
||||
document.class.SavedDocument,
|
||||
{ attachedTo: _id },
|
||||
(res) => {
|
||||
isStarred = res.length !== 0
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
|
||||
async function createEmbedding (file: File): Promise<{ file: Ref<Blob>, type: string } | undefined> {
|
||||
if (doc === undefined) {
|
||||
return undefined
|
||||
@@ -221,18 +216,6 @@
|
||||
]
|
||||
let selectedAside: string | boolean = false
|
||||
|
||||
$: starAction = isStarred
|
||||
? {
|
||||
icon: document.icon.Starred,
|
||||
label: document.string.Unstar,
|
||||
action: () => doc !== undefined && unstarDocument(doc)
|
||||
}
|
||||
: {
|
||||
icon: document.icon.Star,
|
||||
label: document.string.Star,
|
||||
action: () => doc !== undefined && starDocument(doc)
|
||||
}
|
||||
|
||||
$: actions = [
|
||||
{
|
||||
icon: view.icon.CopyId,
|
||||
@@ -249,8 +232,7 @@
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
},
|
||||
starAction
|
||||
}
|
||||
]
|
||||
|
||||
let editor: DocumentEditor
|
||||
@@ -323,6 +305,12 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="utils">
|
||||
{#if doc}
|
||||
<ComponentExtensions
|
||||
extension={view.extensions.EditDocTitleExtension}
|
||||
props={{ size: 'medium', kind: 'ghost', _id: doc._id, _class: doc._class, value: doc, readonly }}
|
||||
/>
|
||||
{/if}
|
||||
{#if !$restrictionStore.disableActions}
|
||||
<Button
|
||||
id="btn-doc-title-open-more"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, {
|
||||
import {
|
||||
generateId,
|
||||
getCurrentAccount,
|
||||
type Class,
|
||||
@@ -31,8 +31,8 @@ import { openDoc } from '@hcengineering/view-resources'
|
||||
|
||||
import CreateDocument from './components/CreateDocument.svelte'
|
||||
import DocumentIcon from './components/DocumentIcon.svelte'
|
||||
import DocumentItem from './components/DocumentItem.svelte'
|
||||
import DocumentInlineEditor from './components/DocumentInlineEditor.svelte'
|
||||
import DocumentItem from './components/DocumentItem.svelte'
|
||||
import DocumentPresenter from './components/DocumentPresenter.svelte'
|
||||
import DocumentSearchIcon from './components/DocumentSearchIcon.svelte'
|
||||
import DocumentToDoPresenter from './components/DocumentToDoPresenter.svelte'
|
||||
@@ -113,23 +113,6 @@ async function editTeamspace (teamspace: Teamspace | undefined): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function starDocument (doc: Document): Promise<void> {
|
||||
const client = getClient()
|
||||
|
||||
await client.createDoc(document.class.SavedDocument, core.space.Workspace, {
|
||||
attachedTo: doc._id
|
||||
})
|
||||
}
|
||||
|
||||
export async function unstarDocument (doc: Document): Promise<void> {
|
||||
const client = getClient()
|
||||
|
||||
const current = await client.findOne(document.class.SavedDocument, { attachedTo: doc._id })
|
||||
if (current !== undefined) {
|
||||
await client.remove(current)
|
||||
}
|
||||
}
|
||||
|
||||
export async function lockContent (doc: Document | Document[]): Promise<void> {
|
||||
const client = getClient()
|
||||
const me = getCurrentAccount()
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
import { type Client, type Doc, type Ref } from '@hcengineering/core'
|
||||
import document, { type Document, documentId } from '@hcengineering/document'
|
||||
import document, { documentId, type Document } from '@hcengineering/document'
|
||||
import { mergeIds, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
import { type AnyComponent, type Location } from '@hcengineering/ui/src/types'
|
||||
|
||||
@@ -50,7 +50,6 @@ export default mergeIds(documentId, document, {
|
||||
Authors: '' as IntlString,
|
||||
Backlinks: '' as IntlString,
|
||||
History: '' as IntlString,
|
||||
SavedDocuments: '' as IntlString,
|
||||
AddLabel: '' as IntlString,
|
||||
NewTeamspace: '' as IntlString,
|
||||
CreateTeamspace: '' as IntlString,
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Class, Doc, Mixin, Ref, SpaceType, SpaceTypeDescriptor, Permission } from '@hcengineering/core'
|
||||
import type { Class, Doc, Mixin, Permission, Ref, SpaceType, SpaceTypeDescriptor } from '@hcengineering/core'
|
||||
import { NotificationGroup, NotificationType } from '@hcengineering/notification'
|
||||
import type { Asset, Plugin, Resource } from '@hcengineering/platform'
|
||||
import { IntlString, plugin } from '@hcengineering/platform'
|
||||
import type { AnyComponent, Location, ResolvedLocation } from '@hcengineering/ui/src/types'
|
||||
import { Action } from '@hcengineering/view'
|
||||
import { Document, DocumentSnapshot, SavedDocument, Teamspace } from './types'
|
||||
import { Document, DocumentSnapshot, Teamspace } from './types'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -33,7 +33,6 @@ export const documentPlugin = plugin(documentId, {
|
||||
class: {
|
||||
Document: '' as Ref<Class<Document>>,
|
||||
DocumentSnapshot: '' as Ref<Class<DocumentSnapshot>>,
|
||||
SavedDocument: '' as Ref<Class<SavedDocument>>,
|
||||
Teamspace: '' as Ref<Class<Teamspace>>
|
||||
},
|
||||
mixin: {
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
//
|
||||
|
||||
import { AccountUuid, Doc, MarkupBlobRef, Rank, Ref, TypedSpace } from '@hcengineering/core'
|
||||
import { Preference } from '@hcengineering/preference'
|
||||
import { IconProps } from '@hcengineering/view'
|
||||
|
||||
/** @public */
|
||||
@@ -45,8 +44,3 @@ export interface DocumentSnapshot extends Doc {
|
||||
content: MarkupBlobRef
|
||||
parent: Ref<Document>
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface SavedDocument extends Preference {
|
||||
attachedTo: Ref<Document>
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ export async function connect (title: string): Promise<Client | undefined> {
|
||||
|
||||
setMetadata(presentation.metadata.Token, exchangedToken)
|
||||
setMetadata(presentation.metadata.WorkspaceUuid, workspaceLoginInfo.workspace)
|
||||
setMetadata(presentation.metadata.WorkspaceName, workspaceLoginInfo.name ?? workspaceLoginInfo.workspaceUrl)
|
||||
setMetadata(presentation.metadata.WorkspaceDataId, workspaceLoginInfo.workspaceDataId)
|
||||
setMetadata(presentation.metadata.Endpoint, workspaceLoginInfo.endpoint)
|
||||
|
||||
|
||||
@@ -115,7 +115,9 @@
|
||||
"ExecutionInitiator": "Iniciátor provedení",
|
||||
"ExecutionStarted": "Provedení spuštěno",
|
||||
"Filter": "Filtr",
|
||||
"FirstMatchValue": "První odpovídající hodnota"
|
||||
"FirstMatchValue": "První odpovídající hodnota",
|
||||
"ConfigLabel": "Konfigurace procesů",
|
||||
"ConfigDescription": "Definujte procesy pro automatizaci pracovních postupů a obchodních procesů."
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Metoda nenalezena: {methodId}",
|
||||
|
||||
@@ -115,7 +115,9 @@
|
||||
"ExecutionInitiator": "Ausführungsinitiator",
|
||||
"ExecutionStarted": "Ausführung gestartet",
|
||||
"Filter": "Filter",
|
||||
"FirstMatchValue": "Erster passender Wert"
|
||||
"FirstMatchValue": "Erster passender Wert",
|
||||
"ConfigLabel": "Prozesskonfiguration",
|
||||
"ConfigDescription": "Definieren Sie Prozesse, um Workflows und Geschäftsprozesse zu automatisieren."
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Methode nicht gefunden: {methodId}",
|
||||
|
||||
@@ -120,7 +120,9 @@
|
||||
"ExecutionInitiator": "Execution initiator",
|
||||
"ExecutionStarted": "Execution started",
|
||||
"Filter": "Filter",
|
||||
"FirstMatchValue": "First match value"
|
||||
"FirstMatchValue": "First match value",
|
||||
"ConfigLabel": "Process configuration",
|
||||
"ConfigDescription": "Define processes to automate workflows and business processes."
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Method not found: {methodId}",
|
||||
|
||||
@@ -120,7 +120,9 @@
|
||||
"ExecutionInitiator": "Iniciador de Ejecución",
|
||||
"ExecutionStarted": "Ejecución Iniciada",
|
||||
"Filter": "Filtro",
|
||||
"FirstMatchValue": "Primer valor coincidente"
|
||||
"FirstMatchValue": "Primer valor coincidente",
|
||||
"ConfigLabel": "Configuración de procesos",
|
||||
"ConfigDescription": "Defina procesos para automatizar flujos de trabajo y procesos comerciales."
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Método no encontrado: {methodId}",
|
||||
|
||||
@@ -120,7 +120,9 @@
|
||||
"ExecutionInitiator": "Initiateur d'exécution",
|
||||
"ExecutionStarted": "Exécution démarrée",
|
||||
"Filter": "Filtrer",
|
||||
"FirstMatchValue": "Première valeur correspondante"
|
||||
"FirstMatchValue": "Première valeur correspondante",
|
||||
"ConfigLabel": "Configuration des processus",
|
||||
"ConfigDescription": "Définissez des processus pour automatiser les flux de travail et les processus métier."
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Méthode introuvable : {methodId}",
|
||||
@@ -138,4 +140,4 @@
|
||||
"RequiredParamsNotProvided": "Paramètres requis non fournis : {params}",
|
||||
"TooDeepTransitionRecursion": "Récursion de transition trop profonde"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"LowerCase": "Minuscolo",
|
||||
"Trim": "Taglia",
|
||||
"FirstValue": "Primo valore",
|
||||
"LastValue": "Ultimo valore",
|
||||
"LastValue": "Ultimo valore",
|
||||
"Random": "Casuale",
|
||||
"AllProcesses": "Tutti i processi",
|
||||
"MyProcesses": "I miei processi",
|
||||
@@ -120,7 +120,9 @@
|
||||
"ExecutionInitiator": "Iniziatore dell'esecuzione",
|
||||
"ExecutionStarted": "Esecuzione avviata",
|
||||
"Filter": "Filtro",
|
||||
"FirstMatchValue": "Primo valore corrispondente"
|
||||
"FirstMatchValue": "Primo valore corrispondente",
|
||||
"ConfigLabel": "Configurazione dei processi",
|
||||
"ConfigDescription": "Definisci i processi per automatizzare i flussi di lavoro e i processi aziendali."
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Metodo non trovato: {methodId}",
|
||||
|
||||
@@ -119,7 +119,9 @@
|
||||
"ExecutionInitiator": "実行の発起人",
|
||||
"ExecutionStarted": "実行が開始されました",
|
||||
"Filter": "フィルター",
|
||||
"FirstMatchValue": "最初の一致する値"
|
||||
"FirstMatchValue": "最初の一致する値",
|
||||
"ConfigLabel": "プロセス構成",
|
||||
"ConfigDescription": "ワークフローやビジネスプロセスを自動化するためのプロセスを定義します。"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "メソッドが見つかりません: {methodId}",
|
||||
|
||||
@@ -120,7 +120,9 @@
|
||||
"ExecutionInitiator": "Iniciador de Execução",
|
||||
"ExecutionStarted": "Execução Iniciada",
|
||||
"Filter": "Filtrar",
|
||||
"FirstMatchValue": "Primeiro Valor Correspondente"
|
||||
"FirstMatchValue": "Primeiro Valor Correspondente",
|
||||
"ConfigLabel": "Configuração de processos",
|
||||
"ConfigDescription": "Defina processos para automatizar fluxos de trabalho e processos de negócios."
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Método não encontrado: {methodId}",
|
||||
|
||||
@@ -120,7 +120,9 @@
|
||||
"ExecutionInitiator": "Инициатор выполнения",
|
||||
"ExecutionStarted": "Выполнение начато",
|
||||
"Filter": "Фильтр",
|
||||
"FirstMatchValue": "Первое подходящее значение"
|
||||
"FirstMatchValue": "Первое подходящее значение",
|
||||
"ConfigLabel": "Конфигурация процессов",
|
||||
"ConfigDescription": "Определяйте процессы для автоматизации рабочих процессов и бизнес-процессов."
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Метод не найден: {methodId}",
|
||||
|
||||
@@ -117,7 +117,9 @@
|
||||
"ProcessFinished": "\"{process}\" süreci \"{state}\" durumunda tamamlandı",
|
||||
"NewProcessToDo": "Yeni süreç Eylem öğesi",
|
||||
"Filter": "Filtre",
|
||||
"FirstMatchValue": "İlk eşleşen değer"
|
||||
"FirstMatchValue": "İlk eşleşen değer",
|
||||
"ConfigLabel": "Süreç yapılandırması",
|
||||
"ConfigDescription": "İş akışlarını ve iş süreçlerini otomatikleştirmek için süreçler tanımlayın."
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Metod bulunamadı: {methodId}",
|
||||
|
||||
@@ -120,7 +120,9 @@
|
||||
"ExecutionInitiator": "执行发起者",
|
||||
"ExecutionStarted": "执行已启动",
|
||||
"Filter": "过滤",
|
||||
"FirstMatchValue": "第一个匹配值"
|
||||
"FirstMatchValue": "第一个匹配值",
|
||||
"ConfigLabel": "流程配置",
|
||||
"ConfigDescription": "定义流程以自动化工作流和业务流程。"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "找不到方法:{methodId}",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/assets/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<symbol id="rating" viewBox="0 0 90 90">
|
||||
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;">
|
||||
<path d="M 45 0.24 L 58.916 24.19 c 0.092 0.185 0.268 0.313 0.472 0.343 l 24.357 3.539 c 1.279 0.186 2.322 1.065 2.721 2.294 c 0.4 1.23 0.073 2.554 -0.853 3.456 l -17.624 17.18 c -0.148 0.144 -0.215 0.351 -0.181 0.554 l 4.161 24.258 c 0.218 1.274 -0.295 2.538 -1.341 3.297 c -1.045 0.76 -2.405 0.858 -3.552 0.258 L 45.291 67.916 C 45.1 67.822 44.9 67.822 44.709 67.916 L 22.923 79.371 c -1.147 0.6 -2.507 0.502 -3.552 -0.258 c -1.046 -0.759 -1.559 -2.023 -1.341 -3.297 l 4.161 -24.258 c 0.034 -0.203 -0.033 -0.41 -0.181 -0.554 L 4.387 33.824 c -0.926 -0.902 -1.253 -2.226 -0.853 -3.456 c 0.399 -1.229 1.442 -2.108 2.721 -2.294 l 24.357 -3.539 c 0.204 -0.03 0.38 -0.158 0.472 -0.343 L 41.977 2.119 C 42.549 0.96 43.707 0.24 45 0.24 z" style="stroke: currentColor; stroke-width: 5; stroke-dasharray: none; stroke-linecap: round; stroke-linejoin: round; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) "/>
|
||||
</g>
|
||||
</symbol>
|
||||
|
||||
<symbol id="star-yellow" viewBox="0 0 256 256">
|
||||
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(3.2 3.2)">
|
||||
<path d="M 45 0.24 C 45 0.24 45 0.24 45 0.24 c -1.293 0 -2.451 0.72 -3.023 1.879 L 31.084 24.19 c -0.092 0.185 -0.268 0.313 -0.472 0.343 L 6.255 28.073 c -1.279 0.186 -2.322 1.065 -2.721 2.294 c -0.4 1.23 -0.073 2.554 0.853 3.456 l 17.624 17.18 c 0.148 0.144 0.215 0.351 0.181 0.554 l -4.161 24.258 c -0.218 1.274 0.295 2.538 1.341 3.297 c 1.045 0.76 2.405 0.858 3.552 0.258 l 21.786 -11.455 C 44.8 67.869 44.9 67.845 45 67.845 C 51.218 48.239 49.905 25.085 45 0.24 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(255,200,80); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
||||
<path d="M 45 0.24 C 45 0.24 45 0.24 45 0.24 c 1.293 0 2.451 0.72 3.023 1.879 L 58.916 24.19 c 0.092 0.185 0.268 0.313 0.472 0.343 l 24.357 3.539 c 1.279 0.186 2.322 1.065 2.721 2.294 c 0.4 1.23 0.073 2.554 -0.853 3.456 l -17.624 17.18 c -0.148 0.144 -0.215 0.351 -0.181 0.554 l 4.161 24.258 c 0.218 1.274 -0.295 2.538 -1.341 3.297 c -1.045 0.76 -2.405 0.858 -3.552 0.258 L 45.291 67.916 C 45.2 67.869 45.1 67.845 45 67.845 C 37.673 48.876 39.309 25.564 45 0.24 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(255,220,100); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
||||
</g>
|
||||
</symbol>
|
||||
|
||||
<symbol id="star-green" viewBox="0 0 256 256">
|
||||
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(3.2 3.2)">
|
||||
<path d="M 45 0.24 C 45 0.24 45 0.24 45 0.24 c -1.293 0 -2.451 0.72 -3.023 1.879 L 31.084 24.19 c -0.092 0.185 -0.268 0.313 -0.472 0.343 L 6.255 28.073 c -1.279 0.186 -2.322 1.065 -2.721 2.294 c -0.4 1.23 -0.073 2.554 0.853 3.456 l 17.624 17.18 c 0.148 0.144 0.215 0.351 0.181 0.554 l -4.161 24.258 c -0.218 1.274 0.295 2.538 1.341 3.297 c 1.045 0.76 2.405 0.858 3.552 0.258 l 21.786 -11.455 C 44.8 67.869 44.9 67.845 45 67.845 C 51.218 48.239 49.905 25.085 45 0.24 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(80,200,120); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
||||
<path d="M 45 0.24 C 45 0.24 45 0.24 45 0.24 c 1.293 0 2.451 0.72 3.023 1.879 L 58.916 24.19 c 0.092 0.185 0.268 0.313 0.472 0.343 l 24.357 3.539 c 1.279 0.186 2.322 1.065 2.721 2.294 c 0.4 1.23 0.073 2.554 -0.853 3.456 l -17.624 17.18 c -0.148 0.144 -0.215 0.351 -0.181 0.554 l 4.161 24.258 c 0.218 1.274 -0.295 2.538 -1.341 3.297 c -1.045 0.76 -2.405 0.858 -3.552 0.258 L 45.291 67.916 C 45.2 67.869 45.1 67.845 45 67.845 C 37.673 48.876 39.309 25.564 45 0.24 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(100,220,140); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
||||
</g>
|
||||
</symbol>
|
||||
|
||||
<symbol id="star-red" viewBox="0 0 256 256">
|
||||
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(3.2 3.2)">
|
||||
<path d="M 45 0.24 C 45 0.24 45 0.24 45 0.24 c -1.293 0 -2.451 0.72 -3.023 1.879 L 31.084 24.19 c -0.092 0.185 -0.268 0.313 -0.472 0.343 L 6.255 28.073 c -1.279 0.186 -2.322 1.065 -2.721 2.294 c -0.4 1.23 -0.073 2.554 0.853 3.456 l 17.624 17.18 c 0.148 0.144 0.215 0.351 0.181 0.554 l -4.161 24.258 c -0.218 1.274 0.295 2.538 1.341 3.297 c 1.045 0.76 2.405 0.858 3.552 0.258 l 21.786 -11.455 C 44.8 67.869 44.9 67.845 45 67.845 C 51.218 48.239 49.905 25.085 45 0.24 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(220,80,80); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
||||
<path d="M 45 0.24 C 45 0.24 45 0.24 45 0.24 c 1.293 0 2.451 0.72 3.023 1.879 L 58.916 24.19 c 0.092 0.185 0.268 0.313 0.472 0.343 l 24.357 3.539 c 1.279 0.186 2.322 1.065 2.721 2.294 c 0.4 1.23 0.073 2.554 -0.853 3.456 l -17.624 17.18 c -0.148 0.144 -0.215 0.351 -0.181 0.554 l 4.161 24.258 c 0.218 1.274 -0.295 2.538 -1.341 3.297 c -1.045 0.76 -2.405 0.858 -3.552 0.258 L 45.291 67.916 C 45.2 67.869 45.1 67.845 45 67.845 C 37.673 48.876 39.309 25.564 45 0.24 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(255,100,100); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
||||
</g>
|
||||
</symbol>
|
||||
|
||||
<symbol id="star-blue" viewBox="0 0 256 256">
|
||||
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(3.2 3.2)">
|
||||
<path d="M 45 0.24 C 45 0.24 45 0.24 45 0.24 c -1.293 0 -2.451 0.72 -3.023 1.879 L 31.084 24.19 c -0.092 0.185 -0.268 0.313 -0.472 0.343 L 6.255 28.073 c -1.279 0.186 -2.322 1.065 -2.721 2.294 c -0.4 1.23 -0.073 2.554 0.853 3.456 l 17.624 17.18 c 0.148 0.144 0.215 0.351 0.181 0.554 l -4.161 24.258 c -0.218 1.274 0.295 2.538 1.341 3.297 c 1.045 0.76 2.405 0.858 3.552 0.258 l 21.786 -11.455 C 44.8 67.869 44.9 67.845 45 67.845 C 51.218 48.239 49.905 25.085 45 0.24 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(80,150,220); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
||||
<path d="M 45 0.24 C 45 0.24 45 0.24 45 0.24 c 1.293 0 2.451 0.72 3.023 1.879 L 58.916 24.19 c 0.092 0.185 0.268 0.313 0.472 0.343 l 24.357 3.539 c 1.279 0.186 2.322 1.065 2.721 2.294 c 0.4 1.23 0.073 2.554 -0.853 3.456 l -17.624 17.18 c -0.148 0.144 -0.215 0.351 -0.181 0.554 l 4.161 24.258 c 0.218 1.274 -0.295 2.538 -1.341 3.297 c -1.045 0.76 -2.405 0.858 -3.552 0.258 L 45.291 67.916 C 45.2 67.869 45.1 67.845 45 67.845 C 37.673 48.876 39.309 25.564 45 0.24 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(100,180,255); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
||||
</g>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.2 KiB |
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "assets"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"string": {
|
||||
"Rating": "Hodnocení",
|
||||
"RatingWidgetEmpty": "Zatím nejsou žádné označené dokumenty. Chcete-li zde zobrazit položky, přidejte hvězdu k dokumentu (klikněte na reakci hvězdy u libovolného dokumentu).",
|
||||
"RatingWidget": "Widget hodnocení",
|
||||
"Level": "Úroveň",
|
||||
"MonthOps": "{month}: {ops} operací",
|
||||
"MonthNoOps": "{month}, žádné operace",
|
||||
"AddStar": "Přidat hvězdu",
|
||||
"AddStarAria": "Přidat reakci hvězdy"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"string": {
|
||||
"Rating": "Bewertung",
|
||||
"RatingWidgetEmpty": "Noch keine markierten Dokumente. Um Elemente hier zu sehen, füge einem Dokument einen Stern hinzu (klicke auf die Stern-Reaktion bei einem Dokument).",
|
||||
"RatingWidget": "Bewertungs-Widget",
|
||||
"Level": "Level",
|
||||
"MonthOps": "{month}: {ops} Vorgänge",
|
||||
"MonthNoOps": "{month}, keine Vorgänge",
|
||||
"AddStar": "Stern hinzufügen",
|
||||
"AddStarAria": "Stern-Reaktion hinzufügen"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"string": {
|
||||
"Rating": "Rating",
|
||||
"RatingWidget": "Rating widget",
|
||||
"RatingWidgetEmpty": "No starred documents yet. To see items here, add a star to a document (click the star reaction on any document).",
|
||||
"Level": "Level",
|
||||
"MonthOps": "{month}: {ops} ops",
|
||||
"MonthNoOps": "{month}, No ops",
|
||||
"AddStar": "Add star",
|
||||
"AddStarAria": "Add star reaction"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"string": {
|
||||
"Rating": "Calificación",
|
||||
"RatingWidget": "Widget de calificación",
|
||||
"Level": "Nivel",
|
||||
"RatingWidgetEmpty": "Aún no hay documentos con estrella. Para ver elementos aquí, añade una estrella a un documento (haz clic en la reacción de estrella en cualquier documento).",
|
||||
"MonthOps": "{month}: {ops} operaciones",
|
||||
"MonthNoOps": "{month}, sin operaciones",
|
||||
"AddStar": "Agregar estrella",
|
||||
"AddStarAria": "Agregar reacción de estrella"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"string": {
|
||||
"Rating": "Note",
|
||||
"RatingWidgetEmpty": "Aucun document marqué d'une étoile pour le moment. Pour afficher des éléments ici, ajoutez une étoile à un document (cliquez sur la réaction étoile sur un document).",
|
||||
"RatingWidget": "Widget de notation",
|
||||
"Level": "Niveau",
|
||||
"MonthOps": "{month} : {ops} opérations",
|
||||
"MonthNoOps": "{month}, aucune opération",
|
||||
"AddStar": "Ajouter une étoile",
|
||||
"AddStarAria": "Ajouter une réaction étoile"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"string": {
|
||||
"Rating": "Valutazione",
|
||||
"RatingWidgetEmpty": "Non ci sono ancora documenti contrassegnati con una stella. Per vederli qui, aggiungi una stella a un documento (clicca sulla reazione a stella su un documento).",
|
||||
"RatingWidget": "Widget di valutazione",
|
||||
"Level": "Livello",
|
||||
"MonthOps": "{month}: {ops} operazioni",
|
||||
"MonthNoOps": "{month}, nessuna operazione",
|
||||
"AddStar": "Aggiungi stella",
|
||||
"AddStarAria": "Aggiungi reazione stella"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"string": {
|
||||
"Rating": "評価",
|
||||
"RatingWidgetEmpty": "まだスターが付いたドキュメントはありません。ここに表示するには、ドキュメントにスターを追加してください(ドキュメントのスター反応をクリックしてください)。",
|
||||
"RatingWidget": "評価ウィジェット",
|
||||
"Level": "レベル",
|
||||
"MonthOps": "{month}月: {ops} 操作",
|
||||
"MonthNoOps": "{month}月、操作なし",
|
||||
"AddStar": "スターを追加",
|
||||
"AddStarAria": "スターの反応を追加"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"string": {
|
||||
"Rating": "Avaliação",
|
||||
"RatingWidgetEmpty": "Ainda não há documentos com estrela. Para ver itens aqui, adicione uma estrela a um documento (clique na reação de estrela em qualquer documento).",
|
||||
"RatingWidget": "Widget de avaliação",
|
||||
"Level": "Nível",
|
||||
"MonthOps": "{month}: {ops} operações",
|
||||
"MonthNoOps": "{month}, sem operações",
|
||||
"AddStar": "Adicionar estrela",
|
||||
"AddStarAria": "Adicionar reação de estrela"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"string": {
|
||||
"Rating": "Рейтинг",
|
||||
"RatingWidgetEmpty": "Пока нет документов со звёздочкой. Чтобы увидеть элементы здесь, добавьте звезду к документу (нажмите на реакцию со звездой в любом документе).",
|
||||
"RatingWidget": "Виджет рейтинга",
|
||||
"Level": "Уровень",
|
||||
"MonthOps": "{month}: {ops} операций",
|
||||
"MonthNoOps": "{month}, операций нет",
|
||||
"AddStar": "Добавить звезду",
|
||||
"AddStarAria": "Добавить реакцию со звездой"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"string": {
|
||||
"Rating": "Puan",
|
||||
"RatingWidgetEmpty": "Henüz yıldızlı belge yok. Burada öğeleri görmek için bir belgeye yıldız ekleyin (herhangi bir belgedeki yıldız tepkisine tıklayın).",
|
||||
"RatingWidget": "Puan aracı",
|
||||
"Level": "Seviye",
|
||||
"MonthOps": "{month}: {ops} işlem",
|
||||
"MonthNoOps": "{month}, işlem yok",
|
||||
"AddStar": "Yıldız ekle",
|
||||
"AddStarAria": "Yıldız tepkisi ekle"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"string": {
|
||||
"Rating": "评分",
|
||||
"RatingWidgetEmpty": "尚无加星的文档。要在此处查看项目,请为文档添加星标(在任何文档上点击星形反应)。",
|
||||
"RatingWidget": "评分组件",
|
||||
"Level": "等级",
|
||||
"MonthOps": "{month}月:{ops} 操作",
|
||||
"MonthNoOps": "{month}月,无操作",
|
||||
"AddStar": "添加星标",
|
||||
"AddStarAria": "添加星标反应"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"name": "@hcengineering/rating-assets",
|
||||
"version": "0.7.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./types/index.d.ts",
|
||||
"files": [
|
||||
"lib",
|
||||
"types",
|
||||
"src",
|
||||
"assets",
|
||||
"lang"
|
||||
],
|
||||
"author": "Hardcore Engineering Inc.",
|
||||
"template": "@hcengineering/assets-package",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"test": "jest --passWithNoTests --silent",
|
||||
"build:docs": "",
|
||||
"format": "format src",
|
||||
"build:watch": "compile",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:test": "jest --passWithNoTests --silent",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.7.19",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint": "^8.54.0",
|
||||
"prettier": "^3.6.2",
|
||||
"@types/node": "^22.15.29",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.7.5",
|
||||
"@hcengineering/rating": "^0.7.0"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"./lang/*.json": {
|
||||
"require": "./lang/*.json",
|
||||
"import": "./lang/*.json"
|
||||
},
|
||||
"./lang": {
|
||||
"require": "./lang",
|
||||
"import": "./lang"
|
||||
},
|
||||
"./assets": {
|
||||
"require": "./assets",
|
||||
"import": "./assets"
|
||||
},
|
||||
"./assets/*.svg": {
|
||||
"require": "./assets/*.svg",
|
||||
"import": "./assets/*.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { makeLocalesTest } from '@hcengineering/platform'
|
||||
|
||||
it(
|
||||
'Locales are equale',
|
||||
makeLocalesTest((lang) => import(`../../lang/${lang}.json`))
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import rating from '@hcengineering/rating'
|
||||
import { loadMetadata } from '@hcengineering/platform'
|
||||
|
||||
const ratingIcon = require('../assets/icon.svg') as string // eslint-disable-line
|
||||
loadMetadata(rating.icon, {
|
||||
Rating: `${ratingIcon}#rating`,
|
||||
StarYellow: `${ratingIcon}#star-yellow`,
|
||||
StarGreen: `${ratingIcon}#star-green`,
|
||||
StarBlue: `${ratingIcon}#star-blue`,
|
||||
StarRed: `${ratingIcon}#star-red`
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/assets/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"types": ["node", "jest"],
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "lib", "dist", "types", "bundle"]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/eslint.config.json'],
|
||||
parserOptions: { tsconfigRootDir: __dirname }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"useTabs": false,
|
||||
"bracketSpacing": true,
|
||||
"proseWrap": "preserve",
|
||||
"plugins": [
|
||||
"prettier-plugin-svelte"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "ui"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 36 36" style="enable-background:new 0 0 36 36;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#SVGID_1_);}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_2_);}
|
||||
</style>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="14.3791" y1="-2.5354" x2="21.6209" y2="38.5354">
|
||||
<stop offset="5.076140e-03" style="stop-color:#C52229"/>
|
||||
<stop offset="1" style="stop-color:#802022"/>
|
||||
</linearGradient>
|
||||
<rect class="st0" width="36" height="36"/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="18" y1="6.2743" x2="18" y2="29.7257">
|
||||
<stop offset="0" style="stop-color:#FFFFFF"/>
|
||||
<stop offset="0.5066" style="stop-color:#FDFDFD"/>
|
||||
<stop offset="0.7696" style="stop-color:#F5F5F5"/>
|
||||
<stop offset="0.9763" style="stop-color:#E8E8E8"/>
|
||||
<stop offset="1" style="stop-color:#E6E6E6"/>
|
||||
</linearGradient>
|
||||
<path class="st1" d="M7.3,9.8c2.8-1.1,7.4-1.3,9.4-1.2l1.3,1.6l1.3-1.6c2-0.1,6.6,0.1,9.4,1.2c-0.4,0.7-2.1,1.8-3,2
|
||||
c-0.1-1.4-2.1-1.7-4-1.7L18,29.7l-3.7-19.7c-2,0-4,0.3-4,1.7C9.4,11.6,7.7,10.5,7.3,9.8L7.3,9.8z M6,7.8l0.7,1.1
|
||||
c2.7-1,5.9-1.4,9.3-1.5c1.3,0,2.6,0,3.9,0c3.4,0.1,6.6,0.6,9.3,1.5L30,7.8c-3.2-1.1-6.6-1.4-10-1.5c-1.3,0-2.6,0-4,0
|
||||
C12.6,6.4,9.2,6.7,6,7.8L6,7.8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)']
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "@hcengineering/rating-resources",
|
||||
"version": "0.7.0",
|
||||
"main": "src/index.ts",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "compile ui",
|
||||
"build:docs": "api-extractor run --local",
|
||||
"format": "format src",
|
||||
"svelte-check": "do-svelte-check",
|
||||
"_phase:svelte-check": "do-svelte-check",
|
||||
"build:watch": "compile ui",
|
||||
"_phase:build": "compile ui",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.7.19",
|
||||
"svelte-loader": "^3.2.0",
|
||||
"sass": "^1.80.0",
|
||||
"svelte-preprocess": "^5.1.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"prettier": "^3.6.2",
|
||||
"svelte-check": "^3.6.9",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"svelte-eslint-parser": "^0.33.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/analytics": "^0.7.5",
|
||||
"@hcengineering/platform": "^0.7.5",
|
||||
"svelte": "^4.2.20",
|
||||
"@hcengineering/rating": "^0.7.0",
|
||||
"@hcengineering/ui": "^0.7.0",
|
||||
"@hcengineering/presentation": "^0.7.0",
|
||||
"@hcengineering/core": "^0.7.10",
|
||||
"@hcengineering/view": "^0.7.0",
|
||||
"@hcengineering/emoji": "^0.7.0",
|
||||
"@hcengineering/emoji-resources": "^0.7.0",
|
||||
"@hcengineering/contact": "^0.7.0",
|
||||
"@hcengineering/contact-resources": "^0.7.0",
|
||||
"@hcengineering/view-resources": "^0.7.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('autoprefixer')
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import ratingPlugin, { DocReaction, ReactionKind } from '@hcengineering/rating'
|
||||
import { Icon } from '@hcengineering/ui'
|
||||
import ReactionPresenter from './ReactionPresenter.svelte'
|
||||
|
||||
export let value: DocReaction | null = null
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
{#if value.reactionType === ReactionKind.Star}
|
||||
<Icon icon={ratingPlugin.icon.StarYellow} size={'small'} />
|
||||
{:else}
|
||||
<ReactionPresenter emoji={value?.emoji ?? ''} />
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -0,0 +1,53 @@
|
||||
<!--
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Ref, type Class, type Doc } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { FixedColumn, ObjectPresenter } from '@hcengineering/view-resources'
|
||||
import RatingEditor from './RatingEditor.svelte'
|
||||
import type { DocReaction } from '@hcengineering/rating'
|
||||
|
||||
export let _class: Class<Doc>
|
||||
export let docs: { _id: Ref<Doc>, reactions: DocReaction[] }[] = []
|
||||
|
||||
const query = createQuery()
|
||||
const limit: number = 20
|
||||
|
||||
let objects: { doc: Doc, reactions: DocReaction[] }[] = []
|
||||
|
||||
$: query.query(
|
||||
_class._id,
|
||||
{ _id: { $in: docs.map((it) => it._id) } },
|
||||
(res) => {
|
||||
objects = res.map((doc) => ({
|
||||
doc,
|
||||
reactions: docs.find((it) => it._id === doc._id)?.reactions ?? []
|
||||
}))
|
||||
},
|
||||
{ limit, total: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="ml-2 flex-col">
|
||||
{#each objects as doc (doc.doc._id)}
|
||||
<div class="flex-presenter p-1 flex flex-between">
|
||||
<FixedColumn key={'reactions_link'}>
|
||||
<ObjectPresenter _class={doc.doc._class} objectId={doc.doc._id} value={doc.doc} noUnderline />
|
||||
</FixedColumn>
|
||||
<RatingEditor _class={doc.doc._class} _id={doc.doc._id} reactions={doc.reactions} showMy={false} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -0,0 +1,120 @@
|
||||
<script lang="ts">
|
||||
import type { PersonRating } from '@hcengineering/rating'
|
||||
|
||||
export let rating: PersonRating | undefined
|
||||
|
||||
// Sorted months
|
||||
$: months = [...(rating?.months ?? [])].sort((a, b) => a[0] - b[0])
|
||||
|
||||
$: fullYears = Array.from(new Set(months.map((it) => Math.floor(it[0] / 100))))
|
||||
|
||||
$: ymin = Math.min(...fullYears)
|
||||
$: ymax = Math.max(...fullYears)
|
||||
|
||||
const monthMax: number = 500
|
||||
|
||||
// Color scale: 0 = #ebedf0, low = #c6e48b, mid = #7bc96f, high = #239a3b, max = #196127
|
||||
function getColor (val: number, max: number): string {
|
||||
max = Math.max(max, monthMax)
|
||||
const COLORS = [
|
||||
'transparent',
|
||||
'#c6e48b',
|
||||
'#a0d16e',
|
||||
'#8ec760',
|
||||
'#7bc96f',
|
||||
'#6bb85e',
|
||||
'#5ba94d',
|
||||
'#4b9a3c',
|
||||
'#3b8b2b',
|
||||
'#2b7c1a',
|
||||
'#239a3b',
|
||||
'#1f7a2c',
|
||||
'#1b7820',
|
||||
'#177614',
|
||||
'#156c11',
|
||||
'#196127',
|
||||
'#144f0e'
|
||||
]
|
||||
if (val <= 0) return COLORS[0]
|
||||
const idx = 1 + Math.min(COLORS.length - 2, Math.floor((val / max) * (COLORS.length - 1)))
|
||||
return COLORS[idx]
|
||||
}
|
||||
|
||||
function getMax (year: number, rating?: PersonRating): number {
|
||||
const monthData = rating?.months?.filter((it) => Math.floor(it[0] / 100) === year)
|
||||
if (monthData === undefined) return monthMax
|
||||
return Math.max(...monthData.map((val) => (val[1] ?? 0) + (val[2] ?? 0) + (val[3] ?? 0)))
|
||||
}
|
||||
|
||||
import ratingPlugin from '@hcengineering/rating'
|
||||
import { tooltip } from '@hcengineering/ui'
|
||||
|
||||
// Formatter for localized month names (short form, e.g. 'Jan', 'Feb' or localized equivalent)
|
||||
const monthFormatter = new Intl.DateTimeFormat(undefined, { month: 'short' })
|
||||
</script>
|
||||
|
||||
{#if rating}
|
||||
<div class="flex flex-col">
|
||||
<div class="rating-activities">
|
||||
<!-- Monthly grid -->
|
||||
{#each Array.from({ length: ymax - ymin + 1 }, (_, i) => i + ymin) as year}
|
||||
{@const ymonths = months.filter((it) => Math.floor(it[0] / 100) === year)}
|
||||
<div class="flex-row flex flex-col p-1">
|
||||
<div class="flex flex-grow justify-center">
|
||||
{year}
|
||||
</div>
|
||||
<div class="contribution-grid">
|
||||
{#each Array.from({ length: 12 }, (_, i) => i) as m, i}
|
||||
{@const monthval = year * 100 + m}
|
||||
{@const yval = ymonths.find((it) => it[0] === monthval)}
|
||||
{#if yval}
|
||||
{@const ops = (yval[1] ?? 0) + (yval[2] ?? 0) + (yval[3] ?? 0)}
|
||||
{@const monthLabel = monthFormatter.format(new Date(year, i, 1))}
|
||||
<div
|
||||
class="contribution-cell"
|
||||
use:tooltip={{ label: ratingPlugin.string.MonthOps, props: { month: monthLabel, ops: String(ops) } }}
|
||||
style="background: {getColor(ops, getMax(year, rating))}"
|
||||
></div>
|
||||
{:else}
|
||||
{@const monthLabel = monthFormatter.format(new Date(year, i, 1))}
|
||||
<div
|
||||
class="contribution-cell"
|
||||
use:tooltip={{ label: ratingPlugin.string.MonthNoOps, props: { month: monthLabel } }}
|
||||
style="background: {getColor(0, monthMax)}"
|
||||
></div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.rating-activities {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 2px;
|
||||
margin-bottom: 1em;
|
||||
max-width: fit-content;
|
||||
max-height: fit-content;
|
||||
}
|
||||
.contribution-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 2px;
|
||||
margin-bottom: 1em;
|
||||
max-width: fit-content;
|
||||
max-height: fit-content;
|
||||
}
|
||||
.contribution-cell {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #e0e0e0;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,259 @@
|
||||
<script lang="ts">
|
||||
import type { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import core, { getCurrentAccount, groupByArray } from '@hcengineering/core'
|
||||
import emojiPlugin from '@hcengineering/emoji'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import type { DocReaction } from '@hcengineering/rating'
|
||||
import ratingPlugin, { ReactionKind } from '@hcengineering/rating'
|
||||
import { Icon, showPopup, tooltip } from '@hcengineering/ui'
|
||||
import ReactionPresenter from './ReactionPresenter.svelte'
|
||||
import { translateCB } from '@hcengineering/platform'
|
||||
|
||||
export let _id: Ref<Doc>
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let showMy = true
|
||||
|
||||
export let reactions: DocReaction[] = []
|
||||
|
||||
let _reactions = reactions
|
||||
|
||||
// Compute reaction groups
|
||||
$: starReactions = _reactions.filter((r) => r.reactionType === ReactionKind.Star)
|
||||
$: otherReactions = groupByArray(
|
||||
_reactions.filter((r) => r.reactionType === ReactionKind.Emoji),
|
||||
(it) => it.emoji ?? ''
|
||||
).entries()
|
||||
$: starCount = starReactions.filter((it) => it.value === 1).length
|
||||
|
||||
const account = getCurrentAccount()
|
||||
|
||||
const socialIds = new Set(account.fullSocialIds.map((it) => it._id))
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const reactionsQuery = createQuery()
|
||||
$: if (reactions.length === 0) {
|
||||
reactionsQuery.query(ratingPlugin.class.DocReaction, { attachedTo: _id, attachedToClass: _class }, (result) => {
|
||||
_reactions = result
|
||||
})
|
||||
} else {
|
||||
reactionsQuery.unsubscribe()
|
||||
_reactions = reactions
|
||||
}
|
||||
|
||||
async function addStarReaction (): Promise<void> {
|
||||
const existing = _reactions.filter((it) => it.reactionType === ReactionKind.Star && socialIds.has(it.modifiedBy))
|
||||
if (existing.length > 0) {
|
||||
for (const e of existing) {
|
||||
if (e.value === 0) {
|
||||
await client.update(e, { value: 1 })
|
||||
} else {
|
||||
await client.update(e, { value: 0 })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await client.addCollection(ratingPlugin.class.DocReaction, core.space.Workspace, _id, _class, '_reactions', {
|
||||
reactionType: ReactionKind.Star,
|
||||
value: 1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function addEmojiReaction (event: MouseEvent): Promise<void> {
|
||||
showPopup(emojiPlugin.component.EmojiPopup, {}, event.target as HTMLElement, async (emoji) => {
|
||||
if (emoji?.text === undefined) return
|
||||
|
||||
const existing = _reactions.filter(
|
||||
(it) => it.reactionType === ReactionKind.Emoji && it.emoji === emoji.text && socialIds.has(it.modifiedBy)
|
||||
)
|
||||
if (existing.length > 0) {
|
||||
for (const e of existing) {
|
||||
await client.remove(e)
|
||||
}
|
||||
} else {
|
||||
await client.addCollection(ratingPlugin.class.DocReaction, core.space.Workspace, _id, _class, '_reactions', {
|
||||
reactionType: ReactionKind.Emoji,
|
||||
value: 0,
|
||||
emoji: emoji.text,
|
||||
image: emoji.image
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function removeReaction (reactions: DocReaction[]): Promise<void> {
|
||||
for (const reaction of reactions) {
|
||||
if (account.fullSocialIds.some((it) => it._id === reaction.modifiedBy)) {
|
||||
await client.removeDoc(reaction._class, reaction.space, reaction._id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: hasSelectedStar = _reactions.some(
|
||||
(it) => it.reactionType === ReactionKind.Star && socialIds.has(it.createdBy ?? it.modifiedBy) && it.value === 1
|
||||
)
|
||||
|
||||
let addStarAria: string | undefined = undefined
|
||||
$: translateCB(ratingPlugin.string.AddStarAria, {}, undefined, (r) => (addStarAria = r))
|
||||
</script>
|
||||
|
||||
<div class="rating-editor">
|
||||
<!-- Inline Reactions -->
|
||||
<div class="reactions-container">
|
||||
<!-- Star Reactions (Premium) -->
|
||||
<button
|
||||
class="star-reaction"
|
||||
on:click={addStarReaction}
|
||||
use:tooltip={{ label: ratingPlugin.string.AddStar }}
|
||||
aria-label={addStarAria}
|
||||
>
|
||||
<span class="star-icon">
|
||||
<Icon icon={hasSelectedStar ? ratingPlugin.icon.StarYellow : ratingPlugin.icon.Rating} size={'small'} />
|
||||
</span>
|
||||
{#if showMy && starCount > 0}
|
||||
<span class="star-count">{starCount}</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<!-- Other Emoji Reactions -->
|
||||
{#each otherReactions as oreact (oreact[0])}
|
||||
{@const emoji = oreact[1][0].emoji}
|
||||
{@const data = oreact[1]}
|
||||
|
||||
<ReactionPresenter
|
||||
{emoji}
|
||||
selected={showMy && data.some((it) => socialIds.has(it.modifiedBy))}
|
||||
socialIds={data.map((it) => it.modifiedBy)}
|
||||
count={showMy ? data.length : 0}
|
||||
on:click={() => removeReaction(oreact[1])}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<!-- Add Emoji Reaction Button -->
|
||||
<ReactionPresenter icon={emojiPlugin.icon.EmojiAdd} iconSize="small" active={true} on:click={addEmojiReaction} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.rating-editor {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.15rem;
|
||||
padding: 0.1rem;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.reactions-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.reaction-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.25rem;
|
||||
background: transparent;
|
||||
border: 1px solid var(--button-secondary-BorderColor);
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
font-size: 0.9rem;
|
||||
flex-shrink: 0;
|
||||
min-height: 1.5rem;
|
||||
gap: 0.25rem;
|
||||
|
||||
&:hover {
|
||||
background: var(--button-secondary-hover-BackgroundColor);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.star-reaction {
|
||||
@extend .reaction-btn;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.star-count {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
color: var(--global-primary-TextColor);
|
||||
min-width: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reaction-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
}
|
||||
|
||||
.reaction-emoji {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.reaction-remove {
|
||||
position: absolute;
|
||||
top: -0.35rem;
|
||||
right: -0.35rem;
|
||||
width: 0.9rem;
|
||||
height: 0.9rem;
|
||||
padding: 0;
|
||||
background: #ff4444;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
font-size: 0.5rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.1s ease;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.15);
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.add-reaction-btn {
|
||||
@extend .reaction-btn;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<!-- Circular Level Display with Progress -->
|
||||
<script lang="ts">
|
||||
import { translateCB } from '@hcengineering/platform'
|
||||
import ratingPlugin, { getLevelInfo } from '@hcengineering/rating'
|
||||
|
||||
export let rating: number = 0
|
||||
export let showValues = false
|
||||
|
||||
$: levelInfo = getLevelInfo(rating)
|
||||
|
||||
let levelLabel: string | undefined = undefined
|
||||
$: translateCB(ratingPlugin.string.Level, {}, undefined, (r) => (levelLabel = r))
|
||||
</script>
|
||||
|
||||
{#if rating > 0}
|
||||
<div class="flex-row-center">
|
||||
{#if levelLabel}{levelLabel}{/if}
|
||||
<div class="level-circle">
|
||||
<svg class="progress-ring" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Background circle -->
|
||||
<circle cx="60" cy="60" r="50" class="progress-ring-bg" />
|
||||
<!-- Progress circle (bold overlay) -->
|
||||
<circle
|
||||
cx="60"
|
||||
cy="60"
|
||||
r="50"
|
||||
class="progress-ring-fill"
|
||||
style="stroke-dashoffset: {314.16 * (1 - levelInfo.progress / 100)}px"
|
||||
/>
|
||||
</svg>
|
||||
<div class="level-content">
|
||||
<span class="level-number">{levelInfo.level}</span>
|
||||
</div>
|
||||
</div>
|
||||
{#if showValues}
|
||||
<span class="text-sm caption-color">({Math.round(rating * 100)}/{Math.round(levelInfo.nextThreshold * 100)})</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.level-circle {
|
||||
position: relative;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.progress-ring {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.progress-ring-bg {
|
||||
fill: none;
|
||||
stroke: var(--global-secondary-TextColor);
|
||||
stroke-width: 1.5;
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
.progress-ring-fill {
|
||||
fill: none;
|
||||
stroke: #ffc107;
|
||||
stroke-width: 3.5;
|
||||
stroke-linecap: round;
|
||||
transition: stroke-dashoffset 0.3s ease;
|
||||
stroke-dasharray: 314.16;
|
||||
|
||||
&:hover {
|
||||
filter: drop-shadow(0 0 0.2rem rgba(255, 193, 7, 0.7));
|
||||
}
|
||||
}
|
||||
|
||||
.level-content {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.level-number {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
color: var(--global-primary-TextColor);
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,87 @@
|
||||
<script lang="ts">
|
||||
import { getCurrentAccount, groupByArray, SortingOrder, type Class, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import rating, { ReactionKind, type DocReaction } from '@hcengineering/rating'
|
||||
import { NavGroup, Label } from '@hcengineering/ui'
|
||||
import ScrollBox from '@hcengineering/ui/src/components/ScrollBox.svelte'
|
||||
import NavigatorRating from './NavigatorRating.svelte'
|
||||
|
||||
const current = getCurrentAccount()
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
let reactions: DocReaction[] = []
|
||||
|
||||
query.query(
|
||||
rating.class.DocReaction,
|
||||
{
|
||||
createdBy: { $in: current.socialIds }
|
||||
},
|
||||
(results) => {
|
||||
reactions = results
|
||||
},
|
||||
{ limit: 5000, sort: { modifiedOn: SortingOrder.Descending } }
|
||||
)
|
||||
|
||||
function groupReactions (reactions: DocReaction[]): {
|
||||
_class: Ref<Class<Doc>>
|
||||
objects: {
|
||||
_id: Ref<Doc>
|
||||
reactions: DocReaction[]
|
||||
}[]
|
||||
}[] {
|
||||
const starred = new Set(
|
||||
reactions.filter((it) => it.reactionType === ReactionKind.Star && it.value === 1).map((it) => it.attachedTo)
|
||||
)
|
||||
|
||||
const validReactions = reactions.filter(
|
||||
(it) => it.attachedTo != null && it.attachedToClass != null && starred.has(it.attachedTo)
|
||||
)
|
||||
|
||||
// I need to filter reactions without star's
|
||||
|
||||
const byBaseClass = groupByArray(validReactions, (it) => it.attachedToClass)
|
||||
return Array.from(byBaseClass.entries()).map(([baseClass, reactions]) => {
|
||||
const byDoc = Array.from(groupByArray(reactions, (it) => it.attachedTo)).map((it) => ({
|
||||
_id: it[0],
|
||||
reactions: it[1]
|
||||
}))
|
||||
return {
|
||||
_class: baseClass,
|
||||
objects: byDoc
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$: groupped = groupReactions(reactions)
|
||||
|
||||
$: isEmpty = groupped.length === 0 || groupped.every((g) => g.objects.length === 0)
|
||||
</script>
|
||||
|
||||
<div class="mt-2" />
|
||||
{#if isEmpty}
|
||||
<div class="p-4 text-center content-dark-color h-full w-full flex-grow" style:align-content="center">
|
||||
<Label label={rating.string.RatingWidgetEmpty} />
|
||||
</div>
|
||||
{:else}
|
||||
<ScrollBox vertical>
|
||||
{#each groupped as group}
|
||||
{@const classifier = getClient().getHierarchy().findClass(group._class)}
|
||||
{#if classifier !== undefined}
|
||||
<NavGroup
|
||||
_id={group._class}
|
||||
categoryName={'features'}
|
||||
label={classifier.label}
|
||||
type="nested"
|
||||
isFold
|
||||
empty={group.objects.length === 0}
|
||||
noDivider
|
||||
>
|
||||
<div class="ml-2 mr-2">
|
||||
<NavigatorRating _class={classifier} docs={group.objects} />
|
||||
</div>
|
||||
</NavGroup>
|
||||
{/if}
|
||||
{/each}
|
||||
</ScrollBox>
|
||||
{/if}
|
||||
@@ -0,0 +1,84 @@
|
||||
<!-- Copyright © 2025 Hardcore Engineering Inc. -->
|
||||
<!-- -->
|
||||
<!-- Licensed under the Eclipse Public License, Version 2.0 (the "License"); -->
|
||||
<!-- you may not use this file except in compliance with the License. You may -->
|
||||
<!-- obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 -->
|
||||
<!-- -->
|
||||
<!-- Unless required by applicable law or agreed to in writing, software -->
|
||||
<!-- distributed under the License is distributed on an "AS IS" BASIS, -->
|
||||
<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -->
|
||||
<!-- -->
|
||||
<!-- See the License for the specific language governing permissions and -->
|
||||
<!-- limitations under the License. -->
|
||||
|
||||
<script lang="ts">
|
||||
import { type PersonId } from '@hcengineering/core'
|
||||
import { EmojiPresenter } from '@hcengineering/emoji-resources'
|
||||
import { Icon, IconComponent, IconSize, tooltip } from '@hcengineering/ui'
|
||||
|
||||
import ReactionsTooltip from './ReactionsTooltip.svelte'
|
||||
|
||||
export let icon: IconComponent | undefined = undefined
|
||||
export let iconSize: IconSize | undefined = undefined
|
||||
export let emoji: string = ''
|
||||
export let count: number | undefined = undefined
|
||||
export let selected: boolean = false
|
||||
export let active: boolean = false
|
||||
export let socialIds: PersonId[] = []
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="reaction"
|
||||
class:selected
|
||||
class:active
|
||||
on:click
|
||||
use:tooltip={(count ?? 0) > 0 ? { component: ReactionsTooltip, props: { socialIds, emoji } } : undefined}
|
||||
>
|
||||
<div class="reaction__emoji" class:foreground={icon != null}>
|
||||
{#if icon}
|
||||
<Icon {icon} size={iconSize ?? 'small'} />
|
||||
{:else}
|
||||
<EmojiPresenter {emoji} />
|
||||
{/if}
|
||||
</div>
|
||||
{#if count !== undefined && count > 0}
|
||||
<div class="reaction__count">{count}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.reaction {
|
||||
display: flex;
|
||||
height: 1.75rem;
|
||||
padding: 0.375rem;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--selector-BackgroundColor);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
background: var(--global-ui-hover-BackgroundColor);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: var(--global-accent-BackgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
.reaction__emoji {
|
||||
color: var(--global-primary-TextColor);
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.reaction__count {
|
||||
color: var(--global-primary-TextColor);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { PersonId } from '@hcengineering/core'
|
||||
import { ObjectPresenter } from '@hcengineering/view-resources'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { getPersonRefByPersonIdStore } from '@hcengineering/contact-resources'
|
||||
|
||||
export let socialIds: PersonId[] = []
|
||||
|
||||
$: personRefByPersonIdStore = getPersonRefByPersonIdStore(socialIds)
|
||||
$: persons = socialIds.map((si) => $personRefByPersonIdStore.get(si))
|
||||
</script>
|
||||
|
||||
<div class="m-2 flex-col flex-gap-2">
|
||||
{#each persons as person}
|
||||
<ObjectPresenter objectId={person} _class={contact.class.Person} disabled />
|
||||
{/each}
|
||||
</div>
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { type Resources } from '@hcengineering/platform'
|
||||
import RatingEditor from './components/RatingEditor.svelte'
|
||||
import RatingRing from './components/RatingRing.svelte'
|
||||
import RatingWidget from './components/RatingWidget.svelte'
|
||||
import DocReactionPresenter from './components/DocReactionPresenter.svelte'
|
||||
import RatingActivities from './components/RatingActivities.svelte'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
RatingEditor,
|
||||
RatingRing,
|
||||
RatingWidget,
|
||||
DocReactionPresenter,
|
||||
RatingActivities
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import rating, { ratingId } from '@hcengineering/rating'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
export default mergeIds(ratingId, rating, {
|
||||
component: {
|
||||
RatingEditor: '' as AnyComponent
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
const sveltePreprocess = require('svelte-preprocess')
|
||||
|
||||
module.exports = {
|
||||
preprocess: sveltePreprocess()
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/ui/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "lib", "dist", "types", "bundle"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@hcengineering/rating",
|
||||
"version": "0.7.0",
|
||||
"main": "lib/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"files": [
|
||||
"lib/**/*",
|
||||
"types/**/*",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"author": "Copyright © Hardcore Engineering Inc.",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"format": "format src",
|
||||
"test": "jest --passWithNoTests --silent",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:test": "jest --passWithNoTests --silent",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.7.19",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.7.10",
|
||||
"@hcengineering/platform": "^0.7.5",
|
||||
"@hcengineering/ui": "^0.7.0"
|
||||
},
|
||||
"repository": "https://github.com/hcengineering/platform",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {
|
||||
Class,
|
||||
Domain,
|
||||
Ref,
|
||||
type AccountUuid,
|
||||
type AttachedDoc,
|
||||
type Blob,
|
||||
type Doc,
|
||||
type PersonId
|
||||
} from '@hcengineering/core'
|
||||
import { Asset, IntlString, plugin, Plugin } from '@hcengineering/platform'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
|
||||
// [date, created, updated, deleted, messages, todos, hours]
|
||||
export type DateCUD = [/* date */ number, /* created */ number, /* updated */ number, /* deleted */ number]
|
||||
|
||||
// Automatically calculated document rating.
|
||||
export interface DocRating extends AttachedDoc {
|
||||
// A current document rating
|
||||
rating: number
|
||||
|
||||
updates: number // Total number of document updates
|
||||
messages: number // Total number of child messages updates.
|
||||
children: number // A child updates.
|
||||
|
||||
stars: number // A number of stars earned
|
||||
reactions: number // A number of reactions earned
|
||||
}
|
||||
|
||||
export enum ReactionKind {
|
||||
Emoji = 0, // Just an emoji reaction, ❤️, 📌, 😊, 😕, 😠, 🤔
|
||||
Star = 1, // A paid ⭐ 🌟, or paid negative star ✭ ✭, value = 1 => star visible for anyone and used for starred, value = 0, visible for anyone, but not shown in starred.
|
||||
RateValue = 3 // A rating value from 0 to 10.
|
||||
}
|
||||
|
||||
export interface DocReaction extends AttachedDoc {
|
||||
// A reaction type identifier
|
||||
reactionType: ReactionKind
|
||||
value: number // +1, -1, 0..10 etc.
|
||||
|
||||
emoji?: string
|
||||
image?: Ref<Blob>
|
||||
}
|
||||
|
||||
export interface PersonRating extends Doc {
|
||||
accountId: AccountUuid
|
||||
|
||||
// A current person rating
|
||||
rating: number // Could be re calculated from values.
|
||||
|
||||
// Document authoring statistics
|
||||
months: DateCUD[] // Hold per month authoring stats
|
||||
|
||||
// create/update/delete Hold per domain authoring stats
|
||||
stats: Record<string, [number, number, number]>
|
||||
|
||||
socialIds: Record<PersonId, number>
|
||||
|
||||
// A special stats, stars on account and stars on documents, commentas authored by person
|
||||
starsEarned: number // All author document stars.
|
||||
reactionsEarned: number // All author document reactions
|
||||
|
||||
stars: number // Stars spend
|
||||
reactions: number // Reactions created
|
||||
|
||||
rageOperations?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const ratingId = 'rating' as Plugin
|
||||
|
||||
export const DOMAIN_RATING_REACTION = 'rating_reaction' as Domain
|
||||
|
||||
export const DOMAIN_PERSON_RATING = 'rating_person' as Domain
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
const ratingPlugin = plugin(ratingId, {
|
||||
class: {
|
||||
DocRating: '' as Ref<Class<DocRating>>,
|
||||
PersonRating: '' as Ref<Class<PersonRating>>,
|
||||
DocReaction: '' as Ref<Class<DocReaction>>
|
||||
},
|
||||
icon: {
|
||||
Rating: '' as Asset,
|
||||
StarYellow: '' as Asset,
|
||||
StarRed: '' as Asset,
|
||||
StarGreen: '' as Asset,
|
||||
StarBlue: '' as Asset
|
||||
},
|
||||
string: {
|
||||
Rating: '' as IntlString,
|
||||
RatingWidget: '' as IntlString,
|
||||
Level: '' as IntlString,
|
||||
MonthOps: '' as IntlString,
|
||||
MonthNoOps: '' as IntlString,
|
||||
// Shown when the Rating widget has no items to display (empty state)
|
||||
RatingWidgetEmpty: '' as IntlString,
|
||||
AddStar: '' as IntlString,
|
||||
AddStarAria: '' as IntlString
|
||||
},
|
||||
component: {
|
||||
RatingRing: '' as AnyComponent,
|
||||
RatingWidget: '' as AnyComponent,
|
||||
DocReactionPresenter: '' as AnyComponent,
|
||||
RatingActivities: '' as AnyComponent
|
||||
},
|
||||
ids: {
|
||||
RatingWidget: '' as any
|
||||
}
|
||||
})
|
||||
|
||||
export default ratingPlugin
|
||||
|
||||
export * from './utils'
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { PluginConfiguration } from '@hcengineering/core'
|
||||
import type { PersonRating } from '.'
|
||||
|
||||
export interface LevelInfo {
|
||||
level: number
|
||||
progress: number // 0-100 percentage to next level
|
||||
currentThreshold: number
|
||||
nextThreshold: number
|
||||
}
|
||||
|
||||
const BASE_MULTIPLIER = 1.5
|
||||
export function ratingToLevel (rating: number): number {
|
||||
// Convert experience rating to level (1-99) using logarithmic progression
|
||||
// Base: level 1 at rating 0, multiplier 1.5 per level
|
||||
// Level = 1 + log(rating + 1) / log(1.5)
|
||||
|
||||
const level = 1 + Math.log(Math.max(rating, 0) + 1) / Math.log(BASE_MULTIPLIER)
|
||||
|
||||
// Clamp to valid range 1-99
|
||||
return Math.max(1, Math.min(99, Math.floor(level)))
|
||||
}
|
||||
|
||||
export function getLevelInfo (rating: number): LevelInfo {
|
||||
// Calculate current level
|
||||
const currentLevel = ratingToLevel(rating)
|
||||
|
||||
// Calculate rating thresholds for current and next level
|
||||
// Inverse formula: rating = multiplier^(level - 1) - 1
|
||||
const currentThreshold = Math.round((Math.pow(BASE_MULTIPLIER, currentLevel - 1) - 1) * 100) / 100
|
||||
const nextThreshold = Math.round((Math.pow(BASE_MULTIPLIER, currentLevel) - 1) * 100) / 100
|
||||
|
||||
// Calculate progress within current level (0-100)
|
||||
const levelRange = nextThreshold - currentThreshold
|
||||
const progressInLevel = rating - currentThreshold
|
||||
const progress = levelRange > 0 ? Math.min(100, Math.max(0, (progressInLevel / levelRange) * 100)) : 0
|
||||
|
||||
return {
|
||||
level: currentLevel,
|
||||
progress,
|
||||
currentThreshold,
|
||||
nextThreshold
|
||||
}
|
||||
}
|
||||
|
||||
export function getRaiting (total: number, rating: PersonRating | undefined, list: PluginConfiguration[]): number {
|
||||
if (rating?.stats == null || total === 0) return 0
|
||||
let result = 0
|
||||
for (const name of list) {
|
||||
const st = rating.stats[name.pluginId]
|
||||
if (st === undefined) {
|
||||
continue
|
||||
}
|
||||
result += ((st[0] + st[1] + st[2]) / total) * 100
|
||||
}
|
||||
return Math.round(result * 100) / 100
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "lib", "dist", "types", "bundle"]
|
||||
}
|
||||
@@ -62,6 +62,7 @@
|
||||
"@hcengineering/workbench-resources": "^0.7.0",
|
||||
"@hcengineering/communication": "^0.7.0",
|
||||
"@hcengineering/chat": "^0.7.0",
|
||||
"@hcengineering/integration-client": "^0.7.0"
|
||||
"@hcengineering/integration-client": "^0.7.0",
|
||||
"@hcengineering/rating": "^0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { PluginConfiguration } from '@hcengineering/core'
|
||||
import { pluginConfigurationStore, getClient } from '@hcengineering/presentation'
|
||||
import { PluginConfiguration, systemAccountUuid } from '@hcengineering/core'
|
||||
import { createQuery, getClient, pluginConfigurationStore, hasResource } from '@hcengineering/presentation'
|
||||
import ratingPlugin, { getRaiting, type PersonRating } from '@hcengineering/rating'
|
||||
import { Breadcrumb, Button, Header, Icon, IconInfo, Label, Scroller } from '@hcengineering/ui'
|
||||
import setting from '../plugin'
|
||||
|
||||
@@ -25,6 +26,19 @@
|
||||
enabled: value
|
||||
})
|
||||
}
|
||||
|
||||
const sysQuery = createQuery()
|
||||
let sysRating: PersonRating | undefined
|
||||
|
||||
sysQuery.query(ratingPlugin.class.PersonRating, { accountId: systemAccountUuid }, (res) => {
|
||||
sysRating = res[0]
|
||||
})
|
||||
|
||||
$: totalVisible = getRaiting(
|
||||
100,
|
||||
sysRating,
|
||||
$pluginConfigurationStore.list.filter((it) => it.enabled && it.hidden !== true && it.system !== true)
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="hulyComponent">
|
||||
@@ -39,6 +53,7 @@
|
||||
<div class="flex-row-center flex-wrap gap-around-4">
|
||||
{#each $pluginConfigurationStore.list as config}
|
||||
{#if config.hidden !== true && config.system !== true}
|
||||
{@const pluginRating = getRaiting(totalVisible, sysRating, [config])}
|
||||
<div class="cardBox flex-col clear-mins" class:enabled={config.enabled ?? true}>
|
||||
<div class="flex-row-center">
|
||||
<span class="mr-2">
|
||||
@@ -46,6 +61,10 @@
|
||||
</span>
|
||||
<span class="fs-title">
|
||||
<Label label={config.label} />
|
||||
|
||||
{#if hasResource(ratingPlugin.component.RatingRing)}
|
||||
- ({pluginRating}%)
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{#if config.description}
|
||||
|
||||
@@ -18,20 +18,22 @@
|
||||
import { getCurrentAccount, SocialIdType } from '@hcengineering/core'
|
||||
import login, { loginId } from '@hcengineering/login'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { AttributeEditor, MessageBox, getClient } from '@hcengineering/presentation'
|
||||
import { AttributeEditor, createQuery, getClient, hasResource, MessageBox } from '@hcengineering/presentation'
|
||||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
Component,
|
||||
createFocusManager,
|
||||
EditBox,
|
||||
FocusHandler,
|
||||
Header,
|
||||
createFocusManager,
|
||||
showPopup,
|
||||
navigate,
|
||||
Scroller
|
||||
Scroller,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { logIn, logOut } from '@hcengineering/workbench-resources'
|
||||
|
||||
import rating, { type PersonRating } from '@hcengineering/rating'
|
||||
import setting from '../plugin'
|
||||
import SocialIdsEditor from './socialIds/SocialIdsEditor.svelte'
|
||||
|
||||
@@ -39,6 +41,16 @@
|
||||
const account = getCurrentAccount()
|
||||
const email = account.fullSocialIds.find((si) => si.type === SocialIdType.EMAIL)?.value ?? ''
|
||||
|
||||
const levelQuery = createQuery()
|
||||
|
||||
let personRating: PersonRating | undefined
|
||||
|
||||
levelQuery.query(rating.class.PersonRating, { accountId: account.uuid }, (res) => {
|
||||
personRating = res[0]
|
||||
})
|
||||
|
||||
$: console.log('SYS', personRating)
|
||||
|
||||
let firstName = ''
|
||||
let lastName = ''
|
||||
let initialized = false
|
||||
@@ -102,7 +114,7 @@
|
||||
<div class="ac-body p-10 flex-col max-w-240 content">
|
||||
{#if $myEmployeeStore}
|
||||
<div class="flex flex-grow w-full">
|
||||
<div class="mr-8">
|
||||
<div class="mr-8 flex-col items-center">
|
||||
<EditableAvatar
|
||||
person={$myEmployeeStore}
|
||||
{email}
|
||||
@@ -111,6 +123,14 @@
|
||||
bind:this={avatarEditor}
|
||||
on:done={onAvatarDone}
|
||||
/>
|
||||
{#if hasResource(rating.component.RatingRing)}
|
||||
<div class="flex-row-center">
|
||||
<Component
|
||||
is={rating.component.RatingRing}
|
||||
props={{ rating: personRating?.rating ?? 0, showValues: true }}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-grow flex-col">
|
||||
<EditBox
|
||||
@@ -149,7 +169,15 @@
|
||||
</div>
|
||||
{/if}
|
||||
<div class="separator" />
|
||||
<SocialIdsEditor />
|
||||
{#if hasResource(rating.component.RatingRing)}
|
||||
{#if personRating != null}
|
||||
<div class="flex-row-center mt-2">
|
||||
<Component is={rating.component.RatingActivities} props={{ rating: personRating }} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="separator" />
|
||||
{/if}
|
||||
<SocialIdsEditor rating={personRating} />
|
||||
<div class="footer">
|
||||
<Button
|
||||
icon={setting.icon.Signout}
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import contact, { SocialIdentity, SocialIdentityProvider, SocialIdentityRef } from '@hcengineering/contact'
|
||||
import { SocialIdentityPresenter } from '@hcengineering/contact-resources'
|
||||
import {
|
||||
getCurrentAccount,
|
||||
loginSocialTypes,
|
||||
@@ -21,19 +22,20 @@
|
||||
setCurrentAccount,
|
||||
SocialId
|
||||
} from '@hcengineering/core'
|
||||
import { Button, getPlatformColorDef, Label, PaletteColorIndexes, showPopup, themeStore } from '@hcengineering/ui'
|
||||
import contact, { SocialIdentity, SocialIdentityProvider, SocialIdentityRef } from '@hcengineering/contact'
|
||||
import { SocialIdentityPresenter } from '@hcengineering/contact-resources'
|
||||
import { getClient, MessageBox } from '@hcengineering/presentation'
|
||||
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { getClient, MessageBox } from '@hcengineering/presentation'
|
||||
import { getPlatformColorDef, Label, PaletteColorIndexes, showPopup, themeStore } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import setting from '../../plugin'
|
||||
import { getAccountClient } from '../../utils'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let socialId: SocialId
|
||||
export let socialIdProvider: SocialIdentityProvider
|
||||
export let canRelease: boolean = false
|
||||
export let socialIdRating: number | undefined = undefined
|
||||
export let socialIdRatingTotal: number | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const accountClient = getAccountClient()
|
||||
@@ -116,6 +118,12 @@
|
||||
<div class="flex-row-center flex-gap-2 root">
|
||||
<SocialIdentityPresenter value={socialIdentity} {socialIdProvider} />
|
||||
|
||||
{#if socialIdRating != null && socialIdRatingTotal != null && socialIdRatingTotal > 0}
|
||||
<div class="on-hover">
|
||||
{Math.round((socialIdRating / socialIdRatingTotal) * 100)}%
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex-grow" />
|
||||
|
||||
{#if isLogin}
|
||||
|
||||
@@ -13,14 +13,18 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getCurrentAccount, loginSocialTypes, notEmpty, SocialIdType } from '@hcengineering/core'
|
||||
import { FocusHandler, createFocusManager, showPopup, Label, Button, Menu, Action, Scroller } from '@hcengineering/ui'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { getCurrentAccount, loginSocialTypes, notEmpty, SocialIdType } from '@hcengineering/core'
|
||||
import { getClient, hasResource } from '@hcengineering/presentation'
|
||||
import { Action, Button, createFocusManager, FocusHandler, Label, Menu, Scroller, showPopup } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
import SocialIdRow from './SocialIdRow.svelte'
|
||||
import type { PersonRating } from '@hcengineering/rating'
|
||||
import ratingPlugin from '@hcengineering/rating'
|
||||
import setting from '../../plugin'
|
||||
import SocialIdRow from './SocialIdRow.svelte'
|
||||
|
||||
export let rating: PersonRating | undefined
|
||||
|
||||
let account = getCurrentAccount()
|
||||
const manager = createFocusManager()
|
||||
@@ -59,6 +63,9 @@
|
||||
function handleAccountUpdated (): void {
|
||||
account = getCurrentAccount()
|
||||
}
|
||||
|
||||
$: socialIdRatingTotal =
|
||||
rating != null ? Object.values(rating?.socialIds ?? {}).reduce((sum, val) => sum + (val ?? 0), 0) : 0
|
||||
</script>
|
||||
|
||||
<FocusHandler {manager} />
|
||||
@@ -80,8 +87,19 @@
|
||||
? !onlyLogin
|
||||
: true}
|
||||
{#if socialIdProvider != null && socialId.type !== SocialIdType.HULY}
|
||||
{@const socialIdRating =
|
||||
rating != null && hasResource(ratingPlugin.component.RatingRing)
|
||||
? rating.socialIds?.[socialId?._id]
|
||||
: undefined}
|
||||
<div class="item">
|
||||
<SocialIdRow {socialId} {socialIdProvider} {canRelease} on:released={handleAccountUpdated} />
|
||||
<SocialIdRow
|
||||
{socialId}
|
||||
{socialIdProvider}
|
||||
{canRelease}
|
||||
on:released={handleAccountUpdated}
|
||||
{socialIdRating}
|
||||
{socialIdRatingTotal}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { getClient as getAccountClientRaw, type AccountClient } from '@hcengineering/account-client'
|
||||
import contact, { getFirstName, getLastName } from '@hcengineering/contact'
|
||||
import { employeeByPersonIdStore } from '@hcengineering/contact-resources'
|
||||
import { type IntegrationKind, type Class, type Doc, type Hierarchy, type Ref } from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import login from '@hcengineering/login'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { type TemplateDataProvider } from '@hcengineering/templates'
|
||||
import { getClient as getAccountClientRaw, type AccountClient } from '@hcengineering/account-client'
|
||||
import { get } from 'svelte/store'
|
||||
import {
|
||||
type Class,
|
||||
type Doc,
|
||||
type Hierarchy,
|
||||
type IntegrationKind,
|
||||
type PluginConfiguration,
|
||||
type Ref
|
||||
} from '@hcengineering/core'
|
||||
import {
|
||||
getIntegrationClient as getIntegrationClientRaw,
|
||||
type IntegrationClient
|
||||
} from '@hcengineering/integration-client'
|
||||
import login from '@hcengineering/login'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import type { PersonRating } from '@hcengineering/rating'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { type TemplateDataProvider } from '@hcengineering/templates'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
function isEditable (hierarchy: Hierarchy, p: Class<Doc>): boolean {
|
||||
let ancestors = [p._id]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user