mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 11:31:57 +03:00
UBER-338: added AppSwitcher popup. (#3329)
Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
parent
876d52c08d
commit
d7400dc03a
@ -648,6 +648,7 @@ input.search {
|
|||||||
.min-w-4 { min-width: 1rem; }
|
.min-w-4 { min-width: 1rem; }
|
||||||
.min-w-8 { min-width: 2rem; }
|
.min-w-8 { min-width: 2rem; }
|
||||||
.min-w-9 { min-width: 2.25rem; }
|
.min-w-9 { min-width: 2.25rem; }
|
||||||
|
.min-w-60 { min-width: 15rem; }
|
||||||
.min-w-80 { min-width: 20rem; }
|
.min-w-80 { min-width: 20rem; }
|
||||||
.min-w-100 { min-width: 25rem; }
|
.min-w-100 { min-width: 25rem; }
|
||||||
.min-w-168 { min-width: 42rem; }
|
.min-w-168 { min-width: 42rem; }
|
||||||
|
@ -15,49 +15,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { IntlString, Asset } from '@hcengineering/platform'
|
import type { IntlString, Asset } from '@hcengineering/platform'
|
||||||
import type { AnySvelteComponent } from '@hcengineering/ui'
|
import type { AnySvelteComponent } from '@hcengineering/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import { Icon, tooltip } from '@hcengineering/ui'
|
import { Icon, tooltip } from '@hcengineering/ui'
|
||||||
import PreviewOn from './icons/PreviewOn.svelte'
|
|
||||||
import PreviewOff from './icons/PreviewOff.svelte'
|
|
||||||
|
|
||||||
export let label: IntlString
|
export let label: IntlString
|
||||||
export let icon: Asset | AnySvelteComponent
|
export let icon: Asset | AnySvelteComponent
|
||||||
export let selected: boolean
|
export let selected: boolean
|
||||||
export let notify: boolean = false
|
export let notify: boolean = false
|
||||||
export let hidden: boolean = false
|
|
||||||
export let editable: 'vertical' | 'horizontal' | false = false
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button class="app" class:selected id={'app-' + label} use:tooltip={{ label }}>
|
||||||
class="app{editable ? ' ' + editable : ''}"
|
|
||||||
class:selected
|
|
||||||
class:hidden
|
|
||||||
id={'app-' + label}
|
|
||||||
use:tooltip={{ label }}
|
|
||||||
>
|
|
||||||
<div class="flex-center icon-container" class:noty={notify}>
|
<div class="flex-center icon-container" class:noty={notify}>
|
||||||
<Icon {icon} size={'medium'} />
|
<Icon {icon} size={'medium'} />
|
||||||
</div>
|
</div>
|
||||||
{#if notify}<div class="marker" />{/if}
|
{#if notify}<div class="marker" />{/if}
|
||||||
{#if editable}
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<div
|
|
||||||
class="starButton"
|
|
||||||
class:hidden
|
|
||||||
on:click|preventDefault|stopPropagation={() => {
|
|
||||||
hidden = !hidden
|
|
||||||
dispatch('visible', !hidden)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{#if hidden}
|
|
||||||
<PreviewOff size={'small'} />
|
|
||||||
{:else}
|
|
||||||
<PreviewOn size={'small'} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -101,16 +71,6 @@
|
|||||||
color: var(--theme-caption-color);
|
color: var(--theme-caption-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.hidden {
|
|
||||||
border: 1px dashed var(--theme-dark-color);
|
|
||||||
.icon-container {
|
|
||||||
color: var(--theme-dark-color);
|
|
||||||
}
|
|
||||||
&:hover .icon-container {
|
|
||||||
color: var(--theme-content-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.marker {
|
.marker {
|
||||||
@ -122,34 +82,4 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: var(--highlight-red);
|
background-color: var(--highlight-red);
|
||||||
}
|
}
|
||||||
|
|
||||||
.starButton {
|
|
||||||
position: absolute;
|
|
||||||
right: 0.25rem;
|
|
||||||
bottom: 0.25rem;
|
|
||||||
height: 1rem;
|
|
||||||
width: 1rem;
|
|
||||||
color: var(--activity-status-busy);
|
|
||||||
transform-origin: center center;
|
|
||||||
transform: scale(1);
|
|
||||||
opacity: 0.8;
|
|
||||||
z-index: 10000;
|
|
||||||
filter: drop-shadow(0 0 1px #000);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.2);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
&.hidden {
|
|
||||||
color: var(--theme-warning-color);
|
|
||||||
transform: scale(0.7);
|
|
||||||
opacity: 0.5;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(0.9);
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2022 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 type { Ref } from '@hcengineering/core'
|
||||||
|
import type { Application } from '@hcengineering/workbench'
|
||||||
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
|
import workbench from '@hcengineering/workbench'
|
||||||
|
import { hideApplication, showApplication } from '../utils'
|
||||||
|
import { Loading, IconCheck, Label, Icon } from '@hcengineering/ui'
|
||||||
|
// import Drag from './icons/Drag.svelte'
|
||||||
|
|
||||||
|
export let apps: Application[] = []
|
||||||
|
|
||||||
|
let activeElement: HTMLElement
|
||||||
|
const btns: HTMLElement[] = []
|
||||||
|
|
||||||
|
function focusTarget (target: HTMLElement): void {
|
||||||
|
activeElement = target
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyDown = (ev: KeyboardEvent): void => {
|
||||||
|
if (ev.key === 'Tab') {
|
||||||
|
ev.preventDefault()
|
||||||
|
ev.stopPropagation()
|
||||||
|
}
|
||||||
|
const n = btns.indexOf(activeElement) ?? 0
|
||||||
|
if (ev.key === 'ArrowDown') {
|
||||||
|
if (n < btns.length - 1) {
|
||||||
|
activeElement = btns[n + 1]
|
||||||
|
}
|
||||||
|
ev.preventDefault()
|
||||||
|
ev.stopPropagation()
|
||||||
|
}
|
||||||
|
if (ev.key === 'ArrowUp') {
|
||||||
|
if (n > 0) {
|
||||||
|
activeElement = btns[n - 1]
|
||||||
|
}
|
||||||
|
ev.preventDefault()
|
||||||
|
ev.stopPropagation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let loaded: boolean = false
|
||||||
|
let hiddenAppsIds: Ref<Application>[] = []
|
||||||
|
const hiddenAppsIdsQuery = createQuery()
|
||||||
|
hiddenAppsIdsQuery.query(workbench.class.HiddenApplication, {}, (res) => {
|
||||||
|
hiddenAppsIds = res.map((r) => r.attachedTo)
|
||||||
|
loaded = true
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="antiPopup min-w-60" on:keydown={keyDown}>
|
||||||
|
<div class="ap-space x2" />
|
||||||
|
<div class="ap-scroll">
|
||||||
|
<div class="ap-box">
|
||||||
|
{#if loaded}
|
||||||
|
{#each apps as app, i}
|
||||||
|
<button
|
||||||
|
bind:this={btns[i]}
|
||||||
|
class="ap-menuItem withIcon flex-row-center flex-grow"
|
||||||
|
class:hover={btns[i] === activeElement}
|
||||||
|
on:click={() => {
|
||||||
|
if (hiddenAppsIds.includes(app._id)) showApplication(app)
|
||||||
|
else hideApplication(app)
|
||||||
|
}}
|
||||||
|
on:mousemove={() => {
|
||||||
|
focusTarget(btns[i])
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="icon mr-2"><Icon icon={app.icon} size={'small'} /></div>
|
||||||
|
<span class="label overflow-label flex-grow"><Label label={app.label} /></span>
|
||||||
|
<div class="ap-check">
|
||||||
|
{#if !hiddenAppsIds.includes(app._id)}
|
||||||
|
<IconCheck size={'small'} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<div class="ap-menuItem empty">
|
||||||
|
<Loading />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ap-space x2" />
|
||||||
|
</div>
|
@ -14,24 +14,24 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Ref } from '@hcengineering/core'
|
import type { Ref } from '@hcengineering/core'
|
||||||
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import { Scroller } from '@hcengineering/ui'
|
import { Scroller } from '@hcengineering/ui'
|
||||||
import { NavLink } from '@hcengineering/view-resources'
|
import { NavLink } from '@hcengineering/view-resources'
|
||||||
import type { Application } from '@hcengineering/workbench'
|
import type { Application } from '@hcengineering/workbench'
|
||||||
import { hideApplication, showApplication } from '../utils'
|
import workbench from '@hcengineering/workbench'
|
||||||
import App from './App.svelte'
|
import App from './App.svelte'
|
||||||
|
|
||||||
export let active: Ref<Application> | undefined
|
export let active: Ref<Application> | undefined
|
||||||
export let apps: Application[] = []
|
export let apps: Application[] = []
|
||||||
export let direction: 'vertical' | 'horizontal' = 'vertical'
|
export let direction: 'vertical' | 'horizontal' = 'vertical'
|
||||||
export let shown: boolean = false
|
|
||||||
|
|
||||||
const loaded: boolean = true
|
let loaded: boolean = false
|
||||||
const hiddenAppsIds: Ref<Application>[] = []
|
let hiddenAppsIds: Ref<Application>[] = []
|
||||||
// const hiddenAppsIdsQuery = createQuery()
|
const hiddenAppsIdsQuery = createQuery()
|
||||||
// hiddenAppsIdsQuery.query(workbench.class.HiddenApplication, {}, (res) => {
|
hiddenAppsIdsQuery.query(workbench.class.HiddenApplication, {}, (res) => {
|
||||||
// hiddenAppsIds = res.map((r) => r.attachedTo)
|
hiddenAppsIds = res.map((r) => r.attachedTo)
|
||||||
// loaded = true
|
loaded = true
|
||||||
// })
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-{direction === 'horizontal' ? 'row-center' : 'col-center'} clear-mins apps-{direction} relative">
|
<div class="flex-{direction === 'horizontal' ? 'row-center' : 'col-center'} clear-mins apps-{direction} relative">
|
||||||
@ -44,20 +44,9 @@
|
|||||||
contentDirection={direction}
|
contentDirection={direction}
|
||||||
buttons={'union'}
|
buttons={'union'}
|
||||||
>
|
>
|
||||||
{#each apps.filter((it) => (shown ? true : !hiddenAppsIds.includes(it._id))) as app}
|
{#each apps.filter((it) => !hiddenAppsIds.includes(it._id)) as app}
|
||||||
<NavLink app={app.alias} shrink={0}>
|
<NavLink app={app.alias} shrink={0}>
|
||||||
<App
|
<App selected={app._id === active} icon={app.icon} label={app.label} />
|
||||||
selected={app._id === active}
|
|
||||||
icon={app.icon}
|
|
||||||
label={app.label}
|
|
||||||
hidden={hiddenAppsIds.includes(app._id)}
|
|
||||||
editable={shown ? direction : false}
|
|
||||||
on:visible={(res) => {
|
|
||||||
if (res.detail === undefined) return
|
|
||||||
if (res.detail) showApplication(app)
|
|
||||||
else hideApplication(app)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{/each}
|
{/each}
|
||||||
<div class="apps-space-{direction}" />
|
<div class="apps-space-{direction}" />
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
import notification, { notificationId } from '@hcengineering/notification'
|
import notification, { notificationId } from '@hcengineering/notification'
|
||||||
import { BrowserNotificatator, NotificationClientImpl } from '@hcengineering/notification-resources'
|
import { BrowserNotificatator, NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||||
import { IntlString, getMetadata, getResource } from '@hcengineering/platform'
|
import { IntlString, getMetadata, getResource } from '@hcengineering/platform'
|
||||||
|
import setting from '@hcengineering/setting'
|
||||||
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation'
|
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import {
|
import {
|
||||||
AnyComponent,
|
AnyComponent,
|
||||||
@ -71,9 +72,10 @@
|
|||||||
import SelectWorkspaceMenu from './SelectWorkspaceMenu.svelte'
|
import SelectWorkspaceMenu from './SelectWorkspaceMenu.svelte'
|
||||||
import SpaceView from './SpaceView.svelte'
|
import SpaceView from './SpaceView.svelte'
|
||||||
import TopMenu from './icons/TopMenu.svelte'
|
import TopMenu from './icons/TopMenu.svelte'
|
||||||
|
import IconSettings from './icons/Settings.svelte'
|
||||||
|
import AppSwitcher from './AppSwitcher.svelte'
|
||||||
|
|
||||||
let contentPanel: HTMLElement
|
let contentPanel: HTMLElement
|
||||||
let shownMenu: boolean = false
|
|
||||||
|
|
||||||
const { setTheme } = getContext('theme') as any
|
const { setTheme } = getContext('theme') as any
|
||||||
|
|
||||||
@ -604,21 +606,15 @@
|
|||||||
/>
|
/>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<div class="divider" />
|
<div class="divider" />
|
||||||
<Applications
|
<Applications apps={getApps(apps)} active={currentApplication?._id} direction={appsDirection} />
|
||||||
apps={getApps(apps)}
|
|
||||||
active={currentApplication?._id}
|
|
||||||
direction={appsDirection}
|
|
||||||
bind:shown={shownMenu}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="info-box {appsDirection}" class:vertical-mobile={appsDirection === 'vertical'} class:mini={appsMini}>
|
<div class="info-box {appsDirection}" class:vertical-mobile={appsDirection === 'vertical'} class:mini={appsMini}>
|
||||||
<!-- <AppItem
|
<AppItem
|
||||||
icon={Settings}
|
icon={IconSettings}
|
||||||
label={setting.string.Settings}
|
label={setting.string.Settings}
|
||||||
selected={shownMenu}
|
|
||||||
size={appsMini ? 'small' : 'large'}
|
size={appsMini ? 'small' : 'large'}
|
||||||
on:click={() => (shownMenu = !shownMenu)}
|
on:click={() => showPopup(AppSwitcher, { apps: getApps(apps) }, popupPosition)}
|
||||||
/> -->
|
/>
|
||||||
<div class="flex-center" class:mt-3={appsDirection === 'vertical'} class:ml-2={appsDirection === 'horizontal'}>
|
<div class="flex-center" class:mt-3={appsDirection === 'vertical'} class:ml-2={appsDirection === 'horizontal'}>
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<div
|
||||||
|
Loading…
Reference in New Issue
Block a user