fix: show files option in linking menu even if not entitled
This commit is contained in:
@@ -39,6 +39,7 @@ import { AndroidBackHandler } from '@/NativeMobileWeb/AndroidBackHandler'
|
|||||||
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
||||||
import { setCustomViewportHeight } from '@/setViewportHeightWithFallback'
|
import { setCustomViewportHeight } from '@/setViewportHeightWithFallback'
|
||||||
import { WebServices } from './WebServices'
|
import { WebServices } from './WebServices'
|
||||||
|
import { FeatureName } from '@/Controllers/FeatureName'
|
||||||
|
|
||||||
export type WebEventObserver = (event: WebAppEvent, data?: unknown) => void
|
export type WebEventObserver = (event: WebAppEvent, data?: unknown) => void
|
||||||
|
|
||||||
@@ -347,6 +348,14 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
|||||||
return this.hasValidSubscription()
|
return this.hasValidSubscription()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get entitledToFiles(): boolean {
|
||||||
|
return this.getViewControllerManager().featuresController.entitledToFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
showPremiumModal(featureName: FeatureName): void {
|
||||||
|
void this.getViewControllerManager().featuresController.showPremiumAlert(featureName)
|
||||||
|
}
|
||||||
|
|
||||||
hasValidSubscription(): boolean {
|
hasValidSubscription(): boolean {
|
||||||
return this.getViewControllerManager().subscriptionController.hasValidSubscription()
|
return this.getViewControllerManager().subscriptionController.hasValidSubscription()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { useFileDragNDrop } from '../FileDragNDropProvider/FileDragNDropProvider
|
|||||||
import { LinkingController } from '@/Controllers/LinkingController'
|
import { LinkingController } from '@/Controllers/LinkingController'
|
||||||
import DailyContentList from './Daily/DailyContentList'
|
import DailyContentList from './Daily/DailyContentList'
|
||||||
import { ListableContentItem } from './Types/ListableContentItem'
|
import { ListableContentItem } from './Types/ListableContentItem'
|
||||||
|
import { FeatureName } from '@/Controllers/FeatureName'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
accountMenuController: AccountMenuController
|
accountMenuController: AccountMenuController
|
||||||
@@ -123,6 +124,11 @@ const ContentListView: FunctionComponent<Props> = ({
|
|||||||
|
|
||||||
const addNewItem = useCallback(async () => {
|
const addNewItem = useCallback(async () => {
|
||||||
if (isFilesSmartView) {
|
if (isFilesSmartView) {
|
||||||
|
if (!application.entitledToFiles) {
|
||||||
|
application.showPremiumModal(FeatureName.Files)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (StreamingFileReader.available()) {
|
if (StreamingFileReader.available()) {
|
||||||
void filesController.uploadNewFile()
|
void filesController.uploadNewFile()
|
||||||
return
|
return
|
||||||
@@ -133,7 +139,7 @@ const ContentListView: FunctionComponent<Props> = ({
|
|||||||
await createNewNote()
|
await createNewNote()
|
||||||
toggleAppPane(AppPaneId.Editor)
|
toggleAppPane(AppPaneId.Editor)
|
||||||
}
|
}
|
||||||
}, [isFilesSmartView, filesController, createNewNote, toggleAppPane])
|
}, [isFilesSmartView, filesController, createNewNote, toggleAppPane, application])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ const ContentListHeader = ({
|
|||||||
}, [OptionsMenu, AddButton, FolderName])
|
}, [OptionsMenu, AddButton, FolderName])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="section-title-bar-header items-start gap-1 overflow-hidden">
|
<div className="section-title-bar-header items-start gap-1">
|
||||||
{!isTablet && PhoneAndDesktopLayout}
|
{!isTablet && PhoneAndDesktopLayout}
|
||||||
{isTablet && TabletLayout}
|
{isTablet && TabletLayout}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ const FileDragNDropProvider = ({ application, children, featuresController, file
|
|||||||
|
|
||||||
resetState()
|
resetState()
|
||||||
|
|
||||||
if (!featuresController.hasFiles) {
|
if (!featuresController.entitledToFiles) {
|
||||||
premiumModal.activate('Files')
|
premiumModal.activate('Files')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -217,7 +217,7 @@ const FileDragNDropProvider = ({ application, children, featuresController, file
|
|||||||
dragCounter.current = 0
|
dragCounter.current = 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[application, featuresController.hasFiles, filesController, premiumModal, resetState],
|
[application, featuresController.entitledToFiles, filesController, premiumModal, resetState],
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,156 +1,17 @@
|
|||||||
|
import { FeatureName } from '@/Controllers/FeatureName'
|
||||||
import { FeaturesController } from '@/Controllers/FeaturesController'
|
import { FeaturesController } from '@/Controllers/FeaturesController'
|
||||||
import { FilesController } from '@/Controllers/FilesController'
|
import { FilesController } from '@/Controllers/FilesController'
|
||||||
import { LinkingController } from '@/Controllers/LinkingController'
|
import { LinkingController } from '@/Controllers/LinkingController'
|
||||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||||
import { formatDateForContextMenu } from '@/Utils/DateUtils'
|
|
||||||
import { getIconForItem } from '@/Utils/Items/Icons/getIconForItem'
|
|
||||||
import { getLinkingSearchResults } from '@/Utils/Items/Search/getSearchResults'
|
import { getLinkingSearchResults } from '@/Utils/Items/Search/getSearchResults'
|
||||||
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
|
|
||||||
import { formatSizeToReadableString } from '@standardnotes/filepicker'
|
|
||||||
import { FileItem } from '@standardnotes/snjs'
|
|
||||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { ChangeEventHandler, useEffect, useRef, useState } from 'react'
|
import { ChangeEventHandler, useEffect, useRef, useState } from 'react'
|
||||||
import { useApplication } from '../ApplicationView/ApplicationProvider'
|
import { useApplication } from '../ApplicationView/ApplicationProvider'
|
||||||
import { PopoverFileItemActionType } from '../AttachedFilesPopover/PopoverFileItemAction'
|
|
||||||
import ClearInputButton from '../ClearInputButton/ClearInputButton'
|
import ClearInputButton from '../ClearInputButton/ClearInputButton'
|
||||||
import Icon from '../Icon/Icon'
|
import Icon from '../Icon/Icon'
|
||||||
import DecoratedInput from '../Input/DecoratedInput'
|
import DecoratedInput from '../Input/DecoratedInput'
|
||||||
import MenuItem from '../Menu/MenuItem'
|
|
||||||
import { MenuItemType } from '../Menu/MenuItemType'
|
|
||||||
import Popover from '../Popover/Popover'
|
|
||||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
|
||||||
import LinkedFileMenuOptions from './LinkedFileMenuOptions'
|
|
||||||
import LinkedItemMeta from './LinkedItemMeta'
|
|
||||||
import LinkedItemSearchResults from './LinkedItemSearchResults'
|
import LinkedItemSearchResults from './LinkedItemSearchResults'
|
||||||
|
import { LinkedItemsSectionItem } from './LinkedItemsSectionItem'
|
||||||
const LinkedItemsSectionItem = ({
|
|
||||||
activateItem,
|
|
||||||
item,
|
|
||||||
searchQuery,
|
|
||||||
unlinkItem,
|
|
||||||
handleFileAction,
|
|
||||||
}: {
|
|
||||||
activateItem: LinkingController['activateItem']
|
|
||||||
item: LinkableItem
|
|
||||||
searchQuery?: string
|
|
||||||
unlinkItem: () => void
|
|
||||||
handleFileAction: FilesController['handleFileAction']
|
|
||||||
}) => {
|
|
||||||
const menuButtonRef = useRef<HTMLButtonElement>(null)
|
|
||||||
const application = useApplication()
|
|
||||||
|
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
|
||||||
const toggleMenu = () => setIsMenuOpen((open) => !open)
|
|
||||||
|
|
||||||
const [isRenamingFile, setIsRenamingFile] = useState(false)
|
|
||||||
|
|
||||||
const [icon, className] = getIconForItem(item, application)
|
|
||||||
const title = item.title ?? ''
|
|
||||||
|
|
||||||
const renameFile = async (name: string) => {
|
|
||||||
if (!(item instanceof FileItem)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await handleFileAction({
|
|
||||||
type: PopoverFileItemActionType.RenameFile,
|
|
||||||
payload: {
|
|
||||||
file: item,
|
|
||||||
name: name,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
setIsRenamingFile(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative flex items-center justify-between">
|
|
||||||
{isRenamingFile && item instanceof FileItem ? (
|
|
||||||
<div className="flex flex-grow items-center gap-4 py-2 pl-3 pr-12">
|
|
||||||
<Icon type={icon} className={classNames('flex-shrink-0', className)} />
|
|
||||||
<input
|
|
||||||
className="min-w-0 flex-grow text-sm"
|
|
||||||
defaultValue={title}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === KeyboardKey.Escape) {
|
|
||||||
setIsRenamingFile(false)
|
|
||||||
} else if (event.key === KeyboardKey.Enter) {
|
|
||||||
const newTitle = event.currentTarget.value
|
|
||||||
void renameFile(newTitle)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
ref={(node) => {
|
|
||||||
if (node) {
|
|
||||||
node.focus()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
className="flex max-w-full flex-grow items-center justify-between gap-4 py-2 pl-3 pr-12 text-sm hover:bg-info-backdrop focus:bg-info-backdrop"
|
|
||||||
onClick={() => activateItem(item)}
|
|
||||||
onContextMenu={(event) => {
|
|
||||||
event.preventDefault()
|
|
||||||
toggleMenu()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<LinkedItemMeta item={item} searchQuery={searchQuery} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
className="absolute right-3 top-1/2 h-7 w-7 -translate-y-1/2 cursor-pointer rounded-full border-0 bg-transparent p-1 hover:bg-contrast"
|
|
||||||
onClick={toggleMenu}
|
|
||||||
ref={menuButtonRef}
|
|
||||||
>
|
|
||||||
<Icon type="more" className="text-neutral" />
|
|
||||||
</button>
|
|
||||||
<Popover
|
|
||||||
open={isMenuOpen}
|
|
||||||
togglePopover={toggleMenu}
|
|
||||||
anchorElement={menuButtonRef.current}
|
|
||||||
side="bottom"
|
|
||||||
align="center"
|
|
||||||
className="py-2"
|
|
||||||
>
|
|
||||||
<MenuItem
|
|
||||||
type={MenuItemType.IconButton}
|
|
||||||
onClick={() => {
|
|
||||||
unlinkItem()
|
|
||||||
toggleMenu()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon type="link-off" className="mr-2 text-danger" />
|
|
||||||
Unlink
|
|
||||||
</MenuItem>
|
|
||||||
{item instanceof FileItem && (
|
|
||||||
<LinkedFileMenuOptions
|
|
||||||
file={item}
|
|
||||||
closeMenu={toggleMenu}
|
|
||||||
handleFileAction={handleFileAction}
|
|
||||||
setIsRenamingFile={setIsRenamingFile}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<HorizontalSeparator classes="my-2" />
|
|
||||||
<div className="mt-1 px-3 py-1 text-xs font-medium text-neutral">
|
|
||||||
<div className="mb-1">
|
|
||||||
<span className="font-semibold">Created at:</span> {formatDateForContextMenu(item.created_at)}
|
|
||||||
</div>
|
|
||||||
<div className="mb-1">
|
|
||||||
<span className="font-semibold">Modified at:</span> {formatDateForContextMenu(item.userModifiedDate)}
|
|
||||||
</div>
|
|
||||||
<div className="mb-1">
|
|
||||||
<span className="font-semibold">ID:</span> {item.uuid}
|
|
||||||
</div>
|
|
||||||
{item instanceof FileItem && (
|
|
||||||
<div>
|
|
||||||
<span className="font-semibold">Size:</span> {formatSizeToReadableString(item.decryptedSize)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const LinkedItemsPanel = ({
|
const LinkedItemsPanel = ({
|
||||||
linkingController,
|
linkingController,
|
||||||
@@ -178,7 +39,7 @@ const LinkedItemsPanel = ({
|
|||||||
activeItem,
|
activeItem,
|
||||||
} = linkingController
|
} = linkingController
|
||||||
|
|
||||||
const { hasFiles } = featuresController
|
const { entitledToFiles } = featuresController
|
||||||
const application = useApplication()
|
const application = useApplication()
|
||||||
|
|
||||||
const fileInputRef = useRef<HTMLInputElement | null>(null)
|
const fileInputRef = useRef<HTMLInputElement | null>(null)
|
||||||
@@ -214,6 +75,11 @@ const LinkedItemsPanel = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectAndUploadFiles = () => {
|
const selectAndUploadFiles = () => {
|
||||||
|
if (!entitledToFiles) {
|
||||||
|
void featuresController.showPremiumAlert(FeatureName.Files)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!fileInputRef.current) {
|
if (!fileInputRef.current) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -307,37 +173,37 @@ const LinkedItemsPanel = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(!!linkedFiles.length || hasFiles) && (
|
|
||||||
<div>
|
<div>
|
||||||
<div className="mt-3 mb-1 px-3 text-menu-item font-semibold uppercase text-passive-0">Linked Files</div>
|
<div className="mt-3 mb-1 px-3 text-menu-item font-semibold uppercase text-passive-0">Linked Files</div>
|
||||||
<div className="my-1">
|
<div className="my-1">
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
className="absolute top-0 left-0 -z-50 h-px w-px opacity-0"
|
className="absolute top-0 left-0 -z-50 h-px w-px opacity-0"
|
||||||
multiple
|
multiple
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
onChange={handleFileInputChange}
|
onChange={handleFileInputChange}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="flex w-full cursor-pointer items-center gap-3 bg-transparent px-3 py-2 text-left text-base text-text hover:bg-info-backdrop hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
|
||||||
|
onClick={selectAndUploadFiles}
|
||||||
|
>
|
||||||
|
<Icon type="add" />
|
||||||
|
Upload and link file(s)
|
||||||
|
</button>
|
||||||
|
{linkedFiles.map((link) => (
|
||||||
|
<LinkedItemsSectionItem
|
||||||
|
key={link.id}
|
||||||
|
item={link.item}
|
||||||
|
searchQuery={searchQuery}
|
||||||
|
unlinkItem={() => unlinkItemFromSelectedItem(link.item)}
|
||||||
|
activateItem={activateItem}
|
||||||
|
handleFileAction={filesController.handleFileAction}
|
||||||
/>
|
/>
|
||||||
<button
|
))}
|
||||||
className="flex w-full cursor-pointer items-center gap-3 bg-transparent px-3 py-2 text-left text-base text-text hover:bg-info-backdrop hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
|
|
||||||
onClick={selectAndUploadFiles}
|
|
||||||
>
|
|
||||||
<Icon type="add" />
|
|
||||||
Upload and link file(s)
|
|
||||||
</button>
|
|
||||||
{linkedFiles.map((link) => (
|
|
||||||
<LinkedItemsSectionItem
|
|
||||||
key={link.id}
|
|
||||||
item={link.item}
|
|
||||||
searchQuery={searchQuery}
|
|
||||||
unlinkItem={() => unlinkItemFromSelectedItem(link.item)}
|
|
||||||
activateItem={activateItem}
|
|
||||||
handleFileAction={filesController.handleFileAction}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
{!!filesLinkingToActiveItem.length && (
|
{!!filesLinkingToActiveItem.length && (
|
||||||
<div>
|
<div>
|
||||||
<div className="mt-3 mb-1 px-3 text-menu-item font-semibold uppercase text-passive-0">
|
<div className="mt-3 mb-1 px-3 text-menu-item font-semibold uppercase text-passive-0">
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import { FilesController } from '@/Controllers/FilesController'
|
||||||
|
import { LinkingController } from '@/Controllers/LinkingController'
|
||||||
|
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||||
|
import { formatDateForContextMenu } from '@/Utils/DateUtils'
|
||||||
|
import { getIconForItem } from '@/Utils/Items/Icons/getIconForItem'
|
||||||
|
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
|
||||||
|
import { formatSizeToReadableString } from '@standardnotes/filepicker'
|
||||||
|
import { FileItem } from '@standardnotes/snjs'
|
||||||
|
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||||
|
import { useRef, useState } from 'react'
|
||||||
|
import { useApplication } from '../ApplicationView/ApplicationProvider'
|
||||||
|
import { PopoverFileItemActionType } from '../AttachedFilesPopover/PopoverFileItemAction'
|
||||||
|
import Icon from '../Icon/Icon'
|
||||||
|
import MenuItem from '../Menu/MenuItem'
|
||||||
|
import { MenuItemType } from '../Menu/MenuItemType'
|
||||||
|
import Popover from '../Popover/Popover'
|
||||||
|
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||||
|
import LinkedFileMenuOptions from './LinkedFileMenuOptions'
|
||||||
|
import LinkedItemMeta from './LinkedItemMeta'
|
||||||
|
|
||||||
|
export const LinkedItemsSectionItem = ({
|
||||||
|
activateItem,
|
||||||
|
item,
|
||||||
|
searchQuery,
|
||||||
|
unlinkItem,
|
||||||
|
handleFileAction,
|
||||||
|
}: {
|
||||||
|
activateItem: LinkingController['activateItem']
|
||||||
|
item: LinkableItem
|
||||||
|
searchQuery?: string
|
||||||
|
unlinkItem: () => void
|
||||||
|
handleFileAction: FilesController['handleFileAction']
|
||||||
|
}) => {
|
||||||
|
const menuButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
|
const application = useApplication()
|
||||||
|
|
||||||
|
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
||||||
|
const toggleMenu = () => setIsMenuOpen((open) => !open)
|
||||||
|
|
||||||
|
const [isRenamingFile, setIsRenamingFile] = useState(false)
|
||||||
|
|
||||||
|
const [icon, className] = getIconForItem(item, application)
|
||||||
|
const title = item.title ?? ''
|
||||||
|
|
||||||
|
const renameFile = async (name: string) => {
|
||||||
|
if (!(item instanceof FileItem)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await handleFileAction({
|
||||||
|
type: PopoverFileItemActionType.RenameFile,
|
||||||
|
payload: {
|
||||||
|
file: item,
|
||||||
|
name: name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
setIsRenamingFile(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex items-center justify-between">
|
||||||
|
{isRenamingFile && item instanceof FileItem ? (
|
||||||
|
<div className="flex flex-grow items-center gap-4 py-2 pl-3 pr-12">
|
||||||
|
<Icon type={icon} className={classNames('flex-shrink-0', className)} />
|
||||||
|
<input
|
||||||
|
className="min-w-0 flex-grow text-sm"
|
||||||
|
defaultValue={title}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === KeyboardKey.Escape) {
|
||||||
|
setIsRenamingFile(false)
|
||||||
|
} else if (event.key === KeyboardKey.Enter) {
|
||||||
|
const newTitle = event.currentTarget.value
|
||||||
|
void renameFile(newTitle)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
ref={(node) => {
|
||||||
|
if (node) {
|
||||||
|
node.focus()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
className="flex max-w-full flex-grow items-center justify-between gap-4 py-2 pl-3 pr-12 text-sm hover:bg-info-backdrop focus:bg-info-backdrop"
|
||||||
|
onClick={() => activateItem(item)}
|
||||||
|
onContextMenu={(event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
toggleMenu()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LinkedItemMeta item={item} searchQuery={searchQuery} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="absolute right-3 top-1/2 h-7 w-7 -translate-y-1/2 cursor-pointer rounded-full border-0 bg-transparent p-1 hover:bg-contrast"
|
||||||
|
onClick={toggleMenu}
|
||||||
|
ref={menuButtonRef}
|
||||||
|
>
|
||||||
|
<Icon type="more" className="text-neutral" />
|
||||||
|
</button>
|
||||||
|
<Popover
|
||||||
|
open={isMenuOpen}
|
||||||
|
togglePopover={toggleMenu}
|
||||||
|
anchorElement={menuButtonRef.current}
|
||||||
|
side="bottom"
|
||||||
|
align="center"
|
||||||
|
className="py-2"
|
||||||
|
>
|
||||||
|
<MenuItem
|
||||||
|
type={MenuItemType.IconButton}
|
||||||
|
onClick={() => {
|
||||||
|
unlinkItem()
|
||||||
|
toggleMenu()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="link-off" className="mr-2 text-danger" />
|
||||||
|
Unlink
|
||||||
|
</MenuItem>
|
||||||
|
{item instanceof FileItem && (
|
||||||
|
<LinkedFileMenuOptions
|
||||||
|
file={item}
|
||||||
|
closeMenu={toggleMenu}
|
||||||
|
handleFileAction={handleFileAction}
|
||||||
|
setIsRenamingFile={setIsRenamingFile}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<HorizontalSeparator classes="my-2" />
|
||||||
|
<div className="mt-1 px-3 py-1 text-xs font-medium text-neutral">
|
||||||
|
<div className="mb-1">
|
||||||
|
<span className="font-semibold">Created at:</span> {formatDateForContextMenu(item.created_at)}
|
||||||
|
</div>
|
||||||
|
<div className="mb-1">
|
||||||
|
<span className="font-semibold">Modified at:</span> {formatDateForContextMenu(item.userModifiedDate)}
|
||||||
|
</div>
|
||||||
|
<div className="mb-1">
|
||||||
|
<span className="font-semibold">ID:</span> {item.uuid}
|
||||||
|
</div>
|
||||||
|
{item instanceof FileItem && (
|
||||||
|
<div>
|
||||||
|
<span className="font-semibold">Size:</span> {formatSizeToReadableString(item.decryptedSize)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ const AccountPreferences = ({ application, viewControllerManager }: Props) => (
|
|||||||
)}
|
)}
|
||||||
<Subscription application={application} viewControllerManager={viewControllerManager} />
|
<Subscription application={application} viewControllerManager={viewControllerManager} />
|
||||||
<SubscriptionSharing application={application} viewControllerManager={viewControllerManager} />
|
<SubscriptionSharing application={application} viewControllerManager={viewControllerManager} />
|
||||||
{application.hasAccount() && viewControllerManager.featuresController.hasFiles && (
|
{application.hasAccount() && viewControllerManager.featuresController.entitledToFiles && (
|
||||||
<FilesSection application={application} />
|
<FilesSection application={application} />
|
||||||
)}
|
)}
|
||||||
{application.hasAccount() && <Email application={application} />}
|
{application.hasAccount() && <Email application={application} />}
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import { WebApplication } from '@/Application/Application'
|
|||||||
import { openSubscriptionDashboard } from '@/Utils/ManageSubscription'
|
import { openSubscriptionDashboard } from '@/Utils/ManageSubscription'
|
||||||
import { PremiumFeatureIconClass, PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon'
|
import { PremiumFeatureIconClass, PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon'
|
||||||
import { PremiumFeatureModalType } from './PremiumFeatureModalType'
|
import { PremiumFeatureModalType } from './PremiumFeatureModalType'
|
||||||
|
import { FeatureName } from '@/Controllers/FeatureName'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
featureName: string
|
featureName: FeatureName | string
|
||||||
hasSubscription: boolean
|
hasSubscription: boolean
|
||||||
hasAccount: boolean
|
hasAccount: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
|
|||||||
3
packages/web/src/javascripts/Controllers/FeatureName.ts
Normal file
3
packages/web/src/javascripts/Controllers/FeatureName.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export enum FeatureName {
|
||||||
|
Files = 'Encrypted File Storage',
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { FeatureName } from './FeatureName'
|
||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { PremiumFeatureModalType } from '@/Components/PremiumFeaturesModal/PremiumFeatureModalType'
|
import { PremiumFeatureModalType } from '@/Components/PremiumFeaturesModal/PremiumFeatureModalType'
|
||||||
import { destroyAllObjectProperties } from '@/Utils'
|
import { destroyAllObjectProperties } from '@/Utils'
|
||||||
@@ -15,7 +16,7 @@ import { CrossControllerEvent } from './CrossControllerEvent'
|
|||||||
export class FeaturesController extends AbstractViewController {
|
export class FeaturesController extends AbstractViewController {
|
||||||
hasFolders: boolean
|
hasFolders: boolean
|
||||||
hasSmartViews: boolean
|
hasSmartViews: boolean
|
||||||
hasFiles: boolean
|
entitledToFiles: boolean
|
||||||
premiumAlertFeatureName: string | undefined
|
premiumAlertFeatureName: string | undefined
|
||||||
premiumAlertType: PremiumFeatureModalType | undefined = undefined
|
premiumAlertType: PremiumFeatureModalType | undefined = undefined
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ export class FeaturesController extends AbstractViewController {
|
|||||||
;(this.closePremiumAlert as unknown) = undefined
|
;(this.closePremiumAlert as unknown) = undefined
|
||||||
;(this.hasFolders as unknown) = undefined
|
;(this.hasFolders as unknown) = undefined
|
||||||
;(this.hasSmartViews as unknown) = undefined
|
;(this.hasSmartViews as unknown) = undefined
|
||||||
;(this.hasFiles as unknown) = undefined
|
;(this.entitledToFiles as unknown) = undefined
|
||||||
;(this.premiumAlertFeatureName as unknown) = undefined
|
;(this.premiumAlertFeatureName as unknown) = undefined
|
||||||
;(this.premiumAlertType as unknown) = undefined
|
;(this.premiumAlertType as unknown) = undefined
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ export class FeaturesController extends AbstractViewController {
|
|||||||
|
|
||||||
this.hasFolders = this.isEntitledToFolders()
|
this.hasFolders = this.isEntitledToFolders()
|
||||||
this.hasSmartViews = this.isEntitledToSmartViews()
|
this.hasSmartViews = this.isEntitledToSmartViews()
|
||||||
this.hasFiles = this.isEntitledToFiles()
|
this.entitledToFiles = this.isEntitledToFiles()
|
||||||
this.premiumAlertFeatureName = undefined
|
this.premiumAlertFeatureName = undefined
|
||||||
|
|
||||||
eventBus.addEventHandler(this, CrossControllerEvent.DisplayPremiumModal)
|
eventBus.addEventHandler(this, CrossControllerEvent.DisplayPremiumModal)
|
||||||
@@ -45,7 +46,7 @@ export class FeaturesController extends AbstractViewController {
|
|||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
hasFolders: observable,
|
hasFolders: observable,
|
||||||
hasSmartViews: observable,
|
hasSmartViews: observable,
|
||||||
hasFiles: observable,
|
entitledToFiles: observable,
|
||||||
premiumAlertType: observable,
|
premiumAlertType: observable,
|
||||||
premiumAlertFeatureName: observable,
|
premiumAlertFeatureName: observable,
|
||||||
showPremiumAlert: action,
|
showPremiumAlert: action,
|
||||||
@@ -67,7 +68,7 @@ export class FeaturesController extends AbstractViewController {
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.hasFolders = this.isEntitledToFolders()
|
this.hasFolders = this.isEntitledToFolders()
|
||||||
this.hasSmartViews = this.isEntitledToSmartViews()
|
this.hasSmartViews = this.isEntitledToSmartViews()
|
||||||
this.hasFiles = this.isEntitledToFiles()
|
this.entitledToFiles = this.isEntitledToFiles()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -81,7 +82,7 @@ export class FeaturesController extends AbstractViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async showPremiumAlert(featureName: string): Promise<void> {
|
public async showPremiumAlert(featureName: FeatureName | string): Promise<void> {
|
||||||
this.premiumAlertFeatureName = featureName
|
this.premiumAlertFeatureName = featureName
|
||||||
this.premiumAlertType = PremiumFeatureModalType.UpgradePrompt
|
this.premiumAlertType = PremiumFeatureModalType.UpgradePrompt
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user