mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-13 11:50:56 +00:00
Fix pinned tabs and create tab (#6864)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
parent
e2d1b544d0
commit
f6ff5de501
@ -105,6 +105,7 @@ export class TTxSidebarEvent extends TTx implements TxSidebarEvent {
|
||||
@UX(workbench.string.Tab)
|
||||
export class TWorkbenchTab extends TPreference implements WorkbenchTab {
|
||||
location!: string
|
||||
name?: string
|
||||
isPinned!: boolean
|
||||
}
|
||||
|
||||
|
@ -73,9 +73,10 @@
|
||||
<div class="close-button {orientation}">
|
||||
<ButtonIcon icon={IconClose} size="min" on:click={() => dispatch('close')} />
|
||||
</div>
|
||||
{:else}
|
||||
{:else if $$slots.postfix === undefined}
|
||||
<div />
|
||||
{/if}
|
||||
<slot name="postfix" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -87,6 +87,9 @@
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M21.7071 2.29289C21.3166 1.90237 20.6834 1.90237 20.2929 2.29289C19.9024 2.68342 19.9024 3.31658 20.2929 3.70711L20.5858 4L14.9628 9.623C14.7214 9.86439 14.394 10 14.0526 10H13.2426C10.2609 10 7.4013 11.1845 5.29289 13.2929C4.90237 13.6834 4.90237 14.3166 5.29289 14.7071L10.5859 20.0001L2 28.5859V30H3.41436L12.0001 21.4143L17.2929 26.7071C17.6834 27.0976 18.3166 27.0976 18.7071 26.7071C20.8155 24.5987 22 21.7391 22 18.7574V17.9474C22 17.606 22.1356 17.2786 22.377 17.0372L28 11.4142L28.2929 11.7071C28.6834 12.0976 29.3166 12.0976 29.7071 11.7071C30.0976 11.3166 30.0976 10.6834 29.7071 10.2929L21.7071 2.29289ZM16.377 11.0372L22 5.41421L26.5858 10L20.9628 15.623C20.3463 16.2395 20 17.0756 20 17.9474V18.7574C20 20.873 19.2746 22.9139 17.9617 24.5474L7.45256 14.0383C9.08614 12.7254 11.127 12 13.2426 12H14.0526C14.9244 12 15.7605 11.6537 16.377 11.0372Z"/>
|
||||
</symbol>
|
||||
<symbol id="pin-tack" viewBox="0 0 18 18">
|
||||
<line x1="9" y1="16.25" x2="9" y2="12.25" stroke-linecap="round" stroke-linejoin="round" stroke="#212121"></line><path d="M14.25,12.25c-.089-.699-.318-1.76-.969-2.875-.335-.574-.703-1.028-1.031-1.375V3.75c0-1.105-.895-2-2-2h-2.5c-1.105,0-2,.895-2,2v4.25c-.329,.347-.697,.801-1.031,1.375-.65,1.115-.88,2.176-.969,2.875H14.25Z" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</symbol>
|
||||
<symbol id="model" viewBox="0 0 24 24">
|
||||
<path d="M17.2,7.8h3.6c1.1,0,2-0.9,2-2V4.2c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0H9.8V4c0-1.5-1.2-2.8-2.8-2.8H4 C2.5,1.2,1.2,2.5,1.2,4v2c0,1.5,1.2,2.8,2.8,2.8h3c1.5,0,2.8-1.2,2.8-2.8V5.8h2V18c0,1.5,1.2,2.8,2.8,2.8h0.8v0c0,1.1,0.9,2,2,2h3.6 c1.1,0,2-0.9,2-2v-1.6c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0h-0.8c-0.7,0-1.2-0.6-1.2-1.2v-4.8h2v0c0,1.1,0.9,2,2,2h3.6 c1.1,0,2-0.9,2-2v-1.6c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0h-2v-6h2v0C15.2,6.9,16.1,7.8,17.2,7.8z M8.2,6 c0,0.7-0.6,1.2-1.2,1.2H4C3.3,7.2,2.8,6.7,2.8,6V4c0-0.7,0.6-1.2,1.2-1.2h3c0.7,0,1.2,0.6,1.2,1.2V6z M16.8,19.2 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V19.2z M16.8,11.7 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V11.7z M16.8,4.2 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V4.2z"/>
|
||||
</symbol>
|
||||
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
@ -61,5 +61,6 @@ loadMetadata(view.icon, {
|
||||
Undo: `${icons}#undo`,
|
||||
Video: `${icons}#video`,
|
||||
Audio: `${icons}#audio`,
|
||||
File: `${icons}#file`
|
||||
File: `${icons}#file`,
|
||||
PinTack: `${icons}#pin-tack`
|
||||
})
|
||||
|
@ -257,7 +257,8 @@ const view = plugin(viewId, {
|
||||
Undo: '' as Asset,
|
||||
Video: '' as Asset,
|
||||
Audio: '' as Asset,
|
||||
File: '' as Asset
|
||||
File: '' as Asset,
|
||||
PinTack: '' as Asset
|
||||
},
|
||||
category: {
|
||||
General: '' as Ref<ActionCategory>,
|
||||
|
@ -13,85 +13,51 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AnySvelteComponent, closePanel, getCurrentLocation, Location, ModernTab, navigate } from '@hcengineering/ui'
|
||||
import {
|
||||
AnySvelteComponent,
|
||||
closePanel,
|
||||
getCurrentLocation,
|
||||
Icon,
|
||||
ModernTab,
|
||||
navigate,
|
||||
languageStore,
|
||||
locationToUrl
|
||||
} from '@hcengineering/ui'
|
||||
import { ComponentExtensions, getClient } from '@hcengineering/presentation'
|
||||
import { Asset, getResource, IntlString } from '@hcengineering/platform'
|
||||
import { type Application, WorkbenchTab } from '@hcengineering/workbench'
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { Asset, getResource, translate } from '@hcengineering/platform'
|
||||
import { WorkbenchTab } from '@hcengineering/workbench'
|
||||
import view from '@hcengineering/view'
|
||||
import { parseLinkId, showMenu } from '@hcengineering/view-resources'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { showMenu } from '@hcengineering/view-resources'
|
||||
|
||||
import { closeTab, getTabLocation, selectTab, tabIdStore, tabsStore } from '../workbench'
|
||||
import { closeTab, getTabDataByLocation, getTabLocation, selectTab, tabIdStore, tabsStore } from '../workbench'
|
||||
import workbench from '../plugin'
|
||||
|
||||
export let tab: WorkbenchTab
|
||||
|
||||
const client = getClient()
|
||||
const linkProviders = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||
|
||||
let name: string | undefined = undefined
|
||||
let label: IntlString | undefined = undefined
|
||||
let name: string | undefined = tab.name
|
||||
let icon: Asset | AnySvelteComponent | undefined
|
||||
let iconProps: Record<string, any> | undefined
|
||||
|
||||
async function getResolvedLocation (loc: Location, app?: Application): Promise<Location> {
|
||||
if (app === undefined) return loc
|
||||
if (app.locationResolver) {
|
||||
const resolver = await getResource(app.locationResolver)
|
||||
return (await resolver(loc))?.loc ?? loc
|
||||
}
|
||||
|
||||
return loc
|
||||
}
|
||||
|
||||
async function getTabName (loc: Location): Promise<string | undefined> {
|
||||
if (loc.fragment == null) return
|
||||
const hierarchy = client.getHierarchy()
|
||||
const [, id, _class] = decodeURIComponent(loc.fragment).split('|')
|
||||
if (_class == null) return
|
||||
|
||||
const mixin = hierarchy.classHierarchyMixin(_class as Ref<Class<Doc>>, view.mixin.ObjectTitle)
|
||||
if (mixin === undefined) return
|
||||
const titleProvider = await getResource(mixin.titleProvider)
|
||||
try {
|
||||
const _id = await parseLinkId(linkProviders, id, _class as Ref<Class<Doc>>)
|
||||
return await titleProvider(client, _id)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTabData (tab: WorkbenchTab): Promise<void> {
|
||||
async function updateTabData (tab: WorkbenchTab, lang: string): Promise<void> {
|
||||
const tabLoc = $tabIdStore === tab._id ? getCurrentLocation() : getTabLocation(tab)
|
||||
const alias = tabLoc.path[2]
|
||||
const application = client.getModel().findAllSync<Application>(workbench.class.Application, { alias })[0]
|
||||
const data = await getTabDataByLocation(tabLoc)
|
||||
|
||||
if (application?.locationDataResolver) {
|
||||
const resolver = await getResource(application.locationDataResolver)
|
||||
const data = await resolver(tabLoc)
|
||||
name = data.name
|
||||
label = data.nameIntl ?? application.label ?? workbench.string.Tab
|
||||
|
||||
if (data.iconComponent) {
|
||||
icon = await getResource(data.iconComponent)
|
||||
} else {
|
||||
icon = data.icon ?? application?.icon
|
||||
}
|
||||
iconProps = data.iconProps
|
||||
name = data.name ?? (await translate(data.label, {}, lang))
|
||||
if (data.iconComponent !== undefined) {
|
||||
icon = await getResource(data.iconComponent)
|
||||
} else {
|
||||
const special = tabLoc.path[3]
|
||||
const specialLabel = application?.navigatorModel?.specials?.find((s) => s.id === special)?.label
|
||||
const resolvedLoc = await getResolvedLocation(tabLoc, application)
|
||||
name = await getTabName(resolvedLoc)
|
||||
label = specialLabel ?? application?.label ?? workbench.string.Tab
|
||||
icon = application?.icon
|
||||
iconProps = undefined
|
||||
icon = data.icon
|
||||
}
|
||||
iconProps = data.iconProps
|
||||
|
||||
if (tab.name !== name && tab.location === locationToUrl(tabLoc)) {
|
||||
await client.update(tab, { name })
|
||||
}
|
||||
}
|
||||
|
||||
$: void updateTabData(tab)
|
||||
$: void updateTabData(tab, $languageStore)
|
||||
|
||||
function handleClickTab (): void {
|
||||
selectTab(tab._id)
|
||||
@ -118,7 +84,6 @@
|
||||
</script>
|
||||
|
||||
<ModernTab
|
||||
labelIntl={label}
|
||||
label={name}
|
||||
{icon}
|
||||
{iconProps}
|
||||
@ -132,4 +97,9 @@
|
||||
<svelte:fragment slot="prefix">
|
||||
<ComponentExtensions extension={workbench.extensions.WorkbenchTabExtensions} props={{ tab }} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="postfix">
|
||||
{#if tab.isPinned}
|
||||
<Icon icon={view.icon.PinTack} size="x-small" />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</ModernTab>
|
||||
|
@ -13,20 +13,26 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
import { derived, get, writable } from 'svelte/store'
|
||||
import core, { concatLink, getCurrentAccount, type Ref } from '@hcengineering/core'
|
||||
import workbench, { type WorkbenchTab } from '@hcengineering/workbench'
|
||||
import core, { type Class, concatLink, type Doc, getCurrentAccount, type Ref } from '@hcengineering/core'
|
||||
import { type Application, workbenchId, type WorkbenchTab } from '@hcengineering/workbench'
|
||||
import {
|
||||
location as locationStore,
|
||||
locationToUrl,
|
||||
parseLocation,
|
||||
type Location,
|
||||
navigate,
|
||||
getCurrentLocation
|
||||
getCurrentLocation,
|
||||
languageStore,
|
||||
type AnyComponent
|
||||
} from '@hcengineering/ui'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import view from '@hcengineering/view'
|
||||
import { type Asset, type IntlString, getMetadata, getResource, translate } from '@hcengineering/platform'
|
||||
import { parseLinkId } from '@hcengineering/view-resources'
|
||||
import { notificationId } from '@hcengineering/notification'
|
||||
|
||||
import { workspaceStore } from './utils'
|
||||
import workbench from './plugin'
|
||||
|
||||
export const tabIdStore = writable<Ref<WorkbenchTab> | undefined>()
|
||||
export const tabsStore = writable<WorkbenchTab[]>([])
|
||||
@ -38,7 +44,14 @@ workspaceStore.subscribe((workspace) => {
|
||||
tabIdStore.set(getTabFromLocalStorage(workspace ?? ''))
|
||||
})
|
||||
|
||||
locationStore.subscribe((loc) => {
|
||||
locationStore.subscribe(() => {
|
||||
void syncTabLoc()
|
||||
})
|
||||
|
||||
tabIdStore.subscribe(saveTabToLocalStorage)
|
||||
|
||||
async function syncTabLoc (): Promise<void> {
|
||||
const loc = getCurrentLocation()
|
||||
const workspace = get(workspaceStore)
|
||||
if (workspace == null || workspace === '') return
|
||||
const tab = get(currentTabStore)
|
||||
@ -51,11 +64,22 @@ locationStore.subscribe((loc) => {
|
||||
return
|
||||
}
|
||||
if (loc.path[2] === '' || loc.path[2] == null) return
|
||||
void getClient().update(tab, { location: locationToUrl(loc) })
|
||||
})
|
||||
|
||||
tabIdStore.subscribe(saveTabToLocalStorage)
|
||||
const data = await getTabDataByLocation(loc)
|
||||
const name = data.name ?? (await translate(data.label, {}, get(languageStore)))
|
||||
|
||||
if (tab.name !== undefined && name !== tab.name && tab.isPinned) {
|
||||
const me = getCurrentAccount()
|
||||
const _id = await getClient().createDoc(workbench.class.WorkbenchTab, core.space.Workspace, {
|
||||
location: locationToUrl(loc),
|
||||
name,
|
||||
attachedTo: me._id,
|
||||
isPinned: false
|
||||
})
|
||||
selectTab(_id)
|
||||
} else {
|
||||
await getClient().update(tab, { location: locationToUrl(loc), name })
|
||||
}
|
||||
}
|
||||
export function syncWorkbenchTab (): void {
|
||||
const workspace = get(workspaceStore)
|
||||
tabIdStore.set(getTabFromLocalStorage(workspace ?? ''))
|
||||
@ -90,7 +114,7 @@ export function selectTab (_id: Ref<WorkbenchTab>): void {
|
||||
tabIdStore.set(_id)
|
||||
}
|
||||
|
||||
export function getTabLocation (tab: WorkbenchTab): Location {
|
||||
export function getTabLocation (tab: Pick<WorkbenchTab, 'location'>): Location {
|
||||
const base = `${window.location.protocol}//${window.location.host}`
|
||||
const front = getMetadata(presentation.metadata.FrontUrl) ?? base
|
||||
const url = new URL(concatLink(front, tab.location))
|
||||
@ -120,13 +144,15 @@ export async function createTab (): Promise<void> {
|
||||
const loc = getCurrentLocation()
|
||||
const client = getClient()
|
||||
const me = getCurrentAccount()
|
||||
const defaultUrl = `${workbenchId}/${loc.path[1]}/${notificationId}`
|
||||
const tab = await client.createDoc(workbench.class.WorkbenchTab, core.space.Workspace, {
|
||||
attachedTo: me._id,
|
||||
location: locationToUrl(loc),
|
||||
location: defaultUrl,
|
||||
isPinned: false
|
||||
})
|
||||
|
||||
selectTab(tab)
|
||||
navigate(getTabLocation({ location: defaultUrl }))
|
||||
}
|
||||
|
||||
export function canCloseTab (tab: WorkbenchTab): boolean {
|
||||
@ -143,3 +169,66 @@ export async function unpinTab (tab: WorkbenchTab): Promise<void> {
|
||||
const client = getClient()
|
||||
await client.update(tab, { isPinned: false })
|
||||
}
|
||||
|
||||
export async function getTabDataByLocation (loc: Location): Promise<{
|
||||
name?: string
|
||||
label: IntlString
|
||||
icon?: Asset
|
||||
iconComponent?: AnyComponent
|
||||
iconProps?: Record<string, any>
|
||||
}> {
|
||||
const client = getClient()
|
||||
const appAlias = loc.path[2]
|
||||
const application = client.getModel().findAllSync<Application>(workbench.class.Application, { alias: appAlias })[0]
|
||||
|
||||
let name: string | undefined
|
||||
let label: IntlString | undefined
|
||||
let icon: Asset | undefined
|
||||
let iconComponent: AnyComponent | undefined
|
||||
let iconProps: Record<string, any> | undefined
|
||||
|
||||
if (application?.locationDataResolver != null) {
|
||||
const resolver = await getResource(application.locationDataResolver)
|
||||
const data = await resolver(loc)
|
||||
name = data.name
|
||||
label = data.nameIntl ?? application.label ?? workbench.string.Tab
|
||||
iconComponent = data.iconComponent
|
||||
icon = data.icon ?? application.icon
|
||||
iconProps = data.iconProps
|
||||
} else {
|
||||
const special = loc.path[3]
|
||||
const specialLabel = application?.navigatorModel?.specials?.find((s) => s.id === special)?.label
|
||||
const resolvedLoc = await getResolvedLocation(loc, application)
|
||||
name = await getDefaultTabName(resolvedLoc)
|
||||
label = specialLabel ?? application?.label ?? workbench.string.Tab
|
||||
icon = application?.icon
|
||||
}
|
||||
|
||||
return { name, label, icon, iconComponent, iconProps }
|
||||
}
|
||||
|
||||
async function getResolvedLocation (loc: Location, app?: Application): Promise<Location> {
|
||||
if (app?.locationResolver === undefined) return loc
|
||||
|
||||
const resolver = await getResource(app.locationResolver)
|
||||
return (await resolver(loc))?.loc ?? loc
|
||||
}
|
||||
|
||||
async function getDefaultTabName (loc: Location): Promise<string | undefined> {
|
||||
if (loc.fragment == null) return
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const [, id, _class] = decodeURIComponent(loc.fragment).split('|')
|
||||
if (_class == null) return
|
||||
|
||||
const mixin = hierarchy.classHierarchyMixin(_class as Ref<Class<Doc>>, view.mixin.ObjectTitle)
|
||||
if (mixin === undefined) return
|
||||
const titleProvider = await getResource(mixin.titleProvider)
|
||||
try {
|
||||
const linkProviders = client.getModel().findAllSync(view.mixin.LinkIdProvider, {})
|
||||
const _id = await parseLinkId(linkProviders, id, _class as Ref<Class<Doc>>)
|
||||
return await titleProvider(client, _id)
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +113,7 @@ export interface TxSidebarEvent<T extends Record<string, any> = Record<string, a
|
||||
export interface WorkbenchTab extends Preference {
|
||||
location: string
|
||||
isPinned: boolean
|
||||
name?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user