Applications list updated layout (#2343)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2022-11-01 18:48:20 +03:00 committed by GitHub
parent 47b28e9dfc
commit 4c1e3b75c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 262 additions and 25 deletions

View File

@ -33,6 +33,7 @@
"@hcengineering/ui": "^0.6.2",
"@hcengineering/view": "^0.6.1",
"@hcengineering/model-view": "~0.6.0",
"@hcengineering/workbench-resources": "~0.6.1"
"@hcengineering/workbench-resources": "~0.6.1",
"@hcengineering/model-preference": "~0.6.0"
}
}

View File

@ -15,10 +15,11 @@
import type { IntlString, Asset } from '@hcengineering/platform'
import { Class, DOMAIN_MODEL, Ref, Space } from '@hcengineering/core'
import { Model, Mixin, Builder, UX } from '@hcengineering/model'
import type { Application, SpaceView, ViewConfiguration } from '@hcengineering/workbench'
import { Model, Mixin, Builder, UX, Prop, TypeRef } from '@hcengineering/model'
import type { Application, SpaceView, ViewConfiguration, HiddenApplication } from '@hcengineering/workbench'
import view, { KeyBinding } from '@hcengineering/view'
import { createAction } from '@hcengineering/model-view'
import preference, { TPreference } from '@hcengineering/model-preference'
import core, { TDoc, TClass } from '@hcengineering/model-core'
import workbench from './plugin'
@ -34,13 +35,19 @@ export class TApplication extends TDoc implements Application {
hidden!: boolean
}
@Model(workbench.class.HiddenApplication, preference.class.Preference)
export class THiddenApplication extends TPreference implements HiddenApplication {
@Prop(TypeRef(workbench.class.Application), workbench.string.HiddenApplication)
attachedTo!: Ref<Application>
}
@Mixin(workbench.mixin.SpaceView, core.class.Class)
export class TSpaceView extends TClass implements SpaceView {
view!: ViewConfiguration
}
export function createModel (builder: Builder): void {
builder.createModel(TApplication, TSpaceView)
builder.createModel(TApplication, TSpaceView, THiddenApplication)
builder.mixin(workbench.class.Application, core.class.Class, view.mixin.AttributePresenter, {
presenter: workbench.component.ApplicationPresenter
})

View File

@ -28,7 +28,8 @@ export default mergeIds(workbenchId, workbench, {
},
string: {
Application: '' as IntlString,
SpaceBrowser: '' as IntlString
SpaceBrowser: '' as IntlString,
HiddenApplication: '' as IntlString
},
function: {
HasArchiveSpaces: '' as Resource<(spaces: Space[]) => boolean>

View File

@ -232,13 +232,9 @@
}
}
&.asideShown {
border: none;
.popupPanel-body__main {
border: 1px solid var(--divider-color);
border-radius: 0 0 .5rem .5rem;
}
&.asideShown .popupPanel-body__main {
border: 1px solid var(--divider-color);
border-radius: 0 0 .5rem .5rem;
}
}
}

View File

@ -254,6 +254,7 @@
<div
bind:this={divBox}
class="box"
class:align-center={contentDirection === 'horizontal'}
style:padding
style:flex-direction={contentDirection === 'vertical'
? 'column'

View File

@ -0,0 +1,31 @@
<!--
// 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">
export let size: 'small' | 'medium' | 'large'
export let fill: string = 'transparent'
export let border: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path
{fill}
d="M8.7,2.5L10,5.1c0.1,0.3,0.3,0.4,0.6,0.5l2.7,0.4c0.7,0.1,0.9,1,0.5,1.5l-2,2c-0.2,0.2-0.3,0.5-0.2,0.8L12,13c0.1,0.7-0.6,1.2-1.2,0.9l-2.4-1.3c-0.2-0.1-0.5-0.1-0.8,0l-2.4,1.3C4.6,14.2,3.9,13.7,4,13l0.5-2.8c0-0.3,0-0.6-0.2-0.8l-2-2C1.8,6.9,2,6,2.7,5.9l2.7-0.4c0.3,0,0.5-0.2,0.6-0.5l1.2-2.6C7.6,1.8,8.4,1.8,8.7,2.5z"
/>
<path
fill={border}
d="M4.8,14.5c-0.3,0-0.5-0.1-0.7-0.2c-0.4-0.3-0.6-0.8-0.6-1.3L4,10.1c0-0.1,0-0.2-0.1-0.3l-2-2C1.5,7.4,1.4,6.8,1.6,6.3c0.2-0.5,0.6-0.8,1.1-0.9L5.4,5c0.1,0,0.2-0.1,0.2-0.2l1.2-2.6C7,1.8,7.5,1.5,8,1.5s1,0.3,1.2,0.8c0,0,0,0,0,0l1.2,2.6c0,0.1,0.1,0.2,0.2,0.2l2.7,0.4c0.5,0.1,0.9,0.4,1.1,0.9c0.2,0.5,0,1-0.3,1.4l-2,2C12.1,9.8,12,10,12,10.1l0.5,2.8c0.1,0.5-0.1,1-0.6,1.3c-0.4,0.3-0.9,0.3-1.4,0.1L8.1,13c-0.1,0-0.2,0-0.3,0l-2.4,1.3C5.2,14.4,5,14.5,4.8,14.5z M8,2.5c-0.1,0-0.2,0-0.3,0.2L6.5,5.3C6.3,5.7,5.9,6,5.5,6L2.8,6.4c-0.2,0-0.2,0.2-0.3,0.2c0,0.1-0.1,0.2,0.1,0.4l2,2C4.9,9.4,5,9.8,4.9,10.2l-0.5,2.8c0,0.2,0.1,0.3,0.1,0.4c0.1,0,0.2,0.1,0.3,0l2.4-1.3c0.4-0.2,0.9-0.2,1.2,0l2.4,1.3c0.1,0.1,0.3,0,0.3,0c0,0,0.2-0.1,0.1-0.4l-0.5-2.8c-0.1-0.4,0.1-0.9,0.4-1.2l2-2c0.1-0.2,0.1-0.3,0.1-0.4c0-0.1-0.1-0.2-0.3-0.2L10.5,6c-0.4-0.1-0.8-0.3-1-0.7L8.3,2.7C8.2,2.5,8.1,2.5,8,2.5z"
/>
</svg>

View File

@ -136,6 +136,7 @@ export { default as IconScale } from './components/icons/Scale.svelte'
export { default as IconScaleFull } from './components/icons/ScaleFull.svelte'
export { default as IconOpen } from './components/icons/Open.svelte'
export { default as IconCheckCircle } from './components/icons/CheckCircle.svelte'
export { default as IconColStar } from './components/icons/ColStar.svelte'
export { default as PanelInstance } from './components/PanelInstance.svelte'
export { default as Panel } from './components/Panel.svelte'

View File

@ -24,6 +24,7 @@
"ContactUs": "Contact us",
"OpenPlatformGuide": "Open Platform Guide",
"AccessWorkspaceSettings": "Access your workspace settings",
"HowToWorkFaster": "Learn how to work faster"
"HowToWorkFaster": "Learn how to work faster",
"HiddenApplication": "Hidden application"
}
}

View File

@ -24,6 +24,7 @@
"ContactUs": "Связаться с нами",
"OpenPlatformGuide": "Открыть руководство пользователя",
"AccessWorkspaceSettings": "Открыть настройки рабочего пространства",
"HowToWorkFaster": "Узнайте как работать эффективнее"
"HowToWorkFaster": "Узнайте как работать эффективнее",
"HiddenApplication": "Скрытое приложение"
}
}

View File

@ -15,7 +15,8 @@
<script lang="ts">
import type { IntlString, Asset } from '@hcengineering/platform'
import type { AnySvelteComponent } from '@hcengineering/ui'
import { Icon, tooltip } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { Icon, tooltip, IconColStar } from '@hcengineering/ui'
export let label: IntlString
export let icon: Asset | AnySvelteComponent
@ -23,12 +24,18 @@
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}
@ -37,6 +44,22 @@
<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">
@ -51,6 +74,9 @@
cursor: pointer;
outline: none;
&.editable {
margin: 0.125rem;
}
&.mini,
.icon-container.mini {
width: calc(var(--status-bar-height) - 8px);
@ -75,20 +101,30 @@
}
&:hover .icon-container {
color: var(--theme-caption-color);
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(--theme-caption-color);
color: var(--caption-color);
}
}
&.selected {
background-color: var(--menu-bg-select);
.icon-container {
color: var(--theme-caption-color);
color: var(--caption-color);
}
}
&.hidden {
border: 1px dashed var(--dark-color);
.icon-container {
color: var(--dark-color);
}
&:hover .icon-container {
color: var(--content-color);
}
}
}
@ -102,4 +138,31 @@
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

@ -17,38 +17,143 @@
import type { Application } from '@hcengineering/workbench'
import { createEventDispatcher } from 'svelte'
import AppItem from './AppItem.svelte'
import { Scroller } from '@hcengineering/ui'
import { Scroller, IconDownOutline } from '@hcengineering/ui'
import { showApplication, hideApplication } from '../utils'
import { createQuery } from '@hcengineering/presentation'
import workbench from '@hcengineering/workbench'
export let active: Ref<Application> | undefined
export let apps: Application[] = []
export let direction: 'vertical' | 'horizontal' = 'vertical'
const dispatch = createEventDispatcher()
let hiddenAppsIds: Ref<Application>[] = []
const hiddenAppsIdsQuery = createQuery()
hiddenAppsIdsQuery.query(workbench.class.HiddenApplication, {}, (res) => {
hiddenAppsIds = res.map((r) => r.attachedTo)
})
let shown: boolean = false
</script>
<div class="flex align-center clear-mins apps-{direction}">
<Scroller invertScroll padding={'.5rem .5rem'} horizontal={direction === 'horizontal'} contentDirection={direction}>
{#each apps as app}
<div class="flex-row-center clear-mins apps-{direction} relative">
<Scroller
invertScroll
padding={direction === 'horizontal' ? '.25rem 0' : '0 .5rem'}
horizontal={direction === 'horizontal'}
contentDirection={direction}
>
<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)
}}
/>
{/each}
<div class="apps-space-{direction}" />
</Scroller>
<div class="thinButton {direction}" class:shown on:click={() => (shown = !shown)}>
<div class="clear-mins pointer-events-none" class:rotate90={direction === 'horizontal'}>
<IconDownOutline size={'medium'} />
</div>
</div>
</div>
<style lang="scss">
.apps-horizontal {
justify-content: center;
margin: 0 1rem;
padding: 0 0.25rem;
min-height: 4rem;
}
.apps-vertical {
margin: 1rem 0;
padding: 0.25rem 0;
min-width: 4rem;
}
.apps-space {
&-vertical {
min-height: 0.25rem;
height: 0.25rem;
}
&-horizontal {
min-width: 0.25rem;
width: 0.25rem;
}
}
.thinButton {
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transform-origin: center center;
border-radius: 0.5rem;
opacity: 0.2;
cursor: pointer;
transition-property: opacity, transform;
transition-timing-function: var(--timing-main);
transition-duration: 0.1s;
&.vertical {
top: 100%;
left: 50%;
width: 2.5rem;
transform: translateX(-50%) scale(0.6) rotate(0deg);
&:hover {
transform: translateX(-50%) scale(0.8);
}
}
&.horizontal {
left: 100%;
top: 50%;
height: 2.5rem;
transform: translateY(-50%) scale(0.6);
&:hover {
transform: translateY(-50%) scale(0.8);
}
&.shown {
transform: translateY(-50%) scale(0.9) rotate(180deg);
&:hover {
transform: translateY(-50%) scale(1) rotate(180deg);
}
}
}
&:hover {
transform: translateX(-50%) scale(0.8);
background-color: var(--accent-bg-color);
opacity: 0.9;
}
&.shown {
transform: translateX(-50%) scale(0.9) rotate(180deg);
opacity: 0.8;
&:hover {
transform: translateX(-50%) scale(1) rotate(180deg);
background-color: var(--accent-bg-color);
opacity: 1;
}
}
}
.rotate90 {
transform-origin: center center;
transform: rotate(-90deg);
}
</style>

View File

@ -18,10 +18,12 @@ import type { Class, Client, Doc, Obj, Ref, Space } from '@hcengineering/core'
import core from '@hcengineering/core'
import type { Asset } from '@hcengineering/platform'
import { getResource } from '@hcengineering/platform'
import { NavigatorModel } from '@hcengineering/workbench'
import workbench, { NavigatorModel } from '@hcengineering/workbench'
import view from '@hcengineering/view'
import { closePanel, getCurrentLocation, navigate } from '@hcengineering/ui'
import { getClient } from '@hcengineering/presentation'
import type { Application } from '@hcengineering/workbench'
import preference from '@hcengineering/preference'
export function classIcon (client: Client, _class: Ref<Class<Obj>>): Asset | undefined {
return client.getHierarchy().getClass(_class).icon
@ -116,3 +118,20 @@ export async function doNavigate (
}
}
}
export async function hideApplication (app: Application): Promise<void> {
const client = getClient()
await client.createDoc(workbench.class.HiddenApplication, preference.space.Preference, {
attachedTo: app._id
})
}
export async function showApplication (app: Application): Promise<void> {
const client = getClient()
const current = await client.findOne(workbench.class.HiddenApplication, { attachedTo: app._id })
if (current !== undefined) {
await client.remove(current)
}
}

View File

@ -29,6 +29,7 @@
"@hcengineering/core": "^0.6.17",
"@hcengineering/platform": "^0.6.7",
"@hcengineering/ui": "^0.6.2",
"@hcengineering/view": "^0.6.1"
"@hcengineering/view": "^0.6.1",
"@hcengineering/preference": "^0.6.1"
}
}

View File

@ -18,6 +18,7 @@ import type { Asset, IntlString, Metadata, Plugin, Resource } from '@hcengineeri
import { plugin } from '@hcengineering/platform'
import { AnyComponent, Location } from '@hcengineering/ui'
import { ViewAction } from '@hcengineering/view'
import type { Preference } from '@hcengineering/preference'
/**
* @public
@ -37,6 +38,13 @@ export interface Application extends Doc {
navFooterComponent?: AnyComponent
}
/**
* @public
*/
export interface HiddenApplication extends Preference {
attachedTo: Ref<Application>
}
/**
* @public
*/
@ -103,7 +111,8 @@ export const workbenchId = 'workbench' as Plugin
export default plugin(workbenchId, {
class: {
Application: '' as Ref<Class<Application>>
Application: '' as Ref<Class<Application>>,
HiddenApplication: '' as Ref<Class<HiddenApplication>>
},
mixin: {
SpaceView: '' as Ref<Mixin<SpaceView>>