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: Editing large notes (greater than 1.5MB) will result in more optimized syncing, in which changes are saved locally immediately, but sync with the server less frequently (roughly every 30 seconds rather than after every change). #2768

Merged
merged 35 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a78d88f
chore: sync when window is blurred
amanharwara Jan 9, 2024
ce699b7
chore: sync when editor loses focus
amanharwara Jan 10, 2024
2367e8f
refactor: separate local & remote save debounce amounts
amanharwara Jan 10, 2024
f86d9cd
Merge branch 'main' into sync-improvements
amanharwara Jan 11, 2024
725ebd9
chore: fix issues
amanharwara Jan 11, 2024
472ed18
refactor: make sure local save is resolved before remote save
amanharwara Jan 11, 2024
9b6ccbe
Merge branch 'main' into sync-improvements
amanharwara Jan 11, 2024
75e92b4
refactor: show waiting status
amanharwara Jan 12, 2024
ac10fc6
fix: sync locally on local save
amanharwara Jan 12, 2024
5352491
Revert "fix: sync locally on local save"
amanharwara Jan 12, 2024
ad2752e
fix: persist mutated item payload on local save
amanharwara Jan 15, 2024
0b25119
Merge branch 'main' into sync-improvements
amanharwara Jan 15, 2024
e2f63cf
fix: test type
amanharwara Jan 15, 2024
c640ebc
refactor: note status indicator ui
amanharwara Jan 18, 2024
613136a
fix: status on sync
amanharwara Jan 18, 2024
9d92e9c
refactor: local only sync of large item
moughxyz Jan 18, 2024
37fce73
fix: release lock
moughxyz Jan 18, 2024
d479bda
fix: handle race where large note sync occurs just before local mutat…
moughxyz Jan 18, 2024
f2ebe39
Merge branch 'main' into sync-improvements
amanharwara Jan 19, 2024
43b2797
chore: popover max-width
amanharwara Jan 19, 2024
1528e92
refactor: trigger sync on action instead of editor blur
amanharwara Jan 19, 2024
dffe8ef
chore: remove unused enum value
amanharwara Jan 19, 2024
de2f9f1
chore: unify large note threshold
amanharwara Jan 19, 2024
98d45c6
feat: show size in note attributes
amanharwara Jan 19, 2024
5c6fd71
refactor: dont defer large note sync if not signed in
amanharwara Jan 19, 2024
27b3f35
chore: wait before changing status
amanharwara Jan 19, 2024
0f8184f
fix: test
amanharwara Jan 19, 2024
a2ed865
chore: move size attribute to last
amanharwara Jan 19, 2024
4a447ee
fix: correct status not being shown
amanharwara Jan 19, 2024
4c67ecd
chore: debounce resize event
amanharwara Jan 19, 2024
1b4f81b
chore: remove margin
amanharwara Jan 19, 2024
4c3b44c
fix: focused mode
amanharwara Jan 19, 2024
7198712
chore: sync large note when exiting focus mode
amanharwara Jan 19, 2024
7e92e7b
chore: show "Last synced" time in large note waiting indicator
amanharwara Jan 19, 2024
efc1ca7
chore: sync large note when note changes
amanharwara Jan 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/icons/src/Icons/ic-clock.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/icons/src/Icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import ChevronUpIcon from './ic-chevron-up.svg'
import CircleIcon from './circle-55.svg'
import ClearCircleFilledIcon from './ic-clear-circle-filled.svg'
import CloseCircleFilledIcon from './ic-close-circle-filled.svg'
import ClockIcon from './ic-clock.svg'
import CloseIcon from './ic-close.svg'
import CloudOffIcon from './ic-cloud-off.svg'
import CodeIcon from './ic-code.svg'
Expand Down Expand Up @@ -263,6 +264,7 @@ export {
CircleIcon,
ClearCircleFilledIcon,
CloseCircleFilledIcon,
ClockIcon,
CloseIcon,
CloudOffIcon,
CodeIcon,
Expand Down
1 change: 1 addition & 0 deletions packages/models/src/Domain/Utilities/Icon/IconType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type IconType =
| 'chevron-right'
| 'chevron-up'
| 'clear-circle-filled'
| 'clock'
| 'close'
| 'cloud-off'
| 'code-2'
Expand Down
2 changes: 1 addition & 1 deletion packages/services/src/Domain/Event/WebAppEvent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export enum WebAppEvent {
NewUpdateAvailable = 'NewUpdateAvailable',
EditorFocused = 'EditorFocused',
EditorDidFocus = 'EditorDidFocus',
BeganBackupDownload = 'BeganBackupDownload',
EndedBackupDownload = 'EndedBackupDownload',
PanelResized = 'PanelResized',
Expand Down
1 change: 1 addition & 0 deletions packages/services/src/Domain/Sync/SyncMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export enum SyncMode {
* all data to see if user has an items key, and if not, only then create a new one.
*/
DownloadFirst = 'DownloadFirst',
LocalOnly = 'LocalOnly',
}
6 changes: 6 additions & 0 deletions packages/snjs/lib/Services/Sync/SyncService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,12 @@ export class SyncService

const { items, beginDate, frozenDirtyIndex, neverSyncedDeleted } = await this.prepareForSync(options)

if (options.mode === SyncMode.LocalOnly) {
this.logger.debug('Syncing local only, skipping remote sync request')
releaseLock()
return
}

const inTimeResolveQueue = this.getPendingRequestsMadeInTimeToPiggyBackOnCurrentRequest()

if (!shouldExecuteSync) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class VisibilityObserver {
*/
document.addEventListener('visibilitychange', this.onVisibilityChange)
window.addEventListener('focus', this.onFocusEvent, false)
window.addEventListener('blur', this.onBlurEvent, false)
}

onVisibilityChange = () => {
Expand All @@ -23,6 +24,10 @@ export class VisibilityObserver {
this.notifyEvent(WebAppEvent.WindowDidFocus)
}

onBlurEvent = () => {
this.notifyEvent(WebAppEvent.WindowDidBlur)
}

private notifyEvent(event: WebAppEvent): void {
if (this.raceTimeout) {
clearTimeout(this.raceTimeout)
Expand All @@ -35,6 +40,7 @@ export class VisibilityObserver {
deinit(): void {
document.removeEventListener('visibilitychange', this.onVisibilityChange)
window.removeEventListener('focus', this.onFocusEvent)
window.removeEventListener('blur', this.onBlurEvent)
;(this.onEvent as unknown) = undefined
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio

useEffect(() => {
const removeObserver = application.addWebEventObserver(async (eventName) => {
if (eventName === WebAppEvent.WindowDidFocus) {
if (eventName === WebAppEvent.WindowDidFocus || eventName === WebAppEvent.WindowDidBlur) {
if (!(await application.protections.isLocked())) {
application.sync.sync().catch(console.error)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import { NoteType, noteTypeForEditorIdentifier } from '@standardnotes/snjs'

type Props = {
noteViewController?: NoteViewController
onClick?: () => void
onClickPreprocessing?: () => Promise<void>
}

const ChangeEditorButton: FunctionComponent<Props> = ({ noteViewController, onClickPreprocessing }: Props) => {
const ChangeEditorButton: FunctionComponent<Props> = ({ noteViewController, onClick, onClickPreprocessing }: Props) => {
const application = useApplication()

const note = application.notesController.firstSelectedNote
Expand Down Expand Up @@ -48,7 +49,10 @@ const ChangeEditorButton: FunctionComponent<Props> = ({ noteViewController, onCl
await onClickPreprocessing()
}
setIsOpen(willMenuOpen)
}, [onClickPreprocessing, isOpen])
if (onClick) {
onClick()
}
}, [isOpen, onClickPreprocessing, onClick])

useEffect(() => {
return application.keyboardService.addCommandHandler({
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/javascripts/Components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class Footer extends AbstractComponent<Props, State> {
case WebAppEvent.NewUpdateAvailable:
this.onNewUpdateAvailable()
break
case WebAppEvent.EditorFocused:
case WebAppEvent.EditorDidFocus:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((data as any).eventSource === EditorEventSource.UserInteraction) {
this.closeAccountMenu()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export const IconNameToSvgMapping = {
bold: icons.BoldIcon,
camera: icons.CameraIcon,
check: icons.CheckIcon,
clock: icons.ClockIcon,
close: icons.CloseIcon,
code: icons.CodeIcon,
comment: icons.FeedbackIcon,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import LinkedItemsPanel from './LinkedItemsPanel'

type Props = {
linkingController: LinkingController
onClick?: () => void
onClickPreprocessing?: () => Promise<void>
}

const LinkedItemsButton = ({ linkingController, onClickPreprocessing }: Props) => {
const LinkedItemsButton = ({ linkingController, onClick, onClickPreprocessing }: Props) => {
const { activeItem, isLinkingPanelOpen, setIsLinkingPanelOpen } = linkingController
const buttonRef = useRef<HTMLButtonElement>(null)

Expand All @@ -20,7 +21,10 @@ const LinkedItemsButton = ({ linkingController, onClickPreprocessing }: Props) =
await onClickPreprocessing()
}
setIsLinkingPanelOpen(willMenuOpen)
}, [isLinkingPanelOpen, onClickPreprocessing, setIsLinkingPanelOpen])
if (onClick) {
onClick()
}
}, [isLinkingPanelOpen, onClick, onClickPreprocessing, setIsLinkingPanelOpen])

if (!activeItem) {
return null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { MILLISECONDS_IN_A_SECOND } from '@/Constants/Constants'

export const EditorSaveTimeoutDebounce = {
Desktop: 350,
ImmediateChange: 100,
NativeMobileWeb: 700,
LargeNote: 60 * MILLISECONDS_IN_A_SECOND,
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ export class ItemGroupController {
controller: NoteViewController | FileViewController,
{ notify = true }: { notify: boolean } = { notify: true },
): void {
if (controller instanceof NoteViewController) {
controller.syncOnlyIfLargeNote()
}
controller.deinit()

removeFromArray(this.itemControllers, controller)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ describe('note view controller', () => {
createTemplateItem: jest.fn().mockReturnValue({} as SNNote),
} as unknown as jest.Mocked<ItemManagerInterface>,
mutator: {} as jest.Mocked<MutatorClientInterface>,
sessions: {
isSignedIn: jest.fn().mockReturnValue(true),
},
} as unknown as jest.Mocked<WebApplication>

application.isNativeMobileWeb = jest.fn().mockReturnValue(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { TemplateNoteViewControllerOptions } from './TemplateNoteViewControllerO
import { log, LoggingDomain } from '@/Logging'
import { NoteSaveFunctionParams, NoteSyncController } from '../../../Controllers/NoteSyncController'
import { IsNativeMobileWeb } from '@standardnotes/ui-services'
import { NoteStatus } from '../NoteStatusIndicator'

export type EditorValues = {
title: string
Expand Down Expand Up @@ -219,4 +220,28 @@ export class NoteViewController implements ItemViewControllerInterface {

await this.syncController.saveAndAwaitLocalPropagation(params)
}

public get syncStatus(): NoteStatus | undefined {
return this.syncController.status
}

public showSavingStatus(): void {
this.syncController.showSavingStatus()
}

public showAllChangesSavedStatus(): void {
this.syncController.showAllChangesSavedStatus()
}

public showErrorSyncStatus(error?: NoteStatus): void {
this.syncController.showErrorStatus(error)
}

public syncNow(): void {
this.sync.sync().catch(console.error)
}

public syncOnlyIfLargeNote(): void {
this.syncController.syncOnlyIfLargeNote()
}
}