mirror of
https://github.com/ascribe/onion.git
synced 2024-12-22 09:23:13 +01:00
Merge remote-tracking branch 'origin/master' into AD-756-branding-for-sluiceascribeio-brow
Conflicts: js/components/header.js js/utils/head_setter.js
This commit is contained in:
commit
68b608763a
@ -22,7 +22,7 @@
|
||||
"react/jsx-sort-props": 0,
|
||||
"react/jsx-uses-react": 1,
|
||||
"react/jsx-uses-vars": 1,
|
||||
"react/no-did-mount-set-state": 1,
|
||||
"react/no-did-mount-set-state": [1, "allow-in-func"],
|
||||
"react/no-did-update-set-state": 1,
|
||||
"react/no-multi-comp": 0,
|
||||
"react/no-unknown-property": 1,
|
||||
@ -58,4 +58,4 @@
|
||||
"superInFunctions": 1,
|
||||
"templateStrings": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
fonts/ascribe-logo.eot
Normal file
BIN
fonts/ascribe-logo.eot
Normal file
Binary file not shown.
19
fonts/ascribe-logo.svg
Normal file
19
fonts/ascribe-logo.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Generated by IcoMoon</metadata>
|
||||
<defs>
|
||||
<font id="ascribe-logo" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="add" d="M1024 482.684h-477.316v477.316h-69.368v-477.316h-477.316v-69.368h477.316v-477.316h69.368v477.316h477.316z" />
|
||||
<glyph unicode="" glyph-name="sort" d="M429.733 809.882l-139.567 150.118-139.6-150.118 25-23.283 97.5 104.887v-955.486h34.133v955.553l97.533-104.954zM848.433 109.401l-97.5-104.887v955.486h-34.133v-955.553l-97.533 104.954-25-23.283 139.567-150.118 139.6 150.118z" />
|
||||
<glyph unicode="" glyph-name="search" d="M1021.1-36.711l-305.914 305.583c67.615 73.406 108.95 171.391 108.95 279.060 0 227.579-184.49 412.068-412.068 412.068s-412.068-184.489-412.068-412.068c0-227.578 184.489-412.068 412.068-412.068 107.625 0 205.576 41.3 278.972 108.866l305.927-305.597 24.133 24.156zM412.068 169.997c-208.394 0-377.935 169.541-377.935 377.935s169.541 377.935 377.935 377.935 377.935-169.54 377.935-377.935c0-208.394-169.541-377.935-377.935-377.935z" />
|
||||
<glyph unicode="" glyph-name="filter" d="M0 960l384.89-534.756 8.722-489.244 184.119 174.521 0.324 314.724 445.947 534.756h-1024zM551.839 447.106c-5.109-6.126-7.91-13.849-7.919-21.826l-0.308-300.068-117.253-111.141-7.341 411.782c-0.124 6.948-2.365 13.692-6.424 19.331l-345.97 480.683h884.467l-399.251-478.761z" />
|
||||
<glyph unicode="" glyph-name="add-white" d="M510.103 923.822c263.415 0 477.719-214.304 477.719-477.719s-214.303-477.718-477.719-477.718c-263.415 0-477.718 214.304-477.718 477.718s214.303 477.719 477.718 477.719zM510.103 957.955c-282.688 0-511.851-229.164-511.851-511.852s229.163-511.851 511.851-511.851 511.852 229.164 511.852 511.851-229.164 511.852-511.852 511.852v0zM796.444 459.378h-273.067v273.067h-34.133v-273.067h-261.689v-34.133h261.689v-261.689h34.133v261.689h273.067z" />
|
||||
<glyph unicode="" d="M512.148 959.852c-282.688 0-511.851-229.164-511.851-511.852s229.164-511.851 511.851-511.851 511.852 229.163 511.852 511.851-229.164 511.852-511.852 511.852z" />
|
||||
<glyph unicode="" d="M796.444 459.378h-273.067v273.067h-34.133v-273.067h-261.689v-34.133h261.689v-261.689h34.133v261.689h273.067z" />
|
||||
<glyph unicode="" glyph-name="icon" d="M550.306 782.458h-75.373l-249.184-613.64h90.453l62.951 159.627h262.477l62.974-159.627h95.755l-250.053 613.64zM403.098 400.255l107.305 274.897 107.28-274.897h-214.586zM1024 448c0 286.204-225.796 512-511.999 512s-512.001-225.796-512.001-512c0-286.204 225.797-512 512.001-512s511.999 225.797 511.999 512v0zM962.165 448c0-245.94-204.249-450.164-450.164-450.164-245.941 0-450.161 204.224-450.161 450.164s204.221 450.164 450.161 450.164c245.915 0 450.164-204.224 450.164-450.164v0z" />
|
||||
<glyph unicode="" glyph-name="logo" horiz-adv-x="4195" d="M499.718 326.19c0 109.528-24.641 157.448-61.607 198.517-38.336 41.077-95.832 71.191-191.673 71.191-95.832 0-171.135-36.957-212.212-64.34l27.382-52.031c13.695 10.954 88.998 54.764 187.571 54.764 99.943 0 177.978-57.505 177.978-173.876v-34.225l-173.876-6.843c-171.135-6.852-253.281-82.146-253.281-191.673s88.989-191.674 212.212-191.674c123.214 0 191.674 75.294 214.944 102.676v-88.989h72.562v376.503zM427.156 113.978c-30.114-47.92-98.573-116.371-201.258-116.371-102.676 0-154.707 61.607-154.707 130.066 0 68.45 42.448 125.955 175.246 132.798l180.719 10.955v-157.448zM1063.784 123.562c0 120.482-119.12 161.551-198.525 188.933-78.035 27.382-146.494 56.134-146.494 121.853s50.661 101.314 121.853 101.314c71.191 0 115.001-24.641 158.819-72.561l43.809 43.809c-49.291 56.134-106.795 88.989-199.887 88.989-93.1 0-193.044-54.764-193.044-169.765s119.112-156.078 173.876-175.246c54.764-19.168 168.394-47.92 168.394-130.066s-69.821-120.482-149.226-120.482c-79.413 0-142.391 39.707-183.46 99.952l-50.661-41.077c39.707-65.718 113.639-123.215 231.38-123.215s223.166 68.451 223.166 187.562v0zM1679.873 93.44c0 0-68.451-93.1-212.212-93.1-143.753 0-242.326 113.639-242.326 271.087s112.268 260.132 239.594 260.132c125.955 0 180.719-61.616 208.101-93.1l45.18 47.912c-13.687 17.798-82.146 109.528-250.548 109.528-167.024 0-317.628-132.798-317.628-328.583 0-194.406 139.65-331.315 310.785-331.315s236.853 82.146 260.133 109.528l-41.077 47.912zM2142.607 586.323c0 0-27.382 9.576-68.45 9.576-68.459 0-138.28-43.81-161.551-130.058v119.112h-71.2v-635.266h71.2v342.278c0 109.528 34.225 162.921 45.18 177.978 10.946 15.066 47.912 57.505 109.528 57.505 34.225 0 53.394-5.473 68.451-10.955l6.843 69.83zM2353.432 810.851c0 32.855-26.012 58.875-58.867 58.875-32.863 0-58.875-26.020-58.875-58.875s26.011-58.875 58.875-58.875c32.855 0 58.867 26.020 58.867 58.875v0zM2330.161 584.953h-71.191v-635.266h71.191v635.266zM3144.767 267.315c0 188.941-123.223 328.583-312.155 328.583-91.73 0-177.987-36.957-239.594-132.798v431.267h-71.191v-944.68h71.191v120.482c53.394-93.1 141.012-134.169 236.853-134.169 191.673 0 314.896 143.753 314.896 331.315v0zM3069.464 267.315c0-146.494-86.257-266.976-239.594-266.976-154.707 0-239.594 120.482-239.594 266.976 0 147.864 84.887 266.976 239.594 266.976 153.337 0 239.594-119.112 239.594-266.976v0zM3836.158 298.808c0 171.135-120.482 297.090-287.514 297.090-168.402 0-308.044-132.798-308.044-328.583 0-194.406 119.112-331.315 303.933-331.315 184.83 0 264.244 95.833 264.244 95.833l-34.234 50.661c0 0-79.405-82.154-223.158-82.154-143.761 0-228.64 99.952-235.491 250.548h516.154c0 0 4.111 27.382 4.111 47.92v0zM3318.633 312.495c4.111 88.998 68.459 221.796 225.899 221.796 157.449 0 219.065-139.65 219.065-221.796h-444.963zM4027.755 897.194h-26.663l-88.15-217.077h31.998l22.269 56.468h92.852l22.277-56.468h33.874l-88.457 217.077zM3975.68 761.989l37.96 97.245 37.951-97.245h-75.91zM4195.326 778.878c0 101.246-79.876 181.122-181.121 181.122s-181.122-79.876-181.122-181.122c0-101.245 79.876-181.122 181.122-181.122s181.121 79.876 181.121 181.122v0zM4173.452 778.878c0-87.002-72.254-159.247-159.247-159.247-87.002 0-159.246 72.245-159.246 159.247s72.244 159.247 159.246 159.247c86.993 0 159.247-72.245 159.247-159.247v0z" />
|
||||
</font></defs></svg>
|
After Width: | Height: | Size: 6.2 KiB |
BIN
fonts/ascribe-logo.ttf
Normal file
BIN
fonts/ascribe-logo.ttf
Normal file
Binary file not shown.
BIN
fonts/ascribe-logo.woff
Normal file
BIN
fonts/ascribe-logo.woff
Normal file
Binary file not shown.
@ -1,43 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="595.28px" height="419.53px" viewBox="0 0 595.28 419.53" enable-background="new 0 0 595.28 419.53" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M362.878,195.61c-1.647-6.692-4.142-13.088-7.412-19.008c-3.401-6.166-7.549-11.797-12.319-16.725
|
||||
c-4.951-5.127-10.511-9.553-16.519-13.149c-6.202-3.728-12.833-6.607-19.722-8.564c-7.088-2.003-14.352-3.075-21.602-3.188
|
||||
c-7.417-0.083-14.843,0.769-22.059,2.592c-4.731,1.212-9.302,2.843-13.703,4.791l7.269,14.875
|
||||
c3.387-1.463,6.895-2.699,10.517-3.627c5.788-1.463,11.74-2.16,17.735-2.082c5.8,0.09,11.635,0.954,17.334,2.562
|
||||
c5.471,1.554,10.754,3.852,15.717,6.833c4.77,2.856,9.184,6.372,13.131,10.46c3.762,3.886,7.035,8.333,9.73,13.219
|
||||
c2.57,4.651,4.53,9.687,5.832,14.971c1.237,5.021,1.809,10.269,1.693,15.6c-0.105,5.053-0.9,10.154-2.362,15.145
|
||||
c-1.409,4.783-3.46,9.395-6.094,13.703c-2.521,4.121-5.625,7.939-9.224,11.348c-3.437,3.256-7.326,6.064-11.562,8.352
|
||||
c-4.053,2.188-8.428,3.838-13.016,4.914c-4.344,1.023-8.87,1.465-13.477,1.32c-4.341-0.141-8.718-0.875-12.994-2.176
|
||||
c-4.068-1.244-7.992-3.043-11.661-5.348c-3.473-2.182-6.704-4.871-9.592-7.98c-2.712-2.932-5.052-6.266-6.955-9.906
|
||||
c-1.799-3.445-3.146-7.162-4.008-11.059c-0.815-3.658-1.137-7.471-0.957-11.338c0.167-3.615,0.837-7.264,1.997-10.867
|
||||
c1.049-3.288,2.634-6.606,4.587-9.602c1.839-2.828,4.165-5.532,6.732-7.829c2.425-2.172,5.2-4.05,8.23-5.574
|
||||
c2.81-1.405,5.947-2.48,9.076-3.11c2.926-0.591,6.12-0.8,9.219-0.598c2.85,0.188,5.858,0.804,8.694,1.777
|
||||
c2.586,0.892,5.217,2.22,7.612,3.845c2.159,1.464,4.247,3.347,6.052,5.468c1.635,1.903,3.084,4.162,4.205,6.548
|
||||
c1.023,2.166,1.791,4.617,2.225,7.114c0.392,2.204,0.48,4.645,0.258,7.045c-0.201,2.135-0.735,4.404-1.526,6.51
|
||||
c-0.75,1.959-1.806,3.893-3.056,5.598c-1.149,1.572-2.592,3.064-4.174,4.32c-1.398,1.107-3.125,2.129-4.843,2.861
|
||||
c-1.534,0.648-3.352,1.137-5.082,1.369c-1.563,0.199-3.315,0.184-4.947-0.047c-1.428-0.201-2.979-0.645-4.357-1.248
|
||||
c-1.193-0.523-2.489-1.326-3.561-2.209c-0.942-0.771-1.886-1.797-2.605-2.832c-0.609-0.871-1.173-1.992-1.563-3.123
|
||||
c-0.299-0.855-0.512-1.979-0.568-3.033c-0.045-0.795,0.052-1.85,0.234-2.666c0.16-0.684,0.506-1.557,0.893-2.246
|
||||
c0.262-0.471,0.75-1.094,1.267-1.594c0.28-0.279,0.826-0.666,1.228-0.875c0.257-0.135,0.845-0.338,1.163-0.402
|
||||
c0.182-0.035,0.657-0.037,0.931-0.008c4.538,0.547,8.644-2.734,9.167-7.275c0.521-4.541-2.736-8.646-7.277-9.168
|
||||
c-0.898-0.103-3.262-0.293-5.914,0.191c-1.95,0.389-4.019,1.104-5.693,1.97c-1.114,0.58-3.28,1.848-5.187,3.743
|
||||
c-1.614,1.559-3.085,3.463-4.134,5.345c-1.135,2.027-2.045,4.35-2.577,6.625c-0.535,2.393-0.756,4.984-0.627,7.271
|
||||
c0.099,1.828,0.434,4.629,1.461,7.555c0.879,2.555,2.137,5.037,3.623,7.16c1.583,2.275,3.546,4.402,5.667,6.146
|
||||
c2.256,1.855,4.823,3.439,7.435,4.586c2.768,1.209,5.775,2.064,8.678,2.473c3.095,0.443,6.339,0.465,9.418,0.068
|
||||
c3.234-0.432,6.479-1.309,9.398-2.545c3.105-1.326,6.101-3.1,8.656-5.127c2.727-2.164,5.233-4.762,7.242-7.508
|
||||
c2.105-2.873,3.891-6.145,5.178-9.51c1.316-3.506,2.19-7.246,2.53-10.838c0.363-3.928,0.214-7.788-0.437-11.451
|
||||
c-0.68-3.911-1.91-7.825-3.553-11.298c-1.761-3.754-3.986-7.211-6.595-10.25c-2.778-3.264-5.925-6.097-9.351-8.415
|
||||
c-3.629-2.464-7.502-4.413-11.51-5.795c-4.27-1.468-8.64-2.356-13.008-2.646c-4.605-0.289-9.173,0.005-13.562,0.892
|
||||
c-4.606,0.927-9.049,2.45-13.225,4.54c-4.354,2.188-8.342,4.896-11.849,8.036c-3.687,3.298-6.906,7.043-9.564,11.13
|
||||
c-2.787,4.276-4.968,8.853-6.477,13.58c-1.604,4.977-2.535,10.077-2.771,15.155c-0.247,5.332,0.203,10.613,1.333,15.689
|
||||
c1.174,5.307,3.022,10.398,5.496,15.137c2.583,4.941,5.772,9.477,9.487,13.492c3.88,4.18,8.228,7.793,12.928,10.746
|
||||
c4.905,3.08,10.16,5.488,15.634,7.164c5.678,1.727,11.493,2.695,17.286,2.887c0.755,0.023,1.51,0.035,2.263,0.035
|
||||
c5.287,0,10.509-0.602,15.541-1.789c5.996-1.406,11.744-3.578,17.084-6.457c5.519-2.977,10.595-6.646,15.089-10.902
|
||||
c4.653-4.41,8.678-9.365,11.959-14.727c3.39-5.545,6.03-11.486,7.856-17.676c1.873-6.408,2.892-12.957,3.026-19.457
|
||||
C365.195,208.741,364.465,202.041,362.878,195.61"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 4.2 KiB |
@ -111,7 +111,11 @@ gulp.task('sass:build', function () {
|
||||
]
|
||||
}).on('error', sass.logError))
|
||||
.pipe(gulpif(!argv.production, sourcemaps.write('./maps')))
|
||||
.pipe(gulpif(argv.production, minifyCss()))
|
||||
// We need to set `advanced` to false, as it merges
|
||||
// some of the styles wrongly
|
||||
.pipe(gulpif(argv.production, minifyCss({
|
||||
advanced: false
|
||||
})))
|
||||
.pipe(gulp.dest('./build/css'))
|
||||
.pipe(browserSync.stream());
|
||||
});
|
||||
|
15
index.html
15
index.html
@ -2,17 +2,8 @@
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta name="msapplication-config" content="<%= BASE_URL %>static/img/hq-favicons/browserconfig.xml">
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="theme-color" content="#48DACB">
|
||||
|
||||
<title>ascribe</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="//brick.a.ssl.fastly.net/Source+Sans+Pro:400,600,700,900">
|
||||
<link rel="stylesheet" href="<%= BASE_URL %>static/css/main.css">
|
||||
<% DEBUG && print('<link rel="stylesheet" href="' + BASE_URL + 'static/css/maps/main.css.map">') %>
|
||||
|
||||
@ -25,6 +16,12 @@
|
||||
<% DEBUG && print('window.DEBUG = true'); %>
|
||||
<% DEBUG && print('window.CREDENTIALS = \'' + CREDENTIALS + '\''); %>
|
||||
</script>
|
||||
<!-- Typekit gibson font -->
|
||||
<script src="https://use.typekit.net/gma2yhj.js"></script>
|
||||
<script>
|
||||
try {Typekit.load({ async: true });}
|
||||
catch(e){}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main"></div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { alt } from '../alt';
|
||||
import ApplicationFetcher from '../fetchers/application_fetcher';
|
||||
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { alt } from '../alt';
|
||||
import CoaFetcher from '../fetchers/coa_fetcher';
|
||||
|
||||
import Q from 'q';
|
||||
|
||||
class CoaActions {
|
||||
constructor() {
|
||||
@ -12,23 +13,38 @@ class CoaActions {
|
||||
);
|
||||
}
|
||||
|
||||
fetchOne(id) {
|
||||
CoaFetcher.fetchOne(id)
|
||||
.then((res) => {
|
||||
this.actions.updateCoa(res.coa);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.logGlobal(err);
|
||||
});
|
||||
fetchOrCreate(id, bitcoinId) {
|
||||
return Q.Promise((resolve, reject) => {
|
||||
CoaFetcher.fetchOne(id)
|
||||
.then((res) => {
|
||||
if (res.coa) {
|
||||
this.actions.updateCoa(res.coa);
|
||||
resolve(res.coa);
|
||||
}
|
||||
else {
|
||||
this.actions.create(bitcoinId);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.logGlobal(err);
|
||||
this.actions.updateCoa(null);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
create(edition) {
|
||||
CoaFetcher.create(edition.bitcoin_id)
|
||||
.then((res) => {
|
||||
this.actions.updateCoa(res.coa);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.logGlobal(err);
|
||||
});
|
||||
|
||||
create(bitcoinId) {
|
||||
return Q.Promise((resolve, reject) => {
|
||||
CoaFetcher.create(bitcoinId)
|
||||
.then((res) => {
|
||||
this.actions.updateCoa(res.coa);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.logGlobal(err);
|
||||
this.actions.updateCoa(null);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { alt } from '../alt';
|
||||
import Q from 'q';
|
||||
|
||||
import OwnershipFetcher from '../fetchers/ownership_fetcher';
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { alt } from '../alt';
|
||||
import OwnershipFetcher from '../fetchers/ownership_fetcher';
|
||||
import Q from 'q';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { alt } from '../alt';
|
||||
import EditionFetcher from '../fetchers/edition_fetcher';
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { alt } from '../alt';
|
||||
import Q from 'q';
|
||||
|
||||
import EditionListFetcher from '../fetchers/edition_list_fetcher.js';
|
||||
@ -33,6 +33,10 @@ class EditionListActions {
|
||||
EditionListFetcher
|
||||
.fetch(pieceId, page, pageSize, orderBy, orderAsc, filterBy)
|
||||
.then((res) => {
|
||||
if(res && !res.editions) {
|
||||
throw new Error('Piece has no editions to fetch.');
|
||||
}
|
||||
|
||||
this.actions.updateEditionList({
|
||||
pieceId,
|
||||
page,
|
||||
@ -46,6 +50,7 @@ class EditionListActions {
|
||||
resolve(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.logGlobal(err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { altThirdParty } from '../alt';
|
||||
|
||||
|
||||
class EventActions {
|
||||
@ -16,4 +16,4 @@ class EventActions {
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(EventActions);
|
||||
export default altThirdParty.createActions(EventActions);
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { alt } from '../alt';
|
||||
|
||||
|
||||
class GlobalNotificationActions {
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { alt } from '../alt';
|
||||
import LicenseFetcher from '../fetchers/license_fetcher';
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { alt } from '../alt';
|
||||
import Q from 'q';
|
||||
|
||||
import NotificationFetcher from '../fetchers/notification_fetcher';
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { alt } from '../alt';
|
||||
import OwnershipFetcher from '../fetchers/ownership_fetcher';
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { alt } from '../alt';
|
||||
import PieceFetcher from '../fetchers/piece_fetcher';
|
||||
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { alt } from '../alt';
|
||||
import Q from 'q';
|
||||
|
||||
import PieceListFetcher from '../fetchers/piece_list_fetcher';
|
||||
|
||||
|
||||
class PieceListActions {
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
@ -21,17 +22,16 @@ class PieceListActions {
|
||||
this.actions.updatePieceList({
|
||||
page,
|
||||
pageSize,
|
||||
search,
|
||||
orderBy,
|
||||
orderAsc,
|
||||
filterBy,
|
||||
search: '',
|
||||
pieceList: [],
|
||||
pieceListCount: -1,
|
||||
unfilteredPieceListCount: -1
|
||||
});
|
||||
|
||||
// afterwards, we can load the list
|
||||
|
||||
return Q.Promise((resolve, reject) => {
|
||||
PieceListFetcher
|
||||
.fetch(page, pageSize, search, orderBy, orderAsc, filterBy)
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { alt } from '../alt';
|
||||
import Q from 'q';
|
||||
|
||||
import PrizeListFetcher from '../fetchers/prize_list_fetcher';
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { altUser } from '../alt';
|
||||
import UserFetcher from '../fetchers/user_fetcher';
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ class UserActions {
|
||||
}
|
||||
|
||||
fetchCurrentUser() {
|
||||
return UserFetcher.fetchOne()
|
||||
UserFetcher.fetchOne()
|
||||
.then((res) => {
|
||||
this.actions.updateCurrentUser(res.users[0]);
|
||||
})
|
||||
@ -24,7 +24,7 @@ class UserActions {
|
||||
}
|
||||
|
||||
logoutCurrentUser() {
|
||||
return UserFetcher.logout()
|
||||
UserFetcher.logout()
|
||||
.then(() => {
|
||||
this.actions.deleteCurrentUser();
|
||||
})
|
||||
@ -34,4 +34,4 @@ class UserActions {
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(UserActions);
|
||||
export default altUser.createActions(UserActions);
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { alt } from '../alt';
|
||||
import WalletSettingsFetcher from '../fetchers/wallet_settings_fetcher';
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../alt';
|
||||
import { altWhitelabel } from '../alt';
|
||||
import WhitelabelFetcher from '../fetchers/whitelabel_fetcher';
|
||||
|
||||
|
||||
@ -26,4 +26,4 @@ class WhitelabelActions {
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(WhitelabelActions);
|
||||
export default altWhitelabel.createActions(WhitelabelActions);
|
||||
|
@ -2,4 +2,7 @@
|
||||
|
||||
import Alt from 'alt';
|
||||
|
||||
export default new Alt();
|
||||
export let alt = new Alt();
|
||||
export let altThirdParty = new Alt();
|
||||
export let altUser = new Alt();
|
||||
export let altWhitelabel = new Alt();
|
||||
|
42
js/app.js
42
js/app.js
@ -3,20 +3,23 @@
|
||||
require('babel/polyfill');
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Router, Redirect } from 'react-router';
|
||||
import history from './history';
|
||||
|
||||
/* eslint-disable */
|
||||
import fetch from 'isomorphic-fetch';
|
||||
/* eslint-enable */
|
||||
|
||||
import ApiUrls from './constants/api_urls';
|
||||
import { updateApiUrls } from './constants/api_urls';
|
||||
import appConstants from './constants/application_constants';
|
||||
|
||||
import AppConstants from './constants/application_constants';
|
||||
import getRoutes from './routes';
|
||||
import requests from './utils/requests';
|
||||
|
||||
import { updateApiUrls } from './constants/api_urls';
|
||||
import { getSubdomainSettings } from './utils/constants_utils';
|
||||
import { initLogging } from './utils/error_utils';
|
||||
import { getSubdomain } from './utils/general_utils';
|
||||
|
||||
import EventActions from './actions/event_actions';
|
||||
|
||||
@ -44,15 +47,14 @@ requests.defaults({
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
class AppGateway {
|
||||
start() {
|
||||
let settings;
|
||||
let subdomain = window.location.host.split('.')[0];
|
||||
let subdomain = getSubdomain();
|
||||
|
||||
try {
|
||||
settings = getSubdomainSettings(subdomain);
|
||||
appConstants.whitelabel = settings;
|
||||
AppConstants.whitelabel = settings;
|
||||
updateApiUrls(settings.type, subdomain);
|
||||
this.load(settings);
|
||||
} catch(err) {
|
||||
@ -66,22 +68,36 @@ class AppGateway {
|
||||
load(settings) {
|
||||
let type = 'default';
|
||||
let subdomain = 'www';
|
||||
let redirectRoute = (<Redirect from="/" to="/collection" />);
|
||||
|
||||
if (settings) {
|
||||
type = settings.type;
|
||||
subdomain = settings.subdomain;
|
||||
}
|
||||
|
||||
// www and cc do not have a landing page
|
||||
if(subdomain && subdomain !== 'cc') {
|
||||
redirectRoute = null;
|
||||
}
|
||||
|
||||
// Adds a client specific class to the body for whitelabel styling
|
||||
window.document.body.classList.add('client--' + subdomain);
|
||||
|
||||
// Send the applicationWillBoot event to the third-party stores
|
||||
EventActions.applicationWillBoot(settings);
|
||||
window.appRouter = Router.run(getRoutes(type, subdomain), Router.HistoryLocation, (App) => {
|
||||
React.render(
|
||||
<App />,
|
||||
document.getElementById('main')
|
||||
);
|
||||
EventActions.routeDidChange();
|
||||
});
|
||||
|
||||
// `history.listen` is called on every route change, which is perfect for
|
||||
// us in that case.
|
||||
history.listen(EventActions.routeDidChange);
|
||||
|
||||
React.render((
|
||||
<Router history={history}>
|
||||
{redirectRoute}
|
||||
{getRoutes(type, subdomain)}
|
||||
</Router>
|
||||
), document.getElementById('main'));
|
||||
|
||||
// Send the applicationDidBoot event to the third-party stores
|
||||
EventActions.applicationDidBoot(settings);
|
||||
}
|
||||
}
|
||||
|
@ -9,21 +9,49 @@ let AccordionList = React.createClass({
|
||||
className: React.PropTypes.string,
|
||||
children: React.PropTypes.arrayOf(React.PropTypes.element).isRequired,
|
||||
loadingElement: React.PropTypes.element,
|
||||
count: React.PropTypes.number
|
||||
count: React.PropTypes.number,
|
||||
itemList: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
search: React.PropTypes.string,
|
||||
searchFor: React.PropTypes.func
|
||||
},
|
||||
|
||||
|
||||
clearSearch() {
|
||||
this.props.searchFor('');
|
||||
},
|
||||
|
||||
render() {
|
||||
const { search } = this.props;
|
||||
|
||||
if(this.props.itemList && this.props.itemList.length > 0) {
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
} else if(this.props.count === 0) {
|
||||
} else if(this.props.count === 0 && !search) {
|
||||
return (
|
||||
<div className="ascribe-accordion-list-placeholder">
|
||||
<p className="text-center">{getLangText('We could not find any works related to you...')}</p>
|
||||
<p className="text-center">{getLangText('To register one, click')} <a href="register_piece">{getLangText('here')}</a>!</p>
|
||||
<p className="text-center">
|
||||
{getLangText('We could not find any works related to you...')}
|
||||
</p>
|
||||
<p className="text-center">
|
||||
{getLangText('To register one, click')}
|
||||
<a href="register_piece">{getLangText('here')}</a>!
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else if(this.props.count === 0 && search) {
|
||||
return (
|
||||
<div className="ascribe-accordion-list-placeholder">
|
||||
<p className="text-center">
|
||||
{getLangText('We could not find any works related to you...')}
|
||||
</p>
|
||||
<p className="text-center">
|
||||
{getLangText('You\'re filtering by the search keyword: \'%s\' ', search)}
|
||||
</p>
|
||||
<p className="text-center">
|
||||
<button className="btn btn-sm btn-default" onClick={this.clearSearch}>{getLangText('Clear search')}</button>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
@ -1,7 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
|
||||
let AccordionListItem = React.createClass({
|
||||
propTypes: {
|
||||
@ -12,39 +13,57 @@ let AccordionListItem = React.createClass({
|
||||
subheading: React.PropTypes.object,
|
||||
subsubheading: React.PropTypes.object,
|
||||
buttons: React.PropTypes.object,
|
||||
linkData: React.PropTypes.string,
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.arrayOf(React.PropTypes.element),
|
||||
React.PropTypes.element
|
||||
])
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
render() {
|
||||
const { linkData,
|
||||
className,
|
||||
thumbnail,
|
||||
heading,
|
||||
subheading,
|
||||
subsubheading,
|
||||
buttons,
|
||||
badge,
|
||||
children } = this.props;
|
||||
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className={this.props.className}>
|
||||
<div className={className}>
|
||||
<div className="wrapper">
|
||||
<div className="col-xs-4 col-sm-3 col-md-2 col-lg-2 clear-paddings">
|
||||
<div className="thumbnail-wrapper">
|
||||
{this.props.thumbnail}
|
||||
<div className="pull-left">
|
||||
<Link to={linkData}>
|
||||
<div className="thumbnail-wrapper">
|
||||
{thumbnail}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-xs-8 col-sm-9 col-md-9 col-lg-9 col-md-offset-1 col-lg-offset-1 accordion-list-item-header">
|
||||
{this.props.heading}
|
||||
{this.props.subheading}
|
||||
{this.props.subsubheading}
|
||||
{this.props.buttons}
|
||||
</div>
|
||||
<div className="accordion-list-item-header">
|
||||
<Link to={linkData}>
|
||||
{heading}
|
||||
</Link>
|
||||
<Link to={linkData}>
|
||||
{subheading}
|
||||
{subsubheading}
|
||||
</Link>
|
||||
<div className="accordion-list-item-buttons">
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span style={{'clear': 'both'}}></span>
|
||||
|
||||
<div className="request-action-badge">
|
||||
{this.props.badge}
|
||||
{badge}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{this.props.children}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import PieceListStore from '../../stores/piece_list_store';
|
||||
import Button from 'react-bootstrap/lib/Button';
|
||||
|
||||
import CreateEditionsButton from '../ascribe_buttons/create_editions_button';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { mergeOptions } from '../../utils/general_utils';
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
@ -75,7 +76,10 @@ let AccordionListItemEditionWidget = React.createClass({
|
||||
// PLEASE FUTURE TIM, DO NOT FUCKING REMOVE IT AGAIN!
|
||||
if(typeof this.state.editionList[pieceId] === 'undefined') {
|
||||
return (
|
||||
<span className="glyph-ascribe-spool-chunked ascribe-color spin"/>
|
||||
<AscribeSpinner
|
||||
size='sm'
|
||||
color='white'
|
||||
classNames='pull-right margin-left-2px'/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
@ -98,7 +102,7 @@ let AccordionListItemEditionWidget = React.createClass({
|
||||
return (
|
||||
<CreateEditionsButton
|
||||
label={getLangText('Create editions')}
|
||||
className="btn-xs pull-right"
|
||||
className="btn-secondary btn-sm pull-right"
|
||||
piece={piece}
|
||||
toggleCreateEditionsDialog={this.props.toggleCreateEditionsDialog}
|
||||
onPollingSuccess={this.props.onPollingSuccess}/>
|
||||
@ -112,12 +116,12 @@ let AccordionListItemEditionWidget = React.createClass({
|
||||
if(piece.first_edition === null) {
|
||||
// user has deleted all his editions and only the piece is showing
|
||||
return (
|
||||
<Button
|
||||
<button
|
||||
disabled
|
||||
title={getLangText('All editions for this have been deleted already.')}
|
||||
className={classNames('btn', 'btn-default', 'btn-xs', 'ascribe-accordion-list-item-edition-widget', this.props.className)}>
|
||||
className={classNames('btn', 'btn-default', 'btn-secondary', 'btn-sm', 'ascribe-accordion-list-item-edition-widget', this.props.className)}>
|
||||
{'0 ' + getLangText('Editions')}
|
||||
</Button>
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
let editionMapping = piece && piece.first_edition ? piece.first_edition.num_editions_available + '/' + piece.num_editions : '';
|
||||
@ -125,7 +129,7 @@ let AccordionListItemEditionWidget = React.createClass({
|
||||
return (
|
||||
<button
|
||||
onClick={this.toggleTable}
|
||||
className={classNames('btn', 'btn-default', 'btn-xs', 'ascribe-accordion-list-item-edition-widget', this.props.className)}>
|
||||
className={classNames('btn', 'btn-secondary', 'btn-sm', 'ascribe-accordion-list-item-edition-widget', this.props.className)}>
|
||||
{editionMapping + ' ' + getLangText('Editions')} {this.getGlyphicon()}
|
||||
</button>
|
||||
);
|
||||
|
@ -1,14 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import AccordionListItem from './accordion_list_item';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
|
||||
let AccordionListItemPiece = React.createClass({
|
||||
propTypes: {
|
||||
@ -24,51 +22,54 @@ let AccordionListItemPiece = React.createClass({
|
||||
badge: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
getLinkData() {
|
||||
let { piece } = this.props;
|
||||
|
||||
if(piece && piece.first_edition) {
|
||||
return `/editions/${piece.first_edition.bitcoin_id}`;
|
||||
|
||||
if(this.props.piece && this.props.piece.first_edition) {
|
||||
return {
|
||||
to: 'edition',
|
||||
params: {
|
||||
editionId: this.props.piece.first_edition.bitcoin_id
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
to: 'piece',
|
||||
params: {
|
||||
pieceId: this.props.piece.id
|
||||
}
|
||||
};
|
||||
return `/pieces/${piece.id}`;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
render() {
|
||||
const { className, piece, artistName, buttons, badge, children, subsubheading } = this.props;
|
||||
const { url, url_safe } = piece.thumbnail;
|
||||
let thumbnail;
|
||||
|
||||
// Since we're going to refactor the thumbnail generation anyway at one point,
|
||||
// for not use the annoying ascribe_spiral.png, we're matching the url against
|
||||
// this name and replace it with a CSS version of the new logo.
|
||||
if(url.match(/https:\/\/.*\/media\/thumbnails\/ascribe_spiral.png/)) {
|
||||
thumbnail = (
|
||||
<span className="ascribe-logo-circle">
|
||||
<span>A</span>
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
thumbnail = (
|
||||
<div style={{backgroundImage: 'url("' + url_safe + '")'}}/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AccordionListItem
|
||||
className={this.props.className}
|
||||
thumbnail={
|
||||
<Link {...this.getLinkData()}>
|
||||
<img src={this.props.piece.thumbnail.url_safe}/>
|
||||
</Link>}
|
||||
heading={
|
||||
<Link {...this.getLinkData()}>
|
||||
<h1>{this.props.piece.title}</h1>
|
||||
</Link>}
|
||||
className={className}
|
||||
thumbnail={thumbnail}
|
||||
heading={<h1>{piece.title}</h1>}
|
||||
subheading={
|
||||
<h3>
|
||||
{getLangText('by ')}
|
||||
{this.props.artistName ? this.props.artistName : this.props.piece.artist_name}
|
||||
{artistName ? artistName : piece.artist_name}
|
||||
</h3>
|
||||
}
|
||||
subsubheading={this.props.subsubheading}
|
||||
buttons={this.props.buttons}
|
||||
badge={this.props.badge}
|
||||
subsubheading={subsubheading}
|
||||
buttons={buttons}
|
||||
badge={badge}
|
||||
linkData={this.getLinkData()}
|
||||
>
|
||||
{this.props.children}
|
||||
{children}
|
||||
</AccordionListItem>
|
||||
);
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ let AccordionListItemTableEditions = React.createClass({
|
||||
showExpandOption = true;
|
||||
}
|
||||
|
||||
let transition = new TransitionModel('edition', 'editionId', 'bitcoin_id', (e) => e.stopPropagation() );
|
||||
let transition = new TransitionModel('editions', 'editionId', 'bitcoin_id', (e) => e.stopPropagation() );
|
||||
|
||||
let columnList = [
|
||||
new ColumnModel(
|
||||
|
@ -1,22 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import Header from '../components/header';
|
||||
import Footer from '../components/footer';
|
||||
import GlobalNotification from './global_notification';
|
||||
|
||||
import getRoutes from '../routes';
|
||||
|
||||
|
||||
let RouteHandler = Router.RouteHandler;
|
||||
|
||||
let AscribeApp = React.createClass({
|
||||
propTypes: {
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.arrayOf(React.PropTypes.element),
|
||||
React.PropTypes.element
|
||||
]),
|
||||
routes: React.PropTypes.arrayOf(React.PropTypes.object)
|
||||
},
|
||||
|
||||
render() {
|
||||
let { children, routes } = this.props;
|
||||
|
||||
return (
|
||||
<div className="container ascribe-default-app">
|
||||
<Header routes={getRoutes()} />
|
||||
<RouteHandler />
|
||||
<Header routes={routes} />
|
||||
{/* Routes are injected here */}
|
||||
<div className="ascribe-body">
|
||||
{children}
|
||||
</div>
|
||||
<Footer />
|
||||
<GlobalNotification />
|
||||
<div id="modal" className="container"></div>
|
||||
|
@ -5,6 +5,8 @@ import React from 'react';
|
||||
import EditionListActions from '../../actions/edition_list_actions';
|
||||
import EditionListStore from '../../stores/edition_list_store';
|
||||
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
import classNames from 'classnames';
|
||||
@ -75,14 +77,17 @@ let CreateEditionsButton = React.createClass({
|
||||
return (
|
||||
<button
|
||||
disabled
|
||||
className={classNames('btn', 'btn-default', this.props.className)}>
|
||||
{getLangText('Creating editions')} <span className="glyph-ascribe-spool-chunked spin"/>
|
||||
className={classNames('btn', this.props.className)}>
|
||||
{getLangText('Creating editions')} <AscribeSpinner
|
||||
size='sm'
|
||||
color='white'
|
||||
classNames='pull-right margin-left-2px'/>
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<button
|
||||
className={classNames('btn', 'btn-default', this.props.className)}
|
||||
className={classNames('btn', this.props.className)}
|
||||
onClick={this.props.toggleCreateEditionsDialog}>
|
||||
{this.props.label}
|
||||
</button>
|
||||
|
@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import Button from 'react-bootstrap/lib/Button';
|
||||
|
||||
@ -24,8 +23,6 @@ let DeleteButton = React.createClass({
|
||||
handleSuccess: React.PropTypes.func
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
render() {
|
||||
let availableAcls;
|
||||
let btnDelete;
|
||||
|
@ -1,20 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link, History } from 'react-router';
|
||||
|
||||
import Row from 'react-bootstrap/lib/Row';
|
||||
import Col from 'react-bootstrap/lib/Col';
|
||||
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
|
||||
import Button from 'react-bootstrap/lib/Button';
|
||||
|
||||
import UserActions from '../../actions/user_actions';
|
||||
import UserStore from '../../stores/user_store';
|
||||
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 HistoryIterator from './history_iterator';
|
||||
|
||||
@ -26,46 +22,37 @@ import Form from './../ascribe_forms/form';
|
||||
import Property from './../ascribe_forms/property';
|
||||
import EditionDetailProperty from './detail_property';
|
||||
import LicenseDetail from './license_detail';
|
||||
import EditionFurtherDetails from './further_details';
|
||||
import FurtherDetails from './further_details';
|
||||
|
||||
import ListRequestActions from './../ascribe_forms/list_form_request_actions';
|
||||
import AclButtonList from './../ascribe_buttons/acl_button_list';
|
||||
import UnConsignRequestButton from './../ascribe_buttons/unconsign_request_button';
|
||||
import DeleteButton from '../ascribe_buttons/delete_button';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
import EditionActionPanel from './edition_action_panel';
|
||||
|
||||
import Note from './note';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { mergeOptions } from '../../utils/general_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
/**
|
||||
* This is the component that implements display-specific functionality
|
||||
*/
|
||||
let Edition = React.createClass({
|
||||
propTypes: {
|
||||
edition: React.PropTypes.object,
|
||||
loadEdition: React.PropTypes.func
|
||||
loadEdition: React.PropTypes.func,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
mixins: [History],
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
UserStore.getState(),
|
||||
PieceListStore.getState()
|
||||
);
|
||||
return UserStore.getState();
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
UserStore.listen(this.onChange);
|
||||
PieceListStore.listen(this.onChange);
|
||||
UserActions.fetchCurrentUser();
|
||||
},
|
||||
|
||||
@ -80,31 +67,12 @@ let Edition = React.createClass({
|
||||
CoaActions.flushCoa();
|
||||
|
||||
UserStore.unlisten(this.onChange);
|
||||
PieceListStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
handleDeleteSuccess(response) {
|
||||
this.refreshCollection();
|
||||
|
||||
EditionListActions.closeAllEditionLists();
|
||||
EditionListActions.clearAllEditionSelections();
|
||||
|
||||
let notification = new GlobalNotificationModel(response.notification, 'success');
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
|
||||
this.transitionTo('pieces');
|
||||
},
|
||||
|
||||
refreshCollection() {
|
||||
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
|
||||
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
|
||||
EditionListActions.refreshEditionList({pieceId: this.props.edition.parent});
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Row>
|
||||
@ -114,19 +82,16 @@ let Edition = React.createClass({
|
||||
</Col>
|
||||
<Col md={6} className="ascribe-edition-details">
|
||||
<div className="ascribe-detail-header">
|
||||
<hr/>
|
||||
<hr 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={ this.props.edition.date_created.slice(0, 4) } />
|
||||
<hr/>
|
||||
</div>
|
||||
<EditionSummary
|
||||
handleSuccess={this.props.loadEdition}
|
||||
refreshCollection={this.refreshCollection}
|
||||
currentUser={this.state.currentUser}
|
||||
edition={this.props.edition}
|
||||
handleDeleteSuccess={this.handleDeleteSuccess}/>
|
||||
|
||||
currentUser={this.state.currentUser}
|
||||
handleSuccess={this.props.loadEdition}/>
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('Certificate of Authenticity')}
|
||||
show={this.props.edition.acl.acl_coa === true}>
|
||||
@ -186,12 +151,13 @@ let Edition = React.createClass({
|
||||
show={this.props.edition.acl.acl_edit
|
||||
|| Object.keys(this.props.edition.extra_data).length > 0
|
||||
|| this.props.edition.other_data.length > 0}>
|
||||
<EditionFurtherDetails
|
||||
<FurtherDetails
|
||||
editable={this.props.edition.acl.acl_edit}
|
||||
pieceId={this.props.edition.parent}
|
||||
extraData={this.props.edition.extra_data}
|
||||
otherData={this.props.edition.other_data}
|
||||
handleSuccess={this.props.loadEdition}/>
|
||||
handleSuccess={this.props.loadEdition}
|
||||
location={this.props.location}/>
|
||||
</CollapsibleParagraph>
|
||||
|
||||
<CollapsibleParagraph
|
||||
@ -209,29 +175,14 @@ let Edition = React.createClass({
|
||||
let EditionSummary = React.createClass({
|
||||
propTypes: {
|
||||
edition: React.PropTypes.object,
|
||||
handleSuccess: React.PropTypes.func,
|
||||
currentUser: React.PropTypes.object,
|
||||
handleDeleteSuccess: React.PropTypes.func,
|
||||
refreshCollection: React.PropTypes.func
|
||||
},
|
||||
|
||||
getTransferWithdrawData(){
|
||||
return {'bitcoin_id': this.props.edition.bitcoin_id};
|
||||
handleSuccess: React.PropTypes.func
|
||||
},
|
||||
|
||||
handleSuccess() {
|
||||
this.props.refreshCollection();
|
||||
this.props.handleSuccess();
|
||||
},
|
||||
|
||||
showNotification(response){
|
||||
this.props.handleSuccess();
|
||||
|
||||
if (response){
|
||||
let notification = new GlobalNotificationModel(response.notification, 'success');
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
}
|
||||
},
|
||||
getStatus(){
|
||||
let status = null;
|
||||
if (this.props.edition.status.length > 0){
|
||||
@ -246,79 +197,26 @@ let EditionSummary = React.createClass({
|
||||
return status;
|
||||
},
|
||||
|
||||
getActions(){
|
||||
let actions = null;
|
||||
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}
|
||||
notifications={this.props.edition.notifications}/>);
|
||||
}
|
||||
|
||||
else {
|
||||
let withdrawButton = null;
|
||||
if (this.props.edition.status.length > 0 && this.props.edition.pending_new_owner && this.props.edition.acl.acl_withdraw_transfer) {
|
||||
withdrawButton = (
|
||||
<Form
|
||||
url={ApiUrls.ownership_transfers_withdraw}
|
||||
getFormData={this.getTransferWithdrawData}
|
||||
handleSuccess={this.showNotification}
|
||||
className='inline'
|
||||
isInline={true}>
|
||||
<Button bsStyle="danger" className="btn-delete pull-center" bsSize="small" type="submit">
|
||||
WITHDRAW TRANSFER
|
||||
</Button>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
let unconsignRequestButton = null;
|
||||
if (this.props.edition.acl.acl_request_unconsign) {
|
||||
unconsignRequestButton = (
|
||||
<UnConsignRequestButton
|
||||
currentUser={this.props.currentUser}
|
||||
edition={this.props.edition}
|
||||
handleSuccess={this.props.handleSuccess} />
|
||||
);
|
||||
}
|
||||
actions = (
|
||||
<Row>
|
||||
<Col md={12}>
|
||||
<AclButtonList
|
||||
className="text-center ascribe-button-list"
|
||||
availableAcls={this.props.edition.acl}
|
||||
editions={[this.props.edition]}
|
||||
handleSuccess={this.handleSuccess}>
|
||||
{withdrawButton}
|
||||
<DeleteButton
|
||||
handleSuccess={this.props.handleDeleteSuccess}
|
||||
editions={[this.props.edition]}/>
|
||||
{unconsignRequestButton}
|
||||
</AclButtonList>
|
||||
</Col>
|
||||
</Row>);
|
||||
}
|
||||
return actions;
|
||||
},
|
||||
render() {
|
||||
let { edition, currentUser } = this.props;
|
||||
return (
|
||||
<div className="ascribe-detail-header">
|
||||
<EditionDetailProperty
|
||||
label={getLangText('EDITION')}
|
||||
value={this.props.edition.edition_number + ' ' + getLangText('of') + ' ' + this.props.edition.num_editions} />
|
||||
value={ edition.edition_number + ' ' + getLangText('of') + ' ' + edition.num_editions} />
|
||||
<EditionDetailProperty
|
||||
label={getLangText('ID')}
|
||||
value={ this.props.edition.bitcoin_id }
|
||||
value={ edition.bitcoin_id }
|
||||
ellipsis={true} />
|
||||
<EditionDetailProperty
|
||||
label={getLangText('OWNER')}
|
||||
value={ this.props.edition.owner } />
|
||||
<LicenseDetail license={this.props.edition.license_type}/>
|
||||
value={ edition.owner } />
|
||||
<LicenseDetail license={edition.license_type}/>
|
||||
{this.getStatus()}
|
||||
{this.getActions()}
|
||||
<EditionActionPanel
|
||||
edition={edition}
|
||||
currentUser={currentUser}
|
||||
handleSuccess={this.handleSuccess} />
|
||||
<hr/>
|
||||
</div>
|
||||
);
|
||||
@ -336,12 +234,13 @@ let CoaDetails = React.createClass({
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
let { edition } = this.props;
|
||||
CoaStore.listen(this.onChange);
|
||||
if(this.props.edition.coa) {
|
||||
CoaActions.fetchOne(this.props.edition.coa);
|
||||
if(edition.coa) {
|
||||
CoaActions.fetchOrCreate(edition.coa, edition.bitcoin_id);
|
||||
}
|
||||
else {
|
||||
CoaActions.create(this.props.edition);
|
||||
CoaActions.create(edition.bitcoin_id);
|
||||
}
|
||||
},
|
||||
|
||||
@ -363,7 +262,7 @@ let CoaDetails = React.createClass({
|
||||
{getLangText('Download')} <Glyphicon glyph="cloud-download"/>
|
||||
</button>
|
||||
</a>
|
||||
<Link to="coa_verify">
|
||||
<Link to="/coa_verify">
|
||||
<button className="btn btn-default btn-xs">
|
||||
{getLangText('Verify')} <Glyphicon glyph="check"/>
|
||||
</button>
|
||||
@ -381,7 +280,7 @@ let CoaDetails = React.createClass({
|
||||
}
|
||||
return (
|
||||
<div className="text-center">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
||||
<AscribeSpinner color='dark-blue' size='lg'/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
169
js/components/ascribe_detail/edition_action_panel.js
Normal file
169
js/components/ascribe_detail/edition_action_panel.js
Normal file
@ -0,0 +1,169 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import Row from 'react-bootstrap/lib/Row';
|
||||
import Col from 'react-bootstrap/lib/Col';
|
||||
import Button from 'react-bootstrap/lib/Button';
|
||||
|
||||
import EditionListActions from '../../actions/edition_list_actions';
|
||||
import PieceListActions from '../../actions/piece_list_actions';
|
||||
import PieceListStore from '../../stores/piece_list_store';
|
||||
|
||||
import Form from './../ascribe_forms/form';
|
||||
import Property from './../ascribe_forms/property';
|
||||
|
||||
import ListRequestActions from './../ascribe_forms/list_form_request_actions';
|
||||
import AclButtonList from './../ascribe_buttons/acl_button_list';
|
||||
import UnConsignRequestButton from './../ascribe_buttons/unconsign_request_button';
|
||||
import DeleteButton from '../ascribe_buttons/delete_button';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
|
||||
import AclProxy from '../acl_proxy';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
/*
|
||||
A component that handles all the actions inside of the edition detail
|
||||
handleSuccess requires a loadEdition action (could be refactored)
|
||||
*/
|
||||
let EditionActionPanel = React.createClass({
|
||||
propTypes: {
|
||||
edition: React.PropTypes.object,
|
||||
currentUser: React.PropTypes.object,
|
||||
handleSuccess: React.PropTypes.func
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
getInitialState() {
|
||||
return PieceListStore.getState();
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
PieceListStore.listen(this.onChange);
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
PieceListStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
handleDeleteSuccess(response) {
|
||||
this.refreshCollection();
|
||||
|
||||
EditionListActions.closeAllEditionLists();
|
||||
EditionListActions.clearAllEditionSelections();
|
||||
|
||||
let notification = new GlobalNotificationModel(response.notification, 'success');
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
|
||||
this.transitionTo('pieces');
|
||||
},
|
||||
|
||||
refreshCollection() {
|
||||
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
|
||||
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
|
||||
EditionListActions.refreshEditionList({pieceId: this.props.edition.parent});
|
||||
},
|
||||
|
||||
handleSuccess(response){
|
||||
this.refreshCollection();
|
||||
this.props.handleSuccess();
|
||||
if (response){
|
||||
let notification = new GlobalNotificationModel(response.notification, 'success');
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
}
|
||||
},
|
||||
|
||||
render(){
|
||||
let {edition, currentUser} = this.props;
|
||||
|
||||
if (edition &&
|
||||
edition.notifications &&
|
||||
edition.notifications.length > 0){
|
||||
return (
|
||||
<ListRequestActions
|
||||
pieceOrEditions={[edition]}
|
||||
currentUser={currentUser}
|
||||
handleSuccess={this.handleSuccess}
|
||||
notifications={edition.notifications}/>);
|
||||
}
|
||||
|
||||
else {
|
||||
return (
|
||||
<Row>
|
||||
<Col md={12}>
|
||||
<AclButtonList
|
||||
className="text-center ascribe-button-list"
|
||||
availableAcls={edition.acl}
|
||||
editions={[edition]}
|
||||
handleSuccess={this.handleSuccess}>
|
||||
<AclProxy
|
||||
aclObject={edition.acl}
|
||||
aclName="acl_withdraw_transfer">
|
||||
<Form
|
||||
url={ApiUrls.ownership_transfers_withdraw}
|
||||
handleSuccess={this.handleSuccess}
|
||||
className='inline'
|
||||
isInline={true}>
|
||||
<Property
|
||||
name="bitcoin_id"
|
||||
hidden={true}>
|
||||
<input
|
||||
type="text"
|
||||
value={edition.bitcoin_id} />
|
||||
</Property>
|
||||
<Button bsStyle="danger" className="btn-delete pull-center" bsSize="small" type="submit">
|
||||
{getLangText('WITHDRAW TRANSFER')}
|
||||
</Button>
|
||||
</Form>
|
||||
</AclProxy>
|
||||
<AclProxy
|
||||
aclObject={edition.acl}
|
||||
aclName="acl_withdraw_consign">
|
||||
<Form
|
||||
url={ApiUrls.ownership_consigns_withdraw}
|
||||
handleSuccess={this.handleSuccess}
|
||||
className='inline'
|
||||
isInline={true}>
|
||||
<Property
|
||||
name="bitcoin_id"
|
||||
hidden={true}>
|
||||
<input
|
||||
type="text"
|
||||
value={edition.bitcoin_id} />
|
||||
</Property>
|
||||
<Button bsStyle="danger" className="btn-delete pull-center" bsSize="small" type="submit">
|
||||
{getLangText('WITHDRAW CONSIGN')}
|
||||
</Button>
|
||||
</Form>
|
||||
</AclProxy>
|
||||
<AclProxy
|
||||
aclObject={edition.acl}
|
||||
aclName="acl_request_unconsign">
|
||||
<UnConsignRequestButton
|
||||
currentUser={currentUser}
|
||||
edition={edition}
|
||||
handleSuccess={this.handleSuccess} />
|
||||
</AclProxy>
|
||||
<DeleteButton
|
||||
handleSuccess={this.handleDeleteSuccess}
|
||||
editions={[edition]}/>
|
||||
</AclButtonList>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default EditionActionPanel;
|
@ -7,14 +7,19 @@ import EditionStore from '../../stores/edition_store';
|
||||
|
||||
import Edition from './edition';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { setDocumentTitle } from '../../utils/dom_utils';
|
||||
|
||||
|
||||
/**
|
||||
* This is the component that implements resource/data specific functionality
|
||||
*/
|
||||
let EditionContainer = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return EditionStore.getState();
|
||||
},
|
||||
@ -62,15 +67,18 @@ let EditionContainer = React.createClass({
|
||||
|
||||
render() {
|
||||
if(this.state.edition && this.state.edition.title) {
|
||||
setDocumentTitle([this.state.edition.artist_name, this.state.edition.title].join(', '));
|
||||
|
||||
return (
|
||||
<Edition
|
||||
edition={this.state.edition}
|
||||
loadEdition={this.loadEdition}/>
|
||||
loadEdition={this.loadEdition}
|
||||
location={this.props.location}/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="fullpage-spinner">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
||||
<AscribeSpinner color='dark-blue' size='lg'/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import Form from './../ascribe_forms/form';
|
||||
|
||||
import PieceExtraDataForm from './../ascribe_forms/form_piece_extradata';
|
||||
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
|
||||
@ -17,13 +16,15 @@ import FurtherDetailsFileuploader from './further_details_fileuploader';
|
||||
|
||||
import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils';
|
||||
|
||||
|
||||
let FurtherDetails = React.createClass({
|
||||
propTypes: {
|
||||
editable: React.PropTypes.bool,
|
||||
pieceId: React.PropTypes.number,
|
||||
extraData: React.PropTypes.object,
|
||||
otherData: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
handleSuccess: React.PropTypes.func
|
||||
handleSuccess: React.PropTypes.func,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
@ -85,7 +86,8 @@ let FurtherDetails = React.createClass({
|
||||
overrideForm={true}
|
||||
pieceId={this.props.pieceId}
|
||||
otherData={this.props.otherData}
|
||||
multiple={true}/>
|
||||
multiple={true}
|
||||
location={this.props.location}/>
|
||||
</Form>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -20,7 +20,8 @@ let FurtherDetailsFileuploader = React.createClass({
|
||||
submitFile: React.PropTypes.func,
|
||||
isReadyForFormSubmission: React.PropTypes.func,
|
||||
editable: React.PropTypes.bool,
|
||||
multiple: React.PropTypes.bool
|
||||
multiple: React.PropTypes.bool,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
@ -43,7 +44,7 @@ let FurtherDetailsFileuploader = React.createClass({
|
||||
|
||||
return (
|
||||
<Property
|
||||
label="Additional files (max. 50MB per file)">
|
||||
label="Additional files">
|
||||
<ReactS3FineUploader
|
||||
uploadStarted={this.props.uploadStarted}
|
||||
keyRoutine={{
|
||||
@ -88,7 +89,8 @@ let FurtherDetailsFileuploader = React.createClass({
|
||||
}}
|
||||
areAssetsDownloadable={true}
|
||||
areAssetsEditable={this.props.editable}
|
||||
multiple={this.props.multiple}/>
|
||||
multiple={this.props.multiple}
|
||||
location={this.props.location}/>
|
||||
</Property>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import PieceActions from '../../actions/piece_actions';
|
||||
import PieceStore from '../../stores/piece_store';
|
||||
@ -35,16 +35,21 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
|
||||
import Note from './note';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { mergeOptions } from '../../utils/general_utils';
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../utils/dom_utils';
|
||||
|
||||
/**
|
||||
* This is the component that implements resource/data specific functionality
|
||||
*/
|
||||
let PieceContainer = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
mixins: [History],
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
@ -132,7 +137,7 @@ let PieceContainer = React.createClass({
|
||||
let notification = new GlobalNotificationModel(response.notification, 'success');
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
|
||||
this.transitionTo('pieces');
|
||||
this.history.pushState(null, '/collection');
|
||||
},
|
||||
|
||||
getCreateEditionsDialog() {
|
||||
@ -207,6 +212,8 @@ let PieceContainer = React.createClass({
|
||||
|
||||
render() {
|
||||
if(this.state.piece && this.state.piece.title) {
|
||||
setDocumentTitle([this.state.piece.artist_name, this.state.piece.title].join(', '));
|
||||
|
||||
return (
|
||||
<Piece
|
||||
piece={this.state.piece}
|
||||
@ -261,7 +268,8 @@ let PieceContainer = React.createClass({
|
||||
pieceId={this.state.piece.id}
|
||||
extraData={this.state.piece.extra_data}
|
||||
otherData={this.state.piece.other_data}
|
||||
handleSuccess={this.loadPiece}/>
|
||||
handleSuccess={this.loadPiece}
|
||||
location={this.props.location}/>
|
||||
</CollapsibleParagraph>
|
||||
|
||||
</Piece>
|
||||
@ -269,7 +277,7 @@ let PieceContainer = React.createClass({
|
||||
} else {
|
||||
return (
|
||||
<div className="fullpage-spinner">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />
|
||||
<AscribeSpinner color='dark-blue' size='lg'/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import Property from '../ascribe_forms/property';
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
@ -43,12 +44,12 @@ let CreateEditionsForm = React.createClass({
|
||||
buttons={
|
||||
<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login">
|
||||
className="btn btn-default btn-wide">
|
||||
{getLangText('Create editions')}
|
||||
</button>}
|
||||
spinner={
|
||||
<button 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" />
|
||||
<button className="btn btn-default btn-wide btn-spinner">
|
||||
<AscribeSpinner color="dark-blue" size="md" />
|
||||
</button>
|
||||
}>
|
||||
<Property
|
||||
|
@ -8,7 +8,7 @@ import Form from './form';
|
||||
import Property from './property';
|
||||
import InputTextAreaToggable from './input_textarea_toggable';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
import { getLangText } from '../../utils/lang_utils.js';
|
||||
|
||||
|
||||
@ -43,7 +43,9 @@ let ConsignForm = React.createClass({
|
||||
</div>}
|
||||
spinner={
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
<p className="pull-right">
|
||||
<AscribeSpinner color='dark-blue' size='md'/>
|
||||
</p>
|
||||
</div>}>
|
||||
<Property
|
||||
name='consignee'
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import ContractListActions from '../../actions/contract_list_actions';
|
||||
import ContractListStore from '../../stores/contract_list_store';
|
||||
@ -15,6 +15,7 @@ import PropertyCollapsible from './property_collapsible';
|
||||
import InputTextAreaToggable from './input_textarea_toggable';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { mergeOptions } from '../../utils/general_utils';
|
||||
@ -25,7 +26,7 @@ let ContractAgreementForm = React.createClass({
|
||||
handleSuccess: React.PropTypes.func
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
mixins: [History],
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
@ -57,7 +58,8 @@ let ContractAgreementForm = React.createClass({
|
||||
let notification = 'Contract agreement send';
|
||||
notification = new GlobalNotificationModel(notification, 'success', 10000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
this.transitionTo('pieces');
|
||||
|
||||
this.history.pushState(null, '/collection');
|
||||
},
|
||||
|
||||
getFormData(){
|
||||
@ -100,12 +102,12 @@ let ContractAgreementForm = React.createClass({
|
||||
handleSuccess={this.handleSubmitSuccess}
|
||||
buttons={<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login">
|
||||
className="btn btn-default btn-wide">
|
||||
{getLangText('Send contract')}
|
||||
</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" />
|
||||
<span className="btn btn-default btn-wide btn-spinner">
|
||||
<AscribeSpinner color="dark-blue" size="md" />
|
||||
</span>
|
||||
}>
|
||||
<div className="ascribe-form-header">
|
||||
@ -138,7 +140,7 @@ let ContractAgreementForm = React.createClass({
|
||||
<div>
|
||||
<p className="text-center">
|
||||
{getLangText('No contracts uploaded yet, please go to the ')}
|
||||
<a href="settings">{getLangText('settings page')}</a>
|
||||
<a href="contract_settings">{getLangText('contract settings page')}</a>
|
||||
{getLangText(' and create them.')}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -47,7 +47,7 @@ let CopyrightAssociationForm = React.createClass({
|
||||
handleSuccess={this.handleSubmitSuccess}>
|
||||
<Property
|
||||
name="copyright_association"
|
||||
className="ascribe-settings-property-collapsible-toggle"
|
||||
className="ascribe-property-collapsible-toggle"
|
||||
label={getLangText('Copyright Association')}
|
||||
style={{paddingBottom: 0}}>
|
||||
<select defaultValue={selectedState} name="contract">
|
||||
|
@ -28,7 +28,8 @@ let CreateContractForm = React.createClass({
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: React.PropTypes.string,
|
||||
plural: React.PropTypes.string
|
||||
})
|
||||
}),
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
@ -86,7 +87,8 @@ let CreateContractForm = React.createClass({
|
||||
areAssetsEditable={true}
|
||||
setIsUploadReady={this.setIsUploadReady}
|
||||
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
|
||||
fileClassToUpload={this.props.fileClassToUpload}/>
|
||||
fileClassToUpload={this.props.fileClassToUpload}
|
||||
location={this.props.location}/>
|
||||
</Property>
|
||||
<Property
|
||||
name='name'
|
||||
|
@ -5,7 +5,7 @@ import React from 'react';
|
||||
import Form from './form';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
@ -55,7 +55,9 @@ let EditionDeleteForm = React.createClass({
|
||||
}
|
||||
spinner={
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
<p className="pull-right">
|
||||
<AscribeSpinner color='dark-blue' size='md'/>
|
||||
</p>
|
||||
</div>
|
||||
}>
|
||||
<p>{getLangText('Are you sure you would like to permanently delete this edition')}?</p>
|
||||
|
@ -5,7 +5,7 @@ import React from 'react';
|
||||
import Form from '../ascribe_forms/form';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
@ -46,7 +46,9 @@ let PieceDeleteForm = React.createClass({
|
||||
}
|
||||
spinner={
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
<p className="pull-right">
|
||||
<AscribeSpinner color='dark-blue' size='md'/>
|
||||
</p>
|
||||
</div>
|
||||
}>
|
||||
<p>{getLangText('Are you sure you would like to permanently delete this piece')}?</p>
|
||||
|
@ -15,7 +15,7 @@ import InputCheckbox from './input_checkbox';
|
||||
import ContractAgreementListStore from '../../stores/contract_agreement_list_store';
|
||||
import ContractAgreementListActions from '../../actions/contract_agreement_list_actions';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { mergeOptions } from '../../utils/general_utils';
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
@ -130,6 +130,9 @@ let LoanForm = React.createClass({
|
||||
src={contract.blob.url_safe}
|
||||
alt="pdf"
|
||||
pluginspage="http://www.adobe.com/products/acrobat/readstep2.html"/>
|
||||
<a href={contract.blob.url_safe} target="_blank">
|
||||
<span className="glyphicon glyphicon-download" aria-hidden="true"></span> {getLangText('Download contract')}
|
||||
</a>
|
||||
{/* We still need to send the server information that we're accepting */}
|
||||
<InputCheckbox
|
||||
style={{'display': 'none'}}
|
||||
@ -141,7 +144,7 @@ let LoanForm = React.createClass({
|
||||
return (
|
||||
<Property
|
||||
name="terms"
|
||||
className="ascribe-settings-property-collapsible-toggle"
|
||||
className="ascribe-property-collapsible-toggle"
|
||||
style={{paddingBottom: 0}}>
|
||||
<InputCheckbox
|
||||
key="terms_explicitly"
|
||||
@ -191,7 +194,7 @@ let LoanForm = React.createClass({
|
||||
return (
|
||||
<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login">
|
||||
className="btn btn-default btn-wide">
|
||||
{getLangText('Finish process')}
|
||||
</button>
|
||||
);
|
||||
@ -222,7 +225,9 @@ let LoanForm = React.createClass({
|
||||
buttons={this.getButtons()}
|
||||
spinner={
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
<p className="pull-right">
|
||||
<AscribeSpinner color='dark-blue' size='md'/>
|
||||
</p>
|
||||
</div>}>
|
||||
<div className={classnames({'ascribe-form-header': true, 'hidden': !this.props.loanHeading})}>
|
||||
<h3>{this.props.loanHeading}</h3>
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
@ -14,6 +14,7 @@ import Property from './property';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
@ -24,10 +25,10 @@ let LoginForm = React.createClass({
|
||||
submitMessage: React.PropTypes.string,
|
||||
redirectOnLoggedIn: React.PropTypes.bool,
|
||||
redirectOnLoginSuccess: React.PropTypes.bool,
|
||||
onLogin: React.PropTypes.func
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
mixins: [History],
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
@ -52,50 +53,19 @@ let LoginForm = React.createClass({
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
|
||||
// if user is already logged in, redirect him to piece list
|
||||
if(this.state.currentUser && this.state.currentUser.email && this.props.redirectOnLoggedIn) {
|
||||
// FIXME: hack to redirect out of the dispatch cycle
|
||||
window.setTimeout(() => this.transitionTo('pieces'), 0);
|
||||
}
|
||||
},
|
||||
|
||||
handleSuccess(){
|
||||
handleSuccess({ success }){
|
||||
let notification = new GlobalNotificationModel('Login successful', 'success', 10000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
|
||||
// register_piece is waiting for a login success as login_container and it is wrapped
|
||||
// in a slides_container component.
|
||||
// The easiest way to check if the user was successfully logged in is to fetch the user
|
||||
// in the user store (which is obviously only possible if the user is logged in), since
|
||||
// register_piece is listening to the changes of the user_store.
|
||||
UserActions.fetchCurrentUser()
|
||||
.then(() => {
|
||||
if(this.props.redirectOnLoginSuccess) {
|
||||
/* Taken from http://stackoverflow.com/a/14916411 */
|
||||
/*
|
||||
We actually have to trick the Browser into showing the "save password" dialog
|
||||
as Chrome expects the login page to be reloaded after the login.
|
||||
Users on Stack Overflow claim this is a bug in chrome and should be fixed in the future.
|
||||
Until then, we redirect the HARD way, but reloading the whole page using window.location
|
||||
*/
|
||||
window.location = AppConstants.baseUrl + 'collection';
|
||||
} else if(this.props.onLogin) {
|
||||
// In some instances we want to give a callback to an outer container,
|
||||
// to show that the one login action the user triggered actually went through.
|
||||
// We can not do this by listening on a store's state as it wouldn't really tell us
|
||||
// if the user did log in or was just fetching the user's data again
|
||||
this.props.onLogin();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.logGlobal(err);
|
||||
});
|
||||
|
||||
if(success) {
|
||||
UserActions.fetchCurrentUser();
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
let email = this.getQuery().email || null;
|
||||
let email = this.props.location.query.email || null;
|
||||
return (
|
||||
<Form
|
||||
className="ascribe-form-bordered"
|
||||
@ -106,12 +76,12 @@ let LoginForm = React.createClass({
|
||||
buttons={
|
||||
<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login">
|
||||
className="btn btn-default btn-wide">
|
||||
{this.props.submitMessage}
|
||||
</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" />
|
||||
<span className="btn btn-default btn-wide btn-spinner">
|
||||
<AscribeSpinner color="dark-blue" size="md" />
|
||||
</span>
|
||||
}>
|
||||
<div className="ascribe-form-header">
|
||||
|
@ -11,6 +11,7 @@ import InputFineUploader from './input_fineuploader';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { mergeOptions } from '../../utils/general_utils';
|
||||
@ -29,7 +30,8 @@ let RegisterPieceForm = React.createClass({
|
||||
onLoggedOut: React.PropTypes.func,
|
||||
|
||||
// For this form to work with SlideContainer, we sometimes have to disable it
|
||||
disabled: React.PropTypes.bool
|
||||
disabled: React.PropTypes.bool,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
@ -83,14 +85,14 @@ let RegisterPieceForm = React.createClass({
|
||||
buttons={
|
||||
<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login"
|
||||
className="btn btn-default btn-wide"
|
||||
disabled={!this.state.isUploadReady || this.props.disabled}>
|
||||
{this.props.submitMessage}
|
||||
</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" />
|
||||
<span className="btn btn-default btn-wide btn-spinner">
|
||||
<AscribeSpinner color="dark-blue" size="md" />
|
||||
</span>
|
||||
}>
|
||||
<div className="ascribe-form-header">
|
||||
@ -113,7 +115,8 @@ let RegisterPieceForm = React.createClass({
|
||||
isFineUploaderActive={this.props.isFineUploaderActive}
|
||||
onLoggedOut={this.props.onLoggedOut}
|
||||
disabled={!this.props.isFineUploaderEditable}
|
||||
enableLocalHashing={enableLocalHashing}/>
|
||||
enableLocalHashing={enableLocalHashing}
|
||||
location={this.props.location}/>
|
||||
</Property>
|
||||
<Property
|
||||
name='artist_name'
|
||||
|
@ -5,7 +5,7 @@ import React from 'react';
|
||||
import Form from './form';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
@ -53,7 +53,9 @@ let EditionRemoveFromCollectionForm = React.createClass({
|
||||
}
|
||||
spinner={
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
<p className="pull-right">
|
||||
<AscribeSpinner color='dark-blue' size='md'/>
|
||||
</p>
|
||||
</div>
|
||||
}>
|
||||
<p>{getLangText('Are you sure you would like to remove these editions from your collection')}?</p>
|
||||
|
@ -5,7 +5,7 @@ import React from 'react';
|
||||
import Form from './form';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
@ -46,7 +46,9 @@ let PieceRemoveFromCollectionForm = React.createClass({
|
||||
}
|
||||
spinner={
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
<p className="pull-right">
|
||||
<AscribeSpinner color='dark-blue' size='md'/>
|
||||
</p>
|
||||
</div>
|
||||
}>
|
||||
<p>{getLangText('Are you sure you would like to remove this piece from your collection')}?</p>
|
||||
|
@ -8,7 +8,7 @@ import InputTextAreaToggable from './input_textarea_toggable';
|
||||
|
||||
import Button from 'react-bootstrap/lib/Button';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils.js';
|
||||
|
||||
@ -47,7 +47,9 @@ let ShareForm = React.createClass({
|
||||
</div>}
|
||||
spinner={
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
<p className="pull-right">
|
||||
<AscribeSpinner color='dark-blue' size='md'/>
|
||||
</p>
|
||||
</div>}>
|
||||
<Property
|
||||
name='share_emails'
|
||||
|
@ -1,11 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import UserStore from '../../stores/user_store';
|
||||
import UserActions from '../../actions/user_actions';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
@ -15,18 +14,21 @@ import Property from './property';
|
||||
import InputCheckbox from './input_checkbox';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
|
||||
let SignupForm = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
headerMessage: React.PropTypes.string,
|
||||
submitMessage: React.PropTypes.string,
|
||||
handleSuccess: React.PropTypes.func,
|
||||
children: React.PropTypes.element
|
||||
children: React.PropTypes.element,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
mixins: [History],
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
@ -34,6 +36,7 @@ let SignupForm = React.createClass({
|
||||
submitMessage: getLangText('Sign up')
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return UserStore.getState();
|
||||
},
|
||||
@ -48,27 +51,23 @@ let SignupForm = React.createClass({
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
|
||||
// if user is already logged in, redirect him to piece list
|
||||
if(this.state.currentUser && this.state.currentUser.email) {
|
||||
this.transitionTo('pieces');
|
||||
}
|
||||
},
|
||||
|
||||
handleSuccess(response){
|
||||
handleSuccess(response) {
|
||||
if (response.user) {
|
||||
let notification = new GlobalNotificationModel(getLangText('Sign up successful'), 'success', 50000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
|
||||
// Refactor this to its own component
|
||||
this.props.handleSuccess(getLangText('We sent an email to your address') + ' ' + response.user.email + ', ' + getLangText('please confirm') + '.');
|
||||
}
|
||||
else if (response.redirect) {
|
||||
this.transitionTo('pieces');
|
||||
} else {
|
||||
UserActions.fetchCurrentUser();
|
||||
}
|
||||
},
|
||||
|
||||
getFormData() {
|
||||
if (this.getQuery().token){
|
||||
return {token: this.getQuery().token};
|
||||
if (this.props.location.query.token){
|
||||
return {token: this.props.location.query.token};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
@ -77,7 +76,9 @@ let SignupForm = React.createClass({
|
||||
let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' +
|
||||
getLangText('This password is securing your digital property like a bank account') + '.\n ' +
|
||||
getLangText('Store it in a safe place') + '!';
|
||||
let email = this.getQuery().email || null;
|
||||
|
||||
let email = this.props.location.query.email || null;
|
||||
|
||||
return (
|
||||
<Form
|
||||
className="ascribe-form-bordered"
|
||||
@ -86,12 +87,12 @@ let SignupForm = React.createClass({
|
||||
getFormData={this.getFormData}
|
||||
handleSuccess={this.handleSuccess}
|
||||
buttons={
|
||||
<button type="submit" className="btn ascribe-btn ascribe-btn-login">
|
||||
<button type="submit" className="btn btn-default btn-wide">
|
||||
{this.props.submitMessage}
|
||||
</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" />
|
||||
<span className="btn btn-default btn-wide btn-spinner">
|
||||
<AscribeSpinner color="dark-blue" size="md" />
|
||||
</span>
|
||||
}>
|
||||
<div className="ascribe-form-header">
|
||||
@ -130,7 +131,7 @@ let SignupForm = React.createClass({
|
||||
{this.props.children}
|
||||
<Property
|
||||
name="terms"
|
||||
className="ascribe-settings-property-collapsible-toggle"
|
||||
className="ascribe-property-collapsible-toggle"
|
||||
style={{paddingBottom: 0}}>
|
||||
<InputCheckbox>
|
||||
<span>
|
||||
|
@ -9,7 +9,7 @@ import InputCheckbox from './input_checkbox';
|
||||
|
||||
import Alert from 'react-bootstrap/lib/Alert';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils.js';
|
||||
@ -40,7 +40,9 @@ let PieceSubmitToPrizeForm = React.createClass({
|
||||
</div>}
|
||||
spinner={
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
<p className="pull-right">
|
||||
<AscribeSpinner color='dark-blue' size='md'/>
|
||||
</p>
|
||||
</div>}>
|
||||
<Property
|
||||
name='artist_statement'
|
||||
@ -64,7 +66,7 @@ let PieceSubmitToPrizeForm = React.createClass({
|
||||
</Property>
|
||||
<Property
|
||||
name="terms"
|
||||
className="ascribe-settings-property-collapsible-toggle"
|
||||
className="ascribe-property-collapsible-toggle"
|
||||
style={{paddingBottom: 0}}>
|
||||
<InputCheckbox>
|
||||
<span>
|
||||
|
@ -9,8 +9,8 @@ import Form from './form';
|
||||
import Property from './property';
|
||||
import InputTextAreaToggable from './input_textarea_toggable';
|
||||
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import { getLangText } from '../../utils/lang_utils.js';
|
||||
|
||||
|
||||
@ -48,7 +48,9 @@ let TransferForm = React.createClass({
|
||||
</div>}
|
||||
spinner={
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
<p className="pull-right">
|
||||
<AscribeSpinner color='dark-blue' size='md'/>
|
||||
</p>
|
||||
</div>}>
|
||||
<Property
|
||||
name='transferee'
|
||||
|
@ -8,7 +8,7 @@ import Form from './form';
|
||||
import Property from './property';
|
||||
import InputTextAreaToggable from './input_textarea_toggable';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
import { getLangText } from '../../utils/lang_utils.js';
|
||||
|
||||
|
||||
@ -45,7 +45,9 @@ let UnConsignForm = React.createClass({
|
||||
</div>}
|
||||
spinner={
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
<p className="pull-right">
|
||||
<AscribeSpinner color='dark-blue' size='md'/>
|
||||
</p>
|
||||
</div>}>
|
||||
<Property
|
||||
name='unconsign_message'
|
||||
|
@ -8,8 +8,7 @@ import Form from './form';
|
||||
import Property from './property';
|
||||
import InputTextAreaToggable from './input_textarea_toggable';
|
||||
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
import { getLangText } from '../../utils/lang_utils.js';
|
||||
|
||||
|
||||
@ -45,7 +44,9 @@ let UnConsignRequestForm = React.createClass({
|
||||
</div>}
|
||||
spinner={
|
||||
<div className="modal-footer">
|
||||
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
|
||||
<p className="pull-right">
|
||||
<AscribeSpinner color='dark-blue' size='md'/>
|
||||
</p>
|
||||
</div>}>
|
||||
<Property
|
||||
name='unconsign_request_message'
|
||||
|
@ -22,6 +22,7 @@ let InputCheckbox = React.createClass({
|
||||
React.PropTypes.arrayOf(React.PropTypes.element),
|
||||
React.PropTypes.element
|
||||
]),
|
||||
name: React.PropTypes.string,
|
||||
|
||||
// provided by Property
|
||||
disabled: React.PropTypes.bool,
|
||||
@ -50,7 +51,7 @@ let InputCheckbox = React.createClass({
|
||||
|
||||
// Developer's are used to define defaultValues for inputs via defaultValue, but since this is a
|
||||
// input of type checkbox we warn the dev to not do that.
|
||||
if(this.props.defaultValue) {
|
||||
if(this.props.defaultValue) { //eslint-disable-line react/prop-types
|
||||
console.warn('InputCheckbox is of type checkbox. Therefore its value is represented by checked and defaultChecked. defaultValue will do nothing!');
|
||||
}
|
||||
|
||||
@ -102,8 +103,10 @@ let InputCheckbox = React.createClass({
|
||||
return (
|
||||
<span
|
||||
style={this.props.style}
|
||||
onClick={this.onChange}>
|
||||
onClick={this.onChange}
|
||||
name={this.props.name}>
|
||||
<input
|
||||
name={this.props.name}
|
||||
type="checkbox"
|
||||
ref="checkbox"
|
||||
onChange={this.onChange}
|
||||
@ -119,4 +122,4 @@ let InputCheckbox = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
export default InputCheckbox;
|
||||
export default InputCheckbox;
|
||||
|
@ -46,7 +46,8 @@ let InputFineUploader = React.createClass({
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: React.PropTypes.string,
|
||||
plural: React.PropTypes.string
|
||||
})
|
||||
}),
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
@ -106,7 +107,8 @@ let InputFineUploader = React.createClass({
|
||||
}}
|
||||
onInactive={this.props.onLoggedOut}
|
||||
enableLocalHashing={this.props.enableLocalHashing}
|
||||
fileClassToUpload={this.props.fileClassToUpload}/>
|
||||
fileClassToUpload={this.props.fileClassToUpload}
|
||||
location={this.props.location}/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -31,6 +31,7 @@ let Property = React.createClass({
|
||||
footer: React.PropTypes.element,
|
||||
handleChange: React.PropTypes.func,
|
||||
ignoreFocus: React.PropTypes.bool,
|
||||
name: React.PropTypes.string.isRequired,
|
||||
className: React.PropTypes.string,
|
||||
|
||||
onClick: React.PropTypes.func,
|
||||
@ -146,7 +147,12 @@ let Property = React.createClass({
|
||||
if(typeof this.props.onClick === 'function') {
|
||||
this.props.onClick();
|
||||
}
|
||||
|
||||
// skip the focus of non-input elements
|
||||
let nonInputHTMLElements = ['pre', 'div'];
|
||||
if (this.refs.input &&
|
||||
nonInputHTMLElements.indexOf(this.refs.input.getDOMNode().nodeName.toLowerCase()) > -1 ) {
|
||||
return;
|
||||
}
|
||||
this.refs.input.getDOMNode().focus();
|
||||
this.setState({
|
||||
isFocused: true
|
||||
@ -210,7 +216,8 @@ let Property = React.createClass({
|
||||
onFocus: this.handleFocus,
|
||||
onBlur: this.handleBlur,
|
||||
disabled: !this.props.editable,
|
||||
ref: 'input'
|
||||
ref: 'input',
|
||||
name: this.props.name
|
||||
});
|
||||
});
|
||||
},
|
||||
@ -240,15 +247,14 @@ let Property = React.createClass({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={'ascribe-settings-wrapper ' + this.getClassName()}
|
||||
className={'ascribe-property-wrapper ' + this.getClassName()}
|
||||
onClick={this.handleFocus}
|
||||
onFocus={this.handleFocus}
|
||||
style={style}>
|
||||
<OverlayTrigger
|
||||
delay={500}
|
||||
placement="top"
|
||||
overlay={tooltip}>
|
||||
<div className={'ascribe-settings-property ' + this.props.className}>
|
||||
<div className={'ascribe-property ' + this.props.className}>
|
||||
{this.state.errors}
|
||||
<span>{this.props.label}</span>
|
||||
{this.renderChildren(style)}
|
||||
@ -260,4 +266,4 @@ let Property = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
export default Property;
|
||||
export default Property;
|
||||
|
@ -62,14 +62,14 @@ let PropertyCollapsile = React.createClass({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={'ascribe-settings-wrapper'}
|
||||
className={'ascribe-property-wrapper'}
|
||||
style={style}>
|
||||
<OverlayTrigger
|
||||
delay={500}
|
||||
placement="top"
|
||||
overlay={tooltip}>
|
||||
<div
|
||||
className="ascribe-settings-property-collapsible-toggle"
|
||||
className="ascribe-property-collapsible-toggle"
|
||||
onClick={this.handleFocus}
|
||||
onFocus={this.handleFocus}>
|
||||
<input
|
||||
@ -84,7 +84,7 @@ let PropertyCollapsile = React.createClass({
|
||||
collapsible
|
||||
expanded={this.state.show}
|
||||
className="bs-custom-panel">
|
||||
<div className="ascribe-settings-property">
|
||||
<div className="ascribe-property">
|
||||
{this.renderChildren()}
|
||||
</div>
|
||||
</Panel>
|
||||
|
@ -103,14 +103,17 @@ let Video = React.createClass({
|
||||
* ReactJS is responsible for DOM manipulation but VideoJS updates the DOM
|
||||
* to install itself to display the video, therefore ReactJS complains that we are
|
||||
* changing the DOM under its feet.
|
||||
* The component supports a fall-back to HTML5 video tag.
|
||||
*
|
||||
* What we do is the following:
|
||||
* 1) set `state.ready = false`
|
||||
* 2) render the cover using the `<Image />` component (because ready is false)
|
||||
* 1) set `state.libraryLoaded = null` (state.libraryLoaded can be in three states: `null`
|
||||
* if we don't know anything about it, `true` if the external library has been loaded,
|
||||
* `false` if we failed to load the external library)
|
||||
* 2) render the cover using the `<Image />` component (because libraryLoaded is null)
|
||||
* 3) on `componentDidMount`, we load the external `css` and `js` resources using
|
||||
* the `InjectInHeadMixin`, attaching a function to `Promise.then` to change
|
||||
* `state.ready` to true
|
||||
* 4) when the promise is succesfully resolved, we change `state.ready` triggering
|
||||
* `state.libraryLoaded` to true
|
||||
* 4) when the promise is succesfully resolved, we change `state.libraryLoaded` triggering
|
||||
* a re-render
|
||||
* 5) the new render calls `prepareVideoHTML` to get the raw HTML of the video tag
|
||||
* (that will be later processed and expanded by VideoJS)
|
||||
@ -129,18 +132,19 @@ let Video = React.createClass({
|
||||
mixins: [InjectInHeadMixin],
|
||||
|
||||
getInitialState() {
|
||||
return { ready: false, videoMounted: false };
|
||||
return { libraryLoaded: null, videoMounted: false };
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
Q.all([
|
||||
this.inject('//vjs.zencdn.net/4.12/video-js.css'),
|
||||
this.inject('//vjs.zencdn.net/4.12/video.js')
|
||||
]).then(this.ready);
|
||||
this.inject('//vjs.zencdn.net/4.12/video.js')])
|
||||
.then(() => this.setState({libraryLoaded: true}))
|
||||
.fail(() => this.setState({libraryLoaded: false}));
|
||||
},
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.ready && !this.state.videoMounted) {
|
||||
if (this.state.libraryLoaded && !this.state.videoMounted) {
|
||||
window.videojs('#mainvideo');
|
||||
/* eslint-disable */
|
||||
this.setState({videoMounted: true});
|
||||
@ -149,11 +153,9 @@ let Video = React.createClass({
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
window.videojs('#mainvideo').dispose();
|
||||
},
|
||||
|
||||
ready() {
|
||||
this.setState({ready: true, videoMounted: false});
|
||||
if (this.state.videoMounted) {
|
||||
window.videojs('#mainvideo').dispose();
|
||||
}
|
||||
},
|
||||
|
||||
prepareVideoHTML() {
|
||||
@ -171,7 +173,7 @@ let Video = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
if (this.state.ready) {
|
||||
if (this.state.libraryLoaded !== null) {
|
||||
return (
|
||||
<div dangerouslySetInnerHTML={{__html: this.prepareVideoHTML() }}/>
|
||||
);
|
||||
|
@ -1,11 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
let PaginationButton = React.createClass({
|
||||
propTypes: {
|
||||
@ -29,21 +29,21 @@ let PaginationButton = React.createClass({
|
||||
page -= 1;
|
||||
directionDisplay = (
|
||||
<span>
|
||||
<span aria-hidden="true">←</span> {getLangText('Previous')}
|
||||
<span aria-hidden="true"><Glyphicon glyph='chevron-left'/></span> {getLangText('Previous')}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
page += 1;
|
||||
directionDisplay = (
|
||||
<span>
|
||||
{getLangText('Next')} <span aria-hidden="true">→</span>
|
||||
{getLangText('Next')} <span aria-hidden="true"><Glyphicon glyph='chevron-right'/></span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.isInRange(page)) {
|
||||
anchor = (
|
||||
<Link to="pieces"
|
||||
<Link to="/collection"
|
||||
query={{page}}
|
||||
onClick={this.props.goToPage(page)}>
|
||||
{directionDisplay}
|
||||
|
@ -33,21 +33,28 @@ let PieceListBulkModal = React.createClass({
|
||||
);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
EditionListStore.listen(this.onChange);
|
||||
UserStore.listen(this.onChange);
|
||||
PieceListStore.listen(this.onChange);
|
||||
|
||||
UserActions.fetchCurrentUser();
|
||||
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
|
||||
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
EditionListStore.unlisten(this.onChange);
|
||||
PieceListStore.unlisten(this.onChange);
|
||||
UserStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
fetchSelectedPieceEditionList() {
|
||||
let filteredPieceIdList = Object.keys(this.state.editionList)
|
||||
.filter((pieceId) => {
|
||||
|
@ -4,16 +4,16 @@ import React from 'react';
|
||||
|
||||
import PieceListToolbarFilterWidget from './piece_list_toolbar_filter_widget';
|
||||
import PieceListToolbarOrderWidget from './piece_list_toolbar_order_widget';
|
||||
import SearchBar from '../search_bar';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
|
||||
import Input from 'react-bootstrap/lib/Input';
|
||||
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let PieceListToolbar = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
searchFor: React.PropTypes.func,
|
||||
searchQuery: React.PropTypes.string,
|
||||
filterParams: React.PropTypes.arrayOf(
|
||||
React.PropTypes.shape({
|
||||
label: React.PropTypes.string,
|
||||
@ -39,11 +39,6 @@ let PieceListToolbar = React.createClass({
|
||||
])
|
||||
},
|
||||
|
||||
searchFor() {
|
||||
let searchTerm = this.refs.search.getInputDOMNode().value;
|
||||
this.props.searchFor(searchTerm);
|
||||
},
|
||||
|
||||
getFilterWidget(){
|
||||
if (this.props.filterParams){
|
||||
return (
|
||||
@ -55,6 +50,7 @@ let PieceListToolbar = React.createClass({
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getOrderWidget(){
|
||||
if (this.props.orderParams){
|
||||
return (
|
||||
@ -68,28 +64,25 @@ let PieceListToolbar = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
let searchIcon = <Glyphicon glyph='search' className="filter-glyph"/>;
|
||||
const { className, children, searchFor, searchQuery } = this.props;
|
||||
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<div className={className}>
|
||||
<div className="row">
|
||||
<div className="col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2">
|
||||
<div className="row">
|
||||
<span className="pull-left">
|
||||
{this.props.children}
|
||||
</span>
|
||||
<span className="pull-right search-bar ascribe-input-glyph">
|
||||
<Input
|
||||
type='text'
|
||||
ref="search"
|
||||
placeholder={getLangText('Search%s', '...')}
|
||||
onChange={this.searchFor}
|
||||
addonAfter={searchIcon} />
|
||||
{children}
|
||||
</span>
|
||||
<span className="pull-right">
|
||||
{this.getOrderWidget()}
|
||||
{this.getFilterWidget()}
|
||||
</span>
|
||||
<SearchBar
|
||||
className="pull-right search-bar ascribe-input-glyph"
|
||||
searchFor={searchFor}
|
||||
searchQuery={searchQuery}
|
||||
threshold={AppConstants.searchThreshold}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -76,7 +76,7 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({
|
||||
render() {
|
||||
let filterIcon = (
|
||||
<span>
|
||||
<span className="glyphicon glyphicon-filter" aria-hidden="true"></span>
|
||||
<span className="ascribe-icon icon-ascribe-filter" aria-hidden="true"></span>
|
||||
<span style={this.isFilterActive()}>*</span>
|
||||
</span>
|
||||
);
|
||||
|
@ -47,8 +47,8 @@ let PieceListToolbarOrderWidget = React.createClass({
|
||||
render() {
|
||||
let filterIcon = (
|
||||
<span>
|
||||
<span className="glyphicon glyphicon-sort-by-alphabet" aria-hidden="true"></span>
|
||||
<span style={this.isOrderActive()}>*</span>
|
||||
<span className="ascribe-icon icon-ascribe-sort" aria-hidden="true"></span>
|
||||
<span style={this.isOrderActive()}>·</span>
|
||||
</span>
|
||||
);
|
||||
return (
|
||||
|
107
js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js
Normal file
107
js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js
Normal file
@ -0,0 +1,107 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import UserStore from '../../../stores/user_store';
|
||||
import UserActions from '../../../actions/user_actions';
|
||||
|
||||
import AppConstants from '../../../constants/application_constants';
|
||||
|
||||
|
||||
const { object } = React.PropTypes;
|
||||
const WHEN_ENUM = ['loggedIn', 'loggedOut'];
|
||||
|
||||
/**
|
||||
* Can be used in combination with `Route` as an intermediate Handler
|
||||
* between the actual component we want to display dependent on a certain state
|
||||
* that is required to display that component.
|
||||
*
|
||||
* @param {string} options.to Any type of route path that is defined in routes.js
|
||||
* @param {enum/string} options.when ('loggedIn' || 'loggedOut')
|
||||
*/
|
||||
export default function AuthProxyHandler({to, when}) {
|
||||
|
||||
// validate `when`, must be contained in `WHEN_ENUM`.
|
||||
// Throw an error otherwise.
|
||||
if(WHEN_ENUM.indexOf(when) === -1) {
|
||||
let whenValues = WHEN_ENUM.join(', ');
|
||||
throw new Error(`"when" must be one of: [${whenValues}] got "${when}" instead`);
|
||||
}
|
||||
|
||||
return (Component) => {
|
||||
return React.createClass({
|
||||
propTypes: {
|
||||
location: object
|
||||
},
|
||||
|
||||
mixins: [History],
|
||||
|
||||
getInitialState() {
|
||||
return UserStore.getState();
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
UserStore.listen(this.onChange);
|
||||
UserActions.fetchCurrentUser();
|
||||
},
|
||||
|
||||
componentDidUpdate() {
|
||||
this.redirectConditionally();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
UserStore.unlisten(this.onChange);
|
||||
},
|
||||
|
||||
redirectConditionally() {
|
||||
const { query } = this.props.location;
|
||||
const { redirectAuthenticated, redirect } = query;
|
||||
|
||||
// The user of this handler specifies with `when`, what kind of status
|
||||
// needs to be checked to conditionally do - if that state is `true` -
|
||||
// a redirect.
|
||||
//
|
||||
// So if when === 'loggedIn', we're checking if the user is logged in (and
|
||||
// vice versa)
|
||||
let exprToValidate = when === 'loggedIn' ?
|
||||
this.state.currentUser && this.state.currentUser.email :
|
||||
this.state.currentUser && !this.state.currentUser.email;
|
||||
|
||||
// and redirect if `true`.
|
||||
if(exprToValidate) {
|
||||
window.setTimeout(() => this.history.replaceState(null, to, query));
|
||||
|
||||
// Otherwise there can also be the case that the backend
|
||||
// wants to redirect the user to a specific route when the user is logged out already
|
||||
} else if(!exprToValidate && when === 'loggedIn' && redirect) {
|
||||
|
||||
delete query.redirect;
|
||||
window.setTimeout(() => this.history.replaceState(null, '/' + redirect, query));
|
||||
|
||||
} else if(!exprToValidate && when === 'loggedOut' && redirectAuthenticated) {
|
||||
/*
|
||||
* redirectAuthenticated contains an arbirary path
|
||||
* eg pieces/<id>, editions/<bitcoin_id>, collection, settings, ...
|
||||
* hence transitionTo cannot be used directly.
|
||||
*
|
||||
* While we're getting rid of `query.redirect` explicitly in the
|
||||
* above `else if` statement, here it's sufficient to just call
|
||||
* `baseUrl` + `redirectAuthenticated`, as it gets rid of queries as well.
|
||||
*/
|
||||
window.location = AppConstants.baseUrl + redirectAuthenticated;
|
||||
}
|
||||
},
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Component {...this.props}/>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
@ -15,7 +15,7 @@ import AclProxy from '../acl_proxy';
|
||||
import CopyrightAssociationForm from '../ascribe_forms/form_copyright_association';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
@ -37,7 +37,7 @@ let AccountSettings = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||
let content = <AscribeSpinner color='dark-blue' size='lg'/>;
|
||||
let profile = null;
|
||||
|
||||
if (this.props.currentUser.username) {
|
||||
@ -78,7 +78,7 @@ let AccountSettings = React.createClass({
|
||||
getFormData={this.getFormDataProfile}>
|
||||
<Property
|
||||
name="hash_locally"
|
||||
className="ascribe-settings-property-collapsible-toggle"
|
||||
className="ascribe-property-collapsible-toggle"
|
||||
style={{paddingBottom: 0}}>
|
||||
<InputCheckbox
|
||||
defaultChecked={this.props.currentUser.profile.hash_locally}>
|
||||
@ -96,11 +96,15 @@ let AccountSettings = React.createClass({
|
||||
title={getLangText('Account')}
|
||||
defaultExpanded={true}>
|
||||
{content}
|
||||
<CopyrightAssociationForm currentUser={this.props.currentUser}/>
|
||||
<AclProxy
|
||||
aclObject={this.props.whitelabel}
|
||||
aclName="acl_view_settings_copyright_association">
|
||||
<CopyrightAssociationForm currentUser={this.props.currentUser}/>
|
||||
</AclProxy>
|
||||
{profile}
|
||||
</CollapsibleParagraph>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default AccountSettings;
|
||||
export default AccountSettings;
|
||||
|
@ -15,7 +15,7 @@ import ActionPanel from '../ascribe_panel/action_panel';
|
||||
import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph';
|
||||
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
@ -57,7 +57,7 @@ let APISettings = React.createClass({
|
||||
},
|
||||
|
||||
getApplications(){
|
||||
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||
let content = <AscribeSpinner color='dark-blue' size='lg'/>;
|
||||
|
||||
if (this.state.applications.length > -1) {
|
||||
content = this.state.applications.map(function(app, i) {
|
||||
|
@ -10,7 +10,7 @@ import Property from '../ascribe_forms/property';
|
||||
|
||||
import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import AscribeSpinner from '../ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
@ -38,7 +38,7 @@ let BitcoinWalletSettings = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||
let content = <AscribeSpinner color='dark-blue' size='lg'/>;
|
||||
|
||||
if (this.state.walletSettings.btc_public_key) {
|
||||
content = (
|
||||
|
@ -23,10 +23,15 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
|
||||
import AclProxy from '../acl_proxy';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../utils/dom_utils';
|
||||
import { mergeOptions, truncateTextAtCharIndex } from '../../utils/general_utils';
|
||||
|
||||
|
||||
let ContractSettings = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState(){
|
||||
return mergeOptions(
|
||||
ContractListStore.getState(),
|
||||
@ -82,6 +87,8 @@ let ContractSettings = React.createClass({
|
||||
let privateContracts = this.getPrivateContracts();
|
||||
let createPublicContractForm = null;
|
||||
|
||||
setDocumentTitle(getLangText('Contracts settings'));
|
||||
|
||||
if(publicContracts.length === 0) {
|
||||
createPublicContractForm = (
|
||||
<CreateContractForm
|
||||
@ -89,7 +96,8 @@ let ContractSettings = React.createClass({
|
||||
fileClassToUpload={{
|
||||
singular: 'new contract',
|
||||
plural: 'new contracts'
|
||||
}}/>
|
||||
}}
|
||||
location={this.props.location}/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -114,7 +122,9 @@ let ContractSettings = React.createClass({
|
||||
<AclProxy
|
||||
aclObject={this.state.whitelabel}
|
||||
aclName="acl_update_public_contract">
|
||||
<ContractSettingsUpdateButton contract={contract}/>
|
||||
<ContractSettingsUpdateButton
|
||||
contract={contract}
|
||||
location={this.props.location}/>
|
||||
</AclProxy>
|
||||
<a
|
||||
className="btn btn-default btn-sm margin-left-2px"
|
||||
@ -144,7 +154,8 @@ let ContractSettings = React.createClass({
|
||||
fileClassToUpload={{
|
||||
singular: getLangText('new contract'),
|
||||
plural: getLangText('new contracts')
|
||||
}}/>
|
||||
}}
|
||||
location={this.props.location}/>
|
||||
{privateContracts.map((contract, i) => {
|
||||
return (
|
||||
<ActionPanel
|
||||
@ -156,7 +167,9 @@ let ContractSettings = React.createClass({
|
||||
<AclProxy
|
||||
aclObject={this.state.whitelabel}
|
||||
aclName="acl_update_private_contract">
|
||||
<ContractSettingsUpdateButton contract={contract}/>
|
||||
<ContractSettingsUpdateButton
|
||||
contract={contract}
|
||||
location={this.props.location}/>
|
||||
</AclProxy>
|
||||
<a
|
||||
className="btn btn-default btn-sm margin-left-2px"
|
||||
@ -183,4 +196,4 @@ let ContractSettings = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
export default ContractSettings;
|
||||
export default ContractSettings;
|
||||
|
@ -20,7 +20,8 @@ import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let ContractSettingsUpdateButton = React.createClass({
|
||||
propTypes: {
|
||||
contract: React.PropTypes.object
|
||||
contract: React.PropTypes.object,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
submitFile(file) {
|
||||
@ -90,7 +91,7 @@ let ContractSettingsUpdateButton = React.createClass({
|
||||
}}
|
||||
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
|
||||
submitFile={this.submitFile}
|
||||
/>
|
||||
location={this.props.location}/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import UserStore from '../../stores/user_store';
|
||||
import UserActions from '../../actions/user_actions';
|
||||
@ -16,6 +15,8 @@ import APISettings from './api_settings';
|
||||
import AclProxy from '../acl_proxy';
|
||||
|
||||
import { mergeOptions } from '../../utils/general_utils';
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../utils/dom_utils';
|
||||
|
||||
|
||||
let SettingsContainer = React.createClass({
|
||||
@ -25,8 +26,6 @@ let SettingsContainer = React.createClass({
|
||||
React.PropTypes.element])
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
UserStore.getState(),
|
||||
@ -56,6 +55,8 @@ let SettingsContainer = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Account settings'));
|
||||
|
||||
if (this.state.currentUser && this.state.currentUser.username) {
|
||||
return (
|
||||
<div className="settings-container">
|
||||
|
@ -1,96 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import ReactAddons from 'react/addons';
|
||||
import React from 'react/addons';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import SlidesContainerBreadcrumbs from './slides_container_breadcrumbs';
|
||||
|
||||
let State = Router.State;
|
||||
let Navigation = Router.Navigation;
|
||||
|
||||
const { arrayOf, element, bool, shape, string, object } = React.PropTypes;
|
||||
|
||||
let SlidesContainer = React.createClass({
|
||||
const SlidesContainer = React.createClass({
|
||||
propTypes: {
|
||||
children: React.PropTypes.arrayOf(React.PropTypes.element),
|
||||
forwardProcess: React.PropTypes.bool.isRequired,
|
||||
children: arrayOf(element),
|
||||
forwardProcess: bool.isRequired,
|
||||
|
||||
glyphiconClassNames: React.PropTypes.shape({
|
||||
pending: React.PropTypes.string,
|
||||
complete: React.PropTypes.string
|
||||
})
|
||||
glyphiconClassNames: shape({
|
||||
pending: string,
|
||||
complete: string
|
||||
}),
|
||||
location: object
|
||||
},
|
||||
|
||||
mixins: [State, Navigation],
|
||||
mixins: [History],
|
||||
|
||||
getInitialState() {
|
||||
// handle queryParameters
|
||||
let queryParams = this.getQuery();
|
||||
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);
|
||||
}
|
||||
|
||||
return {
|
||||
slideNum,
|
||||
startFrom,
|
||||
containerWidth: 0,
|
||||
historyLength: window.history.length
|
||||
containerWidth: 0
|
||||
};
|
||||
},
|
||||
|
||||
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
|
||||
// the specific one we need instead of overwriting them
|
||||
queryParams.slide_num = 0;
|
||||
|
||||
this.replaceWith(this.getPathname(), null, queryParams);
|
||||
}
|
||||
|
||||
// init container width
|
||||
this.handleContainerResize();
|
||||
|
||||
// we're using an event listener on window here,
|
||||
// as it was not possible to listen to the resize events of a dom node
|
||||
window.addEventListener('resize', this.handleContainerResize);
|
||||
},
|
||||
|
||||
componentWillReceiveProps() {
|
||||
let queryParams = this.getQuery();
|
||||
|
||||
// also check if start_from was updated
|
||||
// This applies for example when the user tries to submit a already existing piece
|
||||
// (starting from slide 1 for example) and then clicking on + NEW WORK
|
||||
if(queryParams && !('start_from' in queryParams)) {
|
||||
this.setState({
|
||||
startFrom: -1
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUpdate() {
|
||||
let queryParams = this.getQuery();
|
||||
|
||||
// check if slide_num was defined, and if not then default to 0
|
||||
this.setSlideNum(queryParams.slide_num);
|
||||
// Initially, we need to dispatch 'resize' once to render correctly
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -105,80 +49,26 @@ let SlidesContainer = React.createClass({
|
||||
},
|
||||
|
||||
// When the start_from parameter is used, this.setSlideNum can not simply be used anymore.
|
||||
nextSlide() {
|
||||
let nextSlide = this.state.slideNum + 1;
|
||||
this.setSlideNum(nextSlide);
|
||||
nextSlide(additionalQueryParams) {
|
||||
const slideNum = parseInt(this.props.location.query.slide_num, 10) || 0;
|
||||
let nextSlide = slideNum + 1;
|
||||
this.setSlideNum(nextSlide, additionalQueryParams);
|
||||
},
|
||||
|
||||
// We let every one from the outsite set the page number of the slider,
|
||||
// though only if the slideNum is actually in the range of our children-list.
|
||||
setSlideNum(slideNum) {
|
||||
|
||||
// we do not want to overwrite other queryParams
|
||||
let queryParams = this.getQuery();
|
||||
|
||||
// slideNum can in some instances be not a number,
|
||||
// therefore we have to parse it to one and make sure that its not NaN
|
||||
slideNum = parseInt(slideNum, 10);
|
||||
|
||||
// if slideNum is not a number (even after we parsed it to one) and there has
|
||||
// never been a transition to another slide (this.state.slideNum ==== -1 indicates that)
|
||||
// then we want to "replace" (in this case append) the current url with ?slide_num=0
|
||||
if(isNaN(slideNum) && this.state.slideNum === -1) {
|
||||
slideNum = 0;
|
||||
queryParams.slide_num = slideNum;
|
||||
|
||||
this.replaceWith(this.getPathname(), null, queryParams);
|
||||
this.setState({slideNum: slideNum});
|
||||
return;
|
||||
|
||||
// slideNum always represents the future state. So if slideNum and
|
||||
// this.state.slideNum are equal, there is no sense in redirecting
|
||||
} else if(slideNum === this.state.slideNum) {
|
||||
return;
|
||||
|
||||
// if slideNum is within the range of slides and none of the previous cases
|
||||
// where matched, we can actually do transitions
|
||||
} else if(slideNum >= 0 || slideNum < this.customChildrenCount()) {
|
||||
|
||||
if(slideNum !== this.state.slideNum - 1) {
|
||||
// Bootstrapping the component, getInitialState is called once to save
|
||||
// the tabs history length.
|
||||
// In order to know if we already pushed a new state on the history stack or not,
|
||||
// we're comparing the old history length with the new one and if it didn't change then
|
||||
// we push a new state on it ONCE (ever).
|
||||
// Otherwise, we're able to use the browsers history.forward() method
|
||||
// to keep the stack clean
|
||||
|
||||
if(this.props.forwardProcess) {
|
||||
queryParams.slide_num = slideNum;
|
||||
this.transitionTo(this.getPathname(), null, queryParams);
|
||||
} else {
|
||||
if(this.state.historyLength === window.history.length) {
|
||||
queryParams.slide_num = slideNum;
|
||||
this.transitionTo(this.getPathname(), null, queryParams);
|
||||
} else {
|
||||
window.history.forward();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
slideNum: slideNum
|
||||
});
|
||||
|
||||
} else {
|
||||
throw new Error('You\'re calling a page number that is out of range.');
|
||||
}
|
||||
setSlideNum(nextSlideNum, additionalQueryParams = {}) {
|
||||
let queryParams = Object.assign(this.props.location.query, additionalQueryParams);
|
||||
queryParams.slide_num = nextSlideNum;
|
||||
this.history.pushState(null, this.props.location.pathname, queryParams);
|
||||
},
|
||||
|
||||
// breadcrumbs are defined as attributes of the slides.
|
||||
// To extract them we have to read the DOM element's attributes
|
||||
extractBreadcrumbs() {
|
||||
const startFrom = parseInt(this.props.location.query.start_from, 10) || -1;
|
||||
let breadcrumbs = [];
|
||||
|
||||
ReactAddons.Children.map(this.props.children, (child, i) => {
|
||||
if(child && i >= this.state.startFrom && child.props['data-slide-title']) {
|
||||
React.Children.map(this.props.children, (child, i) => {
|
||||
if(child && i >= startFrom && child.props['data-slide-title']) {
|
||||
breadcrumbs.push(child.props['data-slide-title']);
|
||||
}
|
||||
});
|
||||
@ -191,9 +81,11 @@ let SlidesContainer = React.createClass({
|
||||
// Therefore React.Children.count does not work anymore and we
|
||||
// need to implement our own method.
|
||||
customChildrenCount() {
|
||||
const startFrom = parseInt(this.props.location.query.start_from, 10) || -1;
|
||||
let count = 0;
|
||||
|
||||
React.Children.forEach(this.props.children, (child, i) => {
|
||||
if(i >= this.state.startFrom) {
|
||||
if(i >= startFrom) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
@ -212,7 +104,7 @@ let SlidesContainer = React.createClass({
|
||||
return (
|
||||
<SlidesContainerBreadcrumbs
|
||||
breadcrumbs={breadcrumbs}
|
||||
slideNum={this.state.slideNum}
|
||||
slideNum={parseInt(this.props.location.query.slide_num, 10) || 0}
|
||||
numOfSlides={breadcrumbs.length}
|
||||
containerWidth={this.state.containerWidth}
|
||||
glyphiconClassNames={this.props.glyphiconClassNames}/>
|
||||
@ -225,12 +117,13 @@ let SlidesContainer = React.createClass({
|
||||
// Since we need to give the slides a width, we need to call ReactAddons.addons.cloneWithProps
|
||||
// Also, a key is nice to have!
|
||||
renderChildren() {
|
||||
return ReactAddons.Children.map(this.props.children, (child, i) => {
|
||||
const startFrom = parseInt(this.props.location.query.start_from, 10) || -1;
|
||||
|
||||
return React.Children.map(this.props.children, (child, i) => {
|
||||
// since the default parameter of startFrom is -1, we do not need to check
|
||||
// if its actually present in the url bar, as it will just not match
|
||||
if(child && i >= this.state.startFrom) {
|
||||
return ReactAddons.addons.cloneWithProps(child, {
|
||||
if(child && i >= startFrom) {
|
||||
return React.addons.cloneWithProps(child, {
|
||||
className: 'ascribe-slide',
|
||||
style: {
|
||||
width: this.state.containerWidth
|
||||
@ -246,7 +139,7 @@ let SlidesContainer = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
let spacing = this.state.containerWidth * this.state.slideNum;
|
||||
let spacing = this.state.containerWidth * parseInt(this.props.location.query.slide_num, 10) || 0;
|
||||
let translateXValue = 'translateX(' + (-1) * spacing + 'px)';
|
||||
|
||||
/*
|
||||
@ -280,4 +173,4 @@ let SlidesContainer = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
export default SlidesContainer;
|
||||
export default SlidesContainer;
|
34
js/components/ascribe_spinner.js
Normal file
34
js/components/ascribe_spinner.js
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
let AscribeSpinner = React.createClass({
|
||||
propTypes: {
|
||||
classNames: React.PropTypes.string,
|
||||
size: React.PropTypes.oneOf(['sm', 'md', 'lg']),
|
||||
color: React.PropTypes.oneOf(['blue', 'dark-blue', 'light-blue', 'pink', 'black', 'loop'])
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
inline: false,
|
||||
size: 'md',
|
||||
color: 'loop'
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={
|
||||
classNames('spinner-wrapper-' + this.props.size,
|
||||
'spinner-wrapper-' + this.props.color,
|
||||
this.props.classNames)}>
|
||||
<div className={classNames('spinner-circle')}></div>
|
||||
<div className={classNames('spinner-inner')}>A</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default AscribeSpinner;
|
@ -35,14 +35,7 @@ export class TransitionModel {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
toReactRouterLinkProps(queryValue) {
|
||||
let props = {
|
||||
to: this.to,
|
||||
params: {}
|
||||
};
|
||||
|
||||
props.params[this.queryKey] = queryValue;
|
||||
|
||||
return props;
|
||||
toReactRouterLink(queryValue) {
|
||||
return '/' + this.to + '/' + queryValue;
|
||||
}
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import { ColumnModel } from './models/table_models';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
let TableItemWrapper = React.createClass({
|
||||
propTypes: {
|
||||
@ -15,8 +14,6 @@ let TableItemWrapper = React.createClass({
|
||||
onClick: React.PropTypes.func
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
render() {
|
||||
return (
|
||||
<tr onClick={this.props.onClick}>
|
||||
@ -35,18 +32,13 @@ let TableItemWrapper = React.createClass({
|
||||
);
|
||||
} else {
|
||||
|
||||
let linkProps = column.transition.toReactRouterLinkProps(this.props.columnContent[column.transition.valueKey]);
|
||||
/**
|
||||
* If a transition is defined in columnContent, then we can use
|
||||
* Router.Navigation.transitionTo to redirect the user
|
||||
* programmatically
|
||||
*/
|
||||
let linkString = column.transition.toReactRouterLink(this.props.columnContent[column.transition.valueKey]);
|
||||
return (
|
||||
<td key={i} className={column.className}>
|
||||
<Link
|
||||
to={linkString}
|
||||
className={'ascribe-table-item-column'}
|
||||
onClick={column.transition.callback}
|
||||
{...linkProps}>
|
||||
onClick={column.transition.callback}>
|
||||
<TypeElement {...typeElementProps} />
|
||||
</Link>
|
||||
</td>
|
||||
|
@ -12,6 +12,7 @@ import { getLangText } from '../../../utils/lang_utils';
|
||||
// Taken from: https://github.com/fedosejev/react-file-drag-and-drop
|
||||
let FileDragAndDrop = React.createClass({
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
onDrop: React.PropTypes.func.isRequired,
|
||||
onDragOver: React.PropTypes.func,
|
||||
onInactive: React.PropTypes.func,
|
||||
@ -40,7 +41,8 @@ let FileDragAndDrop = React.createClass({
|
||||
plural: React.PropTypes.string
|
||||
}),
|
||||
|
||||
allowedExtensions: React.PropTypes.string
|
||||
allowedExtensions: React.PropTypes.string,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
handleDragOver(event) {
|
||||
@ -107,6 +109,7 @@ let FileDragAndDrop = React.createClass({
|
||||
},
|
||||
|
||||
handleOnClick() {
|
||||
let evt;
|
||||
// when multiple is set to false and the user already uploaded a piece,
|
||||
// do not propagate event
|
||||
if(this.props.dropzoneInactive) {
|
||||
@ -118,16 +121,18 @@ let FileDragAndDrop = React.createClass({
|
||||
return;
|
||||
}
|
||||
|
||||
// Firefox only recognizes the simulated mouse click if bubbles is set to true,
|
||||
// but since Google Chrome propagates the event much further than needed, we
|
||||
// need to stop propagation as soon as the event is created
|
||||
var evt = new MouseEvent('click', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
try {
|
||||
evt = new MouseEvent('click', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
} catch(e) {
|
||||
// For browsers that do not support the new MouseEvent syntax
|
||||
evt = document.createEvent('MouseEvents');
|
||||
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
|
||||
}
|
||||
|
||||
evt.stopPropagation();
|
||||
this.refs.fileinput.getDOMNode().dispatchEvent(evt);
|
||||
},
|
||||
|
||||
@ -142,7 +147,8 @@ let FileDragAndDrop = React.createClass({
|
||||
fileClassToUpload,
|
||||
areAssetsDownloadable,
|
||||
areAssetsEditable,
|
||||
allowedExtensions
|
||||
allowedExtensions,
|
||||
location
|
||||
} = this.props;
|
||||
|
||||
// has files only is true if there are files that do not have the status deleted or canceled
|
||||
@ -158,10 +164,10 @@ let FileDragAndDrop = React.createClass({
|
||||
<div className="file-drag-and-drop-hashing-dialog">
|
||||
<p>{getLangText('Computing hash(es)... This may take a few minutes.')}</p>
|
||||
<p>
|
||||
<a onClick={this.props.handleCancelHashing}> {getLangText('Cancel hashing')}</a>
|
||||
<a onClick={handleCancelHashing}> {getLangText('Cancel hashing')}</a>
|
||||
</p>
|
||||
<ProgressBar
|
||||
now={Math.ceil(this.props.hashingProgress)}
|
||||
now={Math.ceil(hashingProgress)}
|
||||
label="%(percent)s%"
|
||||
className="ascribe-progress-bar"/>
|
||||
</div>
|
||||
@ -179,7 +185,8 @@ let FileDragAndDrop = React.createClass({
|
||||
hasFiles={hasFiles}
|
||||
onClick={this.handleOnClick}
|
||||
enableLocalHashing={enableLocalHashing}
|
||||
fileClassToUpload={fileClassToUpload}/>
|
||||
fileClassToUpload={fileClassToUpload}
|
||||
location={location}/>
|
||||
<FileDragAndDropPreviewIterator
|
||||
files={filesToUpload}
|
||||
handleDeleteFile={this.handleDeleteFile}
|
||||
@ -188,12 +195,23 @@ let FileDragAndDrop = React.createClass({
|
||||
handleResumeFile={this.handleResumeFile}
|
||||
areAssetsDownloadable={areAssetsDownloadable}
|
||||
areAssetsEditable={areAssetsEditable}/>
|
||||
{/*
|
||||
Opera doesn't trigger simulated click events
|
||||
if the targeted input has `display:none` set.
|
||||
Which means we need to set its visibility to hidden
|
||||
instead of using `display:none`.
|
||||
|
||||
See:
|
||||
- http://stackoverflow.com/questions/12880604/jquery-triggerclick-not-working-on-opera-if-the-element-is-not-displayed
|
||||
*/}
|
||||
<input
|
||||
multiple={multiple}
|
||||
ref="fileinput"
|
||||
type="file"
|
||||
style={{
|
||||
display: 'none',
|
||||
visibility: 'hidden',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
height: 0,
|
||||
width: 0
|
||||
}}
|
||||
|
@ -1,11 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import { getLangText } from '../../../utils/lang_utils';
|
||||
import { dragAndDropAvailable } from '../../../utils/feature_detection_utils';
|
||||
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
let FileDragAndDropDialog = React.createClass({
|
||||
propTypes: {
|
||||
@ -19,13 +20,24 @@ let FileDragAndDropDialog = React.createClass({
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: React.PropTypes.string,
|
||||
plural: React.PropTypes.string
|
||||
})
|
||||
}),
|
||||
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.State],
|
||||
getDragDialog(fileClass) {
|
||||
if(dragAndDropAvailable) {
|
||||
return [
|
||||
<p>{getLangText('Drag %s here', fileClass)}</p>,
|
||||
<p>{getLangText('or')}</p>
|
||||
];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
const queryParams = this.getQuery();
|
||||
const queryParams = this.props.location.query;
|
||||
|
||||
if(this.props.hasFiles) {
|
||||
return null;
|
||||
@ -38,11 +50,13 @@ let FileDragAndDropDialog = React.createClass({
|
||||
let queryParamsUpload = Object.assign({}, queryParams);
|
||||
queryParamsUpload.method = 'upload';
|
||||
|
||||
let { location } = this.props;
|
||||
|
||||
return (
|
||||
<div className="file-drag-and-drop-dialog present-options">
|
||||
<p>{getLangText('Would you rather')}</p>
|
||||
<Link
|
||||
to={this.getPath()}
|
||||
to={location.pathname}
|
||||
query={queryParamsHash}>
|
||||
<span className="btn btn-default btn-sm">
|
||||
{getLangText('Hash your work')}
|
||||
@ -52,7 +66,7 @@ let FileDragAndDropDialog = React.createClass({
|
||||
<span> or </span>
|
||||
|
||||
<Link
|
||||
to={this.getPath()}
|
||||
to={location.pathname}
|
||||
query={queryParamsUpload}>
|
||||
<span className="btn btn-default btn-sm">
|
||||
{getLangText('Upload and hash your work')}
|
||||
@ -64,8 +78,7 @@ let FileDragAndDropDialog = React.createClass({
|
||||
if(this.props.multipleFiles) {
|
||||
return (
|
||||
<span className="file-drag-and-drop-dialog">
|
||||
<p>{getLangText('Drag %s here', this.props.fileClassToUpload.plural)}</p>
|
||||
<p>{getLangText('or')}</p>
|
||||
{this.getDragDialog(this.props.fileClassToUpload.plural)}
|
||||
<span
|
||||
className="btn btn-default"
|
||||
onClick={this.props.onClick}>
|
||||
@ -78,8 +91,7 @@ let FileDragAndDropDialog = React.createClass({
|
||||
|
||||
return (
|
||||
<span className="file-drag-and-drop-dialog">
|
||||
<p>{getLangText('Drag a %s here', this.props.fileClassToUpload.singular)}</p>
|
||||
<p>{getLangText('or')}</p>
|
||||
{this.getDragDialog(this.props.fileClassToUpload.singular)}
|
||||
<span
|
||||
className="btn btn-default"
|
||||
onClick={this.props.onClick}>
|
||||
|
@ -3,7 +3,7 @@
|
||||
import React from 'react';
|
||||
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
|
||||
|
||||
import AppConstants from '../../../constants/application_constants';
|
||||
import AscribeSpinner from '../../ascribe_spinner';
|
||||
import { getLangText } from '../../../utils/lang_utils';
|
||||
|
||||
let FileDragAndDropPreviewImage = React.createClass({
|
||||
@ -53,7 +53,11 @@ let FileDragAndDropPreviewImage = React.createClass({
|
||||
}
|
||||
|
||||
} else {
|
||||
actionSymbol = <img height={35} className="action-file" src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||
actionSymbol = (
|
||||
<div className="spinner-file">
|
||||
<AscribeSpinner color='dark-blue' size='md' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -3,7 +3,7 @@
|
||||
import React from 'react';
|
||||
import ProgressBar from 'react-bootstrap/lib/ProgressBar';
|
||||
|
||||
import AppConstants from '../../../constants/application_constants';
|
||||
import AscribeSpinner from '../../ascribe_spinner';
|
||||
import { getLangText } from '../../../utils/lang_utils';
|
||||
|
||||
let FileDragAndDropPreviewOther = React.createClass({
|
||||
@ -49,7 +49,11 @@ let FileDragAndDropPreviewOther = React.createClass({
|
||||
}
|
||||
|
||||
} else {
|
||||
actionSymbol = <img height={35} src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
|
||||
actionSymbol = (
|
||||
<div className="spinner-file">
|
||||
<AscribeSpinner color='dark-blue' size='md' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
import React from 'react/addons';
|
||||
import fineUploader from 'fineUploader';
|
||||
import Router from 'react-router';
|
||||
import Q from 'q';
|
||||
|
||||
import S3Fetcher from '../../fetchers/s3_fetcher';
|
||||
@ -127,10 +126,10 @@ let ReactS3FineUploader = React.createClass({
|
||||
fileInputElement: React.PropTypes.oneOfType([
|
||||
React.PropTypes.func,
|
||||
React.PropTypes.element
|
||||
])
|
||||
},
|
||||
]),
|
||||
|
||||
mixins: [Router.State],
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
@ -327,7 +326,7 @@ let ReactS3FineUploader = React.createClass({
|
||||
.then((res) => {
|
||||
return res.json();
|
||||
})
|
||||
.then((res) =>{
|
||||
.then((res) => {
|
||||
if(res.otherdata) {
|
||||
file.s3Url = res.otherdata.url_safe;
|
||||
file.s3UrlSafe = res.otherdata.url_safe;
|
||||
@ -649,7 +648,7 @@ let ReactS3FineUploader = React.createClass({
|
||||
//
|
||||
// In the view this only happens when the user is allowed to do local hashing as well
|
||||
// as when the correct query parameter is present in the url ('hash' and not 'upload')
|
||||
let queryParams = this.getQuery();
|
||||
let queryParams = this.props.location.query;
|
||||
if(this.props.enableLocalHashing && queryParams && queryParams.method === 'hash') {
|
||||
|
||||
let convertedFilePromises = [];
|
||||
@ -830,7 +829,7 @@ let ReactS3FineUploader = React.createClass({
|
||||
|
||||
isDropzoneInactive() {
|
||||
let filesToDisplay = this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1);
|
||||
let queryParams = this.getQuery();
|
||||
let queryParams = this.props.location.query;
|
||||
|
||||
if((this.props.enableLocalHashing && !queryParams.method) || !this.props.areAssetsEditable || !this.props.multiple && filesToDisplay.length > 0) {
|
||||
return true;
|
||||
@ -859,12 +858,20 @@ let ReactS3FineUploader = React.createClass({
|
||||
enableLocalHashing,
|
||||
fileClassToUpload,
|
||||
validation,
|
||||
fileInputElement
|
||||
fileInputElement,
|
||||
location
|
||||
} = this.props;
|
||||
|
||||
// Here we initialize the template that has been either provided from the outside
|
||||
// or the default input that is FileDragAndDrop.
|
||||
return React.createElement(fileInputElement, {
|
||||
multiple,
|
||||
areAssetsDownloadable,
|
||||
areAssetsEditable,
|
||||
onInactive,
|
||||
enableLocalHashing,
|
||||
fileClassToUpload,
|
||||
location,
|
||||
onDrop: this.handleUploadFile,
|
||||
filesToUpload: this.state.filesToUpload,
|
||||
handleDeleteFile: this.handleDeleteFile,
|
||||
@ -872,14 +879,8 @@ let ReactS3FineUploader = React.createClass({
|
||||
handlePauseFile: this.handlePauseFile,
|
||||
handleResumeFile: this.handleResumeFile,
|
||||
handleCancelHashing: this.handleCancelHashing,
|
||||
multiple: multiple,
|
||||
areAssetsDownloadable: areAssetsDownloadable,
|
||||
areAssetsEditable: areAssetsEditable,
|
||||
onInactive: onInactive,
|
||||
dropzoneInactive: this.isDropzoneInactive(),
|
||||
hashingProgress: this.state.hashingProgress,
|
||||
enableLocalHashing: enableLocalHashing,
|
||||
fileClassToUpload: fileClassToUpload,
|
||||
allowedExtensions: this.getAllowedExtensions()
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import GlobalNotificationModel from '../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../actions/global_notification_actions';
|
||||
@ -10,14 +9,17 @@ import Form from './ascribe_forms/form';
|
||||
import Property from './ascribe_forms/property';
|
||||
import InputTextAreaToggable from './ascribe_forms/input_textarea_toggable';
|
||||
|
||||
import AscribeSpinner from './ascribe_spinner';
|
||||
|
||||
import ApiUrls from '../constants/api_urls';
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../utils/dom_utils';
|
||||
|
||||
|
||||
let CoaVerifyContainer = React.createClass({
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Verify your Certificate of Authenticity'));
|
||||
|
||||
return (
|
||||
<div className="ascribe-login-wrapper">
|
||||
<br/>
|
||||
@ -45,8 +47,6 @@ let CoaVerifyContainer = React.createClass({
|
||||
|
||||
|
||||
let CoaVerifyForm = React.createClass({
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
handleSuccess(response){
|
||||
let notification = null;
|
||||
if (response.verdict) {
|
||||
@ -64,12 +64,12 @@ let CoaVerifyForm = React.createClass({
|
||||
buttons={
|
||||
<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login">
|
||||
className="btn btn-default btn-wide">
|
||||
{getLangText('Verify your Certificate of Authenticity')}
|
||||
</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" />
|
||||
<span className="btn btn-default btn-wide btn-spinner">
|
||||
<AscribeSpinner color="dark-blue" size="md" />
|
||||
</span>
|
||||
}>
|
||||
<Property
|
||||
|
25
js/components/error_not_found_page.js
Normal file
25
js/components/error_not_found_page.js
Normal file
@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
|
||||
|
||||
let ErrorNotFoundPage = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
<div className="error-wrapper">
|
||||
<h1>404</h1>
|
||||
<p>
|
||||
{getLangText('Ups, the page you are looking for does not exist.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default ErrorNotFoundPage;
|
@ -8,10 +8,9 @@ let Footer = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<div className="ascribe-footer">
|
||||
<hr />
|
||||
<p className="ascribe-sub-sub-statement">
|
||||
<br />
|
||||
<a href="https://github.com/ascribe/REST-main/" target="_blank">api</a> |
|
||||
<a href="http://docs.ascribe.apiary.io/" target="_blank">api</a> |
|
||||
<a href="https://www.ascribe.io/impressum/" target="_blank"> impressum</a> |
|
||||
<a href="https://www.ascribe.io/terms/" target="_blank"> {getLangText('terms of service')}</a> |
|
||||
<a href="https://www.ascribe.io/privacy/" target="_blank"> {getLangText('privacy')}</a>
|
||||
|
@ -1,15 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import Nav from 'react-bootstrap/lib/Nav';
|
||||
import Navbar from 'react-bootstrap/lib/Navbar';
|
||||
import CollapsibleNav from 'react-bootstrap/lib/CollapsibleNav';
|
||||
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
||||
import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink';
|
||||
import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
|
||||
import NavItem from 'react-bootstrap/lib/NavItem';
|
||||
|
||||
import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
|
||||
|
||||
import AclProxy from './acl_proxy';
|
||||
|
||||
@ -31,14 +31,13 @@ import { getLangText } from '../utils/lang_utils';
|
||||
|
||||
import {constructHead} from '../utils/head_setter';
|
||||
|
||||
|
||||
let Header = React.createClass({
|
||||
propTypes: {
|
||||
showAddWork: React.PropTypes.bool,
|
||||
routes: React.PropTypes.element
|
||||
routes: React.PropTypes.arrayOf(React.PropTypes.object)
|
||||
},
|
||||
|
||||
mixins: [Router.State],
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
showAddWork: true
|
||||
@ -63,24 +62,23 @@ let Header = React.createClass({
|
||||
UserStore.unlisten(this.onChange);
|
||||
WhitelabelStore.unlisten(this.onChange);
|
||||
},
|
||||
getLogo(){
|
||||
let whitelabel = this.state.whitelabel;
|
||||
|
||||
getLogo() {
|
||||
let { whitelabel } = this.state;
|
||||
|
||||
if (whitelabel.head) {
|
||||
constructHead(whitelabel.head);
|
||||
}
|
||||
else{
|
||||
setTitle('ascribe');
|
||||
}
|
||||
if (whitelabel.subdomain !== 'www'){
|
||||
|
||||
if (whitelabel.subdomain && whitelabel.subdomain !== 'www' && whitelabel.logo){
|
||||
return (<img className="img-brand" src={whitelabel.logo}/>);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<span>
|
||||
<span>ascribe </span>
|
||||
<span className="glyph-ascribe-spool-chunked ascribe-color"></span>
|
||||
</span>);
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
<span className="icon-ascribe-logo"></span>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
|
||||
getPoweredBy(){
|
||||
@ -89,10 +87,9 @@ let Header = React.createClass({
|
||||
aclObject={this.state.whitelabel}
|
||||
aclName="acl_view_powered_by">
|
||||
<li>
|
||||
<a className="pull-right" href="https://www.ascribe.io/" target="_blank">
|
||||
<a className="pull-right ascribe-powered-by" href="https://www.ascribe.io/" target="_blank">
|
||||
<span id="powered">{getLangText('powered by')} </span>
|
||||
<span>ascribe </span>
|
||||
<span className="glyph-ascribe-spool-chunked ascribe-color"></span>
|
||||
<span className="icon-ascribe-logo"></span>
|
||||
</a>
|
||||
</li>
|
||||
</AclProxy>
|
||||
@ -101,6 +98,7 @@ let Header = React.createClass({
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
|
||||
if(this.state.currentUser && this.state.currentUser.email) {
|
||||
EventActions.profileDidLoad.defer(this.state.currentUser);
|
||||
}
|
||||
@ -141,38 +139,61 @@ let Header = React.createClass({
|
||||
ref='dropdownbutton'
|
||||
eventKey="1"
|
||||
title={this.state.currentUser.username}>
|
||||
<MenuItemLink
|
||||
eventKey="2"
|
||||
to="settings"
|
||||
<LinkContainer
|
||||
to="/settings"
|
||||
onClick={this.onMenuItemClick}>
|
||||
{getLangText('Account Settings')}
|
||||
</MenuItemLink>
|
||||
<MenuItem
|
||||
eventKey="2">
|
||||
{getLangText('Account Settings')}
|
||||
</MenuItem>
|
||||
</LinkContainer>
|
||||
<AclProxy
|
||||
aclObject={this.state.currentUser.acl}
|
||||
aclName="acl_view_settings_contract">
|
||||
<MenuItemLink
|
||||
to="contract_settings"
|
||||
<LinkContainer
|
||||
to="/contract_settings"
|
||||
onClick={this.onMenuItemClick}>
|
||||
{getLangText('Contract Settings')}
|
||||
</MenuItemLink>
|
||||
<MenuItem
|
||||
eventKey="2">
|
||||
{getLangText('Contract Settings')}
|
||||
</MenuItem>
|
||||
</LinkContainer>
|
||||
</AclProxy>
|
||||
<MenuItem divider />
|
||||
<MenuItemLink eventKey="3" to="logout">{getLangText('Log out')}</MenuItemLink>
|
||||
<LinkContainer
|
||||
to="/logout">
|
||||
<MenuItem
|
||||
eventKey="3">
|
||||
{getLangText('Log out')}
|
||||
</MenuItem>
|
||||
</LinkContainer>
|
||||
</DropdownButton>
|
||||
);
|
||||
navRoutesLinks = <NavRoutesLinks routes={this.props.routes} userAcl={this.state.currentUser.acl} navbar right/>;
|
||||
}
|
||||
else {
|
||||
account = <NavItemLink to="login">{getLangText('LOGIN')}</NavItemLink>;
|
||||
signup = <NavItemLink to="signup">{getLangText('SIGNUP')}</NavItemLink>;
|
||||
account = (
|
||||
<LinkContainer
|
||||
to="/login">
|
||||
<NavItem>
|
||||
{getLangText('LOGIN')}
|
||||
</NavItem>
|
||||
</LinkContainer>
|
||||
);
|
||||
signup = (
|
||||
<LinkContainer
|
||||
to="/signup">
|
||||
<NavItem>
|
||||
{getLangText('SIGNUP')}
|
||||
</NavItem>
|
||||
</LinkContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar
|
||||
brand={
|
||||
this.getLogo()
|
||||
}
|
||||
brand={this.getLogo()}
|
||||
toggleNavKey={0}
|
||||
fixedTop={true}>
|
||||
<CollapsibleNav eventKey={0}>
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
|
||||
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
||||
@ -14,8 +14,6 @@ import NotificationStore from '../stores/notification_store';
|
||||
import { mergeOptions } from '../utils/general_utils';
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
|
||||
let HeaderNotifications = React.createClass({
|
||||
|
||||
@ -39,7 +37,7 @@ let HeaderNotifications = React.createClass({
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
onMenuItemClick(event) {
|
||||
onMenuItemClick() {
|
||||
/*
|
||||
This is a hack to make the dropdown close after clicking on an item
|
||||
The function just need to be defined
|
||||
@ -158,23 +156,13 @@ let NotificationListItem = React.createClass({
|
||||
},
|
||||
|
||||
getLinkData() {
|
||||
let { pieceOrEdition } = this.props;
|
||||
|
||||
if (this.isPiece()) {
|
||||
return {
|
||||
to: 'piece',
|
||||
params: {
|
||||
pieceId: this.props.pieceOrEdition.id
|
||||
}
|
||||
};
|
||||
return `/pieces/${pieceOrEdition.id}`;
|
||||
} else {
|
||||
return {
|
||||
to: 'edition',
|
||||
params: {
|
||||
editionId: this.props.pieceOrEdition.bitcoin_id
|
||||
}
|
||||
};
|
||||
return `/editions/${pieceOrEdition.bitcoin_id}`;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
onClick(event){
|
||||
@ -184,7 +172,7 @@ let NotificationListItem = React.createClass({
|
||||
getNotificationText(){
|
||||
let numNotifications = null;
|
||||
if (this.props.notification.length > 1){
|
||||
numNotifications = <div>+ {this.props.notification.length - 1} more...</div>;
|
||||
numNotifications = <div>+ {this.props.notification.length - 1} {getLangText('more...')}</div>;
|
||||
}
|
||||
return (
|
||||
<div className="notification-action">
|
||||
@ -196,7 +184,7 @@ let NotificationListItem = React.createClass({
|
||||
render() {
|
||||
if (this.props.pieceOrEdition) {
|
||||
return (
|
||||
<Link {...this.getLinkData()} onClick={this.onClick}>
|
||||
<Link to={this.getLinkData()} onClick={this.onClick}>
|
||||
<div className="row notification-wrapper">
|
||||
<div className="col-xs-4 clear-paddings">
|
||||
<div className="thumbnail-wrapper">
|
||||
|
@ -1,13 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import LoginForm from './ascribe_forms/form_login';
|
||||
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
import { setDocumentTitle } from '../utils/dom_utils';
|
||||
|
||||
|
||||
let LoginContainer = React.createClass({
|
||||
@ -15,7 +14,8 @@ let LoginContainer = React.createClass({
|
||||
message: React.PropTypes.string,
|
||||
redirectOnLoggedIn: React.PropTypes.bool,
|
||||
redirectOnLoginSuccess: React.PropTypes.bool,
|
||||
onLogin: React.PropTypes.func
|
||||
onLogin: React.PropTypes.func,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
@ -27,16 +27,19 @@ let LoginContainer = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Log in'));
|
||||
|
||||
return (
|
||||
<div className="ascribe-login-wrapper">
|
||||
<LoginForm
|
||||
redirectOnLoggedIn={this.props.redirectOnLoggedIn}
|
||||
redirectOnLoginSuccess={this.props.redirectOnLoginSuccess}
|
||||
message={this.props.message}
|
||||
onLogin={this.props.onLogin}/>
|
||||
onLogin={this.props.onLogin}
|
||||
location={this.props.location}/>
|
||||
<div className="ascribe-login-text">
|
||||
{getLangText('Not an ascribe user')}? <Link to="signup">{getLangText('Sign up')}...</Link><br/>
|
||||
{getLangText('Forgot my password')}? <Link to="password_reset">{getLangText('Rescue me')}...</Link>
|
||||
{getLangText('Not an ascribe user')}? <Link to="/signup">{getLangText('Sign up')}...</Link><br/>
|
||||
{getLangText('Forgot my password')}? <Link to="/password_reset">{getLangText('Rescue me')}...</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,35 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import AscribeSpinner from './ascribe_spinner';
|
||||
|
||||
import UserActions from '../actions/user_actions';
|
||||
import Alt from '../alt';
|
||||
import { alt, altWhitelabel, altUser, altThirdParty } from '../alt';
|
||||
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../utils/dom_utils';
|
||||
|
||||
import AppConstants from '../constants/application_constants';
|
||||
let baseUrl = AppConstants.baseUrl;
|
||||
|
||||
let LogoutContainer = React.createClass({
|
||||
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
mixins: [History],
|
||||
|
||||
componentDidMount() {
|
||||
UserActions.logoutCurrentUser()
|
||||
.then(() => {
|
||||
Alt.flush();
|
||||
// kill intercom (with fire)
|
||||
window.Intercom('shutdown');
|
||||
this.replaceWith(baseUrl);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.logGlobal(err);
|
||||
});
|
||||
UserActions.logoutCurrentUser();
|
||||
alt.flush();
|
||||
altWhitelabel.flush();
|
||||
altUser.flush();
|
||||
altThirdParty.flush();
|
||||
// kill intercom (with fire)
|
||||
window.Intercom('shutdown');
|
||||
},
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
setDocumentTitle(getLangText('Log out'));
|
||||
|
||||
return (
|
||||
<div className="ascribe-loading-position">
|
||||
<AscribeSpinner color='dark-blue' size='lg'/>
|
||||
<h3 className="text-center">
|
||||
{getLangText('Just a sec, we\'re logging you out...')}
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
@ -13,7 +13,7 @@ import { sanitizeList } from '../utils/general_utils';
|
||||
|
||||
let NavRoutesLinks = React.createClass({
|
||||
propTypes: {
|
||||
routes: React.PropTypes.element,
|
||||
routes: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
userAcl: React.PropTypes.object
|
||||
},
|
||||
|
||||
@ -33,15 +33,15 @@ let NavRoutesLinks = React.createClass({
|
||||
return;
|
||||
}
|
||||
|
||||
let links = node.props.children.map((child, j) => {
|
||||
let links = node.childRoutes.map((child, j) => {
|
||||
let childrenFn = null;
|
||||
let { aclName, headerTitle, name, children } = child.props;
|
||||
let { aclName, headerTitle, path, childRoutes } = child;
|
||||
|
||||
// If the node has children that could be rendered, then we want
|
||||
// to execute this function again with the child as the root
|
||||
//
|
||||
// Otherwise we'll just pass childrenFn as false
|
||||
if(child.props.children && child.props.children.length > 0) {
|
||||
if(child.childRoutes && child.childRoutes.length > 0) {
|
||||
childrenFn = this.extractLinksFromRoutes(child, userAcl, i++);
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ let NavRoutesLinks = React.createClass({
|
||||
aclObject={this.props.userAcl}>
|
||||
<NavRoutesLinksLink
|
||||
headerTitle={headerTitle}
|
||||
routeName={name}
|
||||
routePath={'/' + path}
|
||||
depth={i}
|
||||
children={childrenFn}/>
|
||||
</AclProxy>
|
||||
@ -68,7 +68,7 @@ let NavRoutesLinks = React.createClass({
|
||||
<NavRoutesLinksLink
|
||||
key={j}
|
||||
headerTitle={headerTitle}
|
||||
routeName={name}
|
||||
routePath={'/' + path}
|
||||
depth={i}
|
||||
children={childrenFn}/>
|
||||
);
|
||||
@ -88,7 +88,7 @@ let NavRoutesLinks = React.createClass({
|
||||
|
||||
return (
|
||||
<Nav {...this.props}>
|
||||
{this.extractLinksFromRoutes(routes, userAcl, 0)}
|
||||
{this.extractLinksFromRoutes(routes[0], userAcl, 0)}
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
|
@ -3,13 +3,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||
import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink';
|
||||
import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
|
||||
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
||||
import NavItem from 'react-bootstrap/lib/NavItem';
|
||||
|
||||
import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
|
||||
|
||||
|
||||
let NavRoutesLinksLink = React.createClass({
|
||||
propTypes: {
|
||||
headerTitle: React.PropTypes.string,
|
||||
routeName: React.PropTypes.string,
|
||||
routePath: React.PropTypes.string,
|
||||
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.arrayOf(React.PropTypes.element),
|
||||
@ -20,10 +23,10 @@ let NavRoutesLinksLink = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
let { children, headerTitle, depth, routeName } = this.props;
|
||||
let { children, headerTitle, depth, routePath } = this.props;
|
||||
|
||||
// if the route has children, we're returning a DropdownButton that will get filled
|
||||
// with MenuItemLinks
|
||||
// with MenuItems
|
||||
if(children) {
|
||||
return (
|
||||
<DropdownButton title={headerTitle}>
|
||||
@ -33,13 +36,17 @@ let NavRoutesLinksLink = React.createClass({
|
||||
} else {
|
||||
if(depth === 1) {
|
||||
// if the node's child is actually a node of level one (a child of a node), we're
|
||||
// returning a DropdownButton matching MenuItemLink
|
||||
// returning a DropdownButton matching MenuItem
|
||||
return (
|
||||
<MenuItemLink to={routeName}>{headerTitle}</MenuItemLink>
|
||||
<LinkContainer to={routePath}>
|
||||
<MenuItem>{headerTitle}</MenuItem>
|
||||
</LinkContainer>
|
||||
);
|
||||
} else if(depth === 0) {
|
||||
return (
|
||||
<NavItemLink to={routeName}>{headerTitle}</NavItemLink>
|
||||
<LinkContainer to={routePath}>
|
||||
<NavItem>{headerTitle}</NavItem>
|
||||
</LinkContainer>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
|
@ -1,19 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import Form from './ascribe_forms/form';
|
||||
import Property from './ascribe_forms/property';
|
||||
import ApiUrls from '../constants/api_urls';
|
||||
import AscribeSpinner from './ascribe_spinner';
|
||||
|
||||
import GlobalNotificationModel from '../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../actions/global_notification_actions';
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../utils/dom_utils';
|
||||
|
||||
|
||||
let PasswordResetContainer = React.createClass({
|
||||
mixins: [Router.Navigation],
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {isRequested: false};
|
||||
@ -24,12 +28,14 @@ let PasswordResetContainer = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
if (this.props.query.email && this.props.query.token) {
|
||||
let { location } = this.props;
|
||||
|
||||
if (location.query.email && location.query.token) {
|
||||
return (
|
||||
<div>
|
||||
<PasswordResetForm
|
||||
email={this.props.query.email}
|
||||
token={this.props.query.token}/>
|
||||
email={location.query.email}
|
||||
token={location.query.token}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -71,6 +77,8 @@ let PasswordRequestResetForm = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Reset your password'));
|
||||
|
||||
return (
|
||||
<Form
|
||||
ref="form"
|
||||
@ -80,12 +88,12 @@ let PasswordRequestResetForm = React.createClass({
|
||||
buttons={
|
||||
<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login">
|
||||
className="btn btn-default btn-wide">
|
||||
{getLangText('Reset your password')}
|
||||
</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" />
|
||||
<span className="btn btn-default btn-wide btn-spinner">
|
||||
<AscribeSpinner color="dark-blue" size="md" />
|
||||
</span>
|
||||
}>
|
||||
<div className="ascribe-form-header">
|
||||
@ -112,7 +120,7 @@ let PasswordResetForm = React.createClass({
|
||||
token: React.PropTypes.string
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
mixins: [History],
|
||||
|
||||
getFormData() {
|
||||
return {
|
||||
@ -122,7 +130,7 @@ let PasswordResetForm = React.createClass({
|
||||
},
|
||||
|
||||
handleSuccess() {
|
||||
this.transitionTo('pieces');
|
||||
this.history.pushState(null, '/collection');
|
||||
let notification = new GlobalNotificationModel(getLangText('password successfully updated'), 'success', 10000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
@ -138,12 +146,12 @@ let PasswordResetForm = React.createClass({
|
||||
buttons={
|
||||
<button
|
||||
type="submit"
|
||||
className="btn ascribe-btn ascribe-btn-login">
|
||||
className="btn btn-default btn-wide">
|
||||
{getLangText('Reset your password')}
|
||||
</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" />
|
||||
<span className="btn btn-default btn-wide btn-spinner">
|
||||
<AscribeSpinner color="dark-blue" size="md" />
|
||||
</span>
|
||||
}>
|
||||
<div className="ascribe-form-header">
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import PieceListStore from '../stores/piece_list_store';
|
||||
import PieceListActions from '../actions/piece_list_actions';
|
||||
@ -20,10 +20,13 @@ import PieceListFilterDisplay from './piece_list_filter_display';
|
||||
import PieceListBulkModal from './ascribe_piece_list_bulk_modal/piece_list_bulk_modal';
|
||||
import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar';
|
||||
|
||||
import AscribeSpinner from './ascribe_spinner';
|
||||
|
||||
import AppConstants from '../constants/application_constants';
|
||||
|
||||
import { mergeOptions } from '../utils/general_utils';
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../utils/dom_utils';
|
||||
|
||||
|
||||
let PieceList = React.createClass({
|
||||
@ -33,10 +36,11 @@ let PieceList = React.createClass({
|
||||
customSubmitButton: React.PropTypes.element,
|
||||
filterParams: React.PropTypes.array,
|
||||
orderParams: React.PropTypes.array,
|
||||
orderBy: React.PropTypes.string
|
||||
orderBy: React.PropTypes.string,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
mixins: [History],
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
@ -60,7 +64,7 @@ let PieceList = React.createClass({
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
let page = this.getQuery().page || 1;
|
||||
let page = this.props.location.query.page || 1;
|
||||
|
||||
PieceListStore.listen(this.onChange);
|
||||
EditionListStore.listen(this.onChange);
|
||||
@ -75,7 +79,7 @@ let PieceList = React.createClass({
|
||||
componentDidUpdate() {
|
||||
if (this.props.redirectTo && this.state.unfilteredPieceListCount === 0) {
|
||||
// FIXME: hack to redirect out of the dispatch cycle
|
||||
window.setTimeout(() => this.transitionTo(this.props.redirectTo, this.getQuery()));
|
||||
window.setTimeout(() => this.history.pushState(null, this.props.redirectTo, this.props.location.query), 0);
|
||||
}
|
||||
},
|
||||
|
||||
@ -100,10 +104,10 @@ let PieceList = React.createClass({
|
||||
},
|
||||
|
||||
getPagination() {
|
||||
let currentPage = parseInt(this.getQuery().page, 10) || 1;
|
||||
let currentPage = parseInt(this.props.location.query.page, 10) || 1;
|
||||
let totalPages = Math.ceil(this.state.pieceListCount / this.state.pageSize);
|
||||
|
||||
if (this.state.pieceListCount > 10) {
|
||||
if (this.state.pieceListCount > 20) {
|
||||
return (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
@ -116,7 +120,7 @@ let PieceList = React.createClass({
|
||||
searchFor(searchTerm) {
|
||||
PieceListActions.fetchPieceList(1, this.state.pageSize, searchTerm, this.state.orderBy,
|
||||
this.state.orderAsc, this.state.filterBy);
|
||||
this.transitionTo(this.getPathname(), {page: 1});
|
||||
this.history.pushState(null, this.props.location.pathname, {page: 1});
|
||||
},
|
||||
|
||||
applyFilterBy(filterBy){
|
||||
@ -140,7 +144,7 @@ let PieceList = React.createClass({
|
||||
|
||||
// we have to redirect the user always to page one as it could be that there is no page two
|
||||
// for filtered pieces
|
||||
this.transitionTo(this.getPathname(), {page: 1});
|
||||
this.history.pushState(null, this.props.location.pathname, {page: 1});
|
||||
},
|
||||
|
||||
applyOrderBy(orderBy) {
|
||||
@ -149,21 +153,29 @@ let PieceList = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
let loadingElement = (<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />);
|
||||
let loadingElement = <AscribeSpinner color='dark-blue' size='lg'/>;
|
||||
let AccordionListItemType = this.props.accordionListItemType;
|
||||
|
||||
setDocumentTitle(getLangText('Collection'));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PieceListToolbar
|
||||
className="ascribe-piece-list-toolbar"
|
||||
searchFor={this.searchFor}
|
||||
searchQuery={this.state.search}
|
||||
filterParams={this.props.filterParams}
|
||||
orderParams={this.props.orderParams}
|
||||
filterBy={this.state.filterBy}
|
||||
orderBy={this.state.orderBy}
|
||||
applyFilterBy={this.applyFilterBy}
|
||||
applyOrderBy={this.applyOrderBy}>
|
||||
{this.props.customSubmitButton}
|
||||
{this.props.customSubmitButton ?
|
||||
this.props.customSubmitButton :
|
||||
<button className="btn btn-default btn-ascribe-add">
|
||||
<span className="icon-ascribe icon-ascribe-add" />
|
||||
</button>
|
||||
}
|
||||
</PieceListToolbar>
|
||||
<PieceListBulkModal className="ascribe-piece-list-bulk-modal" />
|
||||
<PieceListFilterDisplay
|
||||
@ -177,6 +189,7 @@ let PieceList = React.createClass({
|
||||
orderBy={this.state.orderBy}
|
||||
orderAsc={this.state.orderAsc}
|
||||
search={this.state.search}
|
||||
searchFor={this.searchFor}
|
||||
page={this.state.page}
|
||||
pageSize={this.state.pageSize}
|
||||
loadingElement={loadingElement}>
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import Col from 'react-bootstrap/lib/Col';
|
||||
import Row from 'react-bootstrap/lib/Row';
|
||||
@ -20,12 +20,9 @@ import GlobalNotificationActions from '../actions/global_notification_actions';
|
||||
import PropertyCollapsible from './ascribe_forms/property_collapsible';
|
||||
import RegisterPieceForm from './ascribe_forms/form_register_piece';
|
||||
|
||||
import LoginContainer from './login_container';
|
||||
import SlidesContainer from './ascribe_slides_container/slides_container';
|
||||
|
||||
|
||||
import { mergeOptions } from '../utils/general_utils';
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../utils/dom_utils';
|
||||
|
||||
|
||||
let RegisterPiece = React.createClass( {
|
||||
@ -37,10 +34,11 @@ let RegisterPiece = React.createClass( {
|
||||
React.PropTypes.arrayOf(React.PropTypes.element),
|
||||
React.PropTypes.element,
|
||||
React.PropTypes.string
|
||||
])
|
||||
]),
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
mixins: [History],
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
@ -60,10 +58,10 @@ let RegisterPiece = React.createClass( {
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
WhitelabelActions.fetchWhitelabel();
|
||||
PieceListStore.listen(this.onChange);
|
||||
UserStore.listen(this.onChange);
|
||||
WhitelabelStore.listen(this.onChange);
|
||||
WhitelabelActions.fetchWhitelabel();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -98,7 +96,7 @@ let RegisterPiece = React.createClass( {
|
||||
this.state.filterBy
|
||||
);
|
||||
|
||||
this.transitionTo('piece', {pieceId: response.piece.id});
|
||||
this.history.pushState(null, `/pieces/${response.piece.id}`);
|
||||
},
|
||||
|
||||
getSpecifyEditions() {
|
||||
@ -117,53 +115,22 @@ let RegisterPiece = React.createClass( {
|
||||
}
|
||||
},
|
||||
|
||||
// basically redirects to the second slide (index: 1), when the user is not logged in
|
||||
onLoggedOut() {
|
||||
// only transition to the login store, if user is not logged in
|
||||
// ergo the currentUser object is not properly defined
|
||||
if(this.state.currentUser && !this.state.currentUser.email) {
|
||||
this.refs.slidesContainer.setSlideNum(1);
|
||||
}
|
||||
},
|
||||
|
||||
onLogin() {
|
||||
// once the currentUser object from UserStore is defined (eventually the user was transitioned
|
||||
// to the login form via the slider and successfully logged in), we can direct him back to the
|
||||
// register_piece slide
|
||||
if(this.state.currentUser && this.state.currentUser.email) {
|
||||
window.history.back();
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Register a new piece'));
|
||||
|
||||
return (
|
||||
<SlidesContainer
|
||||
ref="slidesContainer"
|
||||
forwardProcess={false}>
|
||||
<div
|
||||
onClick={this.onLoggedOut}
|
||||
onFocus={this.onLoggedOut}>
|
||||
<Row className="no-margin">
|
||||
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
|
||||
<RegisterPieceForm
|
||||
{...this.props}
|
||||
isFineUploaderActive={this.state.isFineUploaderActive}
|
||||
handleSuccess={this.handleSuccess}
|
||||
onLoggedOut={this.onLoggedOut}>
|
||||
{this.props.children}
|
||||
{this.getSpecifyEditions()}
|
||||
</RegisterPieceForm>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
<div>
|
||||
<LoginContainer
|
||||
message={getLangText('Please login before ascribing your work%s', '...')}
|
||||
redirectOnLoggedIn={false}
|
||||
redirectOnLoginSuccess={false}
|
||||
onLogin={this.onLogin}/>
|
||||
</div>
|
||||
</SlidesContainer>
|
||||
<Row className="no-margin">
|
||||
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
|
||||
<RegisterPieceForm
|
||||
{...this.props}
|
||||
isFineUploaderActive={this.state.isFineUploaderActive}
|
||||
handleSuccess={this.handleSuccess}
|
||||
location={this.props.location}>
|
||||
{this.props.children}
|
||||
{this.getSpecifyEditions()}
|
||||
</RegisterPieceForm>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
143
js/components/search_bar.js
Normal file
143
js/components/search_bar.js
Normal file
@ -0,0 +1,143 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Input from 'react-bootstrap/lib/Input';
|
||||
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
|
||||
|
||||
import AscribeSpinner from './ascribe_spinner';
|
||||
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
|
||||
|
||||
const { func, string, number } = React.PropTypes;
|
||||
|
||||
const SearchBar = React.createClass({
|
||||
propTypes: {
|
||||
// a function that accepts a string as a search query and updates the
|
||||
// propagated `searchQuery` after successfully retrieving the
|
||||
// request from the server
|
||||
searchFor: func.isRequired,
|
||||
searchQuery: string.isRequired,
|
||||
|
||||
className: string,
|
||||
|
||||
// the number of milliseconds the component
|
||||
// should wait before requesting search results from the server
|
||||
threshold: number.isRequired
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
timer: null,
|
||||
searchQuery: '',
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
/**
|
||||
* This enables the `PieceListStore` to override the state
|
||||
* of that component in case someone is changing the `searchQuery` on
|
||||
* another component.
|
||||
*
|
||||
* Like how it's being done in the 'Clear search' dialog.
|
||||
*/
|
||||
if(this.props.searchQuery !== nextProps.searchQuery || !this.state.searchQuery) {
|
||||
this.setState({ searchQuery: nextProps.searchQuery });
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const searchQueryProps = this.props.searchQuery;
|
||||
const searchQueryPrevProps = prevProps.searchQuery;
|
||||
const searchQueryState = this.state.searchQuery;
|
||||
const { loading } = this.state;
|
||||
|
||||
/**
|
||||
* 1. Condition: `loading` must be true, which implies that `evaluateTimer`,
|
||||
* has already been called
|
||||
*
|
||||
* AND
|
||||
*
|
||||
* (
|
||||
* 2. Condition: `searchQueryProps` and `searchQueryState` are true and equal
|
||||
* (which means that the search query has been propagated to the inner
|
||||
* fetch method of `fetchPieceList`, which in turn means that a fetch
|
||||
* has completed)
|
||||
*
|
||||
* OR
|
||||
*
|
||||
* 3. Condition: `searchQueryProps` and `searchQueryState` can be any value (`true` or
|
||||
* `false`, as long as they're equal). This case only occurs when the user
|
||||
* has entered a `searchQuery` and deletes the query in one go, reseting
|
||||
* `searchQueryProps` to empty string ('' === false) again.
|
||||
* )
|
||||
*/
|
||||
|
||||
const firstCondition = !!loading;
|
||||
const secondCondition = searchQueryProps && searchQueryState && searchQueryProps === searchQueryState;
|
||||
const thirdCondition = !searchQueryPrevProps && searchQueryProps === searchQueryState;
|
||||
|
||||
if(firstCondition && (secondCondition || thirdCondition)) {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
startTimer(searchQuery) {
|
||||
const { timer } = this.state;
|
||||
const { threshold } = this.props;
|
||||
|
||||
// The timer waits for the specified threshold time in milliseconds
|
||||
// and then calls `evaluateTimer`.
|
||||
// If another letter has been called in the mean time (timespan < `threshold`),
|
||||
// the present timer gets cleared and a new one is added to `this.state`.
|
||||
// This means that `evaluateTimer`, will only be called when the threshold has actually
|
||||
// passed,
|
||||
clearTimeout(timer); // apparently `clearTimeout` can be called with null, without throwing errors
|
||||
const newTimer = setTimeout(this.evaluateTimer(searchQuery), threshold);
|
||||
|
||||
this.setState({ timer: newTimer });
|
||||
},
|
||||
|
||||
evaluateTimer(searchQuery) {
|
||||
return () => {
|
||||
this.setState({ timer: null, loading: true }, () => {
|
||||
// search for the query
|
||||
this.props.searchFor(searchQuery);
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
handleChange({ target: { value }}) {
|
||||
// On each letter entry we're updating the state of the component
|
||||
// and start a timer, which we're also pushing to the state
|
||||
// of the component
|
||||
this.startTimer(value);
|
||||
this.setState({ searchQuery: value });
|
||||
},
|
||||
|
||||
render() {
|
||||
let searchIcon = <span className='ascribe-icon icon-ascribe-search'/>;
|
||||
const { className } = this.props;
|
||||
const { loading, searchQuery } = this.state;
|
||||
|
||||
if(loading) {
|
||||
searchIcon = <AscribeSpinner size='sm' color='dark-blue'/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={className}>
|
||||
<Input
|
||||
type='text'
|
||||
value={searchQuery}
|
||||
placeholder={getLangText('Search%s', '...')}
|
||||
onChange={this.handleChange}
|
||||
addonAfter={searchIcon} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default SearchBar;
|
@ -1,15 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import SignupForm from './ascribe_forms/form_signup';
|
||||
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../utils/dom_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
let SignupContainer = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
submitted: false,
|
||||
@ -25,6 +29,8 @@ let SignupContainer = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Sign up'));
|
||||
|
||||
if (this.state.submitted){
|
||||
return (
|
||||
<div className="ascribe-login-wrapper">
|
||||
@ -37,9 +43,11 @@ let SignupContainer = React.createClass({
|
||||
}
|
||||
return (
|
||||
<div className="ascribe-login-wrapper">
|
||||
<SignupForm handleSuccess={this.handleSuccess} />
|
||||
<SignupForm
|
||||
handleSuccess={this.handleSuccess}
|
||||
location={this.props.location}/>
|
||||
<div className="ascribe-login-text">
|
||||
{getLangText('Already an ascribe user')}? <Link to="login">{getLangText('Log in')}...</Link><br/>
|
||||
{getLangText('Already an ascribe user')}? <Link to="/login">{getLangText('Log in')}...</Link><br/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import alt from '../../../../alt';
|
||||
import { alt } from '../../../../alt';
|
||||
import Q from 'q';
|
||||
|
||||
import PrizeFetcher from '../fetchers/prize_fetcher';
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user