1
0
mirror of https://github.com/ascribe/onion.git synced 2024-12-22 09:23:13 +01:00

Merge pull request #88 from ascribe/AD-1344-users-want-pretty-detail-page-to-print

AD-1344 Users want pretty detail page to print
This commit is contained in:
Brett Sun 2015-12-23 11:22:01 +01:00
commit 591882618b
19 changed files with 319 additions and 151 deletions

View File

@ -1,9 +1,10 @@
'use strict';
import React from 'react';
import classNames from 'classnames';
let DetailProperty = React.createClass({
const DetailProperty = React.createClass({
propTypes: {
label: React.PropTypes.string,
value: React.PropTypes.oneOfType([
@ -12,6 +13,7 @@ let DetailProperty = React.createClass({
React.PropTypes.element
]),
separator: React.PropTypes.string,
className: React.PropTypes.string,
labelClassName: React.PropTypes.string,
valueClassName: React.PropTypes.string,
ellipsis: React.PropTypes.bool,
@ -30,31 +32,23 @@ let DetailProperty = React.createClass({
},
render() {
let styles = {};
const { labelClassName,
label,
separator,
valueClassName,
children,
value } = this.props;
if(this.props.ellipsis) {
styles = {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
};
}
const {
children,
className,
ellipsis,
label,
labelClassName,
separator,
valueClassName,
value } = this.props;
return (
<div className="row ascribe-detail-property">
<div className={classNames('row ascribe-detail-property', className)}>
<div className="row-same-height">
<div className={labelClassName}>
{label} {separator}
</div>
<div
className={valueClassName}
style={styles}>
<div className={classNames(valueClassName, {'add-overflow-ellipsis': ellipsis})}>
{children || value}
</div>
</div>

View File

@ -16,7 +16,7 @@ import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph
import Form from './../ascribe_forms/form';
import Property from './../ascribe_forms/property';
import EditionDetailProperty from './detail_property';
import DetailProperty from './detail_property';
import LicenseDetail from './license_detail';
import FurtherDetails from './further_details';
@ -57,16 +57,16 @@ let Edition = React.createClass({
return (
<Row>
<Col md={6}>
<Col md={6} className="ascribe-print-col-left">
<MediaContainer
content={this.props.edition}/>
</Col>
<Col md={6} className="ascribe-edition-details">
<Col md={6} className="ascribe-edition-details ascribe-print-col-right">
<div className="ascribe-detail-header">
<hr style={{marginTop: 0}}/>
<hr className="hidden-print" style={{marginTop: 0}}/>
<h1 className="ascribe-detail-title">{this.props.edition.title}</h1>
<EditionDetailProperty label="BY" value={this.props.edition.artist_name} />
<EditionDetailProperty label="DATE" value={Moment(this.props.edition.date_created, 'YYYY-MM-DD').year()} />
<DetailProperty label="BY" value={this.props.edition.artist_name} />
<DetailProperty label="DATE" value={Moment(this.props.edition.date_created, 'YYYY-MM-DD').year()} />
<hr/>
</div>
<EditionSummary
@ -169,10 +169,10 @@ let EditionSummary = React.createClass({
let status = null;
if (this.props.edition.status.length > 0){
let statusStr = this.props.edition.status.join(', ').replace(/_/g, ' ');
status = <EditionDetailProperty label="STATUS" value={ statusStr }/>;
status = <DetailProperty label="STATUS" value={ statusStr }/>;
if (this.props.edition.pending_new_owner && this.props.edition.acl.acl_withdraw_transfer){
status = (
<EditionDetailProperty label="STATUS" value={ statusStr } />
<DetailProperty label="STATUS" value={ statusStr } />
);
}
}
@ -183,14 +183,14 @@ let EditionSummary = React.createClass({
let { actionPanelButtonListType, edition, currentUser } = this.props;
return (
<div className="ascribe-detail-header">
<EditionDetailProperty
<DetailProperty
label={getLangText('EDITION')}
value={ edition.edition_number + ' ' + getLangText('of') + ' ' + edition.num_editions} />
<EditionDetailProperty
<DetailProperty
label={getLangText('ID')}
value={ edition.bitcoin_id }
ellipsis={true} />
<EditionDetailProperty
<DetailProperty
label={getLangText('OWNER')}
value={ edition.owner } />
<LicenseDetail license={edition.license_type}/>
@ -201,14 +201,15 @@ let EditionSummary = React.createClass({
`AclInformation` would show up
*/}
<AclProxy show={currentUser && currentUser.email && Object.keys(edition.acl).length > 1}>
<EditionDetailProperty
label={getLangText('ACTIONS')}>
<DetailProperty
label={getLangText('ACTIONS')}
className="hidden-print">
<EditionActionPanel
actionPanelButtonListType={actionPanelButtonListType}
edition={edition}
currentUser={currentUser}
handleSuccess={this.handleSuccess} />
</EditionDetailProperty>
</DetailProperty>
</AclProxy>
<hr/>
</div>
@ -232,56 +233,60 @@ let CoaDetails = React.createClass({
},
render() {
if(this.props.coaError) {
return (
<div className="text-center">
<p>{getLangText('There was an error generating your Certificate of Authenticity.')}</p>
<p>
{getLangText('Try to refresh the page. If this happens repeatedly, please ')}
<a style={{ cursor: 'pointer' }} onClick={this.contactOnIntercom}>{getLangText('contact us')}</a>.
</p>
</div>
);
}
if(this.props.coa && this.props.coa.url_safe) {
return (
<div>
<div
className="notification-contract-pdf"
style={{paddingBottom: '1em'}}>
<embed
className="embed-form"
src={this.props.coa.url_safe}
alt="pdf"
pluginspage="http://www.adobe.com/products/acrobat/readstep2.html"/>
</div>
<div className="text-center ascribe-button-list">
<a href={this.props.coa.url_safe} target="_blank">
<button className="btn btn-default btn-xs">
{getLangText('Download')} <Glyphicon glyph="cloud-download"/>
</button>
</a>
<Link to="/coa_verify">
<button className="btn btn-default btn-xs">
{getLangText('Verify')} <Glyphicon glyph="check"/>
</button>
</Link>
const { coa = {}, coaError } = this.props;
</div>
let coaDetailElement;
if (coaError) {
coaDetailElement = [
<p>{getLangText('There was an error generating your Certificate of Authenticity.')}</p>,
<p>
{getLangText('Try to refresh the page. If this happens repeatedly, please ')}
<a style={{ cursor: 'pointer' }} onClick={this.contactOnIntercom}>{getLangText('contact us')}</a>.
</p>
];
} else if (coa.url_safe) {
coaDetailElement = [
<div
className="notification-contract-pdf"
style={{paddingBottom: '1em'}}>
<embed
className="embed-form"
src={coa.url_safe}
alt="pdf"
pluginspage="http://www.adobe.com/products/acrobat/readstep2.html"/>
</div>,
<div className="text-center ascribe-button-list">
<a href={coa.url_safe} target="_blank">
<button className="btn btn-default btn-xs">
{getLangText('Download')} <Glyphicon glyph="cloud-download"/>
</button>
</a>
<Link to="/coa_verify">
<button className="btn btn-default btn-xs">
{getLangText('Verify')} <Glyphicon glyph="check"/>
</button>
</Link>
</div>
);
} else if(typeof this.props.coa === 'string'){
return (
<div className="text-center">
{this.props.coa}
</div>
);
}
return (
<div className="text-center">
<AscribeSpinner color='dark-blue' size='md'/>
<p>{getLangText("Just a sec, we\'re generating your COA")}</p>
];
} else if (typeof coa === 'string') {
coaDetailElement = coa;
} else {
coaDetailElement = [
<AscribeSpinner color='dark-blue' size='md'/>,
<p>{getLangText("Just a sec, we're generating your COA")}</p>,
<p>{getLangText('(you may leave the page)')}</p>
];
}
return (
<div>
<div className="text-center hidden-print">
{coaDetailElement}
</div>
{/* Hide the COA and just show that it's a seperate document when printing */}
<div className="visible-print ascribe-coa-print-placeholder">
{getLangText('The COA is available as a seperate document')}
</div>
</div>
);
}
@ -293,16 +298,34 @@ let SpoolDetails = React.createClass({
},
render() {
let bitcoinIdValue = (
<a target="_blank" href={'https://www.blocktrail.com/BTC/address/' + this.props.edition.bitcoin_id}>{this.props.edition.bitcoin_id}</a>
const { edition: {
bitcoin_id: bitcoinId,
hash_as_address: hashAsAddress,
btc_owner_address_noprefix: bitcoinOwnerAddress
} } = this.props;
const bitcoinIdValue = (
<a className="anchor-no-expand-print"
target="_blank"
href={'https://www.blocktrail.com/BTC/address/' + bitcoinId}>
{bitcoinId}
</a>
);
let hashOfArtwork = (
<a target="_blank" href={'https://www.blocktrail.com/BTC/address/' + this.props.edition.hash_as_address}>{this.props.edition.hash_as_address}</a>
const hashOfArtwork = (
<a className="anchor-no-expand-print"
target="_blank"
href={'https://www.blocktrail.com/BTC/address/' + hashAsAddress}>
{hashAsAddress}
</a>
);
let ownerAddress = (
<a target="_blank" href={'https://www.blocktrail.com/BTC/address/' + this.props.edition.btc_owner_address_noprefix}>{this.props.edition.btc_owner_address_noprefix}</a>
const ownerAddress = (
<a className="anchor-no-expand-print"
target="_blank"
href={'https://www.blocktrail.com/BTC/address/' + bitcoinOwnerAddress}>
{bitcoinOwnerAddress}
</a>
);
return (

View File

@ -22,7 +22,11 @@ let HistoryIterator = React.createClass({
return (
<span>
{historicalEventDescription}
<a href={historicalEvent[2]} target="_blank">{contractName}</a>
<a className="anchor-no-expand-print"
target="_blank"
href={historicalEvent[2]}>
{contractName}
</a>
</span>
);
} else if(historicalEvent.length === 2) {

View File

@ -114,7 +114,7 @@ let MediaContainer = React.createClass({
url={content.digital_work.url}
extraData={extraData}
encodingStatus={content.digital_work.isEncoding} />
<p className="text-center">
<p className="text-center hidden-print">
<span className="ascribe-social-button-list">
<FacebookShareButton />
<TwitterShareButton

View File

@ -34,12 +34,12 @@ let Piece = React.createClass({
render() {
return (
<Row>
<Col md={6}>
<Col md={6} className="ascribe-print-col-left">
<MediaContainer
refreshObject={this.updateObject}
content={this.props.piece}/>
</Col>
<Col md={6} className="ascribe-edition-details">
<Col md={6} className="ascribe-edition-details ascribe-print-col-right">
{this.props.header}
{this.props.subheader}
{this.props.buttons}

View File

@ -219,7 +219,9 @@ let PieceContainer = React.createClass({
no more than 1 key, we're hiding the `DetailProperty` actions as otherwise
`AclInformation` would show up
*/}
<DetailProperty label={getLangText('ACTIONS')}>
<DetailProperty
label={getLangText('ACTIONS')}
className="hidden-print">
<AclButtonList
className="ascribe-button-list"
availableAcls={piece.acl}
@ -257,7 +259,7 @@ let PieceContainer = React.createClass({
loadPiece={this.loadPiece}
header={
<div className="ascribe-detail-header">
<hr style={{marginTop: 0}}/>
<hr className="hidden-print" style={{marginTop: 0}}/>
<h1 className="ascribe-detail-title">{this.state.piece.title}</h1>
<DetailProperty label="BY" value={this.state.piece.artist_name} />
<DetailProperty label="DATE" value={Moment(this.state.piece.date_created, 'YYYY-MM-DD').year() } />

View File

@ -26,7 +26,7 @@ let FileDragAndDropDialog = React.createClass({
getDragDialog(fileClass) {
if (dragAndDropAvailable) {
return [
<p>{getLangText('Drag %s here', fileClass)}</p>,
<p className="file-drag-and-drop-dialog-title">{getLangText('Drag %s here', fileClass)}</p>,
<p>{getLangText('or')}</p>
];
} else {
@ -46,6 +46,8 @@ let FileDragAndDropDialog = React.createClass({
if (hasFiles) {
return null;
} else {
let dialogElement;
if (enableLocalHashing && !uploadMethod) {
const currentQueryParams = getCurrentQueryParams();
@ -55,9 +57,9 @@ let FileDragAndDropDialog = React.createClass({
const queryParamsUpload = Object.assign({}, currentQueryParams);
queryParamsUpload.method = 'upload';
return (
<div className="file-drag-and-drop-dialog present-options">
<p>{getLangText('Would you rather')}</p>
dialogElement = (
<div className="present-options">
<p className="file-drag-and-drop-dialog-title">{getLangText('Would you rather')}</p>
{/*
The frontend in live is hosted under /app,
Since `Link` is appending that base url, if its defined
@ -85,32 +87,40 @@ let FileDragAndDropDialog = React.createClass({
);
} else {
if (multipleFiles) {
return (
<span className="file-drag-and-drop-dialog">
{this.getDragDialog(fileClassToUpload.plural)}
<span
className="btn btn-default"
onClick={onClick}>
{getLangText('choose %s to upload', fileClassToUpload.plural)}
</span>
dialogElement = [
this.getDragDialog(fileClassToUpload.plural),
<span
className="btn btn-default"
onClick={onClick}>
{getLangText('choose %s to upload', fileClassToUpload.plural)}
</span>
);
];
} else {
const dialog = uploadMethod === 'hash' ? getLangText('choose a %s to hash', fileClassToUpload.singular)
: getLangText('choose a %s to upload', fileClassToUpload.singular);
return (
<span className="file-drag-and-drop-dialog">
{this.getDragDialog(fileClassToUpload.singular)}
<span
className="btn btn-default"
onClick={onClick}>
{dialog}
</span>
dialogElement = [
this.getDragDialog(fileClassToUpload.singular),
<span
className="btn btn-default"
onClick={onClick}>
{dialog}
</span>
);
];
}
}
return (
<div className="file-drag-and-drop-dialog">
<div className="hidden-print">
{dialogElement}
</div>
{/* Hide the uploader and just show that there's been on files uploaded yet when printing */}
<p className="text-align-center visible-print">
{getLangText('No files uploaded')}
</p>
</div>
);
}
}
});

View File

@ -49,7 +49,7 @@ const FileDragAndDropPreviewImage = React.createClass({
};
let actionSymbol;
// only if assets are actually downloadable, there should be a download icon if the process is already at
// 100%. If not, no actionSymbol should be displayed
if(progress === 100 && areAssetsDownloadable) {
@ -68,7 +68,7 @@ const FileDragAndDropPreviewImage = React.createClass({
return (
<div
className="file-drag-and-drop-preview-image"
className="file-drag-and-drop-preview-image hidden-print"
style={imageStyle}>
<AclProxy
show={showProgress}>

View File

@ -7,7 +7,7 @@ import { getLangText } from '../utils/lang_utils';
let Footer = React.createClass({
render() {
return (
<div className="ascribe-footer">
<div className="ascribe-footer hidden-print">
<p className="ascribe-sub-sub-statement">
<br />
<a href="http://docs.ascribe.apiary.io/" target="_blank">api</a> |

View File

@ -219,10 +219,11 @@ let Header = React.createClass({
return (
<div>
<Navbar
ref="navbar"
brand={this.getLogo()}
toggleNavKey={0}
fixedTop={true}
ref="navbar">
className="hidden-print">
<CollapsibleNav
eventKey={0}>
<Nav navbar left>
@ -237,6 +238,9 @@ let Header = React.createClass({
{navRoutesLinks}
</CollapsibleNav>
</Navbar>
<p className="ascribe-print-header visible-print">
<span className="icon-ascribe-logo" />
</p>
</div>
);
}

View File

@ -25,12 +25,16 @@ let WalletPieceContainer = React.createClass({
currentUser: React.PropTypes.object.isRequired,
loadPiece: React.PropTypes.func.isRequired,
handleDeleteSuccess: React.PropTypes.func.isRequired,
submitButtonType: React.PropTypes.func.isRequired
submitButtonType: React.PropTypes.func.isRequired,
children: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.array
])
},
render() {
if(this.props.piece && this.props.piece.id) {
if (this.props.piece && this.props.piece.id) {
return (
<Piece
piece={this.props.piece}
@ -45,12 +49,12 @@ let WalletPieceContainer = React.createClass({
</div>
}
subheader={
<div className="ascribe-detail-header">
<DetailProperty label={getLangText('REGISTREE')} value={ this.props.piece.user_registered } />
<DetailProperty label={getLangText('ID')} value={ this.props.piece.bitcoin_id } ellipsis={true} />
<hr/>
</div>
}>
<div className="ascribe-detail-header">
<DetailProperty label={getLangText('REGISTREE')} value={ this.props.piece.user_registered } />
<DetailProperty label={getLangText('ID')} value={ this.props.piece.bitcoin_id } ellipsis={true} />
<hr/>
</div>
}>
<WalletActionPanel
piece={this.props.piece}
currentUser={this.props.currentUser}
@ -76,12 +80,10 @@ let WalletPieceContainer = React.createClass({
url={ApiUrls.note_private_piece}
currentUser={this.props.currentUser}/>
</CollapsibleParagraph>
{this.props.children}
</Piece>
);
}
else {
} else {
return (
<div className="fullpage-spinner">
<AscribeSpinner color='dark-blue' size='lg' />

View File

@ -60,9 +60,8 @@ let IkonotvArtistDetailsForm = React.createClass({
render() {
let buttons, spinner, heading;
let { isInline, handleSuccess } = this.props;
if(!isInline) {
if (!isInline) {
buttons = (
<button
type="submit"
@ -89,7 +88,7 @@ let IkonotvArtistDetailsForm = React.createClass({
);
}
if(this.props.piece && this.props.piece.id && this.props.piece.extra_data) {
if (this.props.piece && this.props.piece.id && this.props.piece.extra_data) {
return (
<Form
disabled={this.props.disabled}
@ -150,4 +149,4 @@ let IkonotvArtistDetailsForm = React.createClass({
}
});
export default IkonotvArtistDetailsForm;
export default IkonotvArtistDetailsForm;

View File

@ -61,7 +61,7 @@ let IkonotvArtworkDetailsForm = React.createClass({
let buttons, spinner, heading;
let { isInline, handleSuccess } = this.props;
if(!isInline) {
if (!isInline) {
buttons = (
<button
type="submit"
@ -88,7 +88,7 @@ let IkonotvArtworkDetailsForm = React.createClass({
);
}
if(this.props.piece && this.props.piece.id && this.props.piece.extra_data) {
if (this.props.piece && this.props.piece.id && this.props.piece.extra_data) {
return (
<Form
disabled={this.props.disabled}
@ -166,4 +166,4 @@ let IkonotvArtworkDetailsForm = React.createClass({
}
});
export default IkonotvArtworkDetailsForm;
export default IkonotvArtworkDetailsForm;

View File

@ -1,4 +1,3 @@
.ascribe-footer {
text-align: center;
margin-top: 5em;

118
sass/ascribe_print.scss Normal file
View File

@ -0,0 +1,118 @@
@media print {
@page {
margin: 1.2cm;
}
.ascribe-default-app {
padding: 0 !important;
}
// Utility class to not automatically expand an anchor href after the text
.anchor-no-expand-print:after {
content: '' !important;
}
// Replace navbar header with ascribe logo
.ascribe-print-header {
border-bottom: 1px solid rgba(0, 60, 105, 0.1);
font-size: 1.2em;
margin: 0.5em 0;
text-align: center;
}
// Force left and right columns
.ascribe-print-col-left {
width: 50% !important;
float: left !important;
}
.ascribe-print-col-right {
width: 50% !important;
float: right !important;
}
// Restyle file uploader dialogs
.file-drag-and-drop {
padding-top: 0;
outline-width: 0;
text-align: left;
}
.file-drag-and-drop-position {
margin: 0;
}
// Restyle COA
.ascribe-coa-print-placeholder {
padding: 0 1.5em 1em 1.5em;
margin: 0;
}
// Force collapsible properties to be expanded
.ascribe-collapsible-content .collapse {
display: block;
}
// Decrease property spacing
.ascribe-property-wrapper {
padding-bottom: 0.5em;
}
.ascribe-property {
padding-top: 0.5em;
> div,
> input,
> p,
> pre,
> select,
> span:not(.glyphicon),
> textarea {
margin: 0;
}
}
.ascribe-collapsible-wrapper {
margin-bottom: 5px;
> div:first-child {
margin-top: 0;
padding-bottom: 5px;
}
}
.ascribe-form hr {
margin-bottom: 3px;
}
// Hide placeholder text
input::-webkit-input-placeholder {
opacity: 0;
}
textarea::-webkit-input-placeholder {
opacity: 0;
}
/* firefox 18- */
input:-moz-placeholder {
opacity: 0;
}
textarea:-moz-placeholder {
opacity: 0;
}
/* firefox 19+ */
input::-moz-placeholder {
opacity: 0;
}
textarea::-moz-placeholder {
opacity: 0;
}
/* ie */
input:-ms-input-placeholder {
opacity: 0;
}
textarea:-ms-input-placeholder {
opacity: 0;
}
}

View File

@ -28,9 +28,9 @@
}
.file-drag-and-drop-dialog {
margin: 1.5em 0 1.5em 0;
> p:first-child {
margin: 0 0 1.5em 0;
.file-drag-and-drop-dialog-title {
font-size: 1.5em !important;
margin-bottom: 0;
margin-top: 0;
@ -47,14 +47,6 @@
margin: 1.5em 0 0 0;
}
.file-drag-and-drop .file-drag-and-drop-dialog > p:first-child {
font-size: 1.5em !important;
margin-top: 0;
margin-bottom: 0;
padding-bottom: 0;
}
.file-drag-and-drop-position {
display: inline-block;
margin-left: .7em;
@ -138,6 +130,7 @@
text-align: center;
width: 104px;
// REFACTOR TO USE TABLE CELL
.action-file, .spinner-file, .icon-ascribe-ok {
margin-top: 1em;
line-height: 1.3;
@ -200,4 +193,4 @@
span + .btn {
margin-left: 1em;
}
}
}

View File

@ -44,6 +44,8 @@ $BASE_URL: '<%= BASE_URL %>';
@import 'whitelabel/index';
@import 'ascribe_print';
html,
body {
@ -106,6 +108,12 @@ hr {
color: $ascribe-dark-blue;
}
.add-overflow-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ascribe-subheader {
padding-bottom: 10px;
margin-top: -10px;

View File

@ -7,3 +7,9 @@
padding-top: 70px;
padding-bottom: 10px;
}
@media print {
.ascribe-prize-app {
padding: 0 !important;
}
}

View File

@ -9,3 +9,9 @@
padding-top: 70px;
padding-bottom: 10px;
}
@media print {
.ascribe-wallet-app {
padding: 0 !important;
}
}