From b7c01059e92d1e02f220e146f155b55734ba52c0 Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Thu, 8 Sep 2022 16:59:27 +0200 Subject: [PATCH 1/2] Implementation of StateManager --- packages/utils/src/extension.ts | 58 ++++----------- packages/utils/src/state/manager.ts | 60 ++++++++++++++++ packages/utils/src/state/provider/index.ts | 16 +++++ packages/utils/src/state/provider/stateful.ts | 25 +++++++ packages/utils/src/state/provider/vscode.ts | 71 +++++++++++++++++++ packages/utils/src/state/types.ts | 27 +++++++ packages/widget-news/src/extension.ts | 6 +- packages/widget-projects/src/extension.ts | 16 ++--- packages/widget-todo/src/extension.ts | 18 ++--- 9 files changed, 231 insertions(+), 66 deletions(-) create mode 100644 packages/utils/src/state/manager.ts create mode 100644 packages/utils/src/state/provider/index.ts create mode 100644 packages/utils/src/state/provider/stateful.ts create mode 100644 packages/utils/src/state/provider/vscode.ts create mode 100644 packages/utils/src/state/types.ts diff --git a/packages/utils/src/extension.ts b/packages/utils/src/extension.ts index c0c33db1..55af441f 100644 --- a/packages/utils/src/extension.ts +++ b/packages/utils/src/extension.ts @@ -9,6 +9,7 @@ import { closest } from 'fastest-levenshtein' import { Logger } from './logger' import { GitProvider } from './provider/git' +import { StateManager } from './state/manager' import { DEFAULT_CONFIGURATION, DEFAULT_STATE, DEPRECATED_GLOBAL_STORE_KEY, EXTENSION_ID, pkg } from './constants' import { WorkspaceType, ProjectItem } from './types' import type { Configuration, State, Workspace, ProjectItemTypes } from './types' @@ -19,8 +20,9 @@ const TELEMETRY_CONFIG_ID = 'telemetry' const TELEMETRY_CONFIG_ENABLED_ID = 'enableTelemetry' export default class ExtensionManager extends EventEmitter implements vscode.Disposable { + #stateManager: StateManager + protected _tangle?: Client - protected _state: State protected _configuration: Configuration protected _disposables: vscode.Disposable[] = [ vscode.workspace.onDidChangeConfiguration(this._onConfigChange.bind(this)) @@ -37,21 +39,13 @@ export default class ExtensionManager extends EventEmitter private _defaultState: State ) { super() + this.#stateManager = new StateManager(this._context, this._key, this._defaultState) + this._context.subscriptions.push(this.#stateManager) + this._gitProvider = this._context.subscriptions.find((s) => s instanceof GitProvider) as GitProvider const config = vscode.workspace.getConfiguration('marquee') const oldGlobalStore = this._context.globalState.get(DEPRECATED_GLOBAL_STORE_KEY, {}) - this._state = { - ...this._defaultState, - ...pick(oldGlobalStore, Object.keys(this._defaultState as any)), - ...this._context.globalState.get(this._key) - } - - /** - * preserve state across different machines - */ - this._context.globalState.setKeysForSync(Object.keys(this._defaultState as any)) - this._configuration = { ...this._defaultConfiguration, ...config.get(this._key), @@ -60,7 +54,7 @@ export default class ExtensionManager extends EventEmitter } get state () { - return this._state + return this.#stateManager.state } get configuration () { @@ -144,35 +138,11 @@ export default class ExtensionManager extends EventEmitter this._isConfigUpdateListenerDisabled = false } - /** - * Update extension state - * @param prop state property name - * @param val new state property value - * @param broadcastState set to true if you want to broadcast this state change - * (only needed when updating state from the extension host) - */ async updateState (prop: T, val: State[T], broadcastState?: boolean) { - /** - * check if we have to update - */ - if ( - typeof val !== 'undefined' && - typeof this._state[prop] !== 'undefined' && - hash(this._state[prop] as any) === hash(val as any) - ) { - return - } - - Logger.info(`Update state "${prop.toString()}": ${val as any as string}`) - this._state[prop] = val - await this.emitStateUpdate(broadcastState) - } - - async emitStateUpdate (broadcastState?: boolean) { - await this._context.globalState.update(this._key, this._state) - this.emit('stateUpdate', this._state) + await this.#stateManager.updateState(prop, val) + this.emit('stateUpdate', this.#stateManager.state) if (broadcastState && this._tangle) { - this._tangle.broadcast(this._state as State & Configuration) + this._tangle.broadcast(this.#stateManager.state as State & Configuration) } } @@ -183,10 +153,7 @@ export default class ExtensionManager extends EventEmitter this._isConfigUpdateListenerDisabled = true const config = vscode.workspace.getConfiguration('marquee') - this._state = { ...this._defaultState } - await this._context.globalState.update(this._key, this._state) - await this._context.globalState.update(DEPRECATED_GLOBAL_STORE_KEY, undefined) - this.emit('stateUpdate', this._state) + // this.emit('stateUpdate', this._state) this._configuration = { ...this._defaultConfiguration } await Promise.all( @@ -371,8 +338,7 @@ export default class ExtensionManager extends EventEmitter ) } - this._state[itemName as keyof State] = [modifiedItem, ...otherItems] as any as State[keyof State] - return this.emitStateUpdate(true) + return this.updateState(itemName as keyof State, [modifiedItem, ...otherItems] as any as State[keyof State], true) } reset () { diff --git a/packages/utils/src/state/manager.ts b/packages/utils/src/state/manager.ts new file mode 100644 index 00000000..f8e9dec1 --- /dev/null +++ b/packages/utils/src/state/manager.ts @@ -0,0 +1,60 @@ +import { Disposable, ExtensionContext } from 'vscode' + +import provider from './provider' +import { VSCodeState } from './provider/vscode' +import { Logger } from '../logger' +import type { StateProvider } from './types' + +export class StateManager implements Disposable { + #state?: State + #logger: Logger + #context: ExtensionContext + #provider: StateProvider[] = [] + + constructor (context: ExtensionContext, key: string, defaultState: State) { + this.#logger = Logger.getChildLogger('StateManager') + this.#context = context + this.#provider.push( + /** + * always + */ + new VSCodeState(this.#context, key, defaultState), + ...(this.#context.workspaceState.get('stateProvider') || []) + .filter((p) => Boolean(provider[p])) + .map((p) => new provider[p](context, key, defaultState)) + ) + } + + get state (): State { + if (!this.state) { + throw new Error('StateManager is not in sync') + } + + return this.state + } + + async updateState (prop: T, val: State[T]) { + for (const provider of this.#provider) { + await provider.updateState(prop, val) + } + } + + async clearState () { + for (const provider of this.#provider) { + await provider.clear() + } + } + + /** + * Downloads current State from providers and merge them if necessary + */ + async sync () { + for (const provider of this.#provider) { + await provider.sync() + } + } + + dispose () { + // nothing to do + } +} diff --git a/packages/utils/src/state/provider/index.ts b/packages/utils/src/state/provider/index.ts new file mode 100644 index 00000000..fcc3003d --- /dev/null +++ b/packages/utils/src/state/provider/index.ts @@ -0,0 +1,16 @@ +import type { ExtensionContext } from 'vscode' + +import { StatefulState } from './stateful' +import type { StateProvider } from '../types' + +type StateProviderClass = new ( + context: ExtensionContext, + key: string, + defaultState: any +) => StateProvider + +const provider: Record = { + stateful: StatefulState +} + +export default provider diff --git a/packages/utils/src/state/provider/stateful.ts b/packages/utils/src/state/provider/stateful.ts new file mode 100644 index 00000000..9c957e5c --- /dev/null +++ b/packages/utils/src/state/provider/stateful.ts @@ -0,0 +1,25 @@ +import type { ExtensionContext } from 'vscode' +import type { StateProvider } from '../types' + +export class StatefulState implements StateProvider { + #state: State = {} as State + + constructor (public context: ExtensionContext) {} + + get state () { + return this.#state + } + + async updateState (/*key: keyof State, value: State[keyof State]*/): Promise { + // throw new Error('Method not implemented.') + } + getState (): State { + throw new Error('Method not implemented.') + } + sync (): State { + throw new Error('Method not implemented.') + } + clear (): Promise { + throw new Error('Method not implemented.') + } +} diff --git a/packages/utils/src/state/provider/vscode.ts b/packages/utils/src/state/provider/vscode.ts new file mode 100644 index 00000000..b7a8950c --- /dev/null +++ b/packages/utils/src/state/provider/vscode.ts @@ -0,0 +1,71 @@ +import pick from 'lodash.pick' +import hash from 'object-hash' +import type { ExtensionContext } from 'vscode' + +import { Logger } from '../../logger' +import { DEPRECATED_GLOBAL_STORE_KEY } from '../../constants' +import type { StateProvider } from '../types' + +export class VSCodeState implements StateProvider { + #state: State + + constructor ( + public context: ExtensionContext, + private _key: string, + private _defaultState: State + ) { + const oldGlobalStore = this.context.globalState.get(DEPRECATED_GLOBAL_STORE_KEY, {}) + this.#state = { + ...this._defaultState, + ...pick(oldGlobalStore, Object.keys(this._defaultState as any)), + ...this.context.globalState.get(this._key) + } + + /** + * preserve state across different machines + */ + this.context.globalState.setKeysForSync(Object.keys(this._defaultState as any)) + } + + get state () { + return this.#state + } + + /** + * Update extension state + * @param prop state property name + * @param val new state property value + * @param broadcastState set to true if you want to broadcast this state change + * (only needed when updating state from the extension host) + */ + async updateState (prop: T, val: State[T]) { + /** + * check if we have to update + */ + if ( + typeof val !== 'undefined' && + typeof this.#state[prop] !== 'undefined' && + hash(this.#state[prop] as any) === hash(val as any) + ) { + return + } + + Logger.info(`Update state "${prop.toString()}": ${val as any as string}`) + this.#state[prop] = val + await this.context.globalState.update(this._key, this.#state) + } + + async clear () { + this.#state = { ...this._defaultState } + await this.context.globalState.update(this._key, this.#state) + await this.context.globalState.update(DEPRECATED_GLOBAL_STORE_KEY, undefined) + } + + getState (): State { + throw new Error('Method not implemented.') + } + + sync (): State { + return this.state + } +} diff --git a/packages/utils/src/state/types.ts b/packages/utils/src/state/types.ts new file mode 100644 index 00000000..b18e997e --- /dev/null +++ b/packages/utils/src/state/types.ts @@ -0,0 +1,27 @@ +import { ExtensionContext } from 'vscode' + +export abstract class StateProvider { + abstract context: ExtensionContext + abstract state: State + + /** + * Allows to sync a specific key value pair with provider + * @param key state key + * @param value state value + */ + abstract updateState (key: keyof State, value: State[keyof State]): Promise + /** + * Downloads the latest state from the provider and returns it + */ + abstract getState (): State + /** + * Gets the provider values and compares them with local ones. If a conflict + * is detected the value of the environment that was last updated is picked. + * ToDo(Christian): make this configurable, e.g. allow to get asked etc. + */ + abstract sync (): State + /** + * Clears the state of the provider + */ + abstract clear (): Promise +} diff --git a/packages/widget-news/src/extension.ts b/packages/widget-news/src/extension.ts index 376e4463..77563117 100644 --- a/packages/widget-news/src/extension.ts +++ b/packages/widget-news/src/extension.ts @@ -53,16 +53,16 @@ export class NewsExtensionManager extends ExtensionManager await this.updateState('isFetching', true) try { - let url = this._configuration.feeds[this._state.channel] + let url = this._configuration.feeds[this.state.channel] if (!url) { await this.updateState('channel', Object.keys(this._configuration.feeds)[0], true) throw new Error( - `Channel "${this._state.channel}" not found, ` + + `Channel "${this.state.channel}" not found, ` + `available channels are ${Object.keys(this._configuration.feeds).join(', ')}` ) } - this.#logger.info(`Fetch News ("${this._state.channel}") from ${url}`) + this.#logger.info(`Fetch News ("${this.state.channel}") from ${url}`) const feed = await this._parser.parseURL(url) await this.updateState('news', feed.items as FeedItem[]) diff --git a/packages/widget-projects/src/extension.ts b/packages/widget-projects/src/extension.ts index d9c6d2b4..5cdd8bac 100644 --- a/packages/widget-projects/src/extension.ts +++ b/packages/widget-projects/src/extension.ts @@ -27,7 +27,7 @@ export class ProjectsExtensionManager extends ExtensionManager ws.id === aws.id) && + !this.state.workspaces.find((ws: any) => ws.id === aws.id) && /** * we are not running on a remote machine, this is necessary * because we aren't able to connect to remote VS Code instances @@ -36,19 +36,19 @@ export class ProjectsExtensionManager extends ExtensionManager w.id === id)) { + if (!this.state.workspaces.find((w) => w.id === id)) { delete visitCount[id] } } const lastVisited = { - ...this._state.lastVisited, + ...this.state.lastVisited, [aws.id]: Date.now() } @@ -70,7 +70,7 @@ export class ProjectsExtensionManager extends ExtensionManager w.id === id)) { + if (!this.state.workspaces.find((w) => w.id === id)) { delete lastVisited[id] } } diff --git a/packages/widget-todo/src/extension.ts b/packages/widget-todo/src/extension.ts index 205e1da6..4fc62605 100644 --- a/packages/widget-todo/src/extension.ts +++ b/packages/widget-todo/src/extension.ts @@ -248,9 +248,9 @@ export class TodoExtensionManager extends ExtensionManager return console.warn(`Couldn't find todo to toggle with id "${item.id}"`) } - this.state.todos[todoIndex].checked = !item.checked - this.emitStateUpdate() - this.broadcast({ todos: this.state.todos }) + const todos = [...this.state.todos] + todos[todoIndex].checked = !item.checked + return this.updateState('todos', todos, true) } /** @@ -265,9 +265,9 @@ export class TodoExtensionManager extends ExtensionManager return console.warn(`Couldn't find todo to toggle with id "${item.id}"`) } - this.state.todos[todoIndex].archived = !item.archived - this.emitStateUpdate() - this.broadcast({ todos: this.state.todos }) + const todos = [...this.state.todos] + todos[todoIndex].archived = !item.archived + return this.updateState('todos', todos, true) } /** @@ -288,9 +288,9 @@ export class TodoExtensionManager extends ExtensionManager return console.warn(`Couldn't find todo to toggle with id "${item.id}"`) } - this.state.todos[todoIndex].workspaceId = awsp.id - this.emitStateUpdate() - this.broadcast({ todos: this.state.todos }) + const todos = [...this.state.todos] + todos[todoIndex].workspaceId = awsp.id + return this.updateState('todos', todos, true) } } From 9062822ffeb70a23abf4b66c1f8b751a3d8af2dc Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Sat, 10 Sep 2022 00:55:23 +0200 Subject: [PATCH 2/2] have a central state manager --- CONTRIBUTING.md | 2 +- packages/extension/src/extension.ts | 2 +- packages/extension/src/gui.view.ts | 2 +- .../src/{stateManager.ts => state/manager.ts} | 148 ++++++++++++++---- .../src/state/provider/index.ts | 6 +- .../extension/src/state/provider/stateful.ts | 14 ++ .../extension/src/state/provider/vscode.ts | 14 ++ packages/extension/src/state/types.ts | 98 ++++++++++++ packages/extension/src/tree.view.ts | 2 +- packages/extension/src/utils.ts | 27 ++-- packages/utils/src/extension.ts | 46 +++--- packages/utils/src/state/manager.ts | 60 ------- packages/utils/src/state/provider/stateful.ts | 25 --- packages/utils/src/state/provider/vscode.ts | 71 --------- packages/utils/src/state/types.ts | 27 ---- packages/utils/src/types.ts | 6 + packages/widget-github/src/extension.ts | 8 +- packages/widget-github/src/types.ts | 2 + packages/widget-markdown/src/extension.ts | 4 +- packages/widget-news/src/extension.ts | 4 +- packages/widget-notes/src/extension.ts | 2 +- packages/widget-npm-stats/src/extension.ts | 4 +- packages/widget-projects/src/extension.ts | 2 +- packages/widget-snippets/src/constants.ts | 1 - packages/widget-snippets/src/extension.ts | 4 +- packages/widget-todo/src/extension.ts | 2 +- packages/widget-weather/src/extension.ts | 4 +- packages/widget-weather/src/types.ts | 2 + packages/widget-welcome/src/extension.ts | 4 +- 29 files changed, 323 insertions(+), 270 deletions(-) rename packages/extension/src/{stateManager.ts => state/manager.ts} (63%) rename packages/{utils => extension}/src/state/provider/index.ts (67%) create mode 100644 packages/extension/src/state/provider/stateful.ts create mode 100644 packages/extension/src/state/provider/vscode.ts create mode 100644 packages/extension/src/state/types.ts delete mode 100644 packages/utils/src/state/manager.ts delete mode 100644 packages/utils/src/state/provider/stateful.ts delete mode 100644 packages/utils/src/state/provider/vscode.ts delete mode 100644 packages/utils/src/state/types.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f55bd9f6..33ce5ccc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -246,7 +246,7 @@ import ExtensionManager from '@vscode-marquee/utils/extension' import { DEFAULT_CONFIGURATION, DEFAULT_STATE } from './constants' import type { Configuration, State } from './types' -const STATE_KEY = 'widgets.foobar' +export const STATE_KEY = 'widgets.foobar' export function activate ( context: vscode.ExtensionContext, diff --git a/packages/extension/src/extension.ts b/packages/extension/src/extension.ts index 894bf350..adb953b5 100644 --- a/packages/extension/src/extension.ts +++ b/packages/extension/src/extension.ts @@ -6,7 +6,7 @@ import type { MarqueeEvents } from '@vscode-marquee/utils' import type { Snippet } from '@vscode-marquee/widget-snippets/extension' import telemetry from './telemetry' -import StateManager from './stateManager' +import StateManager from './state/manager' import { MarqueeGui } from './gui.view' import { TreeView } from './tree.view' import { ContextMenu } from './tree.view' diff --git a/packages/extension/src/gui.view.ts b/packages/extension/src/gui.view.ts index ff4df24e..3ed91520 100644 --- a/packages/extension/src/gui.view.ts +++ b/packages/extension/src/gui.view.ts @@ -10,7 +10,7 @@ import type { Client } from 'tangle' import type { MarqueeEvents } from '@vscode-marquee/utils' import telemetry from './telemetry' -import StateManager from './stateManager' +import StateManager from './state/manager' import { DEFAULT_STATE } from './utils' import { DEFAULT_FONT_SIZE, THIRD_PARTY_EXTENSION_DIR } from './constants' import type { ExtensionConfiguration, ExtensionExport } from './types' diff --git a/packages/extension/src/stateManager.ts b/packages/extension/src/state/manager.ts similarity index 63% rename from packages/extension/src/stateManager.ts rename to packages/extension/src/state/manager.ts index 2c125790..8a9ad88d 100644 --- a/packages/extension/src/stateManager.ts +++ b/packages/extension/src/state/manager.ts @@ -1,44 +1,80 @@ import vscode from 'vscode' +import pick from 'lodash.pick' import ExtensionManager, { Logger, + STATE_KEY as keyUtils, pkg as packageJson, activate as activateUtils, State as GlobalState, + StateManager as WidgetStateManager, Configuration as GlobalConfiguration, DEPRECATED_GLOBAL_STORE_KEY, MarqueeEvents } from '@vscode-marquee/utils/extension' -import { activate as activateWelcomeWidget } from '@vscode-marquee/widget-welcome/extension' -import { activate as activateNewsWidget } from '@vscode-marquee/widget-news/extension' -import { activate as activateProjectsWidget, ProjectsExtensionManager } from '@vscode-marquee/widget-projects/extension' -import { activate as activateGitHubWidget } from '@vscode-marquee/widget-github/extension' -import { activate as activateWeatherWidget } from '@vscode-marquee/widget-weather/extension' -import { activate as activateTodoWidget, TodoExtensionManager } from '@vscode-marquee/widget-todo/extension' -import { activate as activateMarkdownWidget, MarkdownExtensionManager } from '@vscode-marquee/widget-markdown/extension' -import { activate as activateNotesWidget, NoteExtensionManager } from '@vscode-marquee/widget-notes/extension' -import { activate as activateNPMStatsWidget } from '@vscode-marquee/widget-npm-stats/extension' -import { activate as activateSnippetsWidget, SnippetExtensionManager } from '@vscode-marquee/widget-snippets/extension' - -import telemetry from './telemetry' -import { activateGUI, GUIExtensionManager } from './utils' -import { FILE_FILTER, CONFIG_FILE_TYPE } from './constants' -import type { ExtensionExport } from './types' +import { + STATE_KEY as keyWelcomeWidget, + activate as activateWelcomeWidget +} from '@vscode-marquee/widget-welcome/extension' +import { + STATE_KEY as keyNewsWidget, + activate as activateNewsWidget +} from '@vscode-marquee/widget-news/extension' +import { + STATE_KEY as keyProjectsWidget, + activate as activateProjectsWidget, ProjectsExtensionManager +} from '@vscode-marquee/widget-projects/extension' +import { + STATE_KEY as keyGitHubWidget, + activate as activateGitHubWidget +} from '@vscode-marquee/widget-github/extension' +import { + STATE_KEY as keyWeatherWidget, + activate as activateWeatherWidget +} from '@vscode-marquee/widget-weather/extension' +import { + STATE_KEY as keyTodoWidget, + activate as activateTodoWidget, TodoExtensionManager +} from '@vscode-marquee/widget-todo/extension' +import { + STATE_KEY as keyMarkdownWidget, + activate as activateMarkdownWidget, MarkdownExtensionManager +} from '@vscode-marquee/widget-markdown/extension' +import { + STATE_KEY as keyNotesWidget, + activate as activateNotesWidget, NoteExtensionManager +} from '@vscode-marquee/widget-notes/extension' +import { + STATE_KEY as keyNPMStatsWidget, + activate as activateNPMStatsWidget +} from '@vscode-marquee/widget-npm-stats/extension' +import { + STATE_KEY as keySnippetsWidget, + activate as activateSnippetsWidget, SnippetExtensionManager +} from '@vscode-marquee/widget-snippets/extension' + +import provider from './provider' +import telemetry from '../telemetry' +import { activateGUI, GUIExtensionManager, STATE_KEY as keyGUI } from '../utils' +import { FILE_FILTER, CONFIG_FILE_TYPE } from '../constants' +import { VSCodeState } from './provider/vscode' +import type { StateProvider, WidgetKeys, WidgetStates } from './types' +import type { ExtensionExport } from '../types' const MARQUEE_WIDGETS = { - '@vscode-marquee/utils': activateUtils, - '@vscode-marquee/gui': activateGUI, - '@vscode-marquee/welcome-widget': activateWelcomeWidget, - '@vscode-marquee/news-widget': activateNewsWidget, - '@vscode-marquee/projects-widget': activateProjectsWidget, - '@vscode-marquee/github-widget': activateGitHubWidget, - '@vscode-marquee/weather-widget': activateWeatherWidget, - '@vscode-marquee/todo-widget': activateTodoWidget, - '@vscode-marquee/markdown-widget': activateMarkdownWidget, - '@vscode-marquee/notes-widget': activateNotesWidget, - '@vscode-marquee/npm-stats-widget': activateNPMStatsWidget, - '@vscode-marquee/snippets-widget': activateSnippetsWidget -} + '@vscode-marquee/utils': [keyUtils, activateUtils], + '@vscode-marquee/gui': [keyGUI, activateGUI], + '@vscode-marquee/welcome-widget': [keyWelcomeWidget, activateWelcomeWidget], + '@vscode-marquee/news-widget': [keyNewsWidget, activateNewsWidget], + '@vscode-marquee/projects-widget': [keyProjectsWidget, activateProjectsWidget], + '@vscode-marquee/github-widget': [keyGitHubWidget, activateGitHubWidget], + '@vscode-marquee/weather-widget': [keyWeatherWidget, activateWeatherWidget], + '@vscode-marquee/todo-widget': [keyTodoWidget, activateTodoWidget], + '@vscode-marquee/markdown-widget': [keyMarkdownWidget, activateMarkdownWidget], + '@vscode-marquee/notes-widget': [keyNotesWidget, activateNotesWidget], + '@vscode-marquee/npm-stats-widget': [keyNPMStatsWidget, activateNPMStatsWidget], + '@vscode-marquee/snippets-widget': [keySnippetsWidget, activateSnippetsWidget] +} as const interface ExportFormat { type: typeof CONFIG_FILE_TYPE @@ -48,14 +84,17 @@ interface ExportFormat { } export default class StateManager implements vscode.Disposable { + #provider: StateProvider[] = [] + #state: Record = {} as Record + public readonly widgetExtensions = Object.entries(MARQUEE_WIDGETS).map( /** * this is to make Marquee core widget look like external widgets * so that the interface is the same */ - ([id, activate]) => ({ + ([id, [key, activate]]) => ({ id, - exports: activate(this._context), + exports: activate(this._context, this.#initState.bind(this, key)), isActive: true, packageJSON: { marquee: { widget: true } } }) as Pick, 'id' | 'exports' | 'isActive' | 'packageJSON'> @@ -73,11 +112,58 @@ export default class StateManager implements vscode.Disposable { vscode.commands.registerCommand('marquee.jsonExport', this._export.bind(this)) ) + this.#provider.push( + /** + * always set VS Code provider since we always have access to it + */ + new VSCodeState(this._context), + /** + * set other providers given they are available and registered through authentification + */ + ...(this._context.workspaceState.get('stateProvider') || []) + .filter((p) => Boolean(provider[p])) + .map((p) => new provider[p](_context)) + ) + + // const modeName = this.#provider[0].getState('configuration', 'modeName') + // this.#provider[0].setState('configuration.globalScope', true) + // this.#provider[0].setState('configuration', { + // globalScope: true + // }) + // const a = this.#provider[0].getState('widgets.projects', 'visitCount') + + + } + + async #initState (key: WidgetKeys, defaultState: State) { + const oldGlobalStore = this._context.globalState.get(DEPRECATED_GLOBAL_STORE_KEY, {}) + this.#state[key] = { + ...defaultState, + ...pick(oldGlobalStore, Object.keys(defaultState as any)), + ...this._context.globalState.get(key) + } + await Promise.all(this.#provider.map((p) => p.setState(key, this.#state[key]))) + /** * delete old global state so that configurations stored in the globalState in v2 * can be applied with v3 */ - this._context.globalState.update(DEPRECATED_GLOBAL_STORE_KEY, undefined) + await this._context.globalState.update(DEPRECATED_GLOBAL_STORE_KEY, undefined) + + const manager: WidgetStateManager = { + get: (property?: keyof State) => { + if (property) { + return this.#state[key][property] + } + + return this.#state[key] + }, + + set: async (property: keyof State extends string ? keyof State : never, value: State[keyof State]) => { + await Promise.all(this.#provider.map((p) => p.setState(property, value))) + } + } + return manager } private async _import () { diff --git a/packages/utils/src/state/provider/index.ts b/packages/extension/src/state/provider/index.ts similarity index 67% rename from packages/utils/src/state/provider/index.ts rename to packages/extension/src/state/provider/index.ts index fcc3003d..a96a9064 100644 --- a/packages/utils/src/state/provider/index.ts +++ b/packages/extension/src/state/provider/index.ts @@ -3,11 +3,7 @@ import type { ExtensionContext } from 'vscode' import { StatefulState } from './stateful' import type { StateProvider } from '../types' -type StateProviderClass = new ( - context: ExtensionContext, - key: string, - defaultState: any -) => StateProvider +type StateProviderClass = new (context: ExtensionContext) => StateProvider const provider: Record = { stateful: StatefulState diff --git a/packages/extension/src/state/provider/stateful.ts b/packages/extension/src/state/provider/stateful.ts new file mode 100644 index 00000000..4a9e190c --- /dev/null +++ b/packages/extension/src/state/provider/stateful.ts @@ -0,0 +1,14 @@ +import type { ExtensionContext } from 'vscode' +import type { StateProvider } from '../types' + +export class StatefulState implements StateProvider { + constructor (public context: ExtensionContext) {} + + getState (): any { + throw new Error('Method not implemented') + } + + async setState () { + throw new Error('Method not implemented') + } +} diff --git a/packages/extension/src/state/provider/vscode.ts b/packages/extension/src/state/provider/vscode.ts new file mode 100644 index 00000000..ee4ea20e --- /dev/null +++ b/packages/extension/src/state/provider/vscode.ts @@ -0,0 +1,14 @@ +import type { ExtensionContext } from 'vscode' +import type { StateProvider } from '../types' + +export class VSCodeState implements StateProvider { + constructor (public context: ExtensionContext) {} + + getState (key: string) { + return this.context.globalState.get(key) as never + } + + async setState (key: string, value: any) { + await this.context.globalState.update(key, value) + } +} diff --git a/packages/extension/src/state/types.ts b/packages/extension/src/state/types.ts new file mode 100644 index 00000000..37a2190a --- /dev/null +++ b/packages/extension/src/state/types.ts @@ -0,0 +1,98 @@ +import { ExtensionContext } from 'vscode' + +import { State } from '../utils' +import type { + STATE_KEY as keyUtils, + State as StateUtils +} from '@vscode-marquee/utils/extension' +import type { + STATE_KEY as keyWelcomeWidget, + State as StateWelcomeWidget +} from '@vscode-marquee/widget-welcome/extension' +import type { + STATE_KEY as keyNewsWidget, + State as StateNewsWidget +} from '@vscode-marquee/widget-news/extension' +import type { + STATE_KEY as keyProjectsWidget, + State as StateProjectsWidget +} from '@vscode-marquee/widget-projects/extension' +import type { + STATE_KEY as keyGitHubWidget, + State as StateGitHubWidget +} from '@vscode-marquee/widget-github/extension' +import type { + STATE_KEY as keyWeatherWidget, + State as StateWeatherWidget +} from '@vscode-marquee/widget-weather/extension' +import type { + STATE_KEY as keyTodoWidget, + State as StateTodoWidget +} from '@vscode-marquee/widget-todo/extension' +import type { + STATE_KEY as keyMarkdownWidget, + State as StateMarkdownWidget +} from '@vscode-marquee/widget-markdown/extension' +import type { + STATE_KEY as keyNotesWidget, + State as StateNotesWidget +} from '@vscode-marquee/widget-notes/extension' +import type { + STATE_KEY as keyNPMStatsWidget, + State as StateNPMStatsWidget +} from '@vscode-marquee/widget-npm-stats/extension' +import type { + STATE_KEY as keySnippetsWidget, + State as StateSnippetsWidget +} from '@vscode-marquee/widget-snippets/extension' + +export type WidgetKeys = ( + typeof keyUtils | typeof keyWelcomeWidget | typeof keyNewsWidget | typeof keyProjectsWidget | + typeof keyGitHubWidget | typeof keyWeatherWidget | typeof keyTodoWidget | typeof keyMarkdownWidget | + typeof keyNotesWidget | typeof keyNPMStatsWidget | typeof keySnippetsWidget +) + +export type WidgetStates = { + [keyUtils]: StateUtils & State + [keyWelcomeWidget]: StateWelcomeWidget + [keyNewsWidget]: StateNewsWidget + [keyProjectsWidget]: StateProjectsWidget + [keyGitHubWidget]: StateGitHubWidget + [keyWeatherWidget]: StateWeatherWidget + [keyTodoWidget]: StateTodoWidget + [keyMarkdownWidget]: StateMarkdownWidget + [keyNotesWidget]: StateNotesWidget + [keyNPMStatsWidget]: StateNPMStatsWidget + [keySnippetsWidget]: StateSnippetsWidget +} + +export abstract class StateProvider { + abstract context: ExtensionContext + + /** + * Allows to sync a specific key value pair with provider + * @param key state key + * @param value state value + */ + abstract setState ( + key: Keys, + value: WidgetStates[Keys] + ): Promise + abstract setState< + Keys extends WidgetKeys, + Property extends keyof WidgetStates[Keys], + ReturnType extends WidgetStates[Keys][Property] + > ( + key: `${Keys}.${Property extends string ? Property : never}`, + value: ReturnType + ): Promise + + /** + * Get the latest state from the provider and returns it + */ + abstract getState (widgetKey: Keys): WidgetStates[Keys] + abstract getState ( + widgetKey: Keys, + property: Property + ): State[Property] +} diff --git a/packages/extension/src/tree.view.ts b/packages/extension/src/tree.view.ts index 6a7e5929..9079a19d 100644 --- a/packages/extension/src/tree.view.ts +++ b/packages/extension/src/tree.view.ts @@ -5,7 +5,7 @@ import type { Snippet } from '@vscode-marquee/widget-snippets/extension' import type { Note } from '@vscode-marquee/widget-notes/extension' import type { Todo } from '@vscode-marquee/widget-todo/extension' -import StateManager from './stateManager' +import StateManager from './state/manager' import { isExpanded, filterByScope } from './utils' const DEFAULT_STATE: State = { todos: [], snippets: [], notes: [] } diff --git a/packages/extension/src/utils.ts b/packages/extension/src/utils.ts index 090ddcfd..e97776b2 100644 --- a/packages/extension/src/utils.ts +++ b/packages/extension/src/utils.ts @@ -1,5 +1,5 @@ import vscode from 'vscode' -import ExtensionManager, { defaultConfigurations, Logger } from '@vscode-marquee/utils/extension' +import ExtensionManager, { defaultConfigurations, Logger, StateManager } from '@vscode-marquee/utils/extension' import { MODES_UPDATE_TIMEOUT } from './constants' @@ -34,8 +34,8 @@ export const DEFAULT_CONFIGURATION = { colorScheme: undefined } -type Configuration = typeof DEFAULT_CONFIGURATION -type State = typeof DEFAULT_STATE +export type Configuration = typeof DEFAULT_CONFIGURATION +export type State = typeof DEFAULT_STATE export class GUIExtensionManager extends ExtensionManager { private _lastModesChange = Date.now() @@ -44,9 +44,9 @@ export class GUIExtensionManager extends ExtensionManager context: vscode.ExtensionContext, key: string, defaultConfiguration: Configuration, - defaultState: State + stateManager: StateManager ) { - super(context, key, defaultConfiguration, defaultState) + super(context, key, defaultConfiguration, stateManager) this._disposables.push(vscode.workspace.onDidChangeConfiguration(this._onModeChange.bind(this))) } @@ -102,15 +102,20 @@ export class GUIExtensionManager extends ExtensionManager } } -export function activateGUI (context: vscode.ExtensionContext) { - const stateManager = new GUIExtensionManager(context, 'configuration', DEFAULT_CONFIGURATION, DEFAULT_STATE) +export const STATE_KEY = 'configuration' +export async function activateGUI ( + context: vscode.ExtensionContext, + getStateManager: (defaultState: State) => Promise> +) { + const stateManager = await getStateManager(DEFAULT_STATE) + const widgetManager = new GUIExtensionManager(context, STATE_KEY, DEFAULT_CONFIGURATION, stateManager) return { marquee: { - disposable: stateManager, - defaultState: stateManager.state, - defaultConfiguration: stateManager.configuration, - setup: stateManager.setBroadcaster.bind(stateManager) + disposable: widgetManager, + defaultState: widgetManager.state, + defaultConfiguration: widgetManager.configuration, + setup: widgetManager.setBroadcaster.bind(widgetManager) } } } diff --git a/packages/utils/src/extension.ts b/packages/utils/src/extension.ts index 55af441f..37136e69 100644 --- a/packages/utils/src/extension.ts +++ b/packages/utils/src/extension.ts @@ -9,19 +9,17 @@ import { closest } from 'fastest-levenshtein' import { Logger } from './logger' import { GitProvider } from './provider/git' -import { StateManager } from './state/manager' import { DEFAULT_CONFIGURATION, DEFAULT_STATE, DEPRECATED_GLOBAL_STORE_KEY, EXTENSION_ID, pkg } from './constants' -import { WorkspaceType, ProjectItem } from './types' +import { WorkspaceType, ProjectItem, StateManager } from './types' import type { Configuration, State, Workspace, ProjectItemTypes } from './types' const NAMESPACE = '144fb8a8-7dbf-4241-8795-0dc12b8e2fb6' const CONFIGURATION_TARGET = vscode.ConfigurationTarget.Global const TELEMETRY_CONFIG_ID = 'telemetry' const TELEMETRY_CONFIG_ENABLED_ID = 'enableTelemetry' +export const STATE_KEY = 'configuration' export default class ExtensionManager extends EventEmitter implements vscode.Disposable { - #stateManager: StateManager - protected _tangle?: Client protected _configuration: Configuration protected _disposables: vscode.Disposable[] = [ @@ -36,11 +34,9 @@ export default class ExtensionManager extends EventEmitter protected _context: vscode.ExtensionContext, protected _key: string, private _defaultConfiguration: Configuration, - private _defaultState: State + private _stateManager: StateManager ) { super() - this.#stateManager = new StateManager(this._context, this._key, this._defaultState) - this._context.subscriptions.push(this.#stateManager) this._gitProvider = this._context.subscriptions.find((s) => s instanceof GitProvider) as GitProvider const config = vscode.workspace.getConfiguration('marquee') @@ -54,7 +50,7 @@ export default class ExtensionManager extends EventEmitter } get state () { - return this.#stateManager.state + return this._stateManager.get() } get configuration () { @@ -139,10 +135,10 @@ export default class ExtensionManager extends EventEmitter } async updateState (prop: T, val: State[T], broadcastState?: boolean) { - await this.#stateManager.updateState(prop, val) - this.emit('stateUpdate', this.#stateManager.state) + await this._stateManager.set(prop, val) + this.emit('stateUpdate', this.state) if (broadcastState && this._tangle) { - this._tangle.broadcast(this.#stateManager.state as State & Configuration) + this._tangle.broadcast(this.state as State & Configuration) } } @@ -232,7 +228,7 @@ export default class ExtensionManager extends EventEmitter /** * listen on state changes */ - for (const stateProp of Object.keys(this._defaultState as any)) { + for (const stateProp of Object.keys(DEFAULT_STATE as any)) { const s = stateProp as keyof State this._subscriptions.push(this._tangle.listen(s, (val) => this.updateState(s, val))) } @@ -368,36 +364,40 @@ export class GlobalExtensionManager extends ExtensionManager Promise> +) { + const stateManager = await getStateManager(DEFAULT_STATE) + const widgetManager = new GlobalExtensionManager( context, - 'configuration', + STATE_KEY, DEFAULT_CONFIGURATION, - DEFAULT_STATE + stateManager ) - const aws = stateManager.getActiveWorkspace() + const aws = widgetManager.getActiveWorkspace() /** * transform configurations from Marquee v2 -> v3 */ const oldGlobalStore = context.globalState.get(DEPRECATED_GLOBAL_STORE_KEY, {}) if (oldGlobalStore.bg) { - stateManager.updateConfiguration('background', oldGlobalStore.bg) + widgetManager.updateConfiguration('background', oldGlobalStore.bg) } /** * set global state to true if we don't have a workspace */ if (!aws) { - stateManager.updateState('globalScope', true) + widgetManager.updateState('globalScope', true) } return { marquee: { - disposable: stateManager, - defaultState: stateManager.state, - defaultConfiguration: stateManager.configuration, - setup: stateManager.setBroadcaster.bind(stateManager) + disposable: widgetManager, + defaultState: widgetManager.state, + defaultConfiguration: widgetManager.configuration, + setup: widgetManager.setBroadcaster.bind(widgetManager) } } } diff --git a/packages/utils/src/state/manager.ts b/packages/utils/src/state/manager.ts deleted file mode 100644 index f8e9dec1..00000000 --- a/packages/utils/src/state/manager.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Disposable, ExtensionContext } from 'vscode' - -import provider from './provider' -import { VSCodeState } from './provider/vscode' -import { Logger } from '../logger' -import type { StateProvider } from './types' - -export class StateManager implements Disposable { - #state?: State - #logger: Logger - #context: ExtensionContext - #provider: StateProvider[] = [] - - constructor (context: ExtensionContext, key: string, defaultState: State) { - this.#logger = Logger.getChildLogger('StateManager') - this.#context = context - this.#provider.push( - /** - * always - */ - new VSCodeState(this.#context, key, defaultState), - ...(this.#context.workspaceState.get('stateProvider') || []) - .filter((p) => Boolean(provider[p])) - .map((p) => new provider[p](context, key, defaultState)) - ) - } - - get state (): State { - if (!this.state) { - throw new Error('StateManager is not in sync') - } - - return this.state - } - - async updateState (prop: T, val: State[T]) { - for (const provider of this.#provider) { - await provider.updateState(prop, val) - } - } - - async clearState () { - for (const provider of this.#provider) { - await provider.clear() - } - } - - /** - * Downloads current State from providers and merge them if necessary - */ - async sync () { - for (const provider of this.#provider) { - await provider.sync() - } - } - - dispose () { - // nothing to do - } -} diff --git a/packages/utils/src/state/provider/stateful.ts b/packages/utils/src/state/provider/stateful.ts deleted file mode 100644 index 9c957e5c..00000000 --- a/packages/utils/src/state/provider/stateful.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { ExtensionContext } from 'vscode' -import type { StateProvider } from '../types' - -export class StatefulState implements StateProvider { - #state: State = {} as State - - constructor (public context: ExtensionContext) {} - - get state () { - return this.#state - } - - async updateState (/*key: keyof State, value: State[keyof State]*/): Promise { - // throw new Error('Method not implemented.') - } - getState (): State { - throw new Error('Method not implemented.') - } - sync (): State { - throw new Error('Method not implemented.') - } - clear (): Promise { - throw new Error('Method not implemented.') - } -} diff --git a/packages/utils/src/state/provider/vscode.ts b/packages/utils/src/state/provider/vscode.ts deleted file mode 100644 index b7a8950c..00000000 --- a/packages/utils/src/state/provider/vscode.ts +++ /dev/null @@ -1,71 +0,0 @@ -import pick from 'lodash.pick' -import hash from 'object-hash' -import type { ExtensionContext } from 'vscode' - -import { Logger } from '../../logger' -import { DEPRECATED_GLOBAL_STORE_KEY } from '../../constants' -import type { StateProvider } from '../types' - -export class VSCodeState implements StateProvider { - #state: State - - constructor ( - public context: ExtensionContext, - private _key: string, - private _defaultState: State - ) { - const oldGlobalStore = this.context.globalState.get(DEPRECATED_GLOBAL_STORE_KEY, {}) - this.#state = { - ...this._defaultState, - ...pick(oldGlobalStore, Object.keys(this._defaultState as any)), - ...this.context.globalState.get(this._key) - } - - /** - * preserve state across different machines - */ - this.context.globalState.setKeysForSync(Object.keys(this._defaultState as any)) - } - - get state () { - return this.#state - } - - /** - * Update extension state - * @param prop state property name - * @param val new state property value - * @param broadcastState set to true if you want to broadcast this state change - * (only needed when updating state from the extension host) - */ - async updateState (prop: T, val: State[T]) { - /** - * check if we have to update - */ - if ( - typeof val !== 'undefined' && - typeof this.#state[prop] !== 'undefined' && - hash(this.#state[prop] as any) === hash(val as any) - ) { - return - } - - Logger.info(`Update state "${prop.toString()}": ${val as any as string}`) - this.#state[prop] = val - await this.context.globalState.update(this._key, this.#state) - } - - async clear () { - this.#state = { ...this._defaultState } - await this.context.globalState.update(this._key, this.#state) - await this.context.globalState.update(DEPRECATED_GLOBAL_STORE_KEY, undefined) - } - - getState (): State { - throw new Error('Method not implemented.') - } - - sync (): State { - return this.state - } -} diff --git a/packages/utils/src/state/types.ts b/packages/utils/src/state/types.ts deleted file mode 100644 index b18e997e..00000000 --- a/packages/utils/src/state/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ExtensionContext } from 'vscode' - -export abstract class StateProvider { - abstract context: ExtensionContext - abstract state: State - - /** - * Allows to sync a specific key value pair with provider - * @param key state key - * @param value state value - */ - abstract updateState (key: keyof State, value: State[keyof State]): Promise - /** - * Downloads the latest state from the provider and returns it - */ - abstract getState (): State - /** - * Gets the provider values and compares them with local ones. If a conflict - * is detected the value of the environment that was last updated is picked. - * ToDo(Christian): make this configurable, e.g. allow to get asked etc. - */ - abstract sync (): State - /** - * Clears the state of the provider - */ - abstract clear (): Promise -} diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index cb717700..32f3d184 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -108,3 +108,9 @@ export interface GitRemote { name: string; url: string; } + +export interface StateManager { + get (): State + get (property: keyof State): State[keyof State] + set (property: keyof State, value: State[keyof State]): Promise +} diff --git a/packages/widget-github/src/extension.ts b/packages/widget-github/src/extension.ts index cf4d8131..3d413ef6 100644 --- a/packages/widget-github/src/extension.ts +++ b/packages/widget-github/src/extension.ts @@ -3,12 +3,12 @@ import vscode from 'vscode' import ExtensionManager, { DEPRECATED_GLOBAL_STORE_KEY } from '@vscode-marquee/utils/extension' import { DEFAULT_CONFIGURATION, DEFAULT_STATE } from './constants' -import type { Configuration } from './types' +import type { Configuration, State } from './types' -const STATE_KEY = 'widgets.github' +export const STATE_KEY = 'widgets.github' export function activate (context: vscode.ExtensionContext) { - const stateManager = new ExtensionManager<{}, Configuration>( + const stateManager = new ExtensionManager( context, STATE_KEY, DEFAULT_CONFIGURATION, @@ -38,3 +38,5 @@ export function activate (context: vscode.ExtensionContext) { } } } + +export * from './types' diff --git a/packages/widget-github/src/types.ts b/packages/widget-github/src/types.ts index 0144276f..2402e37b 100644 --- a/packages/widget-github/src/types.ts +++ b/packages/widget-github/src/types.ts @@ -36,6 +36,8 @@ export interface Configuration { trendFilter?: string } +export interface State {} + export interface ContextTypes extends Configuration { trends: Trend[] isFetching: boolean diff --git a/packages/widget-markdown/src/extension.ts b/packages/widget-markdown/src/extension.ts index 35d8e3ac..823249ce 100644 --- a/packages/widget-markdown/src/extension.ts +++ b/packages/widget-markdown/src/extension.ts @@ -10,7 +10,7 @@ import ExtensionManager, { Logger, ChildLogger } from '@vscode-marquee/utils/ext import { DEFAULT_CONFIGURATION, DEFAULT_STATE } from './constants' import type { Configuration, MarkdownDocument, State } from './types' -const STATE_KEY = 'widgets.markdown' +export const STATE_KEY = 'widgets.markdown' const FILE_UUID_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341' const MARKDOWN_FETCH_ERROR_MESSAGE = 'Couldn\'t fetch markdown file!' @@ -189,3 +189,5 @@ export async function fetchRemoteDocument (url: string) { const data = await res.text() return data } + +export * from './types' diff --git a/packages/widget-news/src/extension.ts b/packages/widget-news/src/extension.ts index 77563117..3729d479 100644 --- a/packages/widget-news/src/extension.ts +++ b/packages/widget-news/src/extension.ts @@ -5,7 +5,7 @@ import ExtensionManager, { Logger, ChildLogger } from '@vscode-marquee/utils/ext import { DEFAULT_CONFIGURATION, DEFAULT_STATE, MIN_UPDATE_INTERVAL } from './constants' import type { Configuration, FeedItem, State } from './types' -const STATE_KEY = 'widgets.news' +export const STATE_KEY = 'widgets.news' const DEFAULT_HNRSS_CHANNELS = ['HN Newest', 'HN Ask', 'HN Show', 'HN Jobs', 'HN Best'] const DEPRECATED_HNRSS_URL = 'https://hnrss.org' @@ -102,3 +102,5 @@ export function activate (context: vscode.ExtensionContext) { }, } } + +export * from './types' diff --git a/packages/widget-notes/src/extension.ts b/packages/widget-notes/src/extension.ts index eb9916a3..8d32ddcb 100644 --- a/packages/widget-notes/src/extension.ts +++ b/packages/widget-notes/src/extension.ts @@ -5,7 +5,7 @@ import ExtensionManager, { Logger, ChildLogger } from '@vscode-marquee/utils/ext import { DEFAULT_STATE } from './constants' import type { State, Note } from './types' -const STATE_KEY = 'widgets.notes' +export const STATE_KEY = 'widgets.notes' export class NoteExtensionManager extends ExtensionManager { #logger: ChildLogger diff --git a/packages/widget-npm-stats/src/extension.ts b/packages/widget-npm-stats/src/extension.ts index 37940964..f5f1f427 100644 --- a/packages/widget-npm-stats/src/extension.ts +++ b/packages/widget-npm-stats/src/extension.ts @@ -8,7 +8,7 @@ import { formatDate } from './utils' import { DEFAULT_CONFIGURATION, DEFAULT_STATE, STATS_URL } from './constants' import type { Configuration, State, JSONObject, StatResponse } from './types' -const STATE_KEY = 'widgets.npm-stats' +export const STATE_KEY = 'widgets.npm-stats' export class NPMStatsExtensionManager extends ExtensionManager { #logger: ChildLogger private _isFetching = false @@ -152,3 +152,5 @@ export function activate (context: vscode.ExtensionContext) { }, } } + +export * from './types' diff --git a/packages/widget-projects/src/extension.ts b/packages/widget-projects/src/extension.ts index 5cdd8bac..1a53c620 100644 --- a/packages/widget-projects/src/extension.ts +++ b/packages/widget-projects/src/extension.ts @@ -5,7 +5,7 @@ import ExtensionManager from '@vscode-marquee/utils/extension' import { DEFAULT_CONFIGURATION, DEFAULT_STATE } from './constants' import type { State, Configuration } from './types' -const STATE_KEY = 'widgets.projects' +export const STATE_KEY = 'widgets.projects' export class ProjectsExtensionManager extends ExtensionManager { constructor (context: vscode.ExtensionContext) { diff --git a/packages/widget-snippets/src/constants.ts b/packages/widget-snippets/src/constants.ts index cea3d36e..8721a0ea 100644 --- a/packages/widget-snippets/src/constants.ts +++ b/packages/widget-snippets/src/constants.ts @@ -1,6 +1,5 @@ import type { State } from './types' -export const STATE_KEY = 'widgets.snippets' export const WIDGET_ID = '@vscode-marquee/snippets-widget' export const DEFAULT_STATE: State = { diff --git a/packages/widget-snippets/src/extension.ts b/packages/widget-snippets/src/extension.ts index 225af216..0db6631d 100644 --- a/packages/widget-snippets/src/extension.ts +++ b/packages/widget-snippets/src/extension.ts @@ -6,9 +6,11 @@ import ExtensionManager, {Logger, ChildLogger } from '@vscode-marquee/utils/exte import Snippet from './models/Snippet' import ContentProvider from './provider/ContentProvider' import SnippetStorageProvider from './provider/SnippetStorageProvider' -import { DEFAULT_STATE, STATE_KEY } from './constants' +import { DEFAULT_STATE } from './constants' import type { SnippetTreeItem, State, Events, Selection } from './types' +export const STATE_KEY = 'widgets.snippets' + export class SnippetExtensionManager extends ExtensionManager { #logger: ChildLogger private _contentProvider = new ContentProvider() diff --git a/packages/widget-todo/src/extension.ts b/packages/widget-todo/src/extension.ts index 4fc62605..9eb3715e 100644 --- a/packages/widget-todo/src/extension.ts +++ b/packages/widget-todo/src/extension.ts @@ -5,7 +5,7 @@ import ExtensionManager, { Logger, ChildLogger } from '@vscode-marquee/utils/ext import { DEFAULT_CONFIGURATION, DEFAULT_STATE } from './constants' import type { Configuration, State, Todo } from './types' -const STATE_KEY = 'widgets.todo' +export const STATE_KEY = 'widgets.todo' export const CODE_TODO = 'marquee_todo' export const TODO = /(TODO|ToDo|Todo|todo)[:]? / diff --git a/packages/widget-weather/src/extension.ts b/packages/widget-weather/src/extension.ts index 25bbe038..c65058c5 100644 --- a/packages/widget-weather/src/extension.ts +++ b/packages/widget-weather/src/extension.ts @@ -5,7 +5,7 @@ import ExtensionManager, { DEPRECATED_GLOBAL_STORE_KEY } from '@vscode-marquee/u import { DEFAULT_CONFIGURATION, DEFAULT_STATE } from './constants' import type { Configuration } from './types' -const STATE_KEY = 'widgets.weather' +export const STATE_KEY = 'widgets.weather' export function activate (context: vscode.ExtensionContext) { const stateManager = new ExtensionManager<{}, Configuration>( @@ -33,3 +33,5 @@ export function activate (context: vscode.ExtensionContext) { } } } + +export * from './types' diff --git a/packages/widget-weather/src/types.ts b/packages/widget-weather/src/types.ts index b3f4bcf9..368f7581 100644 --- a/packages/widget-weather/src/types.ts +++ b/packages/widget-weather/src/types.ts @@ -82,6 +82,8 @@ export interface Configuration { scale?: ScaleUnits } +export interface State {} + export interface Context extends Configuration { forecast?: Forecast isFetching: boolean diff --git a/packages/widget-welcome/src/extension.ts b/packages/widget-welcome/src/extension.ts index 7e82d391..4b828743 100644 --- a/packages/widget-welcome/src/extension.ts +++ b/packages/widget-welcome/src/extension.ts @@ -9,7 +9,7 @@ import type { State, Events, Configuration, Trick } from './types' declare const BACKEND_BASE_URL: string -const STATE_KEY = 'widgets.welcome' +export const STATE_KEY = 'widgets.welcome' const FETCH_INTERVAL = 5 * 1000 * 60 // 5min const AXIOS_RETRIES = process.env.NODE_ENV === 'development' ? 1 @@ -158,3 +158,5 @@ export function activate (context: vscode.ExtensionContext) { } } } + +export * from './types'