@teambit/workspace 0.0.568 → 0.0.572
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__snapshots__/pick-outdated-pkgs.spec.ts.snap +88 -0
- package/component-config-file/component-config-file.ts +98 -0
- package/component-config-file/exceptions/already-exists.ts +7 -0
- package/component-config-file/exceptions/index.ts +1 -0
- package/component-config-file/index.ts +2 -0
- package/component-tree.widget.tsx +16 -0
- package/dist/__snapshots__/pick-outdated-pkgs.spec.js.snap +88 -0
- package/dist/__snapshots__/pick-outdated-pkgs.spec.ts.snap +88 -0
- package/dist/pick-outdated-pkgs.d.ts +12 -0
- package/dist/pick-outdated-pkgs.js +201 -0
- package/dist/pick-outdated-pkgs.js.map +1 -0
- package/dist/pick-outdated-pkgs.spec.d.ts +1 -0
- package/dist/pick-outdated-pkgs.spec.js +64 -0
- package/dist/pick-outdated-pkgs.spec.js.map +1 -0
- package/dist/update.cmd.js +3 -5
- package/dist/update.cmd.js.map +1 -1
- package/dist/workspace.d.ts +8 -1
- package/dist/workspace.js +29 -3
- package/dist/workspace.js.map +1 -1
- package/eject-conf.cmd.tsx +62 -0
- package/events/index.ts +3 -0
- package/events/on-component-add-event.ts +14 -0
- package/events/on-component-change-event.ts +14 -0
- package/events/on-component-removed-event.ts +14 -0
- package/exceptions/dependency-type-not-supported-in-policy.ts +7 -0
- package/exceptions/incorrect-env-aspect.ts +8 -0
- package/exceptions/index.ts +1 -0
- package/install.cmd.tsx +67 -0
- package/link/component-list-links.ts +50 -0
- package/link/core-aspects-links.ts +50 -0
- package/link/get-package-name-from-target.ts +5 -0
- package/link/index.ts +1 -0
- package/link/link-row.ts +20 -0
- package/link/link-to-dir.ts +13 -0
- package/link/link.cmd.ts +96 -0
- package/link/nested-deps-in-nm-links.ts +47 -0
- package/link/rewire-row.ts +17 -0
- package/list.cmd.tsx +30 -0
- package/package-tar/teambit-workspace-0.0.572.tgz +0 -0
- package/package.json +57 -68
- package/types/asset.d.ts +29 -0
- package/types/style.d.ts +42 -0
- package/ui/index.ts +1 -0
- package/ui/workspace/default-component.ts +7 -0
- package/ui/workspace/index.ts +1 -0
- package/ui/workspace/use-workspace.ts +193 -0
- package/ui/workspace/workspace-context.tsx +5 -0
- package/ui/workspace/workspace-model.ts +66 -0
- package/ui/workspace/workspace-overview/index.ts +1 -0
- package/ui/workspace/workspace-overview/workspace-overview.tsx +21 -0
- package/ui/workspace/workspace-provider.tsx +23 -0
- package/ui/workspace/workspace.tsx +99 -0
- package/ui/workspace-components-drawer/index.ts +1 -0
- package/ui/workspace-components-drawer/workspace-components.drawer.tsx +49 -0
- package/update.cmd.tsx +30 -0
- package/watch/check-types.ts +5 -0
- package/watch/output-formatter.ts +50 -0
- package/watch/watch.cmd.tsx +142 -0
- package/watch/watcher.ts +289 -0
- package/workspace-component/component-status.ts +67 -0
- package/workspace-component/index.ts +2 -0
- package/workspace-component/workspace-component-loader.ts +276 -0
- package/workspace-component/workspace-component.ts +51 -0
- package/workspace.ui.runtime.tsx +215 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ComponentStatus as LegacyComponentStatus } from '@teambit/legacy/dist/consumer/component-ops/component-status-loader';
|
|
2
|
+
|
|
3
|
+
export type ModifyInfo = {
|
|
4
|
+
hasModifiedFiles: boolean;
|
|
5
|
+
hasModifiedDependencies: boolean;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export class ComponentStatus {
|
|
9
|
+
constructor(
|
|
10
|
+
/**
|
|
11
|
+
* is the component modified.
|
|
12
|
+
*/
|
|
13
|
+
readonly modifyInfo: ModifyInfo,
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* is the new component new.
|
|
17
|
+
*/
|
|
18
|
+
readonly isNew: boolean,
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* is the component deleted from the workspace.
|
|
22
|
+
*/
|
|
23
|
+
readonly isDeleted: boolean,
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* is the component staged.
|
|
27
|
+
*/
|
|
28
|
+
readonly isStaged: boolean,
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* does the component exists in the workspace.
|
|
32
|
+
*/
|
|
33
|
+
readonly isInWorkspace: boolean,
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* does the component exists in the scope.
|
|
37
|
+
*/
|
|
38
|
+
readonly isInScope: boolean,
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* does the component is outdated (pending for update).
|
|
42
|
+
*/
|
|
43
|
+
readonly isOutdated: boolean,
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* the component is not authored and not imported.
|
|
47
|
+
*/
|
|
48
|
+
readonly nested?: boolean
|
|
49
|
+
) {}
|
|
50
|
+
|
|
51
|
+
static fromLegacy(status: LegacyComponentStatus, hasModifiedDependencies: boolean, isOutdated: boolean) {
|
|
52
|
+
const modify: ModifyInfo = {
|
|
53
|
+
hasModifiedFiles: !!status.modified,
|
|
54
|
+
hasModifiedDependencies,
|
|
55
|
+
};
|
|
56
|
+
return new ComponentStatus(
|
|
57
|
+
modify,
|
|
58
|
+
!!status.newlyCreated,
|
|
59
|
+
!!status.deleted,
|
|
60
|
+
!!status.staged,
|
|
61
|
+
!status.notExist,
|
|
62
|
+
!status.missingFromScope,
|
|
63
|
+
isOutdated,
|
|
64
|
+
!!status.nested
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { Component, ComponentFS, ComponentID, Config, InvalidComponent, State, TagMap } from '@teambit/component';
|
|
2
|
+
import { BitId } from '@teambit/legacy-bit-id';
|
|
3
|
+
import { ExtensionDataList } from '@teambit/legacy/dist/consumer/config/extension-data';
|
|
4
|
+
import mapSeries from 'p-map-series';
|
|
5
|
+
import { compact } from 'lodash';
|
|
6
|
+
import ConsumerComponent from '@teambit/legacy/dist/consumer/component';
|
|
7
|
+
import { MissingBitMapComponent } from '@teambit/legacy/dist/consumer/bit-map/exceptions';
|
|
8
|
+
import { getLatestVersionNumber } from '@teambit/legacy/dist/utils';
|
|
9
|
+
import { IssuesClasses } from '@teambit/component-issues';
|
|
10
|
+
import { ComponentNotFound } from '@teambit/legacy/dist/scope/exceptions';
|
|
11
|
+
import { DependencyResolverAspect, DependencyResolverMain } from '@teambit/dependency-resolver';
|
|
12
|
+
import { Logger } from '@teambit/logger';
|
|
13
|
+
import { EnvsAspect, EnvsMain } from '@teambit/envs';
|
|
14
|
+
import { ExtensionDataEntry } from '@teambit/legacy/dist/consumer/config';
|
|
15
|
+
import { getMaxSizeForComponents, InMemoryCache } from '@teambit/legacy/dist/cache/in-memory-cache';
|
|
16
|
+
import { createInMemoryCache } from '@teambit/legacy/dist/cache/cache-factory';
|
|
17
|
+
import ComponentNotFoundInPath from '@teambit/legacy/dist/consumer/component/exceptions/component-not-found-in-path';
|
|
18
|
+
import { Workspace } from '../workspace';
|
|
19
|
+
import { WorkspaceComponent } from './workspace-component';
|
|
20
|
+
|
|
21
|
+
export class WorkspaceComponentLoader {
|
|
22
|
+
private componentsCache: InMemoryCache<Component>; // cache loaded components
|
|
23
|
+
private componentsCacheForCapsule: InMemoryCache<Component>; // cache loaded components for capsule, must not use the cache for the workspace
|
|
24
|
+
constructor(
|
|
25
|
+
private workspace: Workspace,
|
|
26
|
+
private logger: Logger,
|
|
27
|
+
private dependencyResolver: DependencyResolverMain,
|
|
28
|
+
private envs: EnvsMain
|
|
29
|
+
) {
|
|
30
|
+
this.componentsCache = createInMemoryCache({ maxSize: getMaxSizeForComponents() });
|
|
31
|
+
this.componentsCacheForCapsule = createInMemoryCache({ maxSize: getMaxSizeForComponents() });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async getMany(ids: Array<ComponentID>, forCapsule = false): Promise<Component[]> {
|
|
35
|
+
const idsWithoutEmpty = compact(ids);
|
|
36
|
+
const errors: { id: ComponentID; err: Error }[] = [];
|
|
37
|
+
const longProcessLogger = this.logger.createLongProcessLogger('loading components', ids.length);
|
|
38
|
+
const componentsP = mapSeries(idsWithoutEmpty, async (id: ComponentID) => {
|
|
39
|
+
longProcessLogger.logProgress(id.toString());
|
|
40
|
+
return this.get(id, forCapsule).catch((err) => {
|
|
41
|
+
if (this.isComponentNotExistsError(err)) {
|
|
42
|
+
errors.push({
|
|
43
|
+
id,
|
|
44
|
+
err,
|
|
45
|
+
});
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
throw err;
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
const components = await componentsP;
|
|
52
|
+
errors.forEach((err) => {
|
|
53
|
+
if (!this.workspace.consumer.isLegacy) {
|
|
54
|
+
this.logger.console(`failed loading component ${err.id.toString()}, see full error in debug.log file`);
|
|
55
|
+
}
|
|
56
|
+
this.logger.warn(`failed loading component ${err.id.toString()}`, err.err);
|
|
57
|
+
});
|
|
58
|
+
// remove errored components
|
|
59
|
+
const filteredComponents: Component[] = compact(components);
|
|
60
|
+
longProcessLogger.end();
|
|
61
|
+
return filteredComponents;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getInvalid(ids: Array<ComponentID>): Promise<InvalidComponent[]> {
|
|
65
|
+
const idsWithoutEmpty = compact(ids);
|
|
66
|
+
const errors: InvalidComponent[] = [];
|
|
67
|
+
const longProcessLogger = this.logger.createLongProcessLogger('loading components', ids.length);
|
|
68
|
+
await mapSeries(idsWithoutEmpty, async (id: ComponentID) => {
|
|
69
|
+
longProcessLogger.logProgress(id.toString());
|
|
70
|
+
try {
|
|
71
|
+
await this.workspace.consumer.loadComponent(id._legacy);
|
|
72
|
+
} catch (err: any) {
|
|
73
|
+
if (ConsumerComponent.isComponentInvalidByErrorType(err)) {
|
|
74
|
+
errors.push({
|
|
75
|
+
id,
|
|
76
|
+
err,
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return errors;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async get(
|
|
87
|
+
componentId: ComponentID,
|
|
88
|
+
forCapsule = false,
|
|
89
|
+
legacyComponent?: ConsumerComponent,
|
|
90
|
+
useCache = true,
|
|
91
|
+
storeInCache = true
|
|
92
|
+
): Promise<Component> {
|
|
93
|
+
const bitIdWithVersion: BitId = getLatestVersionNumber(
|
|
94
|
+
this.workspace.consumer.bitmapIdsFromCurrentLane,
|
|
95
|
+
componentId._legacy
|
|
96
|
+
);
|
|
97
|
+
const id = bitIdWithVersion.version ? componentId.changeVersion(bitIdWithVersion.version) : componentId;
|
|
98
|
+
const fromCache = this.getFromCache(id, forCapsule);
|
|
99
|
+
if (fromCache && useCache) {
|
|
100
|
+
return fromCache;
|
|
101
|
+
}
|
|
102
|
+
const consumerComponent = legacyComponent || (await this.getConsumerComponent(id, forCapsule));
|
|
103
|
+
// in case of out-of-sync, the id may changed during the load process
|
|
104
|
+
const updatedId = consumerComponent ? ComponentID.fromLegacy(consumerComponent.id, id.scope) : id;
|
|
105
|
+
const component = await this.loadOne(updatedId, consumerComponent);
|
|
106
|
+
if (storeInCache) {
|
|
107
|
+
this.addMultipleEnvsIssueIfNeeded(component); // it's in storeInCache block, otherwise, it wasn't fully loaded
|
|
108
|
+
this.saveInCache(component, forCapsule);
|
|
109
|
+
}
|
|
110
|
+
return component;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private addMultipleEnvsIssueIfNeeded(component: Component) {
|
|
114
|
+
const envs = this.envs.getAllEnvsConfiguredOnComponent(component);
|
|
115
|
+
if (envs.length < 2) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
component.state.issues.getOrCreate(IssuesClasses.MultipleEnvs).data = envs.map((e) => e.id);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
clearCache() {
|
|
122
|
+
this.componentsCache.deleteAll();
|
|
123
|
+
this.componentsCacheForCapsule.deleteAll();
|
|
124
|
+
}
|
|
125
|
+
clearComponentCache(id: ComponentID) {
|
|
126
|
+
const idStr = id.toString();
|
|
127
|
+
this.componentsCache.delete(idStr);
|
|
128
|
+
this.componentsCacheForCapsule.delete(idStr);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private async loadOne(id: ComponentID, consumerComponent?: ConsumerComponent) {
|
|
132
|
+
const componentFromScope = await this.workspace.scope.get(id);
|
|
133
|
+
if (!consumerComponent) {
|
|
134
|
+
if (!componentFromScope) throw new MissingBitMapComponent(id.toString());
|
|
135
|
+
return componentFromScope;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let extensionDataList = await this.workspace.componentExtensions(id, componentFromScope);
|
|
139
|
+
const extensionsFromConsumerComponent = consumerComponent.extensions || new ExtensionDataList();
|
|
140
|
+
// Merge extensions added by the legacy code in memory (for example data of dependency resolver)
|
|
141
|
+
extensionDataList = ExtensionDataList.mergeConfigs([
|
|
142
|
+
extensionsFromConsumerComponent,
|
|
143
|
+
extensionDataList,
|
|
144
|
+
]).filterRemovedExtensions();
|
|
145
|
+
|
|
146
|
+
// temporarily mutate consumer component extensions until we remove all direct access from legacy to extensions data
|
|
147
|
+
// TODO: remove this once we remove all direct access from legacy code to extensions data
|
|
148
|
+
consumerComponent.extensions = extensionDataList;
|
|
149
|
+
|
|
150
|
+
const state = new State(
|
|
151
|
+
new Config(consumerComponent.mainFile, extensionDataList),
|
|
152
|
+
await this.workspace.createAspectList(extensionDataList),
|
|
153
|
+
ComponentFS.fromVinyls(consumerComponent.files),
|
|
154
|
+
consumerComponent.dependencies,
|
|
155
|
+
consumerComponent
|
|
156
|
+
);
|
|
157
|
+
if (componentFromScope) {
|
|
158
|
+
// Removed by @gilad. do not mutate the component from the scope
|
|
159
|
+
// componentFromScope.state = state;
|
|
160
|
+
// const workspaceComponent = WorkspaceComponent.fromComponent(componentFromScope, this.workspace);
|
|
161
|
+
const workspaceComponent = new WorkspaceComponent(
|
|
162
|
+
componentFromScope.id,
|
|
163
|
+
componentFromScope.head,
|
|
164
|
+
state,
|
|
165
|
+
componentFromScope.tags,
|
|
166
|
+
this.workspace
|
|
167
|
+
);
|
|
168
|
+
return this.executeLoadSlot(workspaceComponent);
|
|
169
|
+
}
|
|
170
|
+
return this.executeLoadSlot(this.newComponentFromState(id, state));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private saveInCache(component: Component, forCapsule: boolean): void {
|
|
174
|
+
if (forCapsule) {
|
|
175
|
+
this.componentsCacheForCapsule.set(component.id.toString(), component);
|
|
176
|
+
} else {
|
|
177
|
+
this.componentsCache.set(component.id.toString(), component);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* make sure that not only the id-str match, but also the legacy-id.
|
|
183
|
+
* this is needed because the ComponentID.toString() is the same whether or not the legacy-id has
|
|
184
|
+
* scope-name, as it includes the defaultScope if the scope is empty.
|
|
185
|
+
* as a result, when out-of-sync is happening and the id is changed to include scope-name in the
|
|
186
|
+
* legacy-id, the component is the cache has the old id.
|
|
187
|
+
*/
|
|
188
|
+
private getFromCache(id: ComponentID, forCapsule: boolean): Component | undefined {
|
|
189
|
+
const fromCache = forCapsule
|
|
190
|
+
? this.componentsCacheForCapsule.get(id.toString())
|
|
191
|
+
: this.componentsCache.get(id.toString());
|
|
192
|
+
if (fromCache && fromCache.id._legacy.isEqual(id._legacy)) {
|
|
193
|
+
return fromCache;
|
|
194
|
+
}
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async getConsumerComponent(id: ComponentID, forCapsule = false): Promise<ConsumerComponent | undefined> {
|
|
199
|
+
try {
|
|
200
|
+
return forCapsule
|
|
201
|
+
? // eslint-disable-next-line @typescript-eslint/return-await
|
|
202
|
+
await this.workspace.consumer.loadComponentForCapsule(id._legacy)
|
|
203
|
+
: // eslint-disable-next-line @typescript-eslint/return-await
|
|
204
|
+
await this.workspace.consumer.loadComponent(id._legacy);
|
|
205
|
+
} catch (err: any) {
|
|
206
|
+
// don't return undefined for any error. otherwise, if the component is invalid (e.g. main
|
|
207
|
+
// file is missing) it returns the model component later unexpectedly, or if it's new, it
|
|
208
|
+
// shows MissingBitMapComponent error incorrectly.
|
|
209
|
+
if (this.isComponentNotExistsError(err)) {
|
|
210
|
+
this.logger.debug(
|
|
211
|
+
`failed loading component "${id.toString()}" from the workspace due to "${err.name}" error\n${err.message}`
|
|
212
|
+
);
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
throw err;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private isComponentNotExistsError(err: Error): boolean {
|
|
220
|
+
return (
|
|
221
|
+
err instanceof ComponentNotFound ||
|
|
222
|
+
err instanceof MissingBitMapComponent ||
|
|
223
|
+
err instanceof ComponentNotFoundInPath
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private async executeLoadSlot(component: Component) {
|
|
228
|
+
const entries = this.workspace.onComponentLoadSlot.toArray();
|
|
229
|
+
const promises = entries.map(async ([extension, onLoad]) => {
|
|
230
|
+
const data = await onLoad(component);
|
|
231
|
+
return this.upsertExtensionData(component, extension, data);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Special load events which runs from the workspace but should run from the correct aspect
|
|
235
|
+
// TODO: remove this once those extensions dependent on workspace
|
|
236
|
+
const envsData = await this.workspace.getEnvSystemDescriptor(component);
|
|
237
|
+
|
|
238
|
+
// Move to deps resolver main runtime once we switch ws<> deps resolver direction
|
|
239
|
+
const policy = await this.dependencyResolver.mergeVariantPolicies(component.config.extensions);
|
|
240
|
+
const dependencies = await this.dependencyResolver.extractDepsFromLegacy(component, policy);
|
|
241
|
+
|
|
242
|
+
const depResolverData = {
|
|
243
|
+
dependencies,
|
|
244
|
+
policy: policy.serialize(),
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
promises.push(this.upsertExtensionData(component, EnvsAspect.id, envsData));
|
|
248
|
+
promises.push(this.upsertExtensionData(component, DependencyResolverAspect.id, depResolverData));
|
|
249
|
+
|
|
250
|
+
await Promise.all(promises);
|
|
251
|
+
|
|
252
|
+
// Update the aspect list to have changes happened during the on load slot (new data added above)
|
|
253
|
+
const updatedAspectList = await this.workspace.createAspectList(component.state.config.extensions);
|
|
254
|
+
component.state.aspects = updatedAspectList;
|
|
255
|
+
return component;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private newComponentFromState(id: ComponentID, state: State): Component {
|
|
259
|
+
return new WorkspaceComponent(id, null, state, new TagMap(), this.workspace);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private async upsertExtensionData(component: Component, extension: string, data: any) {
|
|
263
|
+
const existingExtension = component.state.config.extensions.findExtension(extension);
|
|
264
|
+
if (existingExtension) {
|
|
265
|
+
// Only merge top level of extension data
|
|
266
|
+
Object.assign(existingExtension.data, data);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
component.state.config.extensions.push(await this.getDataEntry(extension, data));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private async getDataEntry(extension: string, data: { [key: string]: any }): Promise<ExtensionDataEntry> {
|
|
273
|
+
// TODO: @gilad we need to refactor the extension data entry api.
|
|
274
|
+
return new ExtensionDataEntry(undefined, undefined, extension, undefined, data);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Component, ComponentID, Snap, State, TagMap } from '@teambit/component';
|
|
2
|
+
import { IssuesList } from '@teambit/component-issues';
|
|
3
|
+
import { Workspace } from '../workspace';
|
|
4
|
+
import { ComponentStatus } from './component-status';
|
|
5
|
+
|
|
6
|
+
// TODO: refactor this to a composition rather than to use inheritance
|
|
7
|
+
export class WorkspaceComponent extends Component {
|
|
8
|
+
constructor(
|
|
9
|
+
/**
|
|
10
|
+
* component ID represented by the `ComponentId` type.
|
|
11
|
+
*/
|
|
12
|
+
readonly id: ComponentID,
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* head version of the component. can be `null` for new components.
|
|
16
|
+
*/
|
|
17
|
+
readonly head: Snap | null = null,
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* state of the component.
|
|
21
|
+
*/
|
|
22
|
+
workspaceState: State,
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* tags of the component.
|
|
26
|
+
*/
|
|
27
|
+
readonly tags: TagMap = new TagMap(),
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* workspace extension.
|
|
31
|
+
*/
|
|
32
|
+
readonly workspace: Workspace
|
|
33
|
+
) {
|
|
34
|
+
super(id, head, workspaceState, tags, workspace);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async getStatus(): Promise<ComponentStatus> {
|
|
38
|
+
return this.workspace.getComponentStatus(this);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* get all issues reported on the component.
|
|
43
|
+
*/
|
|
44
|
+
getIssues(): IssuesList | null {
|
|
45
|
+
return this.workspace.getComponentIssues(this);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static fromComponent(component: Component, workspace: Workspace) {
|
|
49
|
+
return new WorkspaceComponent(component.id, component.head, component.state, component.tags, workspace);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { ComponentAspect, ComponentUI, ComponentModel } from '@teambit/component';
|
|
2
|
+
import { ComponentTreeAspect, ComponentTreeUI, ComponentTreeNode } from '@teambit/component-tree';
|
|
3
|
+
import { Slot, SlotRegistry } from '@teambit/harmony';
|
|
4
|
+
import ReactRouterAspect, { ReactRouterUI } from '@teambit/react-router';
|
|
5
|
+
import { RouteSlot } from '@teambit/ui-foundation.ui.react-router.slot-router';
|
|
6
|
+
import { Menu } from '@teambit/ui-foundation.ui.menu';
|
|
7
|
+
import SidebarAspect, { SidebarUI, SidebarItem, SidebarItemSlot } from '@teambit/sidebar';
|
|
8
|
+
import { MenuItemSlot, MenuItem } from '@teambit/ui-foundation.ui.main-dropdown';
|
|
9
|
+
import { UIAspect, UIRootUI as UIRoot, UIRuntime, UiUI } from '@teambit/ui';
|
|
10
|
+
import { GraphAspect, GraphUI } from '@teambit/graph';
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { RouteProps } from 'react-router-dom';
|
|
13
|
+
import CommandBarAspect, { CommandBarUI, ComponentSearcher, CommandHandler } from '@teambit/command-bar';
|
|
14
|
+
import { MenuLinkItem } from '@teambit/design.ui.surfaces.menu.link-item';
|
|
15
|
+
import { WorkspaceComponentsDrawer } from './ui/workspace-components-drawer';
|
|
16
|
+
import { ComponentTreeWidget } from './component-tree.widget';
|
|
17
|
+
import { Workspace } from './ui';
|
|
18
|
+
import { WorkspaceAspect } from './workspace.aspect';
|
|
19
|
+
|
|
20
|
+
export type SidebarWidgetSlot = SlotRegistry<ComponentTreeNode>;
|
|
21
|
+
|
|
22
|
+
export class WorkspaceUI {
|
|
23
|
+
constructor(
|
|
24
|
+
/**
|
|
25
|
+
* route slot.
|
|
26
|
+
*/
|
|
27
|
+
private routeSlot: RouteSlot,
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* component ui extension.
|
|
31
|
+
*/
|
|
32
|
+
private componentUi: ComponentUI,
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* menu slot
|
|
36
|
+
*/
|
|
37
|
+
private menuSlot: RouteSlot,
|
|
38
|
+
|
|
39
|
+
private menuItemSlot: MenuItemSlot,
|
|
40
|
+
|
|
41
|
+
private sidebar: SidebarUI,
|
|
42
|
+
|
|
43
|
+
private sidebarSlot: SidebarWidgetSlot,
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* sidebar link slot
|
|
47
|
+
*/
|
|
48
|
+
private sidebarItemSlot: SidebarItemSlot,
|
|
49
|
+
|
|
50
|
+
private commandBarUI: CommandBarUI,
|
|
51
|
+
|
|
52
|
+
reactRouterUI: ReactRouterUI
|
|
53
|
+
) {
|
|
54
|
+
this.componentSearcher = new ComponentSearcher(reactRouterUI.navigateTo);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private setKeyBindHandler: (updated: CommandHandler) => void = () => {};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* register a route to the workspace.
|
|
61
|
+
*/
|
|
62
|
+
registerRoute(route: RouteProps) {
|
|
63
|
+
this.routeSlot.register(route);
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
registerSidebarWidget(componentTreeNode: ComponentTreeNode) {
|
|
68
|
+
this.sidebarSlot.register(componentTreeNode);
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
registerMenuItem = (menuItems: MenuItem[]) => {
|
|
73
|
+
this.menuItemSlot.register(menuItems);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
setComponents = (components: ComponentModel[]) => {
|
|
77
|
+
this.componentSearcher.update(components);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
registerSidebarLink = (...links: SidebarItem[]) => {
|
|
81
|
+
this.sidebarItemSlot.register(links);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
componentSearcher: ComponentSearcher;
|
|
85
|
+
|
|
86
|
+
uiRoot(): UIRoot {
|
|
87
|
+
this.sidebar.registerDrawer(new WorkspaceComponentsDrawer(this.sidebarSlot));
|
|
88
|
+
this.commandBarUI.addSearcher(this.componentSearcher);
|
|
89
|
+
const [setKeyBindHandler] = this.commandBarUI.addCommand({
|
|
90
|
+
id: 'sidebar.toggle', // TODO - extract to a component!
|
|
91
|
+
handler: () => {},
|
|
92
|
+
displayName: 'Toggle component list',
|
|
93
|
+
keybinding: 'alt+s',
|
|
94
|
+
});
|
|
95
|
+
this.setKeyBindHandler = setKeyBindHandler;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
routes: [
|
|
99
|
+
{
|
|
100
|
+
path: '/',
|
|
101
|
+
children: (
|
|
102
|
+
<Workspace
|
|
103
|
+
menuSlot={this.menuSlot}
|
|
104
|
+
routeSlot={this.routeSlot}
|
|
105
|
+
sidebar={<this.sidebar.render itemSlot={this.sidebarItemSlot} />}
|
|
106
|
+
workspaceUI={this}
|
|
107
|
+
onSidebarTogglerChange={this.setKeyBindHandler}
|
|
108
|
+
/>
|
|
109
|
+
),
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private menuItems: MenuItem[] = [
|
|
116
|
+
{
|
|
117
|
+
category: 'general',
|
|
118
|
+
title: 'Open command bar',
|
|
119
|
+
keyChar: 'mod+k',
|
|
120
|
+
handler: () => this.commandBarUI?.run('command-bar.open'),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
category: 'general',
|
|
124
|
+
title: 'Toggle component list',
|
|
125
|
+
keyChar: 'alt+s',
|
|
126
|
+
handler: () => this.commandBarUI?.run('sidebar.toggle'),
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
static dependencies = [
|
|
131
|
+
UIAspect,
|
|
132
|
+
ComponentAspect,
|
|
133
|
+
SidebarAspect,
|
|
134
|
+
ComponentTreeAspect,
|
|
135
|
+
CommandBarAspect,
|
|
136
|
+
ReactRouterAspect,
|
|
137
|
+
GraphAspect,
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
static runtime = UIRuntime;
|
|
141
|
+
|
|
142
|
+
static slots = [
|
|
143
|
+
Slot.withType<RouteProps>(),
|
|
144
|
+
Slot.withType<RouteProps>(),
|
|
145
|
+
Slot.withType<ComponentTreeNode>(),
|
|
146
|
+
Slot.withType<MenuItemSlot>(),
|
|
147
|
+
Slot.withType<SidebarItemSlot>(),
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
static async provider(
|
|
151
|
+
[ui, componentUi, sidebar, componentTree, commandBarUI, reactRouterUI, graphUI]: [
|
|
152
|
+
UiUI,
|
|
153
|
+
ComponentUI,
|
|
154
|
+
SidebarUI,
|
|
155
|
+
ComponentTreeUI,
|
|
156
|
+
CommandBarUI,
|
|
157
|
+
ReactRouterUI,
|
|
158
|
+
GraphUI
|
|
159
|
+
],
|
|
160
|
+
config,
|
|
161
|
+
[routeSlot, menuSlot, menuItemSlot, sidebarSlot, sidebarItemSlot]: [
|
|
162
|
+
RouteSlot,
|
|
163
|
+
RouteSlot,
|
|
164
|
+
MenuItemSlot,
|
|
165
|
+
SidebarWidgetSlot,
|
|
166
|
+
SidebarItemSlot
|
|
167
|
+
]
|
|
168
|
+
) {
|
|
169
|
+
componentTree.registerTreeNode(new ComponentTreeWidget());
|
|
170
|
+
sidebarSlot.register(new ComponentTreeWidget());
|
|
171
|
+
graphUI.registerComponentWidget(new ComponentTreeWidget().widget);
|
|
172
|
+
|
|
173
|
+
const workspaceUI = new WorkspaceUI(
|
|
174
|
+
routeSlot,
|
|
175
|
+
componentUi,
|
|
176
|
+
menuSlot,
|
|
177
|
+
menuItemSlot,
|
|
178
|
+
sidebar,
|
|
179
|
+
sidebarSlot,
|
|
180
|
+
sidebarItemSlot,
|
|
181
|
+
commandBarUI,
|
|
182
|
+
reactRouterUI
|
|
183
|
+
);
|
|
184
|
+
ui.registerRoot(workspaceUI.uiRoot.bind(workspaceUI));
|
|
185
|
+
workspaceUI.registerMenuItem(workspaceUI.menuItems);
|
|
186
|
+
|
|
187
|
+
workspaceUI.registerSidebarLink(() => (
|
|
188
|
+
<MenuLinkItem exact href="/" icon="comps">
|
|
189
|
+
Gallery
|
|
190
|
+
</MenuLinkItem>
|
|
191
|
+
));
|
|
192
|
+
|
|
193
|
+
workspaceUI.menuSlot.register([
|
|
194
|
+
{
|
|
195
|
+
exact: true,
|
|
196
|
+
path: '/',
|
|
197
|
+
children: <Menu menuItemSlot={workspaceUI.menuItemSlot} />,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
path: workspaceUI.componentUi.routePath,
|
|
201
|
+
children: workspaceUI.componentUi.getMenu(WorkspaceAspect.id),
|
|
202
|
+
},
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
workspaceUI.routeSlot.register({
|
|
206
|
+
path: workspaceUI.componentUi.routePath,
|
|
207
|
+
children: workspaceUI.componentUi.getComponentUI(WorkspaceAspect.id),
|
|
208
|
+
});
|
|
209
|
+
return workspaceUI;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export default WorkspaceUI;
|
|
214
|
+
|
|
215
|
+
WorkspaceAspect.addRuntime(WorkspaceUI);
|