Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added preference to stop auto-downloading files embedded in a Super note if they're above a set limit #2872

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const BytesInOneMB = 1024 * 1024

export enum AutoDownloadLimit {
TwoAndHalfMB = 2.5 * BytesInOneMB,
FiveMB = 5 * BytesInOneMB,
TenMB = 10 * BytesInOneMB,
TwentyMB = 20 * BytesInOneMB,
FiftyMB = 50 * BytesInOneMB,
}
1 change: 1 addition & 0 deletions packages/models/src/Domain/Syncable/UserPrefs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './EditorLineWidth'
export * from './NewNoteTitleFormat'
export * from './ComponentPreferences'
export * from './PrefDefaults'
export * from './AutoDownloadLimit'
32 changes: 31 additions & 1 deletion packages/services/src/Domain/Preferences/LocalPrefKey.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { EditorFontSize, EditorLineHeight, EditorLineWidth } from '@standardnotes/models'
import {
AutoDownloadLimit,
PrefDefaults,
EditorFontSize,
EditorLineHeight,
EditorLineWidth,
} from '@standardnotes/models'

export enum LocalPrefKey {
ActiveThemes = 'activeThemes',
Expand All @@ -7,6 +13,9 @@ export enum LocalPrefKey {
AutoLightThemeIdentifier = 'autoLightThemeIdentifier',
AutoDarkThemeIdentifier = 'autoDarkThemeIdentifier',

AlwaysAutoDownloadSuperEmbeds = 'alwaysAutoDownloadSuperEmbeds',
SuperEmbedAutoDownloadLimit = 'superEmbedAutoDownloadLimit',

EditorMonospaceEnabled = 'monospaceFont',
EditorLineHeight = 'editorLineHeight',
EditorLineWidth = 'editorLineWidth',
Expand All @@ -20,8 +29,29 @@ export type LocalPrefValue = {
[LocalPrefKey.AutoLightThemeIdentifier]: string
[LocalPrefKey.AutoDarkThemeIdentifier]: string

[LocalPrefKey.AlwaysAutoDownloadSuperEmbeds]: boolean
[LocalPrefKey.SuperEmbedAutoDownloadLimit]: AutoDownloadLimit

[LocalPrefKey.EditorMonospaceEnabled]: boolean
[LocalPrefKey.EditorLineHeight]: EditorLineHeight
[LocalPrefKey.EditorLineWidth]: EditorLineWidth
[LocalPrefKey.EditorFontSize]: EditorFontSize
}

export const LocalPrefDefaults = {
[LocalPrefKey.ActiveThemes]: PrefDefaults[LocalPrefKey.ActiveThemes],
[LocalPrefKey.UseSystemColorScheme]: PrefDefaults[LocalPrefKey.UseSystemColorScheme],
[LocalPrefKey.UseTranslucentUI]: PrefDefaults[LocalPrefKey.UseTranslucentUI],
[LocalPrefKey.AutoLightThemeIdentifier]: PrefDefaults[LocalPrefKey.AutoLightThemeIdentifier],
[LocalPrefKey.AutoDarkThemeIdentifier]: PrefDefaults[LocalPrefKey.AutoDarkThemeIdentifier],

[LocalPrefKey.AlwaysAutoDownloadSuperEmbeds]: false,
[LocalPrefKey.SuperEmbedAutoDownloadLimit]: AutoDownloadLimit.FiveMB,

[LocalPrefKey.EditorMonospaceEnabled]: PrefDefaults[LocalPrefKey.EditorMonospaceEnabled],
[LocalPrefKey.EditorLineHeight]: PrefDefaults[LocalPrefKey.EditorLineHeight],
[LocalPrefKey.EditorLineWidth]: PrefDefaults[LocalPrefKey.EditorLineWidth],
[LocalPrefKey.EditorFontSize]: PrefDefaults[LocalPrefKey.EditorFontSize],
} satisfies {
[key in LocalPrefKey]: LocalPrefValue[key]
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ApplicationEvent,
FileDownloadProgress,
FileItem,
LocalPrefKey,
fileProgressToHumanReadableString,
} from '@standardnotes/snjs'
import { useEffect, useMemo, useState } from 'react'
Expand All @@ -14,6 +15,9 @@ import PreviewComponent from './PreviewComponent'
import Button from '../Button/Button'
import { ProtectedIllustration } from '@standardnotes/icons'
import { ImageZoomLevelProps } from './ImageZoomLevelProps'
import { useLocalPreference } from '../../Hooks/usePreference'
import { formatSizeToReadableString } from '@standardnotes/filepicker'
import { ElementIds } from '@/Constants/ElementIDs'

type Props = {
application: WebApplication
Expand All @@ -22,13 +26,25 @@ type Props = {
} & ImageZoomLevelProps

const FilePreview = ({ file, application, isEmbeddedInSuper = false, imageZoomLevel, setImageZoomLevel }: Props) => {
const [alwaysAutoDownload] = useLocalPreference(LocalPrefKey.AlwaysAutoDownloadSuperEmbeds)
const [autoDownloadLimit] = useLocalPreference(LocalPrefKey.SuperEmbedAutoDownloadLimit)
const isOverLimit = file.decryptedSize > autoDownloadLimit

const [shouldDownload, setShouldDownload] = useState(isEmbeddedInSuper ? alwaysAutoDownload || !isOverLimit : true)
useEffect(() => {
if (!isEmbeddedInSuper) {
return
}
setShouldDownload(alwaysAutoDownload || !isOverLimit)
}, [alwaysAutoDownload, isEmbeddedInSuper, isOverLimit])

const [isAuthorized, setIsAuthorized] = useState(application.isAuthorizedToRenderItem(file))

const isFilePreviewable = useMemo(() => {
return isFileTypePreviewable(file.mimeType)
}, [file.mimeType])

const [isDownloading, setIsDownloading] = useState(true)
const [isDownloading, setIsDownloading] = useState(shouldDownload)
const [downloadProgress, setDownloadProgress] = useState<FileDownloadProgress | undefined>()
const [downloadedBytes, setDownloadedBytes] = useState<Uint8Array>()

Expand All @@ -49,6 +65,10 @@ const FilePreview = ({ file, application, isEmbeddedInSuper = false, imageZoomLe
}, [application, file])

useEffect(() => {
if (!shouldDownload) {
return
}

if (!isFilePreviewable || !isAuthorized) {
setIsDownloading(false)
setDownloadProgress(undefined)
Expand Down Expand Up @@ -85,7 +105,7 @@ const FilePreview = ({ file, application, isEmbeddedInSuper = false, imageZoomLe
}

void downloadFileForPreview()
}, [application.files, downloadedBytes, file, isFilePreviewable, isAuthorized])
}, [application.files, downloadedBytes, file, isFilePreviewable, isAuthorized, shouldDownload])

if (!isAuthorized) {
const hasProtectionSources = application.hasProtectionSources()
Expand Down Expand Up @@ -113,6 +133,58 @@ const FilePreview = ({ file, application, isEmbeddedInSuper = false, imageZoomLe
)
}

if (!shouldDownload && !isDownloading && !downloadedBytes) {
return (
<div className="flex flex-grow flex-col items-center justify-center p-1.5">
<div className="mb-1.5 text-center text-base font-bold">{file.name}</div>
<p className="mb-2.5 max-w-[65ch] text-center text-sm text-passive-0">
This file was not automatically loaded because it was larger ({formatSizeToReadableString(file.decryptedSize)}
) than the set limit for auto-downloading embedded files ({formatSizeToReadableString(autoDownloadLimit)})
</p>
<div className="mb-2 flex flex-wrap gap-3">
<Button primary onClick={() => setShouldDownload(true)}>
Load file
</Button>
<Button
onClick={() => {
const preferences = application.preferencesController
preferences.openPreferences('general')
setTimeout(() => {
const prefElement = document.getElementById(ElementIds.AutoDownloadLimitPreference)
if (prefElement) {
prefElement.scrollIntoView({
block: 'center',
})
prefElement.querySelector('button')?.focus()
setTimeout(() => {
prefElement.animate(
[
{
transform: 'scale(1)',
},
{
transform: 'scale(1.05)',
},
{
transform: 'scale(1)',
},
],
{
duration: 350,
},
)
}, 100)
}
}, 50)
}}
>
Change limit
</Button>
</div>
</div>
)
}

return isDownloading ? (
<div className="flex flex-grow flex-col items-center justify-center">
<div className="flex items-center">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { action, makeAutoObservable, observable } from 'mobx'
import { action, autorun, makeAutoObservable, observable } from 'mobx'
import { WebApplication } from '@/Application/WebApplication'
import { PackageProvider } from '../Panes/Plugins/PackageProvider'
import { securityPrefsHasBubble } from '../Panes/Security/securityPrefsHasBubble'
Expand Down Expand Up @@ -59,6 +59,10 @@ export class PreferencesSessionController {
this.updateMenuBubbleCounts()
}
})

autorun(() => {
this.selectPane(application.preferencesController.currentPane)
})
}

private updateMenuBubbleCounts(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import Switch from '@/Components/Switch/Switch'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
import usePreference from '@/Hooks/usePreference'
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'

type Props = {
application: WebApplication
Expand All @@ -20,14 +19,10 @@ const Defaults: FunctionComponent<Props> = ({ application }) => {
() => (application.getValue(AndroidConfirmBeforeExitKey) as boolean) ?? true,
)

const isMobile = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)

const spellcheck = usePreference(PrefKey.EditorSpellcheck)

const addNoteToParentFolders = usePreference(PrefKey.NoteAddToParentFolders)

const alwaysShowSuperToolbar = usePreference(PrefKey.AlwaysShowSuperToolbar)

const toggleSpellcheck = () => {
application.toggleGlobalSpellcheck().catch(console.error)
}
Expand Down Expand Up @@ -77,25 +72,6 @@ const Defaults: FunctionComponent<Props> = ({ application }) => {
checked={addNoteToParentFolders}
/>
</div>
<HorizontalSeparator classes="my-4" />
{!isMobile && (
<div className="flex justify-between gap-2 md:items-center">
<div className="flex flex-col">
<Subtitle>Use always-visible toolbar in Super notes</Subtitle>
<Text>
When enabled, the Super toolbar will always be shown at the top of the note. It can be temporarily
toggled using Cmd/Ctrl+Shift+K. When disabled, the Super toolbar will only be shown as a floating
toolbar when text is selected.
</Text>
</div>
<Switch
onChange={() => {
application.setPreference(PrefKey.AlwaysShowSuperToolbar, !alwaysShowSuperToolbar).catch(console.error)
}}
checked={alwaysShowSuperToolbar}
/>
</div>
)}
</PreferencesSegment>
</PreferencesGroup>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SmartViews from './SmartViews/SmartViews'
import Moments from './Moments'
import NewNoteDefaults from './NewNoteDefaults'
import { useApplication } from '@/Components/ApplicationProvider'
import SuperNotes from './SuperNotes'

const General: FunctionComponent = () => {
const application = useApplication()
Expand All @@ -18,6 +19,7 @@ const General: FunctionComponent = () => {
<PreferencesPane>
<Persistence application={application} />
<Defaults application={application} />
<SuperNotes application={application} />
<NewNoteDefaults />
<Tools application={application} />
<SmartViews application={application} featuresController={application.featuresController} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { AutoDownloadLimit, LocalPrefKey, PrefKey } from '@standardnotes/snjs'
import { useMediaQuery, MutuallyExclusiveMediaQueryBreakpoints } from '../../../../Hooks/useMediaQuery'
import usePreference, { useLocalPreference } from '../../../../Hooks/usePreference'
import Switch from '../../../Switch/Switch'
import { Subtitle, Title, Text } from '../../PreferencesComponents/Content'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
import { WebApplication } from '../../../../Application/WebApplication'
import HorizontalSeparator from '../../../Shared/HorizontalSeparator'
import Dropdown from '../../../Dropdown/Dropdown'
import { useMemo } from 'react'
import { ElementIds } from '@/Constants/ElementIDs'

const SuperNotes = ({ application }: { application: WebApplication }) => {
const isMobile = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)

const alwaysShowSuperToolbar = usePreference(PrefKey.AlwaysShowSuperToolbar)

const [alwaysAutoDownloadEmbeds, setAlwaysAutoDownloadEmbeds] = useLocalPreference(
LocalPrefKey.AlwaysAutoDownloadSuperEmbeds,
)
const [autoDownloadLimit, setAutoDownloadLimit] = useLocalPreference(LocalPrefKey.SuperEmbedAutoDownloadLimit)

const autoDownloadLimitOptions = useMemo(
() => [
{
label: '2.5 MB',
value: 'TwoAndHalfMB',
},
{
label: '5 MB',
value: 'FiveMB',
},
{
label: '10 MB',
value: 'TenMB',
},
{
label: '20 MB',
value: 'TwentyMB',
},
{
label: '50 MB',
value: 'FiftyMB',
},
],
[],
)

const dropdownValue: keyof typeof AutoDownloadLimit = useMemo(() => {
switch (autoDownloadLimit) {
case AutoDownloadLimit.TwoAndHalfMB:
return 'TwoAndHalfMB'
case AutoDownloadLimit.FiveMB:
return 'FiveMB'
case AutoDownloadLimit.TenMB:
return 'TenMB'
case AutoDownloadLimit.TwentyMB:
return 'TwentyMB'
case AutoDownloadLimit.FiftyMB:
return 'FiftyMB'
}
}, [autoDownloadLimit])

return (
<PreferencesGroup>
<PreferencesSegment>
<Title className="mb-2">Super notes</Title>
{!isMobile && (
<>
<div className="flex justify-between gap-2 md:items-center">
<div className="flex flex-col">
<Subtitle>Use always-visible toolbar in Super notes</Subtitle>
<Text>
When enabled, the Super toolbar will always be shown at the top of the note. It can be temporarily
toggled using Cmd/Ctrl+Shift+K. When disabled, the Super toolbar will only be shown as a floating
toolbar when text is selected.
</Text>
</div>
<Switch
onChange={() => {
application
.setPreference(PrefKey.AlwaysShowSuperToolbar, !alwaysShowSuperToolbar)
.catch(console.error)
}}
checked={alwaysShowSuperToolbar}
/>
</div>
<HorizontalSeparator classes="my-4" />
</>
)}
<div className="flex justify-between gap-2 md:items-center">
<div className="flex flex-col">
<Subtitle>Always auto-download embedded files</Subtitle>
<Text>
When enabled, embedded files will always be automatically downloaded regardless of their file size.
</Text>
</div>
<Switch
onChange={() => {
setAlwaysAutoDownloadEmbeds(!alwaysAutoDownloadEmbeds)
}}
checked={alwaysAutoDownloadEmbeds}
/>
</div>
<HorizontalSeparator classes="my-4" />
<div id={ElementIds.AutoDownloadLimitPreference}>
<Subtitle>Auto-download limit for embedded files</Subtitle>
<Text>Only embedded files below the set limit will be automatically downloaded</Text>
<div className="mt-2">
<Dropdown
label="Select the line height for plaintext notes"
items={autoDownloadLimitOptions}
value={dropdownValue}
onChange={(value) => {
setAutoDownloadLimit(AutoDownloadLimit[value as keyof typeof AutoDownloadLimit])
}}
/>
</div>
</div>
</PreferencesSegment>
</PreferencesGroup>
)
}

export default SuperNotes
Loading
Loading