Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-02-05 21:33:55 +06:00 committed by GitHub
parent 1ba9f3e89c
commit 1c3262a3a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 298 additions and 219 deletions

View File

@ -0,0 +1,67 @@
<!--
// Copyright © 2023 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 { onDestroy } from 'svelte'
import { locationToUrl, navigate, location, getCurrentLocation } from '../location'
import { Location } from '../types'
export let app: string | undefined = undefined
export let space: string | undefined = undefined
export let special: string | undefined = undefined
let loc = createLocation(getCurrentLocation(), app, space, special)
onDestroy(
location.subscribe(async (res) => {
loc = createLocation(res, app, space, special)
})
)
$: href = locationToUrl(loc)
function createLocation (
loc: Location,
app: string | undefined,
space: string | undefined,
special: string | undefined
): Location {
const location: Location = {
path: [...loc.path]
}
if (app !== undefined) {
location.path[2] = app
location.path.length = 3
}
if (space !== undefined) {
location.path[3] = space
location.path.length = 4
}
if (special !== undefined) {
location.path[4] = special
location.path.length = 5
}
return location
}
function clickHandler (e: MouseEvent) {
if (e.metaKey || e.ctrlKey) return
e.preventDefault()
navigate(loc)
}
</script>
<a {href} on:click={clickHandler}>
<slot />
</a>

View File

@ -166,6 +166,7 @@ export { NotificationPosition } from './components/notifications/NotificationPos
export { NotificationSeverity } from './components/notifications/NotificationSeverity'
export { Notification } from './components/notifications/Notification'
export { default as Wizard } from './components/wizard/Wizard.svelte'
export { default as NavLink } from './components/NavLink.svelte'
export * from './types'
export * from './location'

View File

@ -15,33 +15,28 @@
<script lang="ts">
import { Ref, Space } from '@hcengineering/core'
import { Team } from '@hcengineering/tracker'
import { NavLink } from '@hcengineering/ui'
import { SpacesNavModel } from '@hcengineering/workbench'
import { TreeNode, SpecialElement } from '@hcengineering/workbench-resources'
import { createEventDispatcher } from 'svelte'
import { SpecialElement, TreeNode } from '@hcengineering/workbench-resources'
export let space: Team
export let model: SpacesNavModel
export let currentSpace: Ref<Space> | undefined
export let currentSpecial: string | undefined
export let selectSpace: Function
export let getActions: Function
const dispatch = createEventDispatcher()
</script>
{#if model.specials}
<TreeNode icon={space?.icon ?? model.icon} title={space.name} indent={'ml-2'} actions={() => getActions(space)}>
{#each model.specials as special}
<SpecialElement
indent={'ml-4'}
label={special.label}
icon={special.icon}
on:click={() => dispatch('special', special.id)}
selected={currentSpace === space._id && special.id === currentSpecial}
on:click={() => {
selectSpace(space._id, special.id)
}}
/>
<NavLink space={space._id} special={special.id}>
<SpecialElement
indent={'ml-4'}
label={special.label}
icon={special.icon}
selected={currentSpace === space._id && special.id === currentSpecial}
/>
</NavLink>
{/each}
</TreeNode>
{/if}

View File

@ -0,0 +1,147 @@
<!--
// 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.
-->
<script lang="ts">
import type { IntlString, Asset } from '@hcengineering/platform'
import type { AnySvelteComponent } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { Icon, tooltip, IconColStar } from '@hcengineering/ui'
export let label: IntlString
export let icon: Asset | AnySvelteComponent
export let selected: boolean
export let notify: boolean = false
export let hidden: boolean = false
export let editable: boolean = false
const dispatch = createEventDispatcher()
</script>
<button class="app" class:selected class:hidden class:editable id={'app-' + label} use:tooltip={{ label }}>
<div class="flex-center icon-container" class:noty={notify}>
<Icon {icon} size={'large'} />
</div>
{#if notify}<div class="marker" />{/if}
{#if editable}
<div
class="starButton"
class:hidden
on:click|preventDefault|stopPropagation={() => {
hidden = !hidden
dispatch('visible', !hidden)
}}
>
<IconColStar
size={'small'}
fill={hidden ? 'var(--warning-color)' : 'var(--activity-status-busy)'}
border={'var(--button-border-hover)'}
/>
</div>
{/if}
</button>
<style lang="scss">
.app {
position: relative;
padding: 0;
width: 3.25rem;
height: 3.25rem;
background-color: transparent;
border: 1px solid transparent;
border-radius: 0.5rem;
cursor: pointer;
outline: none;
&.editable {
margin: 0.125rem;
}
.icon-container {
width: 3.25rem;
height: 3.25rem;
color: var(--theme-content-trans-color);
.normal-font &.noty {
clip-path: url(#notify-normal);
}
.small-font &.noty {
clip-path: url(#notify-small);
}
}
&:hover .icon-container {
color: var(--caption-color);
}
&:focus {
border: 1px solid var(--primary-button-focused-border);
box-shadow: 0 0 0 3px var(--primary-button-outline);
.icon-container {
color: var(--caption-color);
}
}
&.selected {
background-color: var(--menu-bg-select);
.icon-container {
color: var(--caption-color);
}
}
&.hidden {
border: 1px dashed var(--dark-color);
.icon-container {
color: var(--dark-color);
}
&:hover .icon-container {
color: var(--content-color);
}
}
}
.marker {
position: absolute;
top: 0.75rem;
right: 0.75rem;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
background-color: var(--highlight-red);
}
.starButton {
position: absolute;
right: 0.25rem;
bottom: 0.25rem;
height: 1rem;
width: 1rem;
transform-origin: center center;
transform: scale(1);
opacity: 0.8;
z-index: 10000;
cursor: pointer;
&:hover {
transform: scale(1.2);
opacity: 1;
}
&.hidden {
transform: scale(0.7);
opacity: 0.5;
&:hover {
transform: scale(0.9);
opacity: 0.8;
}
}
}
</style>

View File

@ -13,10 +13,9 @@
// limitations under the License.
-->
<script lang="ts">
import type { IntlString, Asset } from '@hcengineering/platform'
import type { Asset, IntlString } from '@hcengineering/platform'
import type { AnySvelteComponent } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { Icon, tooltip, IconColStar } from '@hcengineering/ui'
import { Icon, tooltip } from '@hcengineering/ui'
export let label: IntlString
export let icon: Asset | AnySvelteComponent
@ -24,18 +23,12 @@
export let selected: boolean
export let mini: boolean = false
export let notify: boolean
export let hidden: boolean = false
export let editable: boolean = false
const dispatch = createEventDispatcher()
</script>
<button
class="app"
class:selected
class:mini
class:hidden
class:editable
id={'app-' + label}
use:tooltip={{ label }}
on:click|stopPropagation={action}
@ -44,22 +37,6 @@
<Icon {icon} size={mini ? 'small' : 'large'} />
</div>
{#if notify}<div class="marker" />{/if}
{#if editable}
<div
class="starButton"
class:hidden
on:click|preventDefault|stopPropagation={() => {
hidden = !hidden
dispatch('visible', !hidden)
}}
>
<IconColStar
size={'small'}
fill={hidden ? 'var(--warning-color)' : 'var(--activity-status-busy)'}
border={'var(--button-border-hover)'}
/>
</div>
{/if}
</button>
<style lang="scss">
@ -74,9 +51,6 @@
cursor: pointer;
outline: none;
&.editable {
margin: 0.125rem;
}
&.mini,
.icon-container.mini {
width: calc(var(--status-bar-height) - 8px);
@ -117,16 +91,6 @@
color: var(--caption-color);
}
}
&.hidden {
border: 1px dashed var(--dark-color);
.icon-container {
color: var(--dark-color);
}
&:hover .icon-container {
color: var(--content-color);
}
}
}
.marker {
@ -138,31 +102,4 @@
border-radius: 50%;
background-color: var(--highlight-red);
}
.starButton {
position: absolute;
right: 0.25rem;
bottom: 0.25rem;
height: 1rem;
width: 1rem;
transform-origin: center center;
transform: scale(1);
opacity: 0.8;
z-index: 10000;
cursor: pointer;
&:hover {
transform: scale(1.2);
opacity: 1;
}
&.hidden {
transform: scale(0.7);
opacity: 0.5;
&:hover {
transform: scale(0.9);
opacity: 0.8;
}
}
}
</style>

View File

@ -14,20 +14,17 @@
-->
<script lang="ts">
import type { Ref } from '@hcengineering/core'
import type { Application } from '@hcengineering/workbench'
import { createEventDispatcher } from 'svelte'
import AppItem from './AppItem.svelte'
import { Scroller, IconDownOutline } from '@hcengineering/ui'
import { showApplication, hideApplication } from '../utils'
import { createQuery } from '@hcengineering/presentation'
import { IconDownOutline, NavLink, Scroller } from '@hcengineering/ui'
import type { Application } from '@hcengineering/workbench'
import workbench from '@hcengineering/workbench'
import { hideApplication, showApplication } from '../utils'
import App from './App.svelte'
export let active: Ref<Application> | undefined
export let apps: Application[] = []
export let direction: 'vertical' | 'horizontal' = 'vertical'
const dispatch = createEventDispatcher()
let loaded: boolean = false
let hiddenAppsIds: Ref<Application>[] = []
const hiddenAppsIdsQuery = createQuery()
@ -49,22 +46,20 @@
>
<div class="apps-space-{direction}" />
{#each apps.filter((it) => (shown ? true : !hiddenAppsIds.includes(it._id))) as app}
<AppItem
selected={app._id === active}
icon={app.icon}
label={app.label}
hidden={hiddenAppsIds.includes(app._id)}
editable={shown}
action={async () => {
dispatch('active', app)
}}
notify={false}
on:visible={(res) => {
if (res.detail === undefined) return
if (res.detail) showApplication(app)
else hideApplication(app)
}}
/>
<NavLink app={app.alias}>
<App
selected={app._id === active}
icon={app.icon}
label={app.label}
hidden={hiddenAppsIds.includes(app._id)}
editable={shown}
on:visible={(res) => {
if (res.detail === undefined) return
if (res.detail) showApplication(app)
else hideApplication(app)
}}
/>
</NavLink>
{/each}
<div class="apps-space-{direction}" />
</Scroller>

View File

@ -18,9 +18,8 @@
import preference, { SpacePreference } from '@hcengineering/preference'
import { createQuery, getClient } from '@hcengineering/presentation'
import setting from '@hcengineering/setting'
import { Button, Scroller, showPopup } from '@hcengineering/ui'
import { Button, NavLink, Scroller, showPopup } from '@hcengineering/ui'
import type { Application, NavigatorModel, SpecialNavModel } from '@hcengineering/workbench'
import { createEventDispatcher } from 'svelte'
import workbench from '../plugin'
import { getSpecialSpaceClass } from '../utils'
import HelpAndSupport from './HelpAndSupport.svelte'
@ -124,7 +123,6 @@
await Promise.all(promises)
return [result, requestIndex]
}
const dispatch = createEventDispatcher()
</script>
{#if model}
@ -134,13 +132,14 @@
{#if row > 0 && specials[row].position !== specials[row - 1].position}
<TreeSeparator />
{/if}
<SpecialElement
label={special.label}
icon={special.icon}
on:click={() => dispatch('special', special.id)}
selected={special.id === currentSpecial}
indent={'ml-2'}
/>
<NavLink space={special.id}>
<SpecialElement
label={special.label}
icon={special.icon}
selected={special.id === currentSpecial}
indent={'ml-2'}
/>
</NavLink>
{/each}
{/if}
@ -156,7 +155,6 @@
{currentSpace}
hasSpaceBrowser={model.specials?.find((p) => p.id === 'spaceBrowser') !== undefined}
model={m}
on:space
on:open
{currentSpecial}
/>

View File

@ -16,11 +16,12 @@
import calendar from '@hcengineering/calendar'
import contact, { Employee, EmployeeAccount } from '@hcengineering/contact'
import core, { Class, Client, Doc, getCurrentAccount, Ref, setCurrentAccount, Space } from '@hcengineering/core'
import { getWorkspaces, Workspace } from '@hcengineering/login-resources'
import notification, { NotificationStatus } from '@hcengineering/notification'
import request, { RequestStatus } from '@hcengineering/request'
import { BrowserNotificatator, NotificationClientImpl } from '@hcengineering/notification-resources'
import { getMetadata, getResource, IntlString } from '@hcengineering/platform'
import { Avatar, createQuery, setClient } from '@hcengineering/presentation'
import request, { RequestStatus } from '@hcengineering/request'
import {
AnyComponent,
areLocationsEqual,
@ -47,7 +48,7 @@
import { getContext, onDestroy, onMount, tick } from 'svelte'
import { subscribeMobile } from '../mobile'
import workbench from '../plugin'
import { doNavigate, workspacesStore } from '../utils'
import { workspacesStore } from '../utils'
import AccountPopup from './AccountPopup.svelte'
import AppItem from './AppItem.svelte'
import Applications from './Applications.svelte'
@ -55,7 +56,6 @@
import NavHeader from './NavHeader.svelte'
import Navigator from './Navigator.svelte'
import SpaceView from './SpaceView.svelte'
import { getWorkspaces, Workspace } from '@hcengineering/login-resources'
export let client: Client
let contentPanel: HTMLElement
@ -168,6 +168,7 @@
await syncLoc(loc)
await updateWindowTitle(loc)
checkOnHide()
})
)
@ -274,38 +275,6 @@
}
}
function navigateApp (app: Application): void {
if (currentAppAlias === app.alias) {
// Nothing to do.
return
}
visibileNav = true
doNavigate([], undefined, {
mode: 'app',
application: app.alias
})
}
function selectSpecial (id: string): void {
if (currentSpecial === id) return
doNavigate([], undefined, {
mode: 'special',
special: id
})
checkOnHide()
}
function selectSpace (spaceId?: Ref<Space>, spaceSpecial?: string): void {
doNavigate([], undefined, {
mode: 'space',
space: spaceId,
spaceSpecial
})
checkOnHide()
}
function closeAside (): void {
const loc = getCurrentLocation()
loc.path.length = 4
@ -459,14 +428,7 @@
notify={false}
/>
</div>
<Applications
{apps}
active={currentApplication?._id}
direction={appsDirection}
on:active={(evt) => {
navigateApp(evt.detail)
}}
/>
<Applications {apps} active={currentApplication?._id} />
<div class="info-box {appsDirection}" class:vertical-mobile={appsDirection === 'vertical' && appsMini}>
<AppItem
icon={request.icon.Requests}
@ -533,8 +495,6 @@
{currentSpecial}
model={navigatorModel}
{currentApplication}
on:special={(evt) => selectSpecial(evt.detail)}
on:space={(evt) => selectSpace(evt.detail.space, evt.detail.spaceSpecial)}
on:open={checkOnHide}
/>
{#if currentApplication.navFooterComponent}

View File

@ -20,7 +20,16 @@
import { getResource } from '@hcengineering/platform'
import preference from '@hcengineering/preference'
import { getClient } from '@hcengineering/presentation'
import { Action, getCurrentLocation, IconAdd, IconEdit, IconSearch, navigate, showPopup } from '@hcengineering/ui'
import {
Action,
getCurrentLocation,
IconAdd,
IconEdit,
IconSearch,
navigate,
NavLink,
showPopup
} from '@hcengineering/ui'
import { getActions as getContributedActions, getObjectPresenter } from '@hcengineering/view-resources'
import { SpacesNavModel } from '@hcengineering/workbench'
import { createEventDispatcher } from 'svelte'
@ -67,10 +76,6 @@
}
}
function selectSpace (id: Ref<Space>, spaceSpecial?: string) {
dispatch('space', { space: id, spaceSpecial })
}
async function getActions (space: Space): Promise<Action[]> {
const result = [starSpace]
@ -114,30 +119,21 @@
{#each spaces as space (space._id)}
{#await getObjectPresenter(client, space._class, { key: '' }) then presenter}
{#if model.specials && presenter}
<svelte:component
this={presenter.presenter}
{space}
{model}
{currentSpace}
{currentSpecial}
{getActions}
{selectSpace}
/>
<svelte:component this={presenter.presenter} {space} {model} {currentSpace} {currentSpecial} {getActions} />
{:else}
{#await getSpaceName(client, space) then name}
<TreeItem
indent={'ml-4'}
_id={space._id}
title={name}
icon={classIcon(client, space._class)}
selected={currentSpace === space._id}
actions={() => getActions(space)}
bold={isChanged(space, $lastViews)}
on:click={() => {
selectSpace(space._id)
}}
/>
{/await}
<NavLink space={space._id}>
{#await getSpaceName(client, space) then name}
<TreeItem
indent={'ml-4'}
_id={space._id}
title={name}
icon={classIcon(client, space._class)}
selected={currentSpace === space._id}
actions={() => getActions(space)}
bold={isChanged(space, $lastViews)}
/>
{/await}
</NavLink>
{/if}
{/await}
{/each}

View File

@ -16,7 +16,6 @@
import type { Asset, IntlString } from '@hcengineering/platform'
import type { Action } from '@hcengineering/ui'
import { ActionIcon, Icon, Label } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
export let icon: Asset | undefined = undefined
export let label: IntlString | undefined = undefined
@ -24,8 +23,6 @@
export let actions: Action[] = []
export let selected: boolean = false
export let indent: 'default' | 'ml-2' | 'ml-4' | 'ml-8' = 'default'
const dispatch = createEventDispatcher()
</script>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
@ -36,9 +33,6 @@
class:ml-2={indent === 'ml-2'}
class:ml-4={indent === 'ml-4'}
class:ml-8={indent === 'ml-8'}
on:click|stopPropagation={() => {
dispatch('click')
}}
>
<div class="an-element__icon">
{#if icon}

View File

@ -20,10 +20,9 @@
import { getResource, IntlString } from '@hcengineering/platform'
import preference from '@hcengineering/preference'
import { getClient } from '@hcengineering/presentation'
import { Action, IconEdit } from '@hcengineering/ui'
import { Action, IconEdit, NavLink } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { getActions as getContributedActions } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import { classIcon, getSpaceName } from '../../utils'
import TreeItem from './TreeItem.svelte'
import TreeNode from './TreeNode.svelte'
@ -32,7 +31,6 @@
export let currentSpace: Ref<Space> | undefined
export let spaces: Space[]
const client = getClient()
const dispatch = createEventDispatcher()
const unStarSpace: Action = {
label: preference.string.Unstar,
@ -60,10 +58,6 @@
}
}
function selectSpace (id: Ref<Space>, spaceSpecial?: string) {
dispatch('space', { space: id, spaceSpecial })
}
async function getActions (space: Space): Promise<Action[]> {
const result = [unStarSpace]
@ -102,18 +96,17 @@
<TreeNode {label} parent actions={async () => [unStarAll]} indent={'ml-2'}>
{#each spaces as space (space._id)}
{#await getSpaceName(client, space) then name}
<TreeItem
indent={'ml-4'}
_id={space._id}
title={name}
icon={classIcon(client, space._class)}
selected={currentSpace === space._id}
actions={() => getActions(space)}
bold={isChanged(space, $lastViews)}
on:click={() => {
selectSpace(space._id)
}}
/>
<NavLink space={space._id}>
<TreeItem
indent={'ml-4'}
_id={space._id}
title={name}
icon={classIcon(client, space._class)}
selected={currentSpace === space._id}
actions={() => getActions(space)}
bold={isChanged(space, $lastViews)}
/>
</NavLink>
{/await}
{/each}
</TreeNode>

View File

@ -17,7 +17,6 @@
import type { Asset, IntlString } from '@hcengineering/platform'
import type { Action } from '@hcengineering/ui'
import { ActionIcon, Icon, IconMoreV, Label, Menu, showPopup } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
export let _id: Ref<Space> | undefined = undefined
export let icon: Asset | undefined = undefined
@ -32,8 +31,6 @@
export let actions: () => Promise<Action[]> = async () => []
export let indent: 'default' | 'ml-2' | 'ml-4' | 'ml-8' = 'default'
const dispatch = createEventDispatcher()
let hovered = false
async function onMenuClick (ev: MouseEvent) {
showPopup(Menu, { actions: await actions(), ctx: _id }, ev.target as HTMLElement, () => {
@ -53,9 +50,8 @@
class:parent
class:collapsed
class:child={!node}
on:click|stopPropagation={() => {
on:click={() => {
collapsed = !collapsed
dispatch('click')
}}
>
<span class="an-element__label" class:bold class:title={node}>
@ -79,7 +75,7 @@
</div>
</span>
{#if node === false}
<div class="an-element__tool" on:click|stopPropagation={onMenuClick}>
<div class="an-element__tool" on:click|preventDefault|stopPropagation={onMenuClick}>
<IconMoreV size={'small'} />
</div>
{:else}
@ -96,7 +92,7 @@
/>
</div>
{:else if actionItems.length > 1}
<div class="an-element__tool" on:click|stopPropagation={onMenuClick}>
<div class="an-element__tool" on:click|preventDefault|stopPropagation={onMenuClick}>
<IconMoreV size={'small'} />
</div>
{/if}