@imposium-hub/components 1.39.1 → 1.41.0

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/Entry.ts CHANGED
@@ -66,10 +66,10 @@ import StaticCompositionRenderer from './components/compositions/StaticCompositi
66
66
  import { IStaticComposition } from './constants/compositions';
67
67
  import ShortcutMenu from './components/shortcut-menu/ShortcutMenu';
68
68
  import TextLayer from './components/compositions/TextLayer';
69
-
70
69
  import { ASSET_TYPES } from './constants/assets';
71
70
  import Portal from './components/portal/Portal';
72
71
  import AssetDetails from './components/asset-details/AssetDetails';
72
+ import PublishWizard from './components/publish-wizard/PublishWizard';
73
73
 
74
74
  /*
75
75
  Redux Handlers / Thunks
@@ -77,6 +77,8 @@ import AssetDetails from './components/asset-details/AssetDetails';
77
77
  import auth from './redux/reducers/auth';
78
78
  import {login, clearCachedAuth} from './redux/actions/auth';
79
79
  import access from './redux/reducers/access';
80
+ import publish from './redux/reducers/publish';
81
+ import {publishVersion, clearStoryPublishStatus, getStoryPublishStatus} from './redux/actions/publish';
80
82
  import {
81
83
  cacheAccessData, clearCachedAccessList, storyAdded,
82
84
  storyNameMutated, orgNameMutated, storyDeleted
@@ -115,6 +117,7 @@ import Timer from './services/Timer';
115
117
  import {getFirstStoryInOrg, string2HexCode, toFixed, padStart, parameterizeServiceUrl, scrapeEmail, fitToContainer,
116
118
  filterHotkeys, mimetypeConformsToOverlay, validateAssetMimetype, formatDateDefault, validateAccessLevel, formattedTime} from './Util';
117
119
  import CompositionRendererLayer from './components/compositions/CompositionRendererLayer';
120
+ import PublishStatusIndicator from './components/publish-wizard/publish/PublishStatusIndicator';
118
121
 
119
122
  export {
120
123
  AppWrapper,
@@ -246,5 +249,11 @@ export {
246
249
  TextLayer,
247
250
  CompositionRendererLayer,
248
251
 
249
- ASSET_TYPES
252
+ ASSET_TYPES,
253
+ PublishWizard,
254
+ publish,
255
+ publishVersion,
256
+ clearStoryPublishStatus,
257
+ getStoryPublishStatus,
258
+ PublishStatusIndicator
250
259
  };
@@ -2,15 +2,19 @@ import * as React from 'react';
2
2
  import { assets as copy } from '../../constants/copy';
3
3
  import CheckboxField from '../checkbox-field/CheckboxField';
4
4
  import DeterminateLoader from '../determinate-loader/DeterminateLoader';
5
- import { toggleAutoTag, toggleAssignToStory } from '../../redux/actions/asset-uploads';
5
+ import { toggleAutoTag, toggleAssignToStory, cancelAssetUpload } from '../../redux/actions/asset-uploads';
6
6
  import { bindActionCreators } from 'redux';
7
7
  import { connect } from 'react-redux';
8
+ import {ICON_TIMES} from '../../constants/icons';
9
+ import Button from '../button/Button';
10
+ import {IImposiumAPI} from '../../services/API';
8
11
 
9
12
  interface IAssetsUploadMenuProps {
10
13
  assetUploads : any;
11
14
  api : any;
12
15
  toggleAutoTag : (toggle : boolean) => any;
13
16
  toggleAssignToStory : (toggle : boolean) => any;
17
+ cancelAssetUpload : (fileName : string, api : IImposiumAPI) => any;
14
18
  }
15
19
 
16
20
  class AssetsUploadMenu extends React.PureComponent<IAssetsUploadMenuProps, {}> {
@@ -18,21 +22,36 @@ class AssetsUploadMenu extends React.PureComponent<IAssetsUploadMenuProps, {}> {
18
22
  super(props);
19
23
  }
20
24
 
25
+ private handleCancelAssetUpload(fileName : string) {
26
+ const {api} = this.props;
27
+ this.props.cancelAssetUpload(fileName, api);
28
+ }
29
+
21
30
  public render = () : JSX.Element => {
22
31
  const {assetUploads: {uploads, autoTag, assignToStory}} = this.props;
23
-
24
32
  let uploadsListInner : JSX.Element[] | JSX.Element;
25
-
26
33
  if (uploads.length > 0) {
27
34
  uploadsListInner = uploads.map((u : any) => {
28
35
  const {filename, percent} = u;
29
36
  const loader : JSX.Element = (percent < 100)
30
37
  ? (<DeterminateLoader progress = {u.percent} />)
31
38
  : (<span>{copy.uploads.preparePhase}</span>);
39
+ const closeButton : JSX.Element = (percent < 100) && (
40
+ <Button
41
+ key = 'btn-cancel'
42
+ onClick = {() => this.handleCancelAssetUpload(filename)}
43
+ style = 'subtle'
44
+ >
45
+ {ICON_TIMES}
46
+ </Button>
47
+ );
32
48
 
33
49
  return (
34
50
  <div className = 'ongoing-upload' key = {filename}>
35
- <span className = 'upload-filename'>{filename}</span>
51
+ <div className='ongoing-upload-inner'>
52
+ <div className = 'upload-filename'>{filename}</div>
53
+ {closeButton}
54
+ </div>
36
55
  {loader}
37
56
  </div>
38
57
  );
@@ -66,7 +85,7 @@ class AssetsUploadMenu extends React.PureComponent<IAssetsUploadMenuProps, {}> {
66
85
  }
67
86
 
68
87
  const mapDispatchToProps = (dispatch) : any => {
69
- return bindActionCreators({toggleAutoTag, toggleAssignToStory}, dispatch);
88
+ return bindActionCreators({toggleAutoTag, toggleAssignToStory, cancelAssetUpload}, dispatch);
70
89
  };
71
90
 
72
91
  const mapStateToProps = (state) : any => {
@@ -0,0 +1,477 @@
1
+ import * as React from 'react';
2
+ import Button from '../button/Button';
3
+ import SelectField from '../select-field/SelectField';
4
+ import HRule from '../h-rule/HRule';
5
+ import {ICON_EMAIL, ICON_GLOBE, ICON_HUBSPOT, ICON_PROJECT_DIAGRAM} from '../../constants/icons';
6
+ import {IImposiumAPI} from '../../services/API';
7
+ import * as copy from '../../constants/copy';
8
+ import {ASSET_TYPES} from '../../constants/assets';
9
+ import HubSpotFlow from './publish/HubSpotFlow';
10
+ import APIIntegration from './publish/APIIntegration';
11
+ import WebpageHosted from './publish/WebpageHosted';
12
+ import EmailWorkflow from './publish/EmailWorkflow';
13
+ import PublishStatusIndicator from './publish/PublishStatusIndicator';
14
+ import {bindActionCreators} from 'redux';
15
+ import {publishVersion} from '../../redux/actions/publish';
16
+ import {connect} from 'react-redux';
17
+
18
+ interface IPublishWizardProps {
19
+ story : any;
20
+ project : any;
21
+ updateEditorConfig(c) : void;
22
+ editor : any;
23
+ updateScene(d) : void;
24
+ editStory(s) : void;
25
+ checkStoryForErrors() : any;
26
+ addViewer(c : any) : void;
27
+ exportExperiences : () => void;
28
+ publishVersion : (api : IImposiumAPI, sId : string) => any;
29
+ batchesList : any;
30
+ publishing : boolean;
31
+ onClose() : any;
32
+ api : IImposiumAPI;
33
+ handleError(e) : void;
34
+ createFreshBatch : (e : string) => any;
35
+ getBatches : () => any;
36
+ getBatchExport : (batchId : string) => any;
37
+ importBatchFromCsv : (storyId : string, batchId : string, batchName : string, csvFile : File, accessKey : string, compId : string) => any;
38
+ handleNotification? : (n) => void;
39
+ batchJobs : any;
40
+ renderBatch : (batchId : string, batchName ? : string) => any;
41
+ }
42
+
43
+ export interface ICredential {
44
+ name : string;
45
+ access_key_id : string;
46
+ secret_access_key : string;
47
+ user_id : string;
48
+ }
49
+
50
+ interface IPublishWizardState {
51
+ compositions : any[];
52
+ selectedComposition : any;
53
+ newPublish : boolean;
54
+ queryRunning : boolean;
55
+ status : string;
56
+ publishMessage : string;
57
+ screenIndex : number;
58
+ accessKey : string;
59
+ credentials : ICredential[];
60
+ done : boolean;
61
+ }
62
+
63
+ interface IBigButtonProps {
64
+ label? : any;
65
+ distributionOptions? : any[];
66
+ key? : any;
67
+ selected? : boolean;
68
+ disabled? : boolean;
69
+ onClick? : (...args) => any;
70
+ style? : any;
71
+ }
72
+
73
+ interface IBigLinkProps {
74
+ label : any;
75
+ link : string;
76
+ }
77
+
78
+ class PublishWizard extends React.PureComponent<IPublishWizardProps, IPublishWizardState> {
79
+ private readonly hiddenFileInputRef : any;
80
+ constructor(props) {
81
+ super(props);
82
+ this.state = {
83
+ compositions: [],
84
+ selectedComposition: null,
85
+ newPublish: true,
86
+ status: '',
87
+ queryRunning: true,
88
+ publishMessage: '',
89
+ screenIndex: 0,
90
+ accessKey: null,
91
+ credentials: [],
92
+ done: false,
93
+ };
94
+ this.hiddenFileInputRef = React.createRef();
95
+ }
96
+
97
+ public componentDidMount() {
98
+
99
+ const {story, api, handleError} = this.props;
100
+
101
+ // Pull in all of the access creds for the composition dropdown
102
+ api.getAssets({type: ASSET_TYPES.VIDEO_COMPOSITION}, story.id).then((res) => {
103
+ this.setState({
104
+ compositions: res.assets
105
+ });
106
+ }).catch((credsError) => {
107
+ handleError(copy.integration.errorPullingComps);
108
+ });
109
+
110
+ // Pull in all of the access creds for the credentials dropdown
111
+ api.getAccssCredentials().then((creds) => {
112
+
113
+ // if the user has no credentials, create some "default" credentials
114
+ if (creds.length === 0) {
115
+ api.createAccessCredentials('default').then((resp) => {
116
+ const { credentials } = this.state;
117
+ const newCreds = [...credentials, ...[resp]];
118
+ this.setState({credentials: newCreds, accessKey: resp[0].access_key_id});
119
+ });
120
+ } else {
121
+ this.setState({
122
+ credentials: creds,
123
+ accessKey: creds[0].access_key_id
124
+ });
125
+ }
126
+
127
+ }).catch((credsError) => {
128
+ handleError(copy.integration.errorGettingCreds);
129
+ });
130
+ }
131
+
132
+ private checkStatus() {
133
+
134
+ const {project: {storyId}, api, handleError} = this.props;
135
+
136
+ if (storyId) {
137
+
138
+ this.setState({
139
+ queryRunning: true,
140
+ newPublish: false,
141
+ status: ''
142
+ }, () => {
143
+
144
+ api.getStoryStatus(storyId).then((resStatus) => {
145
+
146
+ if (resStatus) {
147
+
148
+ if (resStatus.publishing) {
149
+
150
+ this.setState({status: copy.header.publishing}, () => {
151
+ this.pollStatus(resStatus.job_id);
152
+ });
153
+
154
+ }
155
+ } else {
156
+ this.setState({queryRunning: false, status: copy.header.working});
157
+ }
158
+
159
+ }).catch((e) => {
160
+ this.setState({queryRunning: false}, () => {
161
+ if (e.response.status === 404) {
162
+ this.setState({queryRunning: false, status: copy.header.working});
163
+ } else {
164
+ handleError(copy.header.statusError);
165
+ this.setState({
166
+ status: '',
167
+ });
168
+ }
169
+ });
170
+ });
171
+ });
172
+ }
173
+ }
174
+
175
+ private pollStatus(jobId) {
176
+ const {api, handleError} = this.props;
177
+ api.pollJob(jobId).then(() => {
178
+ this.checkStatus();
179
+ }).catch(() => {
180
+ handleError(copy.header.publishPollError);
181
+ this.setState({
182
+ status: '',
183
+ });
184
+ });
185
+ }
186
+
187
+ public onSelectOption = (index) => {
188
+ this.setState({screenIndex: index, done: false});
189
+ }
190
+
191
+ public onCompositionChange = (e) => {
192
+ this.setState({selectedComposition: e});
193
+ }
194
+
195
+ public onCredentialsChange = (e) => {
196
+ this.setState({accessKey: e});
197
+ }
198
+
199
+ private handlePublish() {
200
+
201
+ const {story: {id}, api, handleError} = this.props;
202
+ this.props.publishVersion(api, id).catch((e) => {
203
+ handleError(e);
204
+ });
205
+ this.setState({screenIndex: 1});
206
+ }
207
+
208
+ private renderLowerButtons() {
209
+
210
+ const {screenIndex, done} = this.state;
211
+ const {publishing} = this.props;
212
+
213
+ const lowerButtons = [];
214
+
215
+ // publish and skip buttons
216
+ if (screenIndex === 0) {
217
+
218
+ // publish
219
+ lowerButtons.push(<Button
220
+ tooltip = {copy.publish.btnPublish}
221
+ size={'large'}
222
+ disabled = {publishing}
223
+ key = 'btn-publish'
224
+ onClick={() => this.handlePublish()}
225
+ color = 'primary'>
226
+ {copy.publish.btnPublish}
227
+ </Button>);
228
+
229
+ // skip publish
230
+ lowerButtons.push(<Button
231
+ tooltip = {copy.publish.btnSkip}
232
+ key = 'btn-skip'
233
+ size={'large'}
234
+ onClick={() => this.setState({screenIndex: 1})}
235
+ color = 'primary'>
236
+ {copy.publish.btnSkip}
237
+ </Button>);
238
+ } else {
239
+ // display button when emailworkflow downloading state is true to close the modal
240
+ if (done) {
241
+ lowerButtons.push(<Button
242
+ tooltip = {copy.publish.btnFinished}
243
+ size={'large'}
244
+ key = 'btn-finish'
245
+ onClick={() => this.props.onClose()}
246
+ color = 'primary'>
247
+ {copy.publish.btnFinished}
248
+ </Button>);
249
+ }
250
+ const backIndex = (screenIndex === 1) ? 0 : 1;
251
+ // back button
252
+ lowerButtons.push(<Button
253
+ tooltip = {copy.publish.btnBack}
254
+ size={'large'}
255
+ key = 'btn-back'
256
+ onClick={() => this.setState({screenIndex: backIndex})}
257
+ color = 'primary'>
258
+ {copy.publish.btnBack}
259
+ </Button>);
260
+ }
261
+
262
+ return lowerButtons;
263
+ }
264
+
265
+ private renderPublish() {
266
+ return <div>
267
+ <h2>{copy.publish.publishStepTitle}</h2>
268
+ <HRule/>
269
+ <p>{copy.publish.publishStepDesc}</p>
270
+ </div>;
271
+ }
272
+
273
+ private renderDistributionOptions() {
274
+
275
+ const compOpts = [];
276
+ const credOpts = [];
277
+ const {compositions, selectedComposition, credentials, accessKey} = this.state;
278
+
279
+ for (const comp of compositions) {
280
+ compOpts.push({
281
+ value: comp.id,
282
+ label: comp.name
283
+ });
284
+ }
285
+
286
+ for (const cred of credentials) {
287
+ credOpts.push({
288
+ value: cred.access_key_id,
289
+ label: cred.name
290
+ });
291
+ }
292
+
293
+ const distributionOptions = [
294
+ {
295
+ label : <span>{ICON_GLOBE}&nbsp;{copy.publish.btnWebsite}</span>,
296
+ onClick: (e) => this.onSelectOption(2)
297
+ },
298
+ {
299
+ label : <span>{ICON_EMAIL}&nbsp;{copy.publish.btnEmail}</span>,
300
+ onClick: (e) => this.onSelectOption(3)
301
+ },
302
+ {
303
+ label : <span>{ICON_HUBSPOT}&nbsp;{copy.publish.btnHubspot}</span>,
304
+ onClick: (e) => this.onSelectOption(5)
305
+ },
306
+ {
307
+ label : <span>{ICON_PROJECT_DIAGRAM}&nbsp;{copy.publish.btnAPI}</span>,
308
+ onClick: (e) => this.onSelectOption(6)
309
+ },
310
+ ];
311
+
312
+ return (
313
+ <div>
314
+ <h2>{copy.publish.distributeStepTitle}</h2>
315
+ <HRule/>
316
+ <p>{copy.publish.distributeStepDesc}</p>
317
+ <HRule/>
318
+ <SelectField
319
+ label={copy.project.compName}
320
+ value={selectedComposition}
321
+ onChange={this.onCompositionChange}
322
+ options={compOpts}
323
+ tooltip={copy.project.tooltipCompId}/>
324
+ <SelectField
325
+ label={copy.publish.accessKey}
326
+ value={accessKey}
327
+ onChange={this.onCredentialsChange}
328
+ options={credOpts}
329
+ tooltip={copy.publish.tooltipAccessKey}/>
330
+
331
+ {distributionOptions?.map((option, index) => {
332
+ return (
333
+ <BigButton
334
+ key={index}
335
+ label={option.label}
336
+ onClick={option.onClick}
337
+ />
338
+ );
339
+ })}
340
+ </div>
341
+ );
342
+ }
343
+
344
+ private onDoneHandler = () => {
345
+ this.setState({done: true});
346
+ }
347
+
348
+ public render() {
349
+ const {
350
+ story,
351
+ api,
352
+ handleError,
353
+ createFreshBatch,
354
+ getBatches,
355
+ getBatchExport,
356
+ importBatchFromCsv,
357
+ handleNotification,
358
+ renderBatch,
359
+ batchJobs
360
+ } = this.props;
361
+
362
+ const {screenIndex, status, selectedComposition, compositions, accessKey} = this.state;
363
+ const compName = compositions.find((comp) => {
364
+ return comp.id === selectedComposition;
365
+ })?.name || '';
366
+
367
+ return (
368
+ <div className='publish-wizard'>
369
+ <div className = 'publish-wizard-header'>
370
+ {copy.publish.publishTitle}
371
+ <PublishStatusIndicator api={api}/>
372
+ </div>
373
+ {/*<PaneErrorBoundry>*/}
374
+ <div>
375
+ {screenIndex === 0 && this.renderPublish()}
376
+ {screenIndex === 1 && this.renderDistributionOptions()}
377
+ {screenIndex === 2 && <WebpageHosted story={story} compositionId={selectedComposition} accessKey={accessKey} />}
378
+ {screenIndex === 3 &&
379
+ <EmailWorkflow
380
+ api={api}
381
+ story={story}
382
+ onDone={this.onDoneHandler}
383
+ status={status}
384
+ renderBatch={renderBatch}
385
+ batchJobs={batchJobs}
386
+ accessKey = {accessKey}
387
+ compositionId = {selectedComposition}
388
+ compositionName = {compName}
389
+ handleError={handleError}
390
+ createFreshBatch={createFreshBatch}
391
+ getBatches={getBatches}
392
+ getBatchExport={getBatchExport}
393
+ importBatchFromCsv={importBatchFromCsv}
394
+ />}
395
+ {
396
+ screenIndex === 5 &&
397
+ <HubSpotFlow
398
+ handleError={handleError}
399
+ status={status}
400
+ accessKey = {accessKey}
401
+ story={story}
402
+ handleNotification={handleNotification}
403
+ compositionId = {selectedComposition}
404
+ />
405
+ }
406
+ {screenIndex === 6 && <APIIntegration/>}
407
+ {screenIndex === 7 &&
408
+ <EmailWorkflow
409
+ api={api}
410
+ renderBatch={renderBatch}
411
+ batchJobs={batchJobs}
412
+ story={story}
413
+ onDone={this.onDoneHandler}
414
+ status={status}
415
+ accessKey = {accessKey}
416
+ compositionId = {selectedComposition}
417
+ compositionName = {compName}
418
+ handleError={handleError}
419
+ createFreshBatch={createFreshBatch}
420
+ getBatches={getBatches}
421
+ getBatchExport={getBatchExport}
422
+ importBatchFromCsv={importBatchFromCsv}
423
+ />}
424
+ <br/>
425
+ <HRule/>
426
+ <div className = 'lower-buttons'>{this.renderLowerButtons()}</div>
427
+ </div>
428
+ {/*</PaneErrorBoundry>*/}
429
+ </div>
430
+ );
431
+ }
432
+ }
433
+
434
+ const mapDispatchToProps = (dispatch) => {
435
+ return bindActionCreators({publishVersion}, dispatch);
436
+ };
437
+
438
+ export default connect (null, mapDispatchToProps)(PublishWizard);
439
+
440
+ export const BigButton : React.FC<IBigButtonProps> = (p) => (
441
+ <div className = {`big-link ${(p.disabled) ? 'disabled' : ''}`} onClick = {() => p.onClick()} style={p.style}>
442
+ <h1 style={{color: p.selected && '#2d8ceb' }}>{p.label}</h1>
443
+ </div>
444
+ );
445
+
446
+ export const TabContent = ({children}) => <div className = 'tab-content'>
447
+ <div className = 'settings-tab'>
448
+ <div className='integration-details'>
449
+ {children}
450
+ </div>
451
+ </div>
452
+ </div>;
453
+
454
+ export const TabHeader = (props : {title : string, className? : StyleSheet | any}) => {
455
+ const {className, title} = props;
456
+ return (
457
+ <div className = 'tab-header'>
458
+ <div className={className ? className : 'tab-header-option active'}>
459
+ {title}
460
+ </div>
461
+ </div>
462
+ );
463
+ };
464
+
465
+ export const WizardError = (props : {title : string, description : string}) => {
466
+ const {description, title} = props;
467
+ return <div className = 'publish-wizard-error'>
468
+ <h3>{title}</h3>
469
+ <p>{description}</p>
470
+ </div>;
471
+ };
472
+
473
+ export const BigLink : React.FC<IBigLinkProps> = (p : IBigLinkProps) => {
474
+ return <a className = 'big-link' target = '_blank' href = {p.link}>
475
+ <h1>{p.label}</h1>
476
+ </a>;
477
+ };
@@ -0,0 +1,28 @@
1
+ import * as React from 'react';
2
+ import HRule from '../../h-rule/HRule';
3
+ import {BigLink} from '../PublishWizard';
4
+ import {documentationLinks} from './WebpageHosted';
5
+ import * as copy from '../../../constants/copy';
6
+
7
+ class APIIntegration extends React.PureComponent {
8
+ public render() {
9
+ return (
10
+ <div>
11
+ <h2>{copy.integration.documentation}</h2>
12
+ <HRule/>
13
+ <p>{copy.integration.docParagraph}</p>
14
+ {documentationLinks?.map((link) => {
15
+ return (
16
+ <BigLink
17
+ key={link.key}
18
+ label = {link.label}
19
+ link = {link.link}
20
+ />
21
+ );
22
+ })}
23
+ </div>
24
+ );
25
+ }
26
+ }
27
+
28
+ export default APIIntegration;