From d988763f041438eff90688e261582217e0a75ff2 Mon Sep 17 00:00:00 2001 From: x1unix Date: Sat, 10 Aug 2024 17:23:10 -0400 Subject: [PATCH 1/5] fix: improve description of mouse zoom option --- web/src/components/features/settings/SettingsModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/features/settings/SettingsModal.tsx b/web/src/components/features/settings/SettingsModal.tsx index ac0bd484..244cca05 100644 --- a/web/src/components/features/settings/SettingsModal.tsx +++ b/web/src/components/features/settings/SettingsModal.tsx @@ -247,7 +247,7 @@ class SettingsModal extends ThemeableComponent { title="Mouse Wheel Zoom" control={ { this.touchMonacoProperty('mouseWheelZoom', val) From 2ce5d923617a30a849d04bea8cf779ceecae2500 Mon Sep 17 00:00:00 2001 From: x1unix Date: Sat, 10 Aug 2024 17:47:18 -0400 Subject: [PATCH 2/5] feat: improve settings options location --- .../features/settings/SettingsModal.tsx | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/web/src/components/features/settings/SettingsModal.tsx b/web/src/components/features/settings/SettingsModal.tsx index 244cca05..7c5b0e23 100644 --- a/web/src/components/features/settings/SettingsModal.tsx +++ b/web/src/components/features/settings/SettingsModal.tsx @@ -120,14 +120,14 @@ class SettingsModal extends ThemeableComponent { } /> { - this.touchMonacoProperty('minimap', val) + this.touchSettingsProperty({ enableVimMode: val }) }} /> } @@ -137,7 +137,7 @@ class SettingsModal extends ThemeableComponent { title="Use System Theme" control={ { this.touchSettingsProperty({ @@ -161,14 +161,14 @@ class SettingsModal extends ThemeableComponent { } /> { - this.touchSettingsProperty({ enableVimMode: val }) + this.touchMonacoProperty('mouseWheelZoom', val) }} /> } @@ -192,7 +192,7 @@ class SettingsModal extends ThemeableComponent { { } /> { - this.touchMonacoProperty('cursorStyle', val) + this.touchMonacoProperty('smoothScrolling', val) }} /> } /> { - this.touchMonacoProperty('contextMenu', val) + this.touchMonacoProperty('minimap', val) }} /> } /> { - this.touchMonacoProperty('smoothScrolling', val) + this.touchMonacoProperty('cursorStyle', val) }} /> } /> { - this.touchMonacoProperty('mouseWheelZoom', val) + this.touchMonacoProperty('contextMenu', val) }} /> } From d85a4ca6f4ec56c5e499f9a8f7f7933122353ef3 Mon Sep 17 00:00:00 2001 From: x1unix Date: Sat, 10 Aug 2024 19:54:45 -0400 Subject: [PATCH 3/5] fix: improve mouse zoom config description --- web/src/components/elements/misc/Kbd/Kbd.tsx | 14 ++++++++++++++ web/src/components/elements/misc/Kbd/index.ts | 1 + .../components/features/settings/SettingsModal.tsx | 13 ++++++++++--- web/src/utils/dom.ts | 13 +++++++++++-- 4 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 web/src/components/elements/misc/Kbd/Kbd.tsx create mode 100644 web/src/components/elements/misc/Kbd/index.ts diff --git a/web/src/components/elements/misc/Kbd/Kbd.tsx b/web/src/components/elements/misc/Kbd/Kbd.tsx new file mode 100644 index 00000000..ad4d98b4 --- /dev/null +++ b/web/src/components/elements/misc/Kbd/Kbd.tsx @@ -0,0 +1,14 @@ +import { mergeStyles, useTheme } from '@fluentui/react' +import React from 'react' + +export const Kbd: React.FC = ({ children }) => { + const { semanticColors } = useTheme() + + const style = mergeStyles({ + padding: '1px 3px', + borderRadius: '4px', + background: semanticColors.disabledSubtext, + }) + + return {children} +} diff --git a/web/src/components/elements/misc/Kbd/index.ts b/web/src/components/elements/misc/Kbd/index.ts new file mode 100644 index 00000000..f9fae902 --- /dev/null +++ b/web/src/components/elements/misc/Kbd/index.ts @@ -0,0 +1 @@ +export * from './Kbd' diff --git a/web/src/components/features/settings/SettingsModal.tsx b/web/src/components/features/settings/SettingsModal.tsx index 7c5b0e23..b46afcb4 100644 --- a/web/src/components/features/settings/SettingsModal.tsx +++ b/web/src/components/features/settings/SettingsModal.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Checkbox, Dropdown, type IPivotStyles, PivotItem, TextField } from '@fluentui/react' +import { Checkbox, Dropdown, getTheme, type IPivotStyles, PivotItem, Text, TextField } from '@fluentui/react' import { AnimatedPivot } from '~/components/elements/tabs/AnimatedPivot' import { ThemeableComponent } from '~/components/utils/ThemeableComponent' @@ -11,6 +11,8 @@ import type { RenderingBackend, TerminalSettings } from '~/store/terminal' import { connect, type StateDispatch, type MonacoParamsChanges, type SettingsState } from '~/store' import { cursorBlinkOptions, cursorLineOptions, fontOptions, terminalBackendOptions } from './options' +import { controlKeyLabel } from '~/utils/dom' +import { Kbd } from '~/components/elements/misc/Kbd' export interface SettingsChanges { monaco?: MonacoParamsChanges @@ -40,7 +42,7 @@ interface SettingsModalState { const modalStyles = { main: { - maxWidth: 480, + maxWidth: 520, }, } @@ -100,6 +102,7 @@ class SettingsModal extends ThemeableComponent { render() { const { isOpen } = this.props + const { spacing } = getTheme() return ( this.onClose()} isOpen={isOpen} styles={modalStyles}> @@ -165,8 +168,12 @@ class SettingsModal extends ThemeableComponent { title="Mouse Wheel Zoom" control={ ( + + Zoom the editor font when using mouse wheel and holding {controlKeyLabel()} key + + )} onChange={(_, val) => { this.touchMonacoProperty('mouseWheelZoom', val) }} diff --git a/web/src/utils/dom.ts b/web/src/utils/dom.ts index 9e852cec..2a707605 100644 --- a/web/src/utils/dom.ts +++ b/web/src/utils/dom.ts @@ -1,2 +1,11 @@ -export const isTouchDevice = () => - 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0 +export const isTouchDevice = () => 'ontouchstart' in window || navigator.maxTouchPoints > 0 + +export const isAppleDevice = () => /(iPad|iPhone|iPod|Mac)/g.test(navigator.userAgent) + +/** + * Returns name of a Ctrl or Command key label depending on user agent. + * + * Used to highlight shortcut key combinations. + * @returns + */ +export const controlKeyLabel = () => (isAppleDevice() ? '⌘' : 'Ctrl') From 3506c8c6b2d7dfb7e500cc1b5ca946fcaa95c243 Mon Sep 17 00:00:00 2001 From: x1unix Date: Sat, 10 Aug 2024 19:59:18 -0400 Subject: [PATCH 4/5] feat: expose autosave prop --- .../components/features/settings/SettingsModal.tsx | 13 +++++++++++++ web/src/services/config/config.ts | 9 +++++++++ web/src/store/reducers.ts | 1 + web/src/store/state.ts | 1 + 4 files changed, 24 insertions(+) diff --git a/web/src/components/features/settings/SettingsModal.tsx b/web/src/components/features/settings/SettingsModal.tsx index b46afcb4..5c854afc 100644 --- a/web/src/components/features/settings/SettingsModal.tsx +++ b/web/src/components/features/settings/SettingsModal.tsx @@ -135,6 +135,19 @@ class SettingsModal extends ThemeableComponent { /> } /> + { + this.touchSettingsProperty({ autoSave: val }) + }} + /> + } + /> Date: Sat, 10 Aug 2024 20:12:40 -0400 Subject: [PATCH 5/5] feat: respect autosave prop --- web/src/store/dispatchers/settings.ts | 17 +++++++++++- web/src/store/workspace/config.ts | 26 ++++++++++--------- web/src/store/workspace/dispatchers/files.ts | 24 ++++++++++++----- .../store/workspace/dispatchers/snippet.ts | 9 ++++++- web/src/store/workspace/state.ts | 10 +++++++ 5 files changed, 66 insertions(+), 20 deletions(-) diff --git a/web/src/store/dispatchers/settings.ts b/web/src/store/dispatchers/settings.ts index f5cf39dc..a3628102 100644 --- a/web/src/store/dispatchers/settings.ts +++ b/web/src/store/dispatchers/settings.ts @@ -12,6 +12,7 @@ import { newSettingsChangeAction, newToggleThemeAction, } from '../actions' +import { saveWorkspaceState, truncateWorkspaceState } from '../workspace/config' export function newMonacoParamsChangeDispatcher(changes: MonacoParamsChanges): Dispatcher { return (dispatch: DispatchFn, _: StateProvider) => { @@ -23,7 +24,7 @@ export function newMonacoParamsChangeDispatcher(changes: MonacoParamsChanges): D export const newSettingsChangeDispatcher = (changes: Partial): Dispatcher => - (dispatch: DispatchFn, _: StateProvider) => { + (dispatch: DispatchFn, getState: StateProvider) => { if ('useSystemTheme' in changes) { config.useSystemTheme = !!changes.useSystemTheme changes.darkMode = isDarkModeEnabled() @@ -37,6 +38,20 @@ export const newSettingsChangeDispatcher = config.enableVimMode = !!changes.enableVimMode } + if ('autoSave' in changes) { + config.autoSave = !!changes.autoSave + + // Immediately save workspace + if (changes.autoSave) { + const { workspace } = getState() + if (!workspace.snippet?.id) { + saveWorkspaceState(workspace) + } + } else { + truncateWorkspaceState() + } + } + dispatch(newSettingsChangeAction(changes)) } diff --git a/web/src/store/workspace/config.ts b/web/src/store/workspace/config.ts index 486aa918..f9e31800 100644 --- a/web/src/store/workspace/config.ts +++ b/web/src/store/workspace/config.ts @@ -8,6 +8,20 @@ const defaultWorkspace: WorkspaceState = { files: defaultFiles, } +const sanitizeState = (state: WorkspaceState) => { + // Skip current snippet URL. + const { selectedFile, files } = state + + if (!files) { + // Save defaults if ws is empty. + return defaultWorkspace + } + + return { selectedFile, files } +} + +export const truncateWorkspaceState = () => config.delete(CONFIG_KEY) + export const saveWorkspaceState = (state: WorkspaceState) => { const sanitized = sanitizeState(state) if (!sanitized.files || Object.keys(sanitized.files).length === 0) { @@ -20,15 +34,3 @@ export const saveWorkspaceState = (state: WorkspaceState) => { } export const loadWorkspaceState = (): WorkspaceState => sanitizeState(config.getObject(CONFIG_KEY, defaultWorkspace)) - -const sanitizeState = (state: WorkspaceState) => { - // Skip current snippet URL. - const { selectedFile, files } = state - - if (!files) { - // Save defaults if ws is empty. - return defaultWorkspace - } - - return { selectedFile, files } -} diff --git a/web/src/store/workspace/dispatchers/files.ts b/web/src/store/workspace/dispatchers/files.ts index 8896bf32..f8a74ed6 100644 --- a/web/src/store/workspace/dispatchers/files.ts +++ b/web/src/store/workspace/dispatchers/files.ts @@ -15,17 +15,29 @@ import { type WorkspaceState, defaultFiles } from '../state' const AUTOSAVE_INTERVAL = 1000 +const isAutosaveEnabled = (getState: StateProvider) => { + const { settings } = getState() + return settings.autoSave +} + let saveTimeout: NodeJS.Timeout + const cancelPendingAutosave = () => { clearTimeout(saveTimeout) } -const scheduleAutosave = (getState: StateProvider) => { + +const scheduleWorkspaceAutosave = (getState: StateProvider) => { cancelPendingAutosave() + if (!isAutosaveEnabled(getState)) { + return + } + saveTimeout = setTimeout(() => { const { + settings: { autoSave }, workspace: { snippet, ...wp }, } = getState() - if (snippet) { + if (snippet?.id || !autoSave) { // abort autosave when loaded external snippet. return } @@ -88,7 +100,7 @@ export const dispatchImportFile = (files: FileList) => async (dispatch: Dispatch }) } - scheduleAutosave(getState) + scheduleWorkspaceAutosave(getState) } export const dispatchCreateFile = @@ -112,7 +124,7 @@ export const dispatchCreateFile = payload: [{ filename, content }], }) - scheduleAutosave(getState) + scheduleWorkspaceAutosave(getState) } export const dispatchRemoveFile = (filename: string) => (dispatch: DispatchFn, getState: StateProvider) => { @@ -121,7 +133,7 @@ export const dispatchRemoveFile = (filename: string) => (dispatch: DispatchFn, g payload: { filename }, }) - scheduleAutosave(getState) + scheduleWorkspaceAutosave(getState) } export const dispatchUpdateFile = @@ -131,7 +143,7 @@ export const dispatchUpdateFile = payload: { filename, content }, }) - scheduleAutosave(getState) + scheduleWorkspaceAutosave(getState) } export const dispatchImportSource = (files: Record) => (dispatch: DispatchFn, _: StateProvider) => { diff --git a/web/src/store/workspace/dispatchers/snippet.ts b/web/src/store/workspace/dispatchers/snippet.ts index ccef9043..94b7013c 100644 --- a/web/src/store/workspace/dispatchers/snippet.ts +++ b/web/src/store/workspace/dispatchers/snippet.ts @@ -12,6 +12,7 @@ import { import { newLoadingAction, newErrorAction, newUIStateChangeAction } from '~/store/actions/ui' import { type SnippetLoadPayload, WorkspaceAction, type BulkFileUpdatePayload } from '../actions' import { loadWorkspaceState } from '../config' +import { getDefaultWorkspaceState } from '../state' /** * Dispatch snippet load from a predefined source. @@ -54,9 +55,15 @@ export const dispatchLoadSnippetFromSource = (source: SnippetSource) => async (d export const dispatchLoadSnippet = (snippetId: string | null) => async (dispatch: DispatchFn, getState: StateProvider) => { if (!snippetId) { + const { + settings: { autoSave }, + workspace: { snippet }, + } = getState() + + const shouldAutosave = autoSave && !snippet?.id dispatch({ type: WorkspaceAction.WORKSPACE_IMPORT, - payload: loadWorkspaceState(), + payload: shouldAutosave ? loadWorkspaceState() : getDefaultWorkspaceState(), }) return } diff --git a/web/src/store/workspace/state.ts b/web/src/store/workspace/state.ts index 43be49e0..b69366f6 100644 --- a/web/src/store/workspace/state.ts +++ b/web/src/store/workspace/state.ts @@ -59,3 +59,13 @@ export const initialWorkspaceState: WorkspaceState = { export const defaultFiles = { [defaultFileName]: defaultFile, } + +export const getDefaultWorkspaceState = (): WorkspaceState => ({ + selectedFile: defaultFileName, + snippet: { + loading: false, + }, + files: { + [defaultFileName]: defaultFile, + }, +})