1
0
mirror of https://github.com/ascribe/onion.git synced 2024-11-15 01:25:17 +01:00

Merge remote-tracking branch 'remotes/origin/AD-456-ikonotv-branded-page-for-registra' into AD-885-convert-roles-to-acls

This commit is contained in:
diminator 2015-09-10 18:10:06 +02:00
commit 1106cebf98
54 changed files with 1137 additions and 565 deletions

View File

@ -189,17 +189,7 @@ function bundle(watch) {
.pipe(gulpif(!argv.production, sourcemaps.write())) // writes .map file .pipe(gulpif(!argv.production, sourcemaps.write())) // writes .map file
.on('error', notify.onError('Error: <%= error.message %>')) .on('error', notify.onError('Error: <%= error.message %>'))
.pipe(gulpif(argv.production, uglify({ .pipe(gulpif(argv.production, uglify({
mangle: true, mangle: true
compress: {
sequences: true,
dead_code: true,
conditionals: true,
booleans: true,
unused: true,
if_return: true,
join_vars: true,
drop_console: true
}
}))) })))
.on('error', notify.onError('Error: <%= error.message %>')) .on('error', notify.onError('Error: <%= error.message %>'))
.pipe(gulp.dest('./build/js')) .pipe(gulp.dest('./build/js'))

View File

@ -12,8 +12,8 @@ class ContractListActions {
); );
} }
fetchContractList() { fetchContractList(isActive) {
OwnershipFetcher.fetchContractList() OwnershipFetcher.fetchContractList(isActive)
.then((contracts) => { .then((contracts) => {
this.actions.updateContractList(contracts.results); this.actions.updateContractList(contracts.results);
}) })
@ -23,8 +23,8 @@ class ContractListActions {
}); });
} }
makeContractPublic(contract){
contract.public = true; changeContract(contract){
return Q.Promise((resolve, reject) => { return Q.Promise((resolve, reject) => {
OwnershipFetcher.makeContractPublic(contract) OwnershipFetcher.makeContractPublic(contract)
.then((res) => { .then((res) => {
@ -38,14 +38,12 @@ class ContractListActions {
} }
removeContract(contractId){ removeContract(contractId){
return Q.Promise((resolve, reject) => { return Q.Promise( (resolve, reject) => {
OwnershipFetcher.deleteContract(contractId) OwnershipFetcher.deleteContract(contractId)
.then((res) => { .then((res) => {
console.log('Contract deleted');
resolve(res); resolve(res);
}) })
.catch( (err) => { .catch( (err) => {
console.log('Error while deleting');
console.logGlobal(err); console.logGlobal(err);
reject(err); reject(err);
}); });

View File

@ -0,0 +1,68 @@
'use strict';
import alt from '../alt';
import Q from 'q';
import NotificationFetcher from '../fetchers/notification_fetcher';
class NotificationActions {
constructor() {
this.generateActions(
'updatePieceListNotifications',
'updateEditionListNotifications',
'updateEditionNotifications',
'updatePieceNotifications',
'updateContractAgreementListNotifications'
);
}
fetchPieceListNotifications() {
NotificationFetcher
.fetchPieceListNotifications()
.then((res) => {
this.actions.updatePieceListNotifications(res);
})
.catch((err) => console.logGlobal(err));
}
fetchPieceNotifications(pieceId) {
NotificationFetcher
.fetchPieceNotifications(pieceId)
.then((res) => {
this.actions.updatePieceNotifications(res);
})
.catch((err) => console.logGlobal(err));
}
fetchEditionListNotifications() {
NotificationFetcher
.fetchEditionListNotifications()
.then((res) => {
this.actions.updateEditionListNotifications(res);
})
.catch((err) => console.logGlobal(err));
}
fetchEditionNotifications(editionId) {
NotificationFetcher
.fetchEditionNotifications(editionId)
.then((res) => {
this.actions.updateEditionNotifications(res);
})
.catch((err) => console.logGlobal(err));
}
fetchContractAgreementListNotifications() {
return Q.Promise((resolve, reject) => {
NotificationFetcher
.fetchContractAgreementListNotifications()
.then((res) => {
this.actions.updateContractAgreementListNotifications(res);
resolve(res);
})
.catch((err) => console.logGlobal(err));
});
}
}
export default alt.createActions(NotificationActions);

View File

@ -26,6 +26,7 @@ import EventActions from './actions/event_actions';
import GoogleAnalyticsHandler from './third_party/ga'; import GoogleAnalyticsHandler from './third_party/ga';
import RavenHandler from './third_party/raven'; import RavenHandler from './third_party/raven';
import IntercomHandler from './third_party/intercom'; import IntercomHandler from './third_party/intercom';
import NotificationsHandler from './third_party/notifications';
/* eslint-enable */ /* eslint-enable */
initLogging(); initLogging();
@ -71,9 +72,8 @@ class AppGateway {
type = settings.type; type = settings.type;
subdomain = settings.subdomain; subdomain = settings.subdomain;
} }
EventActions.applicationWillBoot(settings); EventActions.applicationWillBoot(settings);
Router.run(getRoutes(type, subdomain), Router.HistoryLocation, (App) => { window.appRouter = Router.run(getRoutes(type, subdomain), Router.HistoryLocation, (App) => {
React.render( React.render(
<App />, <App />,
document.getElementById('main') document.getElementById('main')

View File

@ -160,7 +160,7 @@ let AccordionListItemTableEditions = React.createClass({
let content = item.acl; let content = item.acl;
return { return {
'content': content, 'content': content,
'requestAction': item.request_action 'notifications': item.notifications
}; }, }; },
'acl', 'acl',
getLangText('Actions'), getLangText('Actions'),

View File

@ -61,8 +61,7 @@ let AccordionListItemWallet = React.createClass({
}, },
getGlyphicon(){ getGlyphicon(){
if ((this.props.content.request_action && this.props.content.request_action.length > 0) || if ((this.props.content.notifications && this.props.content.notifications.length > 0)){
(this.props.content.request_action_editions)){
return ( return (
<OverlayTrigger <OverlayTrigger
delay={500} delay={500}

View File

@ -1,27 +0,0 @@
'use strict';
import React from 'react';
let ButtonSubmitOrClose = React.createClass({
propTypes: {
submitted: React.PropTypes.bool.isRequired,
text: React.PropTypes.string.isRequired
},
render() {
if (this.props.submitted){
return (
<div className="modal-footer">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</div>
);
}
return (
<div className="modal-footer">
<button type="submit" className="btn btn-ascribe-inv">{this.props.text}</button>
</div>
);
}
});
export default ButtonSubmitOrClose;

View File

@ -1,32 +0,0 @@
'use strict';
import React from 'react';
import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils.js';
let ButtonSubmitOrClose = React.createClass({
propTypes: {
submitted: React.PropTypes.bool.isRequired,
text: React.PropTypes.string.isRequired,
onClose: React.PropTypes.func.isRequired
},
render() {
if (this.props.submitted){
return (
<div className="modal-footer">
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
</div>
);
}
return (
<div className="modal-footer">
<button type="submit" className="btn btn-ascribe-inv">{this.props.text}</button>
<button className="btn btn-ascribe-inv" onClick={this.props.onClose}>{getLangText('CLOSE')}</button>
</div>
);
}
});
export default ButtonSubmitOrClose;

View File

@ -14,7 +14,7 @@ import CoaActions from '../../actions/coa_actions';
import CoaStore from '../../stores/coa_store'; import CoaStore from '../../stores/coa_store';
import PieceListActions from '../../actions/piece_list_actions'; import PieceListActions from '../../actions/piece_list_actions';
import PieceListStore from '../../stores/piece_list_store'; import PieceListStore from '../../stores/piece_list_store';
import EditionListActions from '../../actions/edition_list_actions'; import EditionListActions from '../../actions/edition_list_actions';;
import HistoryIterator from './history_iterator'; import HistoryIterator from './history_iterator';
@ -234,13 +234,15 @@ let EditionSummary = React.createClass({
getActions(){ getActions(){
let actions = null; let actions = null;
if (this.props.edition.request_action && this.props.edition.request_action.length > 0){ if (this.props.edition &&
this.props.edition.notifications &&
this.props.edition.notifications.length > 0){
actions = ( actions = (
<ListRequestActions <ListRequestActions
pieceOrEditions={[this.props.edition]} pieceOrEditions={[this.props.edition]}
currentUser={this.props.currentUser} currentUser={this.props.currentUser}
handleSuccess={this.showNotification} handleSuccess={this.showNotification}
requestActions={this.props.edition.request_action}/>); notifications={this.props.edition.notifications}/>);
} }
else { else {

View File

@ -61,8 +61,7 @@ let EditionContainer = React.createClass({
}, },
render() { render() {
console.log(this.state); if(this.state.edition && this.state.edition.title) {
if('title' in this.state.edition) {
return ( return (
<Edition <Edition
edition={this.state.edition} edition={this.state.edition}

View File

@ -39,7 +39,7 @@ let FurtherDetailsFileuploader = React.createClass({
return null; return null;
} }
let otherDataIds = this.props.otherData ? this.props.otherData.map((data)=>{return data.id; }).join() : null; let otherDataIds = this.props.otherData ? this.props.otherData.map((data) => data.id).join() : null;
return ( return (
<Property <Property

View File

@ -172,17 +172,16 @@ let PieceContainer = React.createClass({
return {'id': this.state.piece.id}; return {'id': this.state.piece.id};
}, },
getActions(){ getActions() {
if (this.state.piece && if (this.state.piece &&
this.state.piece.request_action && this.state.piece.notifications &&
this.state.piece.request_action.length > 0) { this.state.piece.notifications.length > 0) {
return ( return (
<ListRequestActions <ListRequestActions
pieceOrEditions={this.state.piece} pieceOrEditions={this.state.piece}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.loadPiece} handleSuccess={this.loadPiece}
requestActions={this.state.piece.request_action}/> notifications={this.state.piece.notifications}/>);
);
} }
else { else {
return ( return (
@ -206,7 +205,7 @@ let PieceContainer = React.createClass({
}, },
render() { render() {
if('title' in this.state.piece) { if(this.state.piece && this.state.piece.title) {
return ( return (
<Piece <Piece
piece={this.state.piece} piece={this.state.piece}

View File

@ -41,7 +41,9 @@ let Form = React.createClass({
// It will make use of the GlobalNotification // It will make use of the GlobalNotification
isInline: React.PropTypes.bool, isInline: React.PropTypes.bool,
autoComplete: React.PropTypes.string autoComplete: React.PropTypes.string,
onReset: React.PropTypes.func
}, },
getDefaultProps() { getDefaultProps() {
@ -60,9 +62,15 @@ let Form = React.createClass({
}; };
}, },
reset(){ reset() {
for (let ref in this.refs){ // If onReset prop is defined from outside,
if (typeof this.refs[ref].reset === 'function'){ // notify component that a form reset is happening.
if(this.props.onReset && typeof this.props.onReset === 'function') {
this.props.onReset();
}
for(let ref in this.refs) {
if (this.refs[ref].reset && typeof this.refs[ref].reset === 'function'){
this.refs[ref].reset(); this.refs[ref].reset();
} }
} }
@ -70,7 +78,6 @@ let Form = React.createClass({
}, },
submit(event){ submit(event){
if(event) { if(event) {
event.preventDefault(); event.preventDefault();
} }
@ -79,7 +86,7 @@ let Form = React.createClass({
this.clearErrors(); this.clearErrors();
// selecting http method based on props // selecting http method based on props
if(this[this.props.method]) { if(this[this.props.method] && typeof this[this.props.method] === 'function') {
window.setTimeout(() => this[this.props.method](), 100); window.setTimeout(() => this[this.props.method](), 100);
} else { } else {
throw new Error('This HTTP method is not supported by form.js (' + this.props.method + ')'); throw new Error('This HTTP method is not supported by form.js (' + this.props.method + ')');
@ -93,6 +100,20 @@ let Form = React.createClass({
.catch(this.handleError); .catch(this.handleError);
}, },
put() {
requests
.put(this.props.url, { body: this.getFormData() })
.then(this.handleSuccess)
.catch(this.handleError);
},
patch() {
requests
.patch(this.props.url, { body: this.getFormData() })
.then(this.handleSuccess)
.catch(this.handleError);
},
delete() { delete() {
requests requests
.delete(this.props.url, this.getFormData()) .delete(this.props.url, this.getFormData())
@ -100,13 +121,13 @@ let Form = React.createClass({
.catch(this.handleError); .catch(this.handleError);
}, },
getFormData(){ getFormData() {
let data = {}; let data = {};
for (let ref in this.refs){ for(let ref in this.refs){
data[this.refs[ref].props.name] = this.refs[ref].state.value; data[this.refs[ref].props.name] = this.refs[ref].state.value;
} }
if ('getFormData' in this.props){ if (this.props.getFormData && typeof this.props.getFormData === 'function'){
data = mergeOptionsWithDuplicates(data, this.props.getFormData()); data = mergeOptionsWithDuplicates(data, this.props.getFormData());
} }
@ -118,11 +139,12 @@ let Form = React.createClass({
}, },
handleSuccess(response){ handleSuccess(response){
if ('handleSuccess' in this.props){ if(this.props.handleSuccess && typeof this.props.handleSuccess === 'function') {
this.props.handleSuccess(response); this.props.handleSuccess(response);
} }
for (var ref in this.refs){
if ('handleSuccess' in this.refs[ref]){ for(let ref in this.refs) {
if(this.refs[ref] && this.refs[ref].handleSuccess && typeof this.refs[ref].handleSuccess === 'function'){
this.refs[ref].handleSuccess(); this.refs[ref].handleSuccess();
} }
} }
@ -134,9 +156,9 @@ let Form = React.createClass({
handleError(err){ handleError(err){
if (err.json) { if (err.json) {
for (var input in err.json.errors){ for (let input in err.json.errors){
if (this.refs && this.refs[input] && this.refs[input].state) { if (this.refs && this.refs[input] && this.refs[input].state) {
this.refs[input].setErrors( err.json.errors[input]); this.refs[input].setErrors(err.json.errors[input]);
} else { } else {
this.setState({errors: this.state.errors.concat(err.json.errors[input])}); this.setState({errors: this.state.errors.concat(err.json.errors[input])});
} }
@ -164,8 +186,8 @@ let Form = React.createClass({
}, },
clearErrors(){ clearErrors(){
for (var ref in this.refs){ for(let ref in this.refs){
if ('clearErrors' in this.refs[ref]){ if (this.refs[ref] && this.refs[ref].clearErrors && typeof this.refs[ref].clearErrors === 'function'){
this.refs[ref].clearErrors(); this.refs[ref].clearErrors();
} }
} }
@ -185,8 +207,16 @@ let Form = React.createClass({
buttons = ( buttons = (
<div className="row" style={{margin: 0}}> <div className="row" style={{margin: 0}}>
<p className="pull-right"> <p className="pull-right">
<Button className="btn btn-default btn-sm ascribe-margin-1px" type="submit">{this.props.buttonSubmitText}</Button> <Button
<Button className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" onClick={this.reset}>CANCEL</Button> className="btn btn-default btn-sm ascribe-margin-1px"
type="submit">
{this.props.buttonSubmitText}
</Button>
<Button
className="btn btn-danger btn-delete btn-sm ascribe-margin-1px"
type="reset">
CANCEL
</Button>
</p> </p>
</div> </div>
); );
@ -251,6 +281,7 @@ let Form = React.createClass({
role="form" role="form"
className={className} className={className}
onSubmit={this.submit} onSubmit={this.submit}
onReset={this.reset}
autoComplete={this.props.autoComplete}> autoComplete={this.props.autoComplete}>
{this.getFakeAutocompletableInputs()} {this.getFakeAutocompletableInputs()}
{this.getErrors()} {this.getErrors()}

View File

@ -35,7 +35,7 @@ let ContractAgreementForm = React.createClass({
componentDidMount() { componentDidMount() {
ContractListStore.listen(this.onChange); ContractListStore.listen(this.onChange);
ContractListActions.fetchContractList(); ContractListActions.fetchContractList({is_active: 'True'});
}, },
componentWillUnmount() { componentWillUnmount() {
@ -50,9 +50,11 @@ let ContractAgreementForm = React.createClass({
this.setState({selectedContract: event.target.selectedIndex}); this.setState({selectedContract: event.target.selectedIndex});
}, },
handleSubmitSuccess(response) { handleSubmitSuccess() {
let notification = new GlobalNotificationModel(response.notification, 'success', 10000); let notification = 'Contract agreement send';
notification = new GlobalNotificationModel(notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
this.refs.form.reset();
}, },
getFormData(){ getFormData(){
@ -60,8 +62,8 @@ let ContractAgreementForm = React.createClass({
}, },
getContracts() { getContracts() {
if (this.state.contractList && this.state.contractList.count > 0) { if (this.state.contractList && this.state.contractList.length > 0) {
let contractList = this.state.contractList.results; let contractList = this.state.contractList;
return ( return (
<Property <Property
name='contract' name='contract'
@ -81,7 +83,7 @@ let ContractAgreementForm = React.createClass({
<option <option
name={i} name={i}
key={i} key={i}
value={ contract.name }> value={ contract.id }>
{ contract.name } { contract.name }
</option> </option>
); );
@ -99,7 +101,7 @@ let ContractAgreementForm = React.createClass({
ref='form' ref='form'
url={ApiUrls.ownership_contract_agreements} url={ApiUrls.ownership_contract_agreements}
getFormData={this.getFormData} getFormData={this.getFormData}
handleSuccess={this.props.handleSuccess} handleSuccess={this.handleSubmitSuccess}
buttons={<button buttons={<button
type="submit" type="submit"
className="btn ascribe-btn ascribe-btn-login"> className="btn ascribe-btn ascribe-btn-login">

View File

@ -16,6 +16,8 @@ import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
import AppConstants from '../../constants/application_constants'; import AppConstants from '../../constants/application_constants';
import ApiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
import { getCookie } from '../../utils/fetch_api_utils'; import { getCookie } from '../../utils/fetch_api_utils';
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils'; import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils';
@ -48,17 +50,17 @@ let CreateContractForm = React.createClass({
}, },
handleCreateSuccess(response) { handleCreateSuccess(response) {
ContractListActions.fetchContractList({is_active: 'True'});
let notification = new GlobalNotificationModel(getLangText('Contract %s successfully created', response.name), 'success', 5000); let notification = new GlobalNotificationModel(getLangText('Contract %s successfully created', response.name), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
this.refs.form.reset();
// also refresh contract lists for the rest of the contract settings page
ContractListActions.fetchContractList();
}, },
render() { render() {
return ( return (
<Form <Form
ref='form'
url={ApiUrls.ownership_contract_list} url={ApiUrls.ownership_contract_list}
getFormData={this.getFormData} getFormData={this.getFormData}
handleSuccess={this.handleCreateSuccess} handleSuccess={this.handleCreateSuccess}
@ -78,6 +80,7 @@ let CreateContractForm = React.createClass({
<Property <Property
label="Contract file"> label="Contract file">
<ReactS3FineUploader <ReactS3FineUploader
ref='uploader'
keyRoutine={{ keyRoutine={{
url: AppConstants.serverUrl + 's3/key/', url: AppConstants.serverUrl + 's3/key/',
fileClass: 'contract' fileClass: 'contract'
@ -118,7 +121,7 @@ let CreateContractForm = React.createClass({
required/> required/>
</Property> </Property>
<Property <Property
name="public" name="is_public"
className="ascribe-settings-property-collapsible-toggle" className="ascribe-settings-property-collapsible-toggle"
style={{paddingBottom: 0}}> style={{paddingBottom: 0}}>
<InputCheckbox> <InputCheckbox>

View File

@ -69,10 +69,11 @@ let LoanForm = React.createClass({
}, },
handleOnChange(event) { handleOnChange(event) {
let potentialEmail = event.target.value; // event.target.value is the submitted email of the loanee
if(event && event.target && event.target.value && event.target.value.match(/.*@.*/)) {
if(potentialEmail.match(/.*@.*/)) { ContractActions.fetchContract(event.target.value);
ContractActions.fetchContract(potentialEmail); } else {
ContractActions.flushContract();
} }
}, },
@ -143,6 +144,7 @@ let LoanForm = React.createClass({
ref='form' ref='form'
url={this.props.url} url={this.props.url}
getFormData={this.getFormData} getFormData={this.getFormData}
onReset={this.handleOnChange}
handleSuccess={this.props.handleSuccess} handleSuccess={this.props.handleSuccess}
buttons={this.getButtons()} buttons={this.getButtons()}
spinner={ spinner={

View File

@ -7,16 +7,13 @@ import UserActions from '../../actions/user_actions';
import Form from './form'; import Form from './form';
import Property from './property'; import Property from './property';
import InputFineUploader from './input_fineuploader';
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
import AppConstants from '../../constants/application_constants';
import ApiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
import { getCookie } from '../../utils/fetch_api_utils';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
import { mergeOptions } from '../../utils/general_utils'; import { mergeOptions } from '../../utils/general_utils';
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils'; import { isReadyForFormSubmission } from '../ascribe_uploader/react_s3_fine_uploader_utils';
let RegisterPieceForm = React.createClass({ let RegisterPieceForm = React.createClass({
@ -45,7 +42,6 @@ let RegisterPieceForm = React.createClass({
getInitialState(){ getInitialState(){
return mergeOptions( return mergeOptions(
{ {
digitalWorkKey: null,
isUploadReady: false isUploadReady: false
}, },
UserStore.getState() UserStore.getState()
@ -65,18 +61,6 @@ let RegisterPieceForm = React.createClass({
this.setState(state); this.setState(state);
}, },
getFormData(){
return {
digital_work_key: this.state.digitalWorkKey
};
},
submitKey(key){
this.setState({
digitalWorkKey: key
});
},
setIsUploadReady(isReady) { setIsUploadReady(isReady) {
this.setState({ this.setState({
isUploadReady: isReady isUploadReady: isReady
@ -94,14 +78,15 @@ let RegisterPieceForm = React.createClass({
className="ascribe-form-bordered" className="ascribe-form-bordered"
ref='form' ref='form'
url={ApiUrls.pieces_list} url={ApiUrls.pieces_list}
getFormData={this.getFormData}
handleSuccess={this.props.handleSuccess} handleSuccess={this.props.handleSuccess}
buttons={<button buttons={
type="submit" <button
className="btn ascribe-btn ascribe-btn-login" type="submit"
disabled={!this.state.isUploadReady || this.props.disabled}> className="btn ascribe-btn ascribe-btn-login"
{this.props.submitMessage} disabled={!this.state.isUploadReady || this.props.disabled}>
</button>} {this.props.submitMessage}
</button>
}
spinner={ spinner={
<span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner"> <span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" /> <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
@ -111,11 +96,11 @@ let RegisterPieceForm = React.createClass({
<h3>{this.props.headerMessage}</h3> <h3>{this.props.headerMessage}</h3>
</div> </div>
<Property <Property
name="digital_work_key"
ignoreFocus={true}> ignoreFocus={true}>
<FileUploader <InputFineUploader
submitKey={this.submitKey}
setIsUploadReady={this.setIsUploadReady} setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile} isReadyForFormSubmission={isReadyForFormSubmission}
isFineUploaderActive={this.props.isFineUploaderActive} isFineUploaderActive={this.props.isFineUploaderActive}
onLoggedOut={this.props.onLoggedOut} onLoggedOut={this.props.onLoggedOut}
editable={this.props.isFineUploaderEditable} editable={this.props.isFineUploaderEditable}
@ -152,73 +137,4 @@ let RegisterPieceForm = React.createClass({
} }
}); });
let FileUploader = React.createClass({
propTypes: {
setIsUploadReady: React.PropTypes.func,
submitKey: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func,
onClick: React.PropTypes.func,
// isFineUploaderActive is used to lock react fine uploader in case
// a user is actually not logged in already to prevent him from droping files
// before login in
isFineUploaderActive: React.PropTypes.bool,
onLoggedOut: React.PropTypes.func,
editable: React.PropTypes.bool,
enableLocalHashing: React.PropTypes.bool,
// provided by Property
disabled: React.PropTypes.bool
},
render() {
let editable = this.props.isFineUploaderActive;
// if disabled is actually set by property, we want to override
// isFineUploaderActive
if(typeof this.props.disabled !== 'undefined') {
editable = !this.props.disabled;
}
return (
<ReactS3FineUploader
onClick={this.props.onClick}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'digitalwork'
}}
createBlobRoutine={{
url: ApiUrls.blob_digitalworks
}}
validation={{
itemLimit: 100000,
sizeLimit: '25000000000'
}}
submitKey={this.props.submitKey}
setIsUploadReady={this.props.setIsUploadReady}
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
areAssetsDownloadable={false}
areAssetsEditable={editable}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/',
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
}}
deleteFile={{
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
}}
onInactive={this.props.onLoggedOut}
enableLocalHashing={this.props.enableLocalHashing} />
);
}
});
export default RegisterPieceForm; export default RegisterPieceForm;

View File

@ -6,7 +6,7 @@ import AclButton from './../ascribe_buttons/acl_button';
import ActionPanel from '../ascribe_panel/action_panel'; import ActionPanel from '../ascribe_panel/action_panel';
import Form from './form'; import Form from './form';
import PieceListActions from '../../actions/piece_list_actions'; import NotificationActions from '../../actions/notification_actions';
import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions'; import GlobalNotificationActions from '../../actions/global_notification_actions';
@ -22,8 +22,7 @@ let RequestActionForm = React.createClass({
React.PropTypes.object, React.PropTypes.object,
React.PropTypes.array React.PropTypes.array
]).isRequired, ]).isRequired,
requestAction: React.PropTypes.string, notifications: React.PropTypes.object,
requestUser: React.PropTypes.string,
currentUser: React.PropTypes.object, currentUser: React.PropTypes.object,
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func
}, },
@ -35,19 +34,19 @@ let RequestActionForm = React.createClass({
getUrls() { getUrls() {
let urls = {}; let urls = {};
if (this.props.requestAction === 'consign'){ if (this.props.notifications.action === 'consign'){
urls.accept = ApiUrls.ownership_consigns_confirm; urls.accept = ApiUrls.ownership_consigns_confirm;
urls.deny = ApiUrls.ownership_consigns_deny; urls.deny = ApiUrls.ownership_consigns_deny;
} else if (this.props.requestAction === 'unconsign'){ } else if (this.props.notifications.action === 'unconsign'){
urls.accept = ApiUrls.ownership_unconsigns; urls.accept = ApiUrls.ownership_unconsigns;
urls.deny = ApiUrls.ownership_unconsigns_deny; urls.deny = ApiUrls.ownership_unconsigns_deny;
} else if (this.props.requestAction === 'loan' && !this.isPiece()){ } else if (this.props.notifications.action === 'loan' && !this.isPiece()){
urls.accept = ApiUrls.ownership_loans_confirm; urls.accept = ApiUrls.ownership_loans_confirm;
urls.deny = ApiUrls.ownership_loans_deny; urls.deny = ApiUrls.ownership_loans_deny;
} else if (this.props.requestAction === 'loan' && this.isPiece()){ } else if (this.props.notifications.action === 'loan' && this.isPiece()){
urls.accept = ApiUrls.ownership_loans_pieces_confirm; urls.accept = ApiUrls.ownership_loans_pieces_confirm;
urls.deny = ApiUrls.ownership_loans_pieces_deny; urls.deny = ApiUrls.ownership_loans_pieces_deny;
} else if (this.props.requestAction === 'loan_request' && this.isPiece()){ } else if (this.props.notifications.action === 'loan_request' && this.isPiece()){
urls.accept = ApiUrls.ownership_loans_pieces_request_confirm; urls.accept = ApiUrls.ownership_loans_pieces_request_confirm;
urls.deny = ApiUrls.ownership_loans_pieces_request_deny; urls.deny = ApiUrls.ownership_loans_pieces_request_deny;
} }
@ -70,8 +69,8 @@ let RequestActionForm = React.createClass({
return () => { return () => {
let message = getLangText('You have successfully') + ' ' + option + ' the ' + action + ' request ' + getLangText('from') + ' ' + owner; let message = getLangText('You have successfully') + ' ' + option + ' the ' + action + ' request ' + getLangText('from') + ' ' + owner;
let notification = new GlobalNotificationModel(message, 'success'); let notifications = new GlobalNotificationModel(message, 'success');
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notifications);
this.handleSuccess(); this.handleSuccess();
@ -79,27 +78,27 @@ let RequestActionForm = React.createClass({
}, },
handleSuccess() { handleSuccess() {
PieceListActions.fetchPieceRequestActions(); if (this.isPiece()){
NotificationActions.fetchPieceListNotifications();
}
else {
NotificationActions.fetchEditionListNotifications();
}
if(this.props.handleSuccess) { if(this.props.handleSuccess) {
this.props.handleSuccess(); this.props.handleSuccess();
} }
}, },
getContent() { getContent() {
let pieceOrEditionStr = this.isPiece() ? getLangText('this work%s', '.') : getLangText('this edition%s', '.');
let message = this.props.requestUser + ' ' + getLangText('requests you') + ' ' + this.props.requestAction + ' ' + pieceOrEditionStr;
if (this.props.requestAction === 'loan_request'){
message = this.props.requestUser + ' ' + getLangText('requests you to loan') + ' ' + pieceOrEditionStr;
}
return ( return (
<span> <span>
{message} {this.props.notifications.action_str + ' by ' + this.props.notifications.by}
</span> </span>
); );
}, },
getAcceptButtonForm(urls) { getAcceptButtonForm(urls) {
if(this.props.requestAction === 'unconsign') { if(this.props.notifications.action === 'unconsign') {
return ( return (
<AclButton <AclButton
availableAcls={{'acl_unconsign': true}} availableAcls={{'acl_unconsign': true}}
@ -109,7 +108,7 @@ let RequestActionForm = React.createClass({
currentUser={this.props.currentUser} currentUser={this.props.currentUser}
handleSuccess={this.handleSuccess} /> handleSuccess={this.handleSuccess} />
); );
} else if(this.props.requestAction === 'loan_request') { } else if(this.props.notifications.action === 'loan_request') {
return ( return (
<AclButton <AclButton
availableAcls={{'acl_loan_request': true}} availableAcls={{'acl_loan_request': true}}
@ -126,7 +125,7 @@ let RequestActionForm = React.createClass({
url={urls.accept} url={urls.accept}
getFormData={this.getFormData} getFormData={this.getFormData}
handleSuccess={ handleSuccess={
this.showNotification(getLangText('accepted'), this.props.requestAction, this.props.requestUser) this.showNotification(getLangText('accepted'), this.props.notifications.action, this.props.notifications.by)
} }
isInline={true} isInline={true}
className='inline pull-right'> className='inline pull-right'>
@ -151,7 +150,7 @@ let RequestActionForm = React.createClass({
isInline={true} isInline={true}
getFormData={this.getFormData} getFormData={this.getFormData}
handleSuccess={ handleSuccess={
this.showNotification(getLangText('denied'), this.props.requestAction, this.props.requestUser) this.showNotification(getLangText('denied'), this.props.notifications.action, this.props.notifications.by)
} }
className='inline pull-right'> className='inline pull-right'>
<button <button

View File

@ -18,7 +18,8 @@ let InputDate = React.createClass({
getInitialState() { getInitialState() {
return { return {
value: null value: null,
value_moment: null
}; };
}, },
@ -45,6 +46,10 @@ let InputDate = React.createClass({
}); });
}, },
reset() {
this.setState(this.getInitialState());
},
render() { render() {
return ( return (
<div> <div>

View File

@ -0,0 +1,95 @@
'use strict';
import React from 'react';
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
import AppConstants from '../../constants/application_constants';
import ApiUrls from '../../constants/api_urls';
import { getCookie } from '../../utils/fetch_api_utils';
let InputFileUploader = React.createClass({
propTypes: {
setIsUploadReady: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func,
onClick: React.PropTypes.func,
// isFineUploaderActive is used to lock react fine uploader in case
// a user is actually not logged in already to prevent him from droping files
// before login in
isFineUploaderActive: React.PropTypes.bool,
onLoggedOut: React.PropTypes.func,
editable: React.PropTypes.bool,
enableLocalHashing: React.PropTypes.bool,
// provided by Property
disabled: React.PropTypes.bool
},
getInitialState() {
return {
value: null
};
},
submitKey(key){
this.setState({
value: key
});
},
reset() {
this.refs.fineuploader.reset();
},
render() {
let editable = this.props.isFineUploaderActive;
// if disabled is actually set by property, we want to override
// isFineUploaderActive
if(typeof this.props.disabled !== 'undefined') {
editable = !this.props.disabled;
}
return (
<ReactS3FineUploader
ref="fineuploader"
onClick={this.props.onClick}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'digitalwork'
}}
createBlobRoutine={{
url: ApiUrls.blob_digitalworks
}}
submitKey={this.submitKey}
validation={{
itemLimit: 100000,
sizeLimit: '25000000000'
}}
setIsUploadReady={this.props.setIsUploadReady}
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
areAssetsDownloadable={false}
areAssetsEditable={editable}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/',
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
}}
deleteFile={{
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
}}
onInactive={this.props.onLoggedOut}
enableLocalHashing={this.props.enableLocalHashing} />
);
}
});
export default InputFileUploader;

View File

@ -4,6 +4,7 @@ import React from 'react';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
let InputTextAreaToggable = React.createClass({ let InputTextAreaToggable = React.createClass({
propTypes: { propTypes: {
editable: React.PropTypes.bool.isRequired, editable: React.PropTypes.bool.isRequired,
@ -17,14 +18,17 @@ let InputTextAreaToggable = React.createClass({
value: this.props.defaultValue value: this.props.defaultValue
}; };
}, },
handleChange(event) { handleChange(event) {
this.setState({value: event.target.value}); this.setState({value: event.target.value});
this.props.onChange(event); this.props.onChange(event);
}, },
render() { render() {
let className = 'form-control ascribe-textarea'; let className = 'form-control ascribe-textarea';
let textarea = null; let textarea = null;
if (this.props.editable){
if(this.props.editable) {
className = className + ' ascribe-textarea-editable'; className = className + ' ascribe-textarea-editable';
textarea = ( textarea = (
<TextareaAutosize <TextareaAutosize
@ -37,10 +41,10 @@ let InputTextAreaToggable = React.createClass({
onBlur={this.props.onBlur} onBlur={this.props.onBlur}
placeholder={this.props.placeholder} /> placeholder={this.props.placeholder} />
); );
} } else {
else{
textarea = <pre className="ascribe-pre">{this.state.value}</pre>; textarea = <pre className="ascribe-pre">{this.state.value}</pre>;
} }
return textarea; return textarea;
} }
}); });

View File

@ -12,20 +12,19 @@ let ListRequestActions = React.createClass({
]).isRequired, ]).isRequired,
currentUser: React.PropTypes.object.isRequired, currentUser: React.PropTypes.object.isRequired,
handleSuccess: React.PropTypes.func.isRequired, handleSuccess: React.PropTypes.func.isRequired,
requestActions: React.PropTypes.array.isRequired notifications: React.PropTypes.array.isRequired
}, },
render () { render () {
if (this.props.requestActions && if (this.props.notifications &&
this.props.requestActions.length > 0) { this.props.notifications.length > 0) {
return ( return (
<div> <div>
{this.props.requestActions.map((requestAction) => {this.props.notifications.map((notification) =>
<RequestActionForm <RequestActionForm
currentUser={this.props.currentUser} currentUser={this.props.currentUser}
pieceOrEditions={ this.props.pieceOrEditions } pieceOrEditions={ this.props.pieceOrEditions }
requestAction={requestAction.action} notifications={notification}
requestUser={requestAction.by}
handleSuccess={this.props.handleSuccess}/>)} handleSuccess={this.props.handleSuccess}/>)}
</div> </div>
); );

View File

@ -29,8 +29,11 @@ let Property = React.createClass({
handleChange: React.PropTypes.func, handleChange: React.PropTypes.func,
ignoreFocus: React.PropTypes.bool, ignoreFocus: React.PropTypes.bool,
className: React.PropTypes.string, className: React.PropTypes.string,
onClick: React.PropTypes.func, onClick: React.PropTypes.func,
onChange: React.PropTypes.func, onChange: React.PropTypes.func,
onBlur: React.PropTypes.func,
children: React.PropTypes.oneOfType([ children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element React.PropTypes.element
@ -90,18 +93,28 @@ let Property = React.createClass({
// maybe do reset by reload instead of front end state? // maybe do reset by reload instead of front end state?
this.setState({value: this.state.initialValue}); this.setState({value: this.state.initialValue});
// resets the value of a custom react component input if (this.refs.input.state && this.refs.input.state.value) {
this.refs.input.state.value = this.state.initialValue; // resets the value of a custom react component input
this.refs.input.state.value = this.state.initialValue;
}
// resets the value of a plain HTML5 input // resets the value of a plain HTML5 input
this.refs.input.getDOMNode().value = this.state.initialValue; this.refs.input.getDOMNode().value = this.state.initialValue;
// For some inputs, reseting state.value is not enough to visually reset the
// component.
//
// So if the input actually needs a visual reset, it needs to implement
// a dedicated reset method.
if(this.refs.input.reset && typeof this.refs.input.reset === 'function') {
this.refs.input.reset();
}
}, },
handleChange(event) { handleChange(event) {
this.props.handleChange(event); this.props.handleChange(event);
if ('onChange' in this.props) { if (this.props.onChange && typeof this.props.onChange === 'function') {
this.props.onChange(event); this.props.onChange(event);
} }
@ -117,7 +130,7 @@ let Property = React.createClass({
// if onClick is defined from the outside, // if onClick is defined from the outside,
// just call it // just call it
if(this.props.onClick) { if(this.props.onClick && typeof this.props.onClick === 'function') {
this.props.onClick(); this.props.onClick();
} }
@ -132,7 +145,7 @@ let Property = React.createClass({
isFocused: false isFocused: false
}); });
if(this.props.onBlur) { if(this.props.onBlur && typeof this.props.onBlur === 'function') {
this.props.onBlur(event); this.props.onBlur(event);
} }
}, },
@ -190,6 +203,7 @@ let Property = React.createClass({
}, },
render() { render() {
let footer = null;
let tooltip = <span/>; let tooltip = <span/>;
let style = this.props.style ? mergeOptions({}, this.props.style) : {}; let style = this.props.style ? mergeOptions({}, this.props.style) : {};
@ -199,7 +213,7 @@ let Property = React.createClass({
{this.props.tooltip} {this.props.tooltip}
</Tooltip>); </Tooltip>);
} }
let footer = null;
if(this.props.footer){ if(this.props.footer){
footer = ( footer = (
<div className="ascribe-property-footer"> <div className="ascribe-property-footer">

View File

@ -42,6 +42,13 @@ let PropertyCollapsile = React.createClass({
} }
}, },
reset() {
// If the child input is a native HTML element, it will be reset automatically
// by the DOM.
// However, we need to collapse this component again.
this.setState(this.getInitialState());
},
render() { render() {
let tooltip = <span/>; let tooltip = <span/>;
if (this.props.tooltip){ if (this.props.tooltip){

View File

@ -28,12 +28,20 @@ let Other = React.createClass({
}, },
render() { render() {
let ext = this.props.url.split('.').pop(); let filename = this.props.url.split('/').pop();
let tokens = filename.split('.');
let preview;
if (tokens.length > 1) {
preview = '.' + tokens.pop();
} else {
preview = 'file';
}
return ( return (
<Panel className="media-other"> <Panel className="media-other">
<p className="text-center"> <p className="text-center">
.{ext} {preview}
</p> </p>
</Panel> </Panel>
); );

View File

@ -1,82 +0,0 @@
'use strict';
import React from 'react';
import PrizeListActions from '../../actions/prize_list_actions';
import PrizeListStore from '../../stores/prize_list_store';
import Table from '../ascribe_table/table';
import TableItem from '../ascribe_table/table_item';
import TableItemText from '../ascribe_table/table_item_text';
import { ColumnModel} from '../ascribe_table/models/table_models';
import { getLangText } from '../../utils/lang_utils';
let PrizesDashboard = React.createClass({
getInitialState() {
return PrizeListStore.getState();
},
componentDidMount() {
PrizeListStore.listen(this.onChange);
PrizeListActions.fetchPrizeList();
},
componentWillUnmount() {
PrizeListStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
getColumnList() {
return [
new ColumnModel(
(item) => {
return {
'content': item.name
}; },
'name',
getLangText('Name'),
TableItemText,
6,
false,
null
),
new ColumnModel(
(item) => {
return {
'content': item.domain
}; },
'domain',
getLangText('Domain'),
TableItemText,
1,
false,
null
)
];
},
render() {
return (
<Table
responsive
className="ascribe-table"
columnList={this.getColumnList()}
itemList={this.state.prizeList}>
{this.state.prizeList.map((item, i) => {
return (
<TableItem
className="ascribe-table-item-selectable"
key={i}/>
);
})}
</Table>
);
}
});
export default PrizesDashboard;

View File

@ -26,7 +26,7 @@ let ContractSettings = React.createClass({
componentDidMount() { componentDidMount() {
ContractListStore.listen(this.onChange); ContractListStore.listen(this.onChange);
ContractListActions.fetchContractList(); ContractListActions.fetchContractList({is_active: 'True'});
}, },
componentWillUnmount() { componentWillUnmount() {
@ -39,32 +39,42 @@ let ContractSettings = React.createClass({
makeContractPublic(contract) { makeContractPublic(contract) {
return () => { return () => {
ContractListActions.makeContractPublic(contract) contract.is_public = true;
.then(() => ContractListActions.fetchContractList()) ContractListActions.changeContract(contract)
.catch((error) => { .then(() => {
let notification = new GlobalNotificationModel(error, 'success', 10000); ContractListActions.fetchContractList({is_active: 'True'});
let notification = getLangText('Contract %s is now public', contract.name);
notification = new GlobalNotificationModel(notification, 'success', 4000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}); })
.catch((err) => {
let notification = new GlobalNotificationModel(err, 'danger', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
});
}; };
}, },
removeContract(contract) { removeContract(contract) {
return () => { return () => {
ContractListActions.removeContract(contract.id) ContractListActions.removeContract(contract.id)
.then(( ) => ContractListActions.fetchContractList()) .then((response) => {
.catch((error) => { ContractListActions.fetchContractList({is_active: 'True'});
let notification = new GlobalNotificationModel(error, 'danger', 10000); let notification = new GlobalNotificationModel(response.notification, 'success', 4000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}); })
.catch((err) => {
let notification = new GlobalNotificationModel(err, 'danger', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
});
}; };
}, },
getPublicContracts(){ getPublicContracts(){
return this.state.contractList.filter((contract) => contract.public); return this.state.contractList.filter((contract) => contract.is_public);
}, },
getPrivateContracts(){ getPrivateContracts(){
return this.state.contractList.filter((contract) => !contract.public); return this.state.contractList.filter((contract) => !contract.is_public);
}, },
render() { render() {
@ -95,8 +105,9 @@ let ContractSettings = React.createClass({
<button className="btn btn-default btn-sm margin-left-2px"> <button className="btn btn-default btn-sm margin-left-2px">
UPDATE UPDATE
</button> </button>
<button className="btn btn-default btn-sm margin-left-2px" <button
onClick={this.removeContract(contract)}> className="btn btn-default btn-sm margin-left-2px"
onClick={this.removeContract(contract)}>
REMOVE REMOVE
</button> </button>
</div> </div>
@ -121,12 +132,14 @@ let ContractSettings = React.createClass({
<button className="btn btn-default btn-sm margin-left-2px"> <button className="btn btn-default btn-sm margin-left-2px">
UPDATE UPDATE
</button> </button>
<button className="btn btn-default btn-sm margin-left-2px" <button
onClick={this.removeContract(contract)}> className="btn btn-default btn-sm margin-left-2px"
onClick={this.removeContract(contract)}>
REMOVE REMOVE
</button> </button>
<button className="btn btn-default btn-sm margin-left-2px" <button
onClick={this.makeContractPublic(contract)}> className="btn btn-default btn-sm margin-left-2px"
onClick={this.makeContractPublic(contract)}>
MAKE PUBLIC MAKE PUBLIC
</button> </button>
</div> </div>

View File

@ -4,13 +4,12 @@ import React from 'react';
import Router from 'react-router'; import Router from 'react-router';
import ReactAddons from 'react/addons'; import ReactAddons from 'react/addons';
import Col from 'react-bootstrap/lib/Col';
import SlidesContainerBreadcrumbs from './slides_container_breadcrumbs'; import SlidesContainerBreadcrumbs from './slides_container_breadcrumbs';
let State = Router.State; let State = Router.State;
let Navigation = Router.Navigation; let Navigation = Router.Navigation;
let SlidesContainer = React.createClass({ let SlidesContainer = React.createClass({
propTypes: { propTypes: {
children: React.PropTypes.arrayOf(React.PropTypes.element), children: React.PropTypes.arrayOf(React.PropTypes.element),
@ -30,12 +29,15 @@ let SlidesContainer = React.createClass({
let slideNum = -1; let slideNum = -1;
let startFrom = -1; let startFrom = -1;
// We can actually need to check if slide_num is present as a key in queryParams.
// We do not really care about its value though...
if(queryParams && 'slide_num' in queryParams) { if(queryParams && 'slide_num' in queryParams) {
slideNum = parseInt(queryParams.slide_num, 10); slideNum = parseInt(queryParams.slide_num, 10);
} }
// if slide_num is not set, this will be done in componentDidMount // if slide_num is not set, this will be done in componentDidMount
// the query param 'start_from' removes all slide children before the respective number // the query param 'start_from' removes all slide children before the respective number
// Also, we use the 'in' keyword for the same reason as above in 'slide_num'
if(queryParams && 'start_from' in queryParams) { if(queryParams && 'start_from' in queryParams) {
startFrom = parseInt(queryParams.start_from, 10); startFrom = parseInt(queryParams.start_from, 10);
} }
@ -51,6 +53,9 @@ let SlidesContainer = React.createClass({
componentDidMount() { componentDidMount() {
// check if slide_num was defined, and if not then default to 0 // check if slide_num was defined, and if not then default to 0
let queryParams = this.getQuery(); let queryParams = this.getQuery();
// We use 'in' to check if the key is present in the user's browser url bar,
// we do not really care about its value at this point
if(!('slide_num' in queryParams)) { if(!('slide_num' in queryParams)) {
// we're first requiring all the other possible queryParams and then set // we're first requiring all the other possible queryParams and then set
@ -241,6 +246,16 @@ let SlidesContainer = React.createClass({
}, },
render() { render() {
let spacing = this.state.containerWidth * this.state.slideNum;
let translateXValue = 'translateX(' + (-1) * spacing + 'px)';
/*
According to the react documentation,
all browser vendor prefixes need to be upper cases in the beginning except for
the Microsoft one *bigfuckingsurprise*
https://facebook.github.io/react/tips/inline-styles.html
*/
return ( return (
<div <div
className="container ascribe-sliding-container-wrapper" className="container ascribe-sliding-container-wrapper"
@ -250,7 +265,11 @@ let SlidesContainer = React.createClass({
className="container ascribe-sliding-container" className="container ascribe-sliding-container"
style={{ style={{
width: this.state.containerWidth * this.customChildrenCount(), width: this.state.containerWidth * this.customChildrenCount(),
transform: 'translateX(' + (-1) * this.state.containerWidth * this.state.slideNum + 'px)' transform: translateXValue,
WebkitTransform: translateXValue,
MozTransform: translateXValue,
OTransform: translateXValue,
mstransform: translateXValue
}}> }}>
<div className="row"> <div className="row">
{this.renderChildren()} {this.renderChildren()}

View File

@ -6,15 +6,15 @@ import React from 'react';
let TableItemAclFiltered = React.createClass({ let TableItemAclFiltered = React.createClass({
propTypes: { propTypes: {
content: React.PropTypes.object, content: React.PropTypes.object,
requestAction: React.PropTypes.string notifications: React.PropTypes.string
}, },
render() { render() {
var availableAcls = ['acl_consign', 'acl_loan', 'acl_transfer', 'acl_view', 'acl_share', 'acl_unshare', 'acl_delete']; var availableAcls = ['acl_consign', 'acl_loan', 'acl_transfer', 'acl_view', 'acl_share', 'acl_unshare', 'acl_delete'];
if (this.props.requestAction && this.props.requestAction.length > 0){ if (this.props.notifications && this.props.notifications.length > 0){
return ( return (
<span> <span>
{this.props.requestAction[0].action + ' request pending'} {this.props.notifications[0].action_str}
</span> </span>
); );
} }

View File

@ -20,7 +20,6 @@ import AppConstants from '../../constants/application_constants';
import { computeHashOfFile } from '../../utils/file_utils'; import { computeHashOfFile } from '../../utils/file_utils';
var ReactS3FineUploader = React.createClass({ var ReactS3FineUploader = React.createClass({
propTypes: { propTypes: {
keyRoutine: React.PropTypes.shape({ keyRoutine: React.PropTypes.shape({
url: React.PropTypes.string, url: React.PropTypes.string,
@ -125,6 +124,7 @@ var ReactS3FineUploader = React.createClass({
bucket: 'ascribe0' bucket: 'ascribe0'
}, },
request: { request: {
//endpoint: 'https://www.ascribe.io.global.prod.fastly.net',
endpoint: 'https://ascribe0.s3.amazonaws.com', endpoint: 'https://ascribe0.s3.amazonaws.com',
accessKey: 'AKIAIVCZJ33WSCBQ3QDA' accessKey: 'AKIAIVCZJ33WSCBQ3QDA'
}, },
@ -235,6 +235,21 @@ var ReactS3FineUploader = React.createClass({
}; };
}, },
// Resets the whole react fineuploader component to its initial state
reset() {
// Cancel all currently ongoing uploads
this.state.uploader.cancelAll();
// and reset component in general
this.state.uploader.reset();
// proclaim that upload is not ready
this.props.setIsUploadReady(false);
// reset internal data structures of component
this.setState(this.getInitialState());
},
requestKey(fileId) { requestKey(fileId) {
let filename = this.state.uploader.getName(fileId); let filename = this.state.uploader.getName(fileId);
let uuid = this.state.uploader.getUuid(fileId); let uuid = this.state.uploader.getUuid(fileId);
@ -356,64 +371,59 @@ var ReactS3FineUploader = React.createClass({
onComplete(id, name, res, xhr) { onComplete(id, name, res, xhr) {
// there has been an issue with the server's connection // there has been an issue with the server's connection
if(xhr.status === 0) { if((xhr && xhr.status === 0) || res.error) {
console.logGlobal(new Error(res.error || 'Complete was called but there wasn\t a success'), false, {
console.logGlobal(new Error('Complete was called but there wasn\t a success'), false, {
files: this.state.filesToUpload, files: this.state.filesToUpload,
chunks: this.state.chunks chunks: this.state.chunks
}); });
} else {
let files = this.state.filesToUpload;
return; // Set the state of the completed file to 'upload successful' in order to
} // remove it from the GUI
files[id].status = 'upload successful';
files[id].key = this.state.uploader.getKey(id);
let files = this.state.filesToUpload; let newState = React.addons.update(this.state, {
filesToUpload: { $set: files }
// Set the state of the completed file to 'upload successful' in order to
// remove it from the GUI
files[id].status = 'upload successful';
files[id].key = this.state.uploader.getKey(id);
let newState = React.addons.update(this.state, {
filesToUpload: { $set: files }
});
this.setState(newState);
// Only after the blob has been created server-side, we can make the form submittable.
this.createBlob(files[id])
.then(() => {
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey
// are optional, we'll only trigger them when they're actually defined
if(this.props.submitKey) {
this.props.submitKey(files[id].key);
} else {
console.warn('You didn\'t define submitKey in as a prop in react-s3-fine-uploader');
}
// for explanation, check comment of if statement above
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
// also, lets check if after the completion of this upload,
// the form is ready for submission or not
if(this.props.isReadyForFormSubmission(this.state.filesToUpload)) {
// if so, set uploadstatus to true
this.props.setIsUploadReady(true);
} else {
this.props.setIsUploadReady(false);
}
} else {
console.warn('You didn\'t define the functions isReadyForFormSubmission and/or setIsUploadReady in as a prop in react-s3-fine-uploader');
}
})
.catch((err) => {
console.logGlobal(err, false, {
files: this.state.filesToUpload,
chunks: this.state.chunks
});
let notification = new GlobalNotificationModel(err.message, 'danger', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
}); });
this.setState(newState);
// Only after the blob has been created server-side, we can make the form submittable.
this.createBlob(files[id])
.then(() => {
// since the form validation props isReadyForFormSubmission, setIsUploadReady and submitKey
// are optional, we'll only trigger them when they're actually defined
if(this.props.submitKey) {
this.props.submitKey(files[id].key);
} else {
console.warn('You didn\'t define submitKey in as a prop in react-s3-fine-uploader');
}
// for explanation, check comment of if statement above
if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) {
// also, lets check if after the completion of this upload,
// the form is ready for submission or not
if(this.props.isReadyForFormSubmission(this.state.filesToUpload)) {
// if so, set uploadstatus to true
this.props.setIsUploadReady(true);
} else {
this.props.setIsUploadReady(false);
}
} else {
console.warn('You didn\'t define the functions isReadyForFormSubmission and/or setIsUploadReady in as a prop in react-s3-fine-uploader');
}
})
.catch((err) => {
console.logGlobal(err, false, {
files: this.state.filesToUpload,
chunks: this.state.chunks
});
let notification = new GlobalNotificationModel(err.message, 'danger', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
});
}
}, },
onError(id, name, errorReason) { onError(id, name, errorReason) {
@ -442,7 +452,6 @@ var ReactS3FineUploader = React.createClass({
}, },
onCancel(id) { onCancel(id) {
// when a upload is canceled, we need to update this components file array // when a upload is canceled, we need to update this components file array
this.setStatusOfFile(id, 'canceled'); this.setStatusOfFile(id, 'canceled');
@ -464,7 +473,6 @@ var ReactS3FineUploader = React.createClass({
}, },
onProgress(id, name, uploadedBytes, totalBytes) { onProgress(id, name, uploadedBytes, totalBytes) {
let newState = React.addons.update(this.state, { let newState = React.addons.update(this.state, {
filesToUpload: { [id]: { filesToUpload: { [id]: {
progress: { $set: (uploadedBytes / totalBytes) * 100} } progress: { $set: (uploadedBytes / totalBytes) * 100} }

View File

@ -0,0 +1,36 @@
'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;

View File

@ -96,6 +96,31 @@ let Header = React.createClass({
} }
}, },
onMenuItemClick(event) {
/*
This is a hack to make the dropdown close after clicking on an item
The function just need to be defined
from https://github.com/react-bootstrap/react-bootstrap/issues/368:
@jvillasante - Have you tried to use onSelect with the DropdownButton?
I don't have a working example that is exactly like yours,
but I just noticed that the Dropdown closes when I've attached an event handler to OnSelect:
<DropdownButton eventKey={3} title="Admin" onSelect={ this.OnSelected } >
onSelected: function(e) {
// doesn't need to have functionality (necessarily) ... just wired up
}
Internally, a call to DropdownButton.setDropDownState(false) is made which will hide the dropdown menu.
So, you should be able to call that directly on the DropdownButton instance as well if needed.
NOW, THAT DIDN'T WORK - the onSelect routine isnt triggered in all cases
Hence, we do this manually
*/
this.refs.dropdownbutton.setDropdownState(false);
},
render() { render() {
let account; let account;
let signup; let signup;
@ -103,9 +128,15 @@ let Header = React.createClass({
if (this.state.currentUser.username){ if (this.state.currentUser.username){
account = ( account = (
<DropdownButton <DropdownButton
ref='dropdownbutton'
eventKey="1" eventKey="1"
title={this.state.currentUser.username}> title={this.state.currentUser.username}>
<MenuItemLink eventKey="2" to="settings">{getLangText('Account Settings')}</MenuItemLink> <MenuItemLink
eventKey="2"
to="settings"
onClick={this.onMenuItemClick}>
{getLangText('Account Settings')}
</MenuItemLink>
<MenuItem divider /> <MenuItem divider />
<MenuItemLink eventKey="3" to="logout">{getLangText('Log out')}</MenuItemLink> <MenuItemLink eventKey="3" to="logout">{getLangText('Log out')}</MenuItemLink>
</DropdownButton> </DropdownButton>

View File

@ -8,7 +8,8 @@ import MenuItem from 'react-bootstrap/lib/MenuItem';
import Nav from 'react-bootstrap/lib/Nav'; import Nav from 'react-bootstrap/lib/Nav';
import PieceListStore from '../stores/piece_list_store'; import NotificationActions from '../actions/notification_actions';
import NotificationStore from '../stores/notification_store';
import { mergeOptions } from '../utils/general_utils'; import { mergeOptions } from '../utils/general_utils';
import { getLangText } from '../utils/lang_utils'; import { getLangText } from '../utils/lang_utils';
@ -20,23 +21,25 @@ let HeaderNotifications = React.createClass({
getInitialState() { getInitialState() {
return mergeOptions( return mergeOptions(
PieceListStore.getState() NotificationStore.getState()
); );
}, },
componentDidMount() { componentDidMount() {
PieceListStore.listen(this.onChange); NotificationStore.listen(this.onChange);
NotificationActions.fetchPieceListNotifications();
NotificationActions.fetchEditionListNotifications();
}, },
componentWillUnmount() { componentWillUnmount() {
PieceListStore.unlisten(this.onChange); NotificationStore.unlisten(this.onChange);
}, },
onChange(state) { onChange(state) {
this.setState(state); this.setState(state);
}, },
onSelected(event) { onMenuItemClick(event) {
/* /*
This is a hack to make the dropdown close after clicking on an item This is a hack to make the dropdown close after clicking on an item
The function just need to be defined The function just need to be defined
@ -54,32 +57,87 @@ let HeaderNotifications = React.createClass({
} }
Internally, a call to DropdownButton.setDropDownState(false) is made which will hide the dropdown menu. Internally, a call to DropdownButton.setDropDownState(false) is made which will hide the dropdown menu.
So, you should be able to call that directly on the DropdownButton instance as well if needed. So, you should be able to call that directly on the DropdownButton instance as well if needed.
NOW, THAT DIDN'T WORK - the onSelect routine isnt triggered in all cases
Hence, we do this manually
*/ */
this.refs.dropdownbutton.setDropdownState(false);
},
getPieceNotifications(){
if (this.state.pieceListNotifications && this.state.pieceListNotifications.length > 0) {
return (
<div>
<div className="notification-header">
Artworks ({this.state.pieceListNotifications.length})
</div>
{this.state.pieceListNotifications.map((pieceNotification, i) => {
return (
<MenuItem eventKey={i + 2}>
<NotificationListItem
ref={i}
notification={pieceNotification.notification}
pieceOrEdition={pieceNotification.piece}
onClick={this.onMenuItemClick}/>
</MenuItem>
);
}
)}
</div>
);
}
return null;
},
getEditionNotifications(){
if (this.state.editionListNotifications && this.state.editionListNotifications.length > 0) {
return (
<div>
<div className="notification-header">
Editions ({this.state.editionListNotifications.length})
</div>
{this.state.editionListNotifications.map((editionNotification, i) => {
return (
<MenuItem eventKey={i + 2}>
<NotificationListItem
ref={'edition' + i}
notification={editionNotification.notification}
pieceOrEdition={editionNotification.edition}
onClick={this.onMenuItemClick}/>
</MenuItem>
);
}
)}
</div>
);
}
return null;
}, },
render() { render() {
if (this.state.requestActions && this.state.requestActions.length > 0) { if ((this.state.pieceListNotifications && this.state.pieceListNotifications.length > 0) ||
(this.state.editionListNotifications && this.state.editionListNotifications.length > 0)){
let numNotifications = 0;
if (this.state.pieceListNotifications && this.state.pieceListNotifications.length > 0) {
numNotifications += this.state.pieceListNotifications.length;
}
if (this.state.editionListNotifications && this.state.editionListNotifications.length > 0) {
numNotifications += this.state.editionListNotifications.length;
}
return ( return (
<Nav navbar right> <Nav navbar right>
<DropdownButton <DropdownButton
ref='dropdownbutton'
eventKey="1" eventKey="1"
title={ title={
<span> <span>
<Glyphicon glyph='envelope' color="green"/> <Glyphicon glyph='envelope' color="green"/>
<span className="notification-amount">({this.state.requestActions.length})</span> <span className="notification-amount">({numNotifications})</span>
</span> </span>
} }
className="notification-menu" className="notification-menu">
onSelect={this.onSelected}> {this.getPieceNotifications()}
{this.state.requestActions.map((pieceOrEdition, i) => { {this.getEditionNotifications()}
return (
<MenuItem eventKey={i + 2}>
<NotificationListItem
ref={i}
pieceOrEdition={pieceOrEdition}/>
</MenuItem>);
}
)}
</DropdownButton> </DropdownButton>
</Nav> </Nav>
); );
@ -90,33 +148,55 @@ let HeaderNotifications = React.createClass({
let NotificationListItem = React.createClass({ let NotificationListItem = React.createClass({
propTypes: { propTypes: {
pieceOrEdition: React.PropTypes.object notification: React.PropTypes.array,
pieceOrEdition: React.PropTypes.object,
onClick: React.PropTypes.func
},
isPiece() {
return !(this.props.pieceOrEdition && this.props.pieceOrEdition.parent);
}, },
getLinkData() { getLinkData() {
if(this.props.pieceOrEdition && this.props.pieceOrEdition.parent) { if (this.isPiece()) {
return {
to: 'edition',
params: {
editionId: this.props.pieceOrEdition.bitcoin_id
}
};
} else {
return { return {
to: 'piece', to: 'piece',
params: { params: {
pieceId: this.props.pieceOrEdition.id pieceId: this.props.pieceOrEdition.id
} }
}; };
} else {
return {
to: 'edition',
params: {
editionId: this.props.pieceOrEdition.bitcoin_id
}
};
} }
}, },
onClick(event){
this.props.onClick(event);
},
getNotificationText(){
let numNotifications = null;
if (this.props.notification.length > 1){
numNotifications = <div>+ {this.props.notification.length - 1} more...</div>;
}
return (
<div className="notification-action">
{this.props.notification[0].action_str}
{numNotifications}
</div>);
},
render() { render() {
if (this.props.pieceOrEdition) { if (this.props.pieceOrEdition) {
return ( return (
<Link {...this.getLinkData()}> <Link {...this.getLinkData()} onClick={this.onClick}>
<div className="row notification-wrapper"> <div className="row notification-wrapper">
<div className="col-xs-4 clear-paddings"> <div className="col-xs-4 clear-paddings">
<div className="thumbnail-wrapper"> <div className="thumbnail-wrapper">
@ -126,13 +206,7 @@ let NotificationListItem = React.createClass({
<div className="col-xs-8 notification-list-item-header"> <div className="col-xs-8 notification-list-item-header">
<h1>{this.props.pieceOrEdition.title}</h1> <h1>{this.props.pieceOrEdition.title}</h1>
<div className="sub-header">by {this.props.pieceOrEdition.artist_name}</div> <div className="sub-header">by {this.props.pieceOrEdition.artist_name}</div>
<div className="notification-action"> {this.getNotificationText()}
{
this.props.pieceOrEdition.request_action.map((requestAction) => {
return 'Pending ' + requestAction.action + ' request';
})
}
</div>
</div> </div>
</div> </div>
</Link>); </Link>);

View File

@ -65,8 +65,7 @@ let PieceList = React.createClass({
let orderBy = this.props.orderBy ? this.props.orderBy : this.state.orderBy; let orderBy = this.props.orderBy ? this.props.orderBy : this.state.orderBy;
if (this.state.pieceList.length === 0 || this.state.page !== page){ if (this.state.pieceList.length === 0 || this.state.page !== page){
PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search, PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search,
orderBy, this.state.orderAsc, this.state.filterBy) orderBy, this.state.orderAsc, this.state.filterBy);
.then(() => PieceListActions.fetchPieceRequestActions());
} }
}, },

View File

@ -88,15 +88,37 @@ let PieceContainer = React.createClass({
PieceActions.fetchOne(this.props.params.pieceId); PieceActions.fetchOne(this.props.params.pieceId);
}, },
getActions() {
if (this.state.piece &&
this.state.piece.notifications &&
this.state.piece.notifications.length > 0) {
return (
<ListRequestActions
pieceOrEditions={this.state.piece}
currentUser={this.state.currentUser}
handleSuccess={this.loadPiece}
notifications={this.state.piece.notifications}/>);
}
},
render() { render() {
if('title' in this.state.piece) { if(this.state.piece && this.state.piece.title) {
/*
This really needs a refactor!
- Tim
*/
// Only show the artist name if you are the participant or if you are a judge and the piece is shortlisted // Only show the artist name if you are the participant or if you are a judge and the piece is shortlisted
let artistName = ((this.state.currentUser.is_jury && !this.state.currentUser.is_judge) || let artistName = ((this.state.currentUser.is_jury && !this.state.currentUser.is_judge) ||
(this.state.currentUser.is_judge && !this.state.piece.selected )) ? (this.state.currentUser.is_judge && !this.state.piece.selected )) ?
<span className="glyphicon glyphicon-eye-close" aria-hidden="true"/> : this.state.piece.artist_name; <span className="glyphicon glyphicon-eye-close" aria-hidden="true"/> : this.state.piece.artist_name;
// Only show the artist email if you are a judge and the piece is shortlisted // Only show the artist email if you are a judge and the piece is shortlisted
let artistEmail = (this.state.currentUser.is_judge && this.state.piece.selected ) ? let artistEmail = (this.state.currentUser.is_judge && this.state.piece.selected ) ?
<DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } /> : null; <DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } /> : null;
return ( return (
<Piece <Piece
piece={this.state.piece} piece={this.state.piece}
@ -111,11 +133,7 @@ let PieceContainer = React.createClass({
<DetailProperty label={getLangText('BY')} value={artistName} /> <DetailProperty label={getLangText('BY')} value={artistName} />
<DetailProperty label={getLangText('DATE')} value={ this.state.piece.date_created.slice(0, 4) } /> <DetailProperty label={getLangText('DATE')} value={ this.state.piece.date_created.slice(0, 4) } />
{artistEmail} {artistEmail}
<ListRequestActions {this.getActions()}
pieceOrEditions={this.state.piece}
currentUser={this.state.currentUser}
handleSuccess={this.loadPiece}
requestActions={this.state.piece.request_action}/>
<hr/> <hr/>
</div> </div>
} }

View File

@ -68,7 +68,7 @@ let CylandPieceContainer = React.createClass({
}, },
render() { render() {
if('title' in this.state.piece) { if(this.state.piece && this.state.piece.title) {
return ( return (
<Piece <Piece
piece={this.state.piece} piece={this.state.piece}

View File

@ -72,6 +72,9 @@ let CylandRegisterPiece = React.createClass({
// we may need to enter the process at step 1 or 2. // 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. // 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. // It is encoded in the URL as a queryParam and we're checking for it here.
//
// We're using 'in' here as we want to know if 'piece_id' is present in the url,
// we don't care about the value.
if(queryParams && 'piece_id' in queryParams) { if(queryParams && 'piece_id' in queryParams) {
PieceActions.fetchOne(queryParams.piece_id); PieceActions.fetchOne(queryParams.piece_id);
} }

View File

@ -58,7 +58,7 @@ let IkonotvSubmitButton = React.createClass({
<InputCheckbox> <InputCheckbox>
<span> <span>
{' ' + getLangText('I agree to the Terms of Service of IkonoTV Archive') + ' '} {' ' + getLangText('I agree to the Terms of Service of IkonoTV Archive') + ' '}
(<a href="https://d1qjsxua1o9x03.cloudfront.net/live/743394beff4b1282ba735e5e3723ed74/contract/bbc92f1d-4504-49f8-818c-8dd7113c6e06.pdf" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}> (<a href="https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/ikonotv/ikono-tos.pdf" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
{getLangText('read')} {getLangText('read')}
</a>) </a>)
</span> </span>

View File

@ -89,15 +89,14 @@ let IkonotvPieceContainer = React.createClass({
getActions(){ getActions(){
if (this.state.piece && if (this.state.piece &&
this.state.piece.request_action && this.state.piece.notifications &&
this.state.piece.request_action.length > 0) { this.state.piece.notifications.length > 0) {
return ( return (
<ListRequestActions <ListRequestActions
pieceOrEditions={this.state.piece} pieceOrEditions={this.state.piece}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.loadPiece} handleSuccess={this.loadPiece}
requestActions={this.state.piece.request_action}/> notifications={this.state.piece.notifications}/>);
);
} }
else { else {
@ -133,7 +132,7 @@ let IkonotvPieceContainer = React.createClass({
}, },
render() { render() {
if('title' in this.state.piece) { if(this.state.piece && this.state.piece.title) {
return ( return (
<Piece <Piece
piece={this.state.piece} piece={this.state.piece}

View File

@ -0,0 +1,180 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import Button from 'react-bootstrap/lib/Button';
import Form from '../../../../ascribe_forms/form';
import Property from '../../../../ascribe_forms/property';
import InputCheckbox from '../../../../ascribe_forms/input_checkbox';
import NotificationActions from '../../../../../actions/notification_actions';
import NotificationStore from '../../../../../stores/notification_store';
import WhitelabelStore from '../../../../../stores/whitelabel_store';
import GlobalNotificationModel from '../../../../../models/global_notification_model';
import GlobalNotificationActions from '../../../../../actions/global_notification_actions';
import apiUrls from '../../../../../constants/api_urls';
import requests from '../../../../../utils/requests';
import { getLangText } from '../../../../../utils/lang_utils';
import { mergeOptions } from '../../../../../utils/general_utils';
let Navigation = Router.Navigation;
let IkonotvContractNotifications = React.createClass({
mixins: [Navigation],
getInitialState() {
return mergeOptions(
NotificationStore.getState(),
WhitelabelStore.getState()
);
},
componentDidMount() {
NotificationStore.listen(this.onChange);
WhitelabelStore.listen(this.onChange);
if (this.state.contractAgreementListNotifications === null){
NotificationActions.fetchContractAgreementListNotifications();
}
},
componentWillUnmount() {
NotificationStore.unlisten(this.onChange);
WhitelabelStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
getContract(){
let notifications = this.state.contractAgreementListNotifications[0];
let blob = notifications.contract_agreement.contract.blob;
if (blob.mime === 'pdf') {
return (
<div className='notification-contract-pdf'>
<embed src={blob.url_safe} alt="pdf"
pluginspage="http://www.adobe.com/products/acrobat/readstep2.html"/>
<div className='notification-contract-pdf-download'>
<a href={blob.url_safe} target="_blank">
<Glyphicon glyph='download-alt'/>
<span style={{padding: '0.3em'}}>
Download PDF version
</span>
</a>
</div>
</div>
);
}
return (
<div className='notification-contract-download'>
<a href={blob.url_safe} target="_blank">
<Glyphicon glyph='download-alt'/>
<span style={{padding: '0.3em'}}>
Download contract
</span>
</a>
</div>
);
},
getAppendix() {
let notifications = this.state.contractAgreementListNotifications[0];
let appendix = notifications.contract_agreement.appendix;
if (appendix) {
return (<div>
<h1>{getLangText('Appendix')}</h1>
<pre>
{appendix.default}
</pre>
</div>
);
}
return null;
},
handleConfirmSuccess() {
let notification = new GlobalNotificationModel(getLangText('You have accepted the conditions'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
this.transitionTo('pieces');
},
handleDeny() {
let contractAgreement = this.state.contractAgreementListNotifications[0].contract_agreement;
requests.put(apiUrls.ownership_contract_agreements_deny, {contract_agreement_id: contractAgreement.id}).then(
() => this.handleDenySuccess()
);
},
handleDenySuccess() {
let notification = new GlobalNotificationModel(getLangText('You have denied the conditions'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
this.transitionTo('pieces');
},
render() {
if (this.state.contractAgreementListNotifications &&
this.state.contractAgreementListNotifications.length > 0) {
let contractAgreement = this.state.contractAgreementListNotifications[0].contract_agreement;
return (
<div className='container'>
<div className='notification-contract-wrapper'>
<div className='notification-contract-logo'>
<img src={this.state.whitelabel.logo}/>
<div className='notification-contract-header'>
{getLangText('Production Contract')}
</div>
</div>
{this.getContract()}
<div className='notification-contract-footer'>
{this.getAppendix}
<h1>{getLangText('Are you a member of any copyright societies?')}</h1>
<p>
ARS, DACS, Bildkunst, Pictoright, SODRAC, Copyright Agency/Viscopy, SAVA, 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
</p>
<Form
ref='form'
url={requests.prepareUrl(apiUrls.ownership_contract_agreements_confirm, {contract_agreement_id: contractAgreement.id})}
handleSuccess={this.handleConfirmSuccess}
method='put'
buttons={
<p style={{marginTop: '1em'}}>
<Button type="submit">{getLangText('I agree with the conditions')}</Button>
<Button bsStyle="danger" className="btn-delete" bsSize="medium" onClick={this.handleDeny}>
{getLangText('I disagree')}
</Button>
</p>
}>
<Property
name="terms"
className="ascribe-settings-property-collapsible-toggle"
style={{paddingBottom: 0}}>
<InputCheckbox>
<span>
{' ' + getLangText('Yes') }
</span>
</InputCheckbox>
</Property>
</Form>
</div>
</div>
</div>
);
}
return null;
}
});
export default IkonotvContractNotifications;

View File

@ -22,6 +22,7 @@ import CylandPieceList from './components/cyland/cyland_piece_list';
import IkonotvPieceList from './components/ikonotv/ikonotv_piece_list'; import IkonotvPieceList from './components/ikonotv/ikonotv_piece_list';
import IkonotvRequestLoan from './components/ikonotv/ikonotv_request_loan'; import IkonotvRequestLoan from './components/ikonotv/ikonotv_request_loan';
import IkonotvPieceContainer from './components/ikonotv/ascribe_detail/ikonotv_piece_container'; import IkonotvPieceContainer from './components/ikonotv/ascribe_detail/ikonotv_piece_container';
import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications';
import CCRegisterPiece from './components/cc/cc_register_piece'; import CCRegisterPiece from './components/cc/cc_register_piece';
@ -77,6 +78,7 @@ let ROUTES = {
<Route name="piece" path="pieces/:pieceId" handler={IkonotvPieceContainer} /> <Route name="piece" path="pieces/:pieceId" handler={IkonotvPieceContainer} />
<Route name="edition" path="editions/:editionId" handler={EditionContainer} /> <Route name="edition" path="editions/:editionId" handler={EditionContainer} />
<Route name="settings" path="settings" handler={SettingsContainer} /> <Route name="settings" path="settings" handler={SettingsContainer} />
<Route name="contract_notifications" path="contract_notifications" handler={IkonotvContractNotifications} />
</Route> </Route>
) )
}; };

View File

@ -27,7 +27,14 @@ let ApiUrls = {
'note_private_piece': AppConstants.apiEndpoint + 'note/private/pieces/', 'note_private_piece': AppConstants.apiEndpoint + 'note/private/pieces/',
'note_public_edition': AppConstants.apiEndpoint + 'note/public/editions/', 'note_public_edition': AppConstants.apiEndpoint + 'note/public/editions/',
'note_public_piece': AppConstants.apiEndpoint + 'note/public/pieces/', 'note_public_piece': AppConstants.apiEndpoint + 'note/public/pieces/',
'notification_piecelist': AppConstants.apiEndpoint + 'notifications/pieces/',
'notification_piece': AppConstants.apiEndpoint + 'notifications/pieces/${piece_id}/',
'notification_editionlist': AppConstants.apiEndpoint + 'notifications/editions/',
'notification_edition': AppConstants.apiEndpoint + 'notifications/editions/${edition_id}/',
'notification_contractagreementlist': AppConstants.apiEndpoint + 'notifications/contract_agreements/',
'ownership_contract_agreements': AppConstants.apiEndpoint + 'ownership/contract_agreements/', 'ownership_contract_agreements': AppConstants.apiEndpoint + 'ownership/contract_agreements/',
'ownership_contract_agreements_confirm': AppConstants.apiEndpoint + 'ownership/contract_agreements/${contract_agreement_id}/accept/',
'ownership_contract_agreements_deny': AppConstants.apiEndpoint + 'ownership/contract_agreements/${contract_agreement_id}/reject/',
'ownership_consigns': AppConstants.apiEndpoint + 'ownership/consigns/', 'ownership_consigns': AppConstants.apiEndpoint + 'ownership/consigns/',
'ownership_consigns_confirm': AppConstants.apiEndpoint + 'ownership/consigns/confirm/', 'ownership_consigns_confirm': AppConstants.apiEndpoint + 'ownership/consigns/confirm/',
'ownership_consigns_deny': AppConstants.apiEndpoint + 'ownership/consigns/deny/', 'ownership_consigns_deny': AppConstants.apiEndpoint + 'ownership/consigns/deny/',
@ -53,7 +60,6 @@ let ApiUrls = {
'piece_extradata': AppConstants.apiEndpoint + 'pieces/${piece_id}/extradata/', 'piece_extradata': AppConstants.apiEndpoint + 'pieces/${piece_id}/extradata/',
'piece_first_edition_id': AppConstants.apiEndpoint + 'pieces/${piece_id}/edition_index/', 'piece_first_edition_id': AppConstants.apiEndpoint + 'pieces/${piece_id}/edition_index/',
'pieces_list': AppConstants.apiEndpoint + 'pieces/', 'pieces_list': AppConstants.apiEndpoint + 'pieces/',
'pieces_list_request_actions': AppConstants.apiEndpoint + 'pieces/request_actions/',
'piece_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/pieces/${piece_id}/', 'piece_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/pieces/${piece_id}/',
'user': AppConstants.apiEndpoint + 'users/', 'user': AppConstants.apiEndpoint + 'users/',
'users_login': AppConstants.apiEndpoint + 'users/login/', 'users_login': AppConstants.apiEndpoint + 'users/login/',

View File

@ -0,0 +1,29 @@
'use strict';
import requests from '../utils/requests';
let NotificationFetcher = {
fetchPieceListNotifications() {
return requests.get('notification_piecelist');
},
fetchPieceNotifications(pieceId) {
return requests.get('notification_piece', {'piece_id': pieceId});
},
fetchEditionListNotifications() {
return requests.get('notification_editionlist');
},
fetchEditionNotifications(editionId) {
return requests.get('notification_edition', {'edition_id': editionId});
},
fetchContractAgreementListNotifications() {
return requests.get('notification_contractagreementlist');
}
};
export default NotificationFetcher;

View File

@ -15,8 +15,8 @@ let OwnershipFetcher = {
/** /**
* Fetch the contracts of the logged-in user from the API. * Fetch the contracts of the logged-in user from the API.
*/ */
fetchContractList(){ fetchContractList(isActive){
return requests.get(ApiUrls.ownership_contract_list); return requests.get(ApiUrls.ownership_contract_list, isActive);
}, },
fetchLoanPieceRequestList(){ fetchLoanPieceRequestList(){
@ -24,11 +24,11 @@ let OwnershipFetcher = {
}, },
makeContractPublic(contractObj){ makeContractPublic(contractObj){
return requests.put('ownership_csontract', { body: contractObj, contract_id: contractObj.id }); return requests.put(ApiUrls.ownership_contract, { body: contractObj, contract_id: contractObj.id });
}, },
deleteContract(contractObjId){ deleteContract(contractObjId){
return requests.delete('ownership_contract', {contract_id: contractObjId}); return requests.delete(ApiUrls.ownership_contract, {contract_id: contractObjId});
} }
}; };

View File

@ -21,8 +21,7 @@ import SettingsContainer from './components/ascribe_settings/settings_container'
import CoaVerifyContainer from './components/coa_verify_container'; import CoaVerifyContainer from './components/coa_verify_container';
import RegisterPiece from './components/register_piece'; import RegisterPiece from './components/register_piece';
import ContractNotification from './components/contract_notification';
import PrizesDashboard from './components/ascribe_prizes_dashboard/prizes_dashboard';
import AppConstants from './constants/application_constants'; import AppConstants from './constants/application_constants';
@ -45,7 +44,7 @@ const COMMON_ROUTES = (
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} /> <Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
<Route name="settings" path="settings" handler={SettingsContainer} /> <Route name="settings" path="settings" handler={SettingsContainer} />
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} /> <Route name="coa_verify" path="verify" handler={CoaVerifyContainer} />
<Route name="prizes" path="prizes" handler={PrizesDashboard} /> <Route name="contract_notifications" path="verify" handler={ContractNotification} />
</Route> </Route>
); );

View File

@ -0,0 +1,41 @@
'use strict';
import React from 'react';
import alt from '../alt';
import NotificationActions from '../actions/notification_actions';
class NotificationStore {
constructor() {
this.pieceListNotifications = {};
this.editionListNotifications = {};
this.contractAgreementListNotifications = null;
this.editionNotifications = null;
this.pieceNotifications = null;
this.bindActions(NotificationActions);
}
onUpdatePieceListNotifications(res) {
this.pieceListNotifications = res.notifications;
}
onUpdatePieceNotifications(res) {
this.pieceNotifications = res.notification;
}
onUpdateEditionListNotifications(res) {
this.editionListNotifications = res.notifications;
}
onUpdateEditionNotifications(res) {
this.editionNotifications = res.notification;
}
onUpdateContractAgreementListNotifications(res) {
this.contractAgreementListNotifications = res.notifications;
}
}
export default alt.createStore(NotificationStore, 'NotificationStore');

36
js/third_party/notifications.js vendored Normal file
View File

@ -0,0 +1,36 @@
'use strict';
import alt from '../alt';
import EventActions from '../actions/event_actions';
import NotificationActions from '../actions/notification_actions';
class NotificationsHandler {
constructor() {
this.bindActions(EventActions);
this.loaded = false;
}
onProfileDidLoad(profile) {
if (this.loaded) {
return;
}
let subdomain = window.location.host.split('.')[0];
if (subdomain === 'ikonotv') {
NotificationActions.fetchContractAgreementListNotifications().then(
(res) => {
if (res.notifications && res.notifications.length > 0) {
this.loaded = true;
console.log('Contractagreement notifications loaded');
setTimeout(() => window.appRouter.transitionTo('contract_notifications'), 0);
}
}
);
}
this.loaded = true;
}
}
export default alt.createStore(NotificationsHandler, 'NotificationsHandler');

View File

@ -52,19 +52,6 @@ export function sumNumList(l) {
return sum; return sum;
} }
export function excludePropFromObject(obj, propList){
let clonedObj = mergeOptions({},obj);
for (let item in propList){
console.log(item);
if (clonedObj[propList[item]]){
console.log('deleting... ');
delete clonedObj[propList[item]];
}
}
console.log(clonedObj);
return clonedObj;
}
/* /*
Taken from http://stackoverflow.com/a/4795914/1263876 Taken from http://stackoverflow.com/a/4795914/1263876
Behaves like C's format string function Behaves like C's format string function
@ -207,4 +194,14 @@ function _mergeOptions(obj1, obj2) {
*/ */
export function escapeHTML(s) { export function escapeHTML(s) {
return document.createElement('div').appendChild(document.createTextNode(s)).parentNode.innerHTML; return document.createElement('div').appendChild(document.createTextNode(s)).parentNode.innerHTML;
}
export function excludePropFromObject(obj, propList){
let clonedObj = mergeOptions({}, obj);
for (let item in propList){
if (clonedObj[propList[item]]){
delete clonedObj[propList[item]];
}
}
return clonedObj;
} }

View File

@ -33,7 +33,7 @@ class Requests {
// If this is the case, we can not try to parse it as JSON. // If this is the case, we can not try to parse it as JSON.
if(responseText !== 'None') { if(responseText !== 'None') {
let body = JSON.parse(responseText); let body = JSON.parse(responseText);
if(body && body.errors) { if(body && body.errors) {
let error = new Error('Form Error'); let error = new Error('Form Error');
error.json = body; error.json = body;
@ -116,7 +116,6 @@ class Requests {
merged.headers['X-CSRFToken'] = csrftoken; merged.headers['X-CSRFToken'] = csrftoken;
} }
merged.method = verb; merged.method = verb;
return fetch(url, merged) return fetch(url, merged)
.then(this.unpackResponse) .then(this.unpackResponse)
.catch(this.handleError); .catch(this.handleError);
@ -139,8 +138,7 @@ class Requests {
_putOrPost(url, paramsAndBody, method){ _putOrPost(url, paramsAndBody, method){
let paramsCopy = this._merge(paramsAndBody); let paramsCopy = this._merge(paramsAndBody);
let params = excludePropFromObject(paramsAndBody,['body']); let params = excludePropFromObject(paramsAndBody, ['body']);
let newUrl = this.prepareUrl(url, params); let newUrl = this.prepareUrl(url, params);
let body = null; let body = null;
if (paramsCopy && paramsCopy.body) { if (paramsCopy && paramsCopy.body) {
@ -157,6 +155,10 @@ class Requests {
return this._putOrPost(url, params, 'put'); return this._putOrPost(url, params, 'put');
} }
patch(url, params){
return this._putOrPost(url, params, 'patch');
}
defaults(options) { defaults(options) {
this.httpOptions = options.http || {}; this.httpOptions = options.http || {};
this.urlMap = options.urlMap || {}; this.urlMap = options.urlMap || {};

View File

@ -3,35 +3,40 @@
video, video,
img { img {
display: block;
height: auto;
margin: 0 auto;
max-height: 640px;
max-width: 100%; max-width: 100%;
max-height: 640px;
width: auto; width: auto;
height: auto;
display: block;
margin: 0 auto;
} }
.media-other { .media-other {
color: #cccccc; color: #cccccc;
font-size: 500%; font-size: 500%;
p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
.audiojs { .audiojs {
background-image: none;
margin: 50px auto; margin: 50px auto;
* { background-image: none;
box-sizing: content-box; }
}
.loaded { .audiojs * {
background-color: $ascribe-color-green; box-sizing: content-box;
background-image: none; }
}
.progress { .audiojs .loaded {
background-color: rgba(255, 255, 255, .8); background-color: $ascribe-color-green;
background-image: none; background-image: none;
} }
.audiojs .progress {
background-color: rgba(255,255,255,0.8);
background-image: none;
} }
.video-js, .video-js,
@ -44,13 +49,12 @@
} }
.vjs-fullscreen { .vjs-fullscreen {
padding-top: 0; padding-top: 0px;
video {
max-height: 100%;
}
} }
.vjs-fullscreen video {
max-height: 100%;
}
.vjs-default-skin .vjs-play-progress, .vjs-default-skin .vjs-play-progress,
.vjs-default-skin .vjs-volume-level { .vjs-default-skin .vjs-volume-level {
@ -58,35 +62,41 @@
} }
.vjs-default-skin .vjs-big-play-button { .vjs-default-skin .vjs-big-play-button {
background-color: rgba(0, 0, 0, .8);
border: 0;
-moz-border-radius: 6px;
-o-border-radius: 6px;
-webkit-border-radius: 6px;
border-radius: 6px; border-radius: 6px;
-moz-box-shadow: none; -o-border-radius: 6px;
-o-box-shadow: none; -moz-border-radius: 6px;
-webkit-box-shadow: none; -webkit-border-radius: 6px;
box-shadow: none; box-shadow: none;
-o-box-shadow: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
width: 100px;
height: 60px; height: 60px;
top: 50%;
left: 50%; left: 50%;
margin: -30px -50px; margin: -30px -50px;
top: 50%;
width: 100px; border: none;
background-color: rgba(0,0,0,.8);
} }
.vjs-default-skin:hover .vjs-big-play-button, .vjs-default-skin:hover .vjs-big-play-button,
.vjs-default-skin .vjs-big-play-button:focus { .vjs-default-skin .vjs-big-play-button:focus {
background-color: rgba(0, 0, 0, .9);
border-color: #fff; border-color: #fff;
-moz-box-shadow: none; background-color: rgba(0,0,0,.9);
-o-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none; box-shadow: none;
-moz-transition: all 0s; -o-box-shadow: none;
-o-transition: all 0s; -moz-box-shadow: none;
-webkit-transition: all 0s; -webkit-box-shadow: none;
transition: all 0s; transition: all 0s;
-o-transition: all 0s;
-moz-transition: all 0s;
-webkit-transition: all 0s;
} }
.vjs-default-skin .vjs-big-play-button:before { .vjs-default-skin .vjs-big-play-button:before {
@ -103,7 +113,7 @@
} }
.vjs-default-skin .vjs-control-bar { .vjs-default-skin .vjs-control-bar {
background-color: rgba(0, 0, 0, .7); background-color: rgba(0,0,0,.7);
} }
} }

View File

@ -2,13 +2,27 @@ $break-small: 764px;
$break-medium: 991px; $break-medium: 991px;
$break-medium: 1200px; $break-medium: 1200px;
.notification-wrapper { .notification-header,.notification-wrapper {
width: 350px; width: 350px;
height:8em; }
padding: 0.3em;
border-bottom: 1px solid #cccccc;
margin: -3px -20px;
.notification-header {
border-bottom: 1px solid #cccccc;
border-top: 1px solid #cccccc;
padding: 0.3em 1em;
background-color: #eeeeee;
}
.notification-wrapper {
height:8.4em;
border-bottom: 1px solid #eeeeee;
margin: -3px 0;
padding: 0.5em;
color: black;
&:hover{
background-color: rgba(2, 182, 163, .05);
}
// ToDo: Include media queries for thumbnail // ToDo: Include media queries for thumbnail
.thumbnail-wrapper { .thumbnail-wrapper {
width: 7.4em; width: 7.4em;
@ -31,9 +45,15 @@ $break-medium: 1200px;
margin-top: 0.3em; margin-top: 0.3em;
margin-bottom: 0.15em; margin-bottom: 0.15em;
font-size: 1.8em; font-size: 1.8em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.sub-header{ .sub-header{
margin-bottom: 1em; margin-bottom: 0.6em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.notification-action{ .notification-action{
color: $ascribe-color-green; color: $ascribe-color-green;
@ -46,6 +66,10 @@ $break-medium: 1200px;
li a { li a {
padding-top: 0; padding-top: 0;
} }
border-top: 0;
overflow-y: auto;
overflow-x: hidden;
max-height: 70vh;
} }
} }

View File

@ -0,0 +1,62 @@
.notification-contract-download {
}
.notification-contract-wrapper{
text-align: center;
}
.notification-contract-logo {
img {
margin-bottom: 1em;
}
.notification-contract-header {
font-size: 2em;
text-transform: uppercase;
margin-bottom: 0.8em;
}
}
.notification-contract-pdf, .notification-contract-footer {
width: 100%;
max-width: 750px;
margin: 0 auto;
}
.notification-contract-pdf {
embed {
border: 1px solid #cccccc;
width: 100%;
height: 60vh;
margin-bottom: 0.4em;
}
.notification-contract-pdf-download {
text-align: left;
margin-left: 1em;
}
}
.notification-contract-footer {
text-align: left;
padding: 1em;
> h1 {
margin-top: 0.4em;
font-size: 1.4em;
}
> p {
font-size: 0.9em;
color: #444444;
}
> pre {
color: #444;
cursor: default;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
background-color: rgba(0, 0, 0, 0);
border: 0;
box-shadow: none;
margin-bottom: 1em;
padding-left: 0;
width: 100%;
}
}

0
sass/lib/buttons.scss Normal file
View File

View File

@ -25,6 +25,7 @@ $BASE_URL: '<%= BASE_URL %>';
@import 'ascribe_global_action'; @import 'ascribe_global_action';
@import 'ascribe_global_notification'; @import 'ascribe_global_notification';
@import 'ascribe_notification_list'; @import 'ascribe_notification_list';
@import 'ascribe_notification_page';
@import 'ascribe_piece_register'; @import 'ascribe_piece_register';
@import 'offset_right'; @import 'offset_right';
@import 'ascribe_settings'; @import 'ascribe_settings';
@ -208,36 +209,20 @@ hr {
border: 1px solid $ascribe-brand-danger; border: 1px solid $ascribe-brand-danger;
} }
} }
.btn-ascribe {
.btn-ascribe,
.btn-ascribe-inv {
border: 1px solid #444; border: 1px solid #444;
border-radius: 0 !important; border-radius: 0 !important;
font-family: sans-serif !important; font-family: sans-serif !important;
line-height: 2em; line-height: 2em;
margin-left: 0 !important; margin-left: 0 !important;
margin-right: 1px; font-family: sans-serif !important;
} border-radius: 0 !important;
.btn-ascribe,
.btn-ascribe-inv:active,
.btn-ascribe-inv:hover {
background-color: #fff;
color: #222 !important; color: #222 !important;
} }
.btn-ascribe:active, .btn-ascribe:active, .btn-ascribe:hover {
.btn-ascribe:hover, color: #FFF !important;
.btn-ascribe-inv {
background-color: #444; background-color: #444;
color: #fff !important;
}
.btn-ascribe-inv:disabled,
.btn-ascribe-inv:focus {
background-color: #BBB !important;
border: 1px solid #444 !important;
color: #444 !important;
} }
.btn-ascribe-sm { .btn-ascribe-sm {