feat: release Files view (#1236)
This commit is contained in:
@@ -26,7 +26,6 @@ import { makeObservable, observable } from 'mobx'
|
|||||||
import { PanelResizedData } from '@/Types/PanelResizedData'
|
import { PanelResizedData } from '@/Types/PanelResizedData'
|
||||||
import { WebAppEvent } from './WebAppEvent'
|
import { WebAppEvent } from './WebAppEvent'
|
||||||
import { isDesktopApplication } from '@/Utils'
|
import { isDesktopApplication } from '@/Utils'
|
||||||
import { storage, StorageKey } from '@/Services/LocalStorage'
|
|
||||||
|
|
||||||
type WebServices = {
|
type WebServices = {
|
||||||
viewControllerManager: ViewControllerManager
|
viewControllerManager: ViewControllerManager
|
||||||
@@ -63,7 +62,7 @@ export class WebApplication extends SNApplication {
|
|||||||
defaultHost: defaultSyncServerHost,
|
defaultHost: defaultSyncServerHost,
|
||||||
appVersion: deviceInterface.appVersion,
|
appVersion: deviceInterface.appVersion,
|
||||||
webSocketUrl: webSocketUrl,
|
webSocketUrl: webSocketUrl,
|
||||||
supportsFileNavigation: storage.get(StorageKey.FilesNavigationEnabled) || false,
|
supportsFileNavigation: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
|
|||||||
@@ -37,12 +37,20 @@ const FileListItem: FunctionComponent<DisplayableListItemProps> = ({
|
|||||||
|
|
||||||
const openContextMenu = useCallback(
|
const openContextMenu = useCallback(
|
||||||
async (posX: number, posY: number) => {
|
async (posX: number, posY: number) => {
|
||||||
const { didSelect } = await selectionController.selectItem(item.uuid)
|
let shouldOpenContextMenu = selected
|
||||||
if (didSelect) {
|
|
||||||
|
if (!selected) {
|
||||||
|
const { didSelect } = await selectionController.selectItem(item.uuid)
|
||||||
|
if (didSelect) {
|
||||||
|
shouldOpenContextMenu = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldOpenContextMenu) {
|
||||||
openFileContextMenu(posX, posY)
|
openFileContextMenu(posX, posY)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectionController, item.uuid, openFileContextMenu],
|
[selected, selectionController, item.uuid, openFileContextMenu],
|
||||||
)
|
)
|
||||||
|
|
||||||
const onClick = useCallback(async () => {
|
const onClick = useCallback(async () => {
|
||||||
|
|||||||
@@ -41,8 +41,16 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openContextMenu = async (posX: number, posY: number) => {
|
const openContextMenu = async (posX: number, posY: number) => {
|
||||||
const { didSelect } = await selectionController.selectItem(item.uuid, true)
|
let shouldOpenContextMenu = selected
|
||||||
if (didSelect) {
|
|
||||||
|
if (!selected) {
|
||||||
|
const { didSelect } = await selectionController.selectItem(item.uuid)
|
||||||
|
if (didSelect) {
|
||||||
|
shouldOpenContextMenu = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldOpenContextMenu) {
|
||||||
openNoteContextMenu(posX, posY)
|
openNoteContextMenu(posX, posY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
|||||||
const { handleFileAction } = filesController
|
const { handleFileAction } = filesController
|
||||||
|
|
||||||
const hasProtectedFiles = useMemo(() => selectedFiles.some((file) => file.protected), [selectedFiles])
|
const hasProtectedFiles = useMemo(() => selectedFiles.some((file) => file.protected), [selectedFiles])
|
||||||
|
const hasSelectedMultipleFiles = useMemo(() => selectedFiles.length > 1, [selectedFiles.length])
|
||||||
|
|
||||||
|
const totalFileSize = useMemo(
|
||||||
|
() => selectedFiles.map((file) => file.decryptedSize).reduce((prev, next) => prev + next, 0),
|
||||||
|
[selectedFiles],
|
||||||
|
)
|
||||||
|
|
||||||
const onPreview = useCallback(() => {
|
const onPreview = useCallback(() => {
|
||||||
void handleFileAction({
|
void handleFileAction({
|
||||||
@@ -149,16 +155,17 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
|||||||
<span className="text-danger">Delete permanently</span>
|
<span className="text-danger">Delete permanently</span>
|
||||||
</button>
|
</button>
|
||||||
<HorizontalSeparator classes="my-2" />
|
<HorizontalSeparator classes="my-2" />
|
||||||
{selectedFiles.length === 1 && (
|
<div className="px-3 pt-1 pb-0.5 text-xs font-medium text-neutral">
|
||||||
<div className="px-3 pt-1 pb-0.5 text-xs font-medium text-neutral">
|
{!hasSelectedMultipleFiles && (
|
||||||
<div className="mb-1">
|
<div className="mb-1">
|
||||||
<span className="font-semibold">File ID:</span> {selectedFiles[0].uuid}
|
<span className="font-semibold">File ID:</span> {selectedFiles[0].uuid}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
)}
|
||||||
<span className="font-semibold">Size:</span> {formatSizeToReadableString(selectedFiles[0].decryptedSize)}
|
<div>
|
||||||
</div>
|
<span className="font-semibold">{hasSelectedMultipleFiles ? 'Total Size:' : 'Size:'}</span>{' '}
|
||||||
|
{formatSizeToReadableString(totalFileSize)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const ImagePreview: FunctionComponent<Props> = ({ objectUrl }) => {
|
|||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
|
maxWidth: 'none',
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
ref={(imgElement) => {
|
ref={(imgElement) => {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const NotesContextMenu = ({
|
|||||||
return contextMenuOpen ? (
|
return contextMenuOpen ? (
|
||||||
<div
|
<div
|
||||||
ref={contextMenuRef}
|
ref={contextMenuRef}
|
||||||
className="max-h-120 fixed z-dropdown-menu flex min-w-80 max-w-xs flex-col overflow-y-auto rounded bg-default pt-2 shadow-main"
|
className="max-h-120 fixed z-dropdown-menu flex min-w-80 max-w-xs flex-col overflow-y-auto rounded bg-default py-2 shadow-main"
|
||||||
style={{
|
style={{
|
||||||
...contextMenuPosition,
|
...contextMenuPosition,
|
||||||
maxHeight: contextMenuMaxHeight,
|
maxHeight: contextMenuMaxHeight,
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ const NoteAttributes: FunctionComponent<{
|
|||||||
const format = editor?.package_info?.file_type || 'txt'
|
const format = editor?.package_info?.file_type || 'txt'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-3 pt-1.5 pb-2.5 text-xs font-medium text-neutral">
|
<div className="px-3 py-1.5 text-xs font-medium text-neutral">
|
||||||
{typeof words === 'number' && (format === 'txt' || format === 'md') ? (
|
{typeof words === 'number' && (format === 'txt' || format === 'md') ? (
|
||||||
<>
|
<>
|
||||||
<div className="mb-1">
|
<div className="mb-1">
|
||||||
@@ -159,12 +159,15 @@ const NoteSizeWarning: FunctionComponent<{
|
|||||||
note: SNNote
|
note: SNNote
|
||||||
}> = ({ note }) => {
|
}> = ({ note }) => {
|
||||||
return new Blob([note.text]).size > NOTE_SIZE_WARNING_THRESHOLD ? (
|
return new Blob([note.text]).size > NOTE_SIZE_WARNING_THRESHOLD ? (
|
||||||
<div className="bg-warning-faded relative flex items-center px-3 py-3.5">
|
<>
|
||||||
<Icon type="warning" className="mr-3 flex-shrink-0 text-accessory-tint-3" />
|
<HorizontalSeparator classes="my-2" />
|
||||||
<div className="leading-140% max-w-80% select-none text-warning">
|
<div className="bg-warning-faded relative flex items-center px-3 py-3.5">
|
||||||
This note may have trouble syncing to the mobile application due to its size.
|
<Icon type="warning" className="mr-3 flex-shrink-0 text-accessory-tint-3" />
|
||||||
|
<div className="leading-140% max-w-80% select-none text-warning">
|
||||||
|
This note may have trouble syncing to the mobile application due to its size.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ const NotesOptionsPanel = ({
|
|||||||
}}
|
}}
|
||||||
className={`${
|
className={`${
|
||||||
open ? 'flex' : 'hidden'
|
open ? 'flex' : 'hidden'
|
||||||
} max-h-120 slide-down-animation fixed min-w-80 max-w-xs flex-col overflow-y-auto rounded bg-default pt-2 shadow-main transition-transform duration-150`}
|
} max-h-120 slide-down-animation fixed min-w-80 max-w-xs flex-col overflow-y-auto rounded bg-default py-2 shadow-main transition-transform duration-150`}
|
||||||
onBlur={closeOnBlur}
|
onBlur={closeOnBlur}
|
||||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
|||||||
import PreferencesGroup from '../../../PreferencesComponents/PreferencesGroup'
|
import PreferencesGroup from '../../../PreferencesComponents/PreferencesGroup'
|
||||||
import PreferencesSegment from '../../../PreferencesComponents/PreferencesSegment'
|
import PreferencesSegment from '../../../PreferencesComponents/PreferencesSegment'
|
||||||
import LabsFeature from './LabsFeature'
|
import LabsFeature from './LabsFeature'
|
||||||
import { StorageKey, useLocalStorageItem } from '@/Services/LocalStorage'
|
|
||||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||||
|
|
||||||
type ExperimentalFeatureItem = {
|
type ExperimentalFeatureItem = {
|
||||||
@@ -25,7 +24,6 @@ type Props = {
|
|||||||
|
|
||||||
const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
||||||
const [experimentalFeatures, setExperimentalFeatures] = useState<ExperimentalFeatureItem[]>([])
|
const [experimentalFeatures, setExperimentalFeatures] = useState<ExperimentalFeatureItem[]>([])
|
||||||
const [isFilesNavigationEnabled, setFilesNavigation] = useLocalStorageItem(StorageKey.FilesNavigationEnabled)
|
|
||||||
|
|
||||||
const reloadExperimentalFeatures = useCallback(() => {
|
const reloadExperimentalFeatures = useCallback(() => {
|
||||||
const experimentalFeatures = application.features.getExperimentalFeatures().map((featureIdentifier) => {
|
const experimentalFeatures = application.features.getExperimentalFeatures().map((featureIdentifier) => {
|
||||||
@@ -47,17 +45,6 @@ const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
|||||||
|
|
||||||
const premiumModal = usePremiumModal()
|
const premiumModal = usePremiumModal()
|
||||||
|
|
||||||
const toggleFilesNavigation = useCallback(() => {
|
|
||||||
const isEntitled = application.features.getFeatureStatus(FeatureIdentifier.Files) === FeatureStatus.Entitled
|
|
||||||
|
|
||||||
if (!isEntitled) {
|
|
||||||
premiumModal.activate('Files navigation')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setFilesNavigation(!isFilesNavigationEnabled)
|
|
||||||
}, [application.features, isFilesNavigationEnabled, premiumModal, setFilesNavigation])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PreferencesGroup>
|
<PreferencesGroup>
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
@@ -88,13 +75,6 @@ const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
<HorizontalSeparator classes="mt-2.5 mb-3" />
|
|
||||||
<LabsFeature
|
|
||||||
name="Files navigation"
|
|
||||||
description={'Enables a "Files" view which allows for better files navigation. Requires reload.'}
|
|
||||||
toggleFeature={toggleFilesNavigation}
|
|
||||||
isEnabled={!!isFilesNavigationEnabled}
|
|
||||||
/>
|
|
||||||
{experimentalFeatures.length === 0 && (
|
{experimentalFeatures.length === 0 && (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
|
|||||||
Reference in New Issue
Block a user