mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-26 10:45:57 +08:00
feat(core): desktop multiple server support (#8979)
This commit is contained in:
@@ -33,7 +33,7 @@ export class EventBus {
|
||||
});
|
||||
|
||||
for (const handler of handlers.values()) {
|
||||
this.on(handler.event.id, handler.handler);
|
||||
this.on(handler.event, handler.handler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,23 +41,25 @@ export class EventBus {
|
||||
return this.parent?.root ?? this;
|
||||
}
|
||||
|
||||
on<T>(id: string, listener: (event: FrameworkEvent<T>) => void) {
|
||||
if (!this.listeners[id]) {
|
||||
this.listeners[id] = [];
|
||||
on<T>(event: FrameworkEvent<T>, listener: (event: T) => void) {
|
||||
if (!this.listeners[event.id]) {
|
||||
this.listeners[event.id] = [];
|
||||
}
|
||||
this.listeners[id].push(listener);
|
||||
const off = this.parent?.on(id, listener);
|
||||
this.listeners[event.id].push(listener);
|
||||
const off = this.parent?.on(event, listener);
|
||||
return () => {
|
||||
this.off(id, listener);
|
||||
this.off(event, listener);
|
||||
off?.();
|
||||
};
|
||||
}
|
||||
|
||||
off<T>(id: string, listener: (event: FrameworkEvent<T>) => void) {
|
||||
if (!this.listeners[id]) {
|
||||
off<T>(event: FrameworkEvent<T>, listener: (event: T) => void) {
|
||||
if (!this.listeners[event.id]) {
|
||||
return;
|
||||
}
|
||||
this.listeners[id] = this.listeners[id].filter(l => l !== listener);
|
||||
this.listeners[event.id] = this.listeners[event.id].filter(
|
||||
l => l !== listener
|
||||
);
|
||||
}
|
||||
|
||||
emit<T>(event: FrameworkEvent<T>, payload: T) {
|
||||
|
||||
@@ -216,6 +216,15 @@ export const AFFINE_FLAGS = {
|
||||
configurable: false,
|
||||
defaultState: isMobile,
|
||||
},
|
||||
enable_multiple_cloud_servers: {
|
||||
category: 'affine',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-multiple-cloud-servers.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-multiple-cloud-servers.description',
|
||||
configurable: isDesktopEnvironment,
|
||||
defaultState: false,
|
||||
},
|
||||
} satisfies { [key in string]: FlagInfo };
|
||||
|
||||
export type AFFINE_FLAGS = typeof AFFINE_FLAGS;
|
||||
|
||||
@@ -8,6 +8,9 @@ export class GlobalContext extends Entity {
|
||||
memento = new MemoryMemento();
|
||||
|
||||
workspaceId = this.define<string>('workspaceId');
|
||||
workspaceFlavour = this.define<string>('workspaceFlavour');
|
||||
|
||||
serverId = this.define<string>('serverId');
|
||||
|
||||
/**
|
||||
* is in doc page
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { Framework } from '../../../framework';
|
||||
@@ -22,7 +21,7 @@ describe('Workspace System', () => {
|
||||
expect(workspaceService.list.workspaces$.value.length).toBe(0);
|
||||
|
||||
const workspace = workspaceService.open({
|
||||
metadata: await workspaceService.create(WorkspaceFlavour.LOCAL),
|
||||
metadata: await workspaceService.create('local'),
|
||||
});
|
||||
|
||||
expect(workspace.workspace).toBeInstanceOf(Workspace);
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
import { combineLatest, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import { Entity } from '../../../framework';
|
||||
import { LiveData } from '../../../livedata';
|
||||
import type { WorkspaceFlavourProvider } from '../providers/flavour';
|
||||
import type { WorkspaceMetadata } from '../metadata';
|
||||
import type { WorkspaceFlavoursService } from '../services/flavours';
|
||||
|
||||
export class WorkspaceList extends Entity {
|
||||
workspaces$ = new LiveData(this.providers.map(p => p.workspaces$))
|
||||
.map(v => {
|
||||
return v;
|
||||
})
|
||||
.flat()
|
||||
.map(workspaces => {
|
||||
return workspaces.flat();
|
||||
});
|
||||
isRevalidating$ = new LiveData(
|
||||
this.providers.map(p => p.isRevalidating$ ?? new LiveData(false))
|
||||
)
|
||||
.flat()
|
||||
.map(isLoadings => isLoadings.some(isLoading => isLoading));
|
||||
workspaces$ = LiveData.from<WorkspaceMetadata[]>(
|
||||
this.flavoursService.flavours$.pipe(
|
||||
switchMap(flavours =>
|
||||
combineLatest(flavours.map(flavour => flavour.workspaces$)).pipe(
|
||||
map(workspaces => workspaces.flat())
|
||||
)
|
||||
)
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
isRevalidating$ = LiveData.from<boolean>(
|
||||
this.flavoursService.flavours$.pipe(
|
||||
switchMap(flavours =>
|
||||
combineLatest(
|
||||
flavours.map(flavour => flavour.isRevalidating$ ?? of(false))
|
||||
).pipe(map(isLoadings => isLoadings.some(isLoading => isLoading)))
|
||||
)
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
workspace$(id: string) {
|
||||
return this.workspaces$.map(workspaces =>
|
||||
@@ -23,12 +34,14 @@ export class WorkspaceList extends Entity {
|
||||
);
|
||||
}
|
||||
|
||||
constructor(private readonly providers: WorkspaceFlavourProvider[]) {
|
||||
constructor(private readonly flavoursService: WorkspaceFlavoursService) {
|
||||
super();
|
||||
}
|
||||
|
||||
revalidate() {
|
||||
this.providers.forEach(provider => provider.revalidate?.());
|
||||
this.flavoursService.flavours$.value.forEach(provider => {
|
||||
provider.revalidate?.();
|
||||
});
|
||||
}
|
||||
|
||||
waitForRevalidation(signal?: AbortSignal) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '../../../livedata';
|
||||
import type { WorkspaceMetadata } from '../metadata';
|
||||
import type { WorkspaceFlavourProvider } from '../providers/flavour';
|
||||
import type { WorkspaceFlavoursService } from '../services/flavours';
|
||||
import type { WorkspaceProfileCacheStore } from '../stores/profile-cache';
|
||||
import type { Workspace } from './workspace';
|
||||
|
||||
@@ -47,12 +48,14 @@ export class WorkspaceProfile extends Entity<{ metadata: WorkspaceMetadata }> {
|
||||
|
||||
constructor(
|
||||
private readonly cache: WorkspaceProfileCacheStore,
|
||||
providers: WorkspaceFlavourProvider[]
|
||||
flavoursService: WorkspaceFlavoursService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.provider =
|
||||
providers.find(p => p.flavour === this.props.metadata.flavour) ?? null;
|
||||
flavoursService.flavours$.value.find(
|
||||
p => p.flavour === this.props.metadata.flavour
|
||||
) ?? null;
|
||||
}
|
||||
|
||||
private setProfile(info: WorkspaceProfileInfo) {
|
||||
|
||||
@@ -5,7 +5,8 @@ export { getAFFiNEWorkspaceSchema } from './global-schema';
|
||||
export type { WorkspaceMetadata } from './metadata';
|
||||
export type { WorkspaceOpenOptions } from './open-options';
|
||||
export type { WorkspaceEngineProvider } from './providers/flavour';
|
||||
export { WorkspaceFlavourProvider } from './providers/flavour';
|
||||
export type { WorkspaceFlavourProvider } from './providers/flavour';
|
||||
export { WorkspaceFlavoursProvider } from './providers/flavour';
|
||||
export { WorkspaceLocalCache, WorkspaceLocalState } from './providers/storage';
|
||||
export { WorkspaceScope } from './scopes/workspace';
|
||||
export { WorkspaceService } from './services/workspace';
|
||||
@@ -21,12 +22,13 @@ import {
|
||||
WorkspaceLocalCacheImpl,
|
||||
WorkspaceLocalStateImpl,
|
||||
} from './impls/storage';
|
||||
import { WorkspaceFlavourProvider } from './providers/flavour';
|
||||
import { WorkspaceFlavoursProvider } from './providers/flavour';
|
||||
import { WorkspaceLocalCache, WorkspaceLocalState } from './providers/storage';
|
||||
import { WorkspaceScope } from './scopes/workspace';
|
||||
import { WorkspaceDestroyService } from './services/destroy';
|
||||
import { WorkspaceEngineService } from './services/engine';
|
||||
import { WorkspaceFactoryService } from './services/factory';
|
||||
import { WorkspaceFlavoursService } from './services/flavours';
|
||||
import { WorkspaceListService } from './services/list';
|
||||
import { WorkspaceProfileService } from './services/profile';
|
||||
import { WorkspaceRepositoryService } from './services/repo';
|
||||
@@ -34,12 +36,12 @@ import { WorkspaceTransformService } from './services/transform';
|
||||
import { WorkspaceService } from './services/workspace';
|
||||
import { WorkspacesService } from './services/workspaces';
|
||||
import { WorkspaceProfileCacheStore } from './stores/profile-cache';
|
||||
import { TestingWorkspaceLocalProvider } from './testing/testing-provider';
|
||||
import { TestingWorkspaceFlavoursProvider } from './testing/testing-provider';
|
||||
|
||||
export function configureWorkspaceModule(framework: Framework) {
|
||||
framework
|
||||
.service(WorkspacesService, [
|
||||
[WorkspaceFlavourProvider],
|
||||
WorkspaceFlavoursService,
|
||||
WorkspaceListService,
|
||||
WorkspaceProfileService,
|
||||
WorkspaceTransformService,
|
||||
@@ -47,22 +49,23 @@ export function configureWorkspaceModule(framework: Framework) {
|
||||
WorkspaceFactoryService,
|
||||
WorkspaceDestroyService,
|
||||
])
|
||||
.service(WorkspaceDestroyService, [[WorkspaceFlavourProvider]])
|
||||
.service(WorkspaceFlavoursService, [[WorkspaceFlavoursProvider]])
|
||||
.service(WorkspaceDestroyService, [WorkspaceFlavoursService])
|
||||
.service(WorkspaceListService)
|
||||
.entity(WorkspaceList, [[WorkspaceFlavourProvider]])
|
||||
.entity(WorkspaceList, [WorkspaceFlavoursService])
|
||||
.service(WorkspaceProfileService)
|
||||
.store(WorkspaceProfileCacheStore, [GlobalCache])
|
||||
.entity(WorkspaceProfile, [
|
||||
WorkspaceProfileCacheStore,
|
||||
[WorkspaceFlavourProvider],
|
||||
WorkspaceFlavoursService,
|
||||
])
|
||||
.service(WorkspaceFactoryService, [[WorkspaceFlavourProvider]])
|
||||
.service(WorkspaceFactoryService, [WorkspaceFlavoursService])
|
||||
.service(WorkspaceTransformService, [
|
||||
WorkspaceFactoryService,
|
||||
WorkspaceDestroyService,
|
||||
])
|
||||
.service(WorkspaceRepositoryService, [
|
||||
[WorkspaceFlavourProvider],
|
||||
WorkspaceFlavoursService,
|
||||
WorkspaceProfileService,
|
||||
])
|
||||
.scope(WorkspaceScope)
|
||||
@@ -82,8 +85,8 @@ export function configureWorkspaceModule(framework: Framework) {
|
||||
|
||||
export function configureTestingWorkspaceProvider(framework: Framework) {
|
||||
framework.impl(
|
||||
WorkspaceFlavourProvider('LOCAL'),
|
||||
TestingWorkspaceLocalProvider,
|
||||
WorkspaceFlavoursProvider('LOCAL'),
|
||||
TestingWorkspaceFlavoursProvider,
|
||||
[GlobalState]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
|
||||
export type WorkspaceMetadata = {
|
||||
id: string;
|
||||
flavour: WorkspaceFlavour;
|
||||
flavour: string;
|
||||
initialized?: boolean;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import type { DocCollection } from '@blocksuite/affine/store';
|
||||
|
||||
import { createIdentifier } from '../../../framework';
|
||||
@@ -22,7 +21,7 @@ export interface WorkspaceEngineProvider {
|
||||
}
|
||||
|
||||
export interface WorkspaceFlavourProvider {
|
||||
flavour: WorkspaceFlavour;
|
||||
flavour: string;
|
||||
|
||||
deleteWorkspace(id: string): Promise<void>;
|
||||
|
||||
@@ -60,5 +59,9 @@ export interface WorkspaceFlavourProvider {
|
||||
onWorkspaceInitialized?(workspace: Workspace): void;
|
||||
}
|
||||
|
||||
export const WorkspaceFlavourProvider =
|
||||
createIdentifier<WorkspaceFlavourProvider>('WorkspaceFlavourProvider');
|
||||
export interface WorkspaceFlavoursProvider {
|
||||
workspaceFlavours$: LiveData<WorkspaceFlavourProvider[]>;
|
||||
}
|
||||
|
||||
export const WorkspaceFlavoursProvider =
|
||||
createIdentifier<WorkspaceFlavoursProvider>('WorkspaceFlavoursProvider');
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { Service } from '../../../framework';
|
||||
import type { WorkspaceMetadata } from '../metadata';
|
||||
import type { WorkspaceFlavourProvider } from '../providers/flavour';
|
||||
import type { WorkspaceFlavoursService } from './flavours';
|
||||
|
||||
export class WorkspaceDestroyService extends Service {
|
||||
constructor(private readonly providers: WorkspaceFlavourProvider[]) {
|
||||
constructor(private readonly flavoursService: WorkspaceFlavoursService) {
|
||||
super();
|
||||
}
|
||||
|
||||
deleteWorkspace = async (metadata: WorkspaceMetadata) => {
|
||||
const provider = this.providers.find(p => p.flavour === metadata.flavour);
|
||||
const provider = this.flavoursService.flavours$.value.find(
|
||||
p => p.flavour === metadata.flavour
|
||||
);
|
||||
if (!provider) {
|
||||
throw new Error(`Unknown workspace flavour: ${metadata.flavour}`);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import type { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import type { DocCollection } from '@blocksuite/affine/store';
|
||||
|
||||
import { Service } from '../../../framework';
|
||||
import type { BlobStorage, DocStorage } from '../../../sync';
|
||||
import type { WorkspaceFlavourProvider } from '../providers/flavour';
|
||||
import type { WorkspaceFlavoursService } from './flavours';
|
||||
|
||||
export class WorkspaceFactoryService extends Service {
|
||||
constructor(private readonly providers: WorkspaceFlavourProvider[]) {
|
||||
constructor(private readonly flavoursService: WorkspaceFlavoursService) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -17,14 +16,16 @@ export class WorkspaceFactoryService extends Service {
|
||||
* @returns workspace id
|
||||
*/
|
||||
create = async (
|
||||
flavour: WorkspaceFlavour,
|
||||
flavour: string,
|
||||
initial: (
|
||||
docCollection: DocCollection,
|
||||
blobStorage: BlobStorage,
|
||||
docStorage: DocStorage
|
||||
) => Promise<void> = () => Promise.resolve()
|
||||
) => {
|
||||
const provider = this.providers.find(x => x.flavour === flavour);
|
||||
const provider = this.flavoursService.flavours$.value.find(
|
||||
x => x.flavour === flavour
|
||||
);
|
||||
if (!provider) {
|
||||
throw new Error(`Unknown workspace flavour: ${flavour}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { combineLatest, map } from 'rxjs';
|
||||
|
||||
import { Service } from '../../../framework';
|
||||
import { LiveData } from '../../../livedata';
|
||||
import type { WorkspaceFlavoursProvider } from '../providers/flavour';
|
||||
|
||||
export class WorkspaceFlavoursService extends Service {
|
||||
constructor(private readonly providers: WorkspaceFlavoursProvider[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
flavours$ = LiveData.from(
|
||||
combineLatest(this.providers.map(p => p.workspaceFlavours$)).pipe(
|
||||
map(flavours => flavours.flat())
|
||||
),
|
||||
[]
|
||||
);
|
||||
}
|
||||
@@ -5,11 +5,9 @@ import { ObjectPool } from '../../../utils';
|
||||
import type { Workspace } from '../entities/workspace';
|
||||
import { WorkspaceInitialized } from '../events';
|
||||
import type { WorkspaceOpenOptions } from '../open-options';
|
||||
import type {
|
||||
WorkspaceEngineProvider,
|
||||
WorkspaceFlavourProvider,
|
||||
} from '../providers/flavour';
|
||||
import type { WorkspaceEngineProvider } from '../providers/flavour';
|
||||
import { WorkspaceScope } from '../scopes/workspace';
|
||||
import type { WorkspaceFlavoursService } from './flavours';
|
||||
import type { WorkspaceProfileService } from './profile';
|
||||
import { WorkspaceService } from './workspace';
|
||||
|
||||
@@ -17,7 +15,7 @@ const logger = new DebugLogger('affine:workspace-repository');
|
||||
|
||||
export class WorkspaceRepositoryService extends Service {
|
||||
constructor(
|
||||
private readonly providers: WorkspaceFlavourProvider[],
|
||||
private readonly flavoursService: WorkspaceFlavoursService,
|
||||
private readonly profileRepo: WorkspaceProfileService
|
||||
) {
|
||||
super();
|
||||
@@ -83,7 +81,7 @@ export class WorkspaceRepositoryService extends Service {
|
||||
logger.info(
|
||||
`open workspace [${openOptions.metadata.flavour}] ${openOptions.metadata.id} `
|
||||
);
|
||||
const flavourProvider = this.providers.find(
|
||||
const flavourProvider = this.flavoursService.flavours$.value.find(
|
||||
p => p.flavour === openOptions.metadata.flavour
|
||||
);
|
||||
const provider =
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { assertEquals } from '@blocksuite/affine/global/utils';
|
||||
import { applyUpdate } from 'yjs';
|
||||
|
||||
@@ -26,12 +25,12 @@ export class WorkspaceTransformService extends Service {
|
||||
local: Workspace,
|
||||
accountId: string
|
||||
): Promise<WorkspaceMetadata> => {
|
||||
assertEquals(local.flavour, WorkspaceFlavour.LOCAL);
|
||||
assertEquals(local.flavour, 'local');
|
||||
|
||||
const localDocStorage = local.engine.doc.storage.behavior;
|
||||
|
||||
const newMetadata = await this.factory.create(
|
||||
WorkspaceFlavour.AFFINE_CLOUD,
|
||||
'affine-cloud',
|
||||
async (docCollection, blobStorage, docStorage) => {
|
||||
const rootDocBinary = await localDocStorage.doc.get(
|
||||
local.docCollection.doc.guid
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Service } from '../../../framework';
|
||||
import type { WorkspaceMetadata } from '..';
|
||||
import type { WorkspaceFlavourProvider } from '../providers/flavour';
|
||||
import type { WorkspaceDestroyService } from './destroy';
|
||||
import type { WorkspaceFactoryService } from './factory';
|
||||
import type { WorkspaceFlavoursService } from './flavours';
|
||||
import type { WorkspaceListService } from './list';
|
||||
import type { WorkspaceProfileService } from './profile';
|
||||
import type { WorkspaceRepositoryService } from './repo';
|
||||
@@ -14,7 +14,7 @@ export class WorkspacesService extends Service {
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly providers: WorkspaceFlavourProvider[],
|
||||
private readonly flavoursService: WorkspaceFlavoursService,
|
||||
private readonly listService: WorkspaceListService,
|
||||
private readonly profileRepo: WorkspaceProfileService,
|
||||
private readonly transform: WorkspaceTransformService,
|
||||
@@ -46,7 +46,7 @@ export class WorkspacesService extends Service {
|
||||
}
|
||||
|
||||
async getWorkspaceBlob(meta: WorkspaceMetadata, blob: string) {
|
||||
return await this.providers
|
||||
return await this.flavoursService.flavours$.value
|
||||
.find(x => x.flavour === meta.flavour)
|
||||
?.getWorkspaceBlob(meta.id, blob);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { DocCollection, nanoid } from '@blocksuite/affine/store';
|
||||
import { map } from 'rxjs';
|
||||
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
||||
@@ -19,21 +18,17 @@ import type { WorkspaceMetadata } from '../metadata';
|
||||
import type {
|
||||
WorkspaceEngineProvider,
|
||||
WorkspaceFlavourProvider,
|
||||
WorkspaceFlavoursProvider,
|
||||
} from '../providers/flavour';
|
||||
|
||||
export class TestingWorkspaceLocalProvider
|
||||
extends Service
|
||||
implements WorkspaceFlavourProvider
|
||||
{
|
||||
flavour: WorkspaceFlavour = WorkspaceFlavour.LOCAL;
|
||||
class TestingWorkspaceLocalProvider implements WorkspaceFlavourProvider {
|
||||
flavour = 'local';
|
||||
|
||||
store = wrapMemento(this.globalStore, 'testing/');
|
||||
workspaceListStore = wrapMemento(this.store, 'workspaces/');
|
||||
docStorage = new MemoryDocStorage(wrapMemento(this.store, 'docs/'));
|
||||
|
||||
constructor(private readonly globalStore: GlobalState) {
|
||||
super();
|
||||
}
|
||||
constructor(private readonly globalStore: GlobalState) {}
|
||||
|
||||
async deleteWorkspace(id: string): Promise<void> {
|
||||
const list = this.workspaceListStore.get<WorkspaceMetadata[]>('list') ?? [];
|
||||
@@ -48,7 +43,7 @@ export class TestingWorkspaceLocalProvider
|
||||
) => Promise<void>
|
||||
): Promise<WorkspaceMetadata> {
|
||||
const id = nanoid();
|
||||
const meta = { id, flavour: WorkspaceFlavour.LOCAL };
|
||||
const meta = { id, flavour: 'local' };
|
||||
|
||||
const blobStorage = new MemoryBlobStorage(
|
||||
wrapMemento(this.store, id + '/blobs/')
|
||||
@@ -75,7 +70,7 @@ export class TestingWorkspaceLocalProvider
|
||||
const list = this.workspaceListStore.get<WorkspaceMetadata[]>('list') ?? [];
|
||||
this.workspaceListStore.set('list', [...list, meta]);
|
||||
|
||||
return { id, flavour: WorkspaceFlavour.LOCAL };
|
||||
return { id, flavour: 'local' };
|
||||
}
|
||||
workspaces$ = LiveData.from<WorkspaceMetadata[]>(
|
||||
this.workspaceListStore
|
||||
@@ -132,3 +127,15 @@ export class TestingWorkspaceLocalProvider
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class TestingWorkspaceFlavoursProvider
|
||||
extends Service
|
||||
implements WorkspaceFlavoursProvider
|
||||
{
|
||||
constructor(private readonly globalStore: GlobalState) {
|
||||
super();
|
||||
}
|
||||
workspaceFlavours$ = new LiveData<WorkspaceFlavourProvider[]>([
|
||||
new TestingWorkspaceLocalProvider(this.globalStore),
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user