Sidebar attachments preview (#6797)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2024-10-03 19:30:32 +04:00 committed by GitHub
parent e09a24cbc1
commit d3a7759ef7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 376 additions and 83 deletions

View File

@ -40,6 +40,7 @@
"@hcengineering/ui": "^0.6.15",
"@hcengineering/view": "^0.6.13",
"@hcengineering/model-presentation": "^0.6.0",
"@hcengineering/model-uploader": "^0.6.0"
"@hcengineering/model-uploader": "^0.6.0",
"@hcengineering/workbench": "^0.6.16"
}
}

View File

@ -31,6 +31,8 @@ import {
import core, { TAttachedDoc } from '@hcengineering/model-core'
import preference, { TPreference } from '@hcengineering/model-preference'
import view, { createAction } from '@hcengineering/model-view'
import workbench, { WidgetType } from '@hcengineering/workbench'
import presentation from '@hcengineering/model-presentation'
import attachment from './plugin'
@ -97,6 +99,24 @@ export function createModel (builder: Builder): void {
editor: attachment.component.Photos
})
builder.createDoc(
workbench.class.Widget,
core.space.Model,
{
label: attachment.string.Files,
type: WidgetType.Flexible,
icon: attachment.icon.Attachment,
component: attachment.component.PreviewWidget,
closeIfNoTabs: true
},
attachment.ids.PreviewWidget
)
builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, {
extension: presentation.extension.FilePreviewPopupActions,
component: attachment.component.PreviewPopupActions
})
builder.createDoc(
activity.class.DocUpdateMessageViewlet,
core.space.Model,

View File

@ -24,7 +24,9 @@ import type { ActionCategory } from '@hcengineering/view'
export default mergeIds(attachmentId, attachment, {
component: {
AttachmentPresenter: '' as AnyComponent
AttachmentPresenter: '' as AnyComponent,
PreviewWidget: '' as AnyComponent,
PreviewPopupActions: '' as AnyComponent
},
string: {
AddAttachment: '' as IntlString,

View File

@ -0,0 +1,43 @@
<!--
// 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 type { Ref, Blob } from '@hcengineering/core'
import { Button } from '@hcengineering/ui'
import { getFileUrl } from '../file'
import Download from './icons/Download.svelte'
import presentation from '../plugin'
export let file: Ref<Blob> | undefined
export let name: string
let download: HTMLAnchorElement
$: srcRef = file !== undefined ? getFileUrl(file, name) : undefined
</script>
{#await srcRef then src}
{#if src !== ''}
<a class="no-line" href={src} download={name} bind:this={download}>
<Button
icon={Download}
kind={'icon'}
on:click={() => {
download.click()
}}
showTooltip={{ label: presentation.string.Download }}
/>
</a>
{/if}
{/await}

View File

@ -15,17 +15,17 @@
<script lang="ts">
import { type Blob, type Ref } from '@hcengineering/core'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { Button, Dialog, tooltip } from '@hcengineering/ui'
import { Dialog, tooltip } from '@hcengineering/ui'
import { createEventDispatcher, onMount } from 'svelte'
import presentation from '../plugin'
import { getFileUrl } from '../file'
import { BlobMetadata } from '../types'
import ActionContext from './ActionContext.svelte'
import FilePreview from './FilePreview.svelte'
import Download from './icons/Download.svelte'
import DownloadFileButton from './DownloadFileButton.svelte'
import { ComponentExtensions } from '../index'
import presentation from '../plugin'
import FileTypeIcon from './FileTypeIcon.svelte'
export let file: Ref<Blob> | undefined
export let name: string
@ -38,21 +38,11 @@
const dispatch = createEventDispatcher()
let download: HTMLAnchorElement
onMount(() => {
if (fullSize) {
dispatch('fullsize')
}
})
function iconLabel (name: string): string {
const parts = `${name}`.split('.')
const ext = parts[parts.length - 1]
return ext.substring(0, 4).toUpperCase()
}
$: srcRef = file !== undefined ? getFileUrl(file, name) : undefined
</script>
<ActionContext context={{ mode: 'browser' }} />
@ -67,9 +57,7 @@
<div class="antiTitle icon-wrapper">
{#if showIcon}
<div class="wrapped-icon">
<div class="flex-center icon">
{iconLabel(name)}
</div>
<FileTypeIcon {name} />
</div>
{/if}
<span class="wrapped-title" use:tooltip={{ label: getEmbeddedLabel(name) }}>{name}</span>
@ -77,39 +65,19 @@
</svelte:fragment>
<svelte:fragment slot="utils">
{#await srcRef then src}
{#if src !== ''}
<a class="no-line" href={src} download={name} bind:this={download}>
<Button
icon={Download}
kind={'ghost'}
on:click={() => {
download.click()
<DownloadFileButton {name} {file} />
<ComponentExtensions
extension={presentation.extension.FilePreviewPopupActions}
props={{
file,
name,
contentType,
metadata
}}
showTooltip={{ label: presentation.string.Download }}
/>
</a>
{/if}
{/await}
</svelte:fragment>
{#if file}
<FilePreview {file} {contentType} {name} {metadata} {props} fit />
{/if}
</Dialog>
<style lang="scss">
.icon {
position: relative;
flex-shrink: 0;
width: 2rem;
height: 2rem;
font-weight: 500;
font-size: 0.625rem;
color: var(--primary-button-color);
background-color: var(--primary-button-default);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.5rem;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,42 @@
<!--
// 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">
export let name: string
function iconLabel (name: string): string {
const parts = `${name}`.split('.')
const ext = parts[parts.length - 1]
return ext.substring(0, 4).toUpperCase()
}
</script>
<div class="flex-center icon">
{iconLabel(name)}
</div>
<style lang="scss">
.icon {
position: relative;
flex-shrink: 0;
width: 2rem;
height: 2rem;
font-weight: 500;
font-size: 0.625rem;
color: var(--primary-button-color);
background-color: var(--primary-button-default);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.5rem;
}
</style>

View File

@ -47,6 +47,8 @@ export { default as BreadcrumbsElement } from './components/breadcrumbs/Breadcru
export { default as ComponentExtensions } from './components/extensions/ComponentExtensions.svelte'
export { default as DocCreateExtComponent } from './components/extensions/DocCreateExtComponent.svelte'
export { default as SearchResult } from './components/SearchResult.svelte'
export { default as DownloadFileButton } from './components/DownloadFileButton.svelte'
export { default as FileTypeIcon } from './components/FileTypeIcon.svelte'
export { default } from './plugin'
export * from './types'
export * from './utils'

View File

@ -123,7 +123,8 @@ export default plugin(presentationId, {
ContentTypeNotSupported: '' as IntlString
},
extension: {
FilePreviewExtension: '' as ComponentExtensionId
FilePreviewExtension: '' as ComponentExtensionId,
FilePreviewPopupActions: '' as ComponentExtensionId
},
metadata: {
ModelVersion: '' as Metadata<string>,

View File

@ -22,6 +22,7 @@
export let icon: Asset | AnySvelteComponent | ComponentType | undefined = undefined
export let iconProps: any | undefined = undefined
export let iconWidth: string | undefined = undefined
export let iconMargin: string | undefined = undefined
export let withoutIconBackground = false
export let label: IntlString | undefined = undefined
export let title: string | undefined = undefined
@ -31,7 +32,12 @@
<button class="hulyBreadcrumb-container {size}" class:current={isCurrent} on:click>
{#if size === 'large' && icon}
<div class="hulyBreadcrumb-avatar" style:width={iconWidth ?? null} class:withoutIconBackground>
<div
class="hulyBreadcrumb-avatar"
style:width={iconWidth ?? null}
style:margin={iconMargin}
class:withoutIconBackground
>
<Icon {icon} size={'small'} {iconProps} />
</div>
{/if}

View File

@ -130,6 +130,7 @@ export interface BreadcrumbItem {
icon?: Asset | AnySvelteComponent | ComponentType
iconProps?: any
iconWidth?: string
iconMargin?: string
withoutIconBackground?: boolean
label?: IntlString
title?: string

View File

@ -52,6 +52,7 @@
"UnPinAttachment": "Mark less important",
"FilterAttachments": "Attachments",
"RemovedAttachment": "Removed attachment",
"ContentType": "Content type"
"ContentType": "Content type",
"OpenInWindow": "Open in window"
}
}

View File

@ -52,6 +52,7 @@
"UnPinAttachment": "Marcar como menos importante",
"FilterAttachments": "Adjuntos",
"RemovedAttachment": "Adjunto eliminado",
"ContentType": "Tipo de contenido"
"ContentType": "Tipo de contenido",
"OpenInWindow": "Abrir en ventana"
}
}

View File

@ -52,6 +52,7 @@
"UnPinAttachment": "Marquer comme moins important",
"FilterAttachments": "Pièces jointes",
"RemovedAttachment": "Pièce jointe supprimée",
"ContentType": "Type de contenu"
"ContentType": "Type de contenu",
"OpenInWindow": "Ouvrir dans une fenêtre"
}
}

View File

@ -52,6 +52,7 @@
"UnPinAttachment": "Marcar como menos importante",
"FilterAttachments": "Anexos",
"RemovedAttachment": "Anexo removido",
"ContentType": "Tipo de conteúdo"
"ContentType": "Tipo de conteúdo",
"OpenInWindow": "Abrir numa janela"
}
}

View File

@ -52,6 +52,7 @@
"UnPinAttachment": "Убрать пометку важное",
"FilterAttachments": "Вложения",
"RemovedAttachment": "Удалил(а) вложение",
"ContentType": "Тип контента"
"ContentType": "Тип контента",
"OpenInWindow": "Открыть в окне"
}
}

View File

@ -52,6 +52,7 @@
"UnPinAttachment": "标记为不重要",
"FilterAttachments": "附件",
"RemovedAttachment": "移除附件",
"ContentType": "内容类型"
"ContentType": "内容类型",
"OpenInWindow": "在新窗口中打开"
}
}

View File

@ -38,8 +38,8 @@
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/analytics": "^0.6.0",
"@hcengineering/activity": "^0.6.0",
"@hcengineering/analytics": "^0.6.0",
"@hcengineering/attachment": "^0.6.14",
"@hcengineering/contact": "^0.6.24",
"@hcengineering/core": "^0.6.32",
@ -55,6 +55,7 @@
"@hcengineering/uploader": "^0.6.0",
"@hcengineering/view": "^0.6.13",
"@hcengineering/view-resources": "^0.6.0",
"@hcengineering/workbench": "^0.6.16",
"filesize": "^8.0.3",
"svelte": "^4.2.12"
},

View File

@ -25,10 +25,12 @@
} from '@hcengineering/presentation'
import { IconMoreH, Menu, Action as UIAction, closeTooltip, showPopup, tooltip } from '@hcengineering/ui'
import view, { Action } from '@hcengineering/view'
import workbench from '@hcengineering/workbench'
import AttachmentAction from './AttachmentAction.svelte'
import FileDownload from './icons/FileDownload.svelte'
import attachmentPlugin from '../plugin'
import { openAttachmentInSidebar } from '../utils'
export let attachment: WithLookup<Attachment>
export let isSaved = false
@ -95,6 +97,13 @@
if (canPreview) {
actions.push(openAction)
}
actions.push({
icon: view.icon.DetailsFilled,
label: workbench.string.OpenInSidebar,
action: async () => {
await openAttachmentInSidebar(attachment)
}
})
actions.push({
label: saveAttachmentAction.label,
icon: saveAttachmentAction.icon,

View File

@ -17,11 +17,10 @@
import type { Attachment } from '@hcengineering/attachment'
import core, { type WithLookup } from '@hcengineering/core'
import presentation, {
FilePreviewPopup,
canPreviewFile,
FilePreviewPopup,
getBlobRef,
getFileUrl,
getPreviewAlignment,
previewTypes,
sizeToWidth
} from '@hcengineering/presentation'
@ -29,7 +28,7 @@
import { permissionsStore } from '@hcengineering/view-resources'
import filesize from 'filesize'
import { createEventDispatcher } from 'svelte'
import { getType } from '../utils'
import { getType, openAttachmentInSidebar } from '../utils'
import AttachmentName from './AttachmentName.svelte'
@ -70,7 +69,7 @@
canPreview = false
}
function clickHandler (e: MouseEvent): void {
async function clickHandler (e: MouseEvent): Promise<void> {
if (value === undefined || !canPreview) return
e.preventDefault()
@ -80,6 +79,7 @@
return
}
closeTooltip()
if (value.type.startsWith('image/') || value.type.startsWith('video/') || value.type.startsWith('audio/')) {
showPopup(
FilePreviewPopup,
{
@ -88,8 +88,11 @@
name: value.name,
metadata: value.metadata
},
getPreviewAlignment(value.type)
'centered'
)
} else {
await openAttachmentInSidebar(value)
}
}
function middleClickHandler (e: MouseEvent): void {

View File

@ -14,13 +14,13 @@
// limitations under the License.
-->
<script lang="ts">
import type { Attachment } from '@hcengineering/attachment'
import { Attachment } from '@hcengineering/attachment'
import { FilePreviewPopup } from '@hcengineering/presentation'
import { closeTooltip, showPopup } from '@hcengineering/ui'
import { ListSelectionProvider } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import { WithLookup } from '@hcengineering/core'
import type { WithLookup } from '@hcengineering/core'
import { AttachmentImageSize } from '../types'
import { getType } from '../utils'
import AttachmentActions from './AttachmentActions.svelte'

View File

@ -0,0 +1,41 @@
<!--
// 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 type { Blob, Ref } from '@hcengineering/core'
import { BlobMetadata } from '@hcengineering/presentation'
import { Button, closePopup, closeTooltip, IconDetailsFilled } from '@hcengineering/ui'
import workbench from '@hcengineering/workbench'
import { openFilePreviewInSidebar } from '../utils'
export let file: Ref<Blob> | undefined
export let name: string
export let contentType: string
export let metadata: BlobMetadata | undefined
</script>
{#if file}
<Button
icon={IconDetailsFilled}
kind="icon"
on:click={() => {
if (file === undefined) return
closeTooltip()
closePopup()
void openFilePreviewInSidebar(file, name, contentType, metadata)
}}
showTooltip={{ label: workbench.string.OpenInSidebar }}
/>
{/if}

View File

@ -0,0 +1,68 @@
<!--
// 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 workbench, { Widget, WidgetTab } from '@hcengineering/workbench'
import { FilePreview, DownloadFileButton, FilePreviewPopup, FileTypeIcon } from '@hcengineering/presentation'
import { Breadcrumbs, Button, closeTooltip, Header, IconOpen, showPopup } from '@hcengineering/ui'
import { getResource } from '@hcengineering/platform'
import view from '@hcengineering/view'
import attachment from '../plugin'
export let widget: Widget
export let tab: WidgetTab
$: file = tab.data?.file
$: fileName = tab.data?.name ?? ''
$: contentType = tab.data?.contentType
$: metadata = tab.data?.metadata
async function closeTab (): Promise<void> {
const fn = await getResource(workbench.function.CloseWidgetTab)
await fn(widget, tab.id)
}
</script>
<Header
allowFullsize={false}
type="type-aside"
hideBefore={true}
hideActions={false}
hideDescription={true}
adaptive="disabled"
closeOnEscape={false}
on:close={closeTab}
>
<Breadcrumbs
items={[{ title: fileName, icon: FileTypeIcon, iconProps: { name: fileName }, iconMargin: '0 0.5rem 0 0' }]}
currentOnly
/>
<svelte:fragment slot="actions">
<DownloadFileButton name={fileName} {file} />
<Button
icon={view.icon.Open}
kind="icon"
showTooltip={{ label: attachment.string.OpenInWindow }}
on:click={() => {
closeTooltip()
showPopup(FilePreviewPopup, { file, name: fileName, contentType, metadata }, 'centered')
}}
/>
</svelte:fragment>
</Header>
{#if file}
<FilePreview {file} {contentType} name={fileName} {metadata} />
{/if}

View File

@ -40,6 +40,8 @@ import AttachmentsUpdatedMessage from './components/activity/AttachmentsUpdatedM
import IconAttachment from './components/icons/Attachment.svelte'
import FileDownload from './components/icons/FileDownload.svelte'
import IconUploadDuo from './components/icons/UploadDuo.svelte'
import PreviewWidget from './components/PreviewWidget.svelte'
import PreviewPopupActions from './components/PreviewPopupActions.svelte'
export * from './types'
@ -256,7 +258,9 @@ export default async (): Promise<Resources> => ({
Attachments,
FileBrowser,
Photos,
PDFViewer
PDFViewer,
PreviewWidget,
PreviewPopupActions
},
activity: {
AttachmentsUpdatedMessage

View File

@ -14,7 +14,7 @@
// limitations under the License.
//
import { type Attachment } from '@hcengineering/attachment'
import { type BlobMetadata, type Attachment } from '@hcengineering/attachment'
import {
type Blob,
type Class,
@ -24,8 +24,10 @@ import {
type Ref,
type Space
} from '@hcengineering/core'
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
import { type FileOrBlob, getFileMetadata, uploadFile } from '@hcengineering/presentation'
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
import { type FileOrBlob, getClient, getFileMetadata, uploadFile } from '@hcengineering/presentation'
import workbench, { type WidgetTab } from '@hcengineering/workbench'
import view from '@hcengineering/view'
import attachment from './plugin'
@ -98,3 +100,38 @@ export function getType (type: string): 'image' | 'text' | 'json' | 'video' | 'a
return 'other'
}
export async function openAttachmentInSidebar (value: Attachment): Promise<void> {
await openFilePreviewInSidebar(value.file, value.name, value.type, value.metadata)
}
export async function openFilePreviewInSidebar (
file: Ref<Blob>,
name: string,
contentType: string,
metadata?: BlobMetadata
): Promise<void> {
const client = getClient()
const widget = client.getModel().findAllSync(workbench.class.Widget, { _id: attachment.ids.PreviewWidget })[0]
const createFn = await getResource(workbench.function.CreateWidgetTab)
let icon = attachment.icon.Attachment
if (contentType.startsWith('image/')) {
icon = view.icon.Image
} else if (contentType.startsWith('video/')) {
icon = view.icon.Video
} else if (contentType.startsWith('audio/')) {
icon = view.icon.Audio
} else {
icon = view.icon.File
}
const tab: WidgetTab = {
id: file,
icon,
name,
widget: attachment.ids.PreviewWidget,
data: { file, name, contentType, metadata }
}
await createFn(widget, tab, true)
}

View File

@ -40,6 +40,7 @@
"@hcengineering/platform": "^0.6.11",
"@hcengineering/ui": "^0.6.15",
"@hcengineering/core": "^0.6.32",
"@hcengineering/workbench": "^0.6.16",
"@hcengineering/preference": "^0.6.13"
},
"repository": "https://github.com/hcengineering/platform",

View File

@ -19,6 +19,7 @@ import type { Asset, Plugin } from '@hcengineering/platform'
import { IntlString, plugin, Resource } from '@hcengineering/platform'
import type { Preference } from '@hcengineering/preference'
import { AnyComponent } from '@hcengineering/ui'
import { Widget } from '@hcengineering/workbench'
export * from './analytics'
@ -87,6 +88,9 @@ export default plugin(attachmentId, {
UploadFile: '' as Resource<(file: File) => Promise<Ref<Blob>>>,
DeleteFile: '' as Resource<(id: string) => Promise<void>>
},
ids: {
PreviewWidget: '' as Ref<Widget>
},
string: {
Files: '' as IntlString,
NoFiles: '' as IntlString,
@ -106,6 +110,7 @@ export default plugin(attachmentId, {
FileBrowserTypeFilterPDFs: '' as IntlString,
DeleteFile: '' as IntlString,
Attachments: '' as IntlString,
FileBrowser: '' as IntlString
FileBrowser: '' as IntlString,
OpenInWindow: '' as IntlString
}
})

View File

@ -196,4 +196,23 @@
<symbol id="undo" viewBox="0 0 32 32">
<path d="M20 10H7.8149L11.4023 6.4141L10 5L4 11L10 17L11.4023 15.5854L7.8179 12H20C21.5913 12 23.1174 12.6321 24.2426 13.7574C25.3679 14.8826 26 16.4087 26 18C26 19.5913 25.3679 21.1174 24.2426 22.2426C23.1174 23.3679 21.5913 24 20 24H13C12.4477 24 12 24.4477 12 25C12 25.5523 12.4477 26 13 26H20C22.1217 26 24.1566 25.1571 25.6569 23.6569C27.1571 22.1566 28 20.1217 28 18C28 15.8783 27.1571 13.8434 25.6569 12.3431C24.1566 10.8429 22.1217 10 20 10Z"/>
</symbol>
<symbol id="video" viewBox="0 0 32 32">
<path d="M13 15V23L20 19L13 15Z" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9 4C7.89543 4 7 4.89543 7 6V26C7 27.1046 7.89543 28 9 28H23C24.1046 28 25 27.1046 25 26V12H21C18.7909 12 17 10.2091 17 8V4H9ZM19 4.41421V8C19 9.10457 19.8954 10 21 10H24.5858L19 4.41421ZM5 6C5 3.79086 6.79086 2 9 2H18.5858C19.1162 2 19.6249 2.21071 20 2.58579L26.4142 9C26.7893 9.37507 27 9.88378 27 10.4142V26C27 28.2091 25.2091 30 23 30H9C6.79086 30 5 28.2091 5 26V6Z"
/>
</symbol>
<symbol id="audio" viewBox="0 0 32 32">
<path d="M13 15V23L20 19L13 15Z" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9 4C7.89543 4 7 4.89543 7 6V26C7 27.1046 7.89543 28 9 28H23C24.1046 28 25 27.1046 25 26V12H21C18.7909 12 17 10.2091 17 8V4H9ZM19 4.41421V8C19 9.10457 19.8954 10 21 10H24.5858L19 4.41421ZM5 6C5 3.79086 6.79086 2 9 2H18.5858C19.1162 2 19.6249 2.21071 20 2.58579L26.4142 9C26.7893 9.37507 27 9.88378 27 10.4142V26C27 28.2091 25.2091 30 23 30H9C6.79086 30 5 28.2091 5 26V6Z"
/>
</symbol>
<symbol id="file" viewBox="0 0 32 32">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 4C7.89543 4 7 4.89543 7 6V26C7 27.1046 7.89543 28 9 28H23C24.1046 28 25 27.1046 25 26V12H21C18.7909 12 17 10.2091 17 8V4H9ZM19 4.41421V8C19 9.10457 19.8954 10 21 10H24.5858L19 4.41421ZM5 6C5 3.79086 6.79086 2 9 2H18.5858C19.1162 2 19.6249 2.21071 20 2.58579L26.4142 9C26.7893 9.37507 27 9.88378 27 10.4142V26C27 28.2091 25.2091 30 23 30H9C6.79086 30 5 28.2091 5 26V6ZM10 17C10 16.4477 10.4477 16 11 16H21C21.5523 16 22 16.4477 22 17C22 17.5523 21.5523 18 21 18H11C10.4477 18 10 17.5523 10 17ZM10 23C10 22.4477 10.4477 22 11 22H21C21.5523 22 22 22.4477 22 23C22 23.5523 21.5523 24 21 24H11C10.4477 24 10 23.5523 10 23Z" />
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -58,5 +58,8 @@ loadMetadata(view.icon, {
Copy: `${icons}#copy`,
DetailsFilled: `${icons}#details-filled`,
Translate: `${icons}#translate`,
Undo: `${icons}#undo`
Undo: `${icons}#undo`,
Video: `${icons}#video`,
Audio: `${icons}#audio`,
File: `${icons}#file`
})

View File

@ -254,7 +254,10 @@ const view = plugin(viewId, {
TodoList: '' as Asset,
DetailsFilled: '' as Asset,
Translate: '' as Asset,
Undo: '' as Asset
Undo: '' as Asset,
Video: '' as Asset,
Audio: '' as Asset,
File: '' as Asset
},
category: {
General: '' as Ref<ActionCategory>,

View File

@ -26,6 +26,7 @@ import ServerManager from './components/ServerManager.svelte'
import WorkbenchTabs from './components/WorkbenchTabs.svelte'
import { isAdminUser } from '@hcengineering/presentation'
import { canCloseTab, closeTab, pinTab, unpinTab } from './workbench'
import { closeWidgetTab, createWidgetTab } from './sidebar'
async function hasArchiveSpaces (spaces: Space[]): Promise<boolean> {
return spaces.find((sp) => sp.archived) !== undefined
@ -54,7 +55,9 @@ export default async (): Promise<Resources> => ({
function: {
HasArchiveSpaces: hasArchiveSpaces,
IsOwner: async (docs: Space[]) => getCurrentAccount().role === AccountRole.Owner || isAdminUser(),
CanCloseTab: canCloseTab
CanCloseTab: canCloseTab,
CreateWidgetTab: createWidgetTab,
CloseWidgetTab: closeWidgetTab
},
actionImpl: {
Navigate: doNavigate,

View File

@ -257,6 +257,10 @@ export default plugin(workbenchId, {
WorkbenchExtensions: '' as ComponentExtensionId,
WorkbenchTabExtensions: '' as ComponentExtensionId
},
function: {
CreateWidgetTab: '' as Resource<(widget: Widget, tab: WidgetTab, newTab: boolean) => Promise<void>>,
CloseWidgetTab: '' as Resource<(widget: Widget, tab: string) => Promise<void>>
},
actionImpl: {
Navigate: '' as ViewAction<{
mode: 'app' | 'special' | 'space'