@@ -106,7 +39,7 @@ let PieceListBulkModal = React.createClass({
+ numberOfSelectedEditions={this.props.selectedEditions.length} />
-
-
-
+ {this.props.children}
@@ -132,7 +57,6 @@ let PieceListBulkModal = React.createClass({
} else {
return null;
}
-
}
});
diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js
index 38de2af6..c463330c 100644
--- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js
+++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js
@@ -28,7 +28,7 @@ let PieceListToolbarFilterWidget = React.createClass({
},
generateFilterByStatement(param) {
- let filterBy = this.props.filterBy;
+ const filterBy = Object.assign({}, this.props.filterBy);
if(filterBy) {
// we need hasOwnProperty since the values are all booleans
@@ -56,13 +56,13 @@ let PieceListToolbarFilterWidget = React.createClass({
*/
filterBy(param) {
return () => {
- let filterBy = this.generateFilterByStatement(param);
+ const filterBy = this.generateFilterByStatement(param);
this.props.applyFilterBy(filterBy);
};
},
isFilterActive() {
- let trueValuesOnly = Object.keys(this.props.filterBy).filter((acl) => acl);
+ const trueValuesOnly = Object.keys(this.props.filterBy).filter((acl) => acl);
// We're hiding the star in that complicated matter so that,
// the surrounding button is not resized up on appearance
@@ -74,7 +74,7 @@ let PieceListToolbarFilterWidget = React.createClass({
},
render() {
- let filterIcon = (
+ const filterIcon = (
*
@@ -140,4 +140,4 @@ let PieceListToolbarFilterWidget = React.createClass({
}
});
-export default PieceListToolbarFilterWidget;
\ No newline at end of file
+export default PieceListToolbarFilterWidget;
diff --git a/js/components/ascribe_settings/contract_settings_update_button.js b/js/components/ascribe_settings/contract_settings_update_button.js
index aa13264a..7649e5b0 100644
--- a/js/components/ascribe_settings/contract_settings_update_button.js
+++ b/js/components/ascribe_settings/contract_settings_update_button.js
@@ -55,7 +55,7 @@ let ContractSettingsUpdateButton = React.createClass({
render() {
return (
-
- A
+ className={classNames('spinner-wrapper-' + size,
+ color ? 'spinner-wrapper-' + color : null,
+ classes)}>
+
+ A
);
}
diff --git a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js
index 0cc7ff5e..1157a540 100644
--- a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js
+++ b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js
@@ -15,7 +15,6 @@ let FileDragAndDrop = React.createClass({
className: React.PropTypes.string,
onDrop: React.PropTypes.func.isRequired,
onDragOver: React.PropTypes.func,
- onInactive: React.PropTypes.func,
filesToUpload: React.PropTypes.array,
handleDeleteFile: React.PropTypes.func,
handleCancelFile: React.PropTypes.func,
@@ -60,28 +59,21 @@ let FileDragAndDrop = React.createClass({
handleDrop(event) {
event.preventDefault();
event.stopPropagation();
- let files;
- if(this.props.dropzoneInactive) {
- // if there is a handle function for doing stuff
- // when the dropzone is inactive, then call it
- if(this.props.onInactive) {
- this.props.onInactive();
+ if (!this.props.dropzoneInactive) {
+ let files;
+
+ // handle Drag and Drop
+ if(event.dataTransfer && event.dataTransfer.files.length > 0) {
+ files = event.dataTransfer.files;
+ } else if(event.target.files) { // handle input type file
+ files = event.target.files;
}
- return;
- }
- // handle Drag and Drop
- if(event.dataTransfer && event.dataTransfer.files.length > 0) {
- files = event.dataTransfer.files;
- } else if(event.target.files) { // handle input type file
- files = event.target.files;
+ if(typeof this.props.onDrop === 'function' && files) {
+ this.props.onDrop(files);
+ }
}
-
- if(typeof this.props.onDrop === 'function' && files) {
- this.props.onDrop(files);
- }
-
},
handleDeleteFile(fileId) {
@@ -113,31 +105,25 @@ let FileDragAndDrop = React.createClass({
},
handleOnClick() {
- let evt;
- // when multiple is set to false and the user already uploaded a piece,
- // do not propagate event
- if(this.props.dropzoneInactive) {
- // if there is a handle function for doing stuff
- // when the dropzone is inactive, then call it
- if(this.props.onInactive) {
- this.props.onInactive();
+ // do not propagate event if the drop zone's inactive,
+ // for example when multiple is set to false and the user already uploaded a piece
+ if (!this.props.dropzoneInactive) {
+ let evt;
+
+ try {
+ evt = new MouseEvent('click', {
+ view: window,
+ bubbles: true,
+ cancelable: true
+ });
+ } catch(e) {
+ // For browsers that do not support the new MouseEvent syntax
+ evt = document.createEvent('MouseEvents');
+ evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
}
- return;
- }
- try {
- evt = new MouseEvent('click', {
- view: window,
- bubbles: true,
- cancelable: true
- });
- } catch(e) {
- // For browsers that do not support the new MouseEvent syntax
- evt = document.createEvent('MouseEvents');
- evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
+ this.refs.fileSelector.getDOMNode().dispatchEvent(evt);
}
-
- this.refs.fileSelector.getDOMNode().dispatchEvent(evt);
},
render: function () {
diff --git a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview.js b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview.js
index 86d4135e..ca1be2d2 100644
--- a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview.js
+++ b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview.js
@@ -5,22 +5,31 @@ import React from 'react';
import FileDragAndDropPreviewImage from './file_drag_and_drop_preview_image';
import FileDragAndDropPreviewOther from './file_drag_and_drop_preview_other';
-
import { getLangText } from '../../../utils/lang_utils';
+import { truncateTextAtCharIndex } from '../../../utils/general_utils';
+import { extractFileExtensionFromString } from '../../../utils/file_utils';
-let FileDragAndDropPreview = React.createClass({
+const { shape, string, number, func, bool } = React.PropTypes;
+
+const FileDragAndDropPreview = React.createClass({
propTypes: {
- file: React.PropTypes.shape({
- url: React.PropTypes.string,
- type: React.PropTypes.string
+ file: shape({
+ url: string,
+ type: string,
+ progress: number,
+ id: number,
+ status: string,
+ s3Url: string,
+ s3UrlSafe: string
}).isRequired,
- handleDeleteFile: React.PropTypes.func,
- handleCancelFile: React.PropTypes.func,
- handlePauseFile: React.PropTypes.func,
- handleResumeFile: React.PropTypes.func,
- areAssetsDownloadable: React.PropTypes.bool,
- areAssetsEditable: React.PropTypes.bool
+ handleDeleteFile: func,
+ handleCancelFile: func,
+ handlePauseFile: func,
+ handleResumeFile: func,
+ areAssetsDownloadable: bool,
+ areAssetsEditable: bool,
+ numberOfDisplayedFiles: number
},
toggleUploadProcess() {
@@ -32,14 +41,21 @@ let FileDragAndDropPreview = React.createClass({
},
handleDeleteFile() {
- // handleDeleteFile is optional, so if its not submitted,
- // don't run it
- // On the other hand, if the files progress is not yet at a 100%,
- // just run fineuploader.cancel
- if(this.props.handleDeleteFile && this.props.file.progress === 100) {
- this.props.handleDeleteFile(this.props.file.id);
- } else if(this.props.handleCancelFile && this.props.file.progress !== 100) {
- this.props.handleCancelFile(this.props.file.id);
+ const { handleDeleteFile,
+ handleCancelFile,
+ file } = this.props;
+ // `handleDeleteFile` is optional, so if its not submitted, don't run it
+ //
+ // For delete though, we only want to trigger it, when we're sure that
+ // the file has *completely* been uploaded to S3 and call now also be
+ // deleted using an HTTP DELETE request.
+ if (handleDeleteFile &&
+ file.progress === 100 &&
+ (file.status === 'upload successful' || file.status === 'online') &&
+ file.s3UrlSafe) {
+ handleDeleteFile(file.id);
+ } else if(handleCancelFile) {
+ handleCancelFile(file.id);
}
},
@@ -50,44 +66,80 @@ let FileDragAndDropPreview = React.createClass({
}
},
+ getFileName() {
+ const { numberOfDisplayedFiles, file } = this.props;
+
+ if(numberOfDisplayedFiles === 1) {
+ return (
+
+ {truncateTextAtCharIndex(file.name, 30, '(...).' + extractFileExtensionFromString(file.name))}
+
+ );
+ } else {
+ return null;
+ }
+ },
+
+ getRemoveButton() {
+ if(this.props.areAssetsEditable) {
+ return (
+
+
+
+ );
+ } else {
+ return null;
+ }
+ },
+
render() {
+ const { file,
+ areAssetsDownloadable,
+ numberOfDisplayedFiles } = this.props;
+ const innerStyle = numberOfDisplayedFiles === 1 ? { verticalAlign: 'middle' } : null;
+ const outerStyle = numberOfDisplayedFiles !== 1 ? { display: 'inline-block' } : null;
+
let previewElement;
- let removeBtn;
// Decide whether an image or a placeholder picture should be displayed
- if(this.props.file.type.split('/')[0] === 'image') {
- previewElement = (
);
+ // If a file has its `thumbnailUrl` defined, then we display it also as an image
+ if(file.type.split('/')[0] === 'image' || file.thumbnailUrl) {
+ previewElement = (
+ 1} />
+ );
} else {
- previewElement = ();
- }
-
- if(this.props.areAssetsEditable) {
- removeBtn = (
-
-
);
+ previewElement = (
+ 1} />
+ );
}
return (
-
- {removeBtn}
- {previewElement}
+
+
+ {this.getRemoveButton()}
+ {previewElement}
+
+ {this.getFileName()}
);
}
diff --git a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_image.js b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_image.js
index c21c91f9..927a5b22 100644
--- a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_image.js
+++ b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_image.js
@@ -3,16 +3,21 @@
import React from 'react';
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
+import AclProxy from '../../acl_proxy';
import AscribeSpinner from '../../ascribe_spinner';
import { getLangText } from '../../../utils/lang_utils';
-let FileDragAndDropPreviewImage = React.createClass({
+
+const { number, string, func, bool } = React.PropTypes;
+
+const FileDragAndDropPreviewImage = React.createClass({
propTypes: {
- progress: React.PropTypes.number,
- url: React.PropTypes.string,
- toggleUploadProcess: React.PropTypes.func,
- downloadUrl: React.PropTypes.string,
- areAssetsDownloadable: React.PropTypes.bool
+ progress: number,
+ url: string,
+ toggleUploadProcess: func,
+ downloadUrl: string,
+ areAssetsDownloadable: bool,
+ showProgress: bool
},
getInitialState() {
@@ -33,40 +38,44 @@ let FileDragAndDropPreviewImage = React.createClass({
},
render() {
- let imageStyle = {
- backgroundImage: 'url("' + this.props.url + '")',
+ const { url,
+ progress,
+ areAssetsDownloadable,
+ downloadUrl,
+ showProgress } = this.props;
+ const imageStyle = {
+ backgroundImage: 'url("' + url + '")',
backgroundSize: 'cover'
};
let actionSymbol;
- if(this.props.progress > 0 && this.props.progress < 99 && this.state.paused) {
- actionSymbol =
;
- } else if(this.props.progress > 0 && this.props.progress < 99 && !this.state.paused) {
- actionSymbol = ;
- } else if(this.props.progress === 100) {
-
- // only if assets are actually downloadable, there should be a download icon if the process is already at
- // 100%. If not, no actionSymbol should be displayed
- if(this.props.areAssetsDownloadable) {
- actionSymbol = ;
- }
-
- } else {
+ // only if assets are actually downloadable, there should be a download icon if the process is already at
+ // 100%. If not, no actionSymbol should be displayed
+ if(progress === 100 && areAssetsDownloadable) {
+ actionSymbol = ;
+ } else if(progress >= 0 && progress < 100) {
actionSymbol = (
);
+ } else {
+ actionSymbol = (
+
+ );
}
return (
);
diff --git a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_iterator.js b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_iterator.js
index 2352407a..982544b4 100644
--- a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_iterator.js
+++ b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_iterator.js
@@ -29,27 +29,25 @@ let FileDragAndDropPreviewIterator = React.createClass({
areAssetsDownloadable,
areAssetsEditable
} = this.props;
-
files = files.filter(displayValidFilesFilter);
if(files && files.length > 0) {
return (
-
-
- {files.map((file, i) => {
- return (
-
- );
- })}
-
+
+ {files.map((file, i) => {
+ return (
+
+ );
+ })}
);
diff --git a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_other.js b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_other.js
index 12e52b0a..e5e7c947 100644
--- a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_other.js
+++ b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_other.js
@@ -3,16 +3,21 @@
import React from 'react';
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
+import AclProxy from '../../acl_proxy';
import AscribeSpinner from '../../ascribe_spinner';
import { getLangText } from '../../../utils/lang_utils';
-let FileDragAndDropPreviewOther = React.createClass({
+
+const { string, number, bool, func } = React.PropTypes;
+
+const FileDragAndDropPreviewOther = React.createClass({
propTypes: {
- type: React.PropTypes.string,
- progress: React.PropTypes.number,
- areAssetsDownloadable: React.PropTypes.bool,
- toggleUploadProcess: React.PropTypes.func,
- downloadUrl: React.PropTypes.string
+ type: string,
+ progress: number,
+ areAssetsDownloadable: bool,
+ toggleUploadProcess: func,
+ downloadUrl: string,
+ showProgress: bool
},
getInitialState() {
@@ -33,39 +38,49 @@ let FileDragAndDropPreviewOther = React.createClass({
},
render() {
-
+ const { progress,
+ areAssetsDownloadable,
+ downloadUrl,
+ type,
+ showProgress } = this.props;
+ const style = !showProgress ? { visibility: 'hidden' } : null;
let actionSymbol;
-
- if(this.props.progress > 0 && this.props.progress < 99 && this.state.paused) {
- actionSymbol =
;
- } else if(this.props.progress > 0 && this.props.progress < 99 && !this.state.paused) {
- actionSymbol = ;
- } else if(this.props.progress === 100) {
- // only if assets are actually downloadable, there should be a download icon if the process is already at
- // 100%. If not, no actionSymbol should be displayed
- if(this.props.areAssetsDownloadable) {
- actionSymbol = ;
- }
-
- } else {
+ // only if assets are actually downloadable, there should be a
+ // download icon if the process is already at 100%.
+ // If not, no actionSymbol should be displayed
+ if (progress === 100 && areAssetsDownloadable) {
+ actionSymbol = (
+
+ );
+ } else if(progress >= 0 && progress < 100) {
actionSymbol = (
);
+ } else {
+ actionSymbol = (
+
+ );
}
return (
{actionSymbol}
-
{'.' + this.props.type}
+
{'.' + type}
diff --git a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_progress.js b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_progress.js
index 1f1fd421..302837d5 100644
--- a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_progress.js
+++ b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview_progress.js
@@ -5,10 +5,9 @@ import React from 'react';
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
import { displayValidProgressFilesFilter } from '../react_s3_fine_uploader_utils';
-import { getLangText } from '../../../utils/lang_utils';
-let FileDragAndDropPreviewProgress = React.createClass({
+const FileDragAndDropPreviewProgress = React.createClass({
propTypes: {
files: React.PropTypes.array
},
@@ -40,24 +39,18 @@ let FileDragAndDropPreviewProgress = React.createClass({
},
render() {
+ const files = this.props.files.filter(displayValidProgressFilesFilter);
+ const style = !files.length ? { display: 'none' } : null;
let overallProgress = this.calcOverallProgress();
- let overallFileSize = this.calcOverallFileSize();
- let style = {
- visibility: 'hidden'
- };
-
- // only visible if overallProgress is over zero
- // or the overallFileSize is greater than 10MB
- if(overallProgress !== 0 && overallFileSize > 10000000) {
- style.visibility = 'visible';
- }
return (
-
+
);
}
});
diff --git a/js/components/ascribe_uploader/ascribe_upload_button/upload_button.js b/js/components/ascribe_uploader/ascribe_upload_button/upload_button.js
index 252adabb..5848bca1 100644
--- a/js/components/ascribe_uploader/ascribe_upload_button/upload_button.js
+++ b/js/components/ascribe_uploader/ascribe_upload_button/upload_button.js
@@ -2,138 +2,195 @@
import React from 'react';
-import Glyphicon from 'react-bootstrap/lib/Glyphicon';
-
import { displayValidProgressFilesFilter } from '../react_s3_fine_uploader_utils';
import { getLangText } from '../../../utils/lang_utils';
import { truncateTextAtCharIndex } from '../../../utils/general_utils';
const { func, array, bool, shape, string } = React.PropTypes;
-let UploadButton = React.createClass({
- propTypes: {
- onDrop: func.isRequired,
- filesToUpload: array,
- multiple: bool,
- // For simplification purposes we're just going to use this prop as a
- // label for the upload button
- fileClassToUpload: shape({
- singular: string,
- plural: string
- }),
+export default function UploadButton({ className = 'btn btn-default btn-sm' } = {}) {
+ return React.createClass({
+ displayName: 'UploadButton',
- allowedExtensions: string,
+ propTypes: {
+ onDrop: func.isRequired,
+ filesToUpload: array,
+ multiple: bool,
- handleCancelFile: func // provided by ReactS3FineUploader
- },
+ // For simplification purposes we're just going to use this prop as a
+ // label for the upload button
+ fileClassToUpload: shape({
+ singular: string,
+ plural: string
+ }),
- handleDrop(event) {
- event.preventDefault();
- event.stopPropagation();
- let files = event.target.files;
+ allowedExtensions: string,
- if(typeof this.props.onDrop === 'function' && files) {
- this.props.onDrop(files);
- }
+ // provided by ReactS3FineUploader
+ handleCancelFile: func,
+ handleDeleteFile: func
+ },
- },
+ getInitialState() {
+ return {
+ disabled: this.getUploadingFiles().length !== 0
+ };
+ },
- getUploadingFiles() {
- return this.props.filesToUpload.filter((file) => file.status === 'uploading');
- },
+ componentWillReceiveProps(nextProps) {
+ if(this.props.filesToUpload !== nextProps.filesToUpload) {
+ this.setState({
+ disabled: this.getUploadingFiles(nextProps.filesToUpload).length !== 0
+ });
+ }
+ },
- getUploadedFile() {
- return this.props.filesToUpload.filter((file) => file.status === 'upload successful')[0];
- },
+ handleDrop(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ let files = event.target.files;
- handleOnClick() {
- const uploadingFiles = this.getUploadingFiles();
- const uploadedFile = this.getUploadedFile();
+ if(typeof this.props.onDrop === 'function' && files) {
+ this.props.onDrop(files);
+ }
+ },
- if(uploadedFile) {
- this.props.handleCancelFile(uploadedFile.id);
- }
- if(uploadingFiles.length === 0) {
- // We only want the button to be clickable if there are no files currently uploading
+ getUploadingFiles(filesToUpload = this.props.filesToUpload) {
+ return filesToUpload.filter((file) => file.status === 'uploading');
+ },
- // Firefox only recognizes the simulated mouse click if bubbles is set to true,
- // but since Google Chrome propagates the event much further than needed, we
- // need to stop propagation as soon as the event is created
- var evt = new MouseEvent('click', {
- view: window,
- bubbles: true,
- cancelable: true
- });
+ getUploadedFile() {
+ return this.props.filesToUpload.filter((file) => file.status === 'upload successful')[0];
+ },
- evt.stopPropagation();
- this.refs.fileinput.getDOMNode().dispatchEvent(evt);
- }
- },
+ clearSelection() {
+ this.refs.fileSelector.getDOMNode().value = '';
+ },
- getButtonLabel() {
- let { filesToUpload, fileClassToUpload } = this.props;
+ handleOnClick() {
+ if(!this.state.disabled) {
+ let evt;
+ const uploadingFiles = this.getUploadingFiles();
+ const uploadedFile = this.getUploadedFile();
- // filter invalid files that might have been deleted or canceled...
- filesToUpload = filesToUpload.filter(displayValidProgressFilesFilter);
+ this.clearSelection();
+ if(uploadingFiles.length) {
+ this.props.handleCancelFile(uploadingFiles[0].id);
+ } else if(uploadedFile && !uploadedFile.s3UrlSafe) {
+ this.props.handleCancelFile(uploadedFile.id);
+ } else if(uploadedFile && uploadedFile.s3UrlSafe) {
+ this.props.handleDeleteFile(uploadedFile.id);
+ }
- if(this.getUploadingFiles().length !== 0) {
- return getLangText('Upload progress') + ': ' + Math.ceil(filesToUpload[0].progress) + '%';
- } else {
- return fileClassToUpload.singular;
- }
- },
+ try {
+ evt = new MouseEvent('click', {
+ view: window,
+ bubbles: true,
+ cancelable: true
+ });
+ } catch(e) {
+ // For browsers that do not support the new MouseEvent syntax
+ evt = document.createEvent('MouseEvents');
+ evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
+ }
+ evt.stopPropagation();
+ this.refs.fileSelector.getDOMNode().dispatchEvent(evt);
+ }
+ },
- getUploadedFileLabel() {
- const uploadedFile = this.getUploadedFile();
+ onClickCancel() {
+ this.clearSelection();
+ const uploadingFile = this.getUploadingFiles()[0];
+ this.props.handleCancelFile(uploadingFile.id);
+ },
- if(uploadedFile) {
+ onClickRemove() {
+ this.clearSelection();
+ const uploadedFile = this.getUploadedFile();
+ this.props.handleDeleteFile(uploadedFile.id);
+ },
+
+
+ getButtonLabel() {
+ let { filesToUpload, fileClassToUpload } = this.props;
+
+ // filter invalid files that might have been deleted or canceled...
+ filesToUpload = filesToUpload.filter(displayValidProgressFilesFilter);
+
+ if(this.getUploadingFiles().length !== 0) {
+ return getLangText('Upload progress') + ': ' + Math.ceil(filesToUpload[0].progress) + '%';
+ } else {
+ return fileClassToUpload.singular;
+ }
+ },
+
+ getUploadedFileLabel() {
+ const uploadedFile = this.getUploadedFile();
+ const uploadingFiles = this.getUploadingFiles();
+
+ if(uploadingFiles.length) {
+ return (
+
+ {' ' + truncateTextAtCharIndex(uploadingFiles[0].name, 40) + ' '}
+ [{getLangText('cancel upload')}]
+
+ );
+ } else if(uploadedFile) {
+ return (
+
+
+ {' ' + truncateTextAtCharIndex(uploadedFile.name, 40) + ' '}
+ [{getLangText('remove')}]
+
+ );
+ } else {
+ return (
+ {getLangText('No file chosen')}
+ );
+ }
+ },
+
+ render() {
+ const {
+ multiple,
+ allowedExtensions } = this.props;
+ const { disabled } = this.state;
+
+
+ /*
+ * We do not want a button that submits here.
+ * As UploadButton could be used in forms that want to be submitted independent
+ * of clicking the selector.
+ * Therefore the wrapping component needs to be an `anchor` tag instead of a `button`
+ */
return (
-
-
- {' ' + truncateTextAtCharIndex(uploadedFile.name, 40)}
-
- );
- } else {
- return (
- {getLangText('No file chosen')}
+
+ {/*
+ The button needs to be of `type="button"` as it would
+ otherwise submit the form its in.
+ */}
+
+ {this.getUploadedFileLabel()}
+
);
}
- },
-
- render() {
- let { multiple,
- allowedExtensions } = this.props;
-
- /*
- * We do not want a button that submits here.
- * As UploadButton could be used in forms that want to be submitted independent
- * of clicking the selector.
- * Therefore the wrapping component needs to be an `anchor` tag instead of a `button`
- */
- return (
-
- );
- }
-});
-
-export default UploadButton;
\ No newline at end of file
+ });
+}
\ No newline at end of file
diff --git a/js/components/ascribe_uploader/react_s3_fine_uploader.js b/js/components/ascribe_uploader/react_s3_fine_uploader.js
index bf4250c5..67b19e02 100644
--- a/js/components/ascribe_uploader/react_s3_fine_uploader.js
+++ b/js/components/ascribe_uploader/react_s3_fine_uploader.js
@@ -18,88 +18,100 @@ import { displayValidFilesFilter, transformAllowedExtensionsToInputAcceptProp }
import { getCookie } from '../../utils/fetch_api_utils';
import { getLangText } from '../../utils/lang_utils';
-let ReactS3FineUploader = React.createClass({
+
+const { shape,
+ string,
+ oneOfType,
+ number,
+ func,
+ bool,
+ any,
+ object,
+ oneOf,
+ element,
+ arrayOf } = React.PropTypes;
+
+const ReactS3FineUploader = React.createClass({
propTypes: {
- keyRoutine: React.PropTypes.shape({
- url: React.PropTypes.string,
- fileClass: React.PropTypes.string,
- pieceId: React.PropTypes.oneOfType([
- React.PropTypes.string,
- React.PropTypes.number
+ keyRoutine: shape({
+ url: string,
+ fileClass: string,
+ pieceId: oneOfType([
+ string,
+ number
])
}),
- createBlobRoutine: React.PropTypes.shape({
- url: React.PropTypes.string,
- pieceId: React.PropTypes.oneOfType([
- React.PropTypes.string,
- React.PropTypes.number
+ createBlobRoutine: shape({
+ url: string,
+ pieceId: oneOfType([
+ string,
+ number
])
}),
- submitFile: React.PropTypes.func,
- autoUpload: React.PropTypes.bool,
- debug: React.PropTypes.bool,
- objectProperties: React.PropTypes.shape({
- acl: React.PropTypes.string
+ handleChangedFile: func, // is for when a file is dropped or selected
+ submitFile: func, // is for when a file has been successfully uploaded, TODO: rename to handleSubmitFile
+ autoUpload: bool,
+ debug: bool,
+ objectProperties: shape({
+ acl: string
}),
- request: React.PropTypes.shape({
- endpoint: React.PropTypes.string,
- accessKey: React.PropTypes.string,
- params: React.PropTypes.shape({
- csrfmiddlewaretoken: React.PropTypes.string
+ request: shape({
+ endpoint: string,
+ accessKey: string,
+ params: shape({
+ csrfmiddlewaretoken: string
})
}),
- signature: React.PropTypes.shape({
- endpoint: React.PropTypes.string
+ signature: shape({
+ endpoint: string
}).isRequired,
- uploadSuccess: React.PropTypes.shape({
- method: React.PropTypes.string,
- endpoint: React.PropTypes.string,
- params: React.PropTypes.shape({
- isBrowserPreviewCapable: React.PropTypes.any, // maybe fix this later
- bitcoin_ID_noPrefix: React.PropTypes.string
+ uploadSuccess: shape({
+ method: string,
+ endpoint: string,
+ params: shape({
+ isBrowserPreviewCapable: any, // maybe fix this later
+ bitcoin_ID_noPrefix: string
})
}),
- cors: React.PropTypes.shape({
- expected: React.PropTypes.bool
+ cors: shape({
+ expected: bool
}),
- chunking: React.PropTypes.shape({
- enabled: React.PropTypes.bool
+ chunking: shape({
+ enabled: bool
}),
- resume: React.PropTypes.shape({
- enabled: React.PropTypes.bool
+ resume: shape({
+ enabled: bool
}),
- deleteFile: React.PropTypes.shape({
- enabled: React.PropTypes.bool,
- method: React.PropTypes.string,
- endpoint: React.PropTypes.string,
- customHeaders: React.PropTypes.object
+ deleteFile: shape({
+ enabled: bool,
+ method: string,
+ endpoint: string,
+ customHeaders: object
}).isRequired,
- session: React.PropTypes.shape({
- customHeaders: React.PropTypes.object,
- endpoint: React.PropTypes.string,
- params: React.PropTypes.object,
- refreshOnRequests: React.PropTypes.bool
+ session: shape({
+ customHeaders: object,
+ endpoint: string,
+ params: object,
+ refreshOnRequests: bool
}),
- validation: React.PropTypes.shape({
- itemLimit: React.PropTypes.number,
- sizeLimit: React.PropTypes.string,
- allowedExtensions: React.PropTypes.arrayOf(React.PropTypes.string)
+ validation: shape({
+ itemLimit: number,
+ sizeLimit: string,
+ allowedExtensions: arrayOf(string)
}),
- messages: React.PropTypes.shape({
- unsupportedBrowser: React.PropTypes.string
+ messages: shape({
+ unsupportedBrowser: string
}),
- formatFileName: React.PropTypes.func,
- multiple: React.PropTypes.bool,
- retry: React.PropTypes.shape({
- enableAuto: React.PropTypes.bool
+ formatFileName: func,
+ multiple: bool,
+ retry: shape({
+ enableAuto: bool
}),
- uploadStarted: React.PropTypes.func,
- setIsUploadReady: React.PropTypes.func,
- isReadyForFormSubmission: React.PropTypes.func,
- areAssetsDownloadable: React.PropTypes.bool,
- areAssetsEditable: React.PropTypes.bool,
- defaultErrorMessage: React.PropTypes.string,
- onInactive: React.PropTypes.func,
+ setIsUploadReady: func,
+ isReadyForFormSubmission: func,
+ areAssetsDownloadable: bool,
+ areAssetsEditable: bool,
+ defaultErrorMessage: string,
// We encountered some cases where people had difficulties to upload their
// works to ascribe due to a slow internet connection.
@@ -112,22 +124,22 @@ let ReactS3FineUploader = React.createClass({
// which should be passed into 'uploadMethod':
// 'hash': upload using the hash
// 'upload': upload full file (default if not specified)
- enableLocalHashing: React.PropTypes.bool,
- uploadMethod: React.PropTypes.oneOf(['hash', 'upload']),
+ enableLocalHashing: bool,
+ uploadMethod: oneOf(['hash', 'upload']),
// A class of a file the user has to upload
// Needs to be defined both in singular as well as in plural
- fileClassToUpload: React.PropTypes.shape({
- singular: React.PropTypes.string,
- plural: React.PropTypes.string
+ fileClassToUpload: shape({
+ singular: string,
+ plural: string
}),
// Uploading functionality of react fineuploader is disconnected from its UI
// layer, which means that literally every (properly adjusted) react element
// can handle the UI handling.
- fileInputElement: React.PropTypes.oneOfType([
- React.PropTypes.func,
- React.PropTypes.element
+ fileInputElement: oneOfType([
+ func,
+ element
])
},
@@ -273,7 +285,7 @@ let ReactS3FineUploader = React.createClass({
// Cancel uploads and clear previously selected files on the input element
cancelUploads(id) {
- !!id ? this.state.uploader.cancel(id) : this.state.uploader.cancelAll();
+ typeof id !== 'undefined' ? this.state.uploader.cancel(id) : this.state.uploader.cancelAll();
// Reset the file input element to clear the previously selected files so that
// the user can reselect them again.
@@ -376,6 +388,21 @@ let ReactS3FineUploader = React.createClass({
});
},
+ setThumbnailForFileId(fileId, url) {
+ const { filesToUpload } = this.state;
+
+ if(fileId < filesToUpload.length) {
+ const changeSet = { $set: url };
+ const newFilesToUpload = React.addons.update(filesToUpload, {
+ [fileId]: { thumbnailUrl: changeSet }
+ });
+
+ this.setState({ filesToUpload: newFilesToUpload });
+ } else {
+ throw new Error('Accessing an index out of range of filesToUpload');
+ }
+ },
+
/* FineUploader specific callback function handlers */
onUploadChunk(id, name, chunkData) {
@@ -510,7 +537,12 @@ let ReactS3FineUploader = React.createClass({
onCancel(id) {
// when a upload is canceled, we need to update this components file array
- this.setStatusOfFile(id, 'canceled');
+ this.setStatusOfFile(id, 'canceled')
+ .then(() => {
+ if(typeof this.props.handleChangedFile === 'function') {
+ this.props.handleChangedFile(this.state.filesToUpload[id]);
+ }
+ });
let notification = new GlobalNotificationModel(getLangText('File upload canceled'), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
@@ -604,7 +636,12 @@ let ReactS3FineUploader = React.createClass({
//
// If there is an error during the deletion, we will just change the status back to 'online'
// and display an error message
- this.setStatusOfFile(fileId, 'deleted');
+ this.setStatusOfFile(fileId, 'deleted')
+ .then(() => {
+ if(typeof this.props.handleChangedFile === 'function') {
+ this.props.handleChangedFile(this.state.filesToUpload[fileId]);
+ }
+ });
// In some instances (when the file was already uploaded and is just displayed to the user
// - for example in the contract or additional files dialog)
@@ -672,11 +709,6 @@ let ReactS3FineUploader = React.createClass({
// override standard files list with only valid files
files = validFiles;
- // Call this method to signal the outside component that an upload is in progress
- if(typeof this.props.uploadStarted === 'function' && files.length > 0) {
- this.props.uploadStarted();
- }
-
// if multiple is set to false and user drops multiple files into the dropzone,
// take the first one and notify user that only one file can be submitted
if(!this.props.multiple && files.length > 1) {
@@ -849,21 +881,37 @@ let ReactS3FineUploader = React.createClass({
// set the new file array
let filesToUpload = React.addons.update(this.state.filesToUpload, { $set: oldAndNewFiles });
- this.setState({ filesToUpload });
+ this.setState({ filesToUpload }, () => {
+ // when files have been dropped or selected by a user, we want to propagate that
+ // information to the outside components, so they can act on it (in our case, because
+ // we want the user to define a thumbnail when the actual work is not renderable
+ // (like e.g. a .zip file))
+ if(typeof this.props.handleChangedFile === 'function') {
+ // its save to assume that the last file in `filesToUpload` is always
+ // the latest file added
+ this.props.handleChangedFile(this.state.filesToUpload.slice(-1)[0]);
+ }
+ });
},
+ // This method has been made promise-based to immediately afterwards
+ // call a callback function (instantly after this.setState went through)
+ // This is e.g. needed when showing/hiding the optional thumbnail upload
+ // field in the registration form
setStatusOfFile(fileId, status) {
- let changeSet = {};
+ return Q.Promise((resolve) => {
+ let changeSet = {};
- if(status === 'deleted' || status === 'canceled') {
- changeSet.progress = { $set: 0 };
- }
+ if(status === 'deleted' || status === 'canceled') {
+ changeSet.progress = { $set: 0 };
+ }
- changeSet.status = { $set: status };
+ changeSet.status = { $set: status };
- let filesToUpload = React.addons.update(this.state.filesToUpload, { [fileId]: changeSet });
+ let filesToUpload = React.addons.update(this.state.filesToUpload, { [fileId]: changeSet });
- this.setState({ filesToUpload });
+ this.setState({ filesToUpload }, resolve);
+ });
},
isDropzoneInactive() {
@@ -891,7 +939,6 @@ let ReactS3FineUploader = React.createClass({
multiple,
areAssetsDownloadable,
areAssetsEditable,
- onInactive,
enableLocalHashing,
fileClassToUpload,
fileInputElement: FileInputElement,
@@ -901,7 +948,6 @@ let ReactS3FineUploader = React.createClass({
multiple,
areAssetsDownloadable,
areAssetsEditable,
- onInactive,
enableLocalHashing,
uploadMethod,
fileClassToUpload,
diff --git a/js/components/ascribe_uploader/react_s3_fine_uploader_utils.js b/js/components/ascribe_uploader/react_s3_fine_uploader_utils.js
index cd1dbce2..ed76c5e8 100644
--- a/js/components/ascribe_uploader/react_s3_fine_uploader_utils.js
+++ b/js/components/ascribe_uploader/react_s3_fine_uploader_utils.js
@@ -42,6 +42,15 @@ export function displayValidFilesFilter(file) {
return file.status !== 'deleted' && file.status !== 'canceled';
}
+/**
+ * Filter function for filtering all files except for deleted and canceled files
+ * @param {object} file A file from filesToUpload that has status as a prop.
+ * @return {boolean}
+ */
+export function displayRemovedFilesFilter(file) {
+ return file.status === 'deleted' || file.status === 'canceled';
+}
+
/**
* Filter function for which files to integrate in the progress process
diff --git a/js/components/contract_notification.js b/js/components/contract_notification.js
deleted file mode 100644
index cd6ceb53..00000000
--- a/js/components/contract_notification.js
+++ /dev/null
@@ -1,36 +0,0 @@
-'use strict';
-
-import React from 'react';
-
-import NotificationStore from '../stores/notification_store';
-
-import { mergeOptions } from '../utils/general_utils';
-
-let ContractNotification = React.createClass({
- getInitialState() {
- return mergeOptions(
- NotificationStore.getState()
- );
- },
-
- componentDidMount() {
- NotificationStore.listen(this.onChange);
- },
-
- componentWillUnmount() {
- NotificationStore.unlisten(this.onChange);
- },
-
- onChange(state) {
- this.setState(state);
- },
-
- render() {
-
- return (
- null
- );
- }
-});
-
-export default ContractNotification;
\ No newline at end of file
diff --git a/js/components/error_not_found_page.js b/js/components/error_not_found_page.js
index 61f83196..0e111ce7 100644
--- a/js/components/error_not_found_page.js
+++ b/js/components/error_not_found_page.js
@@ -6,6 +6,16 @@ import { getLangText } from '../utils/lang_utils';
let ErrorNotFoundPage = React.createClass({
+ propTypes: {
+ message: React.PropTypes.string
+ },
+
+ getDefaultProps() {
+ return {
+ message: getLangText("Oops, the page you are looking for doesn't exist.")
+ };
+ },
+
render() {
return (
@@ -13,7 +23,7 @@ let ErrorNotFoundPage = React.createClass({
404
- {getLangText('Ups, the page you are looking for does not exist.')}
+ {this.props.message}
diff --git a/js/components/global_notification.js b/js/components/global_notification.js
index 59663b28..c1477f67 100644
--- a/js/components/global_notification.js
+++ b/js/components/global_notification.js
@@ -1,7 +1,9 @@
'use strict';
import React from 'react';
+import classNames from 'classnames';
+import GlobalNotificationActions from '../actions/global_notification_actions';
import GlobalNotificationStore from '../stores/global_notification_store';
import Row from 'react-bootstrap/lib/Row';
@@ -9,14 +11,18 @@ import Col from 'react-bootstrap/lib/Col';
import { mergeOptions } from '../utils/general_utils';
+const MAX_NOTIFICATION_BUBBLE_CONTAINER_WIDTH = 768;
+
let GlobalNotification = React.createClass({
getInitialState() {
+ const notificationStore = GlobalNotificationStore.getState();
+
return mergeOptions(
{
containerWidth: 0
},
- this.extractFirstElem(GlobalNotificationStore.getState().notificationQue)
+ notificationStore
);
},
@@ -36,35 +42,8 @@ let GlobalNotification = React.createClass({
window.removeEventListener('resize', this.handleContainerResize);
},
- extractFirstElem(l) {
- if(l.length > 0) {
- return {
- show: true,
- message: l[0]
- };
- } else {
- return {
- show: false,
- message: ''
- };
- }
- },
-
onChange(state) {
- let notification = this.extractFirstElem(state.notificationQue);
-
- // error handling for notifications
- if(notification.message && notification.type === 'danger') {
- console.logGlobal(new Error(notification.message.message));
- }
-
- if(notification.show) {
- this.setState(notification);
- } else {
- this.setState({
- show: false
- });
- }
+ this.setState(state);
},
handleContainerResize() {
@@ -73,32 +52,31 @@ let GlobalNotification = React.createClass({
});
},
- render() {
- let notificationClass = 'ascribe-global-notification';
- let textClass;
+ renderNotification() {
+ const {
+ notificationQueue: [notification],
+ notificationStatus,
+ notificationsPaused,
+ containerWidth } = this.state;
- if(this.state.containerWidth > 768) {
- notificationClass = 'ascribe-global-notification-bubble';
-
- if(this.state.show) {
- notificationClass += ' ascribe-global-notification-bubble-on';
- } else {
- notificationClass += ' ascribe-global-notification-bubble-off';
- }
+ const notificationClasses = [];
+ if (this.state.containerWidth > 768) {
+ notificationClasses.push('ascribe-global-notification-bubble');
+ notificationClasses.push(notificationStatus === 'show' ? 'ascribe-global-notification-bubble-on'
+ : 'ascribe-global-notification-bubble-off');
} else {
- notificationClass = 'ascribe-global-notification';
-
- if(this.state.show) {
- notificationClass += ' ascribe-global-notification-on';
- } else {
- notificationClass += ' ascribe-global-notification-off';
- }
-
+ notificationClasses.push('ascribe-global-notification');
+ notificationClasses.push(notificationStatus === 'show' ? 'ascribe-global-notification-on'
+ : 'ascribe-global-notification-off');
}
- if(this.state.message) {
- switch(this.state.message.type) {
+ let textClass;
+ let message;
+ if (notification && !notificationsPaused) {
+ message = notification.message;
+
+ switch(notification.type) {
case 'success':
textClass = 'ascribe-global-notification-success';
break;
@@ -106,18 +84,23 @@ let GlobalNotification = React.createClass({
textClass = 'ascribe-global-notification-danger';
break;
default:
- console.warn('Could not find a matching type in global_notification.js');
+ console.warn('Could not find a matching notification type in global_notification.js');
}
}
-
+ return (
+
+ );
+ },
+
+ render() {
return (
-
-
{this.state.message.message}
-
+ {this.renderNotification()}
@@ -125,4 +108,4 @@ let GlobalNotification = React.createClass({
}
});
-export default GlobalNotification;
\ No newline at end of file
+export default GlobalNotification;
diff --git a/js/components/header.js b/js/components/header.js
index 51f91318..b3fd543e 100644
--- a/js/components/header.js
+++ b/js/components/header.js
@@ -1,9 +1,10 @@
'use strict';
import React from 'react';
-
import { Link } from 'react-router';
+import history from '../history';
+
import Nav from 'react-bootstrap/lib/Nav';
import Navbar from 'react-bootstrap/lib/Navbar';
import CollapsibleNav from 'react-bootstrap/lib/CollapsibleNav';
@@ -58,11 +59,17 @@ let Header = React.createClass({
UserStore.listen(this.onChange);
WhitelabelActions.fetchWhitelabel();
WhitelabelStore.listen(this.onChange);
+
+ // react-bootstrap 0.25.1 has a bug in which it doesn't
+ // close the mobile expanded navigation after a click by itself.
+ // To get rid of this, we set the state of the component ourselves.
+ history.listen(this.onRouteChange);
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
WhitelabelStore.unlisten(this.onChange);
+ //history.unlisten(this.onRouteChange);
},
getLogo() {
@@ -135,6 +142,13 @@ let Header = React.createClass({
this.refs.dropdownbutton.setDropdownState(false);
},
+ // On route change, close expanded navbar again since react-bootstrap doesn't close
+ // the collapsibleNav by itself on click. setState() isn't available on a ref so
+ // doing this explicitly is the only way for now.
+ onRouteChange() {
+ this.refs.navbar.state.navExpanded = false;
+ },
+
render() {
let account;
let signup;
@@ -201,8 +215,10 @@ let Header = React.createClass({
-
+ fixedTop={true}
+ ref="navbar">
+
diff --git a/js/components/piece_list.js b/js/components/piece_list.js
index 85706d7c..9424117c 100644
--- a/js/components/piece_list.js
+++ b/js/components/piece_list.js
@@ -13,6 +13,9 @@ import AccordionList from './ascribe_accordion_list/accordion_list';
import AccordionListItemWallet from './ascribe_accordion_list/accordion_list_item_wallet';
import AccordionListItemTableEditions from './ascribe_accordion_list/accordion_list_item_table_editions';
+import AclButtonList from './ascribe_buttons/acl_button_list.js';
+import DeleteButton from './ascribe_buttons/delete_button';
+
import Pagination from './ascribe_pagination/pagination';
import PieceListFilterDisplay from './piece_list_filter_display';
@@ -22,7 +25,8 @@ import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar';
import AscribeSpinner from './ascribe_spinner';
-import { mergeOptions } from '../utils/general_utils';
+import { getAvailableAcls } from '../utils/acl_utils';
+import { mergeOptions, isShallowEqual } from '../utils/general_utils';
import { getLangText } from '../utils/lang_utils';
import { setDocumentTitle } from '../utils/dom_utils';
@@ -30,8 +34,11 @@ import { setDocumentTitle } from '../utils/dom_utils';
let PieceList = React.createClass({
propTypes: {
accordionListItemType: React.PropTypes.func,
+ bulkModalButtonListType: React.PropTypes.func,
+ canLoadPieceList: React.PropTypes.bool,
redirectTo: React.PropTypes.string,
customSubmitButton: React.PropTypes.element,
+ customThumbnailPlaceholder: React.PropTypes.func,
filterParams: React.PropTypes.array,
orderParams: React.PropTypes.array,
orderBy: React.PropTypes.string,
@@ -43,6 +50,8 @@ let PieceList = React.createClass({
getDefaultProps() {
return {
accordionListItemType: AccordionListItemWallet,
+ bulkModalButtonListType: AclButtonList,
+ canLoadPieceList: true,
orderParams: ['artist_name', 'title'],
filterParams: [{
label: getLangText('Show works I can'),
@@ -54,23 +63,53 @@ let PieceList = React.createClass({
}]
};
},
+
getInitialState() {
- return mergeOptions(
- PieceListStore.getState(),
- EditionListStore.getState()
+ const pieceListStore = PieceListStore.getState();
+ const stores = mergeOptions(
+ pieceListStore,
+ EditionListStore.getState(),
+ {
+ isFilterDirty: false
+ }
);
+
+ // Use the default filters but use the pieceListStore's settings if they're available
+ stores.filterBy = Object.assign(this.getDefaultFilterBy(), pieceListStore.filterBy);
+
+ return stores;
},
componentDidMount() {
- let page = this.props.location.query.page || 1;
-
PieceListStore.listen(this.onChange);
EditionListStore.listen(this.onChange);
- let orderBy = this.props.orderBy ? this.props.orderBy : this.state.orderBy;
- if (this.state.pieceList.length === 0 || this.state.page !== page){
- PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search,
- orderBy, this.state.orderAsc, this.state.filterBy);
+ let page = this.props.location.query.page || 1;
+ if (this.props.canLoadPieceList && (this.state.pieceList.length === 0 || this.state.page !== page)) {
+ this.loadPieceList({ page });
+ }
+ },
+
+ componentWillReceiveProps(nextProps) {
+ let filterBy;
+ let page = this.props.location.query.page || 1;
+
+ // If the user hasn't changed the filter and the new default filter is different
+ // than the current filter, apply the new default filter
+ if (!this.state.isFilterDirty) {
+ const newDefaultFilterBy = this.getDefaultFilterBy(nextProps);
+
+ // Only need to check shallowly since the filterBy shouldn't be nested
+ if (!isShallowEqual(this.state.filterBy, newDefaultFilterBy)) {
+ filterBy = newDefaultFilterBy;
+ page = 1;
+ }
+ }
+
+ // Only load if we are applying a new filter or if it's the first time we can
+ // load the piece list
+ if (nextProps.canLoadPieceList && (filterBy || !this.props.canLoadPieceList)) {
+ this.loadPieceList({ page, filterBy });
}
},
@@ -90,14 +129,29 @@ let PieceList = React.createClass({
this.setState(state);
},
+ getDefaultFilterBy(props = this.props) {
+ const { filterParams } = props;
+ const defaultFilterBy = {};
+
+ if (filterParams && typeof filterParams.forEach === 'function') {
+ filterParams.forEach(({ items }) => {
+ items.forEach((item) => {
+ if (typeof item === 'object' && item.defaultValue) {
+ defaultFilterBy[item.key] = true;
+ }
+ });
+ });
+ }
+
+ return defaultFilterBy;
+ },
+
paginationGoToPage(page) {
return () => {
// if the users clicks a pager of the pagination,
// the site should go to the top
document.body.scrollTop = document.documentElement.scrollTop = 0;
- PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search,
- this.state.orderBy, this.state.orderAsc,
- this.state.filterBy);
+ this.loadPieceList({ page });
};
},
@@ -116,31 +170,35 @@ let PieceList = React.createClass({
},
searchFor(searchTerm) {
- PieceListActions.fetchPieceList(1, this.state.pageSize, searchTerm, this.state.orderBy,
- this.state.orderAsc, this.state.filterBy);
- this.history.pushState(null, this.props.location.pathname, {page: 1});
+ this.loadPieceList({
+ page: 1,
+ search: searchTerm
+ });
+ this.history.pushState(null, this.props.location.pathname, {page: 1});
},
applyFilterBy(filterBy){
+ this.setState({
+ isFilterDirty: true
+ });
+
// first we need to apply the filter on the piece list
- PieceListActions.fetchPieceList(1, this.state.pageSize, this.state.search,
- this.state.orderBy, this.state.orderAsc, filterBy)
- .then(() => {
- // but also, we need to filter all the open edition lists
- this.state.pieceList
- .forEach((piece) => {
- // but only if they're actually open
- const isEditionListOpenForPiece = this.state.isEditionListOpenForPieceId[piece.id];
+ this
+ .loadPieceList({ page: 1, filterBy })
+ .then(() => {
+ // but also, we need to filter all the open edition lists
+ this.state.pieceList
+ .forEach((piece) => {
+ // but only if they're actually open
+ if(this.state.isEditionListOpenForPieceId[piece.id].show) {
+ EditionListActions.refreshEditionList({
+ pieceId: piece.id,
+ filterBy
+ });
+ }
- if (isEditionListOpenForPiece && isEditionListOpenForPiece.show) {
- EditionListActions.refreshEditionList({
- pieceId: piece.id,
- filterBy
- });
- }
-
- });
- });
+ });
+ });
// we have to redirect the user always to page one as it could be that there is no page two
// for filtered pieces
@@ -152,35 +210,97 @@ let PieceList = React.createClass({
orderBy, this.state.orderAsc, this.state.filterBy);
},
+ loadPieceList({ page, filterBy = this.state.filterBy, search = this.state.search }) {
+ const orderBy = this.state.orderBy || this.props.orderBy;
+
+ return PieceListActions.fetchPieceList(page, this.state.pageSize, search,
+ orderBy, this.state.orderAsc, filterBy);
+ },
+
+ fetchSelectedPieceEditionList() {
+ let filteredPieceIdList = Object.keys(this.state.editionList)
+ .filter((pieceId) => {
+ return this.state.editionList[pieceId]
+ .filter((edition) => edition.selected).length > 0;
+ });
+ return filteredPieceIdList;
+ },
+
+ fetchSelectedEditionList() {
+ let selectedEditionList = [];
+
+ Object
+ .keys(this.state.editionList)
+ .forEach((pieceId) => {
+ let filteredEditionsForPiece = this.state.editionList[pieceId].filter((edition) => edition.selected);
+ selectedEditionList = selectedEditionList.concat(filteredEditionsForPiece);
+ });
+
+ return selectedEditionList;
+ },
+
+ handleAclSuccess() {
+ PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
+ this.state.orderBy, this.state.orderAsc, this.state.filterBy);
+
+ this.fetchSelectedPieceEditionList()
+ .forEach((pieceId) => {
+ EditionListActions.refreshEditionList({pieceId});
+ });
+ EditionListActions.clearAllEditionSelections();
+ },
+
render() {
- let loadingElement = ;
- let AccordionListItemType = this.props.accordionListItemType;
+ const {
+ accordionListItemType: AccordionListItemType,
+ bulkModalButtonListType: BulkModalButtonListType,
+ customSubmitButton,
+ customThumbnailPlaceholder,
+ filterParams,
+ orderParams } = this.props;
+
+ const loadingElement = ;
+
+ const selectedEditions = this.fetchSelectedEditionList();
+ const availableAcls = getAvailableAcls(selectedEditions, (aclName) => aclName !== 'acl_view');
setDocumentTitle(getLangText('Collection'));
-
return (
- {this.props.customSubmitButton ?
- this.props.customSubmitButton :
+ {customSubmitButton ?
+ customSubmitButton :
}
-
+
+
+
+
+
+ filterParams={filterParams}/>
diff --git a/js/components/register_piece.js b/js/components/register_piece.js
index 43ac7bb7..8211e91e 100644
--- a/js/components/register_piece.js
+++ b/js/components/register_piece.js
@@ -17,7 +17,7 @@ import UserStore from '../stores/user_store';
import GlobalNotificationModel from '../models/global_notification_model';
import GlobalNotificationActions from '../actions/global_notification_actions';
-import PropertyCollapsible from './ascribe_forms/property_collapsible';
+import Property from './ascribe_forms/property';
import RegisterPieceForm from './ascribe_forms/form_register_piece';
import { mergeOptions } from '../utils/general_utils';
@@ -44,11 +44,8 @@ let RegisterPiece = React.createClass( {
return mergeOptions(
UserStore.getState(),
WhitelabelStore.getState(),
- PieceListStore.getState(),
- {
- selectedLicense: 0,
- isFineUploaderActive: false
- });
+ PieceListStore.getState()
+ );
},
componentDidMount() {
@@ -66,13 +63,6 @@ let RegisterPiece = React.createClass( {
onChange(state) {
this.setState(state);
-
- if(this.state.currentUser && this.state.currentUser.email) {
- // we should also make the fineuploader component editable again
- this.setState({
- isFineUploaderActive: true
- });
- }
},
handleSuccess(response){
@@ -96,15 +86,16 @@ let RegisterPiece = React.createClass( {
getSpecifyEditions() {
if(this.state.whitelabel && this.state.whitelabel.acl_create_editions || Object.keys(this.state.whitelabel).length === 0) {
return (
-
+ checkboxLabel={getLangText('Specify editions')}
+ expanded={false}>
{getLangText('Editions')}
-
+
);
}
},
@@ -117,7 +108,7 @@ let RegisterPiece = React.createClass( {
{this.props.children}
diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js
index 0c293b15..41f2c25a 100644
--- a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js
+++ b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js
@@ -253,7 +253,7 @@ const PRRegisterPieceForm = React.createClass({
name="digitalWorkKey"
label={getLangText('Select the PDF with your work')}>
- {getLangText('Submitted to prize')}
+ {getLangText('Submitted to prize') + ' '}
+
);
}
diff --git a/js/components/whitelabel/prize/simple_prize/components/ascribe_detail/prize_piece_container.js b/js/components/whitelabel/prize/simple_prize/components/ascribe_detail/prize_piece_container.js
index 93ca50f3..982af7b0 100644
--- a/js/components/whitelabel/prize/simple_prize/components/ascribe_detail/prize_piece_container.js
+++ b/js/components/whitelabel/prize/simple_prize/components/ascribe_detail/prize_piece_container.js
@@ -51,8 +51,7 @@ import { setDocumentTitle } from '../../../../../../utils/dom_utils';
*/
let PieceContainer = React.createClass({
propTypes: {
- params: React.PropTypes.object,
- location: React.PropTypes.object
+ params: React.PropTypes.object
},
getInitialState() {
@@ -111,7 +110,7 @@ let PieceContainer = React.createClass({
},
render() {
- if(this.state.piece && this.state.piece.title) {
+ if(this.state.piece && this.state.piece.id) {
/*
This really needs a refactor!
@@ -162,7 +161,7 @@ let PieceContainer = React.createClass({
piece={this.state.piece}
currentUser={this.state.currentUser}/>
}>
-
+
);
} else {
@@ -292,8 +291,8 @@ let PrizePieceRatings = React.createClass({
url={ApiUrls.ownership_loans_pieces_request}
email={this.props.currentUser.email}
gallery={this.props.piece.prize.name}
- startdate={today}
- enddate={endDate}
+ startDate={today}
+ endDate={endDate}
showPersonalMessage={true}
showPassword={false}
handleSuccess={this.handleLoanSuccess} />
@@ -426,8 +425,7 @@ let PrizePieceRatings = React.createClass({
let PrizePieceDetails = React.createClass({
propTypes: {
- piece: React.PropTypes.object,
- location: React.PropTypes.object
+ piece: React.PropTypes.object
},
render() {
@@ -464,8 +462,7 @@ let PrizePieceDetails = React.createClass({
overrideForm={true}
pieceId={this.props.piece.id}
otherData={this.props.piece.other_data}
- multiple={true}
- location={location}/>
+ multiple={true} />
);
diff --git a/js/components/whitelabel/wallet/components/23vivi/23vivi_accordion_list/23vivi_accordion_list_item_thumbnail_placeholder.js b/js/components/whitelabel/wallet/components/23vivi/23vivi_accordion_list/23vivi_accordion_list_item_thumbnail_placeholder.js
new file mode 100644
index 00000000..f360c932
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/23vivi/23vivi_accordion_list/23vivi_accordion_list_item_thumbnail_placeholder.js
@@ -0,0 +1,15 @@
+'use strict'
+
+import React from 'react';
+
+let Vivi23AccordionListItemThumbnailPlaceholder = React.createClass({
+ render() {
+ return (
+
+ 23
+
+ );
+ }
+});
+
+export default Vivi23AccordionListItemThumbnailPlaceholder;
diff --git a/js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js b/js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js
new file mode 100644
index 00000000..302495a0
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js
@@ -0,0 +1,78 @@
+'use strict';
+
+import React from 'react';
+
+import Button from 'react-bootstrap/lib/Button';
+import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
+
+import WhitelabelActions from '../../../../../actions/whitelabel_actions';
+import WhitelabelStore from '../../../../../stores/whitelabel_store';
+
+import { mergeOptions } from '../../../../../utils/general_utils';
+import { getLangText } from '../../../../../utils/lang_utils';
+import { setDocumentTitle } from '../../../../../utils/dom_utils';
+
+let Vivi23Landing = React.createClass({
+ getInitialState() {
+ return WhitelabelStore.getState();
+ },
+
+ componentWillMount() {
+ setDocumentTitle('23VIVI Marketplace');
+ },
+
+ componentDidMount() {
+ WhitelabelStore.listen(this.onChange);
+ WhitelabelActions.fetchWhitelabel();
+ },
+
+ componentWillUnmount() {
+ WhitelabelStore.unlisten(this.onChange);
+ },
+
+ onChange(state) {
+ this.setState(state);
+ },
+
+ render() {
+ return (
+
+
+
+
+
+
+ {getLangText('Artwork from the 23VIVI Marketplace is powered by') + ' '}
+
+
+
+
+
+
+ {getLangText('Existing ascribe user?')}
+
+
+
+
+
+
+
+ {getLangText('Do you need an account?')}
+
+
+
+
+
+
+
+
+
+ );
+ }
+});
+
+export default Vivi23Landing;
diff --git a/js/components/whitelabel/wallet/components/23vivi/23vivi_piece_list.js b/js/components/whitelabel/wallet/components/23vivi/23vivi_piece_list.js
new file mode 100644
index 00000000..0bfb8aa0
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/23vivi/23vivi_piece_list.js
@@ -0,0 +1,24 @@
+'use strict'
+
+import React from 'react';
+
+import Vivi23AccordionListItemThumbnailPlaceholder from './23vivi_accordion_list/23vivi_accordion_list_item_thumbnail_placeholder';
+
+import MarketPieceList from '../market/market_piece_list';
+
+let Vivi23PieceList = React.createClass({
+ propTypes: {
+ location: React.PropTypes.object
+ },
+
+ render() {
+ return (
+
+ );
+ }
+
+});
+
+export default Vivi23PieceList;
diff --git a/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js b/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js
index b263e517..26a186ca 100644
--- a/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js
+++ b/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js
@@ -30,7 +30,7 @@ let WalletPieceContainer = React.createClass({
render() {
- if(this.props.piece && this.props.piece.title) {
+ if(this.props.piece && this.props.piece.id) {
return (
- {getLangText('Submitted to Cyland')}
+ {getLangText('Submitted to Cyland') + ' '}
+
- {getLangText('Loaned to Cyland')}
+ {getLangText('Loaned to Cyland') + ' '}
+
diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js b/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js
index 0fe5a025..d211d3e8 100644
--- a/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js
+++ b/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js
@@ -89,7 +89,7 @@ let CylandPieceContainer = React.createClass({
},
render() {
- if(this.state.piece && this.state.piece.title) {
+ if(this.state.piece && this.state.piece.id) {
setDocumentTitle([this.state.piece.artist_name, this.state.piece.title].join(', '));
return (
diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js b/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js
index a7631d95..93cc515a 100644
--- a/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js
+++ b/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js
@@ -64,12 +64,6 @@ let CylandAdditionalDataForm = React.createClass({
},
- uploadStarted() {
- this.setState({
- isUploadReady: false
- });
- },
-
setIsUploadReady(isReady) {
this.setState({
isUploadReady: isReady
@@ -122,7 +116,7 @@ let CylandAdditionalDataForm = React.createClass({
+ expanded={!disabled || !!piece.extra_data.artist_bio}>
+ expanded={!disabled || !!piece.extra_data.artist_contact_information}>
+ expanded={!disabled || !!piece.extra_data.conceptual_overview}>
+ expanded={!disabled || !!piece.extra_data.medium}>
+ expanded={!disabled || !!piece.extra_data.size_duration}>
+ expanded={!disabled || !!piece.extra_data.display_instructions}>
+ expanded={!disabled || !!piece.extra_data.additional_details}>
+
-
+
diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js b/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js
index 470da761..42b7c1ad 100644
--- a/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js
+++ b/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js
@@ -53,8 +53,6 @@ let CylandRegisterPiece = React.createClass({
PieceStore.getState(),
WhitelabelStore.getState(),
{
- selectedLicense: 0,
- isFineUploaderActive: false,
step: 0
});
},
@@ -90,13 +88,6 @@ let CylandRegisterPiece = React.createClass({
onChange(state) {
this.setState(state);
-
- if(this.state.currentUser && this.state.currentUser.email) {
- // we should also make the fineuploader component editable again
- this.setState({
- isFineUploaderActive: true
- });
- }
},
handleRegisterSuccess(response){
@@ -167,11 +158,6 @@ let CylandRegisterPiece = React.createClass({
}
},
- // basically redirects to the second slide (index: 1), when the user is not logged in
- onLoggedOut() {
- this.history.pushState(null, '/login');
- },
-
render() {
let today = new Moment();
@@ -197,9 +183,8 @@ let CylandRegisterPiece = React.createClass({
enableLocalHashing={false}
headerMessage={getLangText('Submit to Cyland Archive')}
submitMessage={getLangText('Submit')}
- isFineUploaderActive={this.state.isFineUploaderActive}
+ isFineUploaderActive={true}
handleSuccess={this.handleRegisterSuccess}
- onLoggedOut={this.onLoggedOut}
location={this.props.location}/>
@@ -229,8 +214,8 @@ let CylandRegisterPiece = React.createClass({
url={ApiUrls.ownership_loans_pieces}
email={this.state.whitelabel.user}
gallery="Cyland Archive"
- startdate={today}
- enddate={datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain}
+ startDate={today}
+ endDate={datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain}
showStartDate={false}
showEndDate={false}
showPersonalMessage={false}
diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js
index f2f73767..ce8cebf5 100644
--- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js
+++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js
@@ -81,8 +81,8 @@ let IkonotvAccordionListItem = React.createClass({
- {getLangText('Loaned to IkonoTV')}
+ {getLangText('Loaned to IkonoTV') + ' '}
+
diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_detail/ikonotv_piece_container.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_detail/ikonotv_piece_container.js
index 4e2f6a63..df58b7c7 100644
--- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_detail/ikonotv_piece_container.js
+++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_detail/ikonotv_piece_container.js
@@ -123,7 +123,7 @@ let IkonotvPieceContainer = React.createClass({
);
}
- if(this.state.piece && this.state.piece.title) {
+ if(this.state.piece && this.state.piece.id) {
setDocumentTitle([this.state.piece.artist_name, this.state.piece.title].join(', '));
return (
+ expanded={!this.props.disabled || !!this.props.piece.extra_data.artist_website}>
+ expanded={!this.props.disabled || !!this.props.piece.extra_data.gallery_website}>
+ expanded={!this.props.disabled || !!this.props.piece.extra_data.additional_websites}>
+ expanded={!this.props.disabled || !!this.props.piece.extra_data.conceptual_overview}>
+ expanded={!this.props.disabled || !!this.props.piece.extra_data.medium}>
+ expanded={!this.props.disabled || !!this.props.piece.extra_data.size_duration}>
+ expanded={!this.props.disabled || !!this.props.piece.extra_data.copyright}>
+ expanded={!this.props.disabled || !!this.props.piece.extra_data.courtesy_of}>
+ expanded={!this.props.disabled || !!this.props.piece.extra_data.copyright_of_photography}>
+ expanded={!this.props.disabled || !!this.props.piece.extra_data.additional_details}>
diff --git a/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js b/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js
new file mode 100644
index 00000000..e68b1781
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/lumenus/lumenus_landing.js
@@ -0,0 +1,84 @@
+'use strict';
+
+import React from 'react';
+
+import Button from 'react-bootstrap/lib/Button';
+import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
+
+import WhitelabelActions from '../../../../../actions/whitelabel_actions';
+import WhitelabelStore from '../../../../../stores/whitelabel_store';
+
+import { mergeOptions } from '../../../../../utils/general_utils';
+import { getLangText } from '../../../../../utils/lang_utils';
+import { setDocumentTitle } from '../../../../../utils/dom_utils';
+
+
+let LumenusLanding = React.createClass({
+
+ getInitialState() {
+ return mergeOptions(
+ WhitelabelStore.getState()
+ );
+ },
+
+ componentWillMount() {
+ setDocumentTitle('Lumenus Marketplace');
+ },
+
+ componentDidMount() {
+ WhitelabelStore.listen(this.onChange);
+ WhitelabelActions.fetchWhitelabel();
+ },
+
+ componentWillUnmount() {
+ WhitelabelStore.unlisten(this.onChange);
+ },
+
+ onChange(state) {
+ this.setState(state);
+ },
+
+ render() {
+ return (
+
+
+
+
+
+
+ {getLangText('Artwork from the Lumenus Marketplace is powered by') + ' '}
+
+
+
+
+
+
+
+
+ {getLangText('Existing ascribe user?')}
+
+
+
+
+
+
+
+ {getLangText('Do you need an account?')}
+
+
+
+
+
+
+
+
+
+ );
+ }
+});
+
+export default LumenusLanding;
diff --git a/js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js b/js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js
new file mode 100644
index 00000000..1dcdd4e5
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_buttons/market_acl_button_list.js
@@ -0,0 +1,74 @@
+'use strict';
+
+import React from 'react';
+
+import MarketSubmitButton from './market_submit_button';
+
+import DeleteButton from '../../../../../ascribe_buttons/delete_button';
+import EmailButton from '../../../../../ascribe_buttons/acls/email_button';
+import TransferButton from '../../../../../ascribe_buttons/acls/transfer_button';
+import UnconsignButton from '../../../../../ascribe_buttons/acls/unconsign_button';
+
+import UserActions from '../../../../../../actions/user_actions';
+import UserStore from '../../../../../../stores/user_store';
+
+let MarketAclButtonList = React.createClass({
+ propTypes: {
+ availableAcls: React.PropTypes.object.isRequired,
+ className: React.PropTypes.string,
+ pieceOrEditions: React.PropTypes.array,
+ handleSuccess: React.PropTypes.func,
+ children: React.PropTypes.oneOfType([
+ React.PropTypes.arrayOf(React.PropTypes.element),
+ React.PropTypes.element
+ ])
+ },
+
+ getInitialState() {
+ return UserStore.getState();
+ },
+
+ componentDidMount() {
+ UserStore.listen(this.onChange);
+ UserActions.fetchCurrentUser();
+ },
+
+ componentWillUnmount() {
+ UserStore.unlisten(this.onChange);
+ },
+
+ onChange(state) {
+ this.setState(state);
+ },
+
+ render() {
+ let { availableAcls, className, pieceOrEditions, handleSuccess } = this.props;
+ return (
+
+
+
+
+
+ {this.props.children}
+
+ );
+ }
+});
+
+export default MarketAclButtonList;
diff --git a/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js
new file mode 100644
index 00000000..d8ef4c41
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_buttons/market_submit_button.js
@@ -0,0 +1,160 @@
+'use strict';
+
+import React from 'react';
+import classNames from 'classnames';
+
+import MarketAdditionalDataForm from '../market_forms/market_additional_data_form';
+
+import AclFormFactory from '../../../../../ascribe_forms/acl_form_factory';
+import ConsignForm from '../../../../../ascribe_forms/form_consign';
+
+import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper';
+
+import AclProxy from '../../../../../acl_proxy';
+
+import PieceActions from '../../../../../../actions/piece_actions';
+import WhitelabelActions from '../../../../../../actions/whitelabel_actions';
+import WhitelabelStore from '../../../../../../stores/whitelabel_store';
+
+import ApiUrls from '../../../../../../constants/api_urls';
+
+import { getAclFormMessage, getAclFormDataId } from '../../../../../../utils/form_utils';
+import { getLangText } from '../../../../../../utils/lang_utils';
+
+let MarketSubmitButton = React.createClass({
+ propTypes: {
+ availableAcls: React.PropTypes.object.isRequired,
+ currentUser: React.PropTypes.object,
+ editions: React.PropTypes.array.isRequired,
+ handleSuccess: React.PropTypes.func.isRequired,
+ className: React.PropTypes.string,
+ },
+
+ getInitialState() {
+ return WhitelabelStore.getState();
+ },
+
+ componentDidMount() {
+ WhitelabelStore.listen(this.onChange);
+
+ WhitelabelActions.fetchWhitelabel();
+ },
+
+ componentWillUnmount() {
+ WhitelabelStore.unlisten(this.onChange);
+ },
+
+ onChange(state) {
+ this.setState(state);
+ },
+
+ canEditionBeSubmitted(edition) {
+ if (edition && edition.extra_data && edition.other_data) {
+ const { extra_data, other_data } = edition;
+
+ if (extra_data.artist_bio && extra_data.work_description &&
+ extra_data.technology_details && extra_data.display_instructions &&
+ other_data.length > 0) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ getFormDataId() {
+ return getAclFormDataId(false, this.props.editions);
+ },
+
+ getAggregateEditionDetails() {
+ const { editions } = this.props;
+
+ // Currently, we only care if all the given editions are from the same parent piece
+ // and if they can be submitted
+ return editions.reduce((details, curEdition) => {
+ return {
+ solePieceId: details.solePieceId === curEdition.parent ? details.solePieceId : null,
+ canSubmit: details.canSubmit && this.canEditionBeSubmitted(curEdition)
+ };
+ }, {
+ solePieceId: editions.length > 0 ? editions[0].parent : null,
+ canSubmit: this.canEditionBeSubmitted(editions[0])
+ });
+ },
+
+ handleAdditionalDataSuccess(pieceId) {
+ // Fetch newly updated piece to update the views
+ PieceActions.fetchOne(pieceId);
+
+ this.refs.consignModal.show();
+ },
+
+ render() {
+ const { availableAcls, currentUser, className, editions, handleSuccess } = this.props;
+ const { whitelabel: { name: whitelabelName = 'Market', user: whitelabelAdminEmail } } = this.state;
+ const { solePieceId, canSubmit } = this.getAggregateEditionDetails();
+ const message = getAclFormMessage({
+ aclName: 'acl_consign',
+ entities: editions,
+ isPiece: false,
+ additionalMessage: getLangText('Suggested price:'),
+ senderName: currentUser.username
+ });
+
+ const triggerButton = (
+
+ );
+ const consignForm = (
+
+ );
+
+ if (solePieceId && !canSubmit) {
+ return (
+
+
+
+
+
+
+ {consignForm}
+
+
+ );
+ } else {
+ return (
+
+
+ {consignForm}
+
+
+ );
+ }
+ }
+});
+
+export default MarketSubmitButton;
diff --git a/js/components/whitelabel/wallet/components/market/market_detail/market_edition_container.js b/js/components/whitelabel/wallet/components/market/market_detail/market_edition_container.js
new file mode 100644
index 00000000..97284dbc
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_detail/market_edition_container.js
@@ -0,0 +1,24 @@
+'use strict';
+
+import React from 'react';
+
+import MarketFurtherDetails from './market_further_details';
+
+import MarketAclButtonList from '../market_buttons/market_acl_button_list';
+
+import EditionContainer from '../../../../../ascribe_detail/edition_container';
+
+let MarketEditionContainer = React.createClass({
+ propTypes: EditionContainer.propTypes,
+
+ render() {
+ return (
+
+ );
+ }
+});
+
+export default MarketEditionContainer;
diff --git a/js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js b/js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js
new file mode 100644
index 00000000..4e1e3ee8
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_detail/market_further_details.js
@@ -0,0 +1,23 @@
+'use strict';
+
+import React from 'react';
+
+import MarketAdditionalDataForm from '../market_forms/market_additional_data_form'
+
+let MarketFurtherDetails = React.createClass({
+ propTypes: {
+ pieceId: React.PropTypes.number,
+ handleSuccess: React.PropTypes.func,
+ },
+
+ render() {
+ return (
+
+ );
+ }
+});
+
+export default MarketFurtherDetails;
diff --git a/js/components/whitelabel/wallet/components/market/market_detail/market_piece_container.js b/js/components/whitelabel/wallet/components/market/market_detail/market_piece_container.js
new file mode 100644
index 00000000..d41ade56
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_detail/market_piece_container.js
@@ -0,0 +1,21 @@
+'use strict';
+
+import React from 'react';
+
+import MarketFurtherDetails from './market_further_details';
+
+import PieceContainer from '../../../../../ascribe_detail/piece_container';
+
+let MarketPieceContainer = React.createClass({
+ propTypes: PieceContainer.propTypes,
+
+ render() {
+ return (
+
+ );
+ }
+});
+
+export default MarketPieceContainer;
diff --git a/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js b/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js
new file mode 100644
index 00000000..d136c9cf
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_forms/market_additional_data_form.js
@@ -0,0 +1,235 @@
+'use strict';
+
+import React from 'react';
+
+import Form from '../../../../../ascribe_forms/form';
+import Property from '../../../../../ascribe_forms/property';
+
+import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable';
+
+import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader';
+import AscribeSpinner from '../../../../../ascribe_spinner';
+
+import GlobalNotificationModel from '../../../../../../models/global_notification_model';
+import GlobalNotificationActions from '../../../../../../actions/global_notification_actions';
+
+import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_s3_fine_uploader_utils';
+
+import PieceActions from '../../../../../../actions/piece_actions';
+import PieceStore from '../../../../../../stores/piece_store';
+
+import ApiUrls from '../../../../../../constants/api_urls';
+import AppConstants from '../../../../../../constants/application_constants';
+
+import requests from '../../../../../../utils/requests';
+import { mergeOptions } from '../../../../../../utils/general_utils';
+import { getLangText } from '../../../../../../utils/lang_utils';
+
+
+let MarketAdditionalDataForm = React.createClass({
+ propTypes: {
+ pieceId: React.PropTypes.oneOfType([
+ React.PropTypes.number,
+ React.PropTypes.string
+ ]),
+ editable: React.PropTypes.bool,
+ isInline: React.PropTypes.bool,
+ showHeading: React.PropTypes.bool,
+ showNotification: React.PropTypes.bool,
+ submitLabel: React.PropTypes.string,
+ handleSuccess: React.PropTypes.func
+ },
+
+ getDefaultProps() {
+ return {
+ editable: true,
+ submitLabel: getLangText('Register work')
+ };
+ },
+
+ getInitialState() {
+ const pieceStore = PieceStore.getState();
+
+ return mergeOptions(
+ pieceStore,
+ {
+ // Allow the form to be submitted if there's already an additional image uploaded
+ isUploadReady: this.isUploadReadyOnChange(pieceStore.piece),
+ forceUpdateKey: 0
+ });
+ },
+
+ componentDidMount() {
+ PieceStore.listen(this.onChange);
+
+ if (this.props.pieceId) {
+ PieceActions.fetchOne(this.props.pieceId);
+ }
+ },
+
+ componentWillUnmount() {
+ PieceStore.unlisten(this.onChange);
+ },
+
+ onChange(state) {
+ Object.assign({}, state, {
+ // Allow the form to be submitted if the updated piece already has an additional image uploaded
+ isUploadReady: this.isUploadReadyOnChange(state.piece),
+
+ /**
+ * Increment the forceUpdateKey to force the form to rerender on each change
+ *
+ * THIS IS A HACK TO MAKE SURE THE FORM ALWAYS DISPLAYS THE MOST RECENT STATE
+ * BECAUSE SOME OF OUR FORM ELEMENTS DON'T UPDATE FROM PROP CHANGES (ie.
+ * InputTextAreaToggable).
+ */
+ forceUpdateKey: this.state.forceUpdateKey + 1
+ });
+
+ this.setState(state);
+ },
+
+ getFormData() {
+ let extradata = {};
+ let formRefs = this.refs.form.refs;
+
+ // Put additional fields in extra data object
+ Object
+ .keys(formRefs)
+ .forEach((fieldName) => {
+ extradata[fieldName] = formRefs[fieldName].state.value;
+ });
+
+ return {
+ extradata: extradata,
+ piece_id: this.state.piece.id
+ };
+ },
+
+ isUploadReadyOnChange(piece) {
+ return piece && piece.other_data && piece.other_data.length > 0;
+ },
+
+ handleSuccessWithNotification() {
+ if (typeof this.props.handleSuccess === 'function') {
+ this.props.handleSuccess();
+ }
+
+ let notification = new GlobalNotificationModel(getLangText('Further details successfully updated'), 'success', 10000);
+ GlobalNotificationActions.appendGlobalNotification(notification);
+ },
+
+ setIsUploadReady(isReady) {
+ this.setState({
+ isUploadReady: isReady
+ });
+ },
+
+ render() {
+ const { editable, isInline, handleSuccess, showHeading, showNotification, submitLabel } = this.props;
+ const { piece } = this.state;
+ let buttons, heading;
+
+ let spinner = ;
+
+ if (!isInline) {
+ buttons = (
+
+ );
+
+ spinner = (
+
+ );
+
+ heading = showHeading ? (
+
+
+ {getLangText('Provide additional details')}
+
+
+ ) : null;
+ }
+
+ if (piece && piece.id) {
+ return (
+
+ );
+ } else {
+ return (
+
+ {spinner}
+
+ );
+ }
+ }
+});
+
+export default MarketAdditionalDataForm;
diff --git a/js/components/whitelabel/wallet/components/market/market_piece_list.js b/js/components/whitelabel/wallet/components/market/market_piece_list.js
new file mode 100644
index 00000000..1c74e6de
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_piece_list.js
@@ -0,0 +1,90 @@
+'use strict';
+
+import React from 'react';
+
+import MarketAclButtonList from './market_buttons/market_acl_button_list';
+
+import PieceList from '../../../../piece_list';
+
+import UserActions from '../../../../../actions/user_actions';
+import UserStore from '../../../../../stores/user_store';
+import WhitelabelActions from '../../../../../actions/whitelabel_actions';
+import WhitelabelStore from '../../../../../stores/whitelabel_store';
+
+import { setDocumentTitle } from '../../../../../utils/dom_utils';
+import { mergeOptions } from '../../../../../utils/general_utils';
+import { getLangText } from '../../../../../utils/lang_utils';
+
+let MarketPieceList = React.createClass({
+ propTypes: {
+ customThumbnailPlaceholder: React.PropTypes.func,
+ location: React.PropTypes.object
+ },
+
+ getInitialState() {
+ return mergeOptions(
+ UserStore.getState(),
+ WhitelabelStore.getState()
+ );
+ },
+
+ componentWillMount() {
+ setDocumentTitle(getLangText('Collection'));
+ },
+
+ componentDidMount() {
+ UserStore.listen(this.onChange);
+ WhitelabelStore.listen(this.onChange);
+
+ UserActions.fetchCurrentUser();
+ WhitelabelActions.fetchWhitelabel();
+ },
+
+ componentWillUnmount() {
+ UserStore.unlisten(this.onChange);
+ WhitelabelStore.unlisten(this.onChange);
+ },
+
+ onChange(state) {
+ this.setState(state);
+ },
+
+ render() {
+ const { customThumbnailPlaceholder, location } = this.props;
+ const {
+ currentUser: { email: userEmail },
+ whitelabel: {
+ name: whitelabelName = 'Market',
+ user: whitelabelAdminEmail
+ } } = this.state;
+
+ let filterParams = null;
+ let canLoadPieceList = false;
+
+ if (userEmail && whitelabelAdminEmail) {
+ canLoadPieceList = true;
+ const isUserAdmin = userEmail === whitelabelAdminEmail;
+
+ filterParams = [{
+ label: getLangText('Show works I can'),
+ items: [{
+ key: isUserAdmin ? 'acl_transfer' : 'acl_consign',
+ label: getLangText(isUserAdmin ? 'transfer' : 'consign to %s', whitelabelName),
+ defaultValue: true
+ }]
+ }];
+ }
+
+ return (
+
+ );
+ }
+});
+
+export default MarketPieceList;
diff --git a/js/components/whitelabel/wallet/components/market/market_register_piece.js b/js/components/whitelabel/wallet/components/market/market_register_piece.js
new file mode 100644
index 00000000..387934f9
--- /dev/null
+++ b/js/components/whitelabel/wallet/components/market/market_register_piece.js
@@ -0,0 +1,174 @@
+'use strict';
+
+import React from 'react';
+import { History } from 'react-router';
+
+import Col from 'react-bootstrap/lib/Col';
+import Row from 'react-bootstrap/lib/Row';
+
+import MarketAdditionalDataForm from './market_forms/market_additional_data_form';
+
+import Property from '../../../../ascribe_forms/property';
+import RegisterPieceForm from '../../../../ascribe_forms/form_register_piece';
+
+import PieceActions from '../../../../../actions/piece_actions';
+import PieceListStore from '../../../../../stores/piece_list_store';
+import PieceListActions from '../../../../../actions/piece_list_actions';
+import UserStore from '../../../../../stores/user_store';
+import UserActions from '../../../../../actions/user_actions';
+import WhitelabelActions from '../../../../../actions/whitelabel_actions';
+import WhitelabelStore from '../../../../../stores/whitelabel_store';
+
+import SlidesContainer from '../../../../ascribe_slides_container/slides_container';
+
+import { getLangText } from '../../../../../utils/lang_utils';
+import { setDocumentTitle } from '../../../../../utils/dom_utils';
+import { mergeOptions } from '../../../../../utils/general_utils';
+
+let MarketRegisterPiece = React.createClass({
+ propTypes: {
+ location: React.PropTypes.object
+ },
+
+ mixins: [History],
+
+ getInitialState(){
+ return mergeOptions(
+ PieceListStore.getState(),
+ UserStore.getState(),
+ WhitelabelStore.getState(),
+ {
+ step: 0
+ });
+ },
+
+ componentDidMount() {
+ PieceListStore.listen(this.onChange);
+ UserStore.listen(this.onChange);
+ WhitelabelStore.listen(this.onChange);
+
+ UserActions.fetchCurrentUser();
+ WhitelabelActions.fetchWhitelabel();
+
+ // Reset the piece store to make sure that we don't display old data
+ // if the user repeatedly registers works
+ PieceActions.updatePiece({});
+ },
+
+ componentWillUnmount() {
+ PieceListStore.unlisten(this.onChange);
+ UserStore.unlisten(this.onChange);
+ WhitelabelStore.unlisten(this.onChange);
+ },
+
+ onChange(state) {
+ this.setState(state);
+ },
+
+ handleRegisterSuccess(response) {
+ this.refreshPieceList();
+
+ // Use the response's piece for the next step if available
+ let pieceId = null;
+ if (response && response.piece) {
+ pieceId = response.piece.id;
+ PieceActions.updatePiece(response.piece);
+ }
+
+ this.incrementStep();
+ this.refs.slidesContainer.nextSlide({ piece_id: pieceId });
+ },
+
+ handleAdditionalDataSuccess() {
+ this.refreshPieceList();
+
+ this.history.pushState(null, '/collection');
+ },
+
+ // We need to increase the step to lock the forms that are already filled out
+ incrementStep() {
+ this.setState({
+ step: this.state.step + 1
+ });
+ },
+
+ getPieceFromQueryParam() {
+ const queryParams = this.props.location.query;
+
+ // Since every step of this register process is atomic,
+ // we may need to enter the process at step 1 or 2.
+ // If this is the case, we'll need the piece number to complete submission.
+ // It is encoded in the URL as a queryParam and we're checking for it here.
+ return queryParams && queryParams.piece_id;
+ },
+
+ refreshPieceList() {
+ PieceListActions.fetchPieceList(
+ this.state.page,
+ this.state.pageSize,
+ this.state.searchTerm,
+ this.state.orderBy,
+ this.state.orderAsc,
+ this.state.filterBy
+ );
+ },
+
+ render() {
+ const {
+ step,
+ whitelabel: {
+ name: whitelabelName = 'Market'
+ } } = this.state;
+
+ setDocumentTitle(getLangText('Register a new piece'));
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+});
+
+export default MarketRegisterPiece;
diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js
index 2cdc0054..8ad2eb81 100644
--- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js
+++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js
@@ -4,22 +4,30 @@ import walletConstants from './wallet_application_constants';
// gets subdomain as a parameter
function getWalletApiUrls(subdomain) {
- if (subdomain === 'cyland'){
+ if (subdomain === 'cyland') {
return {
'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/',
'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/',
'piece_extradata': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/extradata/',
'user': walletConstants.walletApiEndpoint + subdomain + '/users/'
};
- }
- else if (subdomain === 'ikonotv'){
+ } else if (subdomain === 'ikonotv') {
return {
'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/',
'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/',
'user': walletConstants.walletApiEndpoint + subdomain + '/users/'
};
+ } else if (subdomain === 'lumenus' || subdomain === '23vivi') {
+ return {
+ 'editions_list': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/editions/',
+ 'edition': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/editions/${bitcoin_id}/',
+ 'pieces_list': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/',
+ 'piece': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/',
+ 'piece_extradata': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/extradata/',
+ 'user': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/users/'
+ };
}
return {};
}
-export default getWalletApiUrls;
\ No newline at end of file
+export default getWalletApiUrls;
diff --git a/js/components/whitelabel/wallet/wallet_app.js b/js/components/whitelabel/wallet/wallet_app.js
index 5056716a..c2810fd0 100644
--- a/js/components/whitelabel/wallet/wallet_app.js
+++ b/js/components/whitelabel/wallet/wallet_app.js
@@ -32,7 +32,7 @@ let WalletApp = React.createClass({
// if the path of the current activeRoute is not defined, then this is the IndexRoute
if ((!path || history.isActive('/login') || history.isActive('/signup') || history.isActive('/contract_notifications'))
- && (['ikonotv']).indexOf(subdomain) > -1) {
+ && (['cyland', 'ikonotv', 'lumenus', '23vivi']).indexOf(subdomain) > -1) {
header = ();
} else {
header = ;
diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js
index 8e4d5197..0a4e3a58 100644
--- a/js/components/whitelabel/wallet/wallet_routes.js
+++ b/js/components/whitelabel/wallet/wallet_routes.js
@@ -16,6 +16,8 @@ import SettingsContainer from '../../../components/ascribe_settings/settings_con
import ContractSettings from '../../../components/ascribe_settings/contract_settings';
import ErrorNotFoundPage from '../../../components/error_not_found_page';
+import CCRegisterPiece from './components/cc/cc_register_piece';
+
import CylandLanding from './components/cyland/cyland_landing';
import CylandPieceContainer from './components/cyland/cyland_detail/cyland_piece_container';
import CylandRegisterPiece from './components/cyland/cyland_register_piece';
@@ -23,12 +25,20 @@ import CylandPieceList from './components/cyland/cyland_piece_list';
import IkonotvLanding from './components/ikonotv/ikonotv_landing';
import IkonotvPieceList from './components/ikonotv/ikonotv_piece_list';
-import ContractAgreementForm from '../../../components/ascribe_forms/form_contract_agreement';
+import SendContractAgreementForm from '../../../components/ascribe_forms/form_send_contract_agreement';
import IkonotvRegisterPiece from './components/ikonotv/ikonotv_register_piece';
import IkonotvPieceContainer from './components/ikonotv/ikonotv_detail/ikonotv_piece_container';
import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications';
-import CCRegisterPiece from './components/cc/cc_register_piece';
+import MarketPieceList from './components/market/market_piece_list';
+import MarketRegisterPiece from './components/market/market_register_piece';
+import MarketPieceContainer from './components/market/market_detail/market_piece_container';
+import MarketEditionContainer from './components/market/market_detail/market_edition_container';
+
+import LumenusLanding from './components/lumenus/lumenus_landing';
+
+import Vivi23Landing from './components/23vivi/23vivi_landing';
+import Vivi23PieceList from './components/23vivi/23vivi_piece_list';
import AuthProxyHandler from '../../../components/ascribe_routes/proxy_routes/auth_proxy_handler';
@@ -128,7 +138,7 @@ let ROUTES = {
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(ContractSettings)}/>
+ ),
+ 'lumenus': (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+ '23vivi': (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)
};
-
function getRoutes(commonRoutes, subdomain) {
if(subdomain in ROUTES) {
return ROUTES[subdomain];
@@ -160,5 +240,4 @@ function getRoutes(commonRoutes, subdomain) {
}
}
-
export default getRoutes;
diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js
index a58a8cc6..74edc51b 100644
--- a/js/constants/application_constants.js
+++ b/js/constants/application_constants.js
@@ -51,6 +51,20 @@ const constants = {
'permissions': ['register', 'edit', 'share', 'del_from_collection'],
'type': 'wallet'
},
+ {
+ 'subdomain': 'lumenus',
+ 'name': 'Lumenus',
+ 'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/lumenus/lumenus-logo.png',
+ 'permissions': ['register', 'edit', 'share', 'del_from_collection'],
+ 'type': 'wallet'
+ },
+ {
+ 'subdomain': '23vivi',
+ 'name': '23VIVI',
+ 'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/23vivi/23vivi-logo.png',
+ 'permissions': ['register', 'edit', 'share', 'del_from_collection'],
+ 'type': 'wallet'
+ },
{
'subdomain': 'portfolioreview',
'name': 'Portfolio Review',
@@ -85,8 +99,18 @@ const constants = {
'Bildrecht GmbH', 'SABAM', 'AUTVIS', 'CREAIMAGEN', 'SONECA', 'Copydan', 'EAU', 'Kuvasto', 'GCA', 'HUNGART',
'IVARO', 'SIAE', 'JASPAR-SPDA', 'AKKA/LAA', 'LATGA-A', 'SOMAAP', 'ARTEGESTION', 'CARIER', 'BONO', 'APSAV',
'SPA', 'GESTOR', 'VISaRTA', 'RAO', 'LITA', 'DALRO', 'VeGaP', 'BUS', 'ProLitteris', 'AGADU', 'AUTORARTE', 'BUBEDRA', 'BBDA', 'BCDA', 'BURIDA', 'ADAVIS', 'BSDA'],
+
'searchThreshold': 500,
+ 'supportedThumbnailFileFormats': [
+ 'x-sgi-movie', 'x-msvideo', 'quicktime', 'mpeg', 'png', 'jpeg', 'gif',
+ 'ogg', 'oga', 'ogv', 'ogx', 'wmv', 'wma', 'flv', '3gpp2', '3p2', '3pg',
+ 'png', 'jpg', 'jpeg', 'gif', '264', '3g', '3g2', '3gp', '3gp2', '3gpp',
+ 'mp4', 'm4a', 'm4v', 'f4v', 'f4a', 'm4b', 'm4r', 'f4b', 'mov', 'quicktime',
+ 'webm', 'x264', 'mpeg', 'mpeg4', 'mpg4', 'bmp', 'eps', 'jp2', 'j2k', 'jpm',
+ 'mj2'
+ ],
+
// in case of whitelabel customization, we store stuff here
'whitelabel': {},
@@ -114,7 +138,12 @@ const constants = {
},
'twitter': {
'sdkUrl': 'https://platform.twitter.com/widgets.js'
- }
+ },
+
+ 'errorMessagesToIgnore': [
+ 'Authentication credentials were not provided.',
+ 'Informations d\'authentification non fournies.'
+ ]
};
export default constants;
diff --git a/js/mixins/react_error.js b/js/mixins/react_error.js
new file mode 100644
index 00000000..14f33a61
--- /dev/null
+++ b/js/mixins/react_error.js
@@ -0,0 +1,16 @@
+'use strict';
+
+import invariant from 'invariant';
+
+const ReactError = {
+ throws(err) {
+ if(!err.handler) {
+ invariant(err.handler, 'Error thrown to ReactError did not have a `handler` function');
+ console.logGlobal('Error thrown to ReactError did not have a `handler` function');
+ } else {
+ err.handler(this, err);
+ }
+ }
+};
+
+export default ReactError;
diff --git a/js/models/errors.js b/js/models/errors.js
new file mode 100644
index 00000000..4573afe4
--- /dev/null
+++ b/js/models/errors.js
@@ -0,0 +1,31 @@
+'use strict';
+
+import React from 'react';
+
+import ErrorNotFoundPage from '../components/error_not_found_page';
+
+
+export class ResourceNotFoundError extends Error {
+ constructor(message) {
+ super(message);
+ this.name = this.constructor.name;
+ this.message = message;
+
+ // `captureStackTrace` might not be available in IE:
+ // - http://stackoverflow.com/a/8460753/1263876
+ if(Error.captureStackTrace) {
+ Error.captureStackTrace(this, this.constructor.name);
+ }
+ }
+
+ handler(component, err) {
+ const monkeyPatchedKey = `_${this.name}MonkeyPatched`;
+
+ if(!component.state[monkeyPatchedKey]) {
+ component.render = () => ;
+ component.setState({
+ [monkeyPatchedKey]: true
+ });
+ }
+ }
+}
diff --git a/js/stores/edition_store.js b/js/stores/edition_store.js
index 14ee4fee..22e78d23 100644
--- a/js/stores/edition_store.js
+++ b/js/stores/edition_store.js
@@ -7,11 +7,17 @@ import EditionActions from '../actions/edition_actions';
class EditionStore {
constructor() {
this.edition = {};
+ this.editionError = null;
this.bindActions(EditionActions);
}
onUpdateEdition(edition) {
this.edition = edition;
+ this.editionError = null;
+ }
+
+ onEditionFailed(error) {
+ this.editionError = error;
}
}
diff --git a/js/stores/global_notification_store.js b/js/stores/global_notification_store.js
index 5a23fe1b..7414812b 100644
--- a/js/stores/global_notification_store.js
+++ b/js/stores/global_notification_store.js
@@ -4,36 +4,63 @@ import { alt } from '../alt';
import GlobalNotificationActions from '../actions/global_notification_actions';
+const GLOBAL_NOTIFICATION_COOLDOWN = 400;
+
class GlobalNotificationStore {
constructor() {
- this.notificationQue = [];
+ this.notificationQueue = [];
+ this.notificationStatus = 'ready';
+ this.notificationsPaused = false;
this.bindActions(GlobalNotificationActions);
}
onAppendGlobalNotification(newNotification) {
- let notificationDelay = 0;
- for(let i = 0; i < this.notificationQue.length; i++) {
- notificationDelay += this.notificationQue[i].dismissAfter;
- }
+ this.notificationQueue.push(newNotification);
- this.notificationQue.push(newNotification);
- setTimeout(GlobalNotificationActions.emulateEmptyStore, notificationDelay + newNotification.dismissAfter);
+ if (!this.notificationsPaused && this.notificationStatus === 'ready') {
+ this.showNextNotification();
+ }
}
- onEmulateEmptyStore() {
- let actualNotificitionQue = this.notificationQue.slice();
+ showNextNotification() {
+ this.notificationStatus = 'show';
- this.notificationQue = [];
+ setTimeout(GlobalNotificationActions.cooldownGlobalNotifications, this.notificationQueue[0].dismissAfter);
+ }
- setTimeout(() => {
- this.notificationQue = actualNotificitionQue.slice();
- GlobalNotificationActions.shiftGlobalNotification();
- }, 400);
+ onCooldownGlobalNotifications() {
+ // When still paused on cooldown, don't shift the queue so we can repeat the current notification.
+ if (!this.notificationsPaused) {
+ this.notificationStatus = 'cooldown';
+
+ // Leave some time between consecutive notifications
+ setTimeout(GlobalNotificationActions.shiftGlobalNotification, GLOBAL_NOTIFICATION_COOLDOWN);
+ } else {
+ this.notificationStatus = 'ready';
+ }
}
onShiftGlobalNotification() {
- this.notificationQue.shift();
+ this.notificationQueue.shift();
+
+ if (!this.notificationsPaused && this.notificationQueue.length > 0) {
+ this.showNextNotification();
+ } else {
+ this.notificationStatus = 'ready';
+ }
+ }
+
+ onPauseGlobalNotifications() {
+ this.notificationsPaused = true;
+ }
+
+ onResumeGlobalNotifications() {
+ this.notificationsPaused = false;
+
+ if (this.notificationStatus === 'ready' && this.notificationQueue.length > 0) {
+ this.showNextNotification();
+ }
}
}
diff --git a/js/stores/piece_store.js b/js/stores/piece_store.js
index ccef50b1..3b04736b 100644
--- a/js/stores/piece_store.js
+++ b/js/stores/piece_store.js
@@ -7,11 +7,13 @@ import PieceActions from '../actions/piece_actions';
class PieceStore {
constructor() {
this.piece = {};
+ this.pieceError = null;
this.bindActions(PieceActions);
}
onUpdatePiece(piece) {
this.piece = piece;
+ this.pieceError = null;
}
onUpdateProperty({key, value}) {
@@ -21,6 +23,10 @@ class PieceStore {
throw new Error('There is no piece defined in PieceStore or the piece object does not have the property you\'re looking for.');
}
}
+
+ onPieceFailed(err) {
+ this.pieceError = err;
+ }
}
export default alt.createStore(PieceStore, 'PieceStore');
diff --git a/js/utils/acl_utils.js b/js/utils/acl_utils.js
index fc3987c1..dd39a380 100644
--- a/js/utils/acl_utils.js
+++ b/js/utils/acl_utils.js
@@ -4,7 +4,7 @@ import { sanitize, intersectLists } from './general_utils';
export function getAvailableAcls(editions, filterFn) {
let availableAcls = [];
- if (!editions || editions.constructor !== Array){
+ if (!editions || editions.constructor !== Array) {
return [];
}
// if you copy a javascript array of objects using slice, then
@@ -33,23 +33,23 @@ export function getAvailableAcls(editions, filterFn) {
});
// If no edition has been selected, availableActions is empty
- // If only one edition has been selected, their actions are available
- // If more than one editions have been selected, their acl properties are intersected
- if(editionsCopy.length >= 1) {
+ // If only one edition has been selected, its actions are available
+ // If more than one editions have been selected, intersect all their acl properties
+ if (editionsCopy.length >= 1) {
availableAcls = editionsCopy[0].acl;
- }
- if(editionsCopy.length >= 2) {
- for(let i = 1; i < editionsCopy.length; i++) {
- availableAcls = intersectLists(availableAcls, editionsCopy[i].acl);
+
+ if (editionsCopy.length >= 2) {
+ for (let i = 1; i < editionsCopy.length; i++) {
+ availableAcls = intersectLists(availableAcls, editionsCopy[i].acl);
+ }
}
}
// convert acls back to key-value object
let availableAclsObj = {};
- for(let i = 0; i < availableAcls.length; i++) {
+ for (let i = 0; i < availableAcls.length; i++) {
availableAclsObj[availableAcls[i]] = true;
}
-
return availableAclsObj;
-}
\ No newline at end of file
+}
diff --git a/js/utils/error_utils.js b/js/utils/error_utils.js
index 4e9de6e2..753bbf61 100644
--- a/js/utils/error_utils.js
+++ b/js/utils/error_utils.js
@@ -13,8 +13,8 @@ import AppConstants from '../constants/application_constants';
* @param {boolean} ignoreSentry Defines whether or not the error should be submitted to Sentry
* @param {string} comment Will also be submitted to Sentry, but will not be logged
*/
-function logGlobal(error, ignoreSentry, comment) {
-
+function logGlobal(error, ignoreSentry = AppConstants.errorMessagesToIgnore.indexOf(error.message) > -1,
+ comment) {
console.error(error);
if(!ignoreSentry) {
diff --git a/js/utils/file_utils.js b/js/utils/file_utils.js
index 3454404a..9c1423b3 100644
--- a/js/utils/file_utils.js
+++ b/js/utils/file_utils.js
@@ -84,4 +84,21 @@ export function computeHashOfFile(file) {
loadNext();
});
+}
+
+/**
+ * Extracts a file extension from a string, by splitting by dot and taking
+ * the last substring
+ *
+ * If a file without an extension is submitted (file), then
+ * this method just returns an empty string.
+ * @param {string} s file's name + extension
+ * @return {string} file extension
+ *
+ * Via: http://stackoverflow.com/a/190878/1263876
+ */
+export function extractFileExtensionFromString(s) {
+ const explodedFileName = s.split('.');
+ return explodedFileName.length > 1 ? explodedFileName.pop()
+ : '';
}
\ No newline at end of file
diff --git a/js/utils/form_utils.js b/js/utils/form_utils.js
index c15eb067..8d12a8c1 100644
--- a/js/utils/form_utils.js
+++ b/js/utils/form_utils.js
@@ -2,6 +2,8 @@
import { getLangText } from './lang_utils';
+import AppConstants from '../constants/application_constants';
+
/**
* Get the data ids of the given piece or editions.
* @param {boolean} isPiece Is the given entities parameter a piece? (False: array of editions)
@@ -70,6 +72,10 @@ export function getAclFormMessage(options) {
throw new Error('Your specified aclName did not match a an acl class.');
}
+ if (options.additionalMessage) {
+ message += '\n\n' + options.additionalMessage;
+ }
+
if (options.senderName) {
message += '\n\n';
message += getLangText('Truly yours,');
diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js
index e717fa75..e81a806d 100644
--- a/js/utils/general_utils.js
+++ b/js/utils/general_utils.js
@@ -1,5 +1,11 @@
'use strict';
+/**
+ * Checks shallow equality
+ * Re-export of shallow from shallow-equals
+ */
+export { default as isShallowEqual } from 'shallow-equals';
+
/**
* Takes an object and returns a shallow copy without any keys
* that fail the passed in filter function.
@@ -109,7 +115,7 @@ function _doesObjectListHaveDuplicates(l) {
export function mergeOptions(...l) {
// If the objects submitted in the list have duplicates,in their key names,
// abort the merge and tell the function's user to check his objects.
- if(_doesObjectListHaveDuplicates(l)) {
+ if (_doesObjectListHaveDuplicates(l)) {
throw new Error('The objects you submitted for merging have duplicates. Merge aborted.');
}
diff --git a/js/utils/regex_utils.js b/js/utils/regex_utils.js
new file mode 100644
index 00000000..af948b2b
--- /dev/null
+++ b/js/utils/regex_utils.js
@@ -0,0 +1,7 @@
+'use strict'
+
+export function isEmail(string) {
+ // This is a bit of a weak test for an email, but you really can't win them all
+ // http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address
+ return !!string && string.match(/.*@.*\..*/);
+}
diff --git a/js/utils/requests.js b/js/utils/requests.js
index a7300634..9195661d 100644
--- a/js/utils/requests.js
+++ b/js/utils/requests.js
@@ -30,6 +30,15 @@ class Requests {
reject(error);
} else if(body && body.detail) {
reject(new Error(body.detail));
+ } else if(!body.success) {
+ let error = new Error('Client Request Error');
+ error.json = {
+ status: response.status,
+ statusText: response.statusText,
+ type: response.type,
+ url: response.url
+ };
+ reject(error);
} else {
resolve(body);
}
@@ -100,8 +109,7 @@ class Requests {
return newUrl;
}
- request(verb, url, options) {
- options = options || {};
+ request(verb, url, options = {}) {
let merged = Object.assign({}, this.httpOptions, options);
let csrftoken = getCookie(AppConstants.csrftoken);
if (csrftoken) {
@@ -129,13 +137,10 @@ class Requests {
}
_putOrPost(url, paramsAndBody, method) {
- let paramsCopy = Object.assign({}, paramsAndBody);
let params = omitFromObject(paramsAndBody, ['body']);
let newUrl = this.prepareUrl(url, params);
- let body = null;
- if (paramsCopy && paramsCopy.body) {
- body = JSON.stringify(paramsCopy.body);
- }
+ let body = paramsAndBody && paramsAndBody.body ? JSON.stringify(paramsAndBody.body)
+ : null;
return this.request(method, newUrl, { body });
}
diff --git a/package.json b/package.json
index 9e1f624c..be5c1202 100644
--- a/package.json
+++ b/package.json
@@ -85,6 +85,7 @@
"react-star-rating": "~1.3.2",
"react-textarea-autosize": "^2.5.2",
"reactify": "^1.1.0",
+ "shallow-equals": "0.0.0",
"shmui": "^0.1.0",
"spark-md5": "~1.0.0",
"uglifyjs": "^2.4.10",
diff --git a/sass/ascribe-fonts/ascribe-fonts.scss b/sass/ascribe-fonts/ascribe-fonts.scss
index 8b66627d..bf2f113f 100644
--- a/sass/ascribe-fonts/ascribe-fonts.scss
+++ b/sass/ascribe-fonts/ascribe-fonts.scss
@@ -1,272 +1,78 @@
+/*
+ These glyphs are generated using: https://icomoon.io
+ Even though it seems radically complicated, check out the site,
+ its fairly straight forward.
+
+ If someone wants you to add a new glyph go to the site,
+ drop in the regular ascribe-logo font and select all icons.
+ Then also add the new glyph, name and address it correctly and download
+ the font again.
+
+ Once you replace all the fonts they gave you, you're done.
+ */
+
@font-face {
- font-family: 'ascribe';
- src: url('#{$BASE_URL}static/fonts/ascribe.eot?-oi6ttk');
- src: url('#{$BASE_URL}static/fonts/ascribe.eot?#iefix-oi6ttk') format('embedded-opentype'),
- url('#{$BASE_URL}static/fonts/ascribe.woff?-oi6ttk') format('woff'),
- url('#{$BASE_URL}static/fonts/ascribe.ttf?-oi6ttk') format('truetype'),
- url('#{$BASE_URL}static/fonts/ascribe.svg?-oi6ttk#ascribe') format('svg');
- font-weight: normal;
- font-style: normal;
+ font-family: 'ascribe-font';
+ src:url('#{$BASE_URL}static/fonts/ascribe-logo.eot?q6qoae');
+ src:url('#{$BASE_URL}static/fonts/ascribe-logo.eot?q6qoae#iefix') format('embedded-opentype'),
+ url('#{$BASE_URL}static/fonts/ascribe-logo.ttf?q6qoae') format('truetype'),
+ url('#{$BASE_URL}static/fonts/ascribe-logo.woff?q6qoae') format('woff'),
+ url('#{$BASE_URL}static/fonts/ascribe-logo.svg?q6qoae#ascribe-logo') format('svg');
+ font-weight: normal;
+ font-style: normal;
}
[class^="icon-ascribe-"], [class*=" icon-ascribe-"] {
- font-family: 'ascribe-logo';
- speak: none;
- font-style: normal;
- font-weight: normal;
- font-variant: normal;
- text-transform: none;
- line-height: 1;
+ font-family: 'ascribe-font';
+ speak: none;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
- /* Better Font Rendering =========== */
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-@font-face {
- font-family: 'ascribe-logo';
- src:url('#{$BASE_URL}static/fonts/ascribe-logo.eot?q6qoae');
- src:url('#{$BASE_URL}static/fonts/ascribe-logo.eot?q6qoae#iefix') format('embedded-opentype'),
- url('#{$BASE_URL}static/fonts/ascribe-logo.ttf?q6qoae') format('truetype'),
- url('#{$BASE_URL}static/fonts/ascribe-logo.woff?q6qoae') format('woff'),
- url('#{$BASE_URL}static/fonts/ascribe-logo.svg?q6qoae#ascribe-logo') format('svg');
- font-weight: normal;
- font-style: normal;
-}
-
-[class^="icon-ascribe-"], [class*=" icon-ascribe-"] {
- font-family: 'ascribe-logo';
- speak: none;
- font-style: normal;
- font-weight: normal;
- font-variant: normal;
- text-transform: none;
- line-height: 1;
-
- /* Better Font Rendering =========== */
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
+ /* Better Font Rendering =========== */
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
.icon-ascribe-add:before {
- content: "\e800";
+ content: "\e800";
}
.icon-ascribe-sort:before {
- content: "\e801";
+ content: "\e801";
}
.icon-ascribe-search:before {
- content: "\e802";
+ content: "\e802";
}
.icon-ascribe-filter:before {
- content: "\e803";
+ content: "\e803";
}
.icon-ascribe-add-white:before {
- content: "\e804";
+ content: "\e804";
}
.icon-ascribe-add-blue .path1:before {
- content: "\e805";
- color: rgb(0, 60, 106);
+ content: "\e805";
+ color: rgb(0, 60, 106);
}
.icon-ascribe-add-blue .path2:before {
- content: "\e806";
- margin-left: -1em;
- color: rgb(255, 255, 255);
+ content: "\e806";
+ margin-left: -1em;
+ color: rgb(255, 255, 255);
}
.icon-ascribe-icon:before {
- content: "\e807";
+ content: "\e807";
}
.icon-ascribe-logo:before {
- content: "\e808";
+ content: "\e808";
}
-
-
-.glyph-ascribe-logo-spool:before {
- content: "\e600";
-}
-
-.glyph-ascribe-spool:before {
- content: "\e601";
-}
-
-.glyph-ascribe-logo-spool-chunked:before {
- content: "\e602";
-}
-
-.glyph-ascribe-spool-chunked:before {
- content: "\e603";
-}
-
-.glyph-ascribe-home:before {
- content: "\e900";
-}
-
-.glyph-ascribe-home2:before {
- content: "\e901";
-}
-
-.glyph-ascribe-home3:before {
- content: "\e902";
-}
-
-.glyph-ascribe-pencil:before {
- content: "\e905";
-}
-
-.glyph-ascribe-pencil2:before {
- content: "\e906";
-}
-
-.glyph-ascribe-quill:before {
- content: "\e907";
-}
-
-.glyph-ascribe-image:before {
- content: "\e90d";
-}
-
-.glyph-ascribe-camera:before {
- content: "\e90f";
-}
-
-.glyph-ascribe-music:before {
- content: "\e911";
-}
-
-.glyph-ascribe-play:before {
- content: "\e912";
-}
-
-.glyph-ascribe-film:before {
- content: "\e913";
-}
-
-.glyph-ascribe-credit-card:before {
- content: "\e93f";
-}
-
-.glyph-ascribe-pushpin:before {
- content: "\e946";
-}
-
-.glyph-ascribe-undo2:before {
- content: "\e967";
-}
-
-.glyph-ascribe-redo2:before {
- content: "\e968";
-}
-
-.glyph-ascribe-enlarge:before {
- content: "\e989";
-}
-
-.glyph-ascribe-shrink:before {
- content: "\e98a";
-}
-
-.glyph-ascribe-enlarge2:before {
- content: "\e98b";
-}
-
-.glyph-ascribe-shrink2:before {
- content: "\e98c";
-}
-
-.glyph-ascribe-share:before {
- content: "\ea7d";
-}
-
-.glyph-ascribe-new-tab:before {
- content: "\ea7e";
-}
-
-.glyph-ascribe-share2:before {
- content: "\ea82";
-}
-
-.glyph-ascribe-google:before {
- content: "\ea87";
-}
-
-.glyph-ascribe-google-plus:before {
- content: "\ea88";
-}
-
-.glyph-ascribe-google-plus2:before {
- content: "\ea89";
-}
-
-.glyph-ascribe-facebook:before {
- content: "\ea8c";
-}
-
-.glyph-ascribe-facebook2:before {
- content: "\ea8d";
-}
-
-.glyph-ascribe-twitter:before {
- content: "\ea91";
-}
-
-.glyph-ascribe-twitter2:before {
- content: "\ea92";
-}
-
-.glyph-ascribe-youtube3:before {
- content: "\ea99";
-}
-
-.glyph-ascribe-dropbox:before {
- content: "\eaaf";
-}
-
-.glyph-ascribe-file-pdf:before {
- content: "\eada";
-}
-
-.glyph-ascribe-chrome:before {
- content: "\eae5";
-}
-
-.glyph-ascribe-firefox:before {
- content: "\eae6";
-}
-
-.glyph-ascribe-IE:before {
- content: "\eae7";
-}
-
-.glyph-ascribe-opera:before {
- content: "\eae8";
-}
-
-.glyph-ascribe-safari:before {
- content: "\eae9";
+.icon-ascribe-ok:before {
+ content: "\e809";
+ font-size: .7em;
}
.btn-glyph-ascribe {
- font-size: 18px;
- padding: 4px 12px 0 10px
-}
-
-.ascribe-logo-circle {
- border: 6px solid #F6F6F6;
- border-radius: 10em;
- position: relative;
- top: 10%;
- left: 10%;
-
- display: block;
- width: 80%;
- height: 80%;
-
- > span {
- color: #F6F6F6;
- position: absolute;
- top: -.29em;
- left: .16em;
-
- font-size: 5em;
- font-weight: normal;
- }
+ font-size: 18px;
+ padding: 4px 12px 0 10px
}
\ No newline at end of file
diff --git a/sass/ascribe_accordion_list.scss b/sass/ascribe_accordion_list.scss
index c0b81096..791743fc 100644
--- a/sass/ascribe_accordion_list.scss
+++ b/sass/ascribe_accordion_list.scss
@@ -60,6 +60,34 @@ $ascribe-accordion-list-item-height: 100px;
background-size: cover;
}
+ .ascribe-logo-circle {
+ border: 6px solid #F6F6F6;
+ border-radius: 10em;
+ position: relative;
+ top: 10%;
+ left: 10%;
+
+ display: block;
+ width: 80%;
+ height: 80%;
+
+ > span {
+ color: #F6F6F6;
+ position: absolute;
+ top: -.29em;
+ left: .16em;
+
+ font-size: 5em;
+ font-weight: normal;
+ }
+ }
+
+ .ascribe-thumbnail-placeholder {
+ color: #F6F6F6;
+ font-size: 5em;
+ font-weight: normal;
+ }
+
//&::before {
// content: ' ';
// display: inline-block;
@@ -211,10 +239,6 @@ $ascribe-accordion-list-item-height: 100px;
-ms-user-select: none;
-webkit-user-select: none;
- &:hover {
- color: $ascribe-dark-blue;
- }
-
.glyphicon {
font-size: .8em;
top: 1px !important;
diff --git a/sass/ascribe_acl_information.scss b/sass/ascribe_acl_information.scss
index 063c8ae6..5a4708f0 100644
--- a/sass/ascribe_acl_information.scss
+++ b/sass/ascribe_acl_information.scss
@@ -22,4 +22,4 @@
.example {
color: #616161;
}
-}
\ No newline at end of file
+}
diff --git a/sass/ascribe_custom_style.scss b/sass/ascribe_custom_style.scss
index 98cce937..96b97783 100644
--- a/sass/ascribe_custom_style.scss
+++ b/sass/ascribe_custom_style.scss
@@ -68,10 +68,15 @@ hr {
.dropdown-menu {
background-color: $ascribe--nav-bg-color;
}
+ .navbar-nav > li > .dropdown-menu {
+ padding: 0;
+ }
.dropdown-menu > li > a {
color: $ascribe--nav-fg-prim-color;
font-weight: $ascribe--font-weight-light;
+ padding-bottom: 9px;
+ padding-top: 9px;
}
.dropdown-menu > li > a:hover,
@@ -79,6 +84,10 @@ hr {
background-color: rgba($ascribe--button-default-color, .05);
}
+ .dropdown-menu > .divider {
+ margin: 0;
+ }
+
.notification-menu {
.dropdown-menu {
background-color: white;
@@ -257,6 +266,24 @@ hr {
font-weight: $ascribe--font-weight-light;
}
+.btn-default {
+ background-color: $ascribe--button-default-color;
+ border-color: $ascribe--button-default-color;
+
+ &:hover,
+ &:active,
+ &:focus,
+ &:active:hover,
+ &:active:focus,
+ &:active.focus,
+ &.active:hover,
+ &.active:focus,
+ &.active.focus {
+ background-color: lighten($ascribe--button-default-color, 20%);
+ border-color: lighten($ascribe--button-default-color, 20%);
+ }
+}
+
// disabled buttons
.btn-default.disabled,
.btn-default.disabled:hover,
@@ -280,9 +307,10 @@ fieldset[disabled] .btn-default.active {
border-color: darken($ascribe--button-default-color, 20%);
}
-.btn-default {
- background-color: $ascribe--button-default-color;
- border-color: $ascribe--button-default-color;
+.btn-secondary {
+ background-color: $ascribe--button-secondary-bg-color;
+ border-color: $ascribe--button-secondary-fg-color;
+ color: $ascribe--button-secondary-fg-color;
&:hover,
&:active,
@@ -293,8 +321,9 @@ fieldset[disabled] .btn-default.active {
&.active:hover,
&.active:focus,
&.active.focus {
- background-color: lighten($ascribe--button-default-color, 20%);
- border-color: lighten($ascribe--button-default-color, 20%);
+ background-color: $ascribe--button-secondary-fg-color;
+ border-color: $ascribe--button-secondary-bg-color;
+ color: $ascribe--button-secondary-bg-color;
}
}
@@ -322,26 +351,6 @@ fieldset[disabled] .btn-secondary.active {
color: darken($ascribe--button-secondary-fg-color, 20%);
}
-.btn-secondary {
- background-color: $ascribe--button-secondary-bg-color;
- border-color: $ascribe--button-secondary-fg-color;
- color: $ascribe--button-secondary-fg-color;
-
- &:hover,
- &:active,
- &:focus,
- &:active:hover,
- &:active:focus,
- &:active.focus,
- &.active:hover,
- &.active:focus,
- &.active.focus {
- background-color: $ascribe--button-secondary-fg-color;
- border-color: $ascribe--button-secondary-bg-color;
- color: $ascribe--button-secondary-bg-color;
- }
-}
-
.btn-tertiary {
background-color: transparent;
border-color: transparent;
@@ -580,11 +589,6 @@ fieldset[disabled] .btn-secondary.active {
background-color: lighten($ascribe--button-default-color, 20%);
}
-// uploader
-.ascribe-progress-bar > .progress-bar {
- background-color: lighten($ascribe--button-default-color, 20%);
-}
-
.action-file {
text-shadow: -1px 0 black,
0 1px black,
diff --git a/sass/ascribe_notification_list.scss b/sass/ascribe_notification_list.scss
index a09f7049..b5f46a4c 100644
--- a/sass/ascribe_notification_list.scss
+++ b/sass/ascribe_notification_list.scss
@@ -2,8 +2,9 @@ $break-small: 764px;
$break-medium: 991px;
$break-medium: 1200px;
-.notification-header,.notification-wrapper {
- width: 350px;
+.notification-header, .notification-wrapper {
+ min-width: 350px;
+ width: 100%;
}
.notification-header {
@@ -81,4 +82,4 @@ $break-medium: 1200px;
border: 1px solid #cccccc;
background-color: white;
margin-top: 1px;
-}
\ No newline at end of file
+}
diff --git a/sass/ascribe_notification_page.scss b/sass/ascribe_notification_page.scss
index 955609d2..7bb37446 100644
--- a/sass/ascribe_notification_page.scss
+++ b/sass/ascribe_notification_page.scss
@@ -31,16 +31,11 @@
margin-top: .5em;
margin-bottom: 1em;
- .loan-form {
- margin-top: .5em;
+ &.embed-form {
height: 45vh;
}
}
- .loan-form {
- height: 40vh;
- }
-
.notification-contract-pdf-download {
text-align: left;
margin-left: 1em;
@@ -69,4 +64,8 @@
padding-left: 0;
width: 100%;
}
+}
+
+.ascribe-property.contract-appendix-form {
+ padding-left: 0;
}
\ No newline at end of file
diff --git a/sass/ascribe_panel.scss b/sass/ascribe_panel.scss
index 09d773ad..f4b70a80 100644
--- a/sass/ascribe_panel.scss
+++ b/sass/ascribe_panel.scss
@@ -1,3 +1,8 @@
+.panel {
+ /* Here we are overriding bootstrap to show the is-focused background color */
+ background-color: transparent;
+}
+
.ascribe-panel-wrapper {
border: 1px solid #ddd;
height: 5em;
@@ -26,7 +31,7 @@
vertical-align: middle;
&:first-child {
- word-break: break-all;
+ word-break: break-word;
font-size: .9em;
}
}
diff --git a/sass/ascribe_piece_list_toolbar.scss b/sass/ascribe_piece_list_toolbar.scss
index f033ee81..06cbd1a7 100644
--- a/sass/ascribe_piece_list_toolbar.scss
+++ b/sass/ascribe_piece_list_toolbar.scss
@@ -81,4 +81,8 @@
top: 2px;
}
}
+
+ .dropdown-menu {
+ min-width: 170px;
+ }
}
diff --git a/sass/ascribe_property.scss b/sass/ascribe_property.scss
index 0bfd5496..d214c57e 100644
--- a/sass/ascribe_property.scss
+++ b/sass/ascribe_property.scss
@@ -89,6 +89,10 @@ $ascribe-red-error: rgb(169, 68, 66);
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
+ > .file-drag-and-drop {
+ margin-top: .5em;
+ }
+
> p {
height: 20px;
margin-bottom: 0;
@@ -166,7 +170,7 @@ $ascribe-red-error: rgb(169, 68, 66);
.ascribe-property-collapsible-toggle {
border-top: 1px solid rgba(0, 0, 0, .05);
display: inline-block;
- padding: .5em 1.5em;
+ padding: .75em 0 .75em 1.5em;
text-align: left;
width: 100%;
}
diff --git a/sass/ascribe_spinner.scss b/sass/ascribe_spinner.scss
index 7f02a383..543b1463 100644
--- a/sass/ascribe_spinner.scss
+++ b/sass/ascribe_spinner.scss
@@ -52,6 +52,13 @@ $ascribe--spinner-size-sm: 15px;
}
}
+.spinner-wrapper-white {
+ color: $ascribe-white;
+ .spinner-circle {
+ border-color: $ascribe-white;
+ }
+}
+
.spinner-wrapper-lg {
width: $ascribe--spinner-size-lg;
height: $ascribe--spinner-size-lg;
@@ -146,4 +153,4 @@ $ascribe--spinner-size-sm: 15px;
40% { color: $ascribe-blue; }
60% { color: $ascribe-light-blue; }
80% { color: $ascribe-pink; }
-}
\ No newline at end of file
+}
diff --git a/sass/ascribe_uploader.scss b/sass/ascribe_uploader.scss
index 49bc70e9..e6dd66cc 100644
--- a/sass/ascribe_uploader.scss
+++ b/sass/ascribe_uploader.scss
@@ -3,19 +3,12 @@
cursor: default !important;
display: block;
height: auto;
- margin-top: 1em;
+ /* margin-top: 1em; */
outline: 1px dashed #9e9e9e;
overflow: auto;
text-align: center;
vertical-align: middle;
- padding: 1.5em 0 1.5em 0;
-
- .file-drag-and-drop-dialog > p:first-child {
- font-size: 1.5em !important;
- margin-bottom: 0;
- margin-top: 0;
- padding-bottom: 0;
- }
+ padding-top: 1.5em;
}
.inactive-dropzone {
@@ -34,21 +27,19 @@
}
}
-.file-drag-and-drop-preview-iterator {
- margin: 2.5em 0 0 0;
- text-align: right;
-
- > div:first-child {
- text-align: center;
- }
-}
-
-.file-drag-and-drop-preview-iterator-spacing {
- margin-bottom: 1em;
-}
-
.file-drag-and-drop-dialog {
margin: 1.5em 0 1.5em 0;
+
+ > p:first-child {
+ font-size: 1.5em !important;
+ margin-bottom: 0;
+ margin-top: 0;
+ padding-bottom: 0;
+ }
+
+ > .btn {
+ margin-bottom: 2em;
+ }
}
.file-drag-and-drop-hashing-dialog {
@@ -107,6 +98,7 @@
cursor: default;
overflow: hidden;
}
+
.action-file {
color: white;
cursor: pointer;
@@ -125,6 +117,13 @@
&:hover {
color: #d9534f;
}
+
+ &.icon-ascribe-ok, &.icon-ascribe-ok:hover {
+ cursor: default;
+ color: $ascribe-dark-blue;
+ font-size: 4.2em;
+ top: .2em;
+ }
}
.spinner-file {
@@ -139,12 +138,16 @@
text-align: center;
width: 104px;
- .action-file, .spinner-file {
+ .action-file, .spinner-file, .icon-ascribe-ok {
+ margin-top: 1em;
+ line-height: 1.3;
+ }
+
+ .ascribe-progress-bar + .action-file, .ascribe-progress-bar + .spinner-file {
margin-top: .6em;
}
}
-
.file-drag-and-drop-preview-other {
display: table-cell;
text-align: center;
@@ -154,7 +157,7 @@
margin-top: 5px;
}
- .action-file, .spinner-file {
+ .action-file:not(.icon-ascribe-ok), .spinner-file {
margin-top: 0;
position: relative;
top: .3em;
@@ -171,6 +174,15 @@
> .progress-bar {
background-color: $ascribe-dark-blue;
}
+
+ span {
+ font-size: 1.2em;
+ color: white;
+ text-shadow: -1px 0 $ascribe--button-default-color,
+ 0 1px $ascribe--button-default-color,
+ 1px 0 $ascribe--button-default-color,
+ 0 -1px $ascribe--button-default-color;
+ }
}
.ascribe-progress-bar-xs {
diff --git a/sass/main.scss b/sass/main.scss
index 8a732e96..5cc91e9a 100644
--- a/sass/main.scss
+++ b/sass/main.scss
@@ -350,7 +350,7 @@ hr {
> span {
font-size: 1.1em;
- font-weight: 600;
+ font-weight: normal;
color: #616161;
padding-left: .3em;
diff --git a/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss b/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss
new file mode 100644
index 00000000..a5026272
--- /dev/null
+++ b/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss
@@ -0,0 +1,377 @@
+/** Sass cannot use a number as the first character of a variable, so we'll have to settle with vivi23 **/
+$vivi23--fg-color: black;
+$vivi23--bg-color: white;
+$vivi23--nav-fg-prim-color: $vivi23--fg-color;
+$vivi23--nav-fg-sec-color: #3a3a3a;
+$vivi23--nav-bg-color: $vivi23--bg-color;
+$vivi23--nav-highlight-color: #f8f8f8;
+$vivi23--button-default-color: $vivi23--fg-color;
+$vivi23--highlight-color: #de2600;
+
+
+.client--23vivi {
+ /** Landing page **/
+ .route--landing {
+ display: table;
+
+ > .container {
+ display: table-cell;
+ padding-bottom: 100px;
+ vertical-align: middle;
+ }
+
+ .vivi23-landing {
+ font-weight: normal;
+ text-align: center;
+ }
+
+ .vivi23-landing--header {
+ background-color: $vivi23--fg-color;
+ border: 1px solid $vivi23--fg-color;
+ color: $vivi23--bg-color;
+ padding: 2em;
+
+ .vivi23-landing--header-logo {
+ margin-top: 1em;
+ margin-bottom: 2em;
+ height: 75px;
+ }
+ }
+
+ .vivi23-landing--content {
+ border: 1px solid darken($vivi23--bg-color, 20%);
+ border-top: none;
+ padding: 2em;
+ }
+ }
+
+ /** Navbar **/
+ .navbar-default {
+ background-color: $vivi23--nav-fg-prim-color;
+
+ .navbar-brand .icon-ascribe-logo {
+ color: $vivi23--bg-color;
+ &:hover {
+ color: darken($vivi23--bg-color, 20%);
+ }
+ }
+
+ }
+
+ .navbar-nav > li > a,
+ .navbar-nav > li > a:focus,
+ .navbar-nav > li > .active a,
+ .navbar-nav > li > .active a:focus {
+ color: $vivi23--nav-bg-color;
+ }
+
+ .navbar-nav > li > a:hover {
+ color: darken($vivi23--nav-bg-color, 20%);
+ }
+
+ .navbar-nav > .active a,
+ .navbar-nav > .active a:hover,
+ .navbar-nav > .active a:focus {
+ background-color: $vivi23--nav-fg-prim-color;
+ border-bottom-color: $vivi23--nav-bg-color;
+ color: $vivi23--nav-bg-color;
+ }
+
+ .navbar-nav > .open > a,
+ .dropdown-menu > .active > a,
+ .dropdown-menu > li > a {
+ color: $vivi23--nav-fg-prim-color;
+ background-color: $vivi23--nav-bg-color;
+ }
+
+ .navbar-nav > .open > a:hover,
+ .navbar-nav > .open > a:focus,
+ .dropdown-menu > .active > a:hover,
+ .dropdown-menu > .active > a:focus,
+ .dropdown-menu > li > a:hover,
+ .dropdown-menu > li > a:focus {
+ color: lighten($vivi23--nav-fg-prim-color, 20%);
+ background-color: $vivi23--nav-highlight-color;
+ }
+
+ .navbar-collapse.collapsing,
+ .navbar-collapse.collapse.in {
+ background-color: $vivi23--nav-bg-color;
+
+ .navbar-nav > .open > a,
+ .navbar-nav > .active > a {
+ background-color: $vivi23--nav-highlight-color;
+ }
+ }
+
+ .navbar-collapse.collapsing li a,
+ .navbar-collapse.collapse.in li a {
+ color: $vivi23--nav-fg-prim-color;
+ }
+ .navbar-collapse.collapse.in li a:not(.ascribe-powered-by):hover {
+ color: lighten($vivi23--nav-fg-prim-color, 20%);
+ background-color: $vivi23--nav-highlight-color;
+ }
+
+ .navbar-toggle {
+ border-color: $vivi23--highlight-color;
+
+ .icon-bar {
+ background-color: $vivi23--highlight-color;
+ }
+ }
+
+ .navbar-toggle:hover,
+ .navbar-toggle:focus {
+ border-color: lighten($vivi23--highlight-color, 10%);
+ background-color: $vivi23--nav-fg-prim-color;
+
+ .icon-bar {
+ background-color: lighten($vivi23--highlight-color, 10%);
+ }
+ }
+
+ .notification-menu {
+ .dropdown-menu {
+ background-color: $vivi23--nav-bg-color;
+ }
+
+ .notification-header {
+ background-color: $vivi23--nav-fg-sec-color;
+ border-top-width: 0;
+ color: $vivi23--nav-bg-color;
+ }
+
+ .notification-action {
+ color: $vivi23--highlight-color;
+ }
+ }
+
+ /** Buttons **/
+ // reset disabled button styling for btn-default
+ .btn-default.disabled,
+ .btn-default.disabled:hover,
+ .btn-default.disabled:focus,
+ .btn-default.disabled.focus,
+ .btn-default.disabled:active,
+ .btn-default.disabled.active,
+ .btn-default[disabled],
+ .btn-default[disabled]:hover,
+ .btn-default[disabled]:focus,
+ .btn-default[disabled].focus,
+ .btn-default[disabled]:active,
+ .btn-default[disabled].active,
+ fieldset[disabled] .btn-default,
+ fieldset[disabled] .btn-default:hover,
+ fieldset[disabled] .btn-default:focus,
+ fieldset[disabled] .btn-default.focus,
+ fieldset[disabled] .btn-default:active,
+ fieldset[disabled] .btn-default.active {
+ background-color: lighten($vivi23--button-default-color, 30%);
+ border-color: lighten($vivi23--button-default-color, 30%);
+ }
+
+ .btn-default {
+ background-color: $vivi23--button-default-color;
+ border-color: $vivi23--button-default-color;
+
+ &:hover,
+ &:active,
+ &:focus,
+ &:active:hover,
+ &:active:focus,
+ &:active.focus,
+ &.active:hover,
+ &.active:focus,
+ &.active.focus {
+ background-color: lighten($vivi23--button-default-color, 30%);
+ border-color: lighten($vivi23--button-default-color, 30%);
+ }
+ }
+
+ // disabled buttons
+ .btn-secondary.disabled,
+ .btn-secondary.disabled:hover,
+ .btn-secondary.disabled:focus,
+ .btn-secondary.disabled.focus,
+ .btn-secondary.disabled:active,
+ .btn-secondary.disabled.active,
+ .btn-secondary[disabled],
+ .btn-secondary[disabled]:hover,
+ .btn-secondary[disabled]:focus,
+ .btn-secondary[disabled].focus,
+ .btn-secondary[disabled]:active,
+ .btn-secondary[disabled].active,
+ fieldset[disabled] .btn-secondary,
+ fieldset[disabled] .btn-secondary:hover,
+ fieldset[disabled] .btn-secondary:focus,
+ fieldset[disabled] .btn-secondary.focus,
+ fieldset[disabled] .btn-secondary:active,
+ fieldset[disabled] .btn-secondary.active {
+ background-color: darken($vivi23--bg-color, 20%);
+ border-color: $vivi23--button-default-color;
+ }
+
+ .btn-secondary {
+ border-color: $vivi23--button-default-color;
+ color: $vivi23--button-default-color;
+
+ &:hover,
+ &:active,
+ &:focus,
+ &:active:hover,
+ &:active:focus,
+ &:active.focus,
+ &.active:hover,
+ &.active:focus,
+ &.active.focus {
+ background-color: $vivi23--button-default-color;
+ border-color: $vivi23--button-default-color;
+ color: $vivi23--bg-color;
+ }
+ }
+
+ .btn-tertiary {
+ &:hover,
+ &:active,
+ &ctive:hover,
+ &.active:hover{
+ background-color: $vivi23--highlight-color;
+ border-color: $vivi23--highlight-color;
+ color: $vivi23--highlight-color;
+ }
+ }
+
+ /** Other components **/
+ .ascribe-piece-list-toolbar .btn-ascribe-add {
+ display: none;
+ }
+
+ .ascribe-footer {
+ display: none;
+ }
+
+ .ascribe-accordion-list-table-toggle:hover {
+ color: $vivi23--fg-color;
+ }
+
+ .request-action-badge {
+ color: $vivi23--fg-color;
+ }
+
+ .acl-information-dropdown-list .title {
+ color: $vivi23--fg-color;
+ }
+
+ // filter widget
+ .ascribe-piece-list-toolbar-filter-widget button {
+ background-color: transparent !important;
+ border-color: transparent !important;
+ color: $vivi23--button-default-color !important;
+
+ &:hover,
+ &:active {
+ background-color: $vivi23--button-default-color !important;
+ border-color: $vivi23--button-default-color !important;
+ color: $vivi23--bg-color !important;
+ }
+ }
+
+ .icon-ascribe-search {
+ color: $vivi23--fg-color;
+ }
+
+ // forms
+ .ascribe-property-wrapper:hover {
+ border-left-color: rgba($vivi23--fg-color, 0.5);
+ }
+
+ .ascribe-property textarea,
+ .ascribe-property input,
+ .search-bar > .form-group > .input-group input {
+ &::-webkit-input-placeholder {
+ color: rgba($vivi23--fg-color, 0.5);
+ }
+ &::-moz-placeholder {
+ color: rgba($vivi23--fg-color, 0.5);
+ }
+ &:-ms-input-placeholder {
+ color: rgba($vivi23--fg-color, 0.5);
+ }
+ &:-moz-placeholder {
+ color: rgba($vivi23--fg-color, 0.5);
+ }
+ }
+
+ .ascribe-property {
+ > div,
+ > input,
+ > pre,
+ > select,
+ > span:not(.glyphicon),
+ > p,
+ > p > span,
+ > textarea {
+ color: $vivi23--fg-color;
+ }
+ }
+
+ // global notification
+ .ascribe-global-notification-success {
+ background-color: lighten($vivi23--fg-color, 20%);
+ }
+
+ // uploader progress
+ .ascribe-progress-bar > .progress-bar {
+ background-color: lighten($vivi23--fg-color, 20%);
+ }
+
+ .ascribe-progress-bar span {
+ text-shadow: -1px 0 lighten($vivi23--fg-color, 20%),
+ 0 1px lighten($vivi23--fg-color, 20%),
+ 1px 0 lighten($vivi23--fg-color, 20%),
+ 0 -1px lighten($vivi23--fg-color, 20%);
+ }
+
+ .action-file.icon-ascribe-ok,
+ .action-file.icon-ascribe-ok:hover {
+ color: lighten($vivi23--fg-color, 20%);
+ }
+
+ // spinner
+ .spinner-circle {
+ border-color: $vivi23--fg-color;
+ }
+ .spinner-inner {
+ display: none;
+ }
+ .btn-secondary .spinner-circle {
+ border-color: $vivi23--bg-color;
+ }
+
+ // video player
+ .ascribe-media-player .vjs-default-skin {
+ .vjs-play-progress,
+ .vjs-volume-level {
+ background-color: $vivi23--highlight-color;
+ }
+ }
+
+ // pager
+ .pager li > a,
+ .pager li > span {
+ background-color: $vivi23--fg-color;
+ border-color: $vivi23--fg-color;
+ }
+ .pager .disabled > a,
+ .pager .disabled > span {
+ background-color: $vivi23--fg-color !important;
+ border-color: $vivi23--fg-color;
+ }
+
+ // intercom
+ #intercom-container .intercom-launcher-button {
+ background-color: $vivi23--button-default-color !important;
+ border-color: $vivi23--button-default-color !important;
+ }
+}
diff --git a/sass/whitelabel/wallet/cc/cc_custom_style.scss b/sass/whitelabel/wallet/cc/cc_custom_style.scss
index 44cb0dd1..774f5b27 100644
--- a/sass/whitelabel/wallet/cc/cc_custom_style.scss
+++ b/sass/whitelabel/wallet/cc/cc_custom_style.scss
@@ -207,4 +207,20 @@ $cc--button-color: $cc--nav-fg-prim-color;
.client--cc .acl-information-dropdown-list .title {
color: $cc--button-color;
+}
+
+.client--cc .action-file.icon-ascribe-ok,
+.client--cc .action-file.icon-ascribe-ok:hover {
+ color: $cc--button-color;
+}
+
+.client--cc .ascribe-progress-bar span {
+ text-shadow: -1px 0 $cc--button-color,
+ 0 1px $cc--button-color,
+ 1px 0 $cc--button-color,
+ 0 -1px $cc--button-color;
+}
+
+.client--cc .upload-button-wrapper > span {
+ color: $cc--button-color;
}
\ No newline at end of file
diff --git a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss
index eaf45621..6c4223ac 100644
--- a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss
+++ b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss
@@ -59,40 +59,14 @@ $cyland--button-color: $cyland--nav-fg-prim-color;
display: none;
}
-
-.client--cyland .icon-ascribe-search{
+.client--cyland .icon-ascribe-search {
color: $cyland--button-color;
}
-.client--cyland .ascribe-piece-list-toolbar .btn-ascribe-add{
+.client--cyland .ascribe-piece-list-toolbar .btn-ascribe-add {
display: none;
}
-// disabled buttons
-.client--cyland {
- .btn-default.disabled,
- .btn-default.disabled:hover,
- .btn-default.disabled:focus,
- .btn-default.disabled.focus,
- .btn-default.disabled:active,
- .btn-default.disabled.active,
- .btn-default[disabled],
- .btn-default[disabled]:hover,
- .btn-default[disabled]:focus,
- .btn-default[disabled].focus,
- .btn-default[disabled]:active,
- .btn-default[disabled].active,
- fieldset[disabled] .btn-default,
- fieldset[disabled] .btn-default:hover,
- fieldset[disabled] .btn-default:focus,
- fieldset[disabled] .btn-default.focus,
- fieldset[disabled] .btn-default:active,
- fieldset[disabled] .btn-default.active {
- background-color: darken($cyland--button-color, 20%);
- border-color: darken($cyland--button-color, 20%);
- }
-}
-
// buttons!
// thought of the day:
// "every great atrocity is the result of people just following orders"
@@ -129,6 +103,26 @@ $cyland--button-color: $cyland--nav-fg-prim-color;
}
}
+ .btn-secondary {
+ background-color: white;
+ border-color: $cyland--button-color;
+ color: $cyland--button-color;
+
+ &:hover,
+ &:active,
+ &:focus,
+ &:active:hover,
+ &:active:focus,
+ &:active.focus,
+ &.active:hover,
+ &.active:focus,
+ &.active.focus {
+ background-color: $cyland--button-color;
+ border-color: $cyland--button-color;
+ color: white;
+ }
+ }
+
.open > .btn-default.dropdown-toggle:hover,
.open > .btn-default.dropdown-toggle:focus,
.open > .btn-default.dropdown-toggle.focus,
@@ -148,6 +142,48 @@ $cyland--button-color: $cyland--nav-fg-prim-color;
}
}
+.client--ikonotv {
+ .btn-default.disabled,
+ .btn-default.disabled:hover,
+ .btn-default.disabled:focus,
+ .btn-default.disabled.focus,
+ .btn-default.disabled:active,
+ .btn-default.disabled.active,
+ .btn-default[disabled],
+ .btn-default[disabled]:hover,
+ .btn-default[disabled]:focus,
+ .btn-default[disabled].focus,
+ .btn-default[disabled]:active,
+ .btn-default[disabled].active,
+ fieldset[disabled] .btn-default,
+ fieldset[disabled] .btn-default:hover,
+ fieldset[disabled] .btn-default:focus,
+ fieldset[disabled] .btn-default.focus,
+ fieldset[disabled] .btn-default:active,
+ fieldset[disabled] .btn-default.active {
+ background-color: darken($cyland--button-color, 20%);
+ border-color: darken($cyland--button-color, 20%);
+ }
+}
+
+// landing page
+.client--cyland {
+ .route--landing {
+ display: table;
+
+ > .container {
+ display: table-cell;
+ padding-bottom: 100px;
+ vertical-align: middle;
+ }
+
+ .cyland-landing {
+ font-weight: normal;
+ text-align: center;
+ }
+ }
+}
+
// spinner!
.client--cyland {
.btn-spinner {
@@ -182,4 +218,20 @@ $cyland--button-color: $cyland--nav-fg-prim-color;
.client--cyland .acl-information-dropdown-list .title {
color: $cyland--button-color;
+}
+
+.client--cyland .action-file.icon-ascribe-ok,
+.client--cyland .action-file.icon-ascribe-ok:hover {
+ color: $cyland--nav-fg-prim-color;
+}
+
+.client--cyland .ascribe-progress-bar span {
+ text-shadow: -1px 0 $cyland--nav-fg-prim-color,
+ 0 1px $cyland--nav-fg-prim-color,
+ 1px 0 $cyland--nav-fg-prim-color,
+ 0 -1px $cyland--nav-fg-prim-color;
+}
+
+.client--cyland .upload-button-wrapper > span {
+ color: $cyland--button-color;
}
\ No newline at end of file
diff --git a/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss b/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss
index 70a5cd18..8f330911 100644
--- a/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss
+++ b/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss
@@ -411,6 +411,26 @@ $ikono--font: 'Helvetica Neue', 'Helvetica', sans-serif !important;
}
}
+ .btn-secondary {
+ background-color: white;
+ border-color: $ikono--button-color;
+ color: $ikono--button-color;
+
+ &:hover,
+ &:active,
+ &:focus,
+ &:active:hover,
+ &:active:focus,
+ &:active.focus,
+ &.active:hover,
+ &.active:focus,
+ &.active.focus {
+ background-color: $ikono--button-color;
+ border-color: $ikono--button-color;
+ color: white;
+ }
+ }
+
.open > .btn-default.dropdown-toggle:hover,
.open > .btn-default.dropdown-toggle:focus,
.open > .btn-default.dropdown-toggle.focus,
@@ -524,4 +544,20 @@ $ikono--font: 'Helvetica Neue', 'Helvetica', sans-serif !important;
.client--ikonotv .acl-information-dropdown-list .title {
color: $ikono--button-color;
+}
+
+.client--ikonotv .action-file.icon-ascribe-ok,
+.client--ikonotv .action-file.icon-ascribe-ok:hover {
+ color: $ikono--button-color;
+}
+
+.client--ikonotv .ascribe-progress-bar span {
+ text-shadow: -1px 0 $ikono--button-color,
+ 0 1px $ikono--button-color,
+ 1px 0 $ikono--button-color,
+ 0 -1px $ikono--button-color;
+}
+
+.client--ikonotv .upload-button-wrapper > span {
+ color: $ikono--button-color;
}
\ No newline at end of file
diff --git a/sass/whitelabel/wallet/index.scss b/sass/whitelabel/wallet/index.scss
index 024fb3cc..01c374d9 100644
--- a/sass/whitelabel/wallet/index.scss
+++ b/sass/whitelabel/wallet/index.scss
@@ -1,6 +1,7 @@
@import 'cc/cc_custom_style';
@import 'cyland/cyland_custom_style';
@import 'ikonotv/ikonotv_custom_style';
+@import '23vivi/23vivi_custom_style';
.ascribe-wallet-app {
border-radius: 0;