mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-09 17:54:32 +00:00
Merge remote-tracking branch 'origin/develop' into staging
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
commit
bcf0901c0f
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Location, location, locationToUrl, navigate } from '@hcengineering/ui'
|
||||
import { Location, location, locationStorageKeyId, locationToUrl, navigate } from '@hcengineering/ui'
|
||||
import { setFilters } from '../../filter'
|
||||
|
||||
export let app: string | undefined = undefined
|
||||
@ -21,6 +21,7 @@
|
||||
export let special: string | undefined = undefined
|
||||
export let disabled = false
|
||||
export let shrink: number | undefined = undefined
|
||||
export let restoreLastLocation = false
|
||||
|
||||
$: loc = createLocation($location, app, space, special)
|
||||
|
||||
@ -32,6 +33,20 @@
|
||||
space: string | undefined,
|
||||
special: string | undefined
|
||||
): Location {
|
||||
if (restoreLastLocation) {
|
||||
const last = localStorage.getItem(`${locationStorageKeyId}_${app}`)
|
||||
if (last != null) {
|
||||
try {
|
||||
const newLocation: Location = JSON.parse(last)
|
||||
if (newLocation.path[2] === app && newLocation.path[3] != null) {
|
||||
return newLocation
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const location: Location = {
|
||||
path: [...loc.path]
|
||||
}
|
||||
@ -50,7 +65,7 @@
|
||||
return location
|
||||
}
|
||||
|
||||
function clickHandler (e: MouseEvent) {
|
||||
function clickHandler (e: MouseEvent): void {
|
||||
if (e.metaKey || e.ctrlKey) return
|
||||
e.preventDefault()
|
||||
setFilters([])
|
||||
|
@ -58,13 +58,13 @@
|
||||
buttons={'union'}
|
||||
>
|
||||
{#each topApps as app}
|
||||
<NavLink app={app.alias} shrink={0}>
|
||||
<NavLink app={app.alias} shrink={0} disabled={app._id === active}>
|
||||
<AppItem selected={app._id === active} icon={app.icon} label={app.label} />
|
||||
</NavLink>
|
||||
{/each}
|
||||
<div class="divider" />
|
||||
{#each bottomdApps as app}
|
||||
<NavLink app={app.alias} shrink={0}>
|
||||
<NavLink app={app.alias} shrink={0} disabled={app._id === active}>
|
||||
<AppItem selected={app._id === active} icon={app.icon} label={app.label} />
|
||||
</NavLink>
|
||||
{/each}
|
||||
|
@ -29,7 +29,7 @@
|
||||
import login from '@hcengineering/login'
|
||||
import notification, { DocNotifyContext, InboxNotification, notificationId } from '@hcengineering/notification'
|
||||
import { BrowserNotificatator, InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||
import { broadcastEvent, getMetadata, getResource, IntlString } from '@hcengineering/platform'
|
||||
import { broadcastEvent, getMetadata, getResource, IntlString, translate } from '@hcengineering/platform'
|
||||
import {
|
||||
ActionContext,
|
||||
ComponentExtensions,
|
||||
@ -56,6 +56,7 @@
|
||||
getLocation,
|
||||
IconSettings,
|
||||
Label,
|
||||
languageStore,
|
||||
Location,
|
||||
location,
|
||||
locationStorageKeyId,
|
||||
@ -112,7 +113,16 @@
|
||||
import TopMenu from './icons/TopMenu.svelte'
|
||||
import WidgetsBar from './sidebar/Sidebar.svelte'
|
||||
import { sidebarStore, SidebarVariant, syncSidebarState } from '../sidebar'
|
||||
import { getTabLocation, selectTab, syncWorkbenchTab, tabIdStore, tabsStore } from '../workbench'
|
||||
import {
|
||||
getTabDataByLocation,
|
||||
getTabLocation,
|
||||
prevTabIdStore,
|
||||
selectTab,
|
||||
syncWorkbenchTab,
|
||||
tabIdStore,
|
||||
tabsStore
|
||||
} from '../workbench'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
let contentPanel: HTMLElement
|
||||
|
||||
@ -161,7 +171,6 @@
|
||||
|
||||
let tabs: WorkbenchTab[] = []
|
||||
let areTabsLoaded = false
|
||||
let prevTab: Ref<WorkbenchTab> | undefined
|
||||
|
||||
const query = createQuery()
|
||||
$: query.query(
|
||||
@ -190,26 +199,35 @@
|
||||
const isLocEqual = tabLoc ? areLocationsEqual(loc, tabLoc) : false
|
||||
if (!isLocEqual) {
|
||||
const url = locationToUrl(loc)
|
||||
const tabByUrl = tabs.find((t) => t.location === url)
|
||||
if (tabByUrl !== undefined) {
|
||||
prevTab = tabByUrl._id
|
||||
selectTab(tabByUrl._id)
|
||||
const data = await getTabDataByLocation(loc)
|
||||
const name = data.name ?? (await translate(data.label, {}, get(languageStore)))
|
||||
const tabByName = get(tabsStore).find((t) => {
|
||||
if (t.location === url) return true
|
||||
if (t.name !== name) return false
|
||||
|
||||
const tabLoc = getTabLocation(t)
|
||||
|
||||
return tabLoc.path[2] === loc.path[2] && tabLoc.path[3] === loc.path[3]
|
||||
})
|
||||
if (tabByName !== undefined) {
|
||||
selectTab(tabByName._id)
|
||||
prevTabIdStore.set(tabByName._id)
|
||||
} else {
|
||||
const tabToReplace = tabs.findLast((t) => !t.isPinned)
|
||||
if (tabToReplace !== undefined) {
|
||||
await client.update(tabToReplace, {
|
||||
location: url
|
||||
})
|
||||
prevTab = tabToReplace._id
|
||||
selectTab(tabToReplace._id)
|
||||
prevTabIdStore.set(tabToReplace._id)
|
||||
} else {
|
||||
const _id = await client.createDoc(workbench.class.WorkbenchTab, core.space.Workspace, {
|
||||
attachedTo: account._id,
|
||||
location: url,
|
||||
isPinned: false
|
||||
})
|
||||
prevTab = _id
|
||||
selectTab(_id)
|
||||
prevTabIdStore.set(_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -372,12 +390,16 @@
|
||||
async function syncLoc (loc: Location): Promise<void> {
|
||||
accessDeniedStore.set(false)
|
||||
const originalLoc = JSON.stringify(loc)
|
||||
if ($tabIdStore !== prevTab) {
|
||||
if (prevTab) {
|
||||
clear(1)
|
||||
clear(2)
|
||||
if ($tabIdStore !== $prevTabIdStore) {
|
||||
if ($prevTabIdStore) {
|
||||
const prevTab = tabs.find((t) => t._id === $prevTabIdStore)
|
||||
const prevTabLoc = prevTab ? getTabLocation(prevTab) : undefined
|
||||
if (prevTabLoc === undefined || prevTabLoc.path[2] !== loc.path[2]) {
|
||||
clear(1)
|
||||
clear(2)
|
||||
}
|
||||
}
|
||||
prevTab = $tabIdStore
|
||||
prevTabIdStore.set($tabIdStore)
|
||||
}
|
||||
if (loc.path.length > 3 && getSpecialComponent(loc.path[3]) === undefined) {
|
||||
// resolve short links
|
||||
@ -766,7 +788,7 @@
|
||||
/>
|
||||
</div>
|
||||
<!-- <ActivityStatus status="active" /> -->
|
||||
<NavLink app={notificationId} shrink={0}>
|
||||
<NavLink app={notificationId} shrink={0} restoreLastLocation>
|
||||
<AppItem
|
||||
icon={notification.icon.Notifications}
|
||||
label={notification.string.Inbox}
|
||||
|
@ -100,6 +100,8 @@
|
||||
<svelte:fragment slot="postfix">
|
||||
{#if tab.isPinned}
|
||||
<Icon icon={view.icon.PinTack} size="x-small" />
|
||||
{:else if $tabsStore.length === 1}
|
||||
<div />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</ModernTab>
|
||||
|
@ -13,7 +13,15 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
import { derived, get, writable } from 'svelte/store'
|
||||
import core, { type Class, concatLink, type Doc, getCurrentAccount, type Ref } from '@hcengineering/core'
|
||||
import core, {
|
||||
type Class,
|
||||
concatLink,
|
||||
type Doc,
|
||||
getCurrentAccount,
|
||||
type Ref,
|
||||
RateLimiter,
|
||||
generateId
|
||||
} from '@hcengineering/core'
|
||||
import { type Application, workbenchId, type WorkbenchTab } from '@hcengineering/workbench'
|
||||
import {
|
||||
location as locationStore,
|
||||
@ -23,29 +31,40 @@ import {
|
||||
navigate,
|
||||
getCurrentLocation,
|
||||
languageStore,
|
||||
type AnyComponent
|
||||
type AnyComponent,
|
||||
locationStorageKeyId
|
||||
} from '@hcengineering/ui'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
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 notification, { notificationId } from '@hcengineering/notification'
|
||||
|
||||
import { workspaceStore } from './utils'
|
||||
import workbench from './plugin'
|
||||
|
||||
export const tabIdStore = writable<Ref<WorkbenchTab> | undefined>()
|
||||
export const prevTabIdStore = writable<Ref<WorkbenchTab> | undefined>()
|
||||
export const tabsStore = writable<WorkbenchTab[]>([])
|
||||
export const currentTabStore = derived([tabIdStore, tabsStore], ([tabId, tabs]) => {
|
||||
return tabs.find((tab) => tab._id === tabId)
|
||||
})
|
||||
|
||||
let prevTabId: Ref<WorkbenchTab> | undefined
|
||||
tabIdStore.subscribe((value) => {
|
||||
prevTabIdStore.set(prevTabId)
|
||||
prevTabId = value
|
||||
})
|
||||
|
||||
// Use rate limiter to control tab creation, preventing multiple tabs during fast location changing
|
||||
const limiter = new RateLimiter(1)
|
||||
|
||||
workspaceStore.subscribe((workspace) => {
|
||||
tabIdStore.set(getTabFromLocalStorage(workspace ?? ''))
|
||||
})
|
||||
|
||||
locationStore.subscribe(() => {
|
||||
void syncTabLoc()
|
||||
locationStore.subscribe((l: Location) => {
|
||||
void limiter.add(syncTabLoc)
|
||||
})
|
||||
|
||||
tabIdStore.subscribe(saveTabToLocalStorage)
|
||||
@ -64,20 +83,51 @@ async function syncTabLoc (): Promise<void> {
|
||||
return
|
||||
}
|
||||
if (loc.path[2] === '' || loc.path[2] == null) return
|
||||
|
||||
const url = locationToUrl(loc)
|
||||
const data = await getTabDataByLocation(loc)
|
||||
const name = data.name ?? (await translate(data.label, {}, get(languageStore)))
|
||||
const tabByName = get(tabsStore).find((t) => {
|
||||
if (t.name !== name) return false
|
||||
|
||||
const tabLoc = getTabLocation(t)
|
||||
|
||||
return tabLoc.path[2] === loc.path[2] && tabLoc.path[3] === loc.path[3]
|
||||
})
|
||||
|
||||
if (tab.name !== undefined && name !== tab.name && tab.isPinned) {
|
||||
if (tabByName !== undefined) {
|
||||
selectTab(tabByName._id)
|
||||
return
|
||||
}
|
||||
|
||||
const me = getCurrentAccount()
|
||||
const _id = await getClient().createDoc(workbench.class.WorkbenchTab, core.space.Workspace, {
|
||||
location: locationToUrl(loc),
|
||||
const tab: WorkbenchTab = {
|
||||
_id: generateId(),
|
||||
_class: workbench.class.WorkbenchTab,
|
||||
space: core.space.Workspace,
|
||||
location: url,
|
||||
name,
|
||||
attachedTo: me._id,
|
||||
isPinned: false
|
||||
})
|
||||
selectTab(_id)
|
||||
isPinned: false,
|
||||
modifiedOn: Date.now(),
|
||||
modifiedBy: me._id
|
||||
}
|
||||
await getClient().createDoc(workbench.class.WorkbenchTab, core.space.Workspace, tab, tab._id)
|
||||
tabsStore.update((tabs) => [...tabs, tab])
|
||||
selectTab(tab._id)
|
||||
} else {
|
||||
await getClient().update(tab, { location: locationToUrl(loc), name })
|
||||
// TODO: Fix this
|
||||
// if (
|
||||
// tabByName !== undefined &&
|
||||
// tabByName._id !== tab._id &&
|
||||
// (loc.path[2] !== tabLoc.path[2] || loc.path[3] !== tabLoc.path[3])
|
||||
// ) {
|
||||
// selectTab(tabByName._id)
|
||||
// return
|
||||
// }
|
||||
|
||||
await getClient().update(tab, { location: url, name })
|
||||
}
|
||||
}
|
||||
export function syncWorkbenchTab (): void {
|
||||
@ -144,11 +194,24 @@ export async function createTab (): Promise<void> {
|
||||
const loc = getCurrentLocation()
|
||||
const client = getClient()
|
||||
const me = getCurrentAccount()
|
||||
const defaultUrl = `${workbenchId}/${loc.path[1]}/${notificationId}`
|
||||
let defaultUrl = `${workbenchId}/${loc.path[1]}/${notificationId}`
|
||||
|
||||
try {
|
||||
const last = localStorage.getItem(`${locationStorageKeyId}_${notificationId}`)
|
||||
const lastLocation: Location | undefined = last != null ? JSON.parse(last) : undefined
|
||||
if (lastLocation != null && lastLocation.path[2] === notificationId) {
|
||||
defaultUrl = locationToUrl(lastLocation)
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const name = await translate(notification.string.Inbox, {}, get(languageStore))
|
||||
const tab = await client.createDoc(workbench.class.WorkbenchTab, core.space.Workspace, {
|
||||
attachedTo: me._id,
|
||||
location: defaultUrl,
|
||||
isPinned: false
|
||||
isPinned: false,
|
||||
name
|
||||
})
|
||||
|
||||
selectTab(tab)
|
||||
|
@ -28,7 +28,9 @@ export class ChannelPage extends CommonPage {
|
||||
.locator('xpath=following-sibling::div[1]')
|
||||
.locator('button', { hasText: channel })
|
||||
|
||||
readonly chooseChannel = (channel: string): Locator => this.page.getByRole('button', { name: channel })
|
||||
readonly chooseChannel = (channel: string): Locator =>
|
||||
this.page.locator('div.antiPanel-navigator').getByRole('button', { name: channel })
|
||||
|
||||
readonly closePopupWindow = (): Locator => this.page.locator('.notifyPopup button[data-id="btnNotifyClose"]')
|
||||
readonly openAddMemberToChannel = (userName: string): Locator => this.page.getByRole('button', { name: userName })
|
||||
readonly addMemberToChannelTableButton = (userName: string): Locator =>
|
||||
|
Loading…
Reference in New Issue
Block a user