@finos/legend-application-studio 28.0.0 → 28.0.1
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/lib/components/ShowcaseManager.d.ts.map +1 -1
- package/lib/components/ShowcaseManager.js +105 -14
- package/lib/components/ShowcaseManager.js.map +1 -1
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +2 -2
- package/lib/stores/ShowcaseManagerState.d.ts +20 -2
- package/lib/stores/ShowcaseManagerState.d.ts.map +1 -1
- package/lib/stores/ShowcaseManagerState.js +79 -6
- package/lib/stores/ShowcaseManagerState.js.map +1 -1
- package/package.json +13 -13
- package/src/components/ShowcaseManager.tsx +375 -12
- package/src/stores/ShowcaseManagerState.ts +105 -6
@@ -28,19 +28,26 @@ import {
|
|
28
28
|
FolderIcon,
|
29
29
|
FolderOpenIcon,
|
30
30
|
HomeIcon,
|
31
|
+
SearchIcon,
|
32
|
+
TimesIcon,
|
33
|
+
CodeIcon,
|
34
|
+
clsx,
|
31
35
|
} from '@finos/legend-art';
|
32
36
|
import {
|
37
|
+
SHOWCASE_MANAGER_SEARCH_CATEGORY,
|
33
38
|
SHOWCASE_MANAGER_VIEW,
|
34
39
|
ShowcaseManagerState,
|
40
|
+
type ShowcaseTextSearchMatchResult,
|
35
41
|
type ShowcasesExplorerTreeNodeData,
|
36
42
|
} from '../stores/ShowcaseManagerState.js';
|
37
|
-
import { isNonNullable } from '@finos/legend-shared';
|
43
|
+
import { debounce, isNonNullable } from '@finos/legend-shared';
|
38
44
|
import { flowResult } from 'mobx';
|
39
45
|
import type { Showcase } from '@finos/legend-server-showcase';
|
40
46
|
import {
|
41
47
|
CODE_EDITOR_LANGUAGE,
|
42
48
|
CodeEditor,
|
43
49
|
} from '@finos/legend-lego/code-editor';
|
50
|
+
import React, { useEffect, useMemo, useRef } from 'react';
|
44
51
|
|
45
52
|
const ShowcasesExplorerTreeNodeContainer = observer(
|
46
53
|
(
|
@@ -161,21 +168,361 @@ const ShowcaseManagerExplorer = observer(
|
|
161
168
|
</div>
|
162
169
|
</div>
|
163
170
|
</div>
|
171
|
+
<button
|
172
|
+
className="showcase-manager__view__search-action"
|
173
|
+
tabIndex={-1}
|
174
|
+
title="Search"
|
175
|
+
onClick={() => {
|
176
|
+
showcaseManagerState.closeShowcase();
|
177
|
+
showcaseManagerState.setCurrentView(SHOWCASE_MANAGER_VIEW.SEARCH);
|
178
|
+
}}
|
179
|
+
>
|
180
|
+
<SearchIcon />
|
181
|
+
</button>
|
164
182
|
</div>
|
165
183
|
<div className="showcase-manager__view__content">
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
184
|
+
<div className="showcase-manager__explorer">
|
185
|
+
{showcaseManagerState.explorerTreeData && (
|
186
|
+
<TreeView
|
187
|
+
components={{
|
188
|
+
TreeNodeContainer: ShowcasesExplorerTreeNodeContainer,
|
189
|
+
}}
|
190
|
+
treeData={showcaseManagerState.explorerTreeData}
|
191
|
+
getChildNodes={getChildNodes}
|
192
|
+
innerProps={{
|
193
|
+
toggleExpandNode,
|
194
|
+
showcaseManagerState,
|
195
|
+
}}
|
196
|
+
/>
|
197
|
+
)}
|
198
|
+
</div>
|
199
|
+
</div>
|
200
|
+
</div>
|
201
|
+
);
|
202
|
+
},
|
203
|
+
);
|
204
|
+
|
205
|
+
const renderPreviewLine = (
|
206
|
+
line: number,
|
207
|
+
text: string,
|
208
|
+
result: ShowcaseTextSearchMatchResult,
|
209
|
+
): React.ReactNode => {
|
210
|
+
const lineMatches = result.match.matches
|
211
|
+
.filter((match) => match.line === line)
|
212
|
+
.sort((a, b) => a.startColumn - b.startColumn);
|
213
|
+
const chunks: React.ReactNode[] = [];
|
214
|
+
let currentIdx = 0;
|
215
|
+
lineMatches.forEach((match, idx) => {
|
216
|
+
if (currentIdx < match.startColumn - 1) {
|
217
|
+
chunks.push(text.substring(currentIdx, match.startColumn - 1));
|
218
|
+
}
|
219
|
+
chunks.push(
|
220
|
+
<span className="showcase-manager__search__code-result__content__line__text--highlighted">
|
221
|
+
{text.substring(match.startColumn - 1, match.endColumn - 1)}
|
222
|
+
</span>,
|
223
|
+
);
|
224
|
+
currentIdx = match.endColumn - 1;
|
225
|
+
});
|
226
|
+
if (currentIdx < text.length) {
|
227
|
+
chunks.push(text.substring(currentIdx, text.length));
|
228
|
+
}
|
229
|
+
return (
|
230
|
+
<>
|
231
|
+
{chunks.map((chunk, idx) => (
|
232
|
+
// eslint-disable-next-line react/no-array-index-key
|
233
|
+
<React.Fragment key={idx}>{chunk}</React.Fragment>
|
234
|
+
))}
|
235
|
+
</>
|
236
|
+
);
|
237
|
+
};
|
238
|
+
|
239
|
+
const ShowcaseManagerCodeSearchResult = observer(
|
240
|
+
(props: {
|
241
|
+
showcaseManagerState: ShowcaseManagerState;
|
242
|
+
result: ShowcaseTextSearchMatchResult;
|
243
|
+
}) => {
|
244
|
+
const { showcaseManagerState, result } = props;
|
245
|
+
const applicationStore = useApplicationStore();
|
246
|
+
|
247
|
+
return (
|
248
|
+
<div className="showcase-manager__search__code-result">
|
249
|
+
<div
|
250
|
+
className="showcase-manager__search__code-result__header"
|
251
|
+
title={`Showcase: ${result.showcase.title}\n\n${
|
252
|
+
result.showcase.description ?? '(no description)'
|
253
|
+
}\n\nClick to open showcase`}
|
254
|
+
onClick={() => {
|
255
|
+
flowResult(
|
256
|
+
showcaseManagerState.openShowcase(result.showcase),
|
257
|
+
).catch(applicationStore.alertUnhandledError);
|
258
|
+
}}
|
259
|
+
>
|
260
|
+
<div className="showcase-manager__search__code-result__header__icon">
|
261
|
+
<GenericTextFileIcon />
|
262
|
+
</div>
|
263
|
+
<div className="showcase-manager__search__code-result__header__title">
|
264
|
+
{result.showcase.path}
|
265
|
+
</div>
|
266
|
+
</div>
|
267
|
+
<div className="showcase-manager__search__code-result__content">
|
268
|
+
{result.match.preview.map((entry) => (
|
269
|
+
<div
|
270
|
+
key={entry.line}
|
271
|
+
className="showcase-manager__search__code-result__content__line"
|
272
|
+
onClick={() => {
|
273
|
+
flowResult(
|
274
|
+
showcaseManagerState.openShowcase(
|
275
|
+
result.showcase,
|
276
|
+
entry.line,
|
277
|
+
),
|
278
|
+
).catch(applicationStore.alertUnhandledError);
|
170
279
|
}}
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
280
|
+
>
|
281
|
+
<div
|
282
|
+
className={clsx(
|
283
|
+
'showcase-manager__search__code-result__content__line__gutter',
|
284
|
+
{
|
285
|
+
'showcase-manager__search__code-result__content__line__gutter--highlighted':
|
286
|
+
Boolean(
|
287
|
+
result.match.matches.find(
|
288
|
+
(match) => match.line === entry.line,
|
289
|
+
),
|
290
|
+
),
|
291
|
+
},
|
292
|
+
)}
|
293
|
+
>
|
294
|
+
{entry.line}
|
295
|
+
</div>
|
296
|
+
<div className="showcase-manager__search__code-result__content__line__text">
|
297
|
+
{renderPreviewLine(entry.line, entry.text, result)}
|
298
|
+
</div>
|
299
|
+
</div>
|
300
|
+
))}
|
301
|
+
</div>
|
302
|
+
</div>
|
303
|
+
);
|
304
|
+
},
|
305
|
+
);
|
306
|
+
|
307
|
+
const ShowcaseManagerSearchPanel = observer(
|
308
|
+
(props: { showcaseManagerState: ShowcaseManagerState }) => {
|
309
|
+
const { showcaseManagerState } = props;
|
310
|
+
const applicationStore = useApplicationStore();
|
311
|
+
const searchTextInpurRef = useRef<HTMLInputElement>(null);
|
312
|
+
const debouncedSearch = useMemo(
|
313
|
+
() =>
|
314
|
+
debounce(
|
315
|
+
() =>
|
316
|
+
flowResult(showcaseManagerState.search()).catch(
|
317
|
+
applicationStore.alertUnhandledError,
|
318
|
+
),
|
319
|
+
300,
|
320
|
+
),
|
321
|
+
[applicationStore, showcaseManagerState],
|
322
|
+
);
|
323
|
+
const clearSearchText = (): void => {
|
324
|
+
debouncedSearch.cancel();
|
325
|
+
showcaseManagerState.resetSearch();
|
326
|
+
};
|
327
|
+
const onSearchTextChange: React.ChangeEventHandler<HTMLInputElement> = (
|
328
|
+
event,
|
329
|
+
): void => {
|
330
|
+
const value = event.target.value;
|
331
|
+
showcaseManagerState.setSearchText(value);
|
332
|
+
debouncedSearch.cancel();
|
333
|
+
if (!value) {
|
334
|
+
showcaseManagerState.resetSearch();
|
335
|
+
} else {
|
336
|
+
debouncedSearch()?.catch(applicationStore.alertUnhandledError);
|
337
|
+
}
|
338
|
+
};
|
339
|
+
const onSearchKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (
|
340
|
+
event,
|
341
|
+
) => {
|
342
|
+
if (event.code === 'Enter') {
|
343
|
+
debouncedSearch.cancel();
|
344
|
+
if (showcaseManagerState.searchText) {
|
345
|
+
debouncedSearch()?.catch(applicationStore.alertUnhandledError);
|
346
|
+
}
|
347
|
+
} else if (event.code === 'Escape') {
|
348
|
+
searchTextInpurRef.current?.select();
|
349
|
+
}
|
350
|
+
};
|
351
|
+
|
352
|
+
useEffect(() => {
|
353
|
+
searchTextInpurRef.current?.select();
|
354
|
+
searchTextInpurRef.current?.focus();
|
355
|
+
}, []);
|
356
|
+
|
357
|
+
return (
|
358
|
+
<div className="showcase-manager__view">
|
359
|
+
<div className="showcase-manager__view__header">
|
360
|
+
<div className="showcase-manager__view__breadcrumbs">
|
361
|
+
<div
|
362
|
+
className="showcase-manager__view__breadcrumb"
|
363
|
+
onClick={() => {
|
364
|
+
showcaseManagerState.closeShowcase();
|
365
|
+
showcaseManagerState.setCurrentView(
|
366
|
+
SHOWCASE_MANAGER_VIEW.EXPLORER,
|
367
|
+
);
|
176
368
|
}}
|
369
|
+
>
|
370
|
+
<div className="showcase-manager__view__breadcrumb__icon">
|
371
|
+
<HomeIcon />
|
372
|
+
</div>
|
373
|
+
<div className="showcase-manager__view__breadcrumb__text">
|
374
|
+
Showcaces
|
375
|
+
</div>
|
376
|
+
</div>
|
377
|
+
<div className="showcase-manager__view__breadcrumb__arrow">
|
378
|
+
<ChevronRightIcon />
|
379
|
+
</div>
|
380
|
+
<div className="showcase-manager__view__breadcrumb">
|
381
|
+
<div className="showcase-manager__view__breadcrumb__text">
|
382
|
+
Search
|
383
|
+
</div>
|
384
|
+
</div>
|
385
|
+
</div>
|
386
|
+
<button
|
387
|
+
className="showcase-manager__view__search-action"
|
388
|
+
tabIndex={-1}
|
389
|
+
title="Search"
|
390
|
+
onClick={() => {
|
391
|
+
// since we're already on the search tab, clicking this will just focus the search input
|
392
|
+
searchTextInpurRef.current?.select();
|
393
|
+
}}
|
394
|
+
>
|
395
|
+
<SearchIcon />
|
396
|
+
</button>
|
397
|
+
</div>
|
398
|
+
<div className="showcase-manager__view__content">
|
399
|
+
<div className="showcase-manager__search__header">
|
400
|
+
<input
|
401
|
+
ref={searchTextInpurRef}
|
402
|
+
className="showcase-manager__search__input input--dark"
|
403
|
+
spellCheck={false}
|
404
|
+
placeholder="Search for showcase, code, etc."
|
405
|
+
value={showcaseManagerState.searchText}
|
406
|
+
onChange={onSearchTextChange}
|
407
|
+
onKeyDown={onSearchKeyDown}
|
177
408
|
/>
|
178
|
-
|
409
|
+
{!showcaseManagerState.searchText ? (
|
410
|
+
<div className="showcase-manager__search__input__search__icon">
|
411
|
+
<SearchIcon />
|
412
|
+
</div>
|
413
|
+
) : (
|
414
|
+
<button
|
415
|
+
className="showcase-manager__search__input__clear-btn"
|
416
|
+
tabIndex={-1}
|
417
|
+
onClick={clearSearchText}
|
418
|
+
title="Clear"
|
419
|
+
>
|
420
|
+
<TimesIcon />
|
421
|
+
</button>
|
422
|
+
)}
|
423
|
+
</div>
|
424
|
+
<div className="showcase-manager__search__results">
|
425
|
+
<div className="showcase-manager__search__results__categories">
|
426
|
+
<div
|
427
|
+
className={clsx('showcase-manager__search__results__category', {
|
428
|
+
'showcase-manager__search__results__category--active':
|
429
|
+
showcaseManagerState.currentSearchCaterogy ===
|
430
|
+
SHOWCASE_MANAGER_SEARCH_CATEGORY.SHOWCASE,
|
431
|
+
})}
|
432
|
+
onClick={() =>
|
433
|
+
showcaseManagerState.setCurrentSearchCategory(
|
434
|
+
SHOWCASE_MANAGER_SEARCH_CATEGORY.SHOWCASE,
|
435
|
+
)
|
436
|
+
}
|
437
|
+
title="Click to select category"
|
438
|
+
>
|
439
|
+
<div className="showcase-manager__search__results__category__content">
|
440
|
+
<div className="showcase-manager__search__results__category__icon">
|
441
|
+
<GenericTextFileIcon />
|
442
|
+
</div>
|
443
|
+
<div className="showcase-manager__search__results__category__label">
|
444
|
+
Showcases
|
445
|
+
</div>
|
446
|
+
</div>
|
447
|
+
<div className="showcase-manager__search__results__category__counter">
|
448
|
+
{showcaseManagerState.showcaseSearchResults?.length ?? 0}
|
449
|
+
</div>
|
450
|
+
</div>
|
451
|
+
<div
|
452
|
+
className={clsx('showcase-manager__search__results__category', {
|
453
|
+
'showcase-manager__search__results__category--active':
|
454
|
+
showcaseManagerState.currentSearchCaterogy ===
|
455
|
+
SHOWCASE_MANAGER_SEARCH_CATEGORY.CODE,
|
456
|
+
})}
|
457
|
+
onClick={() =>
|
458
|
+
showcaseManagerState.setCurrentSearchCategory(
|
459
|
+
SHOWCASE_MANAGER_SEARCH_CATEGORY.CODE,
|
460
|
+
)
|
461
|
+
}
|
462
|
+
title="Click to select category"
|
463
|
+
>
|
464
|
+
<div className="showcase-manager__search__results__category__content">
|
465
|
+
<div className="showcase-manager__search__results__category__icon">
|
466
|
+
<CodeIcon />
|
467
|
+
</div>
|
468
|
+
<div className="showcase-manager__search__results__category__label">
|
469
|
+
Code
|
470
|
+
</div>
|
471
|
+
</div>
|
472
|
+
<div className="showcase-manager__search__results__category__counter">
|
473
|
+
{showcaseManagerState.textSearchResults?.length ?? 0}
|
474
|
+
</div>
|
475
|
+
</div>
|
476
|
+
</div>
|
477
|
+
<div className="showcase-manager__search__results__list">
|
478
|
+
{showcaseManagerState.currentSearchCaterogy ===
|
479
|
+
SHOWCASE_MANAGER_SEARCH_CATEGORY.SHOWCASE && (
|
480
|
+
<>
|
481
|
+
{!showcaseManagerState.showcaseSearchResults && (
|
482
|
+
<BlankPanelContent>No results</BlankPanelContent>
|
483
|
+
)}
|
484
|
+
{showcaseManagerState.showcaseSearchResults?.map(
|
485
|
+
(showcase) => (
|
486
|
+
<div
|
487
|
+
key={showcase.uuid}
|
488
|
+
className="showcase-manager__search__showcase-result"
|
489
|
+
title={`Showcase: ${showcase.title}\n\n${
|
490
|
+
showcase.description ?? '(no description)'
|
491
|
+
}\n\nClick to open showcase`}
|
492
|
+
onClick={() => {
|
493
|
+
flowResult(
|
494
|
+
showcaseManagerState.openShowcase(showcase),
|
495
|
+
).catch(applicationStore.alertUnhandledError);
|
496
|
+
}}
|
497
|
+
>
|
498
|
+
<div className="showcase-manager__search__showcase-result__title">
|
499
|
+
{showcase.title}
|
500
|
+
</div>
|
501
|
+
<div className="showcase-manager__search__showcase-result__description">
|
502
|
+
{showcase.description ?? '(no description)'}
|
503
|
+
</div>
|
504
|
+
</div>
|
505
|
+
),
|
506
|
+
)}
|
507
|
+
</>
|
508
|
+
)}
|
509
|
+
{showcaseManagerState.currentSearchCaterogy ===
|
510
|
+
SHOWCASE_MANAGER_SEARCH_CATEGORY.CODE && (
|
511
|
+
<>
|
512
|
+
{!showcaseManagerState.textSearchResults && (
|
513
|
+
<BlankPanelContent>No results</BlankPanelContent>
|
514
|
+
)}
|
515
|
+
{showcaseManagerState.textSearchResults?.map((result) => (
|
516
|
+
<ShowcaseManagerCodeSearchResult
|
517
|
+
key={result.showcase.uuid}
|
518
|
+
showcaseManagerState={showcaseManagerState}
|
519
|
+
result={result}
|
520
|
+
/>
|
521
|
+
))}
|
522
|
+
</>
|
523
|
+
)}
|
524
|
+
</div>
|
525
|
+
</div>
|
179
526
|
</div>
|
180
527
|
</div>
|
181
528
|
);
|
@@ -222,6 +569,17 @@ const ShowcaseViewer = observer(
|
|
222
569
|
</div>
|
223
570
|
</div>
|
224
571
|
</div>
|
572
|
+
<button
|
573
|
+
className="showcase-manager__view__search-action"
|
574
|
+
tabIndex={-1}
|
575
|
+
title="Search"
|
576
|
+
onClick={() => {
|
577
|
+
showcaseManagerState.closeShowcase();
|
578
|
+
showcaseManagerState.setCurrentView(SHOWCASE_MANAGER_VIEW.SEARCH);
|
579
|
+
}}
|
580
|
+
>
|
581
|
+
<SearchIcon />
|
582
|
+
</button>
|
225
583
|
</div>
|
226
584
|
<div className="showcase-manager__view__content showcase-manager__viewer__content">
|
227
585
|
<div className="showcase-manager__viewer__title">
|
@@ -233,6 +591,7 @@ const ShowcaseViewer = observer(
|
|
233
591
|
language={CODE_EDITOR_LANGUAGE.PURE}
|
234
592
|
inputValue={showcase.code}
|
235
593
|
isReadOnly={true}
|
594
|
+
lineToScroll={showcaseManagerState.showcaseLineToScroll}
|
236
595
|
/>
|
237
596
|
</div>
|
238
597
|
</div>
|
@@ -262,7 +621,11 @@ const ShowcaseManagerContent = observer(
|
|
262
621
|
showcaseManagerState={showcaseManagerState}
|
263
622
|
/>
|
264
623
|
)}
|
265
|
-
{currentView === SHOWCASE_MANAGER_VIEW.SEARCH &&
|
624
|
+
{currentView === SHOWCASE_MANAGER_VIEW.SEARCH && (
|
625
|
+
<ShowcaseManagerSearchPanel
|
626
|
+
showcaseManagerState={showcaseManagerState}
|
627
|
+
/>
|
628
|
+
)}
|
266
629
|
</>
|
267
630
|
)}
|
268
631
|
</div>
|
@@ -20,6 +20,7 @@ import {
|
|
20
20
|
LogEvent,
|
21
21
|
assertErrorThrown,
|
22
22
|
guaranteeNonNullable,
|
23
|
+
isNonNullable,
|
23
24
|
} from '@finos/legend-shared';
|
24
25
|
import { action, flow, makeObservable, observable } from 'mobx';
|
25
26
|
import { LEGEND_STUDIO_APP_EVENT } from '../__lib__/LegendStudioEvent.js';
|
@@ -27,10 +28,13 @@ import {
|
|
27
28
|
type Showcase,
|
28
29
|
type ShowcaseMetadata,
|
29
30
|
ShowcaseRegistryServerClient,
|
31
|
+
type ShowcaseTextSearchMatch,
|
32
|
+
type ShowcaseTextSearchResult,
|
30
33
|
} from '@finos/legend-server-showcase';
|
31
34
|
import type { LegendStudioApplicationStore } from './LegendStudioBaseStore.js';
|
32
35
|
import {
|
33
36
|
ApplicationExtensionState,
|
37
|
+
DEFAULT_TYPEAHEAD_SEARCH_MINIMUM_SEARCH_LENGTH,
|
34
38
|
type GenericLegendApplicationStore,
|
35
39
|
} from '@finos/legend-application';
|
36
40
|
import type { TreeData, TreeNodeData } from '@finos/legend-art';
|
@@ -41,6 +45,11 @@ export enum SHOWCASE_MANAGER_VIEW {
|
|
41
45
|
SEARCH = 'SEARCH',
|
42
46
|
}
|
43
47
|
|
48
|
+
export enum SHOWCASE_MANAGER_SEARCH_CATEGORY {
|
49
|
+
SHOWCASE = 'SHOWCASE',
|
50
|
+
CODE = 'CODE',
|
51
|
+
}
|
52
|
+
|
44
53
|
export class ShowcasesExplorerTreeNodeData implements TreeNodeData {
|
45
54
|
isOpen?: boolean | undefined;
|
46
55
|
id: string;
|
@@ -75,7 +84,7 @@ const buildShowcasesExplorerTreeNode = (
|
|
75
84
|
// showcase node
|
76
85
|
node = new ShowcasesExplorerTreeNodeData(
|
77
86
|
`${parentId ? `${parentId}${DIRECTORY_PATH_DELIMITER}` : ''}${path}`,
|
78
|
-
|
87
|
+
showcase.title,
|
79
88
|
parentId,
|
80
89
|
showcase,
|
81
90
|
);
|
@@ -135,21 +144,33 @@ const buildShowcasesExplorerTreeData = (
|
|
135
144
|
return { rootIds, nodes };
|
136
145
|
};
|
137
146
|
|
147
|
+
export type ShowcaseTextSearchMatchResult = {
|
148
|
+
match: ShowcaseTextSearchMatch;
|
149
|
+
showcase: ShowcaseMetadata;
|
150
|
+
};
|
151
|
+
|
138
152
|
export class ShowcaseManagerState extends ApplicationExtensionState {
|
139
153
|
private static readonly IDENTIFIER = 'showcase-manager';
|
140
154
|
|
141
155
|
readonly applicationStore: LegendStudioApplicationStore;
|
142
156
|
readonly initState = ActionState.create();
|
143
157
|
readonly fetchShowcaseState = ActionState.create();
|
158
|
+
readonly textSearchState = ActionState.create();
|
144
159
|
|
145
160
|
private readonly showcaseServerClient?: ShowcaseRegistryServerClient;
|
146
161
|
|
147
162
|
showcases: ShowcaseMetadata[] = [];
|
148
163
|
currentShowcase?: Showcase | undefined;
|
164
|
+
showcaseLineToScroll?: number | undefined;
|
149
165
|
|
150
166
|
currentView = SHOWCASE_MANAGER_VIEW.EXPLORER;
|
151
167
|
explorerTreeData?: TreeData<ShowcasesExplorerTreeNodeData> | undefined;
|
152
168
|
|
169
|
+
searchText = '';
|
170
|
+
showcaseSearchResults?: ShowcaseMetadata[] | undefined;
|
171
|
+
textSearchResults?: ShowcaseTextSearchMatchResult[] | undefined;
|
172
|
+
currentSearchCaterogy = SHOWCASE_MANAGER_SEARCH_CATEGORY.SHOWCASE;
|
173
|
+
|
153
174
|
constructor(applicationStore: LegendStudioApplicationStore) {
|
154
175
|
super();
|
155
176
|
|
@@ -158,11 +179,20 @@ export class ShowcaseManagerState extends ApplicationExtensionState {
|
|
158
179
|
currentShowcase: observable,
|
159
180
|
currentView: observable,
|
160
181
|
explorerTreeData: observable.ref,
|
161
|
-
|
182
|
+
searchText: observable,
|
183
|
+
textSearchResults: observable.ref,
|
184
|
+
showcaseSearchResults: observable.ref,
|
185
|
+
currentSearchCaterogy: observable,
|
186
|
+
showcaseLineToScroll: observable,
|
162
187
|
setCurrentView: action,
|
163
188
|
closeShowcase: action,
|
164
|
-
openShowcase: flow,
|
165
189
|
setExplorerTreeData: action,
|
190
|
+
setSearchText: action,
|
191
|
+
resetSearch: action,
|
192
|
+
setCurrentSearchCategory: action,
|
193
|
+
search: flow,
|
194
|
+
initialize: flow,
|
195
|
+
openShowcase: flow,
|
166
196
|
});
|
167
197
|
|
168
198
|
this.applicationStore = applicationStore;
|
@@ -225,14 +255,17 @@ export class ShowcaseManagerState extends ApplicationExtensionState {
|
|
225
255
|
this.currentView = val;
|
226
256
|
}
|
227
257
|
|
228
|
-
*openShowcase(
|
258
|
+
*openShowcase(
|
259
|
+
metadata: ShowcaseMetadata,
|
260
|
+
showcaseLineToScroll?: number | undefined,
|
261
|
+
): GeneratorFn<void> {
|
229
262
|
this.fetchShowcaseState.inProgress();
|
230
263
|
|
231
264
|
try {
|
232
265
|
this.currentShowcase = (yield this.client.getShowcase(
|
233
266
|
metadata.path,
|
234
267
|
)) as Showcase;
|
235
|
-
|
268
|
+
this.showcaseLineToScroll = showcaseLineToScroll;
|
236
269
|
this.fetchShowcaseState.pass();
|
237
270
|
} catch (error) {
|
238
271
|
assertErrorThrown(error);
|
@@ -246,12 +279,27 @@ export class ShowcaseManagerState extends ApplicationExtensionState {
|
|
246
279
|
|
247
280
|
closeShowcase(): void {
|
248
281
|
this.currentShowcase = undefined;
|
282
|
+
this.showcaseLineToScroll = undefined;
|
249
283
|
}
|
250
284
|
|
251
285
|
setExplorerTreeData(val: TreeData<ShowcasesExplorerTreeNodeData>): void {
|
252
286
|
this.explorerTreeData = val;
|
253
287
|
}
|
254
288
|
|
289
|
+
setSearchText(val: string): void {
|
290
|
+
this.searchText = val;
|
291
|
+
}
|
292
|
+
|
293
|
+
resetSearch(): void {
|
294
|
+
this.searchText = '';
|
295
|
+
this.textSearchResults = undefined;
|
296
|
+
this.showcaseSearchResults = undefined;
|
297
|
+
}
|
298
|
+
|
299
|
+
setCurrentSearchCategory(val: SHOWCASE_MANAGER_SEARCH_CATEGORY): void {
|
300
|
+
this.currentSearchCaterogy = val;
|
301
|
+
}
|
302
|
+
|
255
303
|
*initialize(): GeneratorFn<void> {
|
256
304
|
if (!this.isEnabled || this.initState.isInProgress) {
|
257
305
|
return;
|
@@ -259,8 +307,16 @@ export class ShowcaseManagerState extends ApplicationExtensionState {
|
|
259
307
|
this.initState.inProgress();
|
260
308
|
|
261
309
|
try {
|
262
|
-
this.showcases = (yield this.client.getShowcases()) as
|
310
|
+
this.showcases = (yield this.client.getShowcases()) as ShowcaseMetadata[];
|
263
311
|
this.explorerTreeData = buildShowcasesExplorerTreeData(this.showcases);
|
312
|
+
// expand all the root nodes by default
|
313
|
+
this.explorerTreeData.rootIds.forEach((rootId) => {
|
314
|
+
const rootNode = this.explorerTreeData?.nodes.get(rootId);
|
315
|
+
if (rootNode) {
|
316
|
+
rootNode.isOpen = true;
|
317
|
+
}
|
318
|
+
});
|
319
|
+
this.setExplorerTreeData({ ...this.explorerTreeData });
|
264
320
|
|
265
321
|
this.initState.pass();
|
266
322
|
} catch (error) {
|
@@ -272,4 +328,47 @@ export class ShowcaseManagerState extends ApplicationExtensionState {
|
|
272
328
|
this.initState.fail();
|
273
329
|
}
|
274
330
|
}
|
331
|
+
|
332
|
+
*search(): GeneratorFn<void> {
|
333
|
+
if (
|
334
|
+
this.textSearchState.isInProgress ||
|
335
|
+
this.searchText.length <= DEFAULT_TYPEAHEAD_SEARCH_MINIMUM_SEARCH_LENGTH
|
336
|
+
) {
|
337
|
+
return;
|
338
|
+
}
|
339
|
+
this.textSearchState.inProgress();
|
340
|
+
|
341
|
+
try {
|
342
|
+
const result = (yield this.client.search(
|
343
|
+
this.searchText,
|
344
|
+
)) as ShowcaseTextSearchResult;
|
345
|
+
this.textSearchResults = result.textMatches
|
346
|
+
.map((match) => {
|
347
|
+
const matchingShowcase = this.showcases.find(
|
348
|
+
(showcase) => showcase.path === match.path,
|
349
|
+
);
|
350
|
+
if (matchingShowcase) {
|
351
|
+
return {
|
352
|
+
showcase: matchingShowcase,
|
353
|
+
match,
|
354
|
+
};
|
355
|
+
}
|
356
|
+
return undefined;
|
357
|
+
})
|
358
|
+
.filter(isNonNullable);
|
359
|
+
this.showcaseSearchResults = result.showcases
|
360
|
+
.map((showcasePath) =>
|
361
|
+
this.showcases.find((showcase) => showcase.path === showcasePath),
|
362
|
+
)
|
363
|
+
.filter(isNonNullable);
|
364
|
+
} catch (error) {
|
365
|
+
assertErrorThrown(error);
|
366
|
+
this.applicationStore.logService.error(
|
367
|
+
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SHOWCASE_MANAGER_FAILURE),
|
368
|
+
error,
|
369
|
+
);
|
370
|
+
} finally {
|
371
|
+
this.textSearchState.complete();
|
372
|
+
}
|
373
|
+
}
|
275
374
|
}
|