Skip to content

Commit efd7735

Browse files
author
sunzhongyi
committed
feat: 实现 Flags 多级存储管理与 UI 操作优化
- 支持 Global (用户级) 和 Workspace (工作区级) 双重存储作用域 - 在 Theme 编辑行添加地球图标,支持通过图标切换存储作用域(默认为 Global) - 优化持久化逻辑,将工作区 Flags 直接同步到项目的 settings.json 文件中 - 新增 Flag 删除约束:若 Flag 下存在已配置的主题,则禁用删除按钮并置灰 - 优化 Webview 通信机制,通过事件驱动同步替代响应式全量同步,解决配置更新导致的页面闪烁问题
1 parent f51d871 commit efd7735

8 files changed

Lines changed: 211 additions & 28 deletions

File tree

extension.ts

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,17 @@ interface WebviewState {
1010
currentView: 'form' | 'text' | 'flags'
1111
commitData: any
1212
textContent: string
13-
flags?: Record<string, Record<string, { deadline?: string; docUrl?: string }>>
13+
}
14+
15+
interface FlagConfig {
16+
[theme: string]: {
17+
deadline?: string
18+
docUrl?: string
19+
}
20+
}
21+
22+
interface FlagsConfig {
23+
[flag: string]: FlagConfig
1424
}
1525

1626
// New interfaces for our settings structure
@@ -47,6 +57,17 @@ export function activate(context: vscode.ExtensionContext) {
4757
SettingsPanel.createOrShow(context)
4858
})
4959
)
60+
61+
// Listen for configuration changes
62+
context.subscriptions.push(
63+
vscode.workspace.onDidChangeConfiguration((e) => {
64+
if (e.affectsConfiguration('commitAssistant.flags')) {
65+
if (CommitEditorPanel.currentPanel) {
66+
CommitEditorPanel.currentPanel.refreshConfig()
67+
}
68+
}
69+
})
70+
)
5071
}
5172

5273
class CommitEditorPanel {
@@ -95,9 +116,15 @@ class CommitEditorPanel {
95116
vscode.window.showErrorMessage(message.text)
96117
return
97118
case 'saveState':
98-
// Save all state to workspace state
99-
this._context.workspaceState.update('state', message.state)
100-
console.log('State saved to workspaceState:', message.state);
119+
// Save UI state to workspace state
120+
this._context.workspaceState.update('state', {
121+
currentView: message.state.currentView,
122+
commitData: message.state.commitData,
123+
textContent: message.state.textContent
124+
})
125+
return
126+
case 'updateFlags':
127+
this._updateFlags(message.globalFlags, message.workspaceFlags)
101128
return
102129
case 'openSettings':
103130
vscode.commands.executeCommand('commitAssistant.openSettings')
@@ -128,6 +155,20 @@ class CommitEditorPanel {
128155
)
129156
}
130157

158+
private async _updateFlags(globalFlags: FlagsConfig, workspaceFlags: FlagsConfig) {
159+
const config = vscode.workspace.getConfiguration('commitAssistant')
160+
try {
161+
await config.update('flags', globalFlags, vscode.ConfigurationTarget.Global)
162+
await config.update('flags', workspaceFlags, vscode.ConfigurationTarget.Workspace)
163+
} catch (error: any) {
164+
vscode.window.showErrorMessage(`Failed to save flags: ${error.message}`)
165+
}
166+
}
167+
168+
public refreshConfig() {
169+
this._sendConfig()
170+
}
171+
131172
private _saveCommitMessage(commitMessage: string) {
132173
const gitExtension = vscode.extensions.getExtension('vscode.git')?.exports
133174
const git = gitExtension?.getAPI(1)
@@ -267,24 +308,20 @@ class CommitEditorPanel {
267308

268309
this._sendConfig()
269310

270-
// Load state from workspace and global storage
311+
// Load UI state from workspace state
271312
const storedState = this._context.workspaceState.get<WebviewState>('state')
272313

273-
// Merge workspace state with global flags
274314
const mergedState = storedState
275315
? {
276316
...storedState,
277-
flags: storedState.flags || {},
278317
}
279318
: {
280319
currentView: 'form',
281320
commitData: {},
282321
textContent: '',
283-
flags: {},
284322
}
285323

286324
// Send merged state to the webview
287-
console.log('State sent to webview:', mergedState);
288325
this._panel.webview.postMessage({ command: 'loadState', state: mergedState })
289326
}
290327

@@ -301,14 +338,36 @@ class CommitEditorPanel {
301338
...commitTypesFromConfig
302339
];
303340

304-
// We could also get flags from config in the future
341+
const inspect = config.inspect<FlagsConfig>('flags')
342+
const globalFlags = inspect?.globalValue || {}
343+
const workspaceFlags = inspect?.workspaceValue || {}
344+
345+
// Merge flags for the webview UI, adding scope info to each theme
346+
const mergedFlags: Record<string, Record<string, any>> = {}
347+
348+
// Add global flags
349+
for (const [flag, themes] of Object.entries(globalFlags)) {
350+
if (!mergedFlags[flag]) mergedFlags[flag] = {}
351+
for (const [theme, data] of Object.entries(themes)) {
352+
mergedFlags[flag][theme] = { ...data, scope: 'global' }
353+
}
354+
}
355+
356+
// Add workspace flags (overwrites or adds to global)
357+
for (const [flag, themes] of Object.entries(workspaceFlags)) {
358+
if (!mergedFlags[flag]) mergedFlags[flag] = {}
359+
for (const [theme, data] of Object.entries(themes)) {
360+
mergedFlags[flag][theme] = { ...data, scope: 'workspace' }
361+
}
362+
}
363+
305364
this._panel.webview.postMessage({
306365
command: 'loadConfig',
307366
config: {
308367
commitTypes,
309368
themeDeadline,
310369
preference,
311-
flags: {}, // Placeholder for future flag configuration
370+
flags: mergedFlags,
312371
},
313372
})
314373
}

package.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,28 @@
111111
}
112112
],
113113
"description": "The list of commit types to display in the type selector. Override this in your user or workspace settings."
114+
},
115+
"commitAssistant.flags": {
116+
"type": "object",
117+
"default": {},
118+
"description": "Custom flags for commit messages. Can be configured at global or workspace level.",
119+
"additionalProperties": {
120+
"type": "object",
121+
"additionalProperties": {
122+
"type": "object",
123+
"properties": {
124+
"deadline": {
125+
"type": "string",
126+
"format": "date",
127+
"description": "Optional deadline for this theme."
128+
},
129+
"docUrl": {
130+
"type": "string",
131+
"description": "Optional documentation URL for this theme."
132+
}
133+
}
134+
}
135+
}
114136
}
115137
}
116138
}

webviews/commit-editor/App.svelte

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,38 @@
2323
}
2424
2525
let textContent = ''
26-
let flags: Record<string, Record<string, { deadline?: string; docUrl?: string }>> = {}
26+
let flags: Record<string, Record<string, { deadline?: string; docUrl?: string; scope?: 'global' | 'workspace' }>> = {}
2727
let commitTypes: { value: string; label: string; description?: string }[] = []
2828
let themeDeadlineConfig: ThemeDeadlineConfig = DEFAULT_THEME_DEADLINE_CONFIG
2929
let preference = { loadingEffect: 'creative' };
3030
let preview = ''
3131
let isAiLoading = false
3232
33+
function syncFlags(currentFlags: typeof flags) {
34+
const globalFlags: any = {}
35+
const workspaceFlags: any = {}
36+
37+
for (const [flag, themes] of Object.entries(currentFlags)) {
38+
for (const [theme, data] of Object.entries(themes)) {
39+
const { scope, ...rest } = data
40+
const target = scope === 'workspace' ? workspaceFlags : globalFlags
41+
if (!target[flag]) target[flag] = {}
42+
target[flag][theme] = rest
43+
}
44+
}
45+
46+
vscode.postMessage({
47+
command: 'updateFlags',
48+
globalFlags,
49+
workspaceFlags
50+
})
51+
}
52+
53+
function handleFlagsChange(event: CustomEvent) {
54+
flags = event.detail
55+
syncFlags(flags)
56+
}
57+
3358
function generateCommitFromForm() {
3459
const { type, scope, description, body, footer, selectedFlags } = commitData
3560
if (!type || !description) return ''
@@ -107,7 +132,6 @@
107132
commitData.selectedFlags = loadedCommitData.selectedFlags || []
108133
}
109134
textContent = state.textContent || ''
110-
flags = state.flags || {}
111135
break
112136
case 'loadConfig':
113137
flags = message.config.flags || {}
@@ -156,15 +180,13 @@
156180
}
157181
preview = message
158182
}
159-
// This makes the block reactive to commitData and flags
183+
// This makes the block reactive to commitData
160184
JSON.stringify(commitData)
161-
JSON.stringify(flags)
162185
163-
// Post the state to the extension host
164-
console.log('Saving state with flags:', flags);
186+
// Post the state to the extension host (without flags)
165187
vscode.postMessage({
166188
command: 'saveState',
167-
state: { currentView, commitData, textContent, flags },
189+
state: { currentView, commitData, textContent },
168190
})
169191
}
170192
</script>
@@ -211,7 +233,7 @@
211233
on:openUrl={handleOpenUrl}
212234
/>
213235
{:else if currentView === 'flags'}
214-
<FlagsView bind:flags {themeDeadlineConfig} on:openUrl={handleOpenUrl} />
236+
<FlagsView bind:flags {themeDeadlineConfig} on:openUrl={handleOpenUrl} on:change={handleFlagsChange} />
215237
{/if}
216238
</div>
217239

webviews/commit-editor/components/FlagItem.svelte

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,25 @@
5757
<div class="rounded-md" style="background-color: var(--vscode-sideBar-background);">
5858
<div class="flex justify-between items-center py-1.5 pl-2.5 pr-1">
5959
<h4 class="text-sm font-semibold">{flagName}</h4>
60-
<button on:click={deleteFlag} class="delete-flag-button">
60+
<button
61+
on:click={deleteFlag}
62+
class="delete-flag-button"
63+
disabled={Object.keys(themes).length > 0}
64+
title={Object.keys(themes).length > 0 ? "Cannot delete flag with themes" : "Delete flag"}
65+
>
6166
<Trash className="w-3.5 h-3.5" />
6267
</button>
6368
</div>
6469

6570
<div class="pl-4 pr-1 pb-1.5">
6671
<div class="space-y-0.5">
67-
{#each Object.entries(themes) as [themeName, { deadline, docUrl }]}
72+
{#each Object.entries(themes) as [themeName, data]}
6873
<ThemeItem
6974
{flagName}
7075
{themeName}
71-
{deadline}
72-
{docUrl}
76+
deadline={data.deadline}
77+
docUrl={data.docUrl}
78+
scope={data.scope}
7379
{themeDeadlineConfig}
7480
on:updateTheme={updateTheme}
7581
on:deleteTheme={deleteTheme}
@@ -140,6 +146,13 @@
140146
background-color: rgba(var(--vscode-editor-foreground-rgb), 0.15);
141147
opacity: 0.8;
142148
}
149+
.delete-flag-button:disabled {
150+
opacity: 0.3;
151+
cursor: not-allowed;
152+
}
153+
.delete-flag-button:disabled:hover {
154+
background-color: rgba(var(--vscode-editor-foreground-rgb), 0.1);
155+
}
143156
.deadline-input {
144157
box-sizing: border-box;
145158
background-color: var(--vscode-input-background);

webviews/commit-editor/components/FlagsView.svelte

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,35 +19,40 @@
1919
flags[newFlagName] = {};
2020
flags = flags; // Trigger reactivity
2121
newFlagName = '';
22+
dispatch('change', flags);
2223
}
2324
}
2425
2526
function handleAddTheme(event: CustomEvent) {
26-
const { flagName, themeName, deadline, docUrl } = event.detail;
27+
const { flagName, themeName, deadline, docUrl, scope } = event.detail;
2728
if (themeName && !flags[flagName][themeName]) {
28-
flags[flagName][themeName] = { deadline, docUrl };
29+
flags[flagName][themeName] = { deadline, docUrl, scope: scope || 'global' };
2930
flags = { ...flags }; // Trigger reactivity
31+
dispatch('change', flags);
3032
}
3133
}
3234
3335
function handleUpdateTheme(event: CustomEvent) {
34-
const { flagName, themeName, deadline, docUrl } = event.detail;
36+
const { flagName, themeName, deadline, docUrl, scope } = event.detail;
3537
if (flags[flagName] && flags[flagName][themeName]) {
36-
flags[flagName][themeName] = { ...flags[flagName][themeName], deadline, docUrl };
38+
flags[flagName][themeName] = { ...flags[flagName][themeName], deadline, docUrl, scope };
3739
flags = { ...flags };
40+
dispatch('change', flags);
3841
}
3942
}
4043
4144
function handleDeleteTheme(event: CustomEvent) {
4245
const { flagName, themeName } = event.detail;
4346
delete flags[flagName][themeName];
4447
flags = { ...flags }; // Trigger reactivity
48+
dispatch('change', flags);
4549
}
4650
4751
function handleDeleteFlag(event: CustomEvent) {
4852
const flagName = event.detail;
4953
delete flags[flagName];
5054
flags = flags; // Trigger reactivity
55+
dispatch('change', flags);
5156
}
5257
5358
function handleOpenUrl(event: CustomEvent) {

webviews/commit-editor/components/ThemeItem.svelte

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import Minus from './icons/Minus.svelte';
44
import Edit from './icons/Edit.svelte';
55
import Check from './icons/Check.svelte';
6+
import Globe from './icons/Globe.svelte';
67
import {
78
type ThemeDeadlineConfig,
89
calculateDeadlineStatus,
@@ -13,6 +14,7 @@
1314
export let themeName: string;
1415
export let deadline: string | undefined;
1516
export let docUrl: string | undefined;
17+
export let scope: 'global' | 'workspace' = 'global';
1618
export let themeDeadlineConfig: ThemeDeadlineConfig;
1719
1820
const dispatch = createEventDispatcher();
@@ -25,7 +27,12 @@
2527
$: colors = getDeadlineColors(status, themeDeadlineConfig);
2628
2729
function updateTheme() {
28-
dispatch('updateTheme', { flagName, themeName, deadline: currentDeadline, docUrl });
30+
dispatch('updateTheme', { flagName, themeName, deadline: currentDeadline, docUrl, scope });
31+
}
32+
33+
function toggleScope() {
34+
scope = scope === 'global' ? 'workspace' : 'global';
35+
updateTheme();
2936
}
3037
3138
function deleteTheme() {
@@ -101,6 +108,14 @@
101108
on:change={updateTheme}
102109
/>
103110

111+
<button
112+
on:click={toggleScope}
113+
class="scope-toggle ml-1 {scope}"
114+
title={scope === 'global' ? 'Global (User Settings)' : 'Workspace (settings.json)'}
115+
>
116+
<Globe className="w-3.5 h-3.5" />
117+
</button>
118+
104119
<button on:click={deleteTheme} class="delete-theme-button ml-1">
105120
<Minus className="w-3.5 h-3.5" />
106121
</button>
@@ -187,6 +202,18 @@
187202
.url-text-link:hover {
188203
opacity: 0.8;
189204
}
205+
.scope-toggle {
206+
color: var(--vscode-editor-foreground);
207+
background-color: transparent;
208+
opacity: 0.25;
209+
}
210+
.scope-toggle.global {
211+
opacity: 0.9;
212+
}
213+
.scope-toggle:hover {
214+
background-color: rgba(var(--vscode-editor-foreground-rgb), 0.1);
215+
opacity: 1;
216+
}
190217
.delete-theme-button {
191218
color: var(--vscode-editor-foreground);
192219
background-color: rgba(var(--vscode-editor-foreground-rgb), 0.1);

0 commit comments

Comments
 (0)