Skip to content

Commit 68e6094

Browse files
authored
feat: Refactor settings and make autosave optional (#401)
* fix: improve description of mouse zoom option * feat: improve settings options location * fix: improve mouse zoom config description * feat: expose autosave prop * feat: respect autosave prop
1 parent 77cf7fc commit 68e6094

File tree

12 files changed

+157
-56
lines changed

12 files changed

+157
-56
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { mergeStyles, useTheme } from '@fluentui/react'
2+
import React from 'react'
3+
4+
export const Kbd: React.FC = ({ children }) => {
5+
const { semanticColors } = useTheme()
6+
7+
const style = mergeStyles({
8+
padding: '1px 3px',
9+
borderRadius: '4px',
10+
background: semanticColors.disabledSubtext,
11+
})
12+
13+
return <kbd className={style}>{children}</kbd>
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Kbd'

web/src/components/features/settings/SettingsModal.tsx

+54-34
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react'
2-
import { Checkbox, Dropdown, type IPivotStyles, PivotItem, TextField } from '@fluentui/react'
2+
import { Checkbox, Dropdown, getTheme, type IPivotStyles, PivotItem, Text, TextField } from '@fluentui/react'
33

44
import { AnimatedPivot } from '~/components/elements/tabs/AnimatedPivot'
55
import { ThemeableComponent } from '~/components/utils/ThemeableComponent'
@@ -11,6 +11,8 @@ import type { RenderingBackend, TerminalSettings } from '~/store/terminal'
1111
import { connect, type StateDispatch, type MonacoParamsChanges, type SettingsState } from '~/store'
1212

1313
import { cursorBlinkOptions, cursorLineOptions, fontOptions, terminalBackendOptions } from './options'
14+
import { controlKeyLabel } from '~/utils/dom'
15+
import { Kbd } from '~/components/elements/misc/Kbd'
1416

1517
export interface SettingsChanges {
1618
monaco?: MonacoParamsChanges
@@ -40,7 +42,7 @@ interface SettingsModalState {
4042

4143
const modalStyles = {
4244
main: {
43-
maxWidth: 480,
45+
maxWidth: 520,
4446
},
4547
}
4648

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

101103
render() {
102104
const { isOpen } = this.props
105+
const { spacing } = getTheme()
103106

104107
return (
105108
<Dialog label="Settings" onDismiss={() => this.onClose()} isOpen={isOpen} styles={modalStyles}>
@@ -120,14 +123,27 @@ class SettingsModal extends ThemeableComponent<Props, SettingsModalState> {
120123
}
121124
/>
122125
<SettingsProperty
123-
key="minimap"
124-
title="Mini Map"
126+
key="enableVimMode"
127+
title="Enable Vim Mode"
125128
control={
126129
<Checkbox
127-
label="Enable mini map on side"
128-
defaultChecked={this.props.monaco?.minimap}
130+
label="Enables Vim motions in code editor"
131+
defaultChecked={this.props.settings?.enableVimMode}
129132
onChange={(_, val) => {
130-
this.touchMonacoProperty('minimap', val)
133+
this.touchSettingsProperty({ enableVimMode: val })
134+
}}
135+
/>
136+
}
137+
/>
138+
<SettingsProperty
139+
key="enableAutoSave"
140+
title="Enable Auto Save"
141+
control={
142+
<Checkbox
143+
label="Restore last editor contents when playground opens"
144+
defaultChecked={this.props.settings?.autoSave}
145+
onChange={(_, val) => {
146+
this.touchSettingsProperty({ autoSave: val })
131147
}}
132148
/>
133149
}
@@ -137,7 +153,7 @@ class SettingsModal extends ThemeableComponent<Props, SettingsModalState> {
137153
title="Use System Theme"
138154
control={
139155
<Checkbox
140-
label="Follow system dark mode preference instead of manual toggle."
156+
label="Match editor theme with system dark mode preference"
141157
defaultChecked={this.props.settings?.useSystemTheme}
142158
onChange={(_, val) => {
143159
this.touchSettingsProperty({
@@ -161,14 +177,18 @@ class SettingsModal extends ThemeableComponent<Props, SettingsModalState> {
161177
}
162178
/>
163179
<SettingsProperty
164-
key="enableVimMode"
165-
title="Enable Vim Mode"
180+
key="mouseWheelZoom"
181+
title="Mouse Wheel Zoom"
166182
control={
167183
<Checkbox
168-
label="Allows usage of Vim key bindings when editing"
169-
defaultChecked={this.props.settings?.enableVimMode}
184+
defaultChecked={this.props.monaco?.mouseWheelZoom}
185+
onRenderLabel={() => (
186+
<Text className="ms-Checkbox-text" style={{ marginLeft: spacing.s2 }}>
187+
Zoom the editor font when using mouse wheel and holding <Kbd>{controlKeyLabel()}</Kbd> key
188+
</Text>
189+
)}
170190
onChange={(_, val) => {
171-
this.touchSettingsProperty({ enableVimMode: val })
191+
this.touchMonacoProperty('mouseWheelZoom', val)
172192
}}
173193
/>
174194
}
@@ -192,7 +212,7 @@ class SettingsModal extends ThemeableComponent<Props, SettingsModalState> {
192212
<SettingsProperty
193213
key="cursorStyle"
194214
title="Cursor Style"
195-
description="Set the cursor style"
215+
description="Controls the cursor style"
196216
control={
197217
<Dropdown
198218
options={cursorLineOptions}
@@ -204,53 +224,53 @@ class SettingsModal extends ThemeableComponent<Props, SettingsModalState> {
204224
}
205225
/>
206226
<SettingsProperty
207-
key="selectOnLineNumbers"
208-
title="Select On Line Numbers"
227+
key="smoothScroll"
228+
title="Smooth Scrolling"
209229
control={
210230
<Checkbox
211-
label="Select corresponding line on line number click"
212-
defaultChecked={this.props.monaco?.selectOnLineNumbers}
231+
label="Controls whether the editor will scroll using an animation"
232+
defaultChecked={this.props.monaco?.smoothScrolling}
213233
onChange={(_, val) => {
214-
this.touchMonacoProperty('cursorStyle', val)
234+
this.touchMonacoProperty('smoothScrolling', val)
215235
}}
216236
/>
217237
}
218238
/>
219239
<SettingsProperty
220-
key="contextMenu"
221-
title="Context Menu"
240+
key="minimap"
241+
title="Mini Map"
222242
control={
223243
<Checkbox
224-
label="Enable editor context menu (on right click)"
225-
defaultChecked={this.props.monaco?.contextMenu}
244+
label="Controls whether the minimap is shown"
245+
defaultChecked={this.props.monaco?.minimap}
226246
onChange={(_, val) => {
227-
this.touchMonacoProperty('contextMenu', val)
247+
this.touchMonacoProperty('minimap', val)
228248
}}
229249
/>
230250
}
231251
/>
232252
<SettingsProperty
233-
key="smoothScroll"
234-
title="Smooth Scrolling"
253+
key="selectOnGutterClick"
254+
title="Select On Gutter Click"
235255
control={
236256
<Checkbox
237-
label="Enable that the editor animates scrolling to a position"
238-
defaultChecked={this.props.monaco?.smoothScrolling}
257+
label="Select corresponding line on line number click"
258+
defaultChecked={this.props.monaco?.selectOnLineNumbers}
239259
onChange={(_, val) => {
240-
this.touchMonacoProperty('smoothScrolling', val)
260+
this.touchMonacoProperty('cursorStyle', val)
241261
}}
242262
/>
243263
}
244264
/>
245265
<SettingsProperty
246-
key="mouseWheelZoom"
247-
title="Mouse Wheel Zoom"
266+
key="contextMenu"
267+
title="Context Menu"
248268
control={
249269
<Checkbox
250-
label="Zoom the font in the editor when using the mouse wheel in combination with holding Ctrl"
251-
defaultChecked={this.props.monaco?.mouseWheelZoom}
270+
label="Enable editor context menu (on right click)"
271+
defaultChecked={this.props.monaco?.contextMenu}
252272
onChange={(_, val) => {
253-
this.touchMonacoProperty('mouseWheelZoom', val)
273+
this.touchMonacoProperty('contextMenu', val)
254274
}}
255275
/>
256276
}

web/src/services/config/config.ts

+9
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { type MonacoSettings, defaultMonacoSettings } from './monaco'
99

1010
const DARK_THEME_KEY = 'ui.darkTheme.enabled'
1111
const USE_SYSTEM_THEME_KEY = 'ui.darkTheme.useSystem'
12+
const AUTOSAVE_ENABLED = 'ui.autosave.enabled'
1213
const RUN_TARGET_KEY = 'go.build.target'
1314
const ENABLE_VIM_MODE_KEY = 'ms.monaco.vimModeEnabled'
1415
const AUTOFORMAT_KEY = 'go.build.autoFormat'
@@ -31,6 +32,14 @@ const Config = {
3132
this.setBoolean(DARK_THEME_KEY, enable)
3233
},
3334

35+
get autoSave() {
36+
return this.getBoolean(AUTOSAVE_ENABLED, false)
37+
},
38+
39+
set autoSave(val: boolean) {
40+
this.setBoolean(AUTOSAVE_ENABLED, val)
41+
},
42+
3443
get useSystemTheme() {
3544
return this.getBoolean(USE_SYSTEM_THEME_KEY, supportsPreferColorScheme())
3645
},

web/src/store/dispatchers/settings.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
newSettingsChangeAction,
1313
newToggleThemeAction,
1414
} from '../actions'
15+
import { saveWorkspaceState, truncateWorkspaceState } from '../workspace/config'
1516

1617
export function newMonacoParamsChangeDispatcher(changes: MonacoParamsChanges): Dispatcher {
1718
return (dispatch: DispatchFn, _: StateProvider) => {
@@ -23,7 +24,7 @@ export function newMonacoParamsChangeDispatcher(changes: MonacoParamsChanges): D
2324

2425
export const newSettingsChangeDispatcher =
2526
(changes: Partial<SettingsState>): Dispatcher =>
26-
(dispatch: DispatchFn, _: StateProvider) => {
27+
(dispatch: DispatchFn, getState: StateProvider) => {
2728
if ('useSystemTheme' in changes) {
2829
config.useSystemTheme = !!changes.useSystemTheme
2930
changes.darkMode = isDarkModeEnabled()
@@ -37,6 +38,20 @@ export const newSettingsChangeDispatcher =
3738
config.enableVimMode = !!changes.enableVimMode
3839
}
3940

41+
if ('autoSave' in changes) {
42+
config.autoSave = !!changes.autoSave
43+
44+
// Immediately save workspace
45+
if (changes.autoSave) {
46+
const { workspace } = getState()
47+
if (!workspace.snippet?.id) {
48+
saveWorkspaceState(workspace)
49+
}
50+
} else {
51+
truncateWorkspaceState()
52+
}
53+
}
54+
4055
dispatch(newSettingsChangeAction(changes))
4156
}
4257

web/src/store/reducers.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { type SettingsState, type State, type StatusState, type PanelState, type
2727

2828
// TODO: move settings reducers and state to store/settings
2929
const initialSettingsState: SettingsState = {
30+
autoSave: config.autoSave,
3031
darkMode: config.darkThemeEnabled,
3132
autoFormat: true,
3233
useSystemTheme: config.useSystemTheme,

web/src/store/state.ts

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface StatusState {
2525
export interface SettingsState {
2626
darkMode: boolean
2727
useSystemTheme: boolean
28+
autoSave: boolean
2829
autoFormat: boolean
2930
enableVimMode: boolean
3031
goProxyUrl: string

web/src/store/workspace/config.ts

+14-12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@ const defaultWorkspace: WorkspaceState = {
88
files: defaultFiles,
99
}
1010

11+
const sanitizeState = (state: WorkspaceState) => {
12+
// Skip current snippet URL.
13+
const { selectedFile, files } = state
14+
15+
if (!files) {
16+
// Save defaults if ws is empty.
17+
return defaultWorkspace
18+
}
19+
20+
return { selectedFile, files }
21+
}
22+
23+
export const truncateWorkspaceState = () => config.delete(CONFIG_KEY)
24+
1125
export const saveWorkspaceState = (state: WorkspaceState) => {
1226
const sanitized = sanitizeState(state)
1327
if (!sanitized.files || Object.keys(sanitized.files).length === 0) {
@@ -20,15 +34,3 @@ export const saveWorkspaceState = (state: WorkspaceState) => {
2034
}
2135

2236
export const loadWorkspaceState = (): WorkspaceState => sanitizeState(config.getObject(CONFIG_KEY, defaultWorkspace))
23-
24-
const sanitizeState = (state: WorkspaceState) => {
25-
// Skip current snippet URL.
26-
const { selectedFile, files } = state
27-
28-
if (!files) {
29-
// Save defaults if ws is empty.
30-
return defaultWorkspace
31-
}
32-
33-
return { selectedFile, files }
34-
}

web/src/store/workspace/dispatchers/files.ts

+18-6
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,29 @@ import { type WorkspaceState, defaultFiles } from '../state'
1515

1616
const AUTOSAVE_INTERVAL = 1000
1717

18+
const isAutosaveEnabled = (getState: StateProvider) => {
19+
const { settings } = getState()
20+
return settings.autoSave
21+
}
22+
1823
let saveTimeout: NodeJS.Timeout
24+
1925
const cancelPendingAutosave = () => {
2026
clearTimeout(saveTimeout)
2127
}
22-
const scheduleAutosave = (getState: StateProvider) => {
28+
29+
const scheduleWorkspaceAutosave = (getState: StateProvider) => {
2330
cancelPendingAutosave()
31+
if (!isAutosaveEnabled(getState)) {
32+
return
33+
}
34+
2435
saveTimeout = setTimeout(() => {
2536
const {
37+
settings: { autoSave },
2638
workspace: { snippet, ...wp },
2739
} = getState()
28-
if (snippet) {
40+
if (snippet?.id || !autoSave) {
2941
// abort autosave when loaded external snippet.
3042
return
3143
}
@@ -88,7 +100,7 @@ export const dispatchImportFile = (files: FileList) => async (dispatch: Dispatch
88100
})
89101
}
90102

91-
scheduleAutosave(getState)
103+
scheduleWorkspaceAutosave(getState)
92104
}
93105

94106
export const dispatchCreateFile =
@@ -112,7 +124,7 @@ export const dispatchCreateFile =
112124
payload: [{ filename, content }],
113125
})
114126

115-
scheduleAutosave(getState)
127+
scheduleWorkspaceAutosave(getState)
116128
}
117129

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

124-
scheduleAutosave(getState)
136+
scheduleWorkspaceAutosave(getState)
125137
}
126138

127139
export const dispatchUpdateFile =
@@ -131,7 +143,7 @@ export const dispatchUpdateFile =
131143
payload: { filename, content },
132144
})
133145

134-
scheduleAutosave(getState)
146+
scheduleWorkspaceAutosave(getState)
135147
}
136148

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

0 commit comments

Comments
 (0)