Make possible to archive spaces with states (#688)

Signed-off-by: Ilya Sumbatyants <ilya.sumb@gmail.com>
This commit is contained in:
Ilya Sumbatyants 2021-12-21 16:08:22 +07:00 committed by GitHub
parent 3fac9ab8e2
commit 9a24af1788
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 422 additions and 83 deletions

View File

@ -36,6 +36,7 @@ describe('client', () => {
name: 'xxx',
description: 'desc',
private: false,
archived: false,
members: []
})
const txes = await client.findAll(core.class.Space, { name: 'xxx' })

View File

@ -75,6 +75,7 @@ export async function generateContacts (transactorUrl: string, dbName: string, o
location: faker.address.city(),
company: faker.company.companyName(),
members: accountIds,
archived: false,
private: false
}
const vacancyId = (options.random ? `vacancy-${generateId()}-${i}` : `vacancy-genid-${i}`) as Ref<Vacancy>

View File

@ -16,6 +16,7 @@
import { MigrateOperation } from '@anticrm/model'
// Import migrate operations.
import { coreOperation } from '@anticrm/model-core'
import { taskOperation } from '@anticrm/model-task'
import { attachmentOperation } from '@anticrm/model-attachment'
import { leadOperation } from '@anticrm/model-lead'
@ -23,6 +24,7 @@ import { recruitOperation } from '@anticrm/model-recruit'
import { viewOperation } from '@anticrm/model-view'
export const migrateOperations: MigrateOperation[] = [
coreOperation,
taskOperation,
attachmentOperation,
leadOperation,

View File

@ -95,12 +95,14 @@ export function createModel (builder: Builder): void {
name: 'general',
description: 'General Channel',
private: false,
archived: false,
members: []
}, chunter.space.General)
builder.createDoc(chunter.class.Channel, core.space.Model, {
name: 'random',
description: 'Random Talks',
private: false,
archived: false,
members: []
}, chunter.space.Random)

View File

@ -251,6 +251,7 @@ export function createModel (builder: Builder): void {
name: 'Employees',
description: 'Employees',
private: false,
archived: false,
members: []
},
contact.space.Employee

View File

@ -23,6 +23,7 @@ export * from './core'
export * from './security'
export * from './tx'
export { core as default }
export { coreOperation } from './migration'
export function createModel (builder: Builder): void {
builder.createModel(

View File

@ -0,0 +1,36 @@
//
// 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 { TxOperations } from '@anticrm/core'
import {
MigrateOperation,
MigrationClient,
MigrationUpgradeClient
} from '@anticrm/model'
import core from './component'
export const coreOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const ops = new TxOperations(client, core.account.System)
const targetSpaces = (await client.findAll(core.class.Space, {}))
.filter((space) => space.archived == null)
await Promise.all(targetSpaces.map(
(space) => ops.updateDoc(space._class, space.space, space._id, { archived: false })
)).catch((e) => console.error(e))
}
}

View File

@ -33,6 +33,9 @@ export class TSpace extends TDoc implements Space {
@Prop(TypeBoolean(), 'Private' as IntlString)
private!: boolean
@Prop(TypeBoolean(), 'Archived' as IntlString)
archived!: boolean
members!: Arr<Ref<Account>>
}

View File

@ -91,6 +91,7 @@ export function createModel (builder: Builder): void {
name: 'Funnel',
description: 'Default funnel',
private: false,
archived: false,
members: []
},
lead.space.DefaultFunnel
@ -157,6 +158,7 @@ export function createModel (builder: Builder): void {
description: 'Manage funnel statuses',
members: [],
private: false,
archived: false,
icon: lead.component.TemplatesIcon
},
lead.space.FunnelTemplates

View File

@ -141,7 +141,8 @@ export function createModel (builder: Builder): void {
name: 'public',
description: 'Public Candidates',
private: false,
members: []
members: [],
archived: false
},
recruit.space.CandidatesPublic
)
@ -262,6 +263,7 @@ export function createModel (builder: Builder): void {
description: 'Manage vacancy statuses',
members: [],
private: false,
archived: false,
icon: recruit.component.TemplatesIcon
},
recruit.space.VacancyTemplates

View File

@ -33,6 +33,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/task": "~0.6.0"
}
}

View File

@ -19,6 +19,7 @@ import core, { TDoc } from '@anticrm/model-core'
import setting from '@anticrm/setting'
import type { Integration, IntegrationType, Handler } from '@anticrm/setting'
import type { IntlString } from '@anticrm/platform'
import task from '@anticrm/task'
import workbench from '@anticrm/model-workbench'
import { AnyComponent } from '@anticrm/ui'
@ -64,7 +65,7 @@ export function createModel (builder: Builder): void {
{
id: 'statuses',
label: setting.string.ManageStatuses,
icon: setting.icon.Statuses,
icon: task.icon.ManageStatuses,
component: setting.component.ManageStatuses
},
{

View File

@ -323,6 +323,7 @@ export function createModel (builder: Builder): void {
name: 'public',
description: 'Public tasks',
private: false,
archived: false,
members: []
},
task.space.TasksPublic
@ -336,6 +337,7 @@ export function createModel (builder: Builder): void {
description: 'Manage project statuses',
members: [],
private: false,
archived: false,
icon: task.component.TemplatesIcon
},
task.space.ProjectTemplates
@ -368,9 +370,50 @@ export function createModel (builder: Builder): void {
task.action.EditStatuses
)
builder.createDoc(
view.class.Action,
core.space.Model,
{
label: 'Archive' as IntlString,
icon: view.icon.Archive,
action: task.actionImpl.ArchiveSpace
},
task.action.ArchiveSpace
)
builder.createDoc(
view.class.Action,
core.space.Model,
{
label: 'Unarchive' as IntlString,
icon: view.icon.Archive,
action: task.actionImpl.UnarchiveSpace
},
task.action.UnarchiveSpace
)
builder.createDoc(view.class.ActionTarget, core.space.Model, {
target: task.class.SpaceWithStates,
action: task.action.EditStatuses
action: task.action.EditStatuses,
query: {
archived: false
}
})
builder.createDoc(view.class.ActionTarget, core.space.Model, {
target: task.class.SpaceWithStates,
action: task.action.ArchiveSpace,
query: {
archived: false
}
})
builder.createDoc(view.class.ActionTarget, core.space.Model, {
target: task.class.SpaceWithStates,
action: task.action.UnarchiveSpace,
query: {
archived: true
}
})
builder.mixin(task.class.State, core.class.Class, view.mixin.AttributeEditor, {
@ -399,7 +442,8 @@ export function createModel (builder: Builder): void {
name: 'Sequences',
description: 'Internal space to store sequence numbers',
members: [],
private: false
private: false,
archived: false
},
task.space.Sequence
)

View File

@ -30,13 +30,17 @@ export default mergeIds(taskId, task, {
CreateTask: '' as Ref<Action>,
EditStatuses: '' as Ref<Action>,
TodoItemMarkDone: '' as Ref<Action>,
TodoItemMarkUnDone: '' as Ref<Action>
TodoItemMarkUnDone: '' as Ref<Action>,
ArchiveSpace: '' as Ref<Action>,
UnarchiveSpace: '' as Ref<Action>
},
actionImpl: {
CreateTask: '' as Resource<(object: Doc) => Promise<void>>,
EditStatuses: '' as Resource<(object: Doc) => Promise<void>>,
TodoItemMarkDone: '' as Resource<(object: Doc) => Promise<void>>,
TodoItemMarkUnDone: '' as Resource<(object: Doc) => Promise<void>>
TodoItemMarkUnDone: '' as Resource<(object: Doc) => Promise<void>>,
ArchiveSpace: '' as Resource<(object: Doc) => Promise<void>>,
UnarchiveSpace: '' as Resource<(object: Doc) => Promise<void>>
},
component: {
ProjectView: '' as AnyComponent,

View File

@ -87,6 +87,7 @@ export function createModel (builder: Builder): void {
name: 'Telegram',
description: 'Space for all telegram messages',
private: false,
archived: false,
members: []
},
telegram.space.Telegram

View File

@ -31,12 +31,13 @@ describe('client', () => {
private: false,
name: 'NewSpace',
description: '',
archived: false,
members: []
})
const result2 = await client.findAll(klass, {})
expect(result2).toHaveLength(3)
await client.createDoc(klass, core.space.Model, { private: false, name: 'NewSpace', description: '', members: [] })
await client.createDoc(klass, core.space.Model, { private: false, name: 'NewSpace', description: '', members: [], archived: false })
const result3 = await client.findAll(klass, {})
expect(result3).toHaveLength(4)
})

View File

@ -65,12 +65,13 @@ describe('memdb', () => {
private: false,
name: 'NewSpace',
description: '',
members: []
members: [],
archived: false
})
const result2 = await client.findAll(core.class.Space, {})
expect(result2).toHaveLength(3)
await client.createDoc(core.class.Space, core.space.Model, { private: false, name: 'NewSpace', description: '', members: [] })
await client.createDoc(core.class.Space, core.space.Model, { private: false, name: 'NewSpace', description: '', members: [], archived: false })
const result3 = await client.findAll(core.class.Space, {})
expect(result3).toHaveLength(4)
})
@ -180,7 +181,8 @@ describe('memdb', () => {
name: 'name',
description: 'desc',
private: false,
members: []
members: [],
archived: false
})
const account = await model.createDoc(core.class.Account, core.space.Model, { email: 'email' })
await model.updateDoc(core.class.Space, core.space.Model, space, { $push: { members: account } })

View File

@ -107,7 +107,8 @@ export function genMinModel (): TxCUD<Doc>[] {
name: 'Sp1',
description: '',
private: false,
members: []
members: [],
archived: false
})
)
@ -116,7 +117,8 @@ export function genMinModel (): TxCUD<Doc>[] {
name: 'Sp2',
description: '',
private: false,
members: []
members: [],
archived: false
})
)
return txes

View File

@ -211,6 +211,7 @@ export interface Space extends Doc {
description: string
private: boolean
members: Arr<Ref<Account>>
archived: boolean
}
/**

View File

@ -97,7 +97,8 @@ export function genMinModel (): TxCUD<Doc>[] {
name: 'Sp1',
description: '',
private: false,
members: [u1, u2]
members: [u1, u2],
archived: false
})
)
@ -106,7 +107,8 @@ export function genMinModel (): TxCUD<Doc>[] {
name: 'Sp2',
description: '',
private: false,
members: [u1]
members: [u1],
archived: false
})
)
return txes

View File

@ -97,6 +97,7 @@ describe('query', () => {
name: '#0',
description: '',
members: [],
archived: false,
x: 0
})
await factory.createDoc<Channel>(core.class.Space, core.space.Model, {
@ -104,6 +105,7 @@ describe('query', () => {
name: '#1',
description: '',
members: [],
archived: false,
x: 1
})
await factory.createDoc<Channel>(core.class.Space, core.space.Model, {
@ -111,6 +113,7 @@ describe('query', () => {
name: '#2',
description: '',
members: [],
archived: false,
x: 2
})
await factory.createDoc<Channel>(core.class.Space, core.space.Model, {
@ -118,6 +121,7 @@ describe('query', () => {
name: '#3',
description: '',
members: [],
archived: false,
x: 3
})
await pp
@ -144,18 +148,21 @@ describe('query', () => {
private: false,
name: '#1',
description: '',
archived: false,
members: []
})
await factory.createDoc(core.class.Space, core.space.Model, {
private: false,
name: '#2',
description: '',
archived: false,
members: []
})
await factory.createDoc(core.class.Space, core.space.Model, {
private: false,
name: '#3',
description: '',
archived: false,
members: []
})
})
@ -180,6 +187,7 @@ describe('query', () => {
private: false,
name: '#1',
description: '',
archived: false,
members: []
})
await factory.createDoc<Channel>(core.class.Space, core.space.Model, {
@ -187,6 +195,7 @@ describe('query', () => {
private: false,
name: '#2',
description: '',
archived: false,
members: []
})
await factory.createDoc<Channel>(core.class.Space, core.space.Model, {
@ -194,6 +203,7 @@ describe('query', () => {
private: false,
name: '#3',
description: '',
archived: false,
members: []
})
await pp
@ -244,6 +254,7 @@ describe('query', () => {
private: true,
name: i.toString(),
description: '',
archived: false,
members: []
})
}

View File

@ -229,7 +229,7 @@ export class LiveQuery extends TxProcessor implements Client {
// Check if query is partially matched.
private matchQuery (q: Query, tx: TxUpdateDoc<Doc>): boolean {
if (!this.client.getHierarchy().isDerived(q._class, tx.objectClass)) {
if (!this.client.getHierarchy().isDerived(tx.objectClass, q._class)) {
return false
}

View File

@ -23,8 +23,9 @@
export let actions: {
label: IntlString
icon?: Asset | AnySvelteComponent
action: () => void | Promise<void>
action: (ctx?: any) => void | Promise<void>
}[] = []
export let ctx: any = undefined
const dispatch = createEventDispatcher()
</script>
@ -33,7 +34,7 @@
{#each actions as action}
<div class="flex-row-center menu-item" on:click={() => {
dispatch('close')
action.action()
action.action(ctx)
}}>
{#if action.icon}
<div class="scale-75">

View File

@ -36,6 +36,7 @@
name,
description,
private: false,
archived: false,
members: [getCurrentAccount()._id]
})
}

View File

@ -36,6 +36,7 @@
name,
description,
private: false,
archived: false,
members: [getCurrentAccount()._id]
})
}

View File

@ -36,6 +36,7 @@
name,
description,
private: false,
archived: false,
members: [getCurrentAccount()._id]
})
}

View File

@ -46,6 +46,7 @@
name,
description,
private: false,
archived: false,
members: []
}
)

View File

@ -38,6 +38,7 @@
name,
description,
private: false,
archived: false,
members: []
})
}

View File

@ -48,6 +48,7 @@
name,
description,
private: false,
archived: false,
members: []
})

View File

@ -19,8 +19,4 @@
<path d="M4,7.7h0.8c0.3,0,0.6-0.3,0.6-0.6S5.1,6.5,4.8,6.5H4c-0.3,0-0.6,0.3-0.6,0.6S3.6,7.7,4,7.7z"/>
<path d="M6.4,9.7H4c-0.3,0-0.6,0.3-0.6,0.6s0.3,0.6,0.6,0.6h2.4c0.3,0,0.6-0.3,0.6-0.6S6.7,9.7,6.4,9.7z"/>
</symbol>
<symbol id="statuses" viewBox="0 0 16 16">
<path d="M13.3,8.3c-0.1,2.8-2.5,5.1-5.4,5.1C5,13.4,2.6,11,2.6,8c0-2.9,2.3-5.2,5.1-5.4c0.1-0.4,0.2-0.7,0.4-1c0,0-0.1,0-0.1,0 C4.4,1.7,1.6,4.5,1.6,8c0,3.5,2.9,6.4,6.4,6.4s6.4-2.9,6.4-6.4c0,0,0-0.1,0-0.1C14,8.1,13.7,8.2,13.3,8.3z"/>
<ellipse cx="12.1" cy="3.9" rx="2.5" ry="2.5"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -20,7 +20,6 @@ const icons = require('../assets/icons.svg')
loadMetadata(setting.icon, {
Setting: `${icons}#settings`,
Integrations: `${icons}#integration`,
Statuses: `${icons}#statuses`,
Support: `${icons}#support`,
Privacy: `${icons}#privacy`,
Terms: `${icons}#terms`

View File

@ -21,6 +21,7 @@
import type { KanbanTemplate, KanbanTemplateSpace, StateTemplate } from '@anticrm/task'
import { KanbanTemplateEditor } from '@anticrm/task-resources'
import setting from '@anticrm/setting'
import task from '@anticrm/task'
import Folders from './Folders.svelte'
import Templates from './Templates.svelte'
@ -53,7 +54,7 @@
<div class="flex-col h-full">
<div class="flex-row-center header">
<div class="content-color mr-3"><Icon icon={setting.icon.Statuses} size={'medium'} /></div>
<div class="content-color mr-3"><Icon icon={task.icon.ManageStatuses} size={'medium'} /></div>
<div class="fs-title"><Label label={setting.string.ManageStatuses}/></div>
</div>
<div class="flex-row-top h-full overflow-x-auto scroll-m-10">

View File

@ -82,7 +82,6 @@ export default plugin(settingId, {
icon: {
Setting: '' as Asset,
Integrations: '' as Asset,
Statuses: '' as Asset,
Support: '' as Asset,
Privacy: '' as Asset,
Terms: '' as Asset

View File

@ -19,4 +19,8 @@
<symbol id='todo-uncheck' viewBox="0 0 16 16">
<circle cx="8" cy="8" r="6" stroke="white" fill="none"/>
</symbol>
<symbol id="manage-statuses" viewBox="0 0 16 16">
<path d="M13.3,8.3c-0.1,2.8-2.5,5.1-5.4,5.1C5,13.4,2.6,11,2.6,8c0-2.9,2.3-5.2,5.1-5.4c0.1-0.4,0.2-0.7,0.4-1c0,0-0.1,0-0.1,0 C4.4,1.7,1.6,4.5,1.6,8c0,3.5,2.9,6.4,6.4,6.4s6.4-2.9,6.4-6.4c0,0,0-0.1,0-0.1C14,8.1,13.7,8.2,13.3,8.3z"/>
<ellipse cx="12.1" cy="3.9" rx="2.5" ry="2.5"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -22,7 +22,8 @@ loadMetadata(task.icon, {
Kanban: `${icons}#kanban`,
Status: `${icons}#status`,
TodoCheck: `${icons}#todo-check`,
TodoUnCheck: `${icons}#todo-uncheck`
TodoUnCheck: `${icons}#todo-uncheck`,
ManageStatuses: `${icons}#manage-statuses`
})
addStringsLoader(taskId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -45,6 +45,7 @@
name,
description,
private: false,
archived: false,
members: []
}
)

View File

@ -24,7 +24,7 @@ import TemplatesIcon from './components/TemplatesIcon.svelte'
import EditIssue from './components/EditIssue.svelte'
import { Doc } from '@anticrm/core'
import { showPopup } from '@anticrm/ui'
import { getClient } from '@anticrm/presentation'
import { getClient, MessageBox } from '@anticrm/presentation'
import KanbanView from './components/kanban/KanbanView.svelte'
import StateEditor from './components/state/StateEditor.svelte'
@ -63,6 +63,48 @@ async function toggleDone (value: boolean, object: TodoItem): Promise<void> {
)
}
async function ArchiveSpace (object: SpaceWithStates): Promise<void> {
showPopup(
MessageBox,
{
label: 'Archive',
message: `Do you want to archive ${object.name}?`
},
undefined,
(result: boolean) => {
if (result) {
const client = getClient()
// eslint-disable-next-line @typescript-eslint/no-floating-promises
client.updateDoc(object._class, object.space, object._id, {
archived: true
})
}
}
)
}
async function UnarchiveSpace (object: SpaceWithStates): Promise<void> {
showPopup(
MessageBox,
{
label: 'Unarchive',
message: `Do you want to unarchive ${object.name}?`
},
undefined,
(result: boolean) => {
if (result) {
const client = getClient()
// eslint-disable-next-line @typescript-eslint/no-floating-promises
client.updateDoc(object._class, object.space, object._id, {
archived: false
})
}
}
)
}
export default async (): Promise<Resources> => ({
component: {
CreateTask,
@ -83,6 +125,8 @@ export default async (): Promise<Resources> => ({
CreateTask: createTask,
EditStatuses: editStatuses,
TodoItemMarkDone: async (obj: TodoItem) => await toggleDone(true, obj),
TodoItemMarkUnDone: async (obj: TodoItem) => await toggleDone(false, obj)
TodoItemMarkUnDone: async (obj: TodoItem) => await toggleDone(false, obj),
ArchiveSpace,
UnarchiveSpace
}
})

View File

@ -190,7 +190,8 @@ const task = plugin(taskId, {
Kanban: '' as Asset,
Status: '' as Asset,
TodoCheck: '' as Asset,
TodoUnCheck: '' as Asset
TodoUnCheck: '' as Asset,
ManageStatuses: '' as Asset
},
global: {
// Global task root, if not attached to some other object.

View File

@ -35,4 +35,8 @@
<path d="M12.8,6.6c-0.8,0-1.4,0.6-1.4,1.4c0,0.8,0.6,1.4,1.4,1.4s1.4-0.6,1.4-1.4C14.2,7.2,13.6,6.6,12.8,6.6z" />
</symbol>
<symbol id="archive" viewBox="0 0 16 16" >
<path d="M3.33333 5.33366V9.33366C3.33333 11.2193 3.33333 12.1621 3.91912 12.7479C4.50491 13.3337 5.44772 13.3337 7.33333 13.3337H8.66667C10.5523 13.3337 11.4951 13.3337 12.0809 12.7479C12.6667 12.1621 12.6667 11.2193 12.6667 9.33366V5.33366M3.33333 5.33366H12.6667M3.33333 5.33366H2.24242C2.20301 5.33366 2.1833 5.33366 2.16683 5.33089C2.08277 5.31675 2.01691 5.25089 2.00277 5.16682C2 5.15036 2 5.13065 2 5.09123V5.09123C2 4.69708 2 4.50001 2.02769 4.33533C2.16905 3.49469 2.8277 2.83604 3.66834 2.69468C3.83301 2.66699 4.03009 2.66699 4.42424 2.66699H11.5758C11.9699 2.66699 12.167 2.66699 12.3317 2.69468C13.1723 2.83604 13.8309 3.49469 13.9723 4.33533C14 4.50001 14 4.69708 14 5.09123V5.09123C14 5.13065 14 5.15036 13.9972 5.16682C13.9831 5.25089 13.9172 5.31675 13.8332 5.33089C13.8167 5.33366 13.797 5.33366 13.7576 5.33366H12.6667" />
<path d="M6.66663 10.667H9.33329" stroke-linecap="round"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -21,7 +21,8 @@ loadMetadata(view.icon, {
Table: `${icons}#table`,
Delete: `${icons}#delete`,
Move: `${icons}#move`,
MoreH: `${icons}#more-h`
MoreH: `${icons}#more-h`,
Archive: `${icons}#archive`
})
addStringsLoader(viewId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import type { Doc } from '@anticrm/core'
import type { Doc, Class, Ref } from '@anticrm/core'
import type { Asset, IntlString, Resource } from '@anticrm/platform'
import { getResource } from '@anticrm/platform'
import { getClient } from '@anticrm/presentation'
@ -22,10 +22,10 @@
import { getActions } from '../utils'
export let object: Doc
let actions: {
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
export let actions: {
label: IntlString
icon?: Asset
icon: Asset
action: () => void
}[] = []
@ -36,7 +36,7 @@
await impl(object)
}
getActions(client, object).then(result => {
getActions(client, object, baseMenuClass).then(result => {
actions = result.map(a => ({
label: a.label,
icon: a.icon,
@ -46,5 +46,5 @@
</script>
<Menu actions={actions} on:close/>
<Menu {actions} on:close/>

View File

@ -26,7 +26,8 @@
export let _class: Ref<Class<Doc>>
export let query: DocumentQuery<Doc>
export let options: FindOptions<Doc> | undefined
export let options: FindOptions<Doc> | undefined = undefined
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
export let config: (BuildModelKey|string)[]
let sortKey = 'modifiedOn'
@ -53,9 +54,9 @@
const client = getClient()
const showMenu = (ev: MouseEvent, object: Doc, row: number): void => {
const showMenu = async (ev: MouseEvent, object: Doc, row: number): Promise<void> => {
selectRow = row
showPopup(Menu, { object }, ev.target as HTMLElement, () => { selectRow = undefined })
showPopup(Menu, { object, baseMenuClass }, ev.target as HTMLElement, () => { selectRow = undefined })
}
function changeSorting (key: string): void {

View File

@ -15,7 +15,7 @@
-->
<script lang="ts">
import type { Class, Doc, FindOptions, Ref, Space } from '@anticrm/core'
import type { Class, Doc, DocumentQuery, FindOptions, Ref, Space } from '@anticrm/core'
import { SortingOrder } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import { CheckBox, IconDown, IconUp, Label, Loading, ScrollBox, showPopup } from '@anticrm/ui'
@ -24,10 +24,12 @@
import Menu from './Menu.svelte'
export let _class: Ref<Class<Doc>>
export let space: Ref<Space>
export let options: FindOptions<Doc> | undefined
export let space: Ref<Space> | undefined = undefined
export let query: DocumentQuery<Doc> = {}
export let options: FindOptions<Doc> | undefined = undefined
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
export let config: string[]
export let search: string
export let search: string = ''
let sortKey = 'modifiedOn'
let sortOrder = SortingOrder.Descending
@ -35,10 +37,14 @@
let objects: Doc[]
const query = createQuery()
$: query.query(
const q = createQuery()
$: q.query(
_class,
search === '' ? { space } : { $search: search },
{
...query,
...search !== '' ? { $search: search } : {},
...search === '' && space !== undefined ? { space } : {}
},
(result) => {
objects = result
},
@ -63,7 +69,7 @@
const showMenu = (ev: MouseEvent, object: Doc, row: number): void => {
selectRow = row
showPopup(Menu, { object }, ev.target as HTMLElement, () => {
showPopup(Menu, { object, baseMenuClass }, ev.target as HTMLElement, () => {
selectRow = undefined
})
}

View File

@ -31,7 +31,7 @@ import MoveView from './components/Move.svelte'
export { default as ContextMenu } from './components/Menu.svelte'
export { buildModel, getActions, getObjectPresenter } from './utils'
export { Table }
export { Table, TableView }
function Delete (object: Doc): void {
showPopup(
@ -55,8 +55,12 @@ async function Move (object: Doc): Promise<void> {
export default async (): Promise<Resources> => ({
actionImpl: {
Delete,
Move
Delete: {
apply: Delete
},
Move: {
apply: Move
}
},
component: {
StringEditor,

View File

@ -144,7 +144,8 @@ const view = plugin(viewId, {
Table: '' as Asset,
Delete: '' as Asset,
MoreH: '' as Asset,
Move: '' as Asset
Move: '' as Asset,
Archive: '' as Asset
}
})
export default view

View File

@ -4,6 +4,8 @@
"Delete": "Delete",
"Create": "Create",
"ShowMenu": "Show menu",
"HideMenu": "Hide menu"
"HideMenu": "Hide menu",
"Archive": "Archive",
"Archived": "Archived {object}"
}
}

View File

@ -0,0 +1,81 @@
<!--
// 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 core from '@anticrm/core'
import type { Space } from '@anticrm/core'
import { translate } from '@anticrm/platform'
import { Label, Icon } from '@anticrm/ui'
import view from '@anticrm/view'
import { TableView } from '@anticrm/view-resources'
import { createQuery } from '@anticrm/presentation'
import { NavigatorModel } from '@anticrm/workbench'
import workbench from '../plugin'
export let model: NavigatorModel | undefined
const query = createQuery()
let spaceSample: Space | undefined
$: if (model) {
query.query(
core.class.Space,
{
_class: { $in: model.spaces.map(x => x.spaceClass) },
archived: true
},
(result) => { spaceSample = result[0] },
{ limit: 1 }
)
}
let spaceName = ''
$: {
const spaceClass = spaceSample?._class ?? ''
const spaceModel = model?.spaces.find(x => x.spaceClass == spaceClass)
const label = spaceModel?.label
if (label) {
void translate(label, {}).then((l) => { spaceName = l.toLowerCase() })
}
}
</script>
<div class="flex-col h-full">
<div class="flex-row-center header">
<div class="content-color mr-3"><Icon icon={view.icon.Archive} size={'medium'} /></div>
<div class="fs-title"><Label label={workbench.string.Archived} params={{ object: spaceName }} /></div>
</div>
{#if spaceSample !== undefined}
<TableView
_class={spaceSample._class}
config={['name', 'company', 'location', 'modifiedOn']}
options={{}}
baseMenuClass={core.class.Space}
query={{
_class: { $in: model?.spaces.map(x => x.spaceClass) ?? [] },
archived: true
}} />
{/if}
</div>
<style lang="scss">
.header {
padding: 0 1.75rem 0 2.5rem;
height: 4rem;
min-height: 4rem;
}
</style>

View File

@ -14,17 +14,36 @@
-->
<script lang="ts">
import type { NavigatorModel, SpecialNavModel } from '@anticrm/workbench'
import core from '@anticrm/core'
import type { Space } from '@anticrm/core'
import type { NavigatorModel } from '@anticrm/workbench'
import { getCurrentLocation, navigate } from '@anticrm/ui'
import { createQuery } from '@anticrm/presentation'
import view from '@anticrm/view'
import workbench from '../plugin'
import SpacesNav from './navigator/SpacesNav.svelte'
import TreeSeparator from './navigator/TreeSeparator.svelte'
import SpecialElement from './navigator/SpecialElement.svelte'
import { getCurrentLocation, navigate } from '@anticrm/ui'
export let model: NavigatorModel | undefined
const query = createQuery()
let archivedSpaces: Space[] = []
$: if (model) {
query.query(
core.class.Space,
{
_class: { $in: model.spaces.map(x => x.spaceClass) },
archived: true
},
(result) => { archivedSpaces = result })
}
function selectSpecial (sp: SpecialNavModel): void {
function selectSpecial (id: string): void {
const loc = getCurrentLocation()
loc.path[2] = sp.id
loc.path[2] = id
loc.path.length = 3
navigate(loc)
}
@ -33,13 +52,26 @@
{#if model}
{#if model.specials}
{#each model.specials as special}
<SpecialElement label={special.label} icon={special.icon} on:click={() => selectSpecial(special)} />
<SpecialElement label={special.label} icon={special.icon} on:click={() => selectSpecial(special.id)} />
{/each}
{#if model.spaces.length}
{/if}
{#if archivedSpaces.length > 0}
<SpecialElement label={workbench.string.Archive} icon={view.icon.Archive} on:click={() => selectSpecial('archive')} />
{/if}
{#if model.spaces.length}
<div class="separator">
<TreeSeparator />
{/if}
</div>
{/if}
{#each model.spaces as m (m.label)}
<SpacesNav model={m}/>
{/each}
{/if}
<style lang="scss">
.separator {
&:nth-child(2) {
display: none;
}
}
</style>

View File

@ -29,11 +29,12 @@
import SpaceHeader from './SpaceHeader.svelte'
import SpaceView from './SpaceView.svelte'
import { AnyComponent, Component, location, Popup, showPopup, TooltipInstance, closeTooltip, ActionIcon, IconEdit } from '@anticrm/ui'
import { AnyComponent, Component, location, Popup, showPopup, TooltipInstance, closeTooltip, ActionIcon, IconEdit, AnySvelteComponent } from '@anticrm/ui'
import core from '@anticrm/core'
import AccountPopup from './AccountPopup.svelte'
import AppItem from './AppItem.svelte'
import TopMenu from './icons/TopMenu.svelte'
import Archive from './Archive.svelte'
export let client: Client
@ -42,6 +43,7 @@
let currentApp: Ref<Application> | undefined
let currentApplication: Application | undefined
let currentSpace: Ref<Space> | undefined
let ownSpecialComponent: AnySvelteComponent | undefined
let specialComponent: AnyComponent | undefined
let currentView: ViewConfiguration | undefined
let createItemDialog: AnyComponent | undefined
@ -52,22 +54,37 @@
currentApplication = (await client.findAll(workbench.class.Application, { _id: currentApp }))[0]
navigatorModel = currentApplication?.navigatorModel
let currentFolder = loc.path[2] as Ref<Space>
ownSpecialComponent = getOwnSpecialComponent(currentFolder)
if (ownSpecialComponent !== undefined) {
return
}
specialComponent = getSpecialComponent(currentFolder)
if (!specialComponent) {
currentSpace = currentFolder
const space = (await client.findAll(core.class.Space, { _id: currentSpace }))[0]
if (space) {
const spaceClass = client.getHierarchy().getClass(space._class) // (await client.findAll(core.class.Class, { _id: space._class }))[0]
const view = client.getHierarchy().as(spaceClass, workbench.mixin.SpaceView)
currentView = view.view
createItemDialog = currentView.createItemDialog
} else {
currentView = undefined
createItemDialog = undefined
}
if (specialComponent !== undefined) {
return
}
currentSpace = currentFolder
const space = (await client.findAll(core.class.Space, { _id: currentSpace }))[0]
if (space) {
const spaceClass = client.getHierarchy().getClass(space._class) // (await client.findAll(core.class.Class, { _id: space._class }))[0]
const view = client.getHierarchy().as(spaceClass, workbench.mixin.SpaceView)
currentView = view.view
createItemDialog = currentView.createItemDialog
} else {
currentView = undefined
createItemDialog = undefined
}
}))
function getOwnSpecialComponent (id: string): AnySvelteComponent | undefined {
if (id === 'archive') {
return Archive
}
}
function getSpecialComponent (id: string): AnyComponent | undefined {
let special = navigatorModel?.specials?.find((x) => x.id === id)
return special?.component
@ -119,7 +136,9 @@
</div>
{/if}
<div class="panel-component">
{#if specialComponent}
{#if ownSpecialComponent}
<svelte:component this={ownSpecialComponent} model={navigatorModel} />
{:else if specialComponent}
<Component is={specialComponent} />
{:else}
<SpaceHeader space={currentSpace} {createItemDialog} />

View File

@ -34,7 +34,7 @@
let spaces: Space[] = []
let selected: Ref<Space> | undefined = undefined
$: query.query(model.spaceClass, {}, result => { spaces = result })
$: query.query(model.spaceClass, { archived: false }, result => { spaces = result })
const addSpace: Action = {
label: model.addSpaceLabel,
@ -72,7 +72,7 @@
result.push({
icon: act.icon ?? IconEdit,
label: act.label,
action: async (props, ev) => {
action: async () => {
const impl = await getResource(act.action)
await impl(space)
}

View File

@ -20,7 +20,7 @@
import type { Asset, IntlString } from '@anticrm/platform'
import type { Action } from '@anticrm/ui'
import type { Ref, Space } from '@anticrm/core'
import { Icon, Label, ActionIcon } from '@anticrm/ui'
import { Icon, Label, ActionIcon, Menu, showPopup, IconMoreH } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
export let _id: Ref<Space> | undefined = undefined
@ -34,9 +34,15 @@
export let actions: Action[] = []
const dispatch = createEventDispatcher()
let hovered = false
function onMenuClick(ev: MouseEvent) {
hovered = true
showPopup(Menu, { actions, ctx: _id }, ev.target as HTMLElement, () => { hovered = false })
}
</script>
<div class="container" class:selected
<div class="container" class:selected class:hovered
on:click|stopPropagation={() => {
if (node && !icon) collapsed = !collapsed
dispatch('click')
@ -52,11 +58,15 @@
<span class="label" class:sub={node}>
{#if label}<Label {label}/>{:else}{title}{/if}
</span>
{#each actions as action}
{#if actions.length === 1}
<div class="tool">
<ActionIcon label={action.label} icon={action.icon} size={'small'} action={(ev) => { action.action(_id, ev) }} />
<ActionIcon label={actions[0].label} icon={actions[0].icon} size={'small'} action={(ev) => { actions[0].action(_id, ev) }} />
</div>
{/each}
{:else if actions.length > 1}
<div class="tool" on:click|stopPropagation={onMenuClick}>
<IconMoreH size={'small'} />
</div>
{/if}
{#if notifications > 0 && collapsed}
<div class="counter">{notifications}</div>
{/if}
@ -106,7 +116,7 @@
color: var(--theme-caption-color);
}
&:hover {
&:hover, &.hovered {
background-color: var(--theme-button-bg-enabled);
.tool {
visibility: visible;

View File

@ -14,9 +14,9 @@
//
import { mergeIds } from '@anticrm/platform'
import type { IntlString } from '@anticrm/platform'
import workbench, { workbenchId } from '@anticrm/workbench'
import { IntlString } from '@anticrm/platform'
export default mergeIds(workbenchId, workbench, {
string: {
@ -24,6 +24,8 @@ export default mergeIds(workbenchId, workbench, {
Delete: '' as IntlString,
Create: '' as IntlString,
ShowMenu: '' as IntlString,
HideMenu: '' as IntlString
HideMenu: '' as IntlString,
Archive: '' as IntlString,
Archived: '' as IntlString
}
})

View File

@ -180,6 +180,7 @@ export function genMinModel (): TxCUD<Doc>[] {
name: 'Sp1',
description: '',
private: false,
archived: false,
members: [u1, u2]
})
)
@ -189,6 +190,7 @@ export function genMinModel (): TxCUD<Doc>[] {
name: 'Sp2',
description: '',
private: false,
archived: false,
members: [u1]
})
)