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: Refactor settings and make autosave optional #401

Merged
merged 5 commits into from
Aug 11, 2024
Merged
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
14 changes: 14 additions & 0 deletions web/src/components/elements/misc/Kbd/Kbd.tsx
Original file line number Diff line number Diff line change
@@ -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 <kbd className={style}>{children}</kbd>
}
1 change: 1 addition & 0 deletions web/src/components/elements/misc/Kbd/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Kbd'
88 changes: 54 additions & 34 deletions web/src/components/features/settings/SettingsModal.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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
Expand Down Expand Up @@ -40,7 +42,7 @@ interface SettingsModalState {

const modalStyles = {
main: {
maxWidth: 480,
maxWidth: 520,
},
}

Expand Down Expand Up @@ -100,6 +102,7 @@ class SettingsModal extends ThemeableComponent<Props, SettingsModalState> {

render() {
const { isOpen } = this.props
const { spacing } = getTheme()

return (
<Dialog label="Settings" onDismiss={() => this.onClose()} isOpen={isOpen} styles={modalStyles}>
Expand All @@ -120,14 +123,27 @@ class SettingsModal extends ThemeableComponent<Props, SettingsModalState> {
}
/>
<SettingsProperty
key="minimap"
title="Mini Map"
key="enableVimMode"
title="Enable Vim Mode"
control={
<Checkbox
label="Enable mini map on side"
defaultChecked={this.props.monaco?.minimap}
label="Enables Vim motions in code editor"
defaultChecked={this.props.settings?.enableVimMode}
onChange={(_, val) => {
this.touchMonacoProperty('minimap', val)
this.touchSettingsProperty({ enableVimMode: val })
}}
/>
}
/>
<SettingsProperty
key="enableAutoSave"
title="Enable Auto Save"
control={
<Checkbox
label="Restore last editor contents when playground opens"
defaultChecked={this.props.settings?.autoSave}
onChange={(_, val) => {
this.touchSettingsProperty({ autoSave: val })
}}
/>
}
Expand All @@ -137,7 +153,7 @@ class SettingsModal extends ThemeableComponent<Props, SettingsModalState> {
title="Use System Theme"
control={
<Checkbox
label="Follow system dark mode preference instead of manual toggle."
label="Match editor theme with system dark mode preference"
defaultChecked={this.props.settings?.useSystemTheme}
onChange={(_, val) => {
this.touchSettingsProperty({
Expand All @@ -161,14 +177,18 @@ class SettingsModal extends ThemeableComponent<Props, SettingsModalState> {
}
/>
<SettingsProperty
key="enableVimMode"
title="Enable Vim Mode"
key="mouseWheelZoom"
title="Mouse Wheel Zoom"
control={
<Checkbox
label="Allows usage of Vim key bindings when editing"
defaultChecked={this.props.settings?.enableVimMode}
defaultChecked={this.props.monaco?.mouseWheelZoom}
onRenderLabel={() => (
<Text className="ms-Checkbox-text" style={{ marginLeft: spacing.s2 }}>
Zoom the editor font when using mouse wheel and holding <Kbd>{controlKeyLabel()}</Kbd> key
</Text>
)}
onChange={(_, val) => {
this.touchSettingsProperty({ enableVimMode: val })
this.touchMonacoProperty('mouseWheelZoom', val)
}}
/>
}
Expand All @@ -192,7 +212,7 @@ class SettingsModal extends ThemeableComponent<Props, SettingsModalState> {
<SettingsProperty
key="cursorStyle"
title="Cursor Style"
description="Set the cursor style"
description="Controls the cursor style"
control={
<Dropdown
options={cursorLineOptions}
Expand All @@ -204,53 +224,53 @@ class SettingsModal extends ThemeableComponent<Props, SettingsModalState> {
}
/>
<SettingsProperty
key="selectOnLineNumbers"
title="Select On Line Numbers"
key="smoothScroll"
title="Smooth Scrolling"
control={
<Checkbox
label="Select corresponding line on line number click"
defaultChecked={this.props.monaco?.selectOnLineNumbers}
label="Controls whether the editor will scroll using an animation"
defaultChecked={this.props.monaco?.smoothScrolling}
onChange={(_, val) => {
this.touchMonacoProperty('cursorStyle', val)
this.touchMonacoProperty('smoothScrolling', val)
}}
/>
}
/>
<SettingsProperty
key="contextMenu"
title="Context Menu"
key="minimap"
title="Mini Map"
control={
<Checkbox
label="Enable editor context menu (on right click)"
defaultChecked={this.props.monaco?.contextMenu}
label="Controls whether the minimap is shown"
defaultChecked={this.props.monaco?.minimap}
onChange={(_, val) => {
this.touchMonacoProperty('contextMenu', val)
this.touchMonacoProperty('minimap', val)
}}
/>
}
/>
<SettingsProperty
key="smoothScroll"
title="Smooth Scrolling"
key="selectOnGutterClick"
title="Select On Gutter Click"
control={
<Checkbox
label="Enable that the editor animates scrolling to a position"
defaultChecked={this.props.monaco?.smoothScrolling}
label="Select corresponding line on line number click"
defaultChecked={this.props.monaco?.selectOnLineNumbers}
onChange={(_, val) => {
this.touchMonacoProperty('smoothScrolling', val)
this.touchMonacoProperty('cursorStyle', val)
}}
/>
}
/>
<SettingsProperty
key="mouseWheelZoom"
title="Mouse Wheel Zoom"
key="contextMenu"
title="Context Menu"
control={
<Checkbox
label="Zoom the font in the editor when using the mouse wheel in combination with holding Ctrl"
defaultChecked={this.props.monaco?.mouseWheelZoom}
label="Enable editor context menu (on right click)"
defaultChecked={this.props.monaco?.contextMenu}
onChange={(_, val) => {
this.touchMonacoProperty('mouseWheelZoom', val)
this.touchMonacoProperty('contextMenu', val)
}}
/>
}
Expand Down
9 changes: 9 additions & 0 deletions web/src/services/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { type MonacoSettings, defaultMonacoSettings } from './monaco'

const DARK_THEME_KEY = 'ui.darkTheme.enabled'
const USE_SYSTEM_THEME_KEY = 'ui.darkTheme.useSystem'
const AUTOSAVE_ENABLED = 'ui.autosave.enabled'
const RUN_TARGET_KEY = 'go.build.target'
const ENABLE_VIM_MODE_KEY = 'ms.monaco.vimModeEnabled'
const AUTOFORMAT_KEY = 'go.build.autoFormat'
Expand All @@ -31,6 +32,14 @@ const Config = {
this.setBoolean(DARK_THEME_KEY, enable)
},

get autoSave() {
return this.getBoolean(AUTOSAVE_ENABLED, false)
},

set autoSave(val: boolean) {
this.setBoolean(AUTOSAVE_ENABLED, val)
},

get useSystemTheme() {
return this.getBoolean(USE_SYSTEM_THEME_KEY, supportsPreferColorScheme())
},
Expand Down
17 changes: 16 additions & 1 deletion web/src/store/dispatchers/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -23,7 +24,7 @@ export function newMonacoParamsChangeDispatcher(changes: MonacoParamsChanges): D

export const newSettingsChangeDispatcher =
(changes: Partial<SettingsState>): Dispatcher =>
(dispatch: DispatchFn, _: StateProvider) => {
(dispatch: DispatchFn, getState: StateProvider) => {
if ('useSystemTheme' in changes) {
config.useSystemTheme = !!changes.useSystemTheme
changes.darkMode = isDarkModeEnabled()
Expand All @@ -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))
}

Expand Down
1 change: 1 addition & 0 deletions web/src/store/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { type SettingsState, type State, type StatusState, type PanelState, type

// TODO: move settings reducers and state to store/settings
const initialSettingsState: SettingsState = {
autoSave: config.autoSave,
darkMode: config.darkThemeEnabled,
autoFormat: true,
useSystemTheme: config.useSystemTheme,
Expand Down
1 change: 1 addition & 0 deletions web/src/store/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface StatusState {
export interface SettingsState {
darkMode: boolean
useSystemTheme: boolean
autoSave: boolean
autoFormat: boolean
enableVimMode: boolean
goProxyUrl: string
Expand Down
26 changes: 14 additions & 12 deletions web/src/store/workspace/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 }
}
24 changes: 18 additions & 6 deletions web/src/store/workspace/dispatchers/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -88,7 +100,7 @@ export const dispatchImportFile = (files: FileList) => async (dispatch: Dispatch
})
}

scheduleAutosave(getState)
scheduleWorkspaceAutosave(getState)
}

export const dispatchCreateFile =
Expand All @@ -112,7 +124,7 @@ export const dispatchCreateFile =
payload: [{ filename, content }],
})

scheduleAutosave(getState)
scheduleWorkspaceAutosave(getState)
}

export const dispatchRemoveFile = (filename: string) => (dispatch: DispatchFn, getState: StateProvider) => {
Expand All @@ -121,7 +133,7 @@ export const dispatchRemoveFile = (filename: string) => (dispatch: DispatchFn, g
payload: { filename },
})

scheduleAutosave(getState)
scheduleWorkspaceAutosave(getState)
}

export const dispatchUpdateFile =
Expand All @@ -131,7 +143,7 @@ export const dispatchUpdateFile =
payload: { filename, content },
})

scheduleAutosave(getState)
scheduleWorkspaceAutosave(getState)
}

export const dispatchImportSource = (files: Record<string, string>) => (dispatch: DispatchFn, _: StateProvider) => {
Expand Down
Loading
Loading