Merge pull request #324 from hcengineering/activity-support

Activity support
This commit is contained in:
Andrey Sobolev 2021-11-19 00:29:45 +07:00 committed by GitHub
commit 5d309d2723
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 3313 additions and 1619 deletions

View File

@ -40,14 +40,14 @@ jobs:
run: node common/scripts/install-run-rush.js rebuild --verbose
- name: Checking svelte sources...
run: common/scripts/each.sh svelte-check
run: node common/scripts/install-run-rush.js svelte-check
- name: Testing...
uses: paambaati/codeclimate-action@v2.7.5
env:
CC_TEST_REPORTER_ID: 346a83855d7124e9da8e6ec36a4fd01dc432f5cdc755eb28da7d9c44da9d1142
with:
coverageCommand: common/scripts/each.sh test
coverageCommand: node common/scripts/install-run-rush.js test
coverageLocations: |
${{github.workspace}}/packages/*/coverage/lcov.info:lcov
${{github.workspace}}/server/*/coverage/lcov.info:lcov

1
.gitignore vendored
View File

@ -70,3 +70,4 @@ common/autoinstallers/*/.npmrc
**/.rush/temp/
bundle.js
dist
tsconfig.tsbuildinfo

View File

@ -184,7 +184,28 @@
"enableParallelism": true,
"incremental": true,
"ignoreMissingScript": true,
"watchForChanges": true
"watchForChanges": true,
"safeForSimultaneousRushProcesses": true
},
{
"commandKind": "bulk",
"name": "svelte-check",
"summary": "svelte-check",
"description": "Perform svelte-check",
"enableParallelism": true,
"incremental": true,
"ignoreDependencyOrder": false,
"ignoreMissingScript": true
},
{
"commandKind": "bulk",
"name": "test",
"summary": "test",
"description": "Perform testing",
"enableParallelism": true,
"incremental": true,
"ignoreDependencyOrder": false,
"ignoreMissingScript": true
},
],

File diff suppressed because it is too large Load Diff

View File

@ -1,37 +0,0 @@
#!/bin/bash
#
# Copyright © 2020 Anticrm Platform Contributors.
#
# 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.
#
set -e
sourceDir=$(dirname "$0")
sourceDir=$(realpath "$sourceDir")
runScript=$sourceDir/install-run-rushx.js
roots=$(node $sourceDir/install-run-rush.js list -f --json | grep "fullPath" | cut -f 2 -d ':' | cut -f 2 -d '"')
for i in $roots
do
pushd ${i}
checkScript=$(cat package.json | grep \"$1\": | wc -l)
if [ $checkScript -gt 0 ]; then
node ${runScript} $@
retVal=$?
if [ $retVal -ne 0 ]; then
echo "Error"
exit $retVal
fi
fi
popd
done

View File

@ -68,6 +68,9 @@
"@anticrm/server-recruit": "~0.6.1",
"@anticrm/server-recruit-resources": "~0.6.0",
"@anticrm/server-view": "~0.6.0",
"@anticrm/server-view-resources": "~0.6.0"
"@anticrm/server-view-resources": "~0.6.0",
"@anticrm/activity": "~0.6.0",
"@anticrm/activity-assets": "~0.6.0",
"@anticrm/activity-resources": "~0.6.0"
}
}

View File

@ -23,6 +23,7 @@ import { taskId } from '@anticrm/task'
import { contactId } from '@anticrm/contact'
import { chunterId } from '@anticrm/chunter'
import { recruitId } from '@anticrm/recruit'
import { activityId } from '@anticrm/activity'
import { serverChunterId } from '@anticrm/server-chunter'
import { serverRecruitId } from '@anticrm/server-recruit'
@ -34,6 +35,7 @@ import '@anticrm/view-assets'
import '@anticrm/chunter-assets'
import '@anticrm/contact-assets'
import '@anticrm/recruit-assets'
import '@anticrm/activity-assets'
import { setMetadata } from '@anticrm/platform'
@ -60,4 +62,5 @@ export function configurePlatform() {
addLocation(contactId, () => import(/* webpackChunkName: "contact" */ '@anticrm/contact-resources'))
addLocation(chunterId, () => import(/* webpackChunkName: "chunter" */ '@anticrm/chunter-resources'))
addLocation(recruitId, () => import(/* webpackChunkName: "recruit" */ '@anticrm/recruit-resources'))
addLocation(activityId, () => import(/*webpackChunkName: "activity" */ '@anticrm/activity-resources'))
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/model-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/model-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

View File

@ -0,0 +1,29 @@
{
"name": "@anticrm/model-activity",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src"
},
"devDependencies": {
"@anticrm/model-rig":"~0.6.0",
"@typescript-eslint/eslint-plugin":"4",
"eslint-plugin-import":"2",
"eslint-plugin-promise":"4",
"eslint-plugin-node":"11",
"eslint":"^7.32.0"
},
"dependencies": {
"@anticrm/core": "~0.6.11",
"@anticrm/model": "~0.6.0",
"@anticrm/contact": "~0.6.2",
"@anticrm/platform": "~0.6.5",
"@anticrm/model-core": "~0.6.0",
"@anticrm/activity": "~0.6.0",
"@anticrm/ui": "~0.6.0"
}
}

View File

@ -0,0 +1,39 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// 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 { TxViewlet } from '@anticrm/activity'
import activity from '@anticrm/activity'
import core, { Class, Doc, DocumentQuery, DOMAIN_MODEL, Ref, Tx } from '@anticrm/core'
import { Builder, Model } from '@anticrm/model'
import { TDoc } from '@anticrm/model-core'
import type { Asset, IntlString } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
@Model(activity.class.TxViewlet, core.class.Doc, DOMAIN_MODEL)
export class TTxViewlet extends TDoc implements TxViewlet {
icon!: Asset
objectClass!: Ref<Class<Doc>>
txClass!: Ref<Class<Tx>>
// Component to display on.
component!: AnyComponent
// Filter
match!: DocumentQuery<Tx>
label!: IntlString
display!: 'inline' | 'content' | 'emphasized'
}
export function createModel (builder: Builder): void {
builder.createModel(TTxViewlet)
}

View File

@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
}
}

View File

@ -33,6 +33,7 @@
"@anticrm/model-server-core": "~0.6.0",
"@anticrm/model-server-chunter": "~0.6.0",
"@anticrm/model-server-recruit": "~0.6.0",
"@anticrm/model-server-view": "~0.6.0"
"@anticrm/model-server-view": "~0.6.0",
"@anticrm/model-activity": "~0.6.0"
}
}

View File

@ -27,12 +27,14 @@ import { createModel as serverCoreModel } from '@anticrm/model-server-core'
import { createModel as serverChunterModel } from '@anticrm/model-server-chunter'
import { createModel as serverRecruitModel } from '@anticrm/model-server-recruit'
import { createModel as serverViewModel } from '@anticrm/model-server-view'
import { createModel as activityModel } from '@anticrm/model-activity'
import { createDemo } from '@anticrm/model-demo'
const builder = new Builder()
coreModel(builder)
activityModel(builder)
viewModel(builder)
workbenchModel(builder)
contactModel(builder)

View File

@ -27,6 +27,7 @@
"@anticrm/platform": "~0.6.5",
"@anticrm/model-core": "~0.6.0",
"@anticrm/model-view": "~0.6.0",
"@anticrm/model-workbench": "~0.6.1"
"@anticrm/model-workbench": "~0.6.1",
"@anticrm/activity": "~0.6.0"
}
}

View File

@ -20,6 +20,7 @@ import { IndexKind } from '@anticrm/core'
import core, { TSpace, TDoc, TAttachedDoc } from '@anticrm/model-core'
import type { Backlink, Channel, Message, Comment, Attachment } from '@anticrm/chunter'
import type { AnyComponent } from '@anticrm/ui'
import activity from '@anticrm/activity'
import workbench from '@anticrm/model-workbench'
@ -124,6 +125,24 @@ export function createModel (builder: Builder): void {
builder.mixin(chunter.class.Attachment, core.class.Class, view.mixin.AttributePresenter, {
presenter: chunter.component.AttachmentPresenter
})
builder.createDoc(activity.class.TxViewlet, core.space.Model, {
objectClass: chunter.class.Comment,
icon: chunter.icon.Chunter,
txClass: core.class.TxCreateDoc,
component: chunter.activity.TxCommentCreate,
label: chunter.string.LeftComment,
display: 'content'
}, chunter.ids.TxCommentCreate)
builder.createDoc(activity.class.TxViewlet, core.space.Model, {
objectClass: chunter.class.Attachment,
icon: chunter.icon.Attachment,
txClass: core.class.TxCreateDoc,
component: chunter.activity.TxAttachmentCreate,
label: chunter.string.AddAttachment,
display: 'emphasized'
}, chunter.ids.TxAttachmentCreate)
}
export default chunter

View File

@ -20,6 +20,7 @@ import { mergeIds } from '@anticrm/platform'
import type { Ref } from '@anticrm/core'
import { ViewletDescriptor } from '@anticrm/view'
import type { AnyComponent } from '@anticrm/ui'
import type { TxViewlet } from '@anticrm/activity'
export default mergeIds(chunterId, chunter, {
component: {
@ -27,9 +28,19 @@ export default mergeIds(chunterId, chunter, {
AttachmentPresenter: '' as AnyComponent
},
string: {
ApplicationLabelChunter: '' as IntlString
ApplicationLabelChunter: '' as IntlString,
LeftComment: '' as IntlString,
AddAttachment: '' as IntlString
},
viewlet: {
Chat: '' as Ref<ViewletDescriptor>
},
ids: {
TxCommentCreate: '' as Ref<TxViewlet>,
TxAttachmentCreate: '' as Ref<TxViewlet>
},
activity: {
TxCommentCreate: '' as AnyComponent,
TxAttachmentCreate: '' as AnyComponent
}
})

View File

@ -22,6 +22,7 @@
"@anticrm/model": "~0.6.0",
"@anticrm/platform": "~0.6.5",
"@anticrm/model-recruit": "~0.6.0",
"@anticrm/model-contact": "~0.6.1"
"@anticrm/model-contact": "~0.6.1",
"@anticrm/contact": "~0.6.2"
}
}

View File

@ -14,26 +14,27 @@
// limitations under the License.
//
import { Employee, EmployeeAccount } from '@anticrm/contact'
import core, { generateId, Ref } from '@anticrm/core'
import { Builder } from '@anticrm/model'
import core, { generateId } from '@anticrm/core'
import contact from '@anticrm/model-contact'
import recruit from '@anticrm/model-recruit'
export function createDemo (builder: Builder): void {
const rosamund = generateId()
const account: Ref<EmployeeAccount> = generateId()
builder.createDoc(contact.class.Employee, contact.space.Employee, {
name: 'Chen,Rosamund',
city: 'Mountain View',
channels: []
}, rosamund)
}, rosamund, account)
builder.createDoc(contact.class.EmployeeAccount, core.space.Model, {
builder.createDoc<EmployeeAccount>(contact.class.EmployeeAccount, core.space.Model, {
email: 'rosamund@hc.engineering',
employee: rosamund as any,
employee: rosamund as Ref<Employee>,
name: 'Chen,Rosamund'
})
}, account, account)
builder.createDoc(recruit.class.Candidate, recruit.space.CandidatesPublic, {
name: 'P.,Andrey',
@ -45,7 +46,7 @@ export function createDemo (builder: Builder): void {
value: 'andrey@hc.engineering'
}
]
})
}, undefined, account)
builder.createDoc(recruit.class.Candidate, recruit.space.CandidatesPublic, {
name: 'M.,Marina',
@ -57,7 +58,7 @@ export function createDemo (builder: Builder): void {
value: 'marina@hc.engineering'
}
]
})
}, undefined, account)
builder.createDoc(recruit.class.Candidate, recruit.space.CandidatesPublic, {
name: 'P.,Alex',
@ -69,5 +70,5 @@ export function createDemo (builder: Builder): void {
value: 'alex@hc.engineering'
}
]
})
}, undefined, account)
}

View File

@ -31,6 +31,7 @@
"@anticrm/recruit-resources": "~0.6.0",
"@anticrm/chunter": "~0.6.0",
"@anticrm/model-chunter": "~0.6.0",
"@anticrm/view": "~0.6.0"
"@anticrm/view": "~0.6.0",
"@anticrm/activity": "~0.6.0"
}
}

View File

@ -18,6 +18,7 @@ import { Builder, Model, UX, Prop, TypeString, TypeBoolean } from '@anticrm/mode
import type { Ref, FindOptions, Doc, Domain, Class } from '@anticrm/core'
import core, { TSpace, TSpaceWithStates, TDocWithState } from '@anticrm/model-core'
import type { Vacancy, Candidates, Candidate, Applicant } from '@anticrm/recruit'
import activity from '@anticrm/activity'
import workbench from '@anticrm/model-workbench'
@ -194,6 +195,32 @@ export function createModel (builder: Builder): void {
attachedTo: recruit.class.Applicant,
sequence: 0
})
builder.createDoc(activity.class.TxViewlet, core.space.Model, {
objectClass: recruit.class.Applicant,
icon: recruit.icon.RecruitApplication,
txClass: core.class.TxCreateDoc,
component: recruit.activity.TxApplicantCreate,
label: recruit.string.TxApplicantCreate,
display: 'emphasized'
}, recruit.ids.TxApplicantCreate)
builder.createDoc(activity.class.TxViewlet, core.space.Model, {
objectClass: recruit.class.Applicant,
icon: recruit.icon.RecruitApplication,
txClass: core.class.TxUpdateDoc,
component: recruit.activity.TxApplicantUpdate,
label: recruit.string.TxApplicantUpdate,
display: 'inline'
}, recruit.ids.TxApplicantUpdate)
builder.createDoc(activity.class.TxViewlet, core.space.Model, {
objectClass: recruit.class.Candidate,
icon: recruit.icon.RecruitApplication,
txClass: core.class.TxCreateDoc,
label: recruit.string.TxCandidateCreate,
display: 'emphasized'
}, recruit.ids.TxCandidateCreate)
}
export { default } from './plugin'

View File

@ -20,6 +20,7 @@ import type { AnyComponent } from '@anticrm/ui'
import type { Action } from '@anticrm/view'
import { recruitId } from '@anticrm/recruit'
import recruit from '@anticrm/recruit-resources/src/plugin'
import { TxViewlet } from '../../chunter/node_modules/@anticrm/activity/lib'
export default mergeIds(recruitId, recruit, {
action: {
@ -32,7 +33,10 @@ export default mergeIds(recruitId, recruit, {
RecruitApplication: '' as IntlString,
Vacancies: '' as IntlString,
CandidatePools: '' as IntlString,
Vacancy: '' as IntlString
Vacancy: '' as IntlString,
TxApplicantCreate: '' as IntlString,
TxCandidateCreate: '' as IntlString,
TxApplicantUpdate: '' as IntlString
},
component: {
CreateVacancy: '' as AnyComponent,
@ -46,5 +50,14 @@ export default mergeIds(recruitId, recruit, {
},
space: {
CandidatesPublic: '' as Ref<Space>
},
ids: {
TxApplicantCreate: '' as Ref<TxViewlet>,
TxCandidateCreate: '' as Ref<TxViewlet>,
TxApplicantUpdate: '' as Ref<TxViewlet>
},
activity: {
TxApplicantCreate: '' as AnyComponent,
TxApplicantUpdate: '' as AnyComponent
}
})

View File

@ -15,22 +15,30 @@
//
import type { Doc } from './classes'
import { getNestedValue } from './query'
type Predicate = (docs: Doc[]) => Doc[]
type PredicateFactory = (pred: any, propertyKey: string) => Predicate
type ExecPredicate = (value: any) => boolean
function execPredicate (docs: Doc[], propertyKey: string, pred: ExecPredicate): Doc[] {
const result: Doc[] = []
for (const doc of docs) {
const value = getNestedValue(propertyKey, doc)
if (pred(value)) {
result.push(doc)
}
}
return result
}
const predicates: Record<string, PredicateFactory> = {
$in: (o: any, propertyKey: string): Predicate => {
$in: (o, propertyKey) => {
if (!Array.isArray(o)) {
throw new Error('$in predicate requires array')
}
return (docs: Doc[]): Doc[] => {
const result: Doc[] = []
for (const doc of docs) {
if (o.includes((doc as any)[propertyKey])) result.push(doc)
}
return result
}
return (docs) => execPredicate(docs, propertyKey, (value) => o.includes(value))
},
$like: (query: string, propertyKey: string): Predicate => {

View File

@ -15,13 +15,60 @@ export function findProperty (objects: Doc[], propertyKey: string, value: any):
}
const result: Doc[] = []
for (const object of objects) {
if ((object as any)[propertyKey] === value) {
const val = getNestedValue(propertyKey, object)
if ((val === value) || isArrayValueCheck(val, value)) {
result.push(object)
}
}
return result
}
function isArrayValueCheck<T, P> (val: T, value: P): boolean {
return Array.isArray(val) && !Array.isArray(value) && val.includes(value)
}
/**
* @public
*/
export function getNestedValue (key: string, doc: Doc): any {
// Check dot notation
if (key.length === 0) {
return doc
}
key = key.split('\\$').join('$')
const dots = key.split('.')
// Replace escapting, since memdb is not escape keys
// We have dots, so iterate in depth
let pos = 0
let value = doc as any
for (const d of dots) {
if (Array.isArray(value) && isNestedArrayQuery(value, d)) {
// Array and d is not an indexed field.
// So return array of nested values.
return getNestedArrayValue(value, dots.slice(pos).join('.'))
}
value = value?.[d]
pos++
}
return value
}
function isNestedArrayQuery (value: any, d: string): boolean {
return Number.isNaN(Number.parseInt(d)) && value?.[d as any] === undefined
}
function getNestedArrayValue (value: any[], name: string): any[] {
const result = []
for (const v of value) {
result.push(...arrayOrValue(getNestedValue(name, v)))
}
return result
}
function arrayOrValue (vv: any): any[] {
return Array.isArray(vv) ? vv : [vv]
}
/**
* @public
*/

View File

@ -40,6 +40,9 @@ export type DocumentQuery<T extends Doc> = {
[P in keyof T]?: ObjQueryType<T[P]>
} & {
$search?: string
// support nested queries e.g. 'user.friends.name'
// this will mark all unrecognized properties as any (including nested queries)
[key: string]: any
}
/**

View File

@ -13,24 +13,11 @@
// limitations under the License.
//
import type {
Ref,
Doc,
Type,
PropertyType,
Attribute,
Tx,
Class,
Obj,
Data,
TxCreateDoc,
Domain,
Mixin as IMixin,
Space,
ExtendedAttributes
import core, {
Account,
Attribute, Class, ClassifierKind, Data, Doc, Domain, ExtendedAttributes, generateId, IndexKind, Mixin as IMixin, Obj, PropertyType, Ref, Space, Tx, TxCreateDoc, TxFactory, TxProcessor, Type
} from '@anticrm/core'
import core, { ClassifierKind, IndexKind, generateId, TxFactory } from '@anticrm/core'
import type { IntlString, Asset } from '@anticrm/platform'
import type { Asset, IntlString } from '@anticrm/platform'
import toposort from 'toposort'
type NoIDs<T extends Tx> = Omit<T, '_id' | 'objectId'>
@ -240,16 +227,20 @@ export class Builder {
_class: Ref<Class<T>>,
space: Ref<Space>,
attributes: Data<T>,
objectId?: Ref<T>
): void {
this.txes.push(
txFactory.createTxCreateDoc(
_class,
space,
attributes,
objectId
)
objectId?: Ref<T>,
modifiedBy?: Ref<Account>
): T {
const tx = txFactory.createTxCreateDoc(
_class,
space,
attributes,
objectId
)
if (modifiedBy !== undefined) {
tx.modifiedBy = modifiedBy
}
this.txes.push(tx)
return TxProcessor.createDoc2Doc(tx)
}
mixin<D extends Doc, M extends D> (

View File

@ -25,6 +25,7 @@
"@anticrm/platform": "~0.6.5",
"@anticrm/core": "~0.6.11",
"@anticrm/chunter": "~0.6.0",
"@anticrm/presentation": "~0.6.2"
"@anticrm/presentation": "~0.6.2",
"@anticrm/activity": "~0.6.0"
}
}

View File

@ -1,103 +0,0 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { Asset } from '@anticrm/platform'
import type { AnySvelteComponent } from '@anticrm/ui'
import { Icon } from '@anticrm/ui'
export let icon: Asset | AnySvelteComponent
</script>
<div class="flex-col msg-container">
<div class="flex-between">
<div class="flex-center icon">
<div class="scale-75">
{#if typeof (icon) === 'string'}
<Icon {icon} size={'small'}/>
{:else}
<svelte:component this={icon} size={'small'} />
{/if}
</div>
</div>
<div class="flex-grow label"><slot /></div>
<div class="content-trans-color">Yesterday, 3:20 PM</div>
</div>
{#if $$slots.content}
<div class="content"><slot name="content" /></div>
{/if}
</div>
<style lang="scss">
.msg-container {
position: relative;
&::after {
content: '';
position: absolute;
top: 2.25rem;
left: 1.125rem;
bottom: 0;
width: 1px;
background-color: var(--theme-card-divider);
}
}
:global(.msg-container + .msg-container::before) {
content: '';
position: absolute;
top: -1.5rem;
left: 1.125rem;
height: 1.5rem;
width: 1px;
background-color: var(--theme-card-divider);
}
.icon {
flex-shrink: 0;
align-self: flex-start;
width: 2.25rem;
height: 2.25rem;
color: var(--theme-caption-color);
border: 1px solid var(--theme-card-divider);
border-radius: 50%;
}
.content {
margin: .5rem 0 .5rem 3.25rem;
padding: 1rem;
background-color: var(--theme-bg-accent-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: .75rem;
}
.label {
flex-wrap: wrap;
margin: 0 1rem;
}
:global(.label b) { color: var(--theme-caption-color); }
:global(.label span) {
display: inline-block;
padding: .125rem .25rem;
color: var(--theme-caption-color);
background-color: var(--theme-bg-focused-color);
border-radius: .25rem;
}
:global(.label span.bar) {
padding: .25rem .5rem;
font-weight: 500;
font-size: .625rem;
background-color: var(--primary-button-enabled);
}
</style>

View File

@ -15,19 +15,12 @@
-->
<script lang="ts">
import type { Asset } from '@anticrm/platform'
import type { Doc } from '@anticrm/core'
import { SortingOrder } from '@anticrm/core'
import { getClient, createQuery, Backlink } from '@anticrm/presentation'
import type { AnySvelteComponent } from '@anticrm/ui'
import { ReferenceInput } from '@anticrm/text-editor'
import { IconClose, IconExpand, IconActivity, ScrollBox, Grid, Icon, IconToDo } from '@anticrm/ui'
import type { Comment } from '@anticrm/chunter'
import ActivityMsg from './ActivityMsg.svelte'
import { createEventDispatcher } from 'svelte'
import chunter from '@anticrm/chunter'
import activity from '@anticrm/activity'
import type { Doc } from '@anticrm/core';
import type { Asset } from '@anticrm/platform';
import type { AnySvelteComponent } from '@anticrm/ui';
import { Icon,IconClose,IconExpand, Component } from '@anticrm/ui';
import { createEventDispatcher } from 'svelte';
export let title: string
export let icon: Asset | AnySvelteComponent
@ -35,19 +28,6 @@
export let object: Doc
const dispatch = createEventDispatcher()
let comments: Comment[]
const client = getClient()
const query = createQuery()
$: query.query(chunter.class.Comment, { attachedTo: object._id }, result => { comments = result }, { sort: { modifiedOn: SortingOrder.Descending } })
function onMessage(event: CustomEvent) {
client.addCollection(chunter.class.Comment, object.space, object._id, object._class, 'comments', {
message: event.detail
})
console.log(event.detail)
}
</script>
<div class="overlay" on:click={() => { dispatch('close') }}/>
@ -73,22 +53,7 @@
</div>
</div>
<div class="rightSection">
<div class="flex-row-center header">
<div class="icon"><IconActivity size={'small'} /></div>
<div class="title">Activity</div>
</div>
<div class="flex-col h-full content">
<ScrollBox vertical stretch>
{#if comments}
<Grid column={1} rowGap={1.5}>
{#each comments as comment}
<Backlink {comment} />
{/each}
</Grid>
{/if}
</ScrollBox>
</div>
<div class="ref-input"><ReferenceInput on:message={onMessage}/></div>
<Component is={activity.component.Activity} props={{object, fullSize}}/>
</div>
{:else}
<div class="unionSection">
@ -103,40 +68,10 @@
<div class="title">{title}</div>
</div>
{#if $$slots.subtitle}<div class="flex-row-center subtitle"><slot name="subtitle" /></div>{/if}
<ScrollBox vertical stretch noShift>
<div class="flex-col content">
<slot />
</div>
<div class="flex-row-center activity header">
<div class="icon"><IconActivity size={'small'} /></div>
<div class="title">Activity</div>
</div>
<div class="flex-col activity content">
{#if comments}
<Grid column={1} rowGap={1.5}>
<!-- Start Demo -->
<ActivityMsg icon={IconToDo}><span>Task TAS189</span> was created by <b>Tim Ferris</b> test</ActivityMsg>
<ActivityMsg icon={IconToDo}>
<b>Rosamund Chen</b> changed status from <span>IN PROGRESS</span> to <span class="bar">ON HOLD</span>
<svelte:fragment slot="content">Content</svelte:fragment>
</ActivityMsg>
<ActivityMsg icon={IconToDo}>Task TAS189 was created by <b>Tim Ferris</b></ActivityMsg>
<ActivityMsg icon={IconToDo}>
Testing <b>Rosamund Chen</b> changed status from <span>IN PROGRESS</span> to <span class="bar">ON HOLD</span>.
<div slot="content">Content</div>
</ActivityMsg>
<ActivityMsg icon={IconToDo}>Task TAS189 was created by <b>Tim Ferris</b></ActivityMsg>
<!-- End Demo -->
{#each comments as comment}
<Backlink {comment} />
{/each}
</Grid>
{/if}
</div>
</ScrollBox>
<div class="ref-input"><ReferenceInput on:message={onMessage}/></div>
<Component is={activity.component.Activity} props={{object, fullSize}}>
<slot />
</Component>
</div>
{/if}
@ -193,24 +128,7 @@
display: flex;
flex-direction: column;
height: max-content;
.content {
flex-shrink: 0;
display: flex;
flex-direction: column;
padding: 1.5rem 2.5rem;
height: max-content;
}
.activity {
background-color: var(--theme-dialog-accent);
&.header { border-bottom: none; }
&.content {
flex-grow: 1;
padding-bottom: 0;
background-color: var(--theme-dialog-accent);
}
}
height: max-content;
}
.fullSize {
@ -236,17 +154,6 @@
}
.rightSection {
background-color: transparent;
.header { border-bottom: 1px solid var(--theme-dialog-divider); }
.content {
flex-grow: 1;
padding: 2.5rem 2.5rem 0;
background-color: var(--theme-dialog-accent);
}
}
.ref-input {
background-color: var(--theme-dialog-accent);
padding: 1.5rem 2.5rem;
}
.tools {

View File

@ -0,0 +1,44 @@
{
"root": true,
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": [
"standard-with-typescript"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"project": [
"**/tsconfig.json"
],
"extraFileExtensions": [".svelte"]
},
"plugins": ["svelte3", "@typescript-eslint", "import"],
"overrides": [
{
"files": ["**/*.svelte"],
"processor" : "svelte3/svelte3",
"rules" : {
"import/first": "off",
"import/no-duplicates": "off",
"import/no-mutable-exports": "off",
"import/no-unresolved": "off",
"no-multiple-empty-lines": "off",
"no-undef-init": "off",
"no-use-before-define": "off"
}
}
],
"settings": {
"svelte3/typescript": true
}
}

View File

@ -0,0 +1,17 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/heft/heft.schema.json",
"eventActions": [
{
"actionKind": "deleteGlobs",
"heftEvent": "clean",
"actionId": "defaultClean",
"globsToDelete": ["dist", "lib", "temp"]
}
],
"heftPlugins": [
{
"plugin": "@rushstack/heft-jest-plugin"
}
]
}

View File

@ -0,0 +1 @@
module.exports = require("@anticrm/platform-rig/profiles/default/config/jest.config")

View File

@ -0,0 +1,3 @@
{
"projectOutputFolderNames": ["lib", "dist"]
}

View File

@ -0,0 +1,76 @@
/**
* Configures the TypeScript plugin for Heft. This plugin also manages linting.
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/heft/typescript.schema.json",
/**
* Can be set to "copy" or "hardlink". If set to "copy", copy files from cache.
* If set to "hardlink", files will be hardlinked to the cache location.
* This option is useful when producing a tarball of build output as TAR files don't
* handle these hardlinks correctly. "hardlink" is the default behavior.
*/
// "copyFromCacheMode": "copy",
/**
* If provided, emit these module kinds in addition to the modules specified in the tsconfig.
* Note that this option only applies to the main tsconfig.json configuration.
*/
"additionalModuleKindsToEmit": [
// {
// /**
// * (Required) Must be one of "commonjs", "amd", "umd", "system", "es2015", "esnext"
// */
// "moduleKind": "amd",
//
// /**
// * (Required) The name of the folder where the output will be written.
// */
// "outFolderName": "lib-amd"
// }
],
/**
* Specifies the intermediary folder that tests will use. Because Jest uses the
* Node.js runtime to execute tests, the module format must be CommonJS.
*
* The default value is "lib".
*/
// "emitFolderNameForTests": "lib-commonjs",
/**
* If set to "true", the TSlint task will not be invoked.
*/
// "disableTslint": true,
/**
* Set this to change the maximum number of file handles that will be opened concurrently for writing.
* The default is 50.
*/
// "maxWriteParallelism": 50,
/**
* Describes the way files should be statically coped from src to TS output folders
*/
"staticAssetsToCopy": {
/**
* File extensions that should be copied from the src folder to the destination folder(s).
*/
"fileExtensions": [".json"]
/**
* Glob patterns that should be explicitly included.
*/
// "includeGlobs": [
// "some/path/*.js"
// ],
/**
* Glob patterns that should be explicitly excluded. This takes precedence over globs listed
* in "includeGlobs" and files that match the file extensions provided in "fileExtensions".
*/
// "excludeGlobs": [
// "some/path/*.css"
// ]
}
}

View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "ES2017",
"module": "commonjs",
"declaration": true,
"strict": true,
"sourceMap": true,
"resolveJsonModule": true,
"types": ["heft-jest"]
}
}

View File

@ -100,6 +100,7 @@ table {
}
.flex { display: flex; }
.inline-flex { display: inline-flex; }
.flex-grow { flex-grow: 1; }
.flex-no-shrink { flex-shrink: 0; }
.flex-nowrap {

View File

@ -31,7 +31,9 @@
<Loading/>
{:then Ctor}
<ErrorBoundary>
<Ctor {...props} on:change on:close on:open on:click/>
<Ctor {...props} on:change on:close on:open on:click>
<slot />
</Ctor>
</ErrorBoundary>
{:catch err}
ERROR: {console.log(err, JSON.stringify(component))}

View File

@ -12,22 +12,34 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Asset, getMetadata } from '@anticrm/platform'
import { AnySvelteComponent } from '../types';
<script lang="ts">
import type { Asset } from '@anticrm/platform'
import { getMetadata } from '@anticrm/platform'
export let icon: Asset
export let icon: Asset | AnySvelteComponent
export let size: 'small' | 'medium' | 'large'
export let fill = 'currentColor'
export let filled: boolean = false
function isAsset (icon: Asset | AnySvelteComponent): boolean {
return typeof icon === 'string'
}
function toAsset (icon: AnySvelteComponent | Asset): Asset {
return icon as Asset
}
let url: string
$: url = getMetadata(icon) ?? 'https://anticrm.org/logo.svg'
$: url = isAsset(icon) ? getMetadata(toAsset(icon)) ?? 'https://anticrm.org/logo.svg' : ''
</script>
<svg class={size} {fill}>
<use href={url} />
</svg>
{#if isAsset(icon)}
<svg class={size} {fill}>
<use href={url} />
</svg>
{:else}
<svelte:component this={icon} {size} {fill} {filled} />
{/if}
<style lang="scss">
.small {

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="activity" viewBox="0 0 20 20">
<path d="M8.4,8.5C8.2,8.4,8,8.3,7.9,8.3c-0.2,0-0.4,0.1-0.5,0.3l-2.9,3.8c-0.2,0.3-0.2,0.7,0.1,1c0.1,0.1,0.3,0.1,0.4,0.1 c0.2,0,0.4-0.1,0.6-0.3L8.1,10l2.8,2.2c0.1,0.1,0.3,0.2,0.5,0.1c0.2,0,0.4-0.1,0.5-0.3l2.8-3.7c0.2-0.3,0.2-0.7-0.1-1 c-0.3-0.2-0.7-0.2-1,0.1l-2.4,3.1L8.4,8.5z"/>
<path d="M17.4,0c-1.4,0-2.6,1.2-2.6,2.6c0,1.4,1.2,2.6,2.6,2.6C18.8,5.1,20,4,20,2.5C20,1.1,18.8,0,17.4,0z M17.4,3.7 c-0.6,0-1.2-0.5-1.2-1.2c0-0.6,0.5-1.2,1.2-1.2c0.6,0,1.2,0.5,1.2,1.2C18.6,3.2,18.1,3.7,17.4,3.7z"/>
<path d="M18.5,6.8c-0.4,0-0.7,0.3-0.7,0.7v6.8c0,2.6-1.6,4.3-4,4.3H5.4c-2.5,0-4-1.6-4-4.3V6.5c0-2.6,1.6-4.3,4-4.3h7.1 c0.4,0,0.7-0.3,0.7-0.7s-0.3-0.7-0.7-0.7H5.4C2.2,0.8,0,3.1,0,6.5v7.9C0,17.7,2.2,20,5.4,20h8.4c3.3,0,5.4-2.3,5.4-5.7V7.5 C19.2,7.1,18.9,6.8,18.5,6.8z"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 900 B

View File

@ -0,0 +1,28 @@
{
"name": "@anticrm/activity-assets",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "",
"lint": "eslint src",
"lint:fix": "eslint --fix src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig":"~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint": "^7.32.0",
"prettier": "^2.4.1"
},
"dependencies": {
"@anticrm/platform":"~0.6.5",
"@anticrm/activity":"~0.6.0"
}
}

View File

@ -0,0 +1,22 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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 { loadMetadata } from '@anticrm/platform'
import activity from '@anticrm/activity'
const icons = require('../assets/icons.svg') // eslint-disable-line
loadMetadata(activity.icon, {
Activity: `${icons}#activity` // eslint-disable-line
})

View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/ui/config/eslint.config.json'],
parserOptions: { tsconfigRootDir: __dirname },
settings: {
'svelte3/ignore-styles': () => true
}
};

View File

@ -0,0 +1,43 @@
{
"name": "@anticrm/activity-resources",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "svelte-check",
"lint": "eslint src",
"lint:fix": "eslint --fix src",
"format": "prettier --write --plugin-search-dir=. src && eslint --fix src",
"svelte-check": "svelte-check"
},
"devDependencies": {
"svelte-loader":"^3.1.2",
"sass":"^1.37.5",
"svelte-preprocess":"^4.7.4",
"@anticrm/platform-rig":"~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-svelte3": "~3.2.1",
"prettier-plugin-svelte": "^2.2.0",
"eslint": "^7.32.0",
"prettier": "^2.4.1",
"svelte-check": "^2.2.10"
},
"dependencies": {
"@anticrm/core":"~0.6.11",
"@anticrm/platform":"~0.6.5",
"@anticrm/ui":"~0.6.0",
"@anticrm/presentation":"~0.6.2",
"@anticrm/activity":"~0.6.0",
"svelte":"^3.37.0",
"@anticrm/chunter":"~0.6.1",
"@anticrm/text-editor":"~0.6.0",
"@anticrm/contact":"~0.6.2",
"@anticrm/view": "~0.6.0"
}
}

View File

@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}

View File

@ -0,0 +1,195 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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 activity, { TxViewlet } from '@anticrm/activity'
import chunter from '@anticrm/chunter'
import type { AttachedDoc, Doc, Ref, TxCollectionCUD, TxCUD } from '@anticrm/core'
import core, { SortingOrder } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import { ReferenceInput } from '@anticrm/text-editor'
import { Grid, IconActivity, ScrollBox } from '@anticrm/ui'
import { ActivityKey, activityKey } from '../utils'
import TxView from './TxView.svelte'
export let fullSize: boolean = false
export let object: Doc
let txes1: TxCUD<Doc>[] = []
let txes2: TxCUD<Doc>[] = []
let txes: TxCUD<Doc>[]
$: txes = Array.from(txes1).concat(txes2).sort((a, b) => b.modifiedOn - a.modifiedOn)
const client = getClient()
const txQuery1 = createQuery()
const txQuery2 = createQuery()
let isAttached = false
$: isAttached = client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)
$: txQuery1.query<TxCollectionCUD<Doc, AttachedDoc>>(
isAttached ? core.class.TxCollectionCUD : core.class.TxCUD,
isAttached
? { 'tx.objectId': object._id as Ref<AttachedDoc> }
: {
objectId: object._id,
_class: { $in: [core.class.TxCreateDoc, core.class.TxUpdateDoc, core.class.TxRemoveDoc] }
},
(result) => {
txes1 = result
},
{ sort: { modifiedOn: SortingOrder.Descending } }
)
$: txQuery2.query<TxCUD<Doc>>(
core.class.TxCollectionCUD,
{
objectId: object._id,
'tx._class': { $in: [core.class.TxCreateDoc, core.class.TxRemoveDoc] }
},
(result) => {
txes2 = result
},
{ sort: { modifiedOn: SortingOrder.Descending } }
)
function onMessage (event: CustomEvent) {
client.addCollection(chunter.class.Comment, object.space, object._id, object._class, 'comments', {
message: event.detail
})
console.log(event.detail)
}
let viewlets: Map<ActivityKey, TxViewlet>
const descriptors = createQuery()
$: descriptors.query(activity.class.TxViewlet, {}, (result) => {
viewlets = new Map(result.map(r => ([activityKey(r.objectClass, r.txClass), r])))
})
</script>
{#if fullSize}
<div class="flex-row-center header">
<div class="icon"><IconActivity size={'small'} /></div>
<div class="title">Activity</div>
</div>
<div class="flex-col h-full right-content">
<ScrollBox vertical stretch>
{#if txes}
<Grid column={1} rowGap={1.5}>
{#each txes as tx}
<TxView {tx} {viewlets}/>
{/each}
</Grid>
{/if}
</ScrollBox>
</div>
<div class="ref-input"><ReferenceInput on:message={onMessage} /></div>
{:else}
<div class="unionSection">
<ScrollBox vertical stretch noShift>
<div class="flex-col content">
<slot />
</div>
<div class="flex-row-center activity header">
<div class="icon"><IconActivity size={'small'} /></div>
<div class="title">Activity</div>
</div>
<div class="flex-col activity content">
{#if txes}
<Grid column={1} rowGap={1.5}>
{#each txes as tx}
<TxView {tx} {viewlets}/>
{/each}
</Grid>
{/if}
</div>
</ScrollBox>
<div class="ref-input"><ReferenceInput on:message={onMessage} /></div>
</div>
{/if}
<style lang="scss">
.header {
flex-shrink: 0;
padding: 0 2.5rem;
height: 4.5rem;
border-bottom: 1px solid var(--theme-card-divider);
.icon {
opacity: 0.6;
}
.title {
flex-grow: 1;
margin-left: 0.5rem;
font-weight: 500;
font-size: 1rem;
color: var(--theme-caption-color);
user-select: none;
}
}
.activity {
background-color: var(--theme-bg-accent-color);
&.header {
border-bottom: none;
}
&.content {
flex-grow: 1;
padding-bottom: 0;
background-color: var(--theme-bg-accent-color);
}
}
.ref-input {
background-color: var(--theme-bg-accent-color);
padding: 1.5rem 2.5rem;
}
.right-content {
flex-grow: 1;
padding: 2.5rem 2.5rem 0;
background-color: var(--theme-dialog-accent);
}
.unionSection {
flex-grow: 1;
display: flex;
flex-direction: column;
height: max-content;
.content {
flex-shrink: 0;
display: flex;
flex-direction: column;
padding: 1.5rem 2.5rem;
height: max-content;
}
.activity {
background-color: var(--theme-bg-accent-color);
&.header {
border-bottom: none;
}
&.content {
flex-grow: 1;
padding-bottom: 0;
background-color: var(--theme-bg-accent-color);
}
}
}
</style>

View File

@ -0,0 +1,202 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { TxViewlet } from '@anticrm/activity'
import contact, { EmployeeAccount, formatName } from '@anticrm/contact'
import type { AttachedDoc, Doc, Ref, Tx, TxCollectionCUD, TxCUD, TxUpdateDoc } from '@anticrm/core'
import core from '@anticrm/core'
import { getResource } from '@anticrm/platform'
import { getClient } from '@anticrm/presentation'
import { Component, Icon, Label, TimeSince } from '@anticrm/ui'
import type { AttributeModel } from '@anticrm/view'
import view, { BuildModelOptions } from '@anticrm/view'
import { activityKey, ActivityKey } from '../utils'
import activity from '@anticrm/activity'
export let tx: Tx
export let viewlets: Map<ActivityKey, TxViewlet>
let viewlet: TxViewlet | undefined
let displayTx: TxCUD<Doc> | undefined
let utx: TxUpdateDoc<Doc> | undefined
const client = getClient()
$: if (client.getHierarchy().isDerived(tx._class, core.class.TxCollectionCUD)) {
const colCUD = (tx as TxCollectionCUD<Doc, AttachedDoc>)
displayTx = colCUD.tx
} else if (client.getHierarchy().isDerived(tx._class, core.class.TxCUD)) {
displayTx = tx as TxCUD<Doc>
}
$: if (displayTx !== undefined) {
const key = activityKey(displayTx.objectClass, displayTx._class)
viewlet = viewlets.get(key)
} else {
viewlet = undefined
}
let employee: EmployeeAccount | undefined
$: client.findOne(contact.class.EmployeeAccount, { _id: tx.modifiedBy as Ref<EmployeeAccount> }).then(account => { employee = account })
$: client.findAll(contact.class.EmployeeAccount, { }).then(accounts => { console.log(tx.modifiedBy, 'accounts', accounts) })
let model: AttributeModel[] = []
let buildModel: ((options: BuildModelOptions) => Promise<AttributeModel[]>)|undefined
getResource(view.api.buildModel).then(bm => {
buildModel = bm
})
$: if (displayTx !== undefined && displayTx._class === core.class.TxUpdateDoc) {
utx = displayTx as TxUpdateDoc<Doc>
const ops = { client, _class: utx.objectClass, keys: Object.keys(utx.operations), ignoreMissing: true }
model = []
buildModel?.(ops).then(m => {
model = m
})
} else {
model = []
utx = undefined
}
function getValue (utx: TxUpdateDoc<Doc>, key: string): any {
return (utx.operations as any)[key]
}
</script>
{#if displayTx && (viewlet !== undefined || model.length > 0)}
<div class="flex-col msg-container">
<div class="flex-between">
<div class="flex-center icon">
<div class="scale-75">
{#if viewlet}
<Icon icon={viewlet.icon} size='medium'/>
{:else}
<Icon icon={activity.icon.Activity} size='medium'/>
{/if}
</div>
</div>
<div class="flex flex-grow label">
<b>
{#if employee}
{formatName(employee.name)}
{:else}
No employee
{/if}
</b>
{#if viewlet}
<Label label={viewlet.label}/>
{/if}
{#if viewlet === undefined && model.length > 0 && utx}
{#each model as m}
<div class='change'>
changed {m.label} to
<div class='value'>
<svelte:component this={m.presenter} value={getValue(utx, m.key)}/>
</div>
</div>
{/each}
{:else if viewlet && viewlet.display === 'inline' && viewlet.component}
<Component is={viewlet.component} props={{ tx: displayTx }} />
{/if}
</div>
<div class="content-trans-color"><TimeSince value={tx.modifiedOn}/></div>
</div>
{#if viewlet && viewlet.component && viewlet.display !== 'inline'}
<div class='content' class:emphasize={viewlet.display === 'emphasized'}>
<Component is={viewlet.component} props={{ tx: displayTx }} />
</div>
{/if}
</div>
{/if}
<style lang="scss">
.change {
display: inline-flex;
align-items: center;
gap: 10px;
.value {
gap: 10px;
display: flex;
align-items: center;
font-weight: 500;
}
}
.msg-container {
position: relative;
&::after {
content: '';
position: absolute;
top: 2.25rem;
left: 1.125rem;
bottom: 0;
width: 1px;
background-color: var(--theme-card-divider);
}
}
:global(.msg-container + .msg-container::before) {
content: '';
position: absolute;
top: -1.5rem;
left: 1.125rem;
height: 1.5rem;
width: 1px;
background-color: var(--theme-card-divider);
}
.icon {
flex-shrink: 0;
align-self: flex-start;
width: 2.25rem;
height: 2.25rem;
color: var(--theme-caption-color);
border: 1px solid var(--theme-card-divider);
border-radius: 50%;
}
.content {
margin: 0.5rem 0 0.5rem 3.25rem;
padding: 1rem;
}
.emphasize {
background-color: var(--theme-bg-accent-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: 0.75rem;
}
.label {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
margin: 0 1rem;
}
:global(.label b) {
color: var(--theme-caption-color);
}
:global(.label span) {
display: inline-block;
padding: 0.125rem 0.25rem;
color: var(--theme-caption-color);
background-color: var(--theme-bg-focused-color);
border-radius: 0.25rem;
}
:global(.label span.bar) {
padding: 0.25rem 0.5rem;
font-weight: 500;
font-size: 0.625rem;
background-color: var(--primary-button-enabled);
}
</style>

View File

@ -0,0 +1,23 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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 { Resources } from '@anticrm/platform'
import Activity from './components/Activity.svelte'
export default async (): Promise<Resources> => ({
component: {
Activity
}
})

View File

@ -0,0 +1,24 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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 chunter, { chunterId } from '@anticrm/chunter'
import type { IntlString } from '@anticrm/platform'
import { mergeIds } from '@anticrm/platform'
export default mergeIds(chunterId, chunter, {
string: {
Activity: '' as IntlString
}
})

View File

@ -0,0 +1,7 @@
import { Class, Doc, Ref, Tx } from '@anticrm/core'
export type ActivityKey = string
export function activityKey (objectClass: Ref<Class<Doc>>, txClass: Ref<Class<Tx>>): ActivityKey {
return objectClass + ':' + txClass
}

View File

@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};

View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@anticrm/platform-rig",
"rigProfile": "default"
}

View File

@ -0,0 +1,31 @@
{
"name": "@anticrm/activity",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig":"~0.6.0",
"@types/heft-jest":"^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint": "^7.32.0",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1"
},
"dependencies": {
"@anticrm/platform":"~0.6.5",
"@anticrm/core":"~0.6.12",
"@anticrm/ui": "~0.6.0"
}
}

View File

@ -0,0 +1,54 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// 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 { Class, Doc, DocumentQuery, Ref, Tx } from '@anticrm/core'
import type { Asset, IntlString, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import type { AnyComponent } from '@anticrm/ui'
/**
* Define an display for all transaction kinds for particular class.
* @public
*/
export interface TxViewlet extends Doc {
icon: Asset
objectClass: Ref<Class<Doc>>
txClass: Ref<Class<Tx>>
// Component to display on.
component?: AnyComponent
// Filter
match?: DocumentQuery<Tx>
// Label will be displayed right after author
label: IntlString
// Do component need to be emphasized or not.
display: 'inline' | 'content' | 'emphasized'
}
/**
* @public
*/
export const activityId = 'activity' as Plugin
export default plugin(activityId, {
icon: {
Activity: '' as Asset
},
class: {
TxViewlet: '' as Ref<Class<TxViewlet>>
},
component: {
Activity: '' as AnyComponent
}
})

View File

@ -0,0 +1,9 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"lib": ["esnext", "dom"]
}
}

View File

@ -0,0 +1,7 @@
{
"string": {
"ApplicationLabelChunter": "Chat",
"LeftComment": "left a comment",
"AddAttachment": "uploaded an attachment"
}
}

View File

@ -13,12 +13,15 @@
// limitations under the License.
//
import { loadMetadata } from '@anticrm/platform'
import chunter from '@anticrm/chunter'
import { addStringsLoader, loadMetadata } from '@anticrm/platform'
import chunter, {chunterId} from '@anticrm/chunter'
const icons = require('../assets/icons.svg')
loadMetadata(chunter.icon, {
Chunter: `${icons}#chunter`,
Hashtag: `${icons}#hashtag`,
Lock: `${icons}#lock`
Lock: `${icons}#lock`,
Attachment: `${icons}#chunter`
})
addStringsLoader(chunterId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -0,0 +1,25 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { Attachment } from "@anticrm/chunter";
import type { TxCreateDoc } from "@anticrm/core";
import { TxProcessor } from '@anticrm/core';
import AttachmentPresenter from "../AttachmentPresenter.svelte";
export let tx: TxCreateDoc<Attachment>
</script>
<AttachmentPresenter value={TxProcessor.createDoc2Doc(tx)}/>

View File

@ -0,0 +1,33 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { Comment } from "@anticrm/chunter"
import type { TxCreateDoc } from "@anticrm/core"
import { MessageViewer } from '@anticrm/presentation'
export let tx: TxCreateDoc<Comment>
</script>
<div class="text">
<MessageViewer message={tx.attributes.message}/>
</div>
<style lang="scss">
.text {
line-height: 150%;
color: var(--theme-content-color);
}
</style>

View File

@ -18,6 +18,8 @@ import ChannelView from './components/ChannelView.svelte'
import Activity from './components/Activity.svelte'
import AttachmentsPresenter from './components/AttachmentsPresenter.svelte'
import AttachmentPresenter from './components/AttachmentPresenter.svelte'
import TxCommentCreate from './components/activity/TxCommentCreate.svelte'
import TxAttachmentCreate from './components/activity/TxAttachmentCreate.svelte'
export { AttachmentsPresenter }
@ -28,5 +30,9 @@ export default async () => ({
Activity,
AttachmentsPresenter,
AttachmentPresenter
},
activity: {
TxCommentCreate: TxCommentCreate,
TxAttachmentCreate
}
})

View File

@ -64,7 +64,8 @@ export default plugin(chunterId, {
icon: {
Chunter: '' as Asset,
Hashtag: '' as Asset,
Lock: '' as Asset
Lock: '' as Asset,
Attachment: '' as Asset
},
class: {
Message: '' as Ref<Class<Message>>,

View File

@ -6,6 +6,9 @@
"VacancyDescription": "Vacancy Description",
"CreateVacancy": "Create Vacancy",
"CreateCandidate": "Create Candidate",
"MakePrivate": "Make Private"
"MakePrivate": "Make Private",
"TxApplicantCreate": "created application",
"TxApplicantUpdate": "",
"TxCandidateCreate": "created candidate"
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/ui/config/eslint.config.json'],
parserOptions: { tsconfigRootDir: __dirname },
settings: {
'svelte3/ignore-styles': () => true
}
};

View File

@ -15,6 +15,18 @@
"svelte-loader":"^3.1.2",
"sass":"^1.37.5",
"svelte-preprocess":"^4.7.4",
"@anticrm/platform-rig":"~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-svelte3": "~3.2.1",
"prettier-plugin-svelte": "^2.2.0",
"eslint": "^7.32.0",
"prettier": "^2.4.1",
"svelte-check": "^2.2.10",
"@types/deep-equal":"^1.0.1"
},
"dependencies": {

View File

@ -94,13 +94,6 @@
</div>
</div>
<!-- Start Demo -->
<div class="group cards-container">
<PluginCard icon={Telegram} />
<PluginCard icon={Gmail} />
</div>
<!-- End Demo -->
<div class="flex-col group">
<div class="flex-row-center">
<div class="caption">Applications</div>

View File

@ -0,0 +1,25 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { Applicant } from '@anticrm/recruit'
import type { TxCreateDoc } from '@anticrm/core'
import { TxProcessor } from '@anticrm/core'
import ApplicationPresenter from '../ApplicationPresenter.svelte'
export let tx: TxCreateDoc<Applicant>
</script>
<ApplicationPresenter value={TxProcessor.createDoc2Doc(tx)}/>

View File

@ -0,0 +1,55 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { Class, State, TxUpdateDoc } from '@anticrm/core'
import core from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
import type { Applicant } from '@anticrm/recruit'
import { Component } from '@anticrm/ui'
import view from '@anticrm/view'
export let tx: TxUpdateDoc<Applicant>
const client = getClient()
const stateClass = client.getModel().getObject(core.class.State) as Class<State>
const statePresenter = client.getHierarchy().as(stateClass, view.mixin.AttributePresenter)
</script>
{#if tx.operations.state}
updated State to
{#if statePresenter?.presenter}
{#await client.findOne(core.class.State, { _id: tx.operations.state }) then st}
{#if st}
<Component is={statePresenter.presenter} props={{ value: st }}/>
{/if}
{/await}
{/if}
{/if}
<style lang='scss'>
.state {
display: flex;
align-items: center;
span {
margin-left: 10px;
margin-right: 10px;
display: flex;
align-items: center;
}
}
</style>

View File

@ -1,14 +1,14 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
//
// 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.
//
@ -25,13 +25,17 @@ import KanbanCard from './components/KanbanCard.svelte'
import ApplicationPresenter from './components/ApplicationPresenter.svelte'
import ApplicationsPresenter from './components/ApplicationsPresenter.svelte'
import { showPopup } from '@anticrm/ui'
import TxApplicantCreate from './components/activity/TxApplicantCreate.svelte'
import TxApplicantUpdate from './components/activity/TxApplicantUpdate.svelte'
async function createApplication(object: Doc): Promise<void> {
import { showPopup } from '@anticrm/ui'
import { Resources } from '@anticrm/platform'
async function createApplication (object: Doc): Promise<void> {
showPopup(CreateApplication, { candidate: object._id, preserveCandidate: true })
}
export default async () => ({
export default async (): Promise<Resources> => ({
actionImpl: {
CreateApplication: createApplication
},
@ -44,6 +48,10 @@ export default async () => ({
Attachments,
KanbanCard,
ApplicationPresenter,
ApplicationsPresenter,
ApplicationsPresenter
},
activity: {
TxApplicantCreate,
TxApplicantUpdate
}
})

View File

@ -70,7 +70,7 @@
}
</script>
{#await buildModel(client, _class, config, options)}
{#await buildModel({client, _class, keys: config, options})}
<Loading/>
{:then model}
<table class="table-body">

View File

@ -73,7 +73,7 @@
}
</script>
{#await buildModel(client, _class, config, options)}
{#await buildModel({client, _class, keys: config, options})}
<Loading/>
{:then model}
<div class="container">

View File

@ -26,8 +26,10 @@ import KanbanView from './components/KanbanView.svelte'
import { getClient, MessageBox } from '@anticrm/presentation'
import { showPopup } from '@anticrm/ui'
import {buildModel} from './utils'
export { Table }
export { buildModel } from './utils'
function Delete(object: Doc): void {
showPopup(MessageBox, {
@ -54,4 +56,7 @@ export default async () => ({
KanbanView,
TimestampPresenter
},
api: {
buildModel
}
})

View File

@ -18,15 +18,9 @@ import type { IntlString } from '@anticrm/platform'
import { getResource } from '@anticrm/platform'
import type { Ref, Class, Obj, FindOptions, Doc, Client } from '@anticrm/core'
import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
import type { Action, ActionTarget } from '@anticrm/view'
import type { Action, ActionTarget, BuildModelOptions } from '@anticrm/view'
import view from '@anticrm/view'
export interface AttributeModel {
key: string
label: IntlString
presenter: AnySvelteComponent
}
import view, { AttributeModel } from '@anticrm/view'
async function getObjectPresenter(client: Client, _class: Ref<Class<Obj>>, preserveKey: string): Promise<AttributeModel> {
const clazz = client.getHierarchy().getClass(_class)
@ -94,11 +88,20 @@ async function getPresenter(client: Client, _class: Ref<Class<Obj>>, key: string
}
}
export async function buildModel(client: Client, _class: Ref<Class<Obj>>, keys: string[], options?: FindOptions<Doc>): Promise<AttributeModel[]> {
console.log('building table model for', _class)
const model = keys.map(key => getPresenter(client, _class, key, key, options))
export async function buildModel(options: BuildModelOptions): Promise<AttributeModel[]> {
console.log('building table model for', options._class)
const model = options.keys.map(key => {
try {
const result = getPresenter(options.client, options._class, key, key, options.options)
return result
} catch(err: any) {
if (!(options.ignoreMissing ?? false)) {
throw err
}
}
})
console.log(model)
return await Promise.all(model)
return (await Promise.all(model)).filter(a => a !== undefined) as AttributeModel[]
}
function filterActions(client: Client, _class: Ref<Class<Obj>>, targets: ActionTarget[]): Ref<Action>[] {

View File

@ -14,11 +14,11 @@
// limitations under the License.
//
import type { Plugin, Asset, Resource } from '@anticrm/platform'
import type { Plugin, Asset, Resource, IntlString } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import type { Ref, Mixin, UXObject, Space, FindOptions, Class, Doc, Arr, State } from '@anticrm/core'
import type { Ref, Mixin, UXObject, Space, FindOptions, Class, Doc, Arr, State, Client, Obj } from '@anticrm/core'
import type { AnyComponent } from '@anticrm/ui'
import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
/**
* @public
@ -103,6 +103,26 @@ export interface Sequence extends Doc {
*/
export const viewId = 'view' as Plugin
/**
* @public
*/
export interface AttributeModel {
key: string
label: IntlString
presenter: AnySvelteComponent
}
/**
* @public
*/
export interface BuildModelOptions {
client: Client
_class: Ref<Class<Obj>>
keys: string[]
options?: FindOptions<Doc>
ignoreMissing?: boolean
}
export default plugin(viewId, {
mixin: {
AttributeEditor: '' as Ref<Mixin<AttributeEditor>>,
@ -128,5 +148,8 @@ export default plugin(viewId, {
icon: {
Table: '' as Asset,
Kanban: '' as Asset
},
api: {
buildModel: '' as Resource<(options: BuildModelOptions) => Promise<AttributeModel[]>>
}
})

View File

@ -796,5 +796,25 @@
"projectFolder": "packages/panel",
"shouldPublish": true
},
{
"packageName": "@anticrm/activity",
"projectFolder": "plugins/activity",
"shouldPublish": true
},
{
"packageName": "@anticrm/activity-assets",
"projectFolder": "plugins/activity-assets",
"shouldPublish": true
},
{
"packageName": "@anticrm/activity-resources",
"projectFolder": "plugins/activity-resources",
"shouldPublish": true
},
{
"packageName": "@anticrm/model-activity",
"projectFolder": "models/activity",
"shouldPublish": true
},
]
}

File diff suppressed because it is too large Load Diff