@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.
@@ -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
- {showcaseManagerState.explorerTreeData && (
167
- <TreeView
168
- components={{
169
- TreeNodeContainer: ShowcasesExplorerTreeNodeContainer,
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
- treeData={showcaseManagerState.explorerTreeData}
172
- getChildNodes={getChildNodes}
173
- innerProps={{
174
- toggleExpandNode,
175
- showcaseManagerState,
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 && <>TODO: 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
- path,
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
- initialize: flow,
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(metadata: ShowcaseMetadata): GeneratorFn<void> {
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 Showcase[];
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
  }