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
.on('error', notify.onError('Error: <%= error.message %>'))
.pipe(gulpif(argv.production, uglify({
mangle: true,
compress: {
sequences: true,
dead_code: true,
conditionals: true,
booleans: true,
unused: true,
if_return: true,
join_vars: true,
drop_console: true
}
mangle: true
})))
.on('error', notify.onError('Error: <%= error.message %>'))
.pipe(gulp.dest('./build/js'))

View File

@ -12,8 +12,8 @@ class ContractListActions {
);
}
fetchContractList() {
OwnershipFetcher.fetchContractList()
fetchContractList(isActive) {
OwnershipFetcher.fetchContractList(isActive)
.then((contracts) => {
this.actions.updateContractList(contracts.results);
})
@ -23,8 +23,8 @@ class ContractListActions {
});
}
makeContractPublic(contract){
contract.public = true;
changeContract(contract){
return Q.Promise((resolve, reject) => {
OwnershipFetcher.makeContractPublic(contract)
.then((res) => {
@ -41,11 +41,9 @@ class ContractListActions {
return Q.Promise( (resolve, reject) => {
OwnershipFetcher.deleteContract(contractId)
.then((res) => {
console.log('Contract deleted');
resolve(res);
})
.catch( (err) => {
console.log('Error while deleting');
console.logGlobal(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 RavenHandler from './third_party/raven';
import IntercomHandler from './third_party/intercom';
import NotificationsHandler from './third_party/notifications';
/* eslint-enable */
initLogging();
@ -71,9 +72,8 @@ class AppGateway {
type = settings.type;
subdomain = settings.subdomain;
}
EventActions.applicationWillBoot(settings);
Router.run(getRoutes(type, subdomain), Router.HistoryLocation, (App) => {
window.appRouter = Router.run(getRoutes(type, subdomain), Router.HistoryLocation, (App) => {
React.render(
<App />,
document.getElementById('main')

View File

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

View File

@ -61,8 +61,7 @@ let AccordionListItemWallet = React.createClass({
},
getGlyphicon(){
if ((this.props.content.request_action && this.props.content.request_action.length > 0) ||
(this.props.content.request_action_editions)){
if ((this.props.content.notifications && this.props.content.notifications.length > 0)){
return (
<OverlayTrigger
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 PieceListActions from '../../actions/piece_list_actions';
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';
@ -234,13 +234,15 @@ let EditionSummary = React.createClass({
getActions(){
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 = (
<ListRequestActions
pieceOrEditions={[this.props.edition]}
currentUser={this.props.currentUser}
handleSuccess={this.showNotification}
requestActions={this.props.edition.request_action}/>);
notifications={this.props.edition.notifications}/>);
}
else {

View File

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

View File

@ -39,7 +39,7 @@ let FurtherDetailsFileuploader = React.createClass({
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 (
<Property

View File

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

View File

@ -41,7 +41,9 @@ let Form = React.createClass({
// It will make use of the GlobalNotification
isInline: React.PropTypes.bool,
autoComplete: React.PropTypes.string
autoComplete: React.PropTypes.string,
onReset: React.PropTypes.func
},
getDefaultProps() {
@ -61,8 +63,14 @@ let Form = React.createClass({
},
reset() {
// If onReset prop is defined from outside,
// 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 (typeof this.refs[ref].reset === 'function'){
if (this.refs[ref].reset && typeof this.refs[ref].reset === 'function'){
this.refs[ref].reset();
}
}
@ -70,7 +78,6 @@ let Form = React.createClass({
},
submit(event){
if(event) {
event.preventDefault();
}
@ -79,7 +86,7 @@ let Form = React.createClass({
this.clearErrors();
// 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);
} else {
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);
},
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() {
requests
.delete(this.props.url, this.getFormData())
@ -106,7 +127,7 @@ let Form = React.createClass({
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());
}
@ -118,11 +139,12 @@ let Form = React.createClass({
},
handleSuccess(response){
if ('handleSuccess' in this.props){
if(this.props.handleSuccess && typeof this.props.handleSuccess === 'function') {
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();
}
}
@ -134,7 +156,7 @@ let Form = React.createClass({
handleError(err){
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) {
this.refs[input].setErrors(err.json.errors[input]);
} else {
@ -164,8 +186,8 @@ let Form = React.createClass({
},
clearErrors(){
for (var ref in this.refs){
if ('clearErrors' in this.refs[ref]){
for(let ref in this.refs){
if (this.refs[ref] && this.refs[ref].clearErrors && typeof this.refs[ref].clearErrors === 'function'){
this.refs[ref].clearErrors();
}
}
@ -185,8 +207,16 @@ let Form = React.createClass({
buttons = (
<div className="row" style={{margin: 0}}>
<p className="pull-right">
<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" onClick={this.reset}>CANCEL</Button>
<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>
</div>
);
@ -251,6 +281,7 @@ let Form = React.createClass({
role="form"
className={className}
onSubmit={this.submit}
onReset={this.reset}
autoComplete={this.props.autoComplete}>
{this.getFakeAutocompletableInputs()}
{this.getErrors()}

View File

@ -35,7 +35,7 @@ let ContractAgreementForm = React.createClass({
componentDidMount() {
ContractListStore.listen(this.onChange);
ContractListActions.fetchContractList();
ContractListActions.fetchContractList({is_active: 'True'});
},
componentWillUnmount() {
@ -50,9 +50,11 @@ let ContractAgreementForm = React.createClass({
this.setState({selectedContract: event.target.selectedIndex});
},
handleSubmitSuccess(response) {
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
handleSubmitSuccess() {
let notification = 'Contract agreement send';
notification = new GlobalNotificationModel(notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
this.refs.form.reset();
},
getFormData(){
@ -60,8 +62,8 @@ let ContractAgreementForm = React.createClass({
},
getContracts() {
if (this.state.contractList && this.state.contractList.count > 0) {
let contractList = this.state.contractList.results;
if (this.state.contractList && this.state.contractList.length > 0) {
let contractList = this.state.contractList;
return (
<Property
name='contract'
@ -81,7 +83,7 @@ let ContractAgreementForm = React.createClass({
<option
name={i}
key={i}
value={ contract.name }>
value={ contract.id }>
{ contract.name }
</option>
);
@ -99,7 +101,7 @@ let ContractAgreementForm = React.createClass({
ref='form'
url={ApiUrls.ownership_contract_agreements}
getFormData={this.getFormData}
handleSuccess={this.props.handleSuccess}
handleSuccess={this.handleSubmitSuccess}
buttons={<button
type="submit"
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 ApiUrls from '../../constants/api_urls';
import { getLangText } from '../../utils/lang_utils';
import { getCookie } from '../../utils/fetch_api_utils';
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils';
@ -48,17 +50,17 @@ let CreateContractForm = React.createClass({
},
handleCreateSuccess(response) {
ContractListActions.fetchContractList({is_active: 'True'});
let notification = new GlobalNotificationModel(getLangText('Contract %s successfully created', response.name), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
// also refresh contract lists for the rest of the contract settings page
ContractListActions.fetchContractList();
this.refs.form.reset();
},
render() {
return (
<Form
ref='form'
url={ApiUrls.ownership_contract_list}
getFormData={this.getFormData}
handleSuccess={this.handleCreateSuccess}
@ -78,6 +80,7 @@ let CreateContractForm = React.createClass({
<Property
label="Contract file">
<ReactS3FineUploader
ref='uploader'
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'contract'
@ -118,7 +121,7 @@ let CreateContractForm = React.createClass({
required/>
</Property>
<Property
name="public"
name="is_public"
className="ascribe-settings-property-collapsible-toggle"
style={{paddingBottom: 0}}>
<InputCheckbox>

View File

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

View File

@ -7,16 +7,13 @@ import UserActions from '../../actions/user_actions';
import Form from './form';
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 { getCookie } from '../../utils/fetch_api_utils';
import { getLangText } from '../../utils/lang_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({
@ -45,7 +42,6 @@ let RegisterPieceForm = React.createClass({
getInitialState(){
return mergeOptions(
{
digitalWorkKey: null,
isUploadReady: false
},
UserStore.getState()
@ -65,18 +61,6 @@ let RegisterPieceForm = React.createClass({
this.setState(state);
},
getFormData(){
return {
digital_work_key: this.state.digitalWorkKey
};
},
submitKey(key){
this.setState({
digitalWorkKey: key
});
},
setIsUploadReady(isReady) {
this.setState({
isUploadReady: isReady
@ -94,14 +78,15 @@ let RegisterPieceForm = React.createClass({
className="ascribe-form-bordered"
ref='form'
url={ApiUrls.pieces_list}
getFormData={this.getFormData}
handleSuccess={this.props.handleSuccess}
buttons={<button
buttons={
<button
type="submit"
className="btn ascribe-btn ascribe-btn-login"
disabled={!this.state.isUploadReady || this.props.disabled}>
{this.props.submitMessage}
</button>}
</button>
}
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" />
@ -111,11 +96,11 @@ let RegisterPieceForm = React.createClass({
<h3>{this.props.headerMessage}</h3>
</div>
<Property
name="digital_work_key"
ignoreFocus={true}>
<FileUploader
submitKey={this.submitKey}
<InputFineUploader
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
isReadyForFormSubmission={isReadyForFormSubmission}
isFineUploaderActive={this.props.isFineUploaderActive}
onLoggedOut={this.props.onLoggedOut}
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;

View File

@ -6,7 +6,7 @@ import AclButton from './../ascribe_buttons/acl_button';
import ActionPanel from '../ascribe_panel/action_panel';
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 GlobalNotificationActions from '../../actions/global_notification_actions';
@ -22,8 +22,7 @@ let RequestActionForm = React.createClass({
React.PropTypes.object,
React.PropTypes.array
]).isRequired,
requestAction: React.PropTypes.string,
requestUser: React.PropTypes.string,
notifications: React.PropTypes.object,
currentUser: React.PropTypes.object,
handleSuccess: React.PropTypes.func
},
@ -35,19 +34,19 @@ let RequestActionForm = React.createClass({
getUrls() {
let urls = {};
if (this.props.requestAction === 'consign'){
if (this.props.notifications.action === 'consign'){
urls.accept = ApiUrls.ownership_consigns_confirm;
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.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.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.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.deny = ApiUrls.ownership_loans_pieces_request_deny;
}
@ -70,8 +69,8 @@ let RequestActionForm = React.createClass({
return () => {
let message = getLangText('You have successfully') + ' ' + option + ' the ' + action + ' request ' + getLangText('from') + ' ' + owner;
let notification = new GlobalNotificationModel(message, 'success');
GlobalNotificationActions.appendGlobalNotification(notification);
let notifications = new GlobalNotificationModel(message, 'success');
GlobalNotificationActions.appendGlobalNotification(notifications);
this.handleSuccess();
@ -79,27 +78,27 @@ let RequestActionForm = React.createClass({
},
handleSuccess() {
PieceListActions.fetchPieceRequestActions();
if (this.isPiece()){
NotificationActions.fetchPieceListNotifications();
}
else {
NotificationActions.fetchEditionListNotifications();
}
if(this.props.handleSuccess) {
this.props.handleSuccess();
}
},
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 (
<span>
{message}
{this.props.notifications.action_str + ' by ' + this.props.notifications.by}
</span>
);
},
getAcceptButtonForm(urls) {
if(this.props.requestAction === 'unconsign') {
if(this.props.notifications.action === 'unconsign') {
return (
<AclButton
availableAcls={{'acl_unconsign': true}}
@ -109,7 +108,7 @@ let RequestActionForm = React.createClass({
currentUser={this.props.currentUser}
handleSuccess={this.handleSuccess} />
);
} else if(this.props.requestAction === 'loan_request') {
} else if(this.props.notifications.action === 'loan_request') {
return (
<AclButton
availableAcls={{'acl_loan_request': true}}
@ -126,7 +125,7 @@ let RequestActionForm = React.createClass({
url={urls.accept}
getFormData={this.getFormData}
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}
className='inline pull-right'>
@ -151,7 +150,7 @@ let RequestActionForm = React.createClass({
isInline={true}
getFormData={this.getFormData}
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'>
<button

View File

@ -18,7 +18,8 @@ let InputDate = React.createClass({
getInitialState() {
return {
value: null
value: null,
value_moment: null
};
},
@ -45,6 +46,10 @@ let InputDate = React.createClass({
});
},
reset() {
this.setState(this.getInitialState());
},
render() {
return (
<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';
let InputTextAreaToggable = React.createClass({
propTypes: {
editable: React.PropTypes.bool.isRequired,
@ -17,13 +18,16 @@ let InputTextAreaToggable = React.createClass({
value: this.props.defaultValue
};
},
handleChange(event) {
this.setState({value: event.target.value});
this.props.onChange(event);
},
render() {
let className = 'form-control ascribe-textarea';
let textarea = null;
if(this.props.editable) {
className = className + ' ascribe-textarea-editable';
textarea = (
@ -37,10 +41,10 @@ let InputTextAreaToggable = React.createClass({
onBlur={this.props.onBlur}
placeholder={this.props.placeholder} />
);
}
else{
} else {
textarea = <pre className="ascribe-pre">{this.state.value}</pre>;
}
return textarea;
}
});

View File

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

View File

@ -29,8 +29,11 @@ let Property = React.createClass({
handleChange: React.PropTypes.func,
ignoreFocus: React.PropTypes.bool,
className: React.PropTypes.string,
onClick: React.PropTypes.func,
onChange: React.PropTypes.func,
onBlur: React.PropTypes.func,
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
@ -90,18 +93,28 @@ let Property = React.createClass({
// maybe do reset by reload instead of front end state?
this.setState({value: this.state.initialValue});
if (this.refs.input.state && this.refs.input.state.value) {
// 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
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) {
this.props.handleChange(event);
if ('onChange' in this.props) {
if (this.props.onChange && typeof this.props.onChange === 'function') {
this.props.onChange(event);
}
@ -117,7 +130,7 @@ let Property = React.createClass({
// if onClick is defined from the outside,
// just call it
if(this.props.onClick) {
if(this.props.onClick && typeof this.props.onClick === 'function') {
this.props.onClick();
}
@ -132,7 +145,7 @@ let Property = React.createClass({
isFocused: false
});
if(this.props.onBlur) {
if(this.props.onBlur && typeof this.props.onBlur === 'function') {
this.props.onBlur(event);
}
},
@ -190,6 +203,7 @@ let Property = React.createClass({
},
render() {
let footer = null;
let tooltip = <span/>;
let style = this.props.style ? mergeOptions({}, this.props.style) : {};
@ -199,7 +213,7 @@ let Property = React.createClass({
{this.props.tooltip}
</Tooltip>);
}
let footer = null;
if(this.props.footer){
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() {
let tooltip = <span/>;
if (this.props.tooltip){

View File

@ -28,12 +28,20 @@ let Other = React.createClass({
},
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 (
<Panel className="media-other">
<p className="text-center">
.{ext}
{preview}
</p>
</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() {
ContractListStore.listen(this.onChange);
ContractListActions.fetchContractList();
ContractListActions.fetchContractList({is_active: 'True'});
},
componentWillUnmount() {
@ -39,10 +39,16 @@ let ContractSettings = React.createClass({
makeContractPublic(contract) {
return () => {
ContractListActions.makeContractPublic(contract)
.then(() => ContractListActions.fetchContractList())
.catch((error) => {
let notification = new GlobalNotificationModel(error, 'success', 10000);
contract.is_public = true;
ContractListActions.changeContract(contract)
.then(() => {
ContractListActions.fetchContractList({is_active: 'True'});
let notification = getLangText('Contract %s is now public', contract.name);
notification = new GlobalNotificationModel(notification, 'success', 4000);
GlobalNotificationActions.appendGlobalNotification(notification);
})
.catch((err) => {
let notification = new GlobalNotificationModel(err, 'danger', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
});
};
@ -51,20 +57,24 @@ let ContractSettings = React.createClass({
removeContract(contract) {
return () => {
ContractListActions.removeContract(contract.id)
.then(( ) => ContractListActions.fetchContractList())
.catch((error) => {
let notification = new GlobalNotificationModel(error, 'danger', 10000);
.then((response) => {
ContractListActions.fetchContractList({is_active: 'True'});
let notification = new GlobalNotificationModel(response.notification, 'success', 4000);
GlobalNotificationActions.appendGlobalNotification(notification);
})
.catch((err) => {
let notification = new GlobalNotificationModel(err, 'danger', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
});
};
},
getPublicContracts(){
return this.state.contractList.filter((contract) => contract.public);
return this.state.contractList.filter((contract) => contract.is_public);
},
getPrivateContracts(){
return this.state.contractList.filter((contract) => !contract.public);
return this.state.contractList.filter((contract) => !contract.is_public);
},
render() {
@ -95,7 +105,8 @@ let ContractSettings = React.createClass({
<button className="btn btn-default btn-sm margin-left-2px">
UPDATE
</button>
<button className="btn btn-default btn-sm margin-left-2px"
<button
className="btn btn-default btn-sm margin-left-2px"
onClick={this.removeContract(contract)}>
REMOVE
</button>
@ -121,11 +132,13 @@ let ContractSettings = React.createClass({
<button className="btn btn-default btn-sm margin-left-2px">
UPDATE
</button>
<button className="btn btn-default btn-sm margin-left-2px"
<button
className="btn btn-default btn-sm margin-left-2px"
onClick={this.removeContract(contract)}>
REMOVE
</button>
<button className="btn btn-default btn-sm margin-left-2px"
<button
className="btn btn-default btn-sm margin-left-2px"
onClick={this.makeContractPublic(contract)}>
MAKE PUBLIC
</button>

View File

@ -4,13 +4,12 @@ import React from 'react';
import Router from 'react-router';
import ReactAddons from 'react/addons';
import Col from 'react-bootstrap/lib/Col';
import SlidesContainerBreadcrumbs from './slides_container_breadcrumbs';
let State = Router.State;
let Navigation = Router.Navigation;
let SlidesContainer = React.createClass({
propTypes: {
children: React.PropTypes.arrayOf(React.PropTypes.element),
@ -30,12 +29,15 @@ let SlidesContainer = React.createClass({
let slideNum = -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) {
slideNum = parseInt(queryParams.slide_num, 10);
}
// 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
// Also, we use the 'in' keyword for the same reason as above in 'slide_num'
if(queryParams && 'start_from' in queryParams) {
startFrom = parseInt(queryParams.start_from, 10);
}
@ -51,6 +53,9 @@ let SlidesContainer = React.createClass({
componentDidMount() {
// check if slide_num was defined, and if not then default to 0
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)) {
// we're first requiring all the other possible queryParams and then set
@ -241,6 +246,16 @@ let SlidesContainer = React.createClass({
},
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 (
<div
className="container ascribe-sliding-container-wrapper"
@ -250,7 +265,11 @@ let SlidesContainer = React.createClass({
className="container ascribe-sliding-container"
style={{
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">
{this.renderChildren()}

View File

@ -6,15 +6,15 @@ import React from 'react';
let TableItemAclFiltered = React.createClass({
propTypes: {
content: React.PropTypes.object,
requestAction: React.PropTypes.string
notifications: React.PropTypes.string
},
render() {
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 (
<span>
{this.props.requestAction[0].action + ' request pending'}
{this.props.notifications[0].action_str}
</span>
);
}

View File

@ -20,7 +20,6 @@ import AppConstants from '../../constants/application_constants';
import { computeHashOfFile } from '../../utils/file_utils';
var ReactS3FineUploader = React.createClass({
propTypes: {
keyRoutine: React.PropTypes.shape({
url: React.PropTypes.string,
@ -125,6 +124,7 @@ var ReactS3FineUploader = React.createClass({
bucket: 'ascribe0'
},
request: {
//endpoint: 'https://www.ascribe.io.global.prod.fastly.net',
endpoint: 'https://ascribe0.s3.amazonaws.com',
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) {
let filename = this.state.uploader.getName(fileId);
let uuid = this.state.uploader.getUuid(fileId);
@ -356,16 +371,12 @@ var ReactS3FineUploader = React.createClass({
onComplete(id, name, res, xhr) {
// there has been an issue with the server's connection
if(xhr.status === 0) {
console.logGlobal(new Error('Complete was called but there wasn\t a success'), false, {
if((xhr && xhr.status === 0) || res.error) {
console.logGlobal(new Error(res.error || 'Complete was called but there wasn\t a success'), false, {
files: this.state.filesToUpload,
chunks: this.state.chunks
});
return;
}
} else {
let files = this.state.filesToUpload;
// Set the state of the completed file to 'upload successful' in order to
@ -412,8 +423,7 @@ var ReactS3FineUploader = React.createClass({
let notification = new GlobalNotificationModel(err.message, 'danger', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
});
}
},
onError(id, name, errorReason) {
@ -442,7 +452,6 @@ var ReactS3FineUploader = React.createClass({
},
onCancel(id) {
// when a upload is canceled, we need to update this components file array
this.setStatusOfFile(id, 'canceled');
@ -464,7 +473,6 @@ var ReactS3FineUploader = React.createClass({
},
onProgress(id, name, uploadedBytes, totalBytes) {
let newState = React.addons.update(this.state, {
filesToUpload: { [id]: {
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() {
let account;
let signup;
@ -103,9 +128,15 @@ let Header = React.createClass({
if (this.state.currentUser.username){
account = (
<DropdownButton
ref='dropdownbutton'
eventKey="1"
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 />
<MenuItemLink eventKey="3" to="logout">{getLangText('Log out')}</MenuItemLink>
</DropdownButton>

View File

@ -8,7 +8,8 @@ import MenuItem from 'react-bootstrap/lib/MenuItem';
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 { getLangText } from '../utils/lang_utils';
@ -20,23 +21,25 @@ let HeaderNotifications = React.createClass({
getInitialState() {
return mergeOptions(
PieceListStore.getState()
NotificationStore.getState()
);
},
componentDidMount() {
PieceListStore.listen(this.onChange);
NotificationStore.listen(this.onChange);
NotificationActions.fetchPieceListNotifications();
NotificationActions.fetchEditionListNotifications();
},
componentWillUnmount() {
PieceListStore.unlisten(this.onChange);
NotificationStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
onSelected(event) {
onMenuItemClick(event) {
/*
This is a hack to make the dropdown close after clicking on an item
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.
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() {
if (this.state.requestActions && this.state.requestActions.length > 0) {
getPieceNotifications(){
if (this.state.pieceListNotifications && this.state.pieceListNotifications.length > 0) {
return (
<Nav navbar right>
<DropdownButton
eventKey="1"
title={
<span>
<Glyphicon glyph='envelope' color="green"/>
<span className="notification-amount">({this.state.requestActions.length})</span>
</span>
}
className="notification-menu"
onSelect={this.onSelected}>
{this.state.requestActions.map((pieceOrEdition, i) => {
<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}
pieceOrEdition={pieceOrEdition}/>
</MenuItem>);
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() {
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 (
<Nav navbar right>
<DropdownButton
ref='dropdownbutton'
eventKey="1"
title={
<span>
<Glyphicon glyph='envelope' color="green"/>
<span className="notification-amount">({numNotifications})</span>
</span>
}
className="notification-menu">
{this.getPieceNotifications()}
{this.getEditionNotifications()}
</DropdownButton>
</Nav>
);
@ -90,33 +148,55 @@ let HeaderNotifications = React.createClass({
let NotificationListItem = React.createClass({
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() {
if(this.props.pieceOrEdition && this.props.pieceOrEdition.parent) {
return {
to: 'edition',
params: {
editionId: this.props.pieceOrEdition.bitcoin_id
}
};
} else {
if (this.isPiece()) {
return {
to: 'piece',
params: {
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() {
if (this.props.pieceOrEdition) {
return (
<Link {...this.getLinkData()}>
<Link {...this.getLinkData()} onClick={this.onClick}>
<div className="row notification-wrapper">
<div className="col-xs-4 clear-paddings">
<div className="thumbnail-wrapper">
@ -126,13 +206,7 @@ let NotificationListItem = React.createClass({
<div className="col-xs-8 notification-list-item-header">
<h1>{this.props.pieceOrEdition.title}</h1>
<div className="sub-header">by {this.props.pieceOrEdition.artist_name}</div>
<div className="notification-action">
{
this.props.pieceOrEdition.request_action.map((requestAction) => {
return 'Pending ' + requestAction.action + ' request';
})
}
</div>
{this.getNotificationText()}
</div>
</div>
</Link>);

View File

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

View File

@ -88,15 +88,37 @@ let PieceContainer = React.createClass({
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() {
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
let artistName = ((this.state.currentUser.is_jury && !this.state.currentUser.is_judge) ||
(this.state.currentUser.is_judge && !this.state.piece.selected )) ?
<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
let artistEmail = (this.state.currentUser.is_judge && this.state.piece.selected ) ?
<DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } /> : null;
return (
<Piece
piece={this.state.piece}
@ -111,11 +133,7 @@ let PieceContainer = React.createClass({
<DetailProperty label={getLangText('BY')} value={artistName} />
<DetailProperty label={getLangText('DATE')} value={ this.state.piece.date_created.slice(0, 4) } />
{artistEmail}
<ListRequestActions
pieceOrEditions={this.state.piece}
currentUser={this.state.currentUser}
handleSuccess={this.loadPiece}
requestActions={this.state.piece.request_action}/>
{this.getActions()}
<hr/>
</div>
}

View File

@ -68,7 +68,7 @@ let CylandPieceContainer = React.createClass({
},
render() {
if('title' in this.state.piece) {
if(this.state.piece && this.state.piece.title) {
return (
<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.
// 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.
//
// 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) {
PieceActions.fetchOne(queryParams.piece_id);
}

View File

@ -58,7 +58,7 @@ let IkonotvSubmitButton = React.createClass({
<InputCheckbox>
<span>
{' ' + 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')}
</a>)
</span>

View File

@ -89,15 +89,14 @@ let IkonotvPieceContainer = React.createClass({
getActions(){
if (this.state.piece &&
this.state.piece.request_action &&
this.state.piece.request_action.length > 0) {
this.state.piece.notifications &&
this.state.piece.notifications.length > 0) {
return (
<ListRequestActions
pieceOrEditions={this.state.piece}
currentUser={this.state.currentUser}
handleSuccess={this.loadPiece}
requestActions={this.state.piece.request_action}/>
);
notifications={this.state.piece.notifications}/>);
}
else {
@ -133,7 +132,7 @@ let IkonotvPieceContainer = React.createClass({
},
render() {
if('title' in this.state.piece) {
if(this.state.piece && this.state.piece.title) {
return (
<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 IkonotvRequestLoan from './components/ikonotv/ikonotv_request_loan';
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';
@ -77,6 +78,7 @@ let ROUTES = {
<Route name="piece" path="pieces/:pieceId" handler={IkonotvPieceContainer} />
<Route name="edition" path="editions/:editionId" handler={EditionContainer} />
<Route name="settings" path="settings" handler={SettingsContainer} />
<Route name="contract_notifications" path="contract_notifications" handler={IkonotvContractNotifications} />
</Route>
)
};

View File

@ -27,7 +27,14 @@ let ApiUrls = {
'note_private_piece': AppConstants.apiEndpoint + 'note/private/pieces/',
'note_public_edition': AppConstants.apiEndpoint + 'note/public/editions/',
'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_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_confirm': AppConstants.apiEndpoint + 'ownership/consigns/confirm/',
'ownership_consigns_deny': AppConstants.apiEndpoint + 'ownership/consigns/deny/',
@ -53,7 +60,6 @@ let ApiUrls = {
'piece_extradata': AppConstants.apiEndpoint + 'pieces/${piece_id}/extradata/',
'piece_first_edition_id': AppConstants.apiEndpoint + 'pieces/${piece_id}/edition_index/',
'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}/',
'user': AppConstants.apiEndpoint + 'users/',
'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.
*/
fetchContractList(){
return requests.get(ApiUrls.ownership_contract_list);
fetchContractList(isActive){
return requests.get(ApiUrls.ownership_contract_list, isActive);
},
fetchLoanPieceRequestList(){
@ -24,11 +24,11 @@ let OwnershipFetcher = {
},
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){
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 RegisterPiece from './components/register_piece';
import PrizesDashboard from './components/ascribe_prizes_dashboard/prizes_dashboard';
import ContractNotification from './components/contract_notification';
import AppConstants from './constants/application_constants';
@ -45,7 +44,7 @@ const COMMON_ROUTES = (
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
<Route name="settings" path="settings" handler={SettingsContainer} />
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} />
<Route name="prizes" path="prizes" handler={PrizesDashboard} />
<Route name="contract_notifications" path="verify" handler={ContractNotification} />
</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;
}
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
Behaves like C's format string function
@ -208,3 +195,13 @@ function _mergeOptions(obj1, obj2) {
export function escapeHTML(s) {
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

@ -116,7 +116,6 @@ class Requests {
merged.headers['X-CSRFToken'] = csrftoken;
}
merged.method = verb;
return fetch(url, merged)
.then(this.unpackResponse)
.catch(this.handleError);
@ -140,7 +139,6 @@ class Requests {
_putOrPost(url, paramsAndBody, method){
let paramsCopy = this._merge(paramsAndBody);
let params = excludePropFromObject(paramsAndBody, ['body']);
let newUrl = this.prepareUrl(url, params);
let body = null;
if (paramsCopy && paramsCopy.body) {
@ -157,6 +155,10 @@ class Requests {
return this._putOrPost(url, params, 'put');
}
patch(url, params){
return this._putOrPost(url, params, 'patch');
}
defaults(options) {
this.httpOptions = options.http || {};
this.urlMap = options.urlMap || {};

View File

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

View File

@ -2,13 +2,27 @@ $break-small: 764px;
$break-medium: 991px;
$break-medium: 1200px;
.notification-wrapper {
.notification-header,.notification-wrapper {
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
.thumbnail-wrapper {
width: 7.4em;
@ -31,9 +45,15 @@ $break-medium: 1200px;
margin-top: 0.3em;
margin-bottom: 0.15em;
font-size: 1.8em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sub-header{
margin-bottom: 1em;
margin-bottom: 0.6em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.notification-action{
color: $ascribe-color-green;
@ -46,6 +66,10 @@ $break-medium: 1200px;
li a {
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_notification';
@import 'ascribe_notification_list';
@import 'ascribe_notification_page';
@import 'ascribe_piece_register';
@import 'offset_right';
@import 'ascribe_settings';
@ -208,36 +209,20 @@ hr {
border: 1px solid $ascribe-brand-danger;
}
}
.btn-ascribe,
.btn-ascribe-inv {
.btn-ascribe {
border: 1px solid #444;
border-radius: 0 !important;
font-family: sans-serif !important;
line-height: 2em;
margin-left: 0 !important;
margin-right: 1px;
}
.btn-ascribe,
.btn-ascribe-inv:active,
.btn-ascribe-inv:hover {
background-color: #fff;
font-family: sans-serif !important;
border-radius: 0 !important;
color: #222 !important;
}
.btn-ascribe:active,
.btn-ascribe:hover,
.btn-ascribe-inv {
.btn-ascribe:active, .btn-ascribe:hover {
color: #FFF !important;
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 {