Adaptive aSide in Settings (#7130)

Signed-off-by: Alexander Platov <alexander.platov@hardcoreeng.com>
This commit is contained in:
Alexander Platov 2024-11-08 08:43:06 +03:00 committed by GitHub
parent 97412400ff
commit ad54f56f4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 127 additions and 27 deletions

View File

@ -38,7 +38,7 @@ import {
import templates from '@hcengineering/templates' import templates from '@hcengineering/templates'
import setting from './plugin' import setting from './plugin'
import workbench from '@hcengineering/model-workbench' import workbench, { WidgetType } from '@hcengineering/model-workbench'
import { type AnyComponent } from '@hcengineering/ui/src/types' import { type AnyComponent } from '@hcengineering/ui/src/types'
export { settingId } from '@hcengineering/setting' export { settingId } from '@hcengineering/setting'
@ -133,6 +133,18 @@ export function createModel (builder: Builder): void {
TSpaceTypeCreator TSpaceTypeCreator
) )
builder.createDoc(
workbench.class.Widget,
core.space.Model,
{
label: setting.string.Settings,
type: WidgetType.Flexible,
icon: setting.icon.Setting,
component: setting.component.SettingsWidget
},
setting.ids.SettingsWidget
)
builder.mixin(setting.class.Integration, core.class.Class, notification.mixin.ClassCollaborators, { builder.mixin(setting.class.Integration, core.class.Class, notification.mixin.ClassCollaborators, {
fields: ['modifiedBy'] fields: ['modifiedBy']
}) })

View File

@ -46,7 +46,8 @@ export default mergeIds(settingId, setting, {
InviteSetting: '' as AnyComponent, InviteSetting: '' as AnyComponent,
ArrayEditor: '' as AnyComponent, ArrayEditor: '' as AnyComponent,
IntegrationPanel: '' as AnyComponent, IntegrationPanel: '' as AnyComponent,
Configure: '' as AnyComponent Configure: '' as AnyComponent,
SettingsWidget: '' as AnyComponent
}, },
category: { category: {
Settings: '' as Ref<ActionCategory> Settings: '' as Ref<ActionCategory>

View File

@ -41,7 +41,8 @@ import workbench from './plugin'
export { workbenchId } from '@hcengineering/workbench' export { workbenchId } from '@hcengineering/workbench'
export { workbenchOperation } from './migration' export { workbenchOperation } from './migration'
export type { Application } export type { Application, Widget }
export { WidgetType } from '@hcengineering/workbench'
@Model(workbench.class.Application, core.class.Doc, DOMAIN_MODEL) @Model(workbench.class.Application, core.class.Doc, DOMAIN_MODEL)
@UX(workbench.string.Application) @UX(workbench.string.Application)

View File

@ -62,6 +62,7 @@
gap: var(--spacing-4); gap: var(--spacing-4);
} }
&__container { &__container {
justify-content: stretch;
height: 100%; height: 100%;
} }
&__container:not(.columns), &__container:not(.columns),

View File

@ -282,4 +282,10 @@
} }
} }
} }
&.short .hulyTableAttr-content.withTitle {
flex-direction: column;
align-items: stretch;
.hulyTableAttr-content__wrapper:empty { display: none; }
}
} }

View File

@ -32,6 +32,7 @@
export let padding: string | undefined = undefined export let padding: string | undefined = undefined
export let hidden: boolean = false export let hidden: boolean = false
export let allowFullsize: boolean = false export let allowFullsize: boolean = false
export let noTopIndent: boolean = false
export let hideFooter: boolean = false export let hideFooter: boolean = false
export let adaptive: 'default' | 'freezeActions' | 'doubleRow' | 'disabled' = 'disabled' export let adaptive: 'default' | 'freezeActions' | 'doubleRow' | 'disabled' = 'disabled'
export let showCancelButton: boolean = true export let showCancelButton: boolean = true
@ -56,7 +57,7 @@
<svelte:window on:keydown={onKeyDown} /> <svelte:window on:keydown={onKeyDown} />
<div class="hulyModal-container {type}" class:hidden> <div class="hulyModal-container {type}" class:hidden class:noTopIndent>
<Header <Header
{type} {type}
{allowFullsize} {allowFullsize}

View File

@ -164,6 +164,11 @@ export const settingsSeparators: DefSeparators = [
{ minSize: 19, size: 30, maxSize: 32, float: 'aside' } { minSize: 19, size: 30, maxSize: 32, float: 'aside' }
] ]
export const twoPanelsSeparators: DefSeparators = [
{ minSize: 12.5, size: 17.5, maxSize: 22.5, float: 'navigator' },
null
]
export const mainSeparators: DefSeparators = [ export const mainSeparators: DefSeparators = [
{ minSize: 30, size: 'auto', maxSize: 'auto' }, { minSize: 30, size: 'auto', maxSize: 'auto' },
{ minSize: 25, size: 30, maxSize: 80, float: 'sidebar' } { minSize: 25, size: 30, maxSize: 80, float: 'sidebar' }

View File

@ -35,7 +35,7 @@
resolvedLocationStore, resolvedLocationStore,
Scroller, Scroller,
Separator, Separator,
settingsSeparators twoPanelsSeparators
} from '@hcengineering/ui' } from '@hcengineering/ui'
import notification from '../../plugin' import notification from '../../plugin'
@ -93,7 +93,7 @@
unsubscribeTypeSetting() unsubscribeTypeSetting()
unsubscribeProviderSetting() unsubscribeProviderSetting()
}) })
defineSeparators('notificationSettings', settingsSeparators) defineSeparators('notificationSettings', twoPanelsSeparators)
</script> </script>
<div class="hulyComponent"> <div class="hulyComponent">

View File

@ -26,7 +26,9 @@
getEventPositionElement, getEventPositionElement,
showPopup, showPopup,
IconSettings, IconSettings,
ModernButton ModernButton,
resizeObserver,
deviceWidths
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { ObjectPresenter } from '@hcengineering/view-resources' import { ObjectPresenter } from '@hcengineering/view-resources'
import settings from '../plugin' import settings from '../plugin'
@ -144,7 +146,13 @@
</div> </div>
{/if} {/if}
{/if} {/if}
<div class="hulyTableAttr-container"> <div
class="hulyTableAttr-container"
use:resizeObserver={(el) => {
if (el.clientWidth < deviceWidths[0] && !el.classList.contains('short')) el.classList.add('short')
else if (el.clientWidth >= deviceWidths[0] && el.classList.contains('short')) el.classList.remove('short')
}}
>
<div class="hulyTableAttr-header font-medium-12" class:withButton={showHierarchy}> <div class="hulyTableAttr-header font-medium-12" class:withButton={showHierarchy}>
{#if showHierarchy} {#if showHierarchy}
<ModernButton icon={IconSettings} kind={'secondary'} size={'small'} {disabled} hasMenu> <ModernButton icon={IconSettings} kind={'secondary'} size={'small'} {disabled} hasMenu>

View File

@ -27,7 +27,7 @@
Header, Header,
Breadcrumb, Breadcrumb,
defineSeparators, defineSeparators,
settingsSeparators, twoPanelsSeparators,
Separator, Separator,
NavGroup NavGroup
} from '@hcengineering/ui' } from '@hcengineering/ui'
@ -97,7 +97,7 @@
$: if (ofClass !== undefined && _class !== undefined && !client.getHierarchy().isDerived(_class, ofClass)) { $: if (ofClass !== undefined && _class !== undefined && !client.getHierarchy().isDerived(_class, ofClass)) {
_class = ofClass _class = ofClass
} }
defineSeparators('workspaceSettings', settingsSeparators) defineSeparators('workspaceSettings', twoPanelsSeparators)
</script> </script>
<div class="hulyComponent"> <div class="hulyComponent">

View File

@ -35,6 +35,7 @@
export let attribute: AnyAttribute export let attribute: AnyAttribute
export let exist: boolean export let exist: boolean
export let disabled: boolean = true export let disabled: boolean = true
export let noTopIndent: boolean = false
let name: string let name: string
let type: Type<PropertyType> | undefined = attribute.type let type: Type<PropertyType> | undefined = attribute.type
@ -133,9 +134,8 @@
okLabel={presentation.string.Save} okLabel={presentation.string.Save}
okAction={save} okAction={save}
canSave={!(name === undefined || name.trim().length === 0) && !disabled} canSave={!(name === undefined || name.trim().length === 0) && !disabled}
onCancel={() => { onCancel={clearSettingsStore}
clearSettingsStore() {noTopIndent}
}}
> >
<svelte:fragment slot="actions"> <svelte:fragment slot="actions">
{#if !disabled} {#if !disabled}

View File

@ -27,7 +27,7 @@
Scroller, Scroller,
Separator, Separator,
defineSeparators, defineSeparators,
settingsSeparators, twoPanelsSeparators,
showPopup showPopup
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { showMenu } from '@hcengineering/view-resources' import { showMenu } from '@hcengineering/view-resources'
@ -51,7 +51,7 @@
showPopup(setting.component.EditEnum, { title: setting.string.CreateEnum }, 'top') showPopup(setting.component.EditEnum, { title: setting.string.CreateEnum }, 'top')
} }
defineSeparators('workspaceSettings', settingsSeparators) defineSeparators('workspaceSettings', twoPanelsSeparators)
</script> </script>
<div class="hulyComponent"> <div class="hulyComponent">

View File

@ -17,7 +17,8 @@
import { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core' import { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import login, { loginId } from '@hcengineering/login' import login, { loginId } from '@hcengineering/login'
import { setMetadata } from '@hcengineering/platform' import { setMetadata } from '@hcengineering/platform'
import presentation, { closeClient, createQuery } from '@hcengineering/presentation' import presentation, { closeClient, getClient, createQuery } from '@hcengineering/presentation'
import settingPlg from '../plugin'
import setting, { SettingsCategory, SettingsEvents } from '@hcengineering/setting' import setting, { SettingsCategory, SettingsEvents } from '@hcengineering/setting'
import { import {
Component, Component,
@ -35,13 +36,18 @@
settingsSeparators, settingsSeparators,
showPopup, showPopup,
type AnyComponent, type AnyComponent,
deviceOptionsStore as deviceInfo deviceOptionsStore as deviceInfo,
resizeObserver,
deviceWidths
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { NavFooter } from '@hcengineering/workbench-resources' import { closeWidget, NavFooter, openWidget, minimizeSidebar } from '@hcengineering/workbench-resources'
import workbench from '@hcengineering/workbench'
import { ComponentType, onDestroy, onMount } from 'svelte' import { ComponentType, onDestroy, onMount } from 'svelte'
import { clearSettingsStore, settingsStore, type SettingsStore } from '../store' import { clearSettingsStore, settingsStore, type SettingsStore } from '../store'
import { Analytics } from '@hcengineering/analytics' import { Analytics } from '@hcengineering/analytics'
const client = getClient()
let category: SettingsCategory | undefined let category: SettingsCategory | undefined
let categoryId: string = '' let categoryId: string = ''
@ -122,11 +128,26 @@
return ss.component === undefined ? null : ss.component return ss.component === undefined ? null : ss.component
} }
$: asideComponent = updatedStore($settingsStore) $: asideComponent = updatedStore($settingsStore)
let moveASide: boolean = false
const widget = client.getModel().findAllSync(workbench.class.Widget, { _id: settingPlg.ids.SettingsWidget })[0]
$: if (moveASide && asideComponent != null) {
openWidget(widget, { component: asideComponent, ...asideProps }, { active: true, openedByUser: true })
$deviceInfo.aside.visible = true
} else if ((moveASide && asideComponent == null) || (!moveASide && asideComponent != null)) {
closeWidget(widget._id)
minimizeSidebar()
}
defineSeparators('setting', settingsSeparators) defineSeparators('setting', settingsSeparators)
</script> </script>
<div class="hulyPanels-container"> <div
class="hulyPanels-container"
use:resizeObserver={(el) => {
if (el.clientWidth < deviceWidths[2] && !moveASide) moveASide = true
else if (el.clientWidth >= deviceWidths[2] && moveASide) moveASide = false
}}
>
{#if $deviceInfo.navigator.visible} {#if $deviceInfo.navigator.visible}
<div <div
class="antiPanel-navigator {$deviceInfo.navigator.direction === 'horizontal' class="antiPanel-navigator {$deviceInfo.navigator.direction === 'horizontal'
@ -208,7 +229,7 @@
<Component is={category.component} props={{ kind: 'content' }} /> <Component is={category.component} props={{ kind: 'content' }} />
{/if} {/if}
</div> </div>
{#if asideComponent != null} {#if asideComponent != null && !moveASide}
<Separator name={'setting'} index={1} color={'transparent'} separatorSize={0} short /> <Separator name={'setting'} index={1} color={'transparent'} separatorSize={0} short />
<div class="hulySidePanel-container"> <div class="hulySidePanel-container">
{#key asideProps} {#key asideProps}

View File

@ -0,0 +1,32 @@
<!--
// Copyright © 2024 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 { WidgetState } from '@hcengineering/workbench-resources'
import { type AnyComponent, Component } from '@hcengineering/ui'
import { ComponentType } from 'svelte'
export let widgetState: WidgetState | undefined
$: asideProps = widgetState?.data
$: asideComponent = asideProps?.component as AnyComponent | ComponentType
</script>
{#key asideProps}
{#if typeof asideComponent === 'string'}
<Component is={asideComponent} props={{ ...asideProps, noTopIndent: true }} />
{:else}
<svelte:component this={asideComponent} {...asideProps} noTopIndent />
{/if}
{/key}

View File

@ -58,6 +58,7 @@ import RefEditor from './components/typeEditors/RefEditor.svelte'
import RoleAssignmentEditor from './components/typeEditors/RoleAssignmentEditor.svelte' import RoleAssignmentEditor from './components/typeEditors/RoleAssignmentEditor.svelte'
import StringTypeEditor from './components/typeEditors/StringTypeEditor.svelte' import StringTypeEditor from './components/typeEditors/StringTypeEditor.svelte'
import WorkspaceSettings from './components/WorkspaceSettings.svelte' import WorkspaceSettings from './components/WorkspaceSettings.svelte'
import SettingsWidget from './components/SettingsWidget.svelte'
import setting from './plugin' import setting from './plugin'
import { filterDescendants, getOwnerFirstName, getOwnerLastName, getOwnerPosition, getValue } from './utils' import { filterDescendants, getOwnerFirstName, getOwnerLastName, getOwnerPosition, getValue } from './utils'
@ -121,7 +122,8 @@ export default async (): Promise<Resources> => ({
SpaceTypePropertiesSectionEditor, SpaceTypePropertiesSectionEditor,
SpaceTypeRolesSectionEditor, SpaceTypeRolesSectionEditor,
RoleEditor, RoleEditor,
RoleAssignmentEditor RoleAssignmentEditor,
SettingsWidget
}, },
actionImpl: { actionImpl: {
DeleteMixin DeleteMixin

View File

@ -13,12 +13,17 @@
// limitations under the License. // limitations under the License.
// //
import { type Ref } from '@hcengineering/core'
import type { IntlString } from '@hcengineering/platform' import type { IntlString } from '@hcengineering/platform'
import { mergeIds } from '@hcengineering/platform' import { mergeIds } from '@hcengineering/platform'
import setting, { settingId } from '@hcengineering/setting' import setting, { settingId } from '@hcengineering/setting'
import { type AnyComponent } from '@hcengineering/ui' import { type AnyComponent } from '@hcengineering/ui'
import { type Widget } from '@hcengineering/workbench'
export default mergeIds(settingId, setting, { export default mergeIds(settingId, setting, {
ids: {
SettingsWidget: '' as Ref<Widget>
},
component: { component: {
EditEnum: '' as AnyComponent, EditEnum: '' as AnyComponent,
ManageSpaceTypes: '' as AnyComponent, ManageSpaceTypes: '' as AnyComponent,

View File

@ -17,7 +17,7 @@
Breadcrumb, Breadcrumb,
Separator, Separator,
defineSeparators, defineSeparators,
settingsSeparators, twoPanelsSeparators,
Scroller Scroller
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { getActions as getContributedActions, TreeItem, TreeNode } from '@hcengineering/view-resources' import { getActions as getContributedActions, TreeItem, TreeNode } from '@hcengineering/view-resources'
@ -147,7 +147,7 @@
} }
let space: Ref<TemplateCategory> | undefined = undefined let space: Ref<TemplateCategory> | undefined = undefined
defineSeparators('workspaceSettings', settingsSeparators) defineSeparators('workspaceSettings', twoPanelsSeparators)
</script> </script>
<div class="hulyComponent"> <div class="hulyComponent">

View File

@ -79,7 +79,7 @@
overflow: hidden; overflow: hidden;
flex-direction: row; flex-direction: row;
min-width: 25rem; min-width: 25rem;
border-radius: var(--medium-BorderRadius); border-radius: 0 var(--medium-BorderRadius) var(--medium-BorderRadius) 0;
&.mini:not(.float) { &.mini:not(.float) {
width: 3.5rem !important; width: 3.5rem !important;
@ -89,6 +89,9 @@
&.mini.float { &.mini.float {
justify-content: flex-end; justify-content: flex-end;
} }
&.float > :global(.sidebar-content) {
border-top: none;
}
} }
@media (max-width: 1024px) { @media (max-width: 1024px) {
.sidebar-container { .sidebar-container {

View File

@ -96,7 +96,7 @@
} }
</script> </script>
<div class="content"> <div class="sidebar-content">
{#if widget?.component} {#if widget?.component}
<div class="component" use:resizeObserver={resize}> <div class="component" use:resizeObserver={resize}>
{#if widget.headerLabel} {#if widget.headerLabel}
@ -145,7 +145,7 @@
<WidgetsBar {widgets} {preferences} selected={widgetId} /> <WidgetsBar {widgets} {preferences} selected={widgetId} />
<style lang="scss"> <style lang="scss">
.content { .sidebar-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -334,6 +334,8 @@ export function minimizeSidebar (closedByUser = false): void {
} }
sidebarStore.set({ ...state, ...widgetsState, widget: undefined, variant: SidebarVariant.MINI }) sidebarStore.set({ ...state, ...widgetsState, widget: undefined, variant: SidebarVariant.MINI })
const devInfo = get(deviceInfo)
if (devInfo.navigator.float && devInfo.aside.visible) deviceInfo.set({ ...devInfo, aside: { visible: false } })
} }
export function updateTabData (widget: Ref<Widget>, tabId: string, data: Record<string, any>): void { export function updateTabData (widget: Ref<Widget>, tabId: string, data: Record<string, any>): void {

View File

@ -12,7 +12,7 @@ export class SidebarPage extends CommonPage {
} }
sidebar = (): Locator => this.page.locator('#sidebar') sidebar = (): Locator => this.page.locator('#sidebar')
content = (): Locator => this.sidebar().locator('.content') content = (): Locator => this.sidebar().locator('.sidebar-content')
contentHeaderByTitle = (title: string): Locator => contentHeaderByTitle = (title: string): Locator =>
this.content().locator(`.hulyHeader-titleGroup:has-text("${title}")`) this.content().locator(`.hulyHeader-titleGroup:has-text("${title}")`)