diff --git a/README.md b/README.md index a3258576..1dc4492b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Install some nice extension for Chrom(e|ium): - [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) ```bash -git clone git@bitbucket.org:ascribe/onion.git +git clone git@github.com:ascribe/onion.git cd onion npm install sudo npm install -g gulp diff --git a/docs/refactor-todo.md b/docs/refactor-todo.md index f7a5917b..c0329bc2 100644 --- a/docs/refactor-todo.md +++ b/docs/refactor-todo.md @@ -8,6 +8,8 @@ queryParams of the piece_list_store should all be reflected in the url and not a single component each should manipulate the URL bar (refactor pagination, use actions and state) - Refactor string-templating for api_urls - Use classNames plugin instead of if-conditional-classes +- Instead of using `currentUser && currentUser.email` in an validation that checks whether we user is logged in or now, in the `UserStore` on login we set a boolean property called `isLoggedIn` that can then be used instead of `email` +- Refactor AclProxy to be a generic hide/show element component. Have it take data input and a validation function to assess whether it should show or hide child elements. Move current Acl checks to another place, eg. acl_utils.js. # Refactor DONE - Refactor forms to generic-declarative form component ✓ diff --git a/index.html b/index.html index 28b4d222..284b7dd4 100644 --- a/index.html +++ b/index.html @@ -2,6 +2,12 @@ + + + + + + ascribe diff --git a/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js b/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js index 47c0fb77..5d3e033f 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js @@ -125,7 +125,6 @@ let AccordionListItemEditionWidget = React.createClass({ ); } else { let editionMapping = piece && piece.first_edition ? piece.first_edition.num_editions_available + '/' + piece.num_editions : ''; - return ( } diff --git a/js/components/ascribe_buttons/acl_button_list.js b/js/components/ascribe_buttons/acl_button_list.js index b9139979..e87a6407 100644 --- a/js/components/ascribe_buttons/acl_button_list.js +++ b/js/components/ascribe_buttons/acl_button_list.js @@ -1,12 +1,15 @@ 'use strict'; -import React from 'react'; +import React from 'react/addons'; import UserActions from '../../actions/user_actions'; import UserStore from '../../stores/user_store'; import AclButton from '../ascribe_buttons/acl_button'; +import { mergeOptions } from '../../utils/general_utils'; + + let AclButtonList = React.createClass({ propTypes: { className: React.PropTypes.string, @@ -15,6 +18,7 @@ let AclButtonList = React.createClass({ React.PropTypes.array ]), availableAcls: React.PropTypes.object, + buttonsStyle: React.PropTypes.object, handleSuccess: React.PropTypes.func, children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), @@ -23,56 +27,97 @@ let AclButtonList = React.createClass({ }, getInitialState() { - return UserStore.getState(); + return mergeOptions( + UserStore.getState(), + { + buttonListSize: 0 + } + ); }, componentDidMount() { UserStore.listen(this.onChange); UserActions.fetchCurrentUser(); + + window.addEventListener('resize', this.handleResize); + window.dispatchEvent(new Event('resize')); + }, + + componentDidUpdate(prevProps) { + if(prevProps.availableAcls && prevProps.availableAcls !== this.props.availableAcls) { + window.dispatchEvent(new Event('resize')); + } }, componentWillUnmount() { UserStore.unlisten(this.onChange); + + window.removeEventListener('resize', this.handleResize); + }, + + handleResize() { + this.setState({ + buttonListSize: this.refs.buttonList.getDOMNode().offsetWidth + }); }, onChange(state) { this.setState(state); }, + renderChildren() { + const { children } = this.props; + const { buttonListSize } = this.state; + + return React.Children.map(children, (child) => { + return React.addons.cloneWithProps(child, { buttonListSize }); + }); + }, + render() { + const { className, + buttonsStyle, + availableAcls, + editions, + handleSuccess } = this.props; + + const { currentUser } = this.state; + return ( -
- - - - - - {this.props.children} +
+ + + + + + + {this.renderChildren()} +
); } diff --git a/js/components/ascribe_buttons/acl_information.js b/js/components/ascribe_buttons/acl_information.js new file mode 100644 index 00000000..8d412e02 --- /dev/null +++ b/js/components/ascribe_buttons/acl_information.js @@ -0,0 +1,133 @@ +'use strict'; + +import React from 'react'; +import classnames from 'classnames'; + +import { InformationTexts } from '../../constants/information_text'; +import { replaceSubstringAtIndex, sanitize, intersectLists } from '../../utils/general_utils'; +import { getLangText } from '../../utils/lang_utils'; + + +let AclInformation = React.createClass({ + propTypes: { + verbs: React.PropTypes.arrayOf(React.PropTypes.string), + aim: React.PropTypes.string.isRequired, + aclObject: React.PropTypes.object, + + // Must be inserted from the outside + buttonListSize: React.PropTypes.number.isRequired + }, + + getDefaultProps() { + return { + buttonListSize: 400 + }; + }, + + getInitialState() { + return { isVisible: false }; + }, + + onOff() { + if(!this.state.isVisible) { + this.setState({ isVisible: true }); + } + else { + this.setState({ isVisible: false }); + } + }, + + getInfoText(title, info, example){ + let aim = this.props.aim; + + if(aim) { + if(aim === 'form') { + return ( +

+ + {replaceSubstringAtIndex(info.slice(2), 's ', ' ')} + + + {' ' + example} + +

+ ); + } + else if(aim === 'button') { + return ( +

+ + {title} + + + {info + ' '} + + + {example} + +

+ ); + } + } + else { + console.log('Aim is required when you want to place information text'); + } + }, + + produceInformationBlock() { + const { titles, informationSentences, exampleSentences } = InformationTexts; + const { verbs, aim } = this.props; + + const availableInformations = intersectLists(verbs, Object.keys(titles)); + + // sorting is not needed, as `this.props.verbs` takes care of sorting already + // So we assume a user of `AclInformationButton` puts an ordered version of + // `verbs` into `propTypes` + let verbsToDisplay = []; + + + if(aim === 'form' && availableInformations.length > 0) { + verbsToDisplay = verbsToDisplay.concat(verbs); + } else if(aim === 'button' && this.props.aclObject) { + const { aclObject } = this.props; + const sanitizedAclObject = sanitize(aclObject, (val) => !val); + verbsToDisplay = verbsToDisplay.concat(intersectLists(verbs, Object.keys(sanitizedAclObject))); + } + + return verbsToDisplay.map((verb) => { + return this.getInfoText(getLangText(titles[verb]), getLangText(informationSentences[verb]), getLangText(exampleSentences[verb])); + }); + }, + + getButton() { + return this.props.aim === 'button' ? + ; + btnDelete = ; } else if(availableAcls.acl_unshare){ @@ -57,7 +57,7 @@ let DeleteButton = React.createClass({ title = getLangText('Remove Piece from Collection'); } - btnDelete = ; + btnDelete = ; } else { return null; diff --git a/js/components/ascribe_detail/detail_property.js b/js/components/ascribe_detail/detail_property.js index 12054996..8b0f50b5 100644 --- a/js/components/ascribe_detail/detail_property.js +++ b/js/components/ascribe_detail/detail_property.js @@ -2,6 +2,7 @@ import React from 'react'; + let DetailProperty = React.createClass({ propTypes: { label: React.PropTypes.string, @@ -23,14 +24,19 @@ let DetailProperty = React.createClass({ getDefaultProps() { return { separator: '', - labelClassName: 'col-xs-3 col-sm-3 col-md-2 col-lg-2 col-xs-height col-bottom ascribe-detail-property-label', + labelClassName: 'col-xs-3 col-sm-3 col-md-2 col-lg-2 col-xs-height ascribe-detail-property-label', valueClassName: 'col-xs-9 col-sm-9 col-md-10 col-lg-10 col-xs-height col-bottom ascribe-detail-property-value' }; }, render() { - let value = this.props.value; let styles = {}; + const { labelClassName, + label, + separator, + valueClassName, + children, + value } = this.props; if(this.props.ellipsis) { styles = { @@ -40,30 +46,16 @@ let DetailProperty = React.createClass({ }; } - - if (this.props.children){ - value = ( -
-
- { this.props.value } -
-
- { this.props.children } -
-
); - } return (
-
- { this.props.label } { this.props.separator} +
+ {label} {separator}
- {value} + {children || value}
diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index bcd6743c..80bf81d5 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -25,11 +25,11 @@ import LicenseDetail from './license_detail'; import FurtherDetails from './further_details'; import EditionActionPanel from './edition_action_panel'; +import AclProxy from '../acl_proxy'; import Note from './note'; import ApiUrls from '../../constants/api_urls'; -import AppConstants from '../../constants/application_constants'; import AscribeSpinner from '../ascribe_spinner'; import { getLangText } from '../../utils/lang_utils'; @@ -95,7 +95,7 @@ let Edition = React.createClass({

{this.props.edition.title}

- +
- - {this.getStatus()} - + + + + +
); diff --git a/js/components/ascribe_detail/edition_action_panel.js b/js/components/ascribe_detail/edition_action_panel.js index 3d6d3043..d0f16103 100644 --- a/js/components/ascribe_detail/edition_action_panel.js +++ b/js/components/ascribe_detail/edition_action_panel.js @@ -22,6 +22,8 @@ import DeleteButton from '../ascribe_buttons/delete_button'; import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationActions from '../../actions/global_notification_actions'; +import AclInformation from '../ascribe_buttons/acl_information'; + import AclProxy from '../acl_proxy'; import ApiUrls from '../../constants/api_urls'; @@ -73,7 +75,7 @@ let EditionActionPanel = React.createClass({ let notification = new GlobalNotificationModel(response.notification, 'success'); GlobalNotificationActions.appendGlobalNotification(notification); - this.history.pushState('/collection'); + this.history.pushState(null, '/collection'); }, refreshCollection() { @@ -111,7 +113,7 @@ let EditionActionPanel = React.createClass({ @@ -131,7 +133,7 @@ let EditionActionPanel = React.createClass({ value={edition.bitcoin_id} readOnly /> - @@ -168,6 +170,10 @@ let EditionActionPanel = React.createClass({ + diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index 879f7b32..554903c3 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -27,6 +27,9 @@ import CreateEditionsForm from '../ascribe_forms/create_editions_form'; import CreateEditionsButton from '../ascribe_buttons/create_editions_button'; import DeleteButton from '../ascribe_buttons/delete_button'; +import AclInformation from '../ascribe_buttons/acl_information'; +import AclProxy from '../acl_proxy'; + import ListRequestActions from '../ascribe_forms/list_form_request_actions'; import GlobalNotificationModel from '../../models/global_notification_model'; @@ -187,33 +190,41 @@ let PieceContainer = React.createClass({ }, getActions() { - if (this.state.piece && - this.state.piece.notifications && - this.state.piece.notifications.length > 0) { + const { piece, currentUser } = this.state; + + if (piece && piece.notifications && piece.notifications.length > 0) { return ( ); - } - else { + notifications={piece.notifications}/>); + } else { return ( - - - - + + + + + + + + + ); } }, @@ -232,7 +243,7 @@ let PieceContainer = React.createClass({

{this.state.piece.title}

- + {this.state.piece.num_editions > 0 ? : null}
diff --git a/js/components/ascribe_forms/form.js b/js/components/ascribe_forms/form.js index c5f60b76..fe15f537 100644 --- a/js/components/ascribe_forms/form.js +++ b/js/components/ascribe_forms/form.js @@ -288,10 +288,8 @@ let Form = React.createClass({ {this.renderChildren()} {this.getButtons()} - ); } }); - export default Form; diff --git a/js/components/ascribe_forms/form_consign.js b/js/components/ascribe_forms/form_consign.js index 9617acd4..db18ba4f 100644 --- a/js/components/ascribe_forms/form_consign.js +++ b/js/components/ascribe_forms/form_consign.js @@ -9,6 +9,9 @@ import Property from './property'; import InputTextAreaToggable from './input_textarea_toggable'; import AscribeSpinner from '../ascribe_spinner'; + +import AclInformation from '../ascribe_buttons/acl_information'; + import { getLangText } from '../../utils/lang_utils.js'; let ConsignForm = React.createClass({ @@ -47,6 +50,7 @@ let ConsignForm = React.createClass({

}> + }> +

{getLangText('Are you sure you would like to permanently delete this edition')}?

{getLangText('This is an irrevocable action%s', '.')}

diff --git a/js/components/ascribe_forms/form_delete_piece.js b/js/components/ascribe_forms/form_delete_piece.js index 4b0c9e39..ee066d3f 100644 --- a/js/components/ascribe_forms/form_delete_piece.js +++ b/js/components/ascribe_forms/form_delete_piece.js @@ -4,6 +4,8 @@ import React from 'react'; import Form from '../ascribe_forms/form'; +import AclInformation from '../ascribe_buttons/acl_information'; + import ApiUrls from '../../constants/api_urls'; import AscribeSpinner from '../ascribe_spinner'; @@ -51,6 +53,7 @@ let PieceDeleteForm = React.createClass({

}> +

{getLangText('Are you sure you would like to permanently delete this piece')}?

{getLangText('This is an irrevocable action%s', '.')}

diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js index 919b6118..d6102f14 100644 --- a/js/components/ascribe_forms/form_loan.js +++ b/js/components/ascribe_forms/form_loan.js @@ -19,7 +19,7 @@ import AscribeSpinner from '../ascribe_spinner'; import { mergeOptions } from '../../utils/general_utils'; import { getLangText } from '../../utils/lang_utils'; - +import AclInformation from '../ascribe_buttons/acl_information'; let LoanForm = React.createClass({ propTypes: { @@ -232,6 +232,7 @@ let LoanForm = React.createClass({

{this.props.loanHeading}

+

}> + diff --git a/js/components/ascribe_forms/form_transfer.js b/js/components/ascribe_forms/form_transfer.js index 010c4829..3fb95ff6 100644 --- a/js/components/ascribe_forms/form_transfer.js +++ b/js/components/ascribe_forms/form_transfer.js @@ -9,6 +9,8 @@ import Form from './form'; import Property from './property'; import InputTextAreaToggable from './input_textarea_toggable'; +import AclInformation from '../ascribe_buttons/acl_information'; + import AscribeSpinner from '../ascribe_spinner'; import { getLangText } from '../../utils/lang_utils.js'; @@ -52,6 +54,7 @@ let TransferForm = React.createClass({

}> + diff --git a/js/components/ascribe_modal/modal_wrapper.js b/js/components/ascribe_modal/modal_wrapper.js index f00eee9e..5c3ce742 100644 --- a/js/components/ascribe_modal/modal_wrapper.js +++ b/js/components/ascribe_modal/modal_wrapper.js @@ -65,7 +65,7 @@ let ModalWrapper = React.createClass({ {this.props.title} -
+
{this.renderChildren()}
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 16886def..38de2af6 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 @@ -7,7 +7,7 @@ import DropdownButton from 'react-bootstrap/lib/DropdownButton'; import { getLangText } from '../../utils/lang_utils.js'; -let PieceListToolbarFilterWidgetFilter = React.createClass({ +let PieceListToolbarFilterWidget = React.createClass({ propTypes: { filterParams: React.PropTypes.arrayOf( React.PropTypes.shape({ @@ -83,6 +83,7 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({ return ( {/* We iterate over filterParams, to receive the label and then for each @@ -139,4 +140,4 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({ } }); -export default PieceListToolbarFilterWidgetFilter; \ No newline at end of file +export default PieceListToolbarFilterWidget; \ No newline at end of file diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js index a3615aec..c38144b0 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js @@ -54,6 +54,7 @@ let PieceListToolbarOrderWidget = React.createClass({ return (
  • @@ -72,7 +73,7 @@ let PieceListToolbarOrderWidget = React.createClass({ -1} />
  • diff --git a/js/components/ascribe_spinner.js b/js/components/ascribe_spinner.js index ecdf641b..e1daf5b2 100644 --- a/js/components/ascribe_spinner.js +++ b/js/components/ascribe_spinner.js @@ -20,12 +20,13 @@ let AscribeSpinner = React.createClass({ render() { return ( -
    -
    -
    A
    +
    +
    +
    A
    ); } diff --git a/js/components/header.js b/js/components/header.js index 042d2b1c..51f91318 100644 --- a/js/components/header.js +++ b/js/components/header.js @@ -2,6 +2,8 @@ import React from 'react'; +import { Link } from 'react-router'; + import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; import CollapsibleNav from 'react-bootstrap/lib/CollapsibleNav'; @@ -29,7 +31,7 @@ import NavRoutesLinks from './nav_routes_links'; import { mergeOptions } from '../utils/general_utils'; import { getLangText } from '../utils/lang_utils'; -import {constructHead} from '../utils/head_setter'; +import { constructHead } from '../utils/dom_utils'; let Header = React.createClass({ @@ -71,12 +73,16 @@ let Header = React.createClass({ } if (whitelabel.subdomain && whitelabel.subdomain !== 'www' && whitelabel.logo){ - return (); + return ( + + Whitelabel brand + + ); } return ( - + ); }, @@ -201,7 +207,7 @@ let Header = React.createClass({ {this.getPoweredBy()} diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 528ee409..425247da 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -25,8 +25,6 @@ import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar'; import AscribeSpinner from './ascribe_spinner'; -import AppConstants from '../constants/application_constants'; - import { getAvailableAcls } from '../utils/acl_utils'; import { mergeOptions } from '../utils/general_utils'; import { getLangText } from '../utils/lang_utils'; diff --git a/js/components/whitelabel/prize/components/ascribe_accordion_list/accordion_list_item_prize.js b/js/components/whitelabel/prize/components/ascribe_accordion_list/accordion_list_item_prize.js index abda8a1b..caef504b 100644 --- a/js/components/whitelabel/prize/components/ascribe_accordion_list/accordion_list_item_prize.js +++ b/js/components/whitelabel/prize/components/ascribe_accordion_list/accordion_list_item_prize.js @@ -182,7 +182,7 @@ let AccordionListItemPrize = React.createClass({ artistName={artistName} subsubheading={
    - {this.props.content.date_created.split('-')[0]} + {new Date(this.props.content.date_created).getFullYear()}
    } buttons={this.getPrizeButtons()} badge={this.getPrizeBadge()}> diff --git a/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js b/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js index a5f9838f..07e84b0e 100644 --- a/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js +++ b/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js @@ -141,7 +141,7 @@ let PieceContainer = React.createClass({

    {this.state.piece.title}

    - + {artistEmail} {this.getActions()}
    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 8a38f02e..5644b5b0 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 @@ -39,7 +39,7 @@ let WalletPieceContainer = React.createClass({

    {this.props.piece.title}

    - +
    } diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js b/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js index ae96db98..755e550b 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js @@ -100,7 +100,7 @@ let CylandAccordionListItem = React.createClass({ piece={this.props.content} subsubheading={
    - {this.props.content.date_created.split('-')[0]} + {new Date(this.props.content.date_created).getFullYear()}
    } buttons={this.getSubmitButtons()}> {this.props.children} diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js index 8a6c38b5..63085fc9 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js @@ -3,7 +3,6 @@ import React from 'react'; import { History } from 'react-router'; - import WhitelabelActions from '../../../../../actions/whitelabel_actions'; import WhitelabelStore from '../../../../../stores/whitelabel_store'; @@ -14,10 +13,13 @@ import LinkContainer from 'react-router-bootstrap/lib/LinkContainer'; import UserStore from '../../../../../stores/user_store'; import UserActions from '../../../../../actions/user_actions'; +import AscribeSpinner from '../../../../ascribe_spinner'; + import { mergeOptions } from '../../../../../utils/general_utils'; import { getLangText } from '../../../../../utils/lang_utils'; import { setDocumentTitle } from '../../../../../utils/dom_utils'; + let CylandLanding = React.createClass({ mixins: [History], @@ -61,10 +63,9 @@ let CylandLanding = React.createClass({
    - {getLangText('Submissions to Cyland Archive are powered by')} + {getLangText('Submissions to Cyland Archive are powered by') + ' '} - ascribe - +
    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 00e7f318..7445eb36 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 @@ -106,7 +106,7 @@ let IkonotvAccordionListItem = React.createClass({ piece={this.props.content} subsubheading={
    - {this.props.content.date_created.split('-')[0]} + {new Date(this.props.content.date_created).getFullYear()}
    } buttons={this.getSubmitButtons()}> {this.props.children} diff --git a/js/constants/information_text.js b/js/constants/information_text.js new file mode 100644 index 00000000..442e481e --- /dev/null +++ b/js/constants/information_text.js @@ -0,0 +1,33 @@ +'use strict'; + +export const InformationTexts = { + 'titles': { + 'acl_consign': 'CONSIGN', + 'acl_loan': 'LOAN', + 'acl_share': 'SHARE', + 'acl_delete': 'DELETE', + 'acl_create_editions': 'CREATE EDITIONS', + 'acl_unconsign': 'UNCONSIGN', + 'acl_request_unconsign': 'REQUEST UNCONSIGN' + }, + 'informationSentences': { + 'acl_consign': ' - Lets someone represent you in dealing with the work, under the terms you agree to.', + 'acl_loan': ' - Lets someone use or put the Work on display for a limited amount of time.', + 'acl_share': ' - Lets someone view the Work or Edition, but does not give rights to publish or display it.', + 'acl_delete': ' - Removes the Work from your Wallet. Note that the previous registration and transfer ' + + 'history will still exist on the blockchain and cannot be deleted.', + 'acl_create_editions': ' Lets the artist set a fixed number of editions of a work which can then be transferred, guaranteeing each edition is authentic and from the artist.', + 'acl_unconsign': 'Ends the consignment agreement between the owner and a consignee.', + 'acl_request_unconsign': 'Lets the owner ask the consignee to confirm that they will no longer manage the work.' + }, + 'exampleSentences': { + 'acl_consign': '(e.g. an artist Consigns 10 Editions of her new Work to a gallery ' + + 'so the gallery can sell them on her behalf, under the terms the artist and the gallery have agreed to)', + 'acl_loan': '(e.g. a collector Loans a Work to a gallery for one month for display in the gallery\'s show)', + 'acl_share': '(e.g. a photographer Shares proofs of a graduation photo with the graduate\'s grandparents)', + 'acl_delete': '(e.g. an artist uploaded the wrong file and doesn\'t want it cluttering his Wallet, so he Deletes it)', + 'acl_create_editions': '(e.g. A company commissions a visual artists to create three limited edition prints for a giveaway)', + 'acl_unconsign': '(e.g. An artist regains full control over their work and releases the consignee of any rights or responsibilities)', + 'acl_request_unconsign': '(e.g. An artist submits an unconsign request to a gallery after his exhibition ends, as per their agreement)' + } +}; \ No newline at end of file diff --git a/js/utils/acl_utils.js b/js/utils/acl_utils.js index 170d7aec..dd39a380 100644 --- a/js/utils/acl_utils.js +++ b/js/utils/acl_utils.js @@ -1,10 +1,6 @@ 'use strict'; -import { sanitize } from './general_utils'; - -function intersectAcls(a, b) { - return a.filter((val) => b.indexOf(val) > -1); -} +import { sanitize, intersectLists } from './general_utils'; export function getAvailableAcls(editions, filterFn) { let availableAcls = []; @@ -37,13 +33,15 @@ 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 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; - } else if (editionsCopy.length >= 2) { - for (let i = 1; i < editionsCopy.length; i++) { - availableAcls = intersectAcls(availableAcls, editionsCopy[i].acl); + + if (editionsCopy.length >= 2) { + for (let i = 1; i < editionsCopy.length; i++) { + availableAcls = intersectLists(availableAcls, editionsCopy[i].acl); + } } } @@ -53,6 +51,5 @@ export function getAvailableAcls(editions, filterFn) { availableAclsObj[availableAcls[i]] = true; } - return availableAclsObj; } diff --git a/js/utils/dom_utils.js b/js/utils/dom_utils.js index c20e1009..d009f90f 100644 --- a/js/utils/dom_utils.js +++ b/js/utils/dom_utils.js @@ -1,9 +1,45 @@ 'use strict'; - /** * Set the title in the browser window. */ export function setDocumentTitle(title) { document.title = title; } + +/** + * @param {string} elementType: string, is the type of the element, such as link, meta, etc. + * @param {string} elementId id of the element + * @param {object} elementAttributes: hash table containing the attributes of the relevant element + */ +function constructHeadElement(elementType, elementId, elementAttributes) { + let head = (document.head || document.getElementsByTagName('head')[0]); + let element = document.createElement(elementType); + let oldElement = document.getElementById(elementId); + element.setAttribute('id', elementId); + for (let k in elementAttributes){ + try { + element.setAttribute(k, elementAttributes[k]); + } + catch(e){ + console.warn(e.message); + } + } + if (oldElement) { + head.removeChild(oldElement); + } + head.appendChild(element); +} + +/** + * Accepts a dictionary of dictionaries which comprises a part or all of html head part + * @param {object} headObject {link : {id1: {rel: ... }}} + */ +export function constructHead(headObject){ + for (let k in headObject){ + let favicons = headObject[k]; + for (let f in favicons){ + constructHeadElement(k, f, favicons[f]); + } + } +} \ No newline at end of file diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js index cc1d22dd..1aea3cd9 100644 --- a/js/utils/general_utils.js +++ b/js/utils/general_utils.js @@ -222,6 +222,16 @@ export function truncateTextAtCharIndex(text, charIndex, replacement = '...') { return truncatedText; } +/** + * @param index, int, the starting index of the substring to be replaced + * @param character, substring to be replaced + * @returns {string} + */ +export function replaceSubstringAtIndex(baseString, substrToReplace, stringToBePut) { + let index = baseString.indexOf(substrToReplace); + return baseString.substr(0, index) + stringToBePut + baseString.substr(index + substrToReplace.length); +} + /** * Extracts the user's subdomain from the browser's window. * If no subdomain is found (for example on a naked domain), the default "www" is just assumed. @@ -232,3 +242,13 @@ export function getSubdomain() { let tokens = host.split('.'); return tokens.length > 2 ? tokens[0] : 'www'; } + +/** + * Takes two lists and returns their intersection as a list + * @param {Array} a + * @param {Array} b + * @return {[Array]} Intersected list of a and b + */ +export function intersectLists(a, b) { + return a.filter((val) => b.indexOf(val) > -1); +} diff --git a/js/utils/head_setter.js b/js/utils/head_setter.js deleted file mode 100644 index 6ca2c9b0..00000000 --- a/js/utils/head_setter.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -// elementType: string, is the type of the element, such as link, meta, etc. -// elementId id of the element -// elementAttributes: hash table containing the attributes of the relevant element - -function constructHeadElement(elementType, elementId, elementAttributes) { - let head = (document.head || document.getElementsByTagName('head')[0]); - let element = document.createElement(elementType); - let oldElement = document.getElementById(elementId); - element.setAttribute('id', elementId); - for (let k in elementAttributes){ - try { - element.setAttribute(k, elementAttributes[k]); - } - catch(e){ - console.warn(e.message); - continue; - } - } - if (oldElement) { - head.removeChild(oldElement); - } - head.appendChild(element); -} - -// Accepts a dictionary of dictionaries which comprises a part or all of html head part -// {link : {id1: {rel: ... }}} -// traverses a tree of depth 3 (no backtracking) -export function constructHead(headObject){ - for (let k in headObject){ - let favicons = headObject[k]; - for (let f in favicons){ - constructHeadElement(k, f, favicons[f]); - } - } -} diff --git a/sass/ascribe_acl_information.scss b/sass/ascribe_acl_information.scss new file mode 100644 index 00000000..063c8ae6 --- /dev/null +++ b/sass/ascribe_acl_information.scss @@ -0,0 +1,25 @@ +.acl-information-dropdown-list { + text-align: justify; + padding: .5em .5em .5em 0; + + p { + margin: 0 .5em 1em 0; + line-height: 1.2; + } + + span { + font-size: 13px; + } + + .title { + color: $ascribe-dark-blue; + } + + .info { + color: #212121; + } + + .example { + color: #616161; + } +} \ No newline at end of file diff --git a/sass/ascribe_custom_style.scss b/sass/ascribe_custom_style.scss index 51281d91..05d39027 100644 --- a/sass/ascribe_custom_style.scss +++ b/sass/ascribe_custom_style.scss @@ -91,12 +91,27 @@ hr { } } - .navbar-brand, - .navbar-brand:hover { + .navbar-brand { font-size: 23px; padding: 12px 15px; - color: $ascribe--nav-fg-prim-color; + + .icon-ascribe-logo { + color: $ascribe--nav-fg-prim-color; + + &:hover { + color: $ascribe--nav-fg-sec-color; + text-decoration: none; + } + &:focus { + text-decoration: none; + } + } + + .img-brand { + height: 100%; + } } + .img-brand .navbar-brand { width: 0; height: 0; @@ -327,6 +342,29 @@ fieldset[disabled] .btn-secondary.active { } } +.btn-tertiary { + background-color: transparent; + border-color: transparent; + color: $ascribe-dark-blue; + + &:focus, + &:active:focus, + &:active.focus { + background-color: transparent; + border-color: transparent; + color: $ascribe-dark-blue; + } + + &:hover, + &:active, + &:active:hover, + &.active:hover{ + background-color: $ascribe-pink; + border-color: $ascribe-pink; + color: $ascribe-white; + } +} + .ascribe-piece-list-toolbar-filter-widget button { background-color: transparent; border: 1px solid transparent; diff --git a/sass/ascribe_edition.scss b/sass/ascribe_edition.scss index 9fa30387..195e79e0 100644 --- a/sass/ascribe_edition.scss +++ b/sass/ascribe_edition.scss @@ -17,8 +17,4 @@ border: 1px solid #CCC; display: table-cell; vertical-align: middle; -} - -.ascribe-button-list { - margin-top: 1em; -} +} \ No newline at end of file diff --git a/sass/lib/buttons.scss b/sass/lib/buttons.scss index e69de29b..68a124f9 100644 --- a/sass/lib/buttons.scss +++ b/sass/lib/buttons.scss @@ -0,0 +1,9 @@ +.btn-transparent { + color: black; + background-color: transparent; + + &:hover, &:active, &:focus { + color:#424242; + outline: none; + } +} \ No newline at end of file diff --git a/sass/lib/modals.scss b/sass/lib/modals.scss new file mode 100644 index 00000000..20a720c1 --- /dev/null +++ b/sass/lib/modals.scss @@ -0,0 +1,8 @@ +.modal-body { + padding-top:0; +} + +.modal-header { + padding: 15px 15px 0 15px; + border-bottom: none; +} \ No newline at end of file diff --git a/sass/main.scss b/sass/main.scss index 4b9e0f71..337dd32e 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -35,6 +35,9 @@ $BASE_URL: '<%= BASE_URL %>'; @import 'ascribe_form'; @import 'ascribe_panel'; @import 'ascribe_collapsible'; +@import 'ascribe_acl_information'; +@import 'lib/buttons'; +@import 'lib/modals'; @import 'ascribe_custom_style'; @import 'ascribe_spinner'; @@ -155,6 +158,7 @@ hr { } .ascribe-detail-property-label { + vertical-align: top; font-size: .8em; } diff --git a/sass/variables.scss b/sass/variables.scss index ccd48864..ac93a2fb 100644 --- a/sass/variables.scss +++ b/sass/variables.scss @@ -613,7 +613,7 @@ $modal-header-border-color: #e5e5e5 !default; $modal-footer-border-color: $modal-header-border-color !default; $modal-lg: 900px !default; -$modal-md: 600px !default; +$modal-md: 500px !default; $modal-sm: 300px !default; diff --git a/sass/whitelabel/prize/sluice/sluice_custom_style.scss b/sass/whitelabel/prize/sluice/sluice_custom_style.scss index e2ceeeb3..4cfb7c82 100644 --- a/sass/whitelabel/prize/sluice/sluice_custom_style.scss +++ b/sass/whitelabel/prize/sluice/sluice_custom_style.scss @@ -251,3 +251,7 @@ $sluice--button-color: $sluice--nav-fg-prim-color; .client--sluice .ascribe-progress-bar > .progress-bar { background-color: $sluice--button-color; } + +.client--sluice .acl-information-dropdown-list .title { + color: $sluice--button-color; +} \ No newline at end of file diff --git a/sass/whitelabel/wallet/cc/cc_custom_style.scss b/sass/whitelabel/wallet/cc/cc_custom_style.scss index 238937e7..44cb0dd1 100644 --- a/sass/whitelabel/wallet/cc/cc_custom_style.scss +++ b/sass/whitelabel/wallet/cc/cc_custom_style.scss @@ -204,3 +204,7 @@ $cc--button-color: $cc--nav-fg-prim-color; .client--cc .ascribe-progress-bar > .progress-bar { background-color: $cc--button-color; } + +.client--cc .acl-information-dropdown-list .title { + 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 c33e247b..eaf45621 100644 --- a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss +++ b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss @@ -179,3 +179,7 @@ $cyland--button-color: $cyland--nav-fg-prim-color; .client--cyland .ascribe-progress-bar > .progress-bar { background-color: $cyland--button-color; } + +.client--cyland .acl-information-dropdown-list .title { + 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 5fc9220b..52affdaf 100644 --- a/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss +++ b/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss @@ -521,3 +521,7 @@ $ikono--font: 'Helvetica Neue', 'Helvetica', sans-serif !important; .client--ikonotv .ascribe-progress-bar > .progress-bar { background-color: $ikono--button-color; } + +.client--ikonotv .acl-information-dropdown-list .title { + color: $ikono--button-color; +} \ No newline at end of file