diff --git a/src/hooks/useShell.tsx b/src/hooks/useShell.tsx new file mode 100644 index 0000000000..cedc36fb43 --- /dev/null +++ b/src/hooks/useShell.tsx @@ -0,0 +1,116 @@ +// Imports +import React, { createContext, useContext, useEffect, useState } from 'react' +import { useNavigate } from 'react-router-dom' + +import { BarLeft } from 'cozy-bar' +import type { File } from '@/components/FolderPicker/types' + +// Types +interface ShellContextType { + runsInShell: boolean + setRunsInShell: React.Dispatch> + selectedFile: string | null + setSelectedFile: React.Dispatch> + openFileInParent: (file: File) => void +} + +// Context +const ShellContext = createContext(undefined) + +export const ShellProvider = ({ + children +}: { + children: React.ReactNode +}): JSX.Element => { + const navigate = useNavigate() + + const [runsInShell, setRunsInShell] = useState(false) + const [selectedFile, setSelectedFile] = useState(null) + + useEffect(() => { + if (window.top) { + window.top.postMessage('loaded', '*') + } + + window.onmessage = function (e: MessageEvent): void { + if (e.data == undefined || e.data == null || typeof e.data !== 'string') + return + if (e.data === 'inShell:true') { + setRunsInShell(true) + } + if (e.data.startsWith('selectedFile:')) { + const fileId = e.data.split('selectedFile:')[1].trim() + setSelectedFile(fileId) + } + if (e.data.startsWith('openFolder:')) { + const folderId = e.data.split('openFolder:')[1].trim() + navigate(`/folder/${folderId}`) + } + } + }, [navigate]) + + if (runsInShell) { + const CSS = ` + .coz-bar-container nav, .coz-bar-container a { + display: none !important; + } + + .coz-bar-container button[aria-label="Rechercher"] { + margin-right: -12px; + } + ` + + const style = document.createElement('style') + style.type = 'text/css' + style.appendChild(document.createTextNode(CSS)) + document.head.appendChild(style) + } + + const openFileInParent = (file: File): void => { + if ('metadata' in file && window.top) { + const id = file.metadata.externalId || '' + window.top.postMessage('openFile:' + id, '*') + } + } + + const contextValue: ShellContextType = { + runsInShell, + setRunsInShell, + selectedFile, + setSelectedFile, + openFileInParent + } + + return ( + + {runsInShell && ( + +
+
+ )} + + {children} +
+ ) +} + +// Hook +export const useShell = (): ShellContextType => { + const context = useContext(ShellContext) + if (!context) { + return { + runsInShell: false, + setRunsInShell: (): void => { + return + }, + selectedFile: null, + setSelectedFile: (): void => { + return + }, + openFileInParent: (): void => { + return + } + } + } + return context +} diff --git a/src/modules/filelist/File.jsx b/src/modules/filelist/File.jsx index d5976a4680..1771e112ad 100644 --- a/src/modules/filelist/File.jsx +++ b/src/modules/filelist/File.jsx @@ -24,6 +24,7 @@ import { import styles from '@/styles/filelist.styl' import { useClipboardContext } from '@/contexts/ClipboardProvider' +import { useShell } from '@/hooks/useShell' import { useViewSwitcherContext } from '@/lib/ViewSwitcherContext' import { ActionMenuWithHeader } from '@/modules/actionmenu/ActionMenuWithHeader' import { getContextMenuActions } from '@/modules/actions/helpers' @@ -74,6 +75,7 @@ const File = ({ onToggleSelect }) => { const { viewType } = useViewSwitcherContext() + const { runsInShell, selectedFile } = useShell() const [actionMenuVisible, setActionMenuVisible] = useState(false) const filerowMenuToggleRef = useRef() @@ -102,9 +104,11 @@ const File = ({ const isCut = isItemCut(attributes._id) const selected = isItemSelected(attributes._id) + const selectedInShell = + runsInShell && selectedFile && selectedFile === attributes._id const filContentRowSelected = cx(styles['fil-content-row'], { - [styles['fil-content-row-selected']]: selected, + [styles['fil-content-row-selected']]: selected || selectedInShell, [styles['fil-content-row-actioned']]: actionMenuVisible, [styles['fil-content-row-disabled']]: styleDisabled || isCut }) diff --git a/src/modules/navigation/hooks/useFileLink.tsx b/src/modules/navigation/hooks/useFileLink.tsx index f56e0f3dea..c485a128ce 100644 --- a/src/modules/navigation/hooks/useFileLink.tsx +++ b/src/modules/navigation/hooks/useFileLink.tsx @@ -15,6 +15,7 @@ import { import { usePublicContext } from '@/modules/public/PublicProvider' import { getFolderPath } from '@/modules/routeUtils' import { isOfficeEnabled as computeOfficeEnabled } from '@/modules/views/OnlyOffice/helpers' +import { useShell } from '@/hooks/useShell' export interface LinkResult { app: string @@ -55,6 +56,7 @@ const useFileLink = ( const { isDesktop } = useBreakpoints() const isOfficeEnabled = computeOfficeEnabled(isDesktop) const { isPublic } = usePublicContext() + const { runsInShell, openFileInParent } = useShell() // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment const cozyUrl = client?.getStackClient().uri as string @@ -128,13 +130,28 @@ const useFileLink = ( shouldBeOpenedInNewTab ) { window.open(href, '_blank') + } else if (runsInShell && file.type && file.type === 'file') { + if (file.name && file.name.endsWith('.docs-note')) { + openFileInParent(file) + } else { + window.open(href, '_blank') + } } else if (app === 'drive') { navigate(to) } else { window.location.href = href } }, - [app, href, navigate, to, shouldBeOpenedInNewTab] + [ + app, + href, + navigate, + to, + shouldBeOpenedInNewTab, + runsInShell, + file, + openFileInParent + ] ) return { diff --git a/src/targets/browser/index.jsx b/src/targets/browser/index.jsx index 3212c74668..d1887bad75 100644 --- a/src/targets/browser/index.jsx +++ b/src/targets/browser/index.jsx @@ -24,11 +24,14 @@ import AppRoute from '@/modules/navigation/AppRoute' // ambient styles import styles from '@/styles/main.styl' // eslint-disable-line no-unused-vars +import { ShellProvider } from '@/hooks/useShell' const AppComponent = props => ( - + + + )