@finos/legend-application-studio 28.13.5 → 28.13.7

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.
@@ -14,17 +14,26 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import { createContext, useContext, useEffect, useMemo, useState } from 'react';
17
+ import {
18
+ createContext,
19
+ useContext,
20
+ useEffect,
21
+ useMemo,
22
+ useState,
23
+ useRef,
24
+ } from 'react';
18
25
  import { observer, useLocalObservable } from 'mobx-react-lite';
19
26
  import {
20
27
  clsx,
21
- MarkdownTextViewer,
22
28
  AssistantIcon,
23
29
  compareLabelFn,
24
- PlusIcon,
25
30
  GitBranchIcon,
26
31
  CustomSelectorInput,
27
- RepoIcon,
32
+ LongArrowRightIcon,
33
+ DividerWithText,
34
+ SearchIcon,
35
+ FileImportIcon,
36
+ BaseCard,
28
37
  } from '@finos/legend-art';
29
38
  import { LEGEND_STUDIO_TEST_ID } from '../../__lib__/LegendStudioTesting.js';
30
39
  import {
@@ -57,6 +66,7 @@ import {
57
66
  import { debounce, guaranteeNonNullable } from '@finos/legend-shared';
58
67
  import { WorkspaceSetupStore } from '../../stores/workspace-setup/WorkspaceSetupStore.js';
59
68
  import { DocumentationLink } from '@finos/legend-lego/application';
69
+ import { openShowcaseManager } from '../../stores/ShowcaseManagerState.js';
60
70
 
61
71
  const WorkspaceSetupStoreContext = createContext<
62
72
  WorkspaceSetupStore | undefined
@@ -64,6 +74,188 @@ const WorkspaceSetupStoreContext = createContext<
64
74
 
65
75
  export const DEFAULT_WORKSPACE_SOURCE = 'HEAD';
66
76
 
77
+ export const ShowcaseCard: React.FC<{ hideDocumentation?: boolean }> = (
78
+ props,
79
+ ) => {
80
+ const applicationStore = useLegendStudioApplicationStore();
81
+ const appDocUrl = applicationStore.documentationService.url;
82
+ return (
83
+ <BaseCard
84
+ className="workspace-setup__content__card"
85
+ cardMedia={undefined}
86
+ cardName="Showcase projects"
87
+ cardContent={
88
+ <div>
89
+ Review showcase projects with sample project code and re-use existing
90
+ code snippets to quickly build your model.
91
+ {!props.hideDocumentation && (
92
+ <>
93
+ Review the comprehensive{' '}
94
+ <a
95
+ href={appDocUrl}
96
+ target="_blank"
97
+ rel="noreferrer"
98
+ className="workspace-setup__content__link"
99
+ >
100
+ documentation
101
+ </a>{' '}
102
+ available for the studio.
103
+ </>
104
+ )}
105
+ </div>
106
+ }
107
+ cardActions={[
108
+ {
109
+ title: 'Showcase explorer',
110
+ content: <FileImportIcon />,
111
+ action: () => openShowcaseManager(applicationStore),
112
+ },
113
+ ]}
114
+ isStable={true}
115
+ />
116
+ );
117
+ };
118
+
119
+ export const DocumentationCard: React.FC = () => {
120
+ const applicationStore = useLegendStudioApplicationStore();
121
+ const appDocUrl = applicationStore.documentationService.url;
122
+ return (
123
+ <BaseCard
124
+ className="workspace-setup__content__card"
125
+ cardMedia={undefined}
126
+ cardName="Documentation"
127
+ cardContent={
128
+ <div>
129
+ Review the comprehensive{' '}
130
+ <a
131
+ href={appDocUrl}
132
+ target="_blank"
133
+ rel="noreferrer"
134
+ className="workspace-setup__content__link"
135
+ >
136
+ documentation
137
+ </a>{' '}
138
+ available for the studio.
139
+ </div>
140
+ }
141
+ cardActions={[
142
+ {
143
+ title: 'Review documentation',
144
+ content: <FileImportIcon />,
145
+ action: () => {
146
+ if (appDocUrl) {
147
+ applicationStore.navigationService.navigator.visitAddress(
148
+ appDocUrl,
149
+ );
150
+ }
151
+ },
152
+ },
153
+ ]}
154
+ isStable={true}
155
+ />
156
+ );
157
+ };
158
+
159
+ export const ProductionCard: React.FC = () => {
160
+ const applicationStore = useLegendStudioApplicationStore();
161
+ const productionDocument = applicationStore.documentationService.getDocEntry(
162
+ LEGEND_STUDIO_DOCUMENTATION_KEY.APPLICATION_PRODUCTION,
163
+ );
164
+ return (
165
+ productionDocument?.title &&
166
+ productionDocument.markdownText &&
167
+ productionDocument.text && (
168
+ <BaseCard
169
+ className="workspace-setup__content__card"
170
+ cardMedia={undefined}
171
+ cardName={productionDocument.title}
172
+ cardContent={productionDocument.markdownText.value}
173
+ cardActions={[
174
+ {
175
+ title: productionDocument.text,
176
+ content: <FileImportIcon />,
177
+ action: () => {
178
+ if (productionDocument.url) {
179
+ applicationStore.navigationService.navigator.visitAddress(
180
+ productionDocument.url,
181
+ );
182
+ }
183
+ },
184
+ },
185
+ ]}
186
+ isStable={true}
187
+ />
188
+ )
189
+ );
190
+ };
191
+
192
+ export const SandboxCard: React.FC = () => {
193
+ const applicationStore = useLegendStudioApplicationStore();
194
+ const sandboxDocument = applicationStore.documentationService.getDocEntry(
195
+ LEGEND_STUDIO_DOCUMENTATION_KEY.APPLICATION_SANDBOX,
196
+ );
197
+ return (
198
+ sandboxDocument?.title &&
199
+ sandboxDocument.markdownText &&
200
+ sandboxDocument.text && (
201
+ <BaseCard
202
+ className="workspace-setup__content__card"
203
+ cardMedia={undefined}
204
+ cardName={sandboxDocument.title}
205
+ cardContent={sandboxDocument.markdownText.value}
206
+ cardActions={[
207
+ {
208
+ title: sandboxDocument.text,
209
+ content: <FileImportIcon />,
210
+ action: () => {
211
+ if (sandboxDocument.url) {
212
+ applicationStore.navigationService.navigator.visitAddress(
213
+ sandboxDocument.url,
214
+ );
215
+ }
216
+ },
217
+ },
218
+ ]}
219
+ isStable={true}
220
+ />
221
+ )
222
+ );
223
+ };
224
+
225
+ export const RuleEngagementCard: React.FC = () => {
226
+ const applicationStore = useLegendStudioApplicationStore();
227
+ const ruleEngagementDocument =
228
+ applicationStore.documentationService.getDocEntry(
229
+ LEGEND_STUDIO_DOCUMENTATION_KEY.APPLICATION_RULE_ENGAGEMENT,
230
+ );
231
+ return (
232
+ ruleEngagementDocument?.title &&
233
+ ruleEngagementDocument.markdownText &&
234
+ ruleEngagementDocument.text && (
235
+ <BaseCard
236
+ className="workspace-setup__content__card"
237
+ cardMedia={undefined}
238
+ cardName={ruleEngagementDocument.title}
239
+ cardContent={ruleEngagementDocument.markdownText.value}
240
+ cardActions={[
241
+ {
242
+ title: ruleEngagementDocument.text,
243
+ content: <FileImportIcon />,
244
+ action: () => {
245
+ if (ruleEngagementDocument.url) {
246
+ applicationStore.navigationService.navigator.visitAddress(
247
+ ruleEngagementDocument.url,
248
+ );
249
+ }
250
+ },
251
+ },
252
+ ]}
253
+ isStable={true}
254
+ />
255
+ )
256
+ );
257
+ };
258
+
67
259
  const WorkspaceSetupStoreProvider: React.FC<{
68
260
  children: React.ReactNode;
69
261
  }> = ({ children }) => {
@@ -104,12 +296,10 @@ export const WorkspaceSetup = withWorkspaceSetupStore(
104
296
  const setupStore = useWorkspaceSetupStore();
105
297
  const applicationStore = useLegendStudioApplicationStore();
106
298
  const [projectSearchText, setProjectSearchText] = useState('');
299
+ const goButtonRef = useRef<HTMLButtonElement>(null);
107
300
 
108
301
  const toggleAssistant = (): void =>
109
302
  applicationStore.assistantService.toggleAssistant();
110
- const documentation = applicationStore.documentationService.getDocEntry(
111
- LEGEND_STUDIO_DOCUMENTATION_KEY.SETUP_WORKSPACE,
112
- );
113
303
 
114
304
  // projects
115
305
  const projectOptions = setupStore.projects
@@ -165,6 +355,7 @@ export const WorkspaceSetup = withWorkspaceSetupStore(
165
355
  `Can't edit current workspace as the current project is not configured`,
166
356
  );
167
357
  }
358
+ goButtonRef.current?.focus();
168
359
  } else {
169
360
  setupStore.resetWorkspace();
170
361
  }
@@ -218,131 +409,168 @@ export const WorkspaceSetup = withWorkspaceSetupStore(
218
409
  className="workspace-setup__content"
219
410
  data-testid={LEGEND_STUDIO_TEST_ID.SETUP__CONTENT}
220
411
  >
221
- <div className="workspace-setup__content__main">
222
- <div className="workspace-setup__title">
223
- <div className="workspace-setup__title__header">
224
- Setup Workspace
225
- <DocumentationLink
226
- documentationKey={
227
- LEGEND_STUDIO_DOCUMENTATION_KEY.SETUP_WORKSPACE
228
- }
229
- />
230
- </div>
231
- {documentation?.markdownText && (
232
- <div className="workspace-setup__title__documentation">
233
- <MarkdownTextViewer value={documentation.markdownText} />
412
+ <div className="workspace-setup__content__body">
413
+ <div className="workspace-setup__content__main">
414
+ <div className="workspace-setup__title">
415
+ <div className="workspace-setup__logo">
416
+ <img
417
+ src="/favicon.ico"
418
+ className="workspace-setup__logo__icon"
419
+ />
234
420
  </div>
235
- )}
236
- </div>
237
- <form
238
- onSubmit={(event) => {
239
- event.preventDefault();
240
- handleProceed();
241
- }}
242
- >
243
- <div className="workspace-setup__selector">
244
- <div
245
- className="workspace-setup__selector__icon"
246
- title="project"
247
- >
248
- <RepoIcon className="workspace-setup__selector__icon--project" />
421
+ <div className="workspace-setup__title__header">
422
+ Welcome to Legend Studio
423
+ <DocumentationLink
424
+ documentationKey={
425
+ LEGEND_STUDIO_DOCUMENTATION_KEY.SETUP_WORKSPACE
426
+ }
427
+ />
249
428
  </div>
250
- <CustomSelectorInput
251
- className="workspace-setup__selector__input"
252
- options={projectOptions}
253
- isLoading={setupStore.loadProjectsState.isInProgress}
254
- onInputChange={onProjectSearchTextChange}
255
- inputValue={projectSearchText}
256
- onChange={onProjectChange}
257
- value={selectedProjectOption}
258
- placeholder="Search for project..."
259
- isClearable={true}
260
- escapeClearsValue={true}
261
- darkMode={true}
262
- formatOptionLabel={getProjectOptionLabelFormatter(
263
- applicationStore,
264
- setupStore.currentProjectConfigurationStatus,
265
- )}
266
- />
267
- <button
268
- className="workspace-setup__selector__action btn--dark"
269
- onClick={showCreateProjectModal}
270
- tabIndex={-1}
271
- type="button" // prevent this toggler being activated on form submission
272
- title="Create a Project"
273
- >
274
- <PlusIcon />
275
- </button>
276
429
  </div>
277
- <div className="workspace-setup__selector">
278
- <div
279
- className="workspace-setup__selector__icon"
280
- title="workspace"
281
- >
282
- <GitBranchIcon className="workspace-setup__selector__icon--workspace" />
430
+ <div className="workspace-setup__selectors">
431
+ <div className="workspace-setup__selectors__container">
432
+ <div className="workspace-setup__selector">
433
+ <div className="workspace-setup__selector__header">
434
+ Search for project
435
+ </div>
436
+ <div className="workspace-setup__selector__content">
437
+ <div
438
+ className="workspace-setup__selector__content__icon"
439
+ title="project"
440
+ >
441
+ <SearchIcon className="workspace-setup__selector__content__icon--project" />
442
+ </div>
443
+ <CustomSelectorInput
444
+ className="workspace-setup__selector__content__input"
445
+ options={projectOptions}
446
+ isLoading={
447
+ setupStore.loadProjectsState.isInProgress
448
+ }
449
+ onInputChange={onProjectSearchTextChange}
450
+ inputValue={projectSearchText}
451
+ onChange={onProjectChange}
452
+ value={selectedProjectOption}
453
+ placeholder="Search for project..."
454
+ isClearable={true}
455
+ escapeClearsValue={true}
456
+ darkMode={true}
457
+ formatOptionLabel={getProjectOptionLabelFormatter(
458
+ applicationStore,
459
+ setupStore.currentProjectConfigurationStatus,
460
+ )}
461
+ />
462
+ </div>
463
+ </div>
464
+ <div className="workspace-setup__selector">
465
+ <div className="workspace-setup__selector__header">
466
+ Choose a workspace
467
+ </div>
468
+ <div className="workspace-setup__selector__content">
469
+ <div
470
+ className="workspace-setup__selector__content__icon"
471
+ title="workspace"
472
+ >
473
+ <GitBranchIcon className="workspace-setup__selector__content__icon--workspace" />
474
+ </div>
475
+ <CustomSelectorInput
476
+ className="workspace-setup__selector__content__input"
477
+ options={workspaceOptions}
478
+ onKeyDown={(
479
+ event: React.KeyboardEvent<HTMLDivElement>,
480
+ ) => {
481
+ if (event.key === 'Enter') {
482
+ goButtonRef.current?.focus();
483
+ handleProceed();
484
+ }
485
+ }}
486
+ disabled={
487
+ !setupStore.currentProject ||
488
+ !setupStore.currentProjectConfigurationStatus ||
489
+ !setupStore.currentProjectConfigurationStatus
490
+ .isConfigured ||
491
+ setupStore.loadProjectsState.isInProgress ||
492
+ setupStore.loadWorkspacesState.isInProgress
493
+ }
494
+ isLoading={
495
+ setupStore.loadWorkspacesState.isInProgress
496
+ }
497
+ onChange={onWorkspaceChange}
498
+ formatOptionLabel={formatWorkspaceOptionLabel}
499
+ value={selectedWorkspaceOption}
500
+ placeholder={
501
+ setupStore.loadWorkspacesState.isInProgress
502
+ ? 'Loading workspaces...'
503
+ : !setupStore.currentProject
504
+ ? 'In order to choose a workspace, a project must be chosen'
505
+ : workspaceOptions.length
506
+ ? 'Choose an existing workspace'
507
+ : 'You have no workspaces. Please create one to proceed...'
508
+ }
509
+ isClearable={true}
510
+ escapeClearsValue={true}
511
+ darkMode={true}
512
+ />
513
+ </div>
514
+ </div>
283
515
  </div>
284
- <CustomSelectorInput
285
- className="workspace-setup__selector__input"
286
- options={workspaceOptions}
287
- disabled={
288
- !setupStore.currentProject ||
289
- !setupStore.currentProjectConfigurationStatus ||
290
- !setupStore.currentProjectConfigurationStatus
291
- .isConfigured ||
292
- setupStore.loadProjectsState.isInProgress ||
293
- setupStore.loadWorkspacesState.isInProgress
294
- }
295
- isLoading={setupStore.loadWorkspacesState.isInProgress}
296
- onChange={onWorkspaceChange}
297
- formatOptionLabel={formatWorkspaceOptionLabel}
298
- value={selectedWorkspaceOption}
299
- placeholder={
300
- setupStore.loadWorkspacesState.isInProgress
301
- ? 'Loading workspaces...'
302
- : !setupStore.currentProject
303
- ? 'In order to choose a workspace, a project must be chosen'
304
- : workspaceOptions.length
305
- ? 'Choose an existing workspace'
306
- : 'You have no workspaces. Please create one to proceed...'
307
- }
308
- isClearable={true}
309
- escapeClearsValue={true}
310
- darkMode={true}
311
- />
312
- <button
313
- className="workspace-setup__selector__action btn--dark"
314
- onClick={showCreateWorkspaceModal}
315
- disabled={
316
- !setupStore.currentProject ||
317
- !setupStore.currentProjectConfigurationStatus ||
318
- !setupStore.currentProjectConfigurationStatus
319
- .isConfigured
320
- }
321
- tabIndex={-1}
322
- type="button" // prevent this toggler being activated on form submission
323
- title="Create a Workspace"
324
- >
325
- <PlusIcon />
326
- </button>
327
516
  </div>
328
- <div className="workspace-setup__actions">
329
- <button
330
- className="workspace-setup__next-btn btn--dark"
331
- onClick={handleProceed}
332
- disabled={
333
- !setupStore.currentProject ||
334
- !setupStore.currentProjectConfigurationStatus ||
335
- !setupStore.currentProjectConfigurationStatus
336
- .isConfigured ||
337
- !setupStore.currentWorkspace ||
338
- setupStore.createWorkspaceState.isInProgress ||
339
- setupStore.createOrImportProjectState.isInProgress
340
- }
341
- >
342
- Edit Workspace
343
- </button>
517
+ <div className="workspace-setup__actions-combo">
518
+ <div className="workspace-setup__actions">
519
+ <div className="workspace-setup__actions__button">
520
+ <button
521
+ className="workspace-setup__go-btn btn--dark"
522
+ onClick={handleProceed}
523
+ ref={goButtonRef}
524
+ disabled={
525
+ !setupStore.currentProject ||
526
+ !setupStore.currentProjectConfigurationStatus ||
527
+ !setupStore.currentProjectConfigurationStatus
528
+ .isConfigured ||
529
+ !setupStore.currentWorkspace ||
530
+ setupStore.createWorkspaceState.isInProgress ||
531
+ setupStore.createOrImportProjectState.isInProgress
532
+ }
533
+ >
534
+ <div className="workspace-setup__go-btn__label">
535
+ Go
536
+ </div>
537
+ <LongArrowRightIcon className="workspace-setup__go-btn__icon" />
538
+ </button>
539
+ </div>
540
+ <DividerWithText className="workspace-setup__divider">
541
+ OR
542
+ </DividerWithText>
543
+ <div className="workspace-setup__actions__button">
544
+ <button
545
+ className="workspace-setup__new-btn"
546
+ onClick={showCreateProjectModal}
547
+ title="Create a Project"
548
+ >
549
+ Create New Project
550
+ </button>
551
+ <button
552
+ className="workspace-setup__new-btn"
553
+ onClick={showCreateWorkspaceModal}
554
+ disabled={
555
+ !setupStore.currentProject ||
556
+ !setupStore.currentProjectConfigurationStatus ||
557
+ !setupStore.currentProjectConfigurationStatus
558
+ .isConfigured
559
+ }
560
+ >
561
+ Create New Workspace
562
+ </button>
563
+ </div>
564
+ </div>
344
565
  </div>
345
- </form>
566
+ </div>
567
+ <div className="workspace-setup__content__cards">
568
+ <RuleEngagementCard />
569
+ <ShowcaseCard />
570
+ {/* The SandboxCard and ProductionCard will appear only if the corresponding documentation entry is added in the config.json file for each realm.*/}
571
+ <SandboxCard />
572
+ <ProductionCard />
573
+ </div>
346
574
  {/* NOTE: We do this to reset the initial state of the modals */}
347
575
  {setupStore.showCreateProjectModal && <CreateProjectModal />}
348
576
  {setupStore.showCreateWorkspaceModal &&
@@ -354,7 +582,6 @@ export const WorkspaceSetup = withWorkspaceSetupStore(
354
582
  </div>
355
583
  </div>
356
584
  </div>
357
-
358
585
  <div
359
586
  data-testid={LEGEND_STUDIO_TEST_ID.STATUS_BAR}
360
587
  className="editor__status-bar"