mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 21:50:34 +03:00
Applications list updated layout (#2343)
Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
parent
47b28e9dfc
commit
4c1e3b75c9
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -254,6 +254,7 @@
|
||||
<div
|
||||
bind:this={divBox}
|
||||
class="box"
|
||||
class:align-center={contentDirection === 'horizontal'}
|
||||
style:padding
|
||||
style:flex-direction={contentDirection === 'vertical'
|
||||
? 'column'
|
||||
|
31
packages/ui/src/components/icons/ColStar.svelte
Normal file
31
packages/ui/src/components/icons/ColStar.svelte
Normal 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>
|
@ -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'
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@
|
||||
"ContactUs": "Связаться с нами",
|
||||
"OpenPlatformGuide": "Открыть руководство пользователя",
|
||||
"AccessWorkspaceSettings": "Открыть настройки рабочего пространства",
|
||||
"HowToWorkFaster": "Узнайте как работать эффективнее"
|
||||
"HowToWorkFaster": "Узнайте как работать эффективнее",
|
||||
"HiddenApplication": "Скрытое приложение"
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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>>
|
||||
|
Loading…
Reference in New Issue
Block a user