UBERF-9716: New profile preview and initial achievements (#8504)
* UBERF-9716: Draft user profile Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Redesign user profile card Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Hide error for profile popup Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Add achievement header and refactor Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Display timezone Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Fix formatting Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Refactor Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Add preview for mentions Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Move achievements to separate module and use component extension Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Clean up Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Fix rush check Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Clean up Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Fix validate Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Fix validate Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Minor fixes Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Fix formatting and template Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Update npm lock Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Add light background and fix some issues Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Clean up Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Fix svelte error Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Clean up Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Add preview to new chat Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Fix svelte Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Update avatar selector Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Use instead of px Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Accent name Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Fix formatting Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Remove unused width prop Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-9716: Remove isButtonIcon Signed-off-by: Artem Savchenko <armisav@gmail.com> --------- Signed-off-by: Artem Savchenko <armisav@gmail.com>
@@ -234,6 +234,9 @@
|
||||
"@hcengineering/inbox": "^0.6.0",
|
||||
"@hcengineering/inbox-assets": "^0.6.0",
|
||||
"@hcengineering/inbox-resources": "^0.6.0",
|
||||
"@hcengineering/achievement": "^0.6.0",
|
||||
"@hcengineering/achievement-assets": "^0.6.0",
|
||||
"@hcengineering/achievement-resources": "^0.6.0",
|
||||
"electron-squirrel-startup": "~1.0.0",
|
||||
"dotenv": "~16.0.0",
|
||||
"electron-context-menu": "^4.0.4",
|
||||
|
||||
@@ -64,6 +64,7 @@ import workbench, { workbenchId } from '@hcengineering/workbench'
|
||||
import { mailId } from '@hcengineering/mail'
|
||||
import { chatId } from '@hcengineering/chat'
|
||||
import { inboxId } from '@hcengineering/inbox'
|
||||
import { achievementId } from '@hcengineering/achievement'
|
||||
|
||||
import '@hcengineering/activity-assets'
|
||||
import '@hcengineering/analytics-collector-assets'
|
||||
@@ -113,6 +114,7 @@ import '@hcengineering/workbench-assets'
|
||||
import '@hcengineering/mail-assets'
|
||||
import '@hcengineering/chat-assets'
|
||||
import '@hcengineering/inbox-assets'
|
||||
import '@hcengineering/achievement-assets'
|
||||
|
||||
import analyticsCollector, { analyticsCollectorId } from '@hcengineering/analytics-collector'
|
||||
import { coreId } from '@hcengineering/core'
|
||||
@@ -224,6 +226,7 @@ function configureI18n (): void {
|
||||
addStringsLoader(chatId, async (lang: string) => await import(`@hcengineering/chat-assets/lang/${lang}.json`))
|
||||
addStringsLoader(inboxId, async (lang: string) => await import(`@hcengineering/inbox-assets/lang/${lang}.json`))
|
||||
addStringsLoader(processId, async (lang: string) => await import(`@hcengineering/process-assets/lang/${lang}.json`))
|
||||
addStringsLoader(achievementId, async (lang: string) => await import(`@hcengineering/achievement-assets/lang/${lang}.json`))
|
||||
}
|
||||
|
||||
export async function configurePlatform (): Promise<void> {
|
||||
@@ -361,6 +364,7 @@ export async function configurePlatform (): Promise<void> {
|
||||
addLocation(inboxId, () => import(/* webpackChunkName: "inbox" */ '@hcengineering/inbox-resources'))
|
||||
addLocation(mailId, () => import(/* webpackChunkName: "card" */ '@hcengineering/mail-resources'))
|
||||
addLocation(processId, () => import(/* webpackChunkName: "process" */ '@hcengineering/process-resources'))
|
||||
addLocation(achievementId, () => import(/* webpackChunkName: "achievement" */ '@hcengineering/achievement-resources'))
|
||||
|
||||
setMetadata(client.metadata.FilterModel, 'ui')
|
||||
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
|
||||
|
||||
@@ -54,6 +54,9 @@
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/achievement": "^0.6.0",
|
||||
"@hcengineering/achievement-assets": "^0.6.0",
|
||||
"@hcengineering/achievement-resources": "^0.6.0",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/activity-assets": "^0.6.3",
|
||||
"@hcengineering/activity-resources": "^0.6.1",
|
||||
|
||||
@@ -73,6 +73,7 @@ import { chatId } from '@hcengineering/chat'
|
||||
import github, { githubId } from '@hcengineering/github'
|
||||
import { bitrixId } from '@hcengineering/bitrix'
|
||||
import {inboxId} from '@hcengineering/inbox'
|
||||
import {achievementId} from '@hcengineering/achievement'
|
||||
|
||||
import '@hcengineering/activity-assets'
|
||||
import '@hcengineering/analytics-collector-assets'
|
||||
@@ -124,6 +125,7 @@ import '@hcengineering/chat-assets'
|
||||
import '@hcengineering/inbox-assets'
|
||||
import '@hcengineering/mail-assets'
|
||||
import '@hcengineering/github-assets'
|
||||
import '@hcengineering/achievement-assets'
|
||||
|
||||
import { coreId } from '@hcengineering/core'
|
||||
import presentation, {
|
||||
@@ -355,6 +357,7 @@ function configureI18n(): void {
|
||||
addStringsLoader(chatId, async (lang: string) => await import(`@hcengineering/chat-assets/lang/${lang}.json`))
|
||||
addStringsLoader(processId, async (lang: string) => await import(`@hcengineering/process-assets/lang/${lang}.json`))
|
||||
addStringsLoader(inboxId, async (lang: string) => await import(`@hcengineering/inbox-assets/lang/${lang}.json`))
|
||||
addStringsLoader(achievementId, async (lang: string) => await import(`@hcengineering/achievement-assets/lang/${lang}.json`))
|
||||
}
|
||||
|
||||
export async function configurePlatform() {
|
||||
@@ -554,6 +557,7 @@ export async function configurePlatform() {
|
||||
addLocation(chatId, () => import(/* webpackChunkName: "chat" */ '@hcengineering/chat-resources'))
|
||||
addLocation(processId, () => import(/* webpackChunkName: "process" */ '@hcengineering/process-resources'))
|
||||
addLocation(inboxId, () => import(/* webpackChunkName: "inbox" */ '@hcengineering/inbox-resources'))
|
||||
addLocation(achievementId, () => import(/* webpackChunkName: "achievement" */ '@hcengineering/achievement-resources'))
|
||||
|
||||
setMetadata(client.metadata.FilterModel, 'ui')
|
||||
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
|
||||
|
||||
@@ -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,43 @@
|
||||
{
|
||||
"name": "@hcengineering/model-achievement",
|
||||
"version": "0.6.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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.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.11.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/achievement": "^0.6.0",
|
||||
"@hcengineering/achievement-resources": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/model": "^0.6.11",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-presentation": "^0.6.0",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/presentation": "^0.6.3",
|
||||
"@hcengineering/ui": "^0.6.15"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// 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 from '@hcengineering/core'
|
||||
import { type Builder } from '@hcengineering/model'
|
||||
import contact from '@hcengineering/contact'
|
||||
import presentation from '@hcengineering/model-presentation'
|
||||
|
||||
import achievement from './plugin'
|
||||
|
||||
export { default } from './plugin'
|
||||
export { achievementId } from '@hcengineering/achievement'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(
|
||||
presentation.class.ComponentPointExtension,
|
||||
core.space.Model,
|
||||
{
|
||||
extension: contact.extension.PersonAchievementsPresenter,
|
||||
component: achievement.component.PersonAchievementsPresenter,
|
||||
props: {}
|
||||
},
|
||||
achievement.extensions.PersonAchievementsPresenter
|
||||
)
|
||||
}
|
||||
@@ -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 { mergeIds } from '@hcengineering/platform'
|
||||
import { achievementId } from '@hcengineering/achievement'
|
||||
import achievement from '@hcengineering/achievement-resources/src/plugin'
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import { type ComponentPointExtension } from '@hcengineering/presentation'
|
||||
|
||||
export default mergeIds(achievementId, achievement, {
|
||||
extensions: {
|
||||
PersonAchievementsPresenter: '' as Ref<ComponentPointExtension>
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/model/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,7 @@
|
||||
"@hcengineering/model-chat": "^0.6.0",
|
||||
"@hcengineering/model-process": "^0.6.0",
|
||||
"@hcengineering/model-server-process": "^0.6.0",
|
||||
"@hcengineering/model-inbox": "^0.6.0"
|
||||
"@hcengineering/model-inbox": "^0.6.0",
|
||||
"@hcengineering/model-achievement": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ import { presenceId, createModel as presenceModel } from '@hcengineering/model-p
|
||||
import { chatId, createModel as chatModel } from '@hcengineering/model-chat'
|
||||
import processes, { processId, createModel as processModel } from '@hcengineering/model-process'
|
||||
import { createModel as inboxModel, inboxId } from '@hcengineering/model-inbox'
|
||||
import { achievementId, createModel as achievementModel } from '@hcengineering/model-achievement'
|
||||
import { type Plugin } from '@hcengineering/platform'
|
||||
|
||||
interface ConfigurablePlugin extends Omit<Data<PluginConfiguration>, 'pluginId' | 'transactions'> {}
|
||||
@@ -452,6 +453,7 @@ export default function buildModel (enabled: string[] = ['*'], disabled: string[
|
||||
[mailModel, mailId],
|
||||
[chatModel, chatId],
|
||||
[inboxModel, inboxId],
|
||||
[achievementModel, achievementId],
|
||||
|
||||
[serverCoreModel, serverCoreId],
|
||||
[serverAttachmentModel, serverAttachmentId],
|
||||
|
||||
@@ -892,6 +892,10 @@ export function createModel (builder: Builder): void {
|
||||
arrayPresenter: contact.component.EmployeeArrayEditor
|
||||
})
|
||||
|
||||
builder.mixin(contact.class.Person, core.class.Class, view.mixin.PreviewPresenter, {
|
||||
presenter: contact.component.PersonPreviewPresenter
|
||||
})
|
||||
|
||||
builder.mixin(contact.mixin.Employee, core.class.Class, view.mixin.IgnoreActions, {
|
||||
actions: [view.action.Delete]
|
||||
})
|
||||
|
||||
@@ -57,7 +57,8 @@ export default mergeIds(contactId, contact, {
|
||||
ActivityChannelPresenter: '' as AnyComponent,
|
||||
EmployeeFilter: '' as AnyComponent,
|
||||
EmployeeFilterValuePresenter: '' as AnyComponent,
|
||||
ChannelIcon: '' as AnyComponent
|
||||
ChannelIcon: '' as AnyComponent,
|
||||
PersonPreviewPresenter: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
SearchEmployee: '' as IntlString,
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
--theme-button-contrast-disabled-color: rgba(0, 0, 0, .5);
|
||||
--theme-button-contrast-border: rgba(255, 255, 255, .2);
|
||||
|
||||
--theme-button-container-color: #25262A;
|
||||
|
||||
--theme-refinput-divider: rgba(255, 255, 255, .07);
|
||||
--theme-refinput-border: rgba(255, 255, 255, .1);
|
||||
|
||||
@@ -193,6 +195,7 @@
|
||||
--theme-popup-header: #3A3A47;
|
||||
--theme-popup-shadow: 0 0 .5rem rgba(0, 0, 0, .2);
|
||||
--theme-popup-checkicon: #FFFFFF99;
|
||||
--theme-popup-deactivated: #1A1A28;
|
||||
--theme-panel-color: #1A1A28;
|
||||
--theme-calendar-today-color: #fff;
|
||||
--theme-calendar-holiday-color: #eb5757;
|
||||
@@ -421,6 +424,8 @@
|
||||
--theme-button-contrast-disabled-color: rgba(255, 255, 255, .5);
|
||||
--theme-button-contrast-border: rgba(0, 0, 0, .2);
|
||||
|
||||
--theme-button-container-color: #F1F1F1;
|
||||
|
||||
--theme-refinput-divider: rgba(0, 0, 0, .07);
|
||||
--theme-refinput-border: rgba(0, 0, 0, .1);
|
||||
|
||||
@@ -497,6 +502,7 @@
|
||||
--theme-popup-header: #EBEBEB;
|
||||
--theme-popup-shadow: 0 0 .5rem rgba(0, 0, 0, .2);
|
||||
--theme-popup-checkicon: #205DC2;
|
||||
--theme-popup-deactivated: #C7C8CA;
|
||||
--theme-panel-color: #FFFFFF;
|
||||
--theme-calendar-today-color: #000;
|
||||
--theme-calendar-holiday-color: #eb5757;
|
||||
@@ -680,4 +686,12 @@
|
||||
--calendar-event-back-color: #f3f6fb;
|
||||
--calendar-event-border-color: rgba(43, 81, 144, 0.2);
|
||||
--calendar-event-handle-color: #2b5190;
|
||||
|
||||
--theme-button-contrast-color: #fff;
|
||||
--theme-button-contrast-enabled: rgba(0, 0, 0, .8);
|
||||
--theme-button-contrast-hovered: #000;
|
||||
--theme-button-contrast-pressed: rgba(0, 0, 0, .6);
|
||||
--theme-button-contrast-disabled: rgba(0, 0, 0, .6);
|
||||
--theme-button-contrast-disabled-color: rgba(255, 255, 255, .5);
|
||||
--theme-button-contrast-border: rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
--love-active-call-transform: scaleY(0.25) scaleX(0.4);
|
||||
--love-active-call-filter: blur(17px);
|
||||
|
||||
--global-online-color: #49a26b;
|
||||
--global-online-color: #209B18;
|
||||
--global-offline-color: #d1d5de;
|
||||
}
|
||||
|
||||
@@ -223,6 +223,6 @@
|
||||
--love-active-call-filter: blur(10px);
|
||||
--love-active-call-transform: scaleY(0.3) scaleX(0.42);
|
||||
|
||||
--global-online-color: #49a26b;
|
||||
--global-online-color: #209B18;
|
||||
--global-offline-color: #5A667E;
|
||||
}
|
||||
|
||||
@@ -130,10 +130,27 @@
|
||||
background-color: var(--theme-button-default);
|
||||
pointer-events: none;
|
||||
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
|
||||
& > * {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.withStatus {
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: cover;
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M10,13.5c0-1.9,1.6-3.5,3.5-3.5c1,0,1.9,0.4,2.5,1.1V0H0v16h11.1C10.4,15.4,10,14.5,10,13.5z' /%3E%3C/svg%3E");
|
||||
&.hulyAvatarSize-large {
|
||||
mask-image:
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 80'%3E%3Ccircle cx='69' cy='69' r='11' /%3E%3C/svg%3E"),
|
||||
linear-gradient(#000 0 0);
|
||||
mask-composite: exclude;
|
||||
}
|
||||
}
|
||||
|
||||
&.circle,
|
||||
@@ -166,7 +183,12 @@
|
||||
& > img { border-width: 2px; }
|
||||
}
|
||||
}
|
||||
img { object-fit: cover; }
|
||||
img {
|
||||
object-fit: cover;
|
||||
&.disabled {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
}
|
||||
.icon,
|
||||
.ava-text::after {
|
||||
position: absolute;
|
||||
@@ -242,6 +264,12 @@
|
||||
width: 4.5rem; // 72 - 63
|
||||
|
||||
.ava-text { font-size: 2.75rem; }
|
||||
|
||||
&.modern {
|
||||
width: 5rem;
|
||||
|
||||
.ava-text { font-size: 3rem; }
|
||||
}
|
||||
}
|
||||
.hulyAvatarSize-x-large {
|
||||
width: 7.5rem; // 120 - 105
|
||||
@@ -297,7 +325,14 @@
|
||||
.small-font &.small { width: 9px; }
|
||||
&.small { width: 10px; }
|
||||
.small-font &.medium { width: 11px; }
|
||||
&.medium { width: 13px; }
|
||||
&.medium {
|
||||
width: 13px;
|
||||
&.modern {
|
||||
right: 4px;
|
||||
bottom: 4px;
|
||||
width: 14px;
|
||||
}
|
||||
}
|
||||
&.large {
|
||||
right: -.125rem;
|
||||
bottom: -.125rem;
|
||||
@@ -309,7 +344,9 @@
|
||||
width: 37%;
|
||||
}
|
||||
}
|
||||
&.online { background-color: var(--global-online-color); }
|
||||
&.online {
|
||||
background-color: var(--global-online-color);
|
||||
}
|
||||
&.offline {
|
||||
border: 1px solid var(--global-offline-color);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { getCurrentAccount } from '@hcengineering/core'
|
||||
import { EmojiPopup, getEventPositionElement, showPopup, Action as MenuAction } from '@hcengineering/ui'
|
||||
import { personByPersonIdStore } from '@hcengineering/contact-resources'
|
||||
import { PersonPreviewProvider, personByPersonIdStore } from '@hcengineering/contact-resources'
|
||||
import type { SocialID } from '@hcengineering/communication-types'
|
||||
import { AttachmentPreview } from '@hcengineering/attachment-resources'
|
||||
import { Message, MessageType } from '@hcengineering/communication-types'
|
||||
@@ -136,13 +136,17 @@
|
||||
<div class="message__body">
|
||||
<!--TODO: remove on:contextmenu-->
|
||||
<div class="message__avatar" on:contextmenu={handleContextMenu}>
|
||||
<Avatar name={author?.name} avatar={author} size={AvatarSize.Small} />
|
||||
<PersonPreviewProvider value={author}>
|
||||
<Avatar name={author?.name} avatar={author} size={AvatarSize.Small} />
|
||||
</PersonPreviewProvider>
|
||||
</div>
|
||||
<div class="message__content">
|
||||
<div class="message__header">
|
||||
<div class="message__username">
|
||||
{formatName(author?.name ?? '')}
|
||||
</div>
|
||||
<PersonPreviewProvider value={author}>
|
||||
<div class="message__username">
|
||||
{formatName(author?.name ?? '')}
|
||||
</div>
|
||||
</PersonPreviewProvider>
|
||||
<div class="message__date">
|
||||
{formatDate(message.created)}
|
||||
</div>
|
||||
|
||||
@@ -30,11 +30,12 @@
|
||||
export let element: HTMLButtonElement | undefined = undefined
|
||||
export let id: string | undefined = undefined
|
||||
export let dataId: string | undefined = undefined
|
||||
export let type: 'type-button' | 'type-button-icon' = 'type-button'
|
||||
</script>
|
||||
|
||||
<ButtonBase
|
||||
bind:element
|
||||
type={'type-button'}
|
||||
{type}
|
||||
{title}
|
||||
{shape}
|
||||
{label}
|
||||
|
||||
@@ -318,7 +318,7 @@
|
||||
/>
|
||||
{#if $tooltip.component && $tooltip.kind !== 'submenu'}
|
||||
<div
|
||||
class="popup-tooltip {options.classList}"
|
||||
class="popup-tooltip {options.classList} {$tooltip.style}"
|
||||
class:shown
|
||||
class:doublePadding={$tooltip.label}
|
||||
use:resizeObserver={(element) => {
|
||||
@@ -369,12 +369,14 @@
|
||||
/>
|
||||
</clipPath>
|
||||
</svg>
|
||||
<div
|
||||
bind:this={nubHTML}
|
||||
style:z-index={($modals.findIndex((t) => t.type === 'tooltip') ?? 1) + 10000}
|
||||
class="nub {nubDirection ?? ''}"
|
||||
class:shown
|
||||
/>
|
||||
{#if !$tooltip.noArrow}
|
||||
<div
|
||||
bind:this={nubHTML}
|
||||
style:z-index={($modals.findIndex((t) => t.type === 'tooltip') ?? 1) + 10000}
|
||||
class="nub {nubDirection ?? ''}"
|
||||
class:shown
|
||||
/>
|
||||
{/if}
|
||||
{:else if $tooltip.label && $tooltip.kind !== 'submenu'}
|
||||
<div
|
||||
class="tooltip {dir ?? ''} {options.classList}"
|
||||
@@ -470,6 +472,22 @@
|
||||
&.doublePadding {
|
||||
padding: 1rem;
|
||||
}
|
||||
&.modern {
|
||||
padding: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
border-radius: 1.5rem;
|
||||
box-shadow:
|
||||
0 6.25rem 5rem rgba(0, 0, 0, 0.15),
|
||||
0 2.5rem 2rem rgba(0, 0, 0, 0.12),
|
||||
0 1.5rem 1rem rgba(0, 0, 0, 0.1),
|
||||
0 0.75rem 0.75rem rgba(0, 0, 0, 0.1),
|
||||
0 0.375rem 0.375rem rgba(0, 0, 0, 0.08),
|
||||
0 0.125rem 0.125rem rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
&.disabled {
|
||||
background-color: var(--popup-color-disabled);
|
||||
}
|
||||
}
|
||||
|
||||
.nub {
|
||||
|
||||
@@ -12,7 +12,9 @@ const emptyTooltip: LabelAndProps = {
|
||||
anchor: undefined,
|
||||
onUpdate: undefined,
|
||||
keys: undefined,
|
||||
kind: 'tooltip'
|
||||
kind: 'tooltip',
|
||||
style: undefined,
|
||||
noArrow: false
|
||||
}
|
||||
let storedValue: LabelAndProps = emptyTooltip
|
||||
export const tooltipstore = derived(modalStore, (modals) => {
|
||||
@@ -25,11 +27,9 @@ export const tooltipstore = derived(modalStore, (modals) => {
|
||||
|
||||
let toHandler: any
|
||||
export function tooltip (node: HTMLElement, options?: LabelAndProps): any {
|
||||
if (options === undefined) {
|
||||
return {}
|
||||
}
|
||||
if (options.label === undefined && options.component === undefined) {
|
||||
if (options?.label === undefined && options?.component === undefined) {
|
||||
// No tooltip
|
||||
// TODO: Fix reactive options update in this case
|
||||
return {}
|
||||
}
|
||||
let opt = options
|
||||
@@ -48,7 +48,9 @@ export function tooltip (node: HTMLElement, options?: LabelAndProps): any {
|
||||
opt.anchor,
|
||||
opt.onUpdate,
|
||||
opt.kind,
|
||||
opt.keys
|
||||
opt.keys,
|
||||
opt.style,
|
||||
opt.noArrow
|
||||
)
|
||||
}, opt.timeout ?? 10)
|
||||
} else {
|
||||
@@ -61,7 +63,9 @@ export function tooltip (node: HTMLElement, options?: LabelAndProps): any {
|
||||
opt.anchor,
|
||||
opt.onUpdate,
|
||||
opt.kind,
|
||||
opt.keys
|
||||
opt.keys,
|
||||
opt.style,
|
||||
opt.noArrow
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -86,7 +90,9 @@ export function tooltip (node: HTMLElement, options?: LabelAndProps): any {
|
||||
opt.anchor,
|
||||
opt.onUpdate,
|
||||
opt.kind,
|
||||
opt.keys
|
||||
opt.keys,
|
||||
opt.style,
|
||||
opt.noArrow
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -111,7 +117,9 @@ export function showTooltip (
|
||||
anchor?: HTMLElement,
|
||||
onUpdate?: (result: any) => void,
|
||||
kind?: 'tooltip' | 'submenu' | 'popup',
|
||||
keys?: string[]
|
||||
keys?: string[],
|
||||
style?: 'default' | 'modern',
|
||||
noArrow?: boolean
|
||||
): void {
|
||||
storedValue = {
|
||||
label,
|
||||
@@ -123,7 +131,9 @@ export function showTooltip (
|
||||
onUpdate,
|
||||
kind,
|
||||
keys,
|
||||
type: 'tooltip'
|
||||
type: 'tooltip',
|
||||
style,
|
||||
noArrow
|
||||
}
|
||||
modalStore.update((old) => {
|
||||
const tooltip = old.find((m) => m?.type === 'tooltip') as LabelAndProps | undefined
|
||||
|
||||
@@ -306,6 +306,8 @@ export interface LabelAndProps {
|
||||
kind?: 'tooltip' | 'submenu' | 'popup'
|
||||
keys?: string[]
|
||||
timeout?: number
|
||||
style?: 'default' | 'modern'
|
||||
noArrow?: boolean
|
||||
}
|
||||
|
||||
export interface ListItem {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/assets/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 98 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,8 @@
|
||||
{
|
||||
"string": {
|
||||
"Achievements": "Dosažení",
|
||||
"EarliestAdopter": "Nejakej první adoptovat",
|
||||
"Epic": "Epic",
|
||||
"Legendary": "Legendární"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"string": {
|
||||
"Achievements": "Erfolge",
|
||||
"EarliestAdopter": "Erste Adopter",
|
||||
"Epic": "Epic",
|
||||
"Legendary": "Legendär"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"string": {
|
||||
"Achievements": "Achievements",
|
||||
"EarliestAdopter": "Earliest Adopter",
|
||||
"Epic": "Epic",
|
||||
"Legendary": "Legendary"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"string": {
|
||||
"Achievements": "Logros",
|
||||
"EarliestAdopter": "Primer Adoptante",
|
||||
"Epic": "Epic",
|
||||
"Legendary": "Legendario"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"string": {
|
||||
"Achievements": "Achèvements",
|
||||
"EarliestAdopter": "Premier Adoptant",
|
||||
"Epic": "Epic",
|
||||
"Legendary": "Légendaire"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"string": {
|
||||
"Achievements": "Riconoscimenti",
|
||||
"EarliestAdopter": "Primo Adoptante",
|
||||
"Epic": "Epic",
|
||||
"Legendary": "Légendaire"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"string": {
|
||||
"Achievements": "Conquistas",
|
||||
"EarliestAdopter": "Primeiro Adoptante",
|
||||
"Epic": "Epic",
|
||||
"Legendary": "Legendário"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"string": {
|
||||
"Achievements": "Достижения",
|
||||
"EarliestAdopter": "Ранний пользователь",
|
||||
"Epic": "Эпик",
|
||||
"Legendary": "Легендарный"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"string": {
|
||||
"Achievements": "成就",
|
||||
"EarliestAdopter": "最早采用者",
|
||||
"Epic": "史诗",
|
||||
"Legendary": "传奇"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@hcengineering/achievement-assets",
|
||||
"version": "0.6.0",
|
||||
"main": "src/index.ts",
|
||||
"author": "Copyright © 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.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.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.1.0",
|
||||
"@types/node": "~20.11.16",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/achievement": "^0.6.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { makeLocalesTest } from '@hcengineering/platform'
|
||||
|
||||
it(
|
||||
'Locales are equale',
|
||||
makeLocalesTest((lang) => import(`../../lang/${lang}.json`))
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// 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 { addStringsLoader, loadMetadata } from '@hcengineering/platform'
|
||||
import achievement, { achievementId } from '@hcengineering/achievement'
|
||||
|
||||
const EarliestAdopter = require('../assets/EarliestAdopter.png') as string // eslint-disable-line
|
||||
const Epic = require('../assets/Epic.png') as string // eslint-disable-line
|
||||
const Legendary = require('../assets/Legendary.png') as string // eslint-disable-line
|
||||
|
||||
loadMetadata(achievement.image, {
|
||||
EarliestAdopter,
|
||||
Epic,
|
||||
Legendary
|
||||
})
|
||||
addStringsLoader(achievementId, async (lang: string) => await import(`../lang/${lang}.json`))
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/assets/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"types": ["node", "jest"],
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -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,5 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)']
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@hcengineering/achievement-resources",
|
||||
"version": "0.6.0",
|
||||
"main": "src/index.ts",
|
||||
"author": "Copyright © Hardcore Engineering Inc.",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "compile ui",
|
||||
"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.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.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.1.0",
|
||||
"prettier-plugin-svelte": "^3.2.2",
|
||||
"@types/node": "~20.11.16",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"typescript": "^5.3.3",
|
||||
"svelte": "^4.2.19",
|
||||
"svelte-check": "^3.6.9",
|
||||
"svelte-preprocess": "^5.1.3",
|
||||
"svelte-loader": "^3.2.0",
|
||||
"postcss": "^8.4.20",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"sass": "^1.53.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"svelte-eslint-parser": "^0.33.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/achievement": "^0.6.0",
|
||||
"@hcengineering/ui": "^0.6.15",
|
||||
"svelte": "^4.2.19"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('autoprefixer')
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<!--
|
||||
// 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 achievement from '@hcengineering/achievement'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
</script>
|
||||
|
||||
<div class="header-container gap-2 px-4">
|
||||
<span class="text-md font-normal content-color">
|
||||
<Label label={achievement.string.Achievements} />
|
||||
</span>
|
||||
<div class="divider" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.header-container {
|
||||
display: flex;
|
||||
padding: 0 0.75rem;
|
||||
align-items: center;
|
||||
}
|
||||
.divider {
|
||||
width: 100%;
|
||||
border-top: 1px solid var(--theme-divider-color);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,50 @@
|
||||
<!--
|
||||
// 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 { Doc, Ref } from '@hcengineering/core'
|
||||
import { Image } from '@hcengineering/ui'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
|
||||
import AchievementsHeader from './AchievementsHeader.svelte'
|
||||
import { getPersonAchievements } from '../utils'
|
||||
|
||||
export let personId: Ref<Doc>
|
||||
export let withHeader: boolean = true
|
||||
|
||||
$: personAchievements = getPersonAchievements(personId)
|
||||
</script>
|
||||
|
||||
{#if personAchievements.length > 0}
|
||||
{#if withHeader}
|
||||
<AchievementsHeader />
|
||||
{/if}
|
||||
<div class="achievements">
|
||||
{#each personAchievements as achievement}
|
||||
<div class="icon">
|
||||
<Image src={getMetadata(achievement.icon)} width="40px" height="60px" />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.achievements {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 0.625rem 0.5rem 0 0.5rem;
|
||||
|
||||
order: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -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 { type Resources } from '@hcengineering/platform'
|
||||
import PersonAchievementsPresenter from './components/PersonAchievementsPresenter.svelte'
|
||||
|
||||
export const achievementResourceId = 'achievement-resources' as const
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
PersonAchievementsPresenter
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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 { mergeIds } from '@hcengineering/platform'
|
||||
import achievement, { achievementId } from '@hcengineering/achievement'
|
||||
import { type AnyComponent } from '@hcengineering/ui'
|
||||
|
||||
export default mergeIds(achievementId, achievement, {
|
||||
component: {
|
||||
PersonAchievementsPresenter: '' as AnyComponent
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// 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 achievement from '@hcengineering/achievement'
|
||||
import { type IntlString, type Asset } from '@hcengineering/platform'
|
||||
|
||||
interface PersonAchievement {
|
||||
icon: Asset
|
||||
tooltip: IntlString
|
||||
}
|
||||
|
||||
// TODO: Remove and replace with actual person achievements, it is just for demonstration. Implement achievement service.
|
||||
const possibleAchievements: PersonAchievement[] = [
|
||||
{ icon: achievement.image.EarliestAdopter, tooltip: achievement.string.EarliestAdopter },
|
||||
{ icon: achievement.image.Epic, tooltip: achievement.string.Epic },
|
||||
{ icon: achievement.image.Legendary, tooltip: achievement.string.Legendary }
|
||||
]
|
||||
|
||||
function hashStringToInt (str: string): number {
|
||||
if (str === undefined) return 0
|
||||
let hash = 0
|
||||
if (str.length === 0) return hash
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i)
|
||||
hash = (hash << 5) - hash + char
|
||||
hash |= 0 // Convert to a 32-bit integer
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
export function getPersonAchievements (personId: string): PersonAchievement[] {
|
||||
const personHash = hashStringToInt(personId)
|
||||
return possibleAchievements.filter((_, index) => {
|
||||
return personHash % (index + 1) === 0
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const sveltePreprocess = require('svelte-preprocess')
|
||||
|
||||
module.exports = {
|
||||
preprocess: sveltePreprocess()
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/ui/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types"
|
||||
}
|
||||
}
|
||||
@@ -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/achievement",
|
||||
"version": "0.6.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.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.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.11.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/ui": "^0.6.15"
|
||||
},
|
||||
"repository": "https://github.com/hcengineering/platform",
|
||||
"publishConfig": {
|
||||
"registry": "https://npm.pkg.github.com"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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 { Asset, IntlString, plugin, Plugin } from '@hcengineering/platform'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const achievementId = 'achievement' as Plugin
|
||||
|
||||
export default plugin(achievementId, {
|
||||
image: {
|
||||
EarliestAdopter: '' as Asset,
|
||||
Epic: '' as Asset,
|
||||
Legendary: '' as Asset
|
||||
},
|
||||
string: {
|
||||
Achievements: '' as IntlString,
|
||||
EarliestAdopter: '' as IntlString,
|
||||
Epic: '' as IntlString,
|
||||
Legendary: '' as IntlString
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -197,7 +197,7 @@
|
||||
{#if $$slots.icon}
|
||||
<slot name="icon" />
|
||||
{:else if person}
|
||||
<Avatar size="medium" {person} name={person.name} />
|
||||
<Avatar size="medium" {person} name={person.name} showPreview />
|
||||
{:else}
|
||||
<SystemAvatar size="medium" />
|
||||
{/if}
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
]}
|
||||
selected={isListDisplayMode ? 'table' : 'card'}
|
||||
on:select={(result) => {
|
||||
if (result.detail !== undefined) isListDisplayMode = result.detail === 'table' ?? false
|
||||
if (result.detail !== undefined) isListDisplayMode = result.detail === 'table'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -20,8 +20,12 @@
|
||||
import chunter from '../plugin'
|
||||
import { createDirect } from '../utils'
|
||||
import { openChannelInSidebar } from '../navigation'
|
||||
import { Asset } from '@hcengineering/platform'
|
||||
|
||||
export let employee: Employee
|
||||
export let kind: 'primary' | 'secondary' | 'tertiary' | 'negative' = 'secondary'
|
||||
export let icon: Asset | undefined = undefined
|
||||
export let type: 'type-button' | 'type-button-icon' = 'type-button'
|
||||
|
||||
async function openDirect (): Promise<void> {
|
||||
const dm = await createDirect([employee._id])
|
||||
@@ -41,9 +45,11 @@
|
||||
</script>
|
||||
|
||||
<ModernButton
|
||||
label={chunter.string.Message}
|
||||
icon={view.icon.Bubble}
|
||||
label={type === 'type-button-icon' ? undefined : chunter.string.Message}
|
||||
icon={icon ?? view.icon.Bubble}
|
||||
size="small"
|
||||
iconSize="small"
|
||||
{type}
|
||||
{kind}
|
||||
on:click={openDirect}
|
||||
/>
|
||||
|
||||
@@ -121,4 +121,24 @@
|
||||
c5.959,1.695,10.706,6.453,12.388,12.416c0.124,0.44,0.525,0.729,0.962,0.729c0.09,0,0.181-0.012,0.272-0.037
|
||||
c0.531-0.15,0.841-0.703,0.691-1.234C39.887,14.753,34.613,9.467,27.991,7.582z"/>
|
||||
</symbol>
|
||||
<symbol id="contacts" viewBox="0 0 32 32">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.7,5.7c-1.4,0-2.7,0.5-3.5,1.5c-0.9,0.9-1.3,2.3-1.2,3.7c0.2,2.7,2.3,5.1,4.8,5.1c2.5,0,4.6-2.3,4.8-5.1C25.7,8,23.5,5.7,20.7,5.7z M18.5,8.5C18,9,17.7,9.8,17.7,10.8c0.1,2,1.6,3.3,2.9,3.3c1.3,0,2.8-1.3,2.9-3.3c0.1-1.9-1.1-3.2-2.9-3.2C19.8,7.6,19,7.9,18.5,8.5z" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.4,24.1c1.1-4.2,5.2-6.3,9.3-6.3c4,0,8.2,2,9.3,6.3c0.2,0.9-0.4,2.1-1.6,2.1H13C11.7,26.3,11.2,25.1,11.4,24.1z M13.3,24.4h14.8c-0.9-3-3.9-4.7-7.4-4.7C17.2,19.7,14.1,21.4,13.3,24.4z" />
|
||||
<path d="M9.6,16.2c-2.1,0-3.9-1.9-4-4.3c-0.1-1.2,0.3-2.3,1-3.1c0.8-0.8,1.8-1.2,3-1.2c1.2,0,2.2,0.4,3,1.3c0.8,0.8,1.1,1.9,1.1,3.1C13.5,14.3,11.7,16.2,9.6,16.2z M9.6,9.5C9,9.5,8.4,9.7,8,10.1c-0.4,0.4-0.6,1-0.5,1.7c0.1,1.4,1.1,2.5,2.2,2.5s2.1-1.2,2.2-2.5c0-0.7-0.2-1.3-0.6-1.7C10.9,9.7,10.3,9.5,9.6,9.5z" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2,22.5c0.9-3.5,4.3-5.2,7.6-5.2c1.3,0,2.6,0.2,3.8,0.8c0.5,0.2,0.7,0.8,0.5,1.2c-0.2,0.5-0.8,0.7-1.2,0.5c-0.9-0.4-1.9-0.6-3.1-0.6c-2.6,0-4.9,1.2-5.7,3.4H10c0.5,0,0.9,0.4,0.9,0.9s-0.4,0.9-0.9,0.9H3.5C2.4,24.4,1.8,23.3,2,22.5L2,22.5z" />
|
||||
</symbol>
|
||||
<symbol id="clock" viewBox="0 0 14 14">
|
||||
<path d="M7.43758 3.49984C7.43758 3.25821 7.24171 3.06234 7.00008 3.06234C6.75846 3.06234 6.56258 3.25821 6.56258 3.49984V6.99984C6.56258 7.11587 6.60868 7.22715 6.69072 7.3092L8.44072 9.0592C8.61158 9.23005 8.88859 9.23005 9.05944 9.0592C9.2303 8.88834 9.2303 8.61133 9.05944 8.44048L7.43758 6.81862V3.49984Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.00008 0.729004C3.5368 0.729004 0.729248 3.53655 0.729248 6.99984C0.729248 10.4631 3.5368 13.2707 7.00008 13.2707C10.4634 13.2707 13.2709 10.4631 13.2709 6.99984C13.2709 3.53655 10.4634 0.729004 7.00008 0.729004ZM1.60425 6.99984C1.60425 4.0198 4.02005 1.604 7.00008 1.604C9.98012 1.604 12.3959 4.0198 12.3959 6.99984C12.3959 9.97987 9.98012 12.3957 7.00008 12.3957C4.02005 12.3957 1.60425 9.97987 1.60425 6.99984Z"/>
|
||||
</symbol>
|
||||
<symbol id="chat" viewBox="0 0 20 20">
|
||||
<path d="M7.50357 9.1665H5.82943V10.8332H7.50357V9.1665Z"/>
|
||||
<path d="M10.8332 9.1665H9.15904V10.8332H10.8332V9.1665Z"/>
|
||||
<path d="M14.1628 9.1665H12.4886V10.8332H14.1628V9.1665Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.99996 1.0415C5.08386 1.0415 1.04346 4.83623 1.04346 9.5835C1.04346 11.875 1.98622 13.8743 3.47407 15.4235L2.73443 18.1527L2.73401 18.1542C2.5954 18.6709 3.11754 19.1115 3.60158 18.8977L3.60494 18.8963L6.67685 17.5182C7.70558 17.9104 8.82735 18.1255 9.99996 18.1255C14.916 18.1255 18.9565 14.3309 18.9565 9.5835C18.9565 4.83623 14.916 1.0415 9.99996 1.0415ZM2.29346 9.5835C2.29346 5.58593 5.71334 2.2915 9.99996 2.2915C14.2866 2.2915 17.7065 5.58593 17.7065 9.5835C17.7065 13.5812 14.2866 16.8755 9.99996 16.8755C8.89636 16.8755 7.84897 16.6564 6.9024 16.2626L6.65299 16.1589L4.28155 17.2227L4.87167 15.0452L4.60601 14.7886C3.17406 13.4056 2.29346 11.6207 2.29346 9.5835Z"/>
|
||||
</symbol>
|
||||
<symbol id="user" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.99991 1.0415C7.58366 1.0415 5.62491 3.00026 5.62491 5.4165C5.62491 7.83275 7.58366 9.7915 9.99991 9.7915C12.4162 9.7915 14.3749 7.83275 14.3749 5.4165C14.3749 3.00026 12.4162 1.0415 9.99991 1.0415ZM6.87491 5.4165C6.87491 3.69062 8.27402 2.2915 9.99991 2.2915C11.7258 2.2915 13.1249 3.69061 13.1249 5.4165C13.1249 7.1424 11.7258 8.5415 9.99991 8.5415C8.27402 8.5415 6.87491 7.14239 6.87491 5.4165Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.16145 12.3642C8.12316 10.6006 11.8766 10.6006 14.8383 12.3642C14.9352 12.4219 15.055 12.4894 15.1905 12.5659C15.7845 12.901 16.6825 13.4076 17.2976 14.0097C17.6823 14.3863 18.0479 14.8825 18.1143 15.4905C18.185 16.137 17.903 16.7437 17.3371 17.2828C16.3609 18.2128 15.1894 18.9582 13.6742 18.9582H6.32563C4.81037 18.9582 3.63889 18.2128 2.66269 17.2829C2.09684 16.7438 1.81477 16.137 1.88545 15.4905C1.9519 14.8825 2.31744 14.3863 2.70217 14.0097C3.31731 13.4076 4.21525 12.901 4.80926 12.5659C4.94481 12.4894 5.06453 12.4219 5.16145 12.3642ZM5.80098 13.4382C8.36863 11.9092 11.6312 11.9092 14.1988 13.4382C14.3387 13.5215 14.492 13.6084 14.6526 13.6995C15.2464 14.0364 15.9398 14.4298 16.4232 14.903C16.7233 15.1967 16.8513 15.4391 16.8717 15.6263C16.888 15.7749 16.8504 16.02 16.4749 16.3778C15.6117 17.2002 14.7345 17.7082 13.6742 17.7082H6.32563C5.26526 17.7082 4.38812 17.2002 3.52491 16.3778C3.14938 16.0201 3.1118 15.7749 3.12804 15.6263C3.14851 15.4391 3.27644 15.1967 3.57654 14.903C4.05995 14.4298 4.75331 14.0365 5.34715 13.6996C5.50773 13.6085 5.66107 13.5215 5.80098 13.4382Z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 92 KiB |
@@ -109,6 +109,8 @@
|
||||
"ViewProfile": "Zobrazit profil",
|
||||
"Viber": "Viber",
|
||||
"ViberPlaceholder": "Viber",
|
||||
"UserProfile": "Uživatelský profil"
|
||||
"UserProfile": "Uživatelský profil",
|
||||
"DeactivatedAccount": "Deaktivovaný účet",
|
||||
"LocalTime": "místní čas"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,8 @@
|
||||
"ViewProfile": "Profil anzeigen",
|
||||
"Viber": "Viber",
|
||||
"ViberPlaceholder": "Viber",
|
||||
"UserProfile": "Benutzerprofil"
|
||||
"UserProfile": "Benutzerprofil",
|
||||
"DeactivatedAccount": "Deaktivierter Account",
|
||||
"LocalTime": "Ortszeit"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,8 @@
|
||||
"SocialIds": "Social IDs",
|
||||
"Type": "Type",
|
||||
"Confirmed": "Confirmed",
|
||||
"UserProfile": "User profile"
|
||||
"UserProfile": "User profile",
|
||||
"DeactivatedAccount": "Deactivated account",
|
||||
"LocalTime": "local time"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,8 @@
|
||||
"SocialIds": "Identificaciones sociales",
|
||||
"Type": "Tipo",
|
||||
"Confirmed": "Confirmado",
|
||||
"UserProfile": "Perfil de usuario"
|
||||
"UserProfile": "Perfil de usuario",
|
||||
"DeactivatedAccount": "Cuenta desactivada",
|
||||
"LocalTime": "hora local"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,8 @@
|
||||
"SocialIds": "Identifiants sociaux",
|
||||
"Type": "Type",
|
||||
"Confirmed": "Confirmé",
|
||||
"UserProfile": "Profil utilisateur"
|
||||
"UserProfile": "Profil utilisateur",
|
||||
"DeactivatedAccount": "Compte désactivé",
|
||||
"LocalTime": "heure locale"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,8 @@
|
||||
"SocialIds": "ID social",
|
||||
"Type": "Tipo",
|
||||
"Confirmed": "Confermato",
|
||||
"UserProfile": "Profilo utente"
|
||||
"UserProfile": "Profilo utente",
|
||||
"DeactivatedAccount": "Account disattivato",
|
||||
"LocalTime": "ora locale"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,8 @@
|
||||
"SocialIds": "Identificações sociais",
|
||||
"Type": "Tipo",
|
||||
"Confirmed": "Confirmado",
|
||||
"UserProfile": "Perfil do usuário"
|
||||
"UserProfile": "Perfil do usuário",
|
||||
"DeactivatedAccount": "Conta desativada",
|
||||
"LocalTime": "hora local"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,8 @@
|
||||
"SocialIds": "Социальные ID",
|
||||
"Type": "Тип",
|
||||
"Confirmed": "Подтвержден",
|
||||
"UserProfile": "Профиль пользователя"
|
||||
"UserProfile": "Профиль пользователя",
|
||||
"DeactivatedAccount": "Деактивированный аккаунт",
|
||||
"LocalTime": "местного времени"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,8 @@
|
||||
"SocialIds": "社交 ID",
|
||||
"Type": "类型",
|
||||
"Confirmed": "已确认",
|
||||
"UserProfile": "用户资料"
|
||||
"UserProfile": "用户资料",
|
||||
"DeactivatedAccount": "已停用账户",
|
||||
"LocalTime": "当地时间"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,17 @@ loadMetadata(contact.icon, {
|
||||
Profile: `${icons}#profile`,
|
||||
KickUser: `${icons}#kickUser`,
|
||||
ComponentMembers: `${icons}#componentMembers`,
|
||||
Contacts: `${icons}#contacts`
|
||||
Contacts: `${icons}#contacts`,
|
||||
Clock: `${icons}#clock`,
|
||||
Chat: `${icons}#chat`,
|
||||
User: `${icons}#user`
|
||||
})
|
||||
addStringsLoader(contactId, async (lang: string) => await import(`../lang/${lang}.json`))
|
||||
|
||||
const ProfileBackground = require('../assets/profile-background.png') as string // eslint-disable-line
|
||||
const ProfileBackgroundLight = require('../assets/profile-background-light.png') as string // eslint-disable-line
|
||||
|
||||
loadMetadata(contact.image, {
|
||||
ProfileBackground,
|
||||
ProfileBackgroundLight
|
||||
})
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/workbench": "^0.6.16",
|
||||
"@hcengineering/account-client": "^0.6.0",
|
||||
"@hcengineering/achievement": "^0.6.0",
|
||||
"svelte": "^4.2.19",
|
||||
"crypto-js": "^4.2.0"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
// 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
|
||||
@@ -32,24 +32,33 @@
|
||||
getPlatformAvatarColorForTextDef,
|
||||
getPlatformColor,
|
||||
IconSize,
|
||||
themeStore
|
||||
themeStore,
|
||||
tooltip
|
||||
} from '@hcengineering/ui'
|
||||
import { onMount } from 'svelte'
|
||||
import { AccountUuid, type Data, PersonUuid, Ref, type WithLookup } from '@hcengineering/core'
|
||||
|
||||
import { loadUsersStatus, statusByUserStore } from '../utils'
|
||||
import AvatarInstance from './AvatarInstance.svelte'
|
||||
import { getPreviewPopup } from './person/utils'
|
||||
|
||||
export let person: (Data<WithLookup<AvatarInfo>> & { _id?: Ref<Person>, personUuid?: PersonUuid }) | undefined =
|
||||
undefined
|
||||
export let person:
|
||||
| (Data<WithLookup<AvatarInfo>> & { _id?: Ref<Person>, personUuid?: PersonUuid })
|
||||
| Person
|
||||
| undefined = undefined
|
||||
export let name: string | null | undefined = undefined
|
||||
export let direct: Blob | undefined = undefined
|
||||
export let size: IconSize
|
||||
export let statusSize: IconSize | undefined = undefined
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let variant: 'circle' | 'roundedRect' | 'none' = 'roundedRect'
|
||||
export let borderColor: number | undefined = undefined
|
||||
export let showStatus: boolean = false
|
||||
export let adaptiveName: boolean = false
|
||||
export let showPreview: boolean = false
|
||||
export let disabled: boolean = false
|
||||
export let style: 'modern' | undefined = undefined
|
||||
export let clickable: boolean = false
|
||||
|
||||
export function pulse (): void {
|
||||
avatarInst.pulse()
|
||||
@@ -112,8 +121,35 @@
|
||||
person?.personUuid !== undefined && $statusByUserStore.get(person.personUuid as AccountUuid)?.online === true
|
||||
</script>
|
||||
|
||||
{#if showStatus && person}
|
||||
<div class="relative">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="flex-presenter" use:tooltip={getPreviewPopup(person, showPreview)} on:click>
|
||||
{#if showStatus && person}
|
||||
<div class="relative">
|
||||
<AvatarInstance
|
||||
bind:this={avatarInst}
|
||||
{url}
|
||||
srcset={srcSet}
|
||||
{displayName}
|
||||
{size}
|
||||
{icon}
|
||||
{variant}
|
||||
{color}
|
||||
{bColor}
|
||||
bind:element
|
||||
{adaptiveName}
|
||||
{disabled}
|
||||
{style}
|
||||
{clickable}
|
||||
withStatus
|
||||
/>
|
||||
<div
|
||||
class="hulyAvatar-statusMarker {statusSize ?? size} {style}"
|
||||
class:online={isOnline}
|
||||
class:offline={!isOnline}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<AvatarInstance
|
||||
bind:this={avatarInst}
|
||||
{url}
|
||||
@@ -125,23 +161,16 @@
|
||||
{color}
|
||||
{bColor}
|
||||
bind:element
|
||||
withStatus
|
||||
{adaptiveName}
|
||||
{disabled}
|
||||
{style}
|
||||
{clickable}
|
||||
/>
|
||||
<div class="hulyAvatar-statusMarker {size}" class:online={isOnline} class:offline={!isOnline} />
|
||||
</div>
|
||||
{:else}
|
||||
<AvatarInstance
|
||||
bind:this={avatarInst}
|
||||
{url}
|
||||
srcset={srcSet}
|
||||
{displayName}
|
||||
{size}
|
||||
{icon}
|
||||
{variant}
|
||||
{color}
|
||||
{bColor}
|
||||
bind:element
|
||||
{adaptiveName}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.flex-presenter {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
// 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
|
||||
@@ -17,6 +17,9 @@
|
||||
import { themeStore } from '@hcengineering/theme'
|
||||
import { AnySvelteComponent, ColorDefinition, Icon, IconSize, resizeObserver } from '@hcengineering/ui'
|
||||
import AvatarIcon from './icons/Avatar.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let url: string | undefined
|
||||
export let srcset: string | undefined
|
||||
@@ -29,6 +32,13 @@
|
||||
export let withStatus: boolean = false
|
||||
export let element: HTMLElement
|
||||
export let adaptiveName: boolean = false
|
||||
export let disabled: boolean = false
|
||||
export let style: 'modern' | undefined = undefined
|
||||
export let clickable: boolean = false
|
||||
|
||||
function handleClick (): void {
|
||||
dispatch('click')
|
||||
}
|
||||
|
||||
export function pulse (): void {
|
||||
if (element === undefined) return
|
||||
@@ -50,21 +60,30 @@
|
||||
}
|
||||
|
||||
$: hasImg = url != null && !imgError
|
||||
$: background =
|
||||
!hasImg && disabled
|
||||
? 'var(--theme-popup-deactivated)'
|
||||
: color && !hasImg
|
||||
? color.icon
|
||||
: 'var(--theme-button-default)'
|
||||
</script>
|
||||
|
||||
{#if (size === 'full' || adaptiveName) && !url && displayName && displayName !== ''}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
bind:this={element}
|
||||
class="hulyAvatar-container hulyAvatarSize-{size} {variant}"
|
||||
class="hulyAvatar-container hulyAvatarSize-{size} {variant} {style} {clickable ? 'clickable' : ''}"
|
||||
class:no-img={!hasImg && color}
|
||||
class:bordered={!hasImg && color === undefined}
|
||||
class:border={bColor !== undefined}
|
||||
class:withStatus
|
||||
style:--border-color={bColor ?? 'var(--primary-button-default)'}
|
||||
style:background-color={color && !hasImg ? color.icon : 'var(--theme-button-default)'}
|
||||
style:background-color={background}
|
||||
use:resizeObserver={(element) => {
|
||||
fontSize = element.clientWidth * 0.6
|
||||
}}
|
||||
on:click={handleClick}
|
||||
>
|
||||
<div
|
||||
class="ava-text"
|
||||
@@ -74,22 +93,31 @@
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
bind:this={element}
|
||||
class="hulyAvatar-container hulyAvatarSize-{size} stat {variant}"
|
||||
class="hulyAvatar-container hulyAvatarSize-{size} {variant} {style} {clickable ? 'clickable' : ''}"
|
||||
class:no-img={!hasImg && color}
|
||||
class:bordered={!hasImg && color === undefined}
|
||||
class:border={bColor !== undefined}
|
||||
class:withStatus
|
||||
style:--border-color={bColor ?? 'var(--primary-button-default)'}
|
||||
style:background-color={color && !hasImg ? color.icon : 'var(--theme-button-default)'}
|
||||
style:background-color={background}
|
||||
on:click={handleClick}
|
||||
>
|
||||
{#if url && !imgError}
|
||||
<img class="hulyAvatarSize-{size} ava-image" src={url} {srcset} alt={''} on:error={handleImgError} />
|
||||
<img
|
||||
class="hulyAvatarSize-{size} ava-image {disabled ? 'disabled' : ''}"
|
||||
src={url}
|
||||
{srcset}
|
||||
alt={''}
|
||||
on:error={handleImgError}
|
||||
/>
|
||||
{:else if displayName && displayName !== ''}
|
||||
<div
|
||||
class="ava-text"
|
||||
style:color={color ? color.iconText : 'var(--primary-button-color)'}
|
||||
style:color={disabled ? 'white' : color ? color.iconText : 'var(--primary-button-color)'}
|
||||
data-name={displayName.toLocaleUpperCase()}
|
||||
/>
|
||||
{:else}
|
||||
@@ -99,3 +127,17 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.clickable > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
.clickable img {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -93,18 +93,16 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="cursor-pointer" on:click|self={showSelectionPopup}>
|
||||
<AvatarComponent
|
||||
{direct}
|
||||
{size}
|
||||
{icon}
|
||||
person={{
|
||||
avatarType: selectedAvatarType,
|
||||
avatarProps: selectedAvatarProps,
|
||||
avatar: selectedAvatar
|
||||
}}
|
||||
{name}
|
||||
/>
|
||||
</div>
|
||||
<AvatarComponent
|
||||
{direct}
|
||||
{size}
|
||||
{icon}
|
||||
person={{
|
||||
avatarType: selectedAvatarType,
|
||||
avatarProps: selectedAvatarProps,
|
||||
avatar: selectedAvatar
|
||||
}}
|
||||
{name}
|
||||
clickable
|
||||
on:click={showSelectionPopup}
|
||||
/>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Employee, Person } from '@hcengineering/contact'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import { Ref, WithLookup } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import ui, { IconSize, LabelAndProps } from '@hcengineering/ui'
|
||||
import { PersonLabelTooltip, employeeByIdStore, personByIdStore } from '..'
|
||||
import ui, { IconSize } from '@hcengineering/ui'
|
||||
import { PersonLabelTooltip, personByIdStore } from '..'
|
||||
import PersonPresenter from '../components/PersonPresenter.svelte'
|
||||
import contact from '../plugin'
|
||||
import EmployeePreviewPopup from './EmployeePreviewPopup.svelte'
|
||||
import { getPreviewPopup } from './person/utils'
|
||||
|
||||
export let value: Ref<Person> | WithLookup<Person> | null | undefined
|
||||
export let showPopup: boolean = true
|
||||
@@ -35,24 +35,13 @@
|
||||
$: employeeValue = person != null ? h.as(person, contact.mixin.Employee) : undefined
|
||||
|
||||
$: active = employeeValue?.active ?? false
|
||||
|
||||
function getPreviewPopup (active: boolean, value: Employee | undefined): LabelAndProps | undefined {
|
||||
if (!active || value === undefined || !showPopup) {
|
||||
return undefined
|
||||
}
|
||||
return {
|
||||
component: EmployeePreviewPopup,
|
||||
props: { employeeId: value._id },
|
||||
timeout: 300
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<PersonPresenter
|
||||
value={employeeValue}
|
||||
{tooltipLabels}
|
||||
onEdit={onEmployeeEdit}
|
||||
customTooltip={getPreviewPopup(active, employeeValue)}
|
||||
customTooltip={getPreviewPopup(employeeValue, showPopup)}
|
||||
{shouldShowAvatar}
|
||||
{shouldShowName}
|
||||
{avatarSize}
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { ModernButton, navigate, resizeObserver } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import view from '@hcengineering/view'
|
||||
import { getObjectLinkFragment } from '@hcengineering/view-resources'
|
||||
import { ComponentExtensions, getClient } from '@hcengineering/presentation'
|
||||
|
||||
import contact from '../plugin'
|
||||
import Avatar from './Avatar.svelte'
|
||||
import { employeeByIdStore, statusByUserStore } from '../utils'
|
||||
import { EmployeePresenter } from '../index'
|
||||
|
||||
export let employeeId: Ref<Employee>
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let employee: Employee | undefined = undefined
|
||||
|
||||
$: employee = $employeeByIdStore.get(employeeId)
|
||||
$: isOnline = employee?.personUuid !== undefined && $statusByUserStore.get(employee.personUuid)?.online === true
|
||||
|
||||
// const statusesQuery = createQuery()
|
||||
// let editable = false
|
||||
// let status: Status | undefined = undefined
|
||||
// $: editable = employeeId === me
|
||||
// statusesQuery.query(contact.class.Status, { attachedTo: employeeId }, (res) => {
|
||||
// status = res[0]
|
||||
// })
|
||||
|
||||
// function setStatus (): void {
|
||||
// if (!employee) return
|
||||
// showPopup(
|
||||
// EmployeeSetStatusPopup,
|
||||
// {
|
||||
// currentStatus: status
|
||||
// },
|
||||
// undefined,
|
||||
// () => {},
|
||||
// async (newStatus: Status) => {
|
||||
// if (status && newStatus) {
|
||||
// await client.updateDoc(contact.class.Status, status.space, status._id, { ...newStatus })
|
||||
// } else if (status && !newStatus) {
|
||||
// await client.removeDoc(contact.class.Status, status.space, status._id)
|
||||
// } else {
|
||||
// await client.addCollection(contact.class.Status, employee.space, employeeId, contact.mixin.Employee, 'statuses', {
|
||||
// name: newStatus.name,
|
||||
// dueDate: newStatus.dueDate
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// dispatch('close')
|
||||
// }
|
||||
|
||||
async function viewProfile (): Promise<void> {
|
||||
if (employee === undefined) return
|
||||
const panelComponent = hierarchy.classHierarchyMixin(employee._class as Ref<Class<Doc>>, view.mixin.ObjectPanel)
|
||||
const comp = panelComponent?.component ?? view.component.EditDoc
|
||||
const loc = await getObjectLinkFragment(hierarchy, employee, {}, comp)
|
||||
navigate(loc)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="root flex-col"
|
||||
use:resizeObserver={() => {
|
||||
dispatch('changeContent')
|
||||
}}
|
||||
>
|
||||
{#if employee}
|
||||
<div class="flex-presenter flex-gap-2 p-2">
|
||||
<Avatar size="large" person={employee} name={employee.name} />
|
||||
<span class="username">
|
||||
<EmployeePresenter value={employee} shouldShowAvatar={false} showPopup={false} compact />
|
||||
</span>
|
||||
<span class="hulyAvatar-statusMarker small relative mt-0-5" class:online={isOnline} class:offline={!isOnline} />
|
||||
</div>
|
||||
<div class="separator" />
|
||||
<div class="flex-presenter flex-gap-2 p-2">
|
||||
<ComponentExtensions extension={contact.extension.EmployeePopupActions} props={{ employee }} />
|
||||
<ModernButton
|
||||
label={contact.string.ViewProfile}
|
||||
icon={contact.icon.Person}
|
||||
size="small"
|
||||
iconSize="small"
|
||||
on:click={viewProfile}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!--{#if status}-->
|
||||
<!-- <div class="pb-2">-->
|
||||
<!-- <Label label={contact.string.Status} />-->
|
||||
<!-- <div class="flex-row-stretch statusContainer">-->
|
||||
<!-- <div class="pr-2">-->
|
||||
<!-- <EmployeeStatusPresenter {employee} withTooltip={false} />-->
|
||||
<!-- </div>-->
|
||||
<!-- {#if editable}-->
|
||||
<!-- <div class="setStatusButton">-->
|
||||
<!-- <Button icon={Edit} title={contact.string.SetStatus} on:click={setStatus} />-->
|
||||
<!-- </div>-->
|
||||
<!-- {/if}-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!--{:else if editable}-->
|
||||
<!-- <!– svelte-ignore a11y-click-events-have-key-events –>-->
|
||||
<!-- <!– svelte-ignore a11y-no-static-element-interactions –>-->
|
||||
<!-- <div class="flex-row-stretch over-underline pb-2" on:click={setStatus}>-->
|
||||
<!-- <Label label={contact.string.SetStatus} />-->
|
||||
<!-- </div>-->
|
||||
<!--{/if}-->
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: auto;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
max-width: 30rem;
|
||||
background: var(--theme-popup-color);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.separator {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: var(--global-ui-BorderColor);
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
//.statusContainer {
|
||||
// .setStatusButton {
|
||||
// opacity: 0;
|
||||
// }
|
||||
//
|
||||
// &:hover .setStatusButton {
|
||||
// opacity: 1;
|
||||
// }
|
||||
//}
|
||||
</style>
|
||||
@@ -209,17 +209,7 @@
|
||||
<div class="flex-col-center gapV-4 mx-6">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="cursor-pointer"
|
||||
on:click|self={(e) => {
|
||||
if (imageOnly) {
|
||||
handleImageAvatarClick()
|
||||
} else {
|
||||
if (selectedAvatarType === AvatarType.IMAGE) handleImageAvatarClick()
|
||||
else if (selectedAvatarType === AvatarType.COLOR) showColorPopup(e)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="cursor-pointer">
|
||||
<AvatarComponent
|
||||
person={{
|
||||
avatarType: selectedAvatarType,
|
||||
@@ -230,6 +220,15 @@
|
||||
size={'2x-large'}
|
||||
{icon}
|
||||
{name}
|
||||
clickable
|
||||
on:click={(e) => {
|
||||
if (imageOnly) {
|
||||
void handleImageAvatarClick()
|
||||
} else {
|
||||
if (selectedAvatarType === AvatarType.IMAGE) void handleImageAvatarClick()
|
||||
else if (selectedAvatarType === AvatarType.COLOR) showColorPopup(e)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<TabList
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<!--
|
||||
// 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 { Label } from '@hcengineering/ui'
|
||||
import contact from '@hcengineering/contact'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
|
||||
export let header: IntlString = contact.string.DeactivatedAccount
|
||||
</script>
|
||||
|
||||
<div class="deactivated-header">
|
||||
<div class="deactivated-label">
|
||||
<div class="deactivated-text">
|
||||
<span class="text-normal font-medium content-color">
|
||||
<Label label={header} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.deactivated-text {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.deactivated-label {
|
||||
flex: 1 1 0;
|
||||
height: 2rem;
|
||||
border-radius: 0.375rem;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.deactivated-header {
|
||||
width: 22.5rem;
|
||||
align-self: stretch;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
background: var(--theme-popup-deactivated);
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
display: inline-flex;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,158 @@
|
||||
<!--
|
||||
// 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 { Employee } from '@hcengineering/contact'
|
||||
import { AccountUuid, Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { ButtonIcon, Loading, 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, getAccountClient } from '../../utils'
|
||||
import { EmployeePresenter } from '../../index'
|
||||
import TimePresenter from './TimePresenter.svelte'
|
||||
import DeactivatedHeader from './DeactivatedHeader.svelte'
|
||||
|
||||
export let _id: Ref<Employee>
|
||||
export let disabled: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let employee: Employee | undefined = undefined
|
||||
let timezone: string | undefined = undefined
|
||||
let isTimezoneLoading = true
|
||||
|
||||
$: employee = $employeeByIdStore.get(_id)
|
||||
$: void loadPersonTimezone(employee?.personUuid)
|
||||
|
||||
async function viewProfile (): Promise<void> {
|
||||
if (employee === undefined) return
|
||||
const panelComponent = hierarchy.classHierarchyMixin(employee._class as Ref<Class<Doc>>, view.mixin.ObjectPanel)
|
||||
const comp = panelComponent?.component ?? view.component.EditDoc
|
||||
const loc = await getObjectLinkFragment(hierarchy, employee, {}, comp)
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
async function loadPersonTimezone (personId: AccountUuid | undefined): Promise<void> {
|
||||
if (personId === undefined) return
|
||||
isTimezoneLoading = true
|
||||
try {
|
||||
const accountInfo = await getAccountClient().getAccountInfo(personId)
|
||||
timezone = accountInfo.timezone
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
isTimezoneLoading = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModernProfilePopup {disabled}>
|
||||
<div slot="header">
|
||||
{#if disabled}
|
||||
<div class="flex-presenter">
|
||||
<DeactivatedHeader>
|
||||
<div slot="actions">
|
||||
<div class="flex-presenter flex-gap-2 flex-center">
|
||||
<ComponentExtensions
|
||||
extension={contact.extension.EmployeePopupActions}
|
||||
props={{ employee, icon: contact.icon.Chat, type: 'type-button-icon' }}
|
||||
/>
|
||||
<ButtonIcon icon={contact.icon.User} size="small" iconSize="small" on:click={viewProfile} />
|
||||
</div>
|
||||
</div>
|
||||
</DeactivatedHeader>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div slot="content">
|
||||
{#if !disabled}
|
||||
<div class="flex-presenter cursor-default flex-gap-2 p-2">
|
||||
<Avatar
|
||||
size="large"
|
||||
person={employee}
|
||||
name={employee?.name}
|
||||
{disabled}
|
||||
showStatus
|
||||
statusSize="medium"
|
||||
style="modern"
|
||||
clickable
|
||||
on:click={viewProfile}
|
||||
/>
|
||||
<div class="flex-col flex-gap-0-5">
|
||||
<EmployeePresenter value={employee} shouldShowAvatar={false} showPopup={false} compact accent />
|
||||
<span class="flex-presenter cursor-default">
|
||||
{#if isTimezoneLoading}
|
||||
<Loading size="small" />
|
||||
{:else if timezone != null}
|
||||
<TimePresenter {timezone} />
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="py-1">
|
||||
<ComponentExtensions
|
||||
extension={contact.extension.PersonAchievementsPresenter}
|
||||
props={{
|
||||
personId: _id
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex-presenter flex-gap-2 p-2">
|
||||
<div class="flex-presenter">
|
||||
<Avatar
|
||||
size="large"
|
||||
person={employee}
|
||||
name={employee?.name}
|
||||
{disabled}
|
||||
style="modern"
|
||||
clickable
|
||||
on:click={viewProfile}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-col">
|
||||
<EmployeePresenter value={employee} shouldShowAvatar={false} showPopup={false} compact accent />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div slot="actions">
|
||||
{#if !disabled}
|
||||
<div class="flex-presenter flex-gap-2 flex-center">
|
||||
<div class="button-container">
|
||||
<ComponentExtensions
|
||||
extension={contact.extension.EmployeePopupActions}
|
||||
props={{ employee, icon: contact.icon.Chat, type: 'type-button-icon' }}
|
||||
/>
|
||||
</div>
|
||||
<div class="button-container">
|
||||
<ButtonIcon icon={contact.icon.User} size="small" iconSize="small" on:click={viewProfile} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</ModernProfilePopup>
|
||||
|
||||
<style lang="scss">
|
||||
.button-container {
|
||||
border-radius: var(--small-BorderRadius);
|
||||
display: flex;
|
||||
background-color: var(--theme-button-container-color);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,127 @@
|
||||
<!--
|
||||
// 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 { getMetadata } from '@hcengineering/platform'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { getCurrentTheme, isThemeDark } from '@hcengineering/theme'
|
||||
|
||||
export let disabled: boolean = false
|
||||
const backgroundImage = isThemeDark(getCurrentTheme())
|
||||
? contact.image.ProfileBackground
|
||||
: contact.image.ProfileBackgroundLight
|
||||
|
||||
$: style = disabled ? 'gray' : ''
|
||||
</script>
|
||||
|
||||
<div class="user-popup {style}">
|
||||
<slot name="header" />
|
||||
{#if !disabled}
|
||||
<div class="image-container">
|
||||
<div
|
||||
class="img {style}"
|
||||
style={`background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 25%, var(--theme-popup-color) 95%), url("${getMetadata(backgroundImage)}"); background-size: cover;`}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="action-container">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.user-popup {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 0;
|
||||
width: 22.5rem;
|
||||
}
|
||||
.content,
|
||||
.content {
|
||||
padding-left: 0.25rem;
|
||||
isolation: isolate;
|
||||
order: 0;
|
||||
width: 100%;
|
||||
|
||||
.main-container {
|
||||
padding: 1rem 0;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
position: absolute;
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
right: 1rem;
|
||||
top: 1rem;
|
||||
border-radius: 0.375rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.image-container {
|
||||
/* image-container */
|
||||
width: 100%;
|
||||
min-height: 9rem;
|
||||
display: flex;
|
||||
|
||||
/* Inside auto layout */
|
||||
flex: none;
|
||||
order: 0;
|
||||
align-self: stretch;
|
||||
flex-grow: 0;
|
||||
margin: -1.875rem 0;
|
||||
z-index: -100;
|
||||
}
|
||||
|
||||
.action-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex: none;
|
||||
align-self: stretch;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.img {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: 9rem;
|
||||
|
||||
border-radius: 0;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
z-index: -100;
|
||||
|
||||
&.gray {
|
||||
filter: gray;
|
||||
-webkit-filter: grayscale(1);
|
||||
filter: grayscale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import { Ref, WithLookup } from '@hcengineering/core'
|
||||
import { tooltip } from '@hcengineering/ui'
|
||||
|
||||
import { personByIdStore } from '../..'
|
||||
import { getPreviewPopup } from './utils'
|
||||
|
||||
export let value: Ref<Person> | WithLookup<Person> | null | undefined
|
||||
export let showPopup: boolean = true
|
||||
|
||||
$: person = typeof value === 'string' ? ($personByIdStore.get(value) as Person) : (value as Person)
|
||||
</script>
|
||||
|
||||
<div class="flex" use:tooltip={getPreviewPopup(person, showPopup)}>
|
||||
<slot />
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
<!--
|
||||
// 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 { Icon, Label } from '@hcengineering/ui'
|
||||
|
||||
import contact from '@hcengineering/contact'
|
||||
|
||||
export let timezone: string
|
||||
|
||||
function displayTimeInTimezone (timezone: string): string {
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
timeZone: timezone,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
}
|
||||
|
||||
const formatter = new Intl.DateTimeFormat([], options)
|
||||
const now = new Date()
|
||||
return formatter.format(now)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="time-container">
|
||||
<div class="clock-icon">
|
||||
<Icon icon={contact.icon.Clock} size={'x-small'} />
|
||||
</div>
|
||||
<div class="text-normal font-normal content-color select-text">
|
||||
<span class="time-text-span">{displayTimeInTimezone(timezone)} <Label label={contact.string.LocalTime} /></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.clock-icon {
|
||||
position: relative;
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
|
||||
.time-container {
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
display: inline-flex;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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 { Person } from '@hcengineering/contact'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import type { LabelAndProps } from '@hcengineering/ui'
|
||||
import contact from '../../plugin'
|
||||
import EmployeePreviewPopup from './EmployeePreviewPopup.svelte'
|
||||
import type { Class, Ref } from '@hcengineering/core'
|
||||
|
||||
const client = getClient()
|
||||
const h = client.getHierarchy()
|
||||
|
||||
export function getPreviewPopup (
|
||||
person: Person | { _id?: Ref<Person>, _class?: Ref<Class<Person>> } | undefined,
|
||||
showPopup: boolean
|
||||
): LabelAndProps | undefined {
|
||||
if (person?._id === undefined || person?._class === undefined || !showPopup) return
|
||||
const isPerson = person != null && h.isDerived(person._class, contact.class.Person)
|
||||
const employee = isPerson ? h.as(person as Person, contact.mixin.Employee) : undefined
|
||||
return {
|
||||
component: EmployeePreviewPopup,
|
||||
props: { _id: person._id, classList: ['modern'], disabled: employee?.active === false },
|
||||
timeout: 300,
|
||||
style: 'modern',
|
||||
noArrow: true
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,7 @@ import IconAddMember from './components/icons/AddMember.svelte'
|
||||
import ExpandRightDouble from './components/icons/ExpandRightDouble.svelte'
|
||||
import IconMembers from './components/icons/Members.svelte'
|
||||
import ContactNamePresenter from './components/ContactNamePresenter.svelte'
|
||||
import PersonPreviewProvider from './components/person/PersonPreviewProvider.svelte'
|
||||
|
||||
import { get } from 'svelte/store'
|
||||
import { canResendInvitation } from './visibilityTester'
|
||||
@@ -202,7 +203,8 @@ export {
|
||||
UserInfo,
|
||||
UsersList,
|
||||
UsersPopup,
|
||||
PersonIdFilter
|
||||
PersonIdFilter,
|
||||
PersonPreviewProvider
|
||||
}
|
||||
|
||||
const toObjectSearchResult = (e: WithLookup<Contact>): ObjectSearchResult => ({
|
||||
|
||||
@@ -61,7 +61,7 @@ import core, {
|
||||
} from '@hcengineering/core'
|
||||
import login from '@hcengineering/login'
|
||||
import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
|
||||
import { getEmbeddedLabel, getMetadata, getResource, type IntlString, translate } from '@hcengineering/platform'
|
||||
import { getMetadata, getResource, type IntlString, translate } from '@hcengineering/platform'
|
||||
import presentation, { createQuery, getClient, onClient } from '@hcengineering/presentation'
|
||||
import { type TemplateDataProvider } from '@hcengineering/templates'
|
||||
import {
|
||||
@@ -78,6 +78,7 @@ import { type LocationData } from '@hcengineering/workbench'
|
||||
import { derived, get, type Readable, writable } from 'svelte/store'
|
||||
|
||||
import contact from './plugin'
|
||||
import { getPreviewPopup } from './components/person/utils'
|
||||
|
||||
export function formatDate (dueDateMs: Timestamp): string {
|
||||
return new Date(dueDateMs).toLocaleString('default', {
|
||||
@@ -545,13 +546,7 @@ export async function contactTitleProvider (client: Client, ref: Ref<Contact>, d
|
||||
}
|
||||
|
||||
export function getPersonTooltip (client: Client, value: Person | null | undefined): LabelAndProps | undefined {
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
return value == null
|
||||
? undefined
|
||||
: {
|
||||
label: getEmbeddedLabel(getName(hierarchy, value))
|
||||
}
|
||||
return value == null ? undefined : getPreviewPopup(value, true)
|
||||
}
|
||||
|
||||
export async function channelIdentifierProvider (client: Client, ref: Ref<Channel>, doc?: Channel): Promise<string> {
|
||||
|
||||
@@ -292,7 +292,14 @@ export const contactPlugin = plugin(contactId, {
|
||||
Profile: '' as Asset,
|
||||
KickUser: '' as Asset,
|
||||
Contacts: '' as Asset,
|
||||
Viber: '' as Asset
|
||||
Viber: '' as Asset,
|
||||
Clock: '' as Asset,
|
||||
Chat: '' as Asset,
|
||||
User: '' as Asset
|
||||
},
|
||||
image: {
|
||||
ProfileBackground: '' as Asset,
|
||||
ProfileBackgroundLight: '' as Asset
|
||||
},
|
||||
space: {
|
||||
Contacts: '' as Ref<Space>
|
||||
@@ -327,7 +334,9 @@ export const contactPlugin = plugin(contactId, {
|
||||
SocialIds: '' as IntlString,
|
||||
Type: '' as IntlString,
|
||||
Confirmed: '' as IntlString,
|
||||
UserProfile: '' as IntlString
|
||||
UserProfile: '' as IntlString,
|
||||
DeactivatedAccount: '' as IntlString,
|
||||
LocalTime: '' as IntlString
|
||||
},
|
||||
viewlet: {
|
||||
TableMember: '' as Ref<Viewlet>,
|
||||
@@ -361,7 +370,8 @@ export const contactPlugin = plugin(contactId, {
|
||||
MentionCommonNotificationType: '' as Ref<Doc>
|
||||
},
|
||||
extension: {
|
||||
EmployeePopupActions: '' as ComponentExtensionId
|
||||
EmployeePopupActions: '' as ComponentExtensionId,
|
||||
PersonAchievementsPresenter: '' as ComponentExtensionId
|
||||
},
|
||||
store: {
|
||||
Permissions: '' as Resource<Readable<PermissionsStore>>
|
||||
|
||||
@@ -132,6 +132,7 @@
|
||||
"RemoveRelationConfirmation": "Chcete odstranit vztah?",
|
||||
"MasterDetail": "Hlavní-detailní",
|
||||
"Tree": "Strom",
|
||||
"Document": "Dokument"
|
||||
"Document": "Dokument",
|
||||
"Loading": "Načítání..."
|
||||
}
|
||||
}
|
||||
@@ -132,6 +132,7 @@
|
||||
"RemoveRelationConfirmation": "Möchten Sie die Beziehung entfernen?",
|
||||
"MasterDetail": "Master-detail",
|
||||
"Tree": "Baum",
|
||||
"Document": "Dokument"
|
||||
"Document": "Dokument",
|
||||
"Loading": "Laden..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@
|
||||
"RemoveRelationConfirmation": "Do you want to remove relation?",
|
||||
"MasterDetail": "Master-detail",
|
||||
"Tree": "Tree",
|
||||
"Document": "Document"
|
||||
"Document": "Document",
|
||||
"Loading": "Loading..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@
|
||||
"RemoveRelationConfirmation": "¿Quieres eliminar la relación?",
|
||||
"MasterDetail": "Maestro-detalle",
|
||||
"Tree": "Árbol",
|
||||
"Document": "Documento"
|
||||
"Document": "Documento",
|
||||
"Loading": "Cargando..."
|
||||
}
|
||||
}
|
||||
@@ -127,6 +127,7 @@
|
||||
"RemoveRelationConfirmation": "Voulez-vous supprimer la relation ?",
|
||||
"MasterDetail": "Maître-détail",
|
||||
"Tree": "Arbre",
|
||||
"Document": "Document"
|
||||
"Document": "Document",
|
||||
"Loading": "Chargement..."
|
||||
}
|
||||
}
|
||||