Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-02-03 16:03:53 +07:00 committed by GitHub
parent e7c0f2406a
commit 8c51a4ec2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 233 additions and 174 deletions

View File

@ -56,34 +56,14 @@ module.exports = {
{
test: /\.svelte$/,
use: {
loader: 'svelte-loader',
loader: 'svelte-loader',
options: {
compilerOptions: {
dev: !prod,
},
emitCss: true,
preprocess: require('svelte-preprocess')({ postcss: true })
}
// options: {
// dev: !prod,
// emitCss: true,
// hotReload: !prod,
// preprocess: require('svelte-preprocess')({
// babel: {
// presets: [
// [
// '@babel/preset-env',
// {
// loose: true,
// modules: false,
// targets: {
// esmodules: true
// }
// }
// ],
// '@babel/typescript'
// ],
// plugins: ['@babel/plugin-proposal-optional-chaining']
// }
// })
// }
preprocess: require('svelte-preprocess')({ postcss: true })
}
}
},

View File

@ -129,6 +129,14 @@ export function createModel (builder: Builder): void {
icon: contact.icon.Person,
label: recruit.string.Candidates,
position: 'bottom'
},
{
id: 'archive',
component: workbench.component.Archive,
icon: view.icon.Archive,
label: workbench.string.Archive,
position: 'top',
visibleIf: workbench.function.HasArchiveSpaces
}
]
}
@ -287,6 +295,22 @@ export function createModel (builder: Builder): void {
label: recruit.string.SearchApplication,
query: recruit.completion.ApplicationQuery
}, recruit.completion.ApplicationCategory)
builder.createDoc(view.class.ActionTarget, core.space.Model, {
target: recruit.class.Vacancy,
action: task.action.ArchiveSpace,
query: {
archived: false
}
})
builder.createDoc(view.class.ActionTarget, core.space.Model, {
target: recruit.class.Vacancy,
action: task.action.UnarchiveSpace,
query: {
archived: true
}
})
}
export { default } from './plugin'

View File

@ -404,22 +404,6 @@ export function createModel (builder: Builder): void {
}
})
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, {
editor: task.component.StateEditor
})

View File

@ -13,12 +13,20 @@
// limitations under the License.
//
import { mergeIds } from '@anticrm/platform'
import { Space } from '@anticrm/core'
import { IntlString, mergeIds, Resource } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
import workbench, { workbenchId } from '@anticrm/workbench'
export default mergeIds(workbenchId, workbench, {
component: {
ApplicationPresenter: '' as AnyComponent
ApplicationPresenter: '' as AnyComponent,
Archive: '' as AnyComponent
},
string: {
Archive: '' as IntlString
},
function: {
HasArchiveSpaces: '' as Resource<(spaces: Space[]) => boolean>
}
})

View File

@ -15,8 +15,7 @@
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import { ActionIcon, IconMoreH, Label } from '@anticrm/ui'
import workbench from '../plugin'
import { Label } from '@anticrm/ui'
export let label: IntlString
// export let action: () => Promise<void> | void

View File

@ -14,78 +14,91 @@
-->
<script lang="ts">
import core from '@anticrm/core'
import type { Space } from '@anticrm/core'
import type { NavigatorModel, SpecialNavModel } from '@anticrm/workbench'
import { getCurrentLocation, navigate, Scroller } from '@anticrm/ui'
import core, { Ref, SortingOrder, Space } from '@anticrm/core'
import { getResource } from '@anticrm/platform'
import { createQuery } from '@anticrm/presentation'
import view from '@anticrm/view'
import workbench from '../plugin'
import { Scroller } from '@anticrm/ui'
import type { NavigatorModel, SpecialNavModel } from '@anticrm/workbench'
import { createEventDispatcher } from 'svelte'
import SpacesNav from './navigator/SpacesNav.svelte'
import TreeSeparator from './navigator/TreeSeparator.svelte'
import SpecialElement from './navigator/SpecialElement.svelte'
import TreeSeparator from './navigator/TreeSeparator.svelte'
export let model: NavigatorModel | undefined
export let currentSpace: Ref<Space> | undefined
export let currentSpecial: string | undefined
const query = createQuery()
let archivedSpaces: Space[] = []
let spaces: Space[] = []
let shownSpaces: Space[] = []
$: if (model) {
query.query(
core.class.Space,
{
_class: { $in: model.spaces.map(x => x.spaceClass) },
archived: true
_class: { $in: model.spaces.map(x => x.spaceClass) }
},
(result) => { archivedSpaces = result })
(result) => { spaces = result },
{ sort: { name: SortingOrder.Ascending } })
}
let showDivider: Boolean = false
let specTopCount: number
let specBottomCount: number
let spArchCount: number
let spModelCount: number
$: if (model) {
if (model.specials) {
specTopCount = getSpecials(model.specials, 'top').length
specBottomCount = getSpecials(model.specials, 'bottom').length
let topSpecials: SpecialNavModel[] = []
let bottomSpecials: SpecialNavModel[] = []
async function update (model: NavigatorModel, spaces: Space[]) {
if (model.specials !== undefined) {
topSpecials = await getSpecials(model.specials, 'top', spaces)
specTopCount = topSpecials.length
bottomSpecials = await getSpecials(model.specials, 'bottom', spaces)
specBottomCount = bottomSpecials.length
}
if (model.spaces) spModelCount = model.spaces.length
if (archivedSpaces) spArchCount = archivedSpaces.length
showDivider = ((specTopCount > 0 || spArchCount > 0) && (specBottomCount > 0 || spModelCount > 0)) ?? false
showDivider = (specTopCount > 0) ?? false
shownSpaces = spaces.filter(sp => !sp.archived)
}
function selectSpecial (id: string): void {
const loc = getCurrentLocation()
loc.path[2] = id
loc.path.length = 3
navigate(loc)
}
function getSpecials (specials: SpecialNavModel[], state: 'top' | 'bottom'): SpecialNavModel[] {
return specials.filter(p => (p.position ?? 'top') === state)
$: if (model) update(model, spaces)
async function getSpecials (specials: SpecialNavModel[], state: 'top' | 'bottom', spaces: Space[]): Promise<SpecialNavModel[]> {
const result: SpecialNavModel[] = []
for (const sp of specials) {
if ((sp.position ?? 'top') === state) {
if (sp.visibleIf !== undefined) {
const f = await getResource(sp.visibleIf)
if (f(spaces)) {
result.push(sp)
}
} else {
result.push(sp)
}
}
}
return result
}
const dispatch = createEventDispatcher()
</script>
{#if model}
<Scroller>
{#if model.specials}
{#each getSpecials(model.specials, 'top') as special}
<SpecialElement label={special.label} icon={special.icon} on:click={() => selectSpecial(special.id)} />
{#each topSpecials as special}
<SpecialElement label={special.label} icon={special.icon} on:click={() => dispatch('special', special.id)} selected={special.id === currentSpecial} />
{/each}
{/if}
{#if archivedSpaces.length > 0}
<SpecialElement label={workbench.string.Archive} icon={view.icon.Archive} on:click={() => selectSpecial('archive')} />
{/if}
{#if showDivider}<TreeSeparator />{/if}
{#each model.spaces as m (m.label)}
<SpacesNav model={m}/>
<SpacesNav spaces={shownSpaces} {currentSpace} model={m} on:space/>
{/each}
{#if model.specials}
{#each getSpecials(model.specials, 'bottom') as special}
<SpecialElement label={special.label} icon={special.icon} on:click={() => selectSpecial(special.id)} />
{#each bottomSpecials as special}
<SpecialElement label={special.label} icon={special.icon} on:click={() => dispatch('special', special.id)} selected={special.id === currentSpecial} />
{/each}
{/if}
<div class="antiNav-space" />

View File

@ -13,15 +13,15 @@
// limitations under the License.
-->
<script lang="ts">
import { Client, getCurrentAccount, Ref, Space } from '@anticrm/core'
import core from '@anticrm/core'
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
import core, { Client, getCurrentAccount, Ref, Space } from '@anticrm/core'
import { Avatar, createQuery, setClient } from '@anticrm/presentation'
import {
AnyComponent,
AnySvelteComponent,
closeTooltip,
AnyComponent, closeTooltip,
Component,
getCurrentLocation,
location,
navigate,
Popup,
showPopup,
TooltipInstance
@ -33,62 +33,93 @@
import ActivityStatus from './ActivityStatus.svelte'
import AppItem from './AppItem.svelte'
import Applications from './Applications.svelte'
import Archive from './Archive.svelte'
import TopMenu from './icons/TopMenu.svelte'
import NavHeader from './NavHeader.svelte'
import Navigator from './Navigator.svelte'
import SpaceView from './SpaceView.svelte'
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
export let client: Client
setClient(client)
let currentApp: Ref<Application> | undefined
let currentApplication: Application | undefined
let currentSpace: Ref<Space> | undefined
let ownSpecialComponent: AnySvelteComponent | undefined
let currentSpecial: string | undefined
let specialComponent: AnyComponent | undefined
let currentApplication: Application | undefined
let currentView: ViewConfiguration | undefined
let createItemDialog: AnyComponent | undefined
let navigatorModel: NavigatorModel | undefined
onDestroy(
location.subscribe(async (loc) => {
currentApp = loc.path[1] as Ref<Application>
currentApplication = await client.findOne(workbench.class.Application, { _id: currentApp })
navigatorModel = currentApplication?.navigatorModel
if (currentApp !== loc.path[1]) {
currentApp = loc.path[1] as Ref<Application>
currentApplication = await client.findOne(workbench.class.Application, { _id: currentApp })
navigatorModel = currentApplication?.navigatorModel
}
const currentFolder = loc.path[2] as Ref<Space>
ownSpecialComponent = getOwnSpecialComponent(currentFolder)
if (ownSpecialComponent !== undefined) {
return
if (currentSpecial !== currentFolder) {
specialComponent = getSpecialComponent(currentFolder)
if (specialComponent !== undefined) {
currentSpecial = currentFolder
return
}
}
specialComponent = getSpecialComponent(currentFolder)
if (specialComponent !== undefined) {
return
}
const space = await client.findOne(core.class.Space, { _id: currentFolder })
currentSpace = currentFolder
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
}
updateSpace(currentSpace)
})
)
function getOwnSpecialComponent (id: string): AnySvelteComponent | undefined {
if (id === 'archive') {
return Archive
async function updateSpace (spaceId?: Ref<Space>): Promise<void> {
if (spaceId === currentSpace) {
return
}
if (spaceId === undefined) {
return
}
const space = await client.findOne(core.class.Space, { _id: spaceId })
if (space) {
currentSpace = spaceId
currentSpecial = undefined
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
const loc = getCurrentLocation()
loc.path[2] = spaceId
loc.path.length = 3
navigate(loc)
} else {
currentView = undefined
createItemDialog = undefined
}
}
$: updateSpace(currentSpace)
function selectSpecial (id: string): void {
specialComponent = getSpecialComponent(id)
if (specialComponent !== undefined) {
currentSpecial = id
currentSpace = undefined
const loc = getCurrentLocation()
loc.path[2] = id
loc.path.length = 3
navigate(loc)
}
}
function selectArchive (): void {
currentSpace = undefined
currentSpecial = undefined
const loc = getCurrentLocation()
loc.path[2] = 'archive'
loc.path.length = 3
navigate(loc)
}
function getSpecialComponent (id: string): AnyComponent | undefined {
@ -112,17 +143,28 @@
let employee: Employee | undefined
const accountQ = createQuery()
const employeeQ = createQuery()
$: accountQ.query(contact.class.EmployeeAccount, {
_id: getCurrentAccount()._id as Ref<EmployeeAccount>
}, (res) => {
account = res[0]
}, { limit: 1 })
$: accountQ.query(
contact.class.EmployeeAccount,
{
_id: getCurrentAccount()._id as Ref<EmployeeAccount>
},
(res) => {
account = res[0]
},
{ limit: 1 }
)
$: account && employeeQ.query(contact.class.Employee, {
_id: account.employee
}, (res) => {
employee = res[0]
}, { limit: 1 })
$: account &&
employeeQ.query(
contact.class.Employee,
{
_id: account.employee
},
(res) => {
employee = res[0]
},
{ limit: 1 }
)
</script>
{#if client}
@ -145,6 +187,7 @@
label={visibileNav ? workbench.string.HideMenu : workbench.string.ShowMenu}
selected={!visibileNav}
action={toggleNav}
notify={false}
/>
</div>
<Applications {apps} active={currentApp} />
@ -166,20 +209,23 @@
{#if currentApplication}
<NavHeader label={currentApplication.label} />
{/if}
<Navigator model={navigatorModel} />
</div>
<Navigator
{currentSpace}
{currentSpecial}
model={navigatorModel}
on:special={(evt) => selectSpecial(evt.detail)}
on:space={(evt) => updateSpace(evt.detail)}
on:archive={(evt) => selectArchive()}
/>
</div>
{/if}
<div class="antiPanel-component filled indent">
{#if currentApplication && currentApplication.component}
<Component is={currentApplication.component} />
<Component is={currentApplication.component} />
{:else if specialComponent}
<Component is={specialComponent} />
{:else}
{#if ownSpecialComponent}
<svelte:component this={ownSpecialComponent} model={navigatorModel} />
{:else if specialComponent}
<Component is={specialComponent} />
{:else}
<SpaceView {currentSpace} {currentView} {createItemDialog} />
{/if}
<SpaceView {currentSpace} {currentView} {createItemDialog} />
{/if}
</div>
<!-- <div class="aside"><Chat thread/></div> -->

View File

@ -12,30 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { SortingOrder } from '@anticrm/core'
import type { Doc, Ref, Space } from '@anticrm/core'
import core from '@anticrm/core'
import { getResource, IntlString } from '@anticrm/platform'
import { createQuery, getClient } from '@anticrm/presentation'
import { Action, getCurrentLocation, IconAdd, IconEdit, location, navigate, showPopup } from '@anticrm/ui'
import { getClient } from '@anticrm/presentation'
import { Action, IconAdd, IconEdit, showPopup } from '@anticrm/ui'
import { getActions as getContributedActions } from '@anticrm/view-resources'
import type { SpacesNavModel } from '@anticrm/workbench'
import { onDestroy } from 'svelte'
import { createEventDispatcher } from 'svelte'
import { classIcon } from '../../utils'
import SpacePanel from './SpacePanel.svelte'
import TreeItem from './TreeItem.svelte'
import TreeNode from './TreeNode.svelte'
export let model: SpacesNavModel
export let currentSpace: Ref<Space> | undefined
export let spaces: Space[]
const client = getClient()
const query = createQuery()
let spaces: Space[] = []
let selected: Ref<Space> | undefined = undefined
$: query.query(model.spaceClass, { archived: false }, result => { spaces = result }, { sort: { name: SortingOrder.Ascending }})
const dispatch = createEventDispatcher()
const addSpace: Action = {
label: model.addSpaceLabel,
@ -53,18 +48,10 @@
}
}
function selectSpace (id: Ref<Space>) {
const loc = getCurrentLocation()
loc.path[2] = id
loc.path.length = 3
navigate(loc)
dispatch('space', id)
}
onDestroy(location.subscribe(async (loc) => {
selected = loc.path[2] as Ref<Space>
}))
async function getActions (space: Space): Promise<Action[]> {
const result = [editSpace]
@ -87,7 +74,16 @@
<TreeNode label={model.label} actions={[addSpace]}>
{#each spaces as space}
{#await getActions(space) then actions}
<TreeItem _id={space._id} title={space.name} icon={classIcon(client, space._class)} selected={selected === space._id} {actions} on:click={() => { selectSpace(space._id) }}/>
<TreeItem
_id={space._id}
title={space.name}
icon={classIcon(client, space._class)}
selected={currentSpace === space._id}
{actions}
on:click={() => {
selectSpace(space._id)
}}
/>
{/await}
{/each}
</TreeNode>

View File

@ -22,13 +22,14 @@
export let label: IntlString | undefined = undefined
export let notifications = 0
export let actions: Action[] = []
export let selected: boolean = false
const dispatch = createEventDispatcher()
</script>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<div
class="flex-row-center container"
class="flex-row-center container" class:selected={selected}
on:click|stopPropagation={() => {
dispatch('click')
}}
@ -88,6 +89,10 @@
line-height: 100%;
}
&.selected {
background-color: var(--theme-button-bg-enabled);
}
&:hover {
background-color: var(--theme-button-bg-enabled);
.tool {

View File

@ -16,15 +16,20 @@
import WorkbenchApp from './components/WorkbenchApp.svelte'
import ApplicationPresenter from './components/ApplicationPresenter.svelte'
import { Resources } from '@anticrm/platform'
import Archive from './components/Archive.svelte'
import { Space } from '@anticrm/core'
function hasArchiveSpaces (spaces: Space[]): boolean {
return spaces.find(sp => sp.archived) !== undefined
}
/*!
* Anticrm Platform Workbench Plugin
* © 2020 Anticrm Platform Contributors. All Rights Reserved.
* Licensed under the Eclipse Public License, Version 2.0
*/
export default async (): Promise<Resources> => ({
component: {
WorkbenchApp,
ApplicationPresenter
ApplicationPresenter,
Archive
},
function: {
HasArchiveSpaces: hasArchiveSpaces
}
})

View File

@ -1,24 +1,22 @@
//
// 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.
//
import type { Ref, Obj, Class } from '@anticrm/core'
import type { Class, Client, Obj, Ref } from '@anticrm/core'
import type { Asset } from '@anticrm/platform'
import type { Client } from '@anticrm/core'
import type { EmployeeAccount } from '@anticrm/contact'
export function classIcon(client: Client, _class: Ref<Class<Obj>>): Asset | undefined {
export function classIcon (client: Client, _class: Ref<Class<Obj>>): Asset | undefined {
return client.getHierarchy().getClass(_class).icon
}

View File

@ -14,7 +14,7 @@
//
import type { Class, Doc, Mixin, Obj, Ref, Space } from '@anticrm/core'
import type { Asset, IntlString, Metadata, Plugin } from '@anticrm/platform'
import type { Asset, IntlString, Metadata, Plugin, Resource } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import type { AnyComponent } from '@anticrm/ui'
@ -59,6 +59,7 @@ export interface SpecialNavModel {
icon: Asset
component: AnyComponent
position?: 'top'|'bottom' // undefined == 'top
visibleIf?: Resource<(spaces: Space[]) => boolean>
}
/**