hook multifile to ui
This commit is contained in:
parent
e42ad175db
commit
c9ae76b209
77 changed files with 1528 additions and 1111 deletions
|
@ -65,6 +65,14 @@ export async function fileInfo(id, owner_token) {
|
|||
throw new Error(response.status);
|
||||
}
|
||||
|
||||
export async function hasPassword(id) {
|
||||
const response = await fetch(`/api/exists/${id}`);
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
throw new Error(response.status);
|
||||
}
|
||||
|
||||
export async function metadata(id, keychain) {
|
||||
const result = await fetchWithAuthAndRetry(
|
||||
`/api/metadata/${id}`,
|
||||
|
|
|
@ -17,6 +17,10 @@ export default class Archive {
|
|||
return this.files.reduce((total, file) => total + file.size, 0);
|
||||
}
|
||||
|
||||
get numFiles() {
|
||||
return this.files.length;
|
||||
}
|
||||
|
||||
get manifest() {
|
||||
return {
|
||||
files: this.files.map(file => ({
|
||||
|
|
183
app/base.css
183
app/base.css
|
@ -1,14 +1,16 @@
|
|||
:root {
|
||||
--pageBGColor: #fff;
|
||||
--primaryControlBGColor: #0297f8;
|
||||
--lightControlBGColor: #e6e6e6;
|
||||
--primaryControlBGColor: #0a84ff;
|
||||
--primaryControlFGColor: #fff;
|
||||
--primaryControlHoverColor: #0287e8;
|
||||
--primaryControlHoverColor: #0473e2;
|
||||
--inputTextColor: #737373;
|
||||
--errorColor: #d70022;
|
||||
--linkColor: #0094fb;
|
||||
--textColor: #0c0c0d;
|
||||
--lightBorderColor: rgba(12, 12, 12, 0.2);
|
||||
--lightTextColor: #737373;
|
||||
--successControlBGColor: #05a700;
|
||||
--successControlBGColor: #12bc00;
|
||||
--successControlFGColor: #fff;
|
||||
}
|
||||
|
||||
|
@ -41,17 +43,21 @@ a {
|
|||
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: auto;
|
||||
padding: 0 20px;
|
||||
padding: 0 25px;
|
||||
box-sizing: border-box;
|
||||
min-height: 500px;
|
||||
max-height: 630px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.stripedBox {
|
||||
width: 480px;
|
||||
flex: none;
|
||||
position: relative;
|
||||
width: 400px;
|
||||
background-color: white;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 0 0 3px rgba(155, 155, 155, 0.4);
|
||||
box-shadow: 0 0 0 3px rgba(12, 12, 13, 0.2);
|
||||
background-image: repeating-linear-gradient(
|
||||
45deg,
|
||||
white,
|
||||
|
@ -68,8 +74,11 @@ a {
|
|||
.mainContent {
|
||||
height: 100%;
|
||||
background-color: white;
|
||||
box-sizing: border-box;
|
||||
margin: 0 10px;
|
||||
padding: 1px 10px 0 10px; /* top wtf? */
|
||||
padding: 10px 10px 28px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
|
@ -77,7 +86,8 @@ a {
|
|||
}
|
||||
|
||||
.uploads {
|
||||
flex: auto;
|
||||
flex: 0 0 262px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.noscript {
|
||||
|
@ -87,13 +97,19 @@ a {
|
|||
}
|
||||
|
||||
.btn {
|
||||
font-size: 15px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
line-height: 70px;
|
||||
font-size: 21px;
|
||||
font-weight: 500;
|
||||
color: var(--primaryControlFGColor);
|
||||
cursor: pointer;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
letter-spacing: 0.56px;
|
||||
color: var(--primaryControlFGColor);
|
||||
background: var(--primaryControlBGColor);
|
||||
border: 1px solid var(--primaryControlBGColor);
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
@ -101,75 +117,52 @@ a {
|
|||
background-color: var(--primaryControlHoverColor);
|
||||
}
|
||||
|
||||
.btn--cancel {
|
||||
color: var(--errorColor);
|
||||
background: var(--pageBGColor);
|
||||
font-size: 15px;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
.btn--stripes {
|
||||
background: repeating-linear-gradient(
|
||||
-65deg,
|
||||
#7c7c7c 0,
|
||||
#7c7c7c 17px,
|
||||
#737373 17px,
|
||||
#737373 30px
|
||||
);
|
||||
background-size: 300% 300%;
|
||||
animation: barberpole 12s linear infinite;
|
||||
}
|
||||
|
||||
.btn--cancel:disabled {
|
||||
text-decoration: none;
|
||||
cursor: auto;
|
||||
}
|
||||
@keyframes barberpole {
|
||||
0% {
|
||||
background-position: 100% 0%;
|
||||
}
|
||||
|
||||
.btn--cancel:hover {
|
||||
background-color: var(--pageBGColor);
|
||||
100% {
|
||||
background-position: 0% 0%;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
flex: 2 0 auto;
|
||||
border: 1px solid var(--primaryControlBGColor);
|
||||
border-radius: 6px 0 0 6px;
|
||||
border: 1px solid var(--lightBorderColor);
|
||||
font-size: 20px;
|
||||
color: var(--inputTextColor);
|
||||
font-family: 'SF Pro Text', sans-serif;
|
||||
letter-spacing: 0;
|
||||
line-height: 23px;
|
||||
font-weight: 300;
|
||||
height: 46px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.input--error {
|
||||
border-color: var(--errorColor);
|
||||
}
|
||||
|
||||
.input--noBtn {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.inputBtn {
|
||||
flex: auto;
|
||||
background: var(--primaryControlBGColor);
|
||||
border-radius: 0 6px 6px 0;
|
||||
border: 1px solid var(--primaryControlBGColor);
|
||||
color: var(--primaryControlFGColor);
|
||||
cursor: pointer;
|
||||
|
||||
/* Force flat button look */
|
||||
/* stylelint-disable-next-line plugin/no-unsupported-browser-features */
|
||||
appearance: none;
|
||||
font-size: 15px;
|
||||
padding-bottom: 3px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
white-space: nowrap;
|
||||
.input--error {
|
||||
border-color: var(--errorColor);
|
||||
}
|
||||
|
||||
.inputBtn:disabled {
|
||||
cursor: auto;
|
||||
.inputBtn.inputError {
|
||||
background-color: var(--errorColor);
|
||||
}
|
||||
|
||||
.inputBtn:hover {
|
||||
background-color: var(--primaryControlHoverColor);
|
||||
}
|
||||
|
||||
.inputBtn--hidden {
|
||||
display: none;
|
||||
.inputBtn.inputError:hover {
|
||||
background-color: var(--errorColor);
|
||||
}
|
||||
|
||||
.cursor--pointer {
|
||||
|
@ -188,15 +181,15 @@ a {
|
|||
}
|
||||
|
||||
.link--action {
|
||||
text-decoration: underline;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page {
|
||||
margin: 0 auto 30px;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -223,6 +216,13 @@ a {
|
|||
animation: fadeout 200ms linear;
|
||||
}
|
||||
|
||||
.goBackButton {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 18px;
|
||||
}
|
||||
|
||||
@keyframes fadeout {
|
||||
0% {
|
||||
opacity: 1;
|
||||
|
@ -250,6 +250,7 @@ a {
|
|||
|
||||
.error {
|
||||
color: var(--errorColor);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
@ -264,34 +265,50 @@ a {
|
|||
}
|
||||
|
||||
.description {
|
||||
font-size: 15px;
|
||||
line-height: 23px;
|
||||
max-width: 630px;
|
||||
text-align: center;
|
||||
margin: 0 auto 60px;
|
||||
color: var(--textColor);
|
||||
width: 92%;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
margin: 14px auto;
|
||||
color: var(--lightTextColor);
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
@media (max-device-width: 768px), (max-width: 768px) {
|
||||
.visible {
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.noDisplay {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.flexible {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media (max-device-width: 750px), (max-width: 750px) {
|
||||
.description {
|
||||
margin: 0 auto 25px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-device-width: 520px), (max-width: 520px) {
|
||||
.input {
|
||||
font-size: 22px;
|
||||
padding: 10px 10px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
.main {
|
||||
flex-direction: column;
|
||||
min-height: 700px;
|
||||
}
|
||||
|
||||
.inputBtn {
|
||||
border-radius: 0 0 6px 6px;
|
||||
flex: 0 1 65px;
|
||||
.spacer {
|
||||
flex: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.input--noBtn {
|
||||
border-radius: 6px;
|
||||
.stripedBox {
|
||||
max-height: 550px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.uploads {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin: 15px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
/* global MAXFILESIZE */
|
||||
import Archive from './archive';
|
||||
import { bytes } from './utils';
|
||||
import { checkSize } from './utils';
|
||||
|
||||
export default function(state, emitter) {
|
||||
emitter.on('DOMContentLoaded', () => {
|
||||
document.body.addEventListener('dragover', event => {
|
||||
if (state.route === '/') {
|
||||
event.preventDefault();
|
||||
const files = document.querySelector('.uploadedFilesWrapper');
|
||||
files.classList.add('uploadArea--noEvents');
|
||||
}
|
||||
});
|
||||
document.body.addEventListener('drop', event => {
|
||||
|
@ -15,21 +15,12 @@ export default function(state, emitter) {
|
|||
document
|
||||
.querySelector('.uploadArea')
|
||||
.classList.remove('uploadArea--dragging');
|
||||
const target = event.dataTransfer;
|
||||
if (target.files.length === 0) {
|
||||
return;
|
||||
}
|
||||
const file = new Archive(target.files);
|
||||
|
||||
if (file.size === 0) {
|
||||
return;
|
||||
}
|
||||
if (file.size > MAXFILESIZE) {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert(state.translate('fileTooBig', { size: bytes(MAXFILESIZE) }));
|
||||
return;
|
||||
}
|
||||
emitter.emit('upload', { file, type: 'drop' });
|
||||
const target = event.dataTransfer;
|
||||
|
||||
checkSize(target.files, state.files);
|
||||
|
||||
emitter.emit('addFiles', { files: target.files });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
import FileSender from './fileSender';
|
||||
import FileReceiver from './fileReceiver';
|
||||
import {
|
||||
copyToClipboard,
|
||||
delay,
|
||||
fadeOut,
|
||||
openLinksInNewTab,
|
||||
percent
|
||||
} from './utils';
|
||||
import { copyToClipboard, delay, openLinksInNewTab, percent } from './utils';
|
||||
import * as metrics from './metrics';
|
||||
import { hasPassword } from './api';
|
||||
import Archive from './archive';
|
||||
|
||||
export default function(state, emitter) {
|
||||
let lastRender = 0;
|
||||
let updateTitle = false;
|
||||
state.files = [];
|
||||
|
||||
function render() {
|
||||
emitter.emit('render');
|
||||
|
@ -64,6 +61,16 @@ export default function(state, emitter) {
|
|||
metrics.changedDownloadLimit(file);
|
||||
});
|
||||
|
||||
emitter.on('removeUpload', async ({ file }) => {
|
||||
for (let i = 0; i < state.files.length; i++) {
|
||||
if (state.files[i] === file) {
|
||||
state.files.splice(i, 1);
|
||||
render();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
emitter.on('delete', async ({ file, location }) => {
|
||||
try {
|
||||
metrics.deletedUpload({
|
||||
|
@ -85,11 +92,22 @@ export default function(state, emitter) {
|
|||
state.transfer.cancel();
|
||||
});
|
||||
|
||||
emitter.on('upload', async ({ file, type }) => {
|
||||
emitter.on('addFiles', async ({ files }) => {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
state.files.push(files[i]);
|
||||
}
|
||||
render();
|
||||
});
|
||||
|
||||
//TODO: hook up to multi-file upload functionality
|
||||
emitter.on('upload', async ({ files, type, dlCount, password }) => {
|
||||
const file = new Archive(files);
|
||||
|
||||
const size = file.size;
|
||||
const sender = new FileSender(file);
|
||||
sender.on('progress', updateProgress);
|
||||
sender.on('encrypting', render);
|
||||
sender.on('complete', render);
|
||||
state.transfer = sender;
|
||||
state.uploading = true;
|
||||
render();
|
||||
|
@ -98,19 +116,25 @@ export default function(state, emitter) {
|
|||
await delay(200);
|
||||
try {
|
||||
metrics.startedUpload({ size, type });
|
||||
|
||||
const ownedFile = await sender.upload();
|
||||
ownedFile.type = type;
|
||||
state.storage.totalUploads += 1;
|
||||
metrics.completedUpload(ownedFile);
|
||||
|
||||
state.storage.addFile(ownedFile);
|
||||
|
||||
if (password) {
|
||||
emitter.emit('password', { password, file: ownedFile });
|
||||
}
|
||||
emitter.emit('changeLimit', { file: ownedFile, value: dlCount });
|
||||
|
||||
const cancelBtn = document.getElementById('cancel-upload');
|
||||
if (cancelBtn) {
|
||||
cancelBtn.hidden = 'hidden';
|
||||
}
|
||||
if (document.querySelector('.page')) {
|
||||
await delay(1000);
|
||||
await fadeOut('.page');
|
||||
}
|
||||
emitter.emit('pushState', `/share/${ownedFile.id}`);
|
||||
} catch (err) {
|
||||
|
@ -127,6 +151,8 @@ export default function(state, emitter) {
|
|||
}
|
||||
} finally {
|
||||
openLinksInNewTab(links, false);
|
||||
state.files = [];
|
||||
state.password = '';
|
||||
state.uploading = false;
|
||||
state.transfer = null;
|
||||
}
|
||||
|
@ -150,6 +176,17 @@ export default function(state, emitter) {
|
|||
render();
|
||||
});
|
||||
|
||||
emitter.on('getPasswordExist', async ({ id }) => {
|
||||
try {
|
||||
state.fileInfo = await hasPassword(id);
|
||||
render();
|
||||
} catch (e) {
|
||||
if (e.message === '404') {
|
||||
return emitter.emit('pushState', '/404');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
emitter.on('getMetadata', async () => {
|
||||
const file = state.fileInfo;
|
||||
|
||||
|
@ -172,6 +209,7 @@ export default function(state, emitter) {
|
|||
emitter.on('download', async file => {
|
||||
state.transfer.on('progress', updateProgress);
|
||||
state.transfer.on('decrypting', render);
|
||||
state.transfer.on('complete', render);
|
||||
const links = openLinksInNewTab();
|
||||
const size = file.size;
|
||||
try {
|
||||
|
@ -186,12 +224,11 @@ export default function(state, emitter) {
|
|||
const speed = size / (time / 1000);
|
||||
if (document.querySelector('.page')) {
|
||||
await delay(1000);
|
||||
await fadeOut('.page');
|
||||
}
|
||||
state.storage.totalDownloads += 1;
|
||||
state.transfer.reset();
|
||||
metrics.completedDownload({ size, time, speed });
|
||||
emitter.emit('pushState', '/completed');
|
||||
//emitter.emit('pushState', '/completed');
|
||||
} catch (err) {
|
||||
if (err.message === '0') {
|
||||
// download cancelled
|
||||
|
|
|
@ -172,6 +172,7 @@ export default class FileReceiver extends Nanobus {
|
|||
|
||||
this.downloadRequest = null;
|
||||
this.msg = 'downloadFinish';
|
||||
this.emit('complete');
|
||||
this.state = 'complete';
|
||||
} catch (e) {
|
||||
this.downloadRequest = null;
|
||||
|
|
|
@ -93,6 +93,7 @@ export default class FileSender extends Nanobus {
|
|||
url: `${result.url}#${secretKey}`,
|
||||
name: this.file.name,
|
||||
size: this.file.size,
|
||||
manifest: this.file.manifest,
|
||||
time: time,
|
||||
speed: this.file.size / (time / 1000),
|
||||
createdAt: Date.now(),
|
||||
|
@ -101,6 +102,7 @@ export default class FileSender extends Nanobus {
|
|||
nonce: this.keychain.nonce,
|
||||
ownerToken: result.ownerToken
|
||||
});
|
||||
|
||||
return ownedFile;
|
||||
} catch (e) {
|
||||
this.msg = 'errorPageHeader';
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
@import './templates/activeBackground/activeBackground.css';
|
||||
@import './templates/header/header.css';
|
||||
@import './templates/downloadButton/downloadButton.css';
|
||||
@import './templates/progress/progress.css';
|
||||
@import './templates/passwordInput/passwordInput.css';
|
||||
@import './templates/downloadPassword/downloadPassword.css';
|
||||
@import './templates/setPasswordSection/setPasswordSection.css';
|
||||
|
@ -11,7 +10,14 @@
|
|||
@import './templates/selectbox/selectbox.css';
|
||||
@import './templates/fileList/fileList.css';
|
||||
@import './templates/file/file.css';
|
||||
@import './templates/uploadedFile/uploadedFile.css';
|
||||
@import './templates/uploadedFileList/uploadedFileList.css';
|
||||
@import './templates/popup/popup.css';
|
||||
@import './templates/title/title.css';
|
||||
@import './templates/fileIcon/fileIcon.css';
|
||||
@import './templates/signupPromo/signupPromo.css';
|
||||
@import './templates/userAccount/userAccount.css';
|
||||
@import './pages/welcome/welcome.css';
|
||||
@import './pages/share/share.css';
|
||||
@import './pages/signin/signin.css';
|
||||
@import './pages/unsupported/unsupported.css';
|
||||
|
|
|
@ -9,6 +9,7 @@ export default class OwnedFile {
|
|||
this.name = obj.name;
|
||||
this.size = obj.size;
|
||||
this.type = obj.type;
|
||||
this.manifest = obj.manifest;
|
||||
this.time = obj.time;
|
||||
this.speed = obj.speed;
|
||||
this.createdAt = obj.createdAt;
|
||||
|
@ -70,6 +71,7 @@ export default class OwnedFile {
|
|||
name: this.name,
|
||||
size: this.size,
|
||||
type: this.type,
|
||||
manifest: this.manifest,
|
||||
time: this.time,
|
||||
speed: this.speed,
|
||||
createdAt: this.createdAt,
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
const html = require('choo/html');
|
||||
const progress = require('../../templates/progress');
|
||||
const { fadeOut } = require('../../utils');
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
return html`
|
||||
<div class="page effect--fadeIn">
|
||||
<div class="title">
|
||||
${state.translate('downloadFinish')}
|
||||
</div>
|
||||
<div class="description"></div>
|
||||
${progress(1)}
|
||||
<div class="progressSection">
|
||||
<div class="progressSection__text"></div>
|
||||
</div>
|
||||
<a class="link link--action"
|
||||
href="/"
|
||||
onclick=${sendNew}>${state.translate('sendYourFilesLink')}</a>
|
||||
</div>`;
|
||||
|
||||
async function sendNew(e) {
|
||||
e.preventDefault();
|
||||
await fadeOut('.page');
|
||||
emit('pushState', '/');
|
||||
}
|
||||
};
|
|
@ -1,42 +0,0 @@
|
|||
const html = require('choo/html');
|
||||
const progress = require('../../templates/progress');
|
||||
const { bytes } = require('../../utils');
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
const transfer = state.transfer;
|
||||
const cancelBtn = html`
|
||||
<button
|
||||
id="cancel"
|
||||
class="btn btn--cancel"
|
||||
title="${state.translate('deletePopupCancel')}"
|
||||
onclick=${cancel}>
|
||||
${state.translate('deletePopupCancel')}
|
||||
</button>`;
|
||||
|
||||
return html`
|
||||
<div class="page effect--fadeIn">
|
||||
<div class="title">
|
||||
${state.translate('downloadingPageProgress', {
|
||||
filename: state.fileInfo.name,
|
||||
size: bytes(state.fileInfo.size)
|
||||
})}
|
||||
</div>
|
||||
<div class="description">
|
||||
${state.translate('downloadingPageMessage')}
|
||||
</div>
|
||||
${progress(transfer.progressRatio, transfer.progressIndefinite)}
|
||||
<div class="progressSection">
|
||||
<div class="progressSection__text">
|
||||
${state.translate(transfer.msg, transfer.sizes)}
|
||||
</div>
|
||||
${transfer.state === 'downloading' ? cancelBtn : null}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
function cancel() {
|
||||
const btn = document.getElementById('cancel');
|
||||
btn.remove();
|
||||
emit('cancel');
|
||||
}
|
||||
};
|
|
@ -1,10 +1,22 @@
|
|||
const html = require('choo/html');
|
||||
const assets = require('../../../common/assets');
|
||||
const title = require('../../templates/title');
|
||||
|
||||
module.exports = function(state) {
|
||||
return html`
|
||||
<div class="page">
|
||||
<div class="title">${state.translate('errorPageHeader')}</div>
|
||||
<img src="${assets.get('illustration_error.svg')}"/>
|
||||
|
||||
${title(state)}
|
||||
|
||||
<div class="error">${state.translate('errorPageHeader')}</div>
|
||||
<img class="flexible" src="${assets.get('illustration_error.svg')}"/>
|
||||
|
||||
<div class="description">
|
||||
${state.translate('uploadPageExplainer')}
|
||||
</div>
|
||||
<a class="link link--action" href="/">
|
||||
${state.translate('sendYourFilesLink')}
|
||||
</a>
|
||||
|
||||
</div>`;
|
||||
};
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
const html = require('choo/html');
|
||||
const assets = require('../../../common/assets');
|
||||
const title = require('../../templates/title');
|
||||
|
||||
module.exports = function(state) {
|
||||
return html`
|
||||
<div class="page">
|
||||
<div class="title">${state.translate('expiredPageHeader')}</div>
|
||||
<img src="${assets.get('illustration_expired.svg')}" id="expired-img">
|
||||
|
||||
${title(state)}
|
||||
|
||||
<div class="error">${state.translate('expiredPageHeader')}</div>
|
||||
<img src="${assets.get(
|
||||
'illustration_expired.svg'
|
||||
)}" class="flexible" id="expired-img">
|
||||
<div class="description">
|
||||
${state.translate('uploadPageExplainer')}
|
||||
</div>
|
||||
|
|
19
app/pages/password/index.js
Normal file
19
app/pages/password/index.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
const html = require('choo/html');
|
||||
const titleSection = require('../../templates/title');
|
||||
const downloadPassword = require('../../templates/downloadPassword');
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
return html`
|
||||
<div class="page">
|
||||
${titleSection(state)}
|
||||
|
||||
<div class="description">${state.translate('downloadMessage2')}</div>
|
||||
${downloadPassword(state, emit)}
|
||||
|
||||
<a class="link link--action" href="/">
|
||||
${state.translate('sendYourFilesLink')}
|
||||
</a>
|
||||
|
||||
</div>
|
||||
`;
|
||||
};
|
|
@ -1,40 +1,25 @@
|
|||
const html = require('choo/html');
|
||||
const assets = require('../../../common/assets');
|
||||
const { bytes } = require('../../utils');
|
||||
const titleSection = require('../../templates/title');
|
||||
const downloadButton = require('../../templates/downloadButton');
|
||||
const downloadedFiles = require('../../templates/uploadedFileList');
|
||||
|
||||
module.exports = function(state, pageAction) {
|
||||
const fileInfo = state.fileInfo;
|
||||
module.exports = function(state, emit) {
|
||||
const storageFile = state.storage.getFileById(state.params.id);
|
||||
|
||||
const size = fileInfo.size
|
||||
? state.translate('downloadFileSize', { size: bytes(fileInfo.size) })
|
||||
: '';
|
||||
const multifiles = Array.from(storageFile.manifest.files);
|
||||
|
||||
const title = fileInfo.name
|
||||
? state.translate('downloadFileName', { filename: fileInfo.name })
|
||||
: state.translate('downloadFileTitle');
|
||||
|
||||
const info = html`
|
||||
<div id="dl-file"
|
||||
data-nonce="${fileInfo.nonce}"
|
||||
data-requires-password="${fileInfo.requiresPassword}"></div>`;
|
||||
if (!pageAction) {
|
||||
return info;
|
||||
}
|
||||
return html`
|
||||
<div class="page">
|
||||
<div class="title">
|
||||
<span>${title}</span>
|
||||
<span>${' ' + size}</span>
|
||||
</div>
|
||||
<div class="description">${state.translate('downloadMessage')}</div>
|
||||
<img
|
||||
src="${assets.get('illustration_download.svg')}"
|
||||
title="${state.translate('downloadAltText')}"/>
|
||||
${pageAction}
|
||||
${titleSection(state)}
|
||||
|
||||
${downloadedFiles(multifiles, state, emit)}
|
||||
<div class="description">${state.translate('downloadMessage2')}</div>
|
||||
${downloadButton(state, emit)}
|
||||
|
||||
<a class="link link--action" href="/">
|
||||
${state.translate('sendYourFilesLink')}
|
||||
</a>
|
||||
${info}
|
||||
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
|
|
@ -3,42 +3,51 @@ const html = require('choo/html');
|
|||
const raw = require('choo/html/raw');
|
||||
const assets = require('../../../common/assets');
|
||||
const notFound = require('../notFound');
|
||||
const setPasswordSection = require('../../templates/setPasswordSection');
|
||||
const selectbox = require('../../templates/selectbox');
|
||||
const deletePopup = require('../../templates/popup');
|
||||
const uploadedFiles = require('../../templates/uploadedFileList');
|
||||
const { allowedCopy, delay, fadeOut } = require('../../utils');
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
const file = state.storage.getFileById(state.params.id);
|
||||
if (!file) {
|
||||
return notFound(state, emit);
|
||||
return notFound(state);
|
||||
}
|
||||
|
||||
const passwordReminderClass = file._hasPassword
|
||||
? ''
|
||||
: 'passwordReminder--hidden';
|
||||
|
||||
const multifiles = Array.from(file.manifest.files);
|
||||
|
||||
return html`
|
||||
<div id="shareWrapper" class="effect--fadeIn">
|
||||
${expireInfo(file, state.translate, emit)}
|
||||
<div class="sharePage">
|
||||
|
||||
<div class="page effect--fadeIn" id="shareWrapper">
|
||||
<a href="/" class="goBackButton">
|
||||
<img src="${assets.get('back-arrow.svg')}"/>
|
||||
</a>
|
||||
${expireInfo(file, state.translate)}
|
||||
|
||||
${uploadedFiles(multifiles, state, emit)}
|
||||
|
||||
|
||||
<div class="sharePage__copyText">
|
||||
${state.translate('copyUrlFormLabelWithName', { filename: file.name })}
|
||||
${state.translate('copyUrlFormLabelWithName', { filename: '' })}
|
||||
<div class="sharePage__passwordReminder ${passwordReminderClass}">(don't forget the password too)</div>
|
||||
</div>
|
||||
<div class="copySection">
|
||||
<input
|
||||
id="fileUrl"
|
||||
class="copySection__url"
|
||||
type="url"
|
||||
value="${file.url}"
|
||||
readonly="true"/>
|
||||
<button id="copyBtn"
|
||||
class="inputBtn inputBtn--copy"
|
||||
title="${state.translate('copyUrlFormButton')}"
|
||||
onclick=${copyLink}>${state.translate('copyUrlFormButton')}</button>
|
||||
</div>
|
||||
${setPasswordSection(state, emit)}
|
||||
<button
|
||||
class="btn btn--delete"
|
||||
title="${state.translate('deleteFileButton')}"
|
||||
onclick=${showPopup}>${state.translate('deleteFileButton')}
|
||||
|
||||
<input
|
||||
id="fileUrl"
|
||||
class="copySection__url"
|
||||
type="url"
|
||||
value="${file.url}"
|
||||
readonly="true"/>
|
||||
|
||||
<button id="copyBtn"
|
||||
class="btn copyBtn"
|
||||
title="${state.translate('copyUrlFormButton')}"
|
||||
onclick=${copyLink}>${state.translate('copyUrlFormButton')}
|
||||
</button>
|
||||
|
||||
<div class="sharePage__deletePopup">
|
||||
${deletePopup(
|
||||
state.translate('deletePopupText'),
|
||||
|
@ -47,11 +56,15 @@ module.exports = function(state, emit) {
|
|||
deleteFile
|
||||
)}
|
||||
</div>
|
||||
<a class="link link--action"
|
||||
href="/"
|
||||
onclick=${sendNew}>${state.translate('sendAnotherFileLink')}</a>
|
||||
|
||||
<a
|
||||
class="error btn--delete"
|
||||
title="${state.translate('deleteFileButton')}"
|
||||
onclick=${showPopup}>${state.translate('deleteFileButton')}
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
`;
|
||||
|
||||
function showPopup() {
|
||||
|
@ -60,30 +73,22 @@ module.exports = function(state, emit) {
|
|||
popup.focus();
|
||||
}
|
||||
|
||||
async function sendNew(e) {
|
||||
e.preventDefault();
|
||||
await fadeOut('#shareWrapper');
|
||||
emit('pushState', '/');
|
||||
}
|
||||
|
||||
async function copyLink() {
|
||||
if (allowedCopy()) {
|
||||
emit('copy', { url: file.url, location: 'success-screen' });
|
||||
const input = document.getElementById('fileUrl');
|
||||
input.disabled = true;
|
||||
input.classList.add('input--copied');
|
||||
const copyBtn = document.getElementById('copyBtn');
|
||||
copyBtn.disabled = true;
|
||||
copyBtn.classList.add('inputBtn--copied');
|
||||
copyBtn.classList.add('copyBtn--copied');
|
||||
copyBtn.replaceChild(
|
||||
html`<img src="${assets.get('check-16.svg')}" class="cursor--pointer">`,
|
||||
html`<label>${state.translate('copiedUrl')}</label>`,
|
||||
copyBtn.firstChild
|
||||
);
|
||||
await delay(2000);
|
||||
input.disabled = false;
|
||||
input.classList.remove('input--copied');
|
||||
copyBtn.disabled = false;
|
||||
copyBtn.classList.remove('inputBtn--copied');
|
||||
copyBtn.classList.remove('copyBtn--copied');
|
||||
copyBtn.textContent = state.translate('copyUrlFormButton');
|
||||
}
|
||||
}
|
||||
|
@ -95,18 +100,14 @@ module.exports = function(state, emit) {
|
|||
}
|
||||
};
|
||||
|
||||
function expireInfo(file, translate, emit) {
|
||||
function expireInfo(file, translate) {
|
||||
const hours = Math.floor(EXPIRE_SECONDS / 60 / 60);
|
||||
const el = html`<div class="title">${raw(
|
||||
const el = html`<div class="shareTitle">${raw(
|
||||
translate('expireInfo', {
|
||||
downloadCount: '<select></select>',
|
||||
downloadCount: translate('downloadCount', { num: file.dlimit }),
|
||||
timespan: translate('timespanHours', { num: hours })
|
||||
})
|
||||
)}</div>`;
|
||||
const select = el.querySelector('select');
|
||||
const options = [1, 2, 3, 4, 5, 20].filter(i => i > (file.dtotal || 0));
|
||||
const t = num => translate('downloadCount', { num });
|
||||
const changed = value => emit('changeLimit', { file, value });
|
||||
el.replaceChild(selectbox(file.dlimit || 1, options, t, changed), select);
|
||||
|
||||
return el;
|
||||
}
|
||||
|
|
|
@ -1,112 +1,86 @@
|
|||
.sharePage {
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 640px;
|
||||
.sharePage__copyText {
|
||||
margin: 8px 0 8px;
|
||||
word-wrap: break-word;
|
||||
font-size: 15px;
|
||||
color: var(--lightTextColor);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sharePage__copyText {
|
||||
align-self: flex-start;
|
||||
margin-top: 60px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--textColor);
|
||||
max-width: 614px;
|
||||
word-wrap: break-word;
|
||||
.sharePage__passwordReminder {
|
||||
font-size: 11px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.passwordReminder--hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sharePage__deletePopup {
|
||||
position: relative;
|
||||
align-self: center;
|
||||
bottom: 50px;
|
||||
margin-top: -70px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.shareTitle {
|
||||
color: var(--textColor);
|
||||
margin: 8px auto 15px;
|
||||
text-align: center;
|
||||
font-family: 'SF Pro Text', sans-serif;
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.copySection {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.copySection__url {
|
||||
flex: 1;
|
||||
height: 56px;
|
||||
border: 1px solid var(--primaryControlBGColor);
|
||||
border-radius: 6px 0 0 6px;
|
||||
font-size: 20px;
|
||||
color: var(--inputTextColor);
|
||||
box-sizing: border-box;
|
||||
height: 30px;
|
||||
border: 1px solid var(--lightBorderColor);
|
||||
border-radius: 4px;
|
||||
font-size: 15px;
|
||||
color: var(--primaryControlBGColor);
|
||||
margin: 0 0 6px;
|
||||
padding: 6px;
|
||||
font-family: 'SF Pro Text', sans-serif;
|
||||
letter-spacing: 0;
|
||||
line-height: 23px;
|
||||
font-weight: 300;
|
||||
padding-left: 10px;
|
||||
line-height: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.copySection__url:disabled {
|
||||
border: 1px solid var(--successControlBGColor);
|
||||
background: var(--successControlFGColor);
|
||||
}
|
||||
|
||||
.inputBtn--copy {
|
||||
flex: 0 1 165px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.input--copied {
|
||||
border-color: var(--successControlBGColor);
|
||||
}
|
||||
|
||||
.inputBtn--copied,
|
||||
.inputBtn--copied:hover {
|
||||
.copyBtn {
|
||||
transition: background 0.5s;
|
||||
}
|
||||
|
||||
.copyBtn--copied,
|
||||
.copyBtn--copied:hover {
|
||||
background: var(--successControlBGColor);
|
||||
border: 1px solid var(--successControlBGColor);
|
||||
color: var(--successControlFGColor);
|
||||
transition: background 0s;
|
||||
}
|
||||
|
||||
.btn--delete {
|
||||
border: none;
|
||||
align-self: center;
|
||||
width: 176px;
|
||||
height: 44px;
|
||||
background: #fff;
|
||||
border-color: rgba(12, 12, 13, 0.3);
|
||||
margin-top: 50px;
|
||||
margin-bottom: 12px;
|
||||
color: #313131;
|
||||
margin: 10px 0 0;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
color: var(--errorColor);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn--delete:hover {
|
||||
background: #efeff1;
|
||||
}
|
||||
|
||||
@media (max-device-width: 768px), (max-width: 768px) {
|
||||
.copySection {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.copySection__url {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-device-width: 520px), (max-width: 520px) {
|
||||
.copySection {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.copySection__url {
|
||||
font-size: 22px;
|
||||
padding: 15px 10px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
}
|
||||
|
||||
.sharePage__copyText {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.inputBtn--copy {
|
||||
border-radius: 0 0 6px 6px;
|
||||
flex: 0 1 65px;
|
||||
}
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
60
app/pages/signin/index.js
Normal file
60
app/pages/signin/index.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
const html = require('choo/html');
|
||||
const assets = require('../../../common/assets');
|
||||
const title = require('../../templates/title');
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
module.exports = function(state, emit) {
|
||||
return html`
|
||||
|
||||
<div class="page signInPage">
|
||||
<a href="/" class="goBackButton">
|
||||
<img src="${assets.get('back-arrow.svg')}"/>
|
||||
</a>
|
||||
${title(state)}
|
||||
|
||||
<div class="signIn__info flexible">
|
||||
${state.translate('accountBenefitTitle')}
|
||||
<ul>
|
||||
<li>${state.translate('accountBenefitMultiFile')}</li>
|
||||
<li>${state.translate('accountBenefitLargeFiles')}</li>
|
||||
<li>${state.translate('accountBenefitExpiry')}</li>
|
||||
<li>${state.translate('accountBenefitSync')}</li>
|
||||
<li>${state.translate('accountBenefitNotify')}</li>
|
||||
<li>${state.translate('accountBenefitMore')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="signIn__form flexible">
|
||||
|
||||
<img class="signIn__firefoxLogo"
|
||||
src="${assets.get('firefox_logo-only.svg')}"
|
||||
width=56 height=56
|
||||
alt="Firefox logo"/>
|
||||
|
||||
<div class="signIn__emailLabel">
|
||||
${state.translate('signInEmailEnter')}
|
||||
</div>
|
||||
${state.translate('signInContinueMessage')}
|
||||
|
||||
<form
|
||||
data-no-csrf>
|
||||
<input
|
||||
type="text"
|
||||
class="signIn__emailInput"
|
||||
placeholder=${state.translate('emailEntryPlaceholder')}/>
|
||||
|
||||
<input
|
||||
class='noDisplay'
|
||||
id="emailSubmit"
|
||||
type="submit"/>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<label class="btn" for="emailSubmit">
|
||||
${state.translate('signInContinueButton')}
|
||||
</label>
|
||||
|
||||
</div>
|
||||
`;
|
||||
};
|
33
app/pages/signin/signin.css
Normal file
33
app/pages/signin/signin.css
Normal file
|
@ -0,0 +1,33 @@
|
|||
.signInPage {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
color: var(--lightTextColor);
|
||||
}
|
||||
|
||||
.signIn__info {
|
||||
width: 308px;
|
||||
margin: 16px auto 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.signIn__firefoxLogo {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.signIn__emailLabel {
|
||||
font-size: 22px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.signIn__emailInput {
|
||||
box-sizing: border-box;
|
||||
width: 310px;
|
||||
height: 40px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
margin: 0;
|
||||
padding: 0 8px;
|
||||
font-size: 18px;
|
||||
color: var(--lightTextColor);
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
const html = require('choo/html');
|
||||
const assets = require('../../../common/assets');
|
||||
const title = require('../../templates/title');
|
||||
|
||||
module.exports = function(state) {
|
||||
let strings = {};
|
||||
let why = '';
|
||||
let url = '';
|
||||
let buttonAction = '';
|
||||
|
||||
if (state.params.reason !== 'outdated') {
|
||||
strings = unsupportedStrings(state);
|
||||
why = html`
|
||||
|
@ -28,20 +30,26 @@ module.exports = function(state) {
|
|||
${strings.button}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="unsupportedPage">
|
||||
<div class="title">${strings.title}</div>
|
||||
<div class="description">
|
||||
<div class="page unsupportedPage">
|
||||
${title(state)}
|
||||
<div class="error unsupportedPage__error">${strings.header}</div>
|
||||
<div class="description flexible">
|
||||
${strings.description}
|
||||
${why}
|
||||
</div>
|
||||
${why}
|
||||
<a href="${url}" class="firefoxDownload">
|
||||
<img
|
||||
src="${assets.get('firefox_logo-only.svg')}"
|
||||
class="firefoxDownload__logo"
|
||||
alt="Firefox"/>
|
||||
${buttonAction}
|
||||
</a>
|
||||
|
||||
<div class="flexible firefoxDownload">
|
||||
<a href="${url}" class="firefoxDownload__button">
|
||||
<img
|
||||
src="${assets.get('firefox_logo-only.svg')}"
|
||||
class="firefoxDownload__logo"
|
||||
alt="Firefox"/>
|
||||
${buttonAction}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="unsupportedPage__info">
|
||||
${strings.explainer}
|
||||
</div>
|
||||
|
@ -50,7 +58,7 @@ module.exports = function(state) {
|
|||
|
||||
function outdatedStrings(state) {
|
||||
return {
|
||||
title: state.translate('notSupportedHeader'),
|
||||
header: state.translate('notSupportedHeader'),
|
||||
description: state.translate('notSupportedOutdatedDetail'),
|
||||
button: state.translate('updateFirefox'),
|
||||
explainer: state.translate('uploadPageExplainer')
|
||||
|
@ -59,7 +67,7 @@ function outdatedStrings(state) {
|
|||
|
||||
function unsupportedStrings(state) {
|
||||
return {
|
||||
title: state.translate('notSupportedHeader'),
|
||||
header: state.translate('notSupportedHeader'),
|
||||
description: state.translate('notSupportedDetail'),
|
||||
button: state.translate('downloadFirefoxButtonSub'),
|
||||
explainer: state.translate('uploadPageExplainer')
|
||||
|
|
|
@ -1,26 +1,30 @@
|
|||
.unsupportedPage {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.unsupportedPage__error {
|
||||
margin: 10px 0 20px;
|
||||
}
|
||||
|
||||
.unsupportedPage__info {
|
||||
font-size: 13px;
|
||||
line-height: 23px;
|
||||
text-align: center;
|
||||
color: var(--lightTextColor);
|
||||
margin: 0 auto 23px;
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
|
||||
.firefoxDownload {
|
||||
margin-bottom: 181px;
|
||||
height: 80px;
|
||||
background: #98e02b;
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.firefoxDownload__button {
|
||||
margin: 0 auto 20px;
|
||||
height: 70px;
|
||||
width: 250px;
|
||||
background: #12bc00;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
box-shadow: 0 5px 3px rgb(234, 234, 234);
|
||||
font-family: 'Fira Sans', 'segoe ui', sans-serif;
|
||||
font-weight: 500;
|
||||
color: var(--primaryControlFGColor);
|
||||
|
@ -29,21 +33,22 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
padding: 0 25px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.firefoxDownload__logo {
|
||||
width: 70px;
|
||||
width: 55px;
|
||||
}
|
||||
|
||||
.firefoxDownload__action {
|
||||
text-align: left;
|
||||
margin-left: 20.4px;
|
||||
text-transform: uppercase;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.firefoxDownload__text {
|
||||
text-transform: none;
|
||||
font-family: 'Fira Sans', 'segoe ui', sans-serif;
|
||||
font-weight: 300;
|
||||
font-size: 18px;
|
||||
letter-spacing: -0.69px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
const html = require('choo/html');
|
||||
const progress = require('../../templates/progress');
|
||||
const { bytes } = require('../../utils');
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
const transfer = state.transfer;
|
||||
|
||||
return html`
|
||||
<div class="page effect--fadeIn">
|
||||
<div class="title">
|
||||
${state.translate('uploadingPageProgress', {
|
||||
filename: transfer.file.name,
|
||||
size: bytes(transfer.file.size)
|
||||
})}
|
||||
</div>
|
||||
<div class="description"></div>
|
||||
${progress(transfer.progressRatio, transfer.progressIndefinite)}
|
||||
<div class="progressSection">
|
||||
<div class="progressSection__text">
|
||||
${state.translate(transfer.msg, transfer.sizes)}
|
||||
</div>
|
||||
<button
|
||||
id="cancel-upload"
|
||||
class="btn btn--cancel"
|
||||
title="${state.translate('uploadingPageCancel')}"
|
||||
onclick=${cancel}>
|
||||
${state.translate('uploadingPageCancel')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
function cancel() {
|
||||
const btn = document.getElementById('cancel-upload');
|
||||
btn.disabled = true;
|
||||
btn.textContent = state.translate('uploadCancelNotification');
|
||||
emit('cancel');
|
||||
}
|
||||
};
|
|
@ -1,50 +1,86 @@
|
|||
/* global MAXFILESIZE */
|
||||
const html = require('choo/html');
|
||||
const assets = require('../../../common/assets');
|
||||
const fileList = require('../../templates/fileList');
|
||||
const { bytes, fadeOut } = require('../../utils');
|
||||
const { checkSize } = require('../../utils');
|
||||
const title = require('../../templates/title');
|
||||
const setPasswordSection = require('../../templates/setPasswordSection');
|
||||
const uploadBox = require('../../templates/uploadedFileList');
|
||||
const expireInfo = require('../../templates/expireInfo');
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
// the page flickers if both the server and browser set 'effect--fadeIn'
|
||||
const fade = state.layout ? '' : 'effect--fadeIn';
|
||||
const files = state.files ? state.files : [];
|
||||
|
||||
const optionClass = state.uploading ? 'uploadOptions--faded' : '';
|
||||
const btnUploading = state.uploading ? 'btn--stripes' : '';
|
||||
const faded = files.length > 0 ? 'uploadArea--faded' : '';
|
||||
const selectFileClass = files.length > 0 ? 'btn--hidden' : '';
|
||||
const sendFileClass = files.length > 0 ? '' : 'btn--hidden';
|
||||
|
||||
let btnText = '';
|
||||
|
||||
if (state.encrypting) {
|
||||
btnText = state.translate('encryptingFile');
|
||||
} else if (state.uploading) {
|
||||
btnText = `sending... ${Math.floor(state.transfer.progressRatio * 100)}%`;
|
||||
} else {
|
||||
//default pre-upload text
|
||||
btnText = state.translate('uploadSuccessConfirmHeader');
|
||||
}
|
||||
|
||||
return html`
|
||||
<div id="page-one" class="${fade}">
|
||||
<div class="title">${state.translate('uploadPageHeader')}</div>
|
||||
<div class="description">
|
||||
<div>${state.translate('uploadPageExplainer')}</div>
|
||||
<a
|
||||
href="https://testpilot.firefox.com/experiments/send"
|
||||
class="link">
|
||||
${state.translate('uploadPageLearnMore')}
|
||||
</a>
|
||||
</div>
|
||||
<div class="uploadArea"
|
||||
<div id="page-one" class="page ${fade}">
|
||||
${title(state)}
|
||||
|
||||
<label class="uploadArea"
|
||||
ondragover=${dragover}
|
||||
ondragleave=${dragleave}>
|
||||
<img
|
||||
src="${assets.get('upload.svg')}"
|
||||
title="${state.translate('uploadSvgAlt')}"/>
|
||||
<div class="uploadArea__msg">
|
||||
${state.translate('uploadPageDropMessage')}
|
||||
|
||||
${uploadBox(files, state, emit)}
|
||||
|
||||
<div class="uploadedFilesWrapper ${faded}">
|
||||
<img
|
||||
class="uploadArea__icon"
|
||||
src="${assets.get('addfile.svg')}"
|
||||
title="${state.translate('uploadSvgAlt')}"/>
|
||||
<div class="uploadArea__msg">
|
||||
${state.translate('uploadDropDragMessage')}
|
||||
</div>
|
||||
|
||||
<span class="uploadArea__clickMsg">
|
||||
${state.translate('uploadDropClickMessage')}
|
||||
</span>
|
||||
</div>
|
||||
<span class="uploadArea__sizeMsg">
|
||||
${state.translate('uploadPageSizeMessage')}
|
||||
</span>
|
||||
|
||||
<input id="file-upload"
|
||||
class="inputFile"
|
||||
class="inputFile fileBox"
|
||||
type="file"
|
||||
multiple
|
||||
name="fileUploaded"
|
||||
onfocus=${onfocus}
|
||||
onblur=${onblur}
|
||||
onchange=${upload} />
|
||||
<label for="file-upload"
|
||||
class="btn btn--file"
|
||||
title="${state.translate('uploadPageBrowseButton1')}">
|
||||
${state.translate('uploadPageBrowseButton1')}
|
||||
</label>
|
||||
onchange=${addFiles} />
|
||||
|
||||
</label>
|
||||
|
||||
<div class="uploadOptions ${optionClass}">
|
||||
${expireInfo(state)}
|
||||
${setPasswordSection(state)}
|
||||
</div>
|
||||
${fileList(state, emit)}
|
||||
|
||||
<label for="file-upload"
|
||||
class="btn btn--file ${selectFileClass}"
|
||||
title="${state.translate('uploadPageBrowseButton1')}">
|
||||
${state.translate('uploadPageBrowseButton1')}
|
||||
</label>
|
||||
|
||||
<button
|
||||
class="btn ${btnUploading} ${sendFileClass}"
|
||||
onclick=${upload}
|
||||
title="${btnText}">
|
||||
${btnText}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -66,22 +102,23 @@ module.exports = function(state, emit) {
|
|||
event.target.classList.remove('inputFile--focused');
|
||||
}
|
||||
|
||||
async function addFiles(event) {
|
||||
event.preventDefault();
|
||||
const target = event.target;
|
||||
checkSize(target.files, state.files);
|
||||
emit('addFiles', { files: target.files });
|
||||
}
|
||||
|
||||
async function upload(event) {
|
||||
event.preventDefault();
|
||||
const Archive = require('../../archive').default;
|
||||
const target = event.target;
|
||||
const file = new Archive(target.files);
|
||||
|
||||
if (file.size === 0) {
|
||||
return;
|
||||
if (files.length > 0) {
|
||||
emit('upload', {
|
||||
files,
|
||||
type: 'click',
|
||||
dlCount: state.downloadCount,
|
||||
password: state.password
|
||||
});
|
||||
}
|
||||
if (file.size > MAXFILESIZE) {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert(state.translate('fileTooBig', { size: bytes(MAXFILESIZE) }));
|
||||
return;
|
||||
}
|
||||
|
||||
await fadeOut('#page-one');
|
||||
emit('upload', { file, type: 'click' });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,61 +1,64 @@
|
|||
.uploadArea {
|
||||
border: 3px dashed rgba(0, 148, 251, 0.5);
|
||||
margin: 0 auto 10px;
|
||||
height: 255px;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
border: 1px dashed rgba(12, 12, 13, 0.2);
|
||||
margin: 0 0 10px;
|
||||
height: 400px;
|
||||
border-radius: 4px;
|
||||
overflow: scroll;
|
||||
transition: transform 150ms;
|
||||
padding: 15px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.uploadArea__msg {
|
||||
font-size: 22px;
|
||||
font-size: 15px;
|
||||
color: var(--lightTextColor);
|
||||
margin: 20px 0 10px;
|
||||
margin: 12px 0 0;
|
||||
font-family: 'SF Pro Text', sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.uploadArea__sizeMsg {
|
||||
.uploadArea__clickMsg {
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
line-height: 12px;
|
||||
color: var(--lightTextColor);
|
||||
margin-bottom: 22px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.uploadArea--dragging {
|
||||
border: 5px dashed rgba(0, 148, 251, 0.5);
|
||||
height: 251px;
|
||||
border: 1px dashed rgba(12, 12, 13, 0.4);
|
||||
transform: scale(1.04);
|
||||
border-radius: 4.2px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.uploadArea--dragging * {
|
||||
.uploadArea--faded * {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.uploadArea--noEvents,
|
||||
.uploadArea--noEvents * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.btn--file {
|
||||
font-size: 20px;
|
||||
min-width: 240px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
display: inline-block;
|
||||
background-color: #737373;
|
||||
}
|
||||
|
||||
.btn--file:hover {
|
||||
background-color: #636363;
|
||||
}
|
||||
|
||||
.btn--hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.inputFile {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.inputFile--focused + .btn--file {
|
||||
|
@ -63,3 +66,24 @@
|
|||
outline: 1px dotted #000;
|
||||
outline: -webkit-focus-ring-color auto 5px;
|
||||
}
|
||||
|
||||
.uploadArea > .uploadedFiles {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
flex: none;
|
||||
width: 100%;
|
||||
border: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.uploadOptions {
|
||||
text-align: left;
|
||||
font-size: 13px;
|
||||
color: var(--lightTextColor);
|
||||
}
|
||||
|
||||
.uploadOptions--faded {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
@ -1,60 +1,24 @@
|
|||
const preview = require('../pages/preview');
|
||||
const download = require('../pages/download');
|
||||
const notFound = require('../pages/notFound');
|
||||
const downloadPassword = require('../templates/downloadPassword');
|
||||
const downloadButton = require('../templates/downloadButton');
|
||||
|
||||
function hasFileInfo() {
|
||||
return !!document.getElementById('dl-file');
|
||||
}
|
||||
|
||||
function getFileInfoFromDOM() {
|
||||
const el = document.getElementById('dl-file');
|
||||
if (!el) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
nonce: el.getAttribute('data-nonce'),
|
||||
requiresPassword: !!+el.getAttribute('data-requires-password')
|
||||
};
|
||||
}
|
||||
|
||||
function createFileInfo(state) {
|
||||
const metadata = getFileInfoFromDOM();
|
||||
return {
|
||||
id: state.params.id,
|
||||
secretKey: state.params.key,
|
||||
nonce: metadata.nonce,
|
||||
requiresPassword: metadata.requiresPassword
|
||||
};
|
||||
}
|
||||
const password = require('../pages/password');
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
if (!state.fileInfo) {
|
||||
// This is a fresh page load
|
||||
// We need to parse the file info from the server's html
|
||||
if (!hasFileInfo()) {
|
||||
return notFound(state, emit);
|
||||
}
|
||||
state.fileInfo = createFileInfo(state);
|
||||
|
||||
if (!state.fileInfo.requiresPassword) {
|
||||
emit('getMetadata');
|
||||
}
|
||||
emit('getPasswordExist', { id: state.params.id });
|
||||
return;
|
||||
}
|
||||
|
||||
state.fileInfo.id = state.params.id;
|
||||
state.fileInfo.secretKey = state.params.key;
|
||||
|
||||
if (!state.transfer && !state.fileInfo.requiresPassword) {
|
||||
emit('getMetadata');
|
||||
}
|
||||
|
||||
let pageAction = null; //default state: we don't have file metadata
|
||||
if (state.transfer) {
|
||||
const s = state.transfer.state;
|
||||
if (['downloading', 'decrypting', 'complete'].indexOf(s) > -1) {
|
||||
// Downloading is in progress
|
||||
return download(state, emit);
|
||||
}
|
||||
// we have file metadata
|
||||
pageAction = downloadButton(state, emit);
|
||||
} else if (state.fileInfo.requiresPassword && !state.fileInfo.password) {
|
||||
// we're waiting on the user for a valid password
|
||||
pageAction = downloadPassword(state, emit);
|
||||
return preview(state, emit);
|
||||
}
|
||||
|
||||
if (state.fileInfo.requiresPassword && !state.fileInfo.password) {
|
||||
return password(state, emit);
|
||||
}
|
||||
return preview(state, pageAction);
|
||||
};
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
const welcome = require('../pages/welcome');
|
||||
const upload = require('../pages/upload');
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
if (state.uploading) {
|
||||
return upload(state, emit);
|
||||
}
|
||||
return welcome(state, emit);
|
||||
};
|
||||
|
|
|
@ -5,7 +5,10 @@ const download = require('./download');
|
|||
const header = require('../templates/header');
|
||||
const footer = require('../templates/footer');
|
||||
const fxPromo = require('../templates/fxPromo');
|
||||
const signupPromo = require('../templates/signupPromo');
|
||||
const activeBackground = require('../templates/activeBackground');
|
||||
const fileList = require('../templates/fileList');
|
||||
const profile = require('../templates/userAccount');
|
||||
|
||||
nanotiming.disabled = true;
|
||||
const app = choo();
|
||||
|
@ -20,6 +23,7 @@ function body(template) {
|
|||
return function(state, emit) {
|
||||
const b = html`<body class="background ${activeBackground(state)}">
|
||||
${banner(state, emit)}
|
||||
${signupPromo(state)}
|
||||
${header(state)}
|
||||
<main class="main">
|
||||
<noscript>
|
||||
|
@ -35,11 +39,17 @@ function body(template) {
|
|||
</noscript>
|
||||
<div class="stripedBox">
|
||||
<div class="mainContent">
|
||||
|
||||
${profile(state)}
|
||||
|
||||
${template(state, emit)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spacer"></div>
|
||||
<div class="uploads"></div>
|
||||
<div class="uploads">
|
||||
${fileList(state)}
|
||||
</div>
|
||||
</main>
|
||||
${footer(state)}
|
||||
</body>`;
|
||||
|
@ -55,11 +65,11 @@ app.route('/', body(require('./home')));
|
|||
app.route('/share/:id', body(require('../pages/share')));
|
||||
app.route('/download/:id', body(download));
|
||||
app.route('/download/:id/:key', body(download));
|
||||
app.route('/completed', body(require('../pages/completed')));
|
||||
app.route('/unsupported/:reason', body(require('../pages/unsupported')));
|
||||
app.route('/legal', body(require('../pages/legal')));
|
||||
app.route('/error', body(require('../pages/error')));
|
||||
app.route('/blank', body(require('../pages/blank')));
|
||||
app.route('*', body(require('../pages/notFound')));
|
||||
app.route('/signin', body(require('../pages/signin')));
|
||||
|
||||
module.exports = app;
|
||||
|
|
|
@ -47,6 +47,7 @@ class Storage {
|
|||
if (!f.id) {
|
||||
f.id = f.fileId;
|
||||
}
|
||||
|
||||
fs.push(f);
|
||||
} catch (err) {
|
||||
// obviously you're not a golfer
|
||||
|
|
|
@ -1,6 +1,20 @@
|
|||
.btn--download {
|
||||
width: 180px;
|
||||
height: 44px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 30px;
|
||||
margin: 0 0 13px;
|
||||
}
|
||||
|
||||
.btn--complete,
|
||||
.btn--complete:hover {
|
||||
background-color: var(--successControlBGColor);
|
||||
}
|
||||
|
||||
.btn--blueStripes {
|
||||
background: repeating-linear-gradient(
|
||||
-65deg,
|
||||
#3282f2 0,
|
||||
#3282f2 17px,
|
||||
#3c87eb 17px,
|
||||
#3c87eb 30px
|
||||
);
|
||||
background-size: 300% 300%;
|
||||
animation: barberpole 12s linear infinite;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,36 @@
|
|||
const html = require('choo/html');
|
||||
const percent = require('../../utils').percent;
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
const downloadState = state.transfer.state;
|
||||
const progress = percent(state.transfer.progressRatio);
|
||||
|
||||
let btnText = '';
|
||||
let btnClass = '';
|
||||
|
||||
if (downloadState === 'complete') {
|
||||
btnText = state.translate('downloadFinish');
|
||||
btnClass = 'btn--complete';
|
||||
} else if (downloadState === 'decrypting') {
|
||||
btnText = state.translate('decryptingFile');
|
||||
btnClass = 'btn--blueStripes';
|
||||
} else if (downloadState === 'downloading') {
|
||||
btnText = state.translate('downloadProgressButton', { progress });
|
||||
btnClass = 'btn--blueStripes';
|
||||
} else {
|
||||
btnText = state.translate('downloadButtonLabel');
|
||||
}
|
||||
|
||||
return html`
|
||||
<button class="btn btn--download"
|
||||
onclick=${download}>${state.translate('downloadButtonLabel')}
|
||||
<button class="btn btn--download ${btnClass}"
|
||||
onclick=${download}>
|
||||
${btnText}
|
||||
</button>`;
|
||||
|
||||
function download(event) {
|
||||
event.preventDefault();
|
||||
emit('download', state.fileInfo);
|
||||
if (downloadState !== 'complete') {
|
||||
emit('download', state.fileInfo);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,22 +1,31 @@
|
|||
.passwordSection {
|
||||
text-align: left;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.passwordForm {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
margin: 13px;
|
||||
}
|
||||
|
||||
.passwordForm__input {
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
height: 40px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (max-device-width: 520px), (max-width: 520px) {
|
||||
.passwordSection {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.passwordForm {
|
||||
flex-direction: column;
|
||||
}
|
||||
.unlockBtn {
|
||||
margin-top: 48px;
|
||||
}
|
||||
|
||||
.unlockBtn--error,
|
||||
.unlockBtn--error:hover {
|
||||
background-color: var(--errorColor);
|
||||
}
|
||||
|
||||
.passwordForm__error {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
|
|
@ -3,33 +3,33 @@ const html = require('choo/html');
|
|||
module.exports = function(state, emit) {
|
||||
const fileInfo = state.fileInfo;
|
||||
const invalid = fileInfo.password === null;
|
||||
const label = invalid
|
||||
? html`
|
||||
<label class="error" for="password-input">
|
||||
${state.translate('passwordTryAgain')}
|
||||
</label>`
|
||||
: html`
|
||||
<label for="password-input">
|
||||
${state.translate('unlockInputLabel')}
|
||||
</label>`;
|
||||
const inputClass = invalid
|
||||
? 'input input--noBtn input--error'
|
||||
: 'input input--noBtn';
|
||||
|
||||
const visible = invalid ? 'visible' : '';
|
||||
const invalidBtn = invalid ? 'unlockBtn--error' : '';
|
||||
|
||||
const div = html`
|
||||
<div class="passwordSection">
|
||||
${label}
|
||||
|
||||
<label
|
||||
class="error passwordForm__error ${visible}"
|
||||
for="password-input">
|
||||
${state.translate('passwordTryAgain')}
|
||||
</label>
|
||||
|
||||
<form class="passwordForm" onsubmit=${checkPassword} data-no-csrf>
|
||||
<input id="password-input"
|
||||
class="${inputClass}"
|
||||
class="input passwordForm__input"
|
||||
maxlength="64"
|
||||
autocomplete="off"
|
||||
placeholder="${state.translate('unlockInputPlaceholder')}"
|
||||
oninput=${inputChanged}
|
||||
type="password" />
|
||||
|
||||
<input type="submit"
|
||||
id="password-btn"
|
||||
class="inputBtn inputBtn--hidden"
|
||||
value="${state.translate('unlockButtonLabel')}"/>
|
||||
class="btn unlockBtn ${invalidBtn}"
|
||||
value="${state.translate('unlockInputLabel')}"/>
|
||||
|
||||
</form>
|
||||
</div>`;
|
||||
|
||||
|
@ -38,16 +38,10 @@ module.exports = function(state, emit) {
|
|||
}
|
||||
|
||||
function inputChanged() {
|
||||
const input = document.getElementById('password-input');
|
||||
const input = document.querySelector('.passwordForm__error');
|
||||
input.classList.remove('visible');
|
||||
const btn = document.getElementById('password-btn');
|
||||
input.classList.remove('input--error');
|
||||
if (input.value.length > 0) {
|
||||
btn.classList.remove('inputBtn--hidden');
|
||||
input.classList.remove('input--noBtn');
|
||||
} else {
|
||||
btn.classList.add('inputBtn--hidden');
|
||||
input.classList.add('input--noBtn');
|
||||
}
|
||||
btn.classList.remove('unlockBtn--error');
|
||||
}
|
||||
|
||||
function checkPassword(event) {
|
||||
|
|
36
app/templates/expireInfo/index.js
Normal file
36
app/templates/expireInfo/index.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
const html = require('choo/html');
|
||||
const raw = require('choo/html/raw');
|
||||
const selectbox = require('../selectbox');
|
||||
|
||||
module.exports = function(state) {
|
||||
const el = html`<div> ${raw(
|
||||
state.translate('frontPageExpireInfo', {
|
||||
downloadCount: '<select id=dlCount></select>',
|
||||
timespan: state.translate('timespanHours', { num: 24 }) //'<select id=timespan></select>'
|
||||
})
|
||||
)}
|
||||
</div>`;
|
||||
|
||||
const dlCountSelect = el.querySelector('#dlCount');
|
||||
el.replaceChild(
|
||||
selectbox(
|
||||
state.downloadCount || 1,
|
||||
[1, 2, 3, 4, 5, 20],
|
||||
num => state.translate('downloadCount', { num }),
|
||||
value => {
|
||||
state.downloadCount = value;
|
||||
}
|
||||
),
|
||||
dlCountSelect
|
||||
);
|
||||
|
||||
/*
|
||||
const timeSelect = el.querySelector('#timespan');
|
||||
el.replaceChild(
|
||||
selectbox(1, [1, 2, 3, 4, 5], num => num, () => {}),
|
||||
timeSelect
|
||||
);
|
||||
*/
|
||||
|
||||
return el;
|
||||
};
|
|
@ -1,26 +1,94 @@
|
|||
.fileData {
|
||||
font-size: 15px;
|
||||
vertical-align: top;
|
||||
color: var(--lightTextColor);
|
||||
padding: 17px 19px 0;
|
||||
line-height: 23px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fileData--overflow {
|
||||
text-overflow: ellipsis;
|
||||
max-width: 0;
|
||||
.fileToast {
|
||||
margin: 13px 0 0;
|
||||
overflow: hidden;
|
||||
font-size: 11px;
|
||||
line-height: 18px;
|
||||
color: var(--lightTextColor);
|
||||
background-color: var(--pageBGColor);
|
||||
position: relative;
|
||||
box-shadow: 0 0 0 3px rgba(12, 12, 12, 0.2);
|
||||
box-sizing: border-box;
|
||||
height: 53px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.fileToast__content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.fileToast::after {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
content: '';
|
||||
transition: all 0.25s;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
background-color: var(--primaryControlBGColor);
|
||||
}
|
||||
|
||||
.fileToast:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.fileToast--active {
|
||||
color: var(--primaryControlFGColor);
|
||||
}
|
||||
|
||||
.fileToast--active::after {
|
||||
left: 0%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fileData {
|
||||
margin: 8px 16px 8px 44px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fileName {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fileData--center {
|
||||
text-align: center;
|
||||
.fileInfo {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-device-width: 520px), (max-width: 520px) {
|
||||
.fileToast .fileIcon {
|
||||
margin: 2px 8px;
|
||||
}
|
||||
|
||||
@media (max-device-width: 750px), (max-width: 750px) {
|
||||
.fileToast {
|
||||
height: 32px;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.fileToast__content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.fileData {
|
||||
font-size: 13px;
|
||||
padding: 17px 5px 0;
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.fileInfo {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.fileToast .fileIcon {
|
||||
margin: 0;
|
||||
transform: scale(0.5);
|
||||
color: transparent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,73 +1,50 @@
|
|||
const html = require('choo/html');
|
||||
const assets = require('../../../common/assets');
|
||||
const number = require('../../utils').number;
|
||||
const deletePopup = require('../popup');
|
||||
const bytes = require('../../utils').bytes;
|
||||
const fileIcon = require('../fileIcon');
|
||||
|
||||
module.exports = function(file, state, emit) {
|
||||
module.exports = function(file, state) {
|
||||
const ttl = file.expiresAt - Date.now();
|
||||
const remainingTime =
|
||||
timeLeft(ttl, state) || state.translate('linkExpiredAlt');
|
||||
const downloadLimit = file.dlimit || 1;
|
||||
const totalDownloads = file.dtotal || 0;
|
||||
|
||||
const multiFiles = file.manifest.files;
|
||||
const fileName =
|
||||
multiFiles.length > 1
|
||||
? `${multiFiles[0].name} + ${state.translate('fileCount', {
|
||||
num: multiFiles.length - 1
|
||||
})}`
|
||||
: file.name;
|
||||
|
||||
const activeClass = isOnSharePage() ? 'fileToast--active' : '';
|
||||
return html`
|
||||
<tr id="${file.id}">
|
||||
<td class="fileData fileData--overflow" title="${file.name}">
|
||||
<a class="link" href="/share/${file.id}">${file.name}</a>
|
||||
</td>
|
||||
<td class="fileData fileData--center">
|
||||
<img
|
||||
onclick=${copyClick}
|
||||
src="${assets.get('copy-16.svg')}"
|
||||
class="cursor--pointer"
|
||||
title="${state.translate('copyUrlHover')}"
|
||||
tabindex="0">
|
||||
<span hidden="true">
|
||||
${state.translate('copiedUrl')}
|
||||
</span>
|
||||
</td>
|
||||
<td class="fileData fileData--overflow">${remainingTime}</td>
|
||||
<td class="fileData fileData--center">${number(totalDownloads)} / ${number(
|
||||
downloadLimit
|
||||
)}</td>
|
||||
<td class="fileData fileData--center">
|
||||
<img
|
||||
onclick=${showPopup}
|
||||
src="${assets.get('close-16.svg')}"
|
||||
class="cursor--pointer"
|
||||
title="${state.translate('deleteButtonHover')}"
|
||||
tabindex="0">
|
||||
${deletePopup(
|
||||
state.translate('deletePopupText'),
|
||||
state.translate('deletePopupYes'),
|
||||
state.translate('deletePopupCancel'),
|
||||
deleteFile
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<a href=${toastClick()}>
|
||||
<li class="fileToast ${activeClass}" id="${file.id}">
|
||||
<div class="fileToast__content">
|
||||
${fileIcon(file.name, file._hasPassword)}
|
||||
<div class="fileData">
|
||||
<p class="fileName">${fileName}</p>
|
||||
<p class="fileInfo">
|
||||
<span>${bytes(file.size)}</span> ·
|
||||
<span>${state.translate('downloadCount', {
|
||||
num: `${number(totalDownloads)} / ${number(downloadLimit)}`
|
||||
})}</span>
|
||||
<span>${remainingTime}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</a>
|
||||
`;
|
||||
|
||||
function copyClick(e) {
|
||||
emit('copy', { url: file.url, location: 'upload-list' });
|
||||
const icon = e.target;
|
||||
const text = e.target.nextSibling;
|
||||
icon.hidden = true;
|
||||
text.hidden = false;
|
||||
setTimeout(() => {
|
||||
icon.hidden = false;
|
||||
text.hidden = true;
|
||||
}, 500);
|
||||
function toastClick() {
|
||||
return isOnSharePage() ? '/' : `/share/${file.id}`;
|
||||
}
|
||||
|
||||
function showPopup() {
|
||||
const tr = document.getElementById(file.id);
|
||||
const popup = tr.querySelector('.popup');
|
||||
popup.classList.add('popup--show');
|
||||
popup.focus();
|
||||
}
|
||||
|
||||
function deleteFile() {
|
||||
emit('delete', { file, location: 'upload-list' });
|
||||
emit('render');
|
||||
function isOnSharePage() {
|
||||
return state.href.includes('/share/') && state.params.id === file.id;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
28
app/templates/fileIcon/fileIcon.css
Normal file
28
app/templates/fileIcon/fileIcon.css
Normal file
|
@ -0,0 +1,28 @@
|
|||
.fileIcon {
|
||||
position: relative;
|
||||
float: left;
|
||||
pointer-events: none;
|
||||
margin: 8px;
|
||||
color: #fff;
|
||||
background-image: url('../assets/red_file.svg');
|
||||
width: 22px;
|
||||
height: 32px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fileIcon__lock {
|
||||
margin: 7px 0 0 5px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.fileIcon__lock--visible {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.fileIcon__fileType {
|
||||
position: absolute;
|
||||
margin: 16px 0 0 2px;
|
||||
font-size: 7px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
17
app/templates/fileIcon/index.js
Normal file
17
app/templates/fileIcon/index.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
const html = require('choo/html');
|
||||
const assets = require('../../../common/assets');
|
||||
|
||||
module.exports = function(name, hasPassword) {
|
||||
let type = '';
|
||||
if (name) {
|
||||
type = name.split('.').pop();
|
||||
}
|
||||
const lockClass = hasPassword ? 'fileIcon__lock--visible' : '';
|
||||
return html`
|
||||
<div class="fileIcon">
|
||||
<div class="fileIcon__fileType">${type}</div>
|
||||
<img class="fileIcon__lock ${lockClass}"src="${assets.get(
|
||||
'lock-white.svg'
|
||||
)}"/>
|
||||
</div>`;
|
||||
};
|
|
@ -1,52 +1,21 @@
|
|||
.fileList {
|
||||
margin: 45.3px auto;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 3px;
|
||||
font-family: 'Segoe UI', 'SF Pro Text', sans-serif;
|
||||
width: 262px;
|
||||
max-height: 80%;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.fileList__header {
|
||||
font-size: 16px;
|
||||
color: var(--lightTextColor);
|
||||
font-weight: lighter;
|
||||
text-align: left;
|
||||
background: rgba(0, 148, 251, 0.05);
|
||||
height: 40px;
|
||||
border-top: 1px solid rgba(0, 148, 251, 0.1);
|
||||
padding: 0 19px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fileList__body {
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.fileList__nameCol {
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
.fileList__copyCol {
|
||||
text-align: center;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.fileList__expireCol {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.fileList__dlCol {
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
.fileList__delCol {
|
||||
text-align: center;
|
||||
width: 7%;
|
||||
}
|
||||
|
||||
@media (max-device-width: 520px), (max-width: 520px) {
|
||||
.fileList__header {
|
||||
font-size: 14px;
|
||||
padding: 0 5px;
|
||||
@media (max-device-width: 750px), (max-width: 750px) {
|
||||
.fileList {
|
||||
position: static;
|
||||
width: 400px;
|
||||
max-height: 200px;
|
||||
margin: 6px 0 0 -3px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +1,12 @@
|
|||
const html = require('choo/html');
|
||||
const file = require('../file');
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
module.exports = function(state) {
|
||||
if (state.storage.files.length) {
|
||||
return html`
|
||||
<table class="fileList">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="fileList__header fileList__nameCol">
|
||||
${state.translate('uploadedFile')}
|
||||
</th>
|
||||
<th class="fileList__header fileList__copyCol">
|
||||
${state.translate('copyFileList')}
|
||||
</th>
|
||||
<th class="fileList__header fileList__expireCol" >
|
||||
${state.translate('timeFileList')}
|
||||
</th>
|
||||
<th class="fileList__header fileList__dlCol" >
|
||||
${state.translate('downloadsFileList')}
|
||||
</th>
|
||||
<th class="fileList__header fileList__delCol">
|
||||
${state.translate('deleteFileList')}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fileList__body">
|
||||
${state.storage.files.map(f => file(f, state, emit))}
|
||||
</tbody>
|
||||
</table>
|
||||
<ul class="fileList">
|
||||
${state.storage.files.map(f => file(f, state))}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
bottom: 0;
|
||||
left: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 50px 31px 41px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.legalSection {
|
||||
|
@ -20,18 +21,18 @@
|
|||
}
|
||||
|
||||
.legalSection__link {
|
||||
color: var(--lightTextColor);
|
||||
opacity: 0.9;
|
||||
color: #fff;
|
||||
text-shadow: 0 0 3px #000;
|
||||
white-space: nowrap;
|
||||
margin-right: 2vw;
|
||||
}
|
||||
|
||||
.legalSection__link:hover {
|
||||
opacity: 1;
|
||||
.legalSection__link:visited {
|
||||
color: #ededf0;
|
||||
}
|
||||
|
||||
.legalSection__link:visited {
|
||||
color: var(--lightTextColor);
|
||||
.legalSection__link:hover {
|
||||
color: #d7d7db;
|
||||
}
|
||||
|
||||
.legalSection__mozLogo {
|
||||
|
@ -60,12 +61,12 @@
|
|||
margin-bottom: -5px;
|
||||
}
|
||||
|
||||
@media (max-device-width: 768px), (max-width: 768px) {
|
||||
@media (max-device-width: 750px), (max-width: 750px) {
|
||||
.footer {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
max-width: 630px;
|
||||
padding: 20px 31px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,8 +38,6 @@ module.exports = function(state) {
|
|||
class="legalSection__link">
|
||||
${state.translate('reportIPInfringement')}
|
||||
</a>
|
||||
</div>
|
||||
<div class="socialSection">
|
||||
<a
|
||||
href="https://github.com/mozilla/send"
|
||||
class="socialSection__link">
|
||||
|
@ -52,9 +50,9 @@ module.exports = function(state) {
|
|||
href="https://twitter.com/FxTestPilot"
|
||||
class="socialSection__link">
|
||||
<img
|
||||
class="socialSection__icon"
|
||||
src="${assets.get('twitter-icon.svg')}"
|
||||
alt="twitter"/>
|
||||
class="legalSection__mozLogo"
|
||||
src="${assets.get('mozilla-logo.svg')}"
|
||||
alt="mozilla"/>
|
||||
</a>
|
||||
</div>
|
||||
</footer>`;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 31px;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@
|
|||
color: var(--primaryControlFGColor);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
float: right;
|
||||
float: left;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
opacity: 0.9;
|
||||
|
@ -88,17 +88,10 @@
|
|||
background-color: var(--primaryControlHoverColor);
|
||||
}
|
||||
|
||||
@media (max-device-width: 520px), (max-width: 520px) {
|
||||
@media (max-device-width: 750px), (max-width: 750px) {
|
||||
.header {
|
||||
padding-top: 60px;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.feedback {
|
||||
margin-top: 10px;
|
||||
min-width: 30px;
|
||||
max-width: 300px;
|
||||
text-indent: 2px;
|
||||
padding: 5px 5px 5px 20px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,6 @@ module.exports = function(state) {
|
|||
const feedbackUrl = `https://qsurvey.mozilla.com/s3/txp-firefox-send?ver=${version}&browser=${browser}`;
|
||||
const header = html`
|
||||
<header class="header">
|
||||
<div class="logo">
|
||||
</div>
|
||||
<a href="${feedbackUrl}"
|
||||
rel="noreferrer noopener"
|
||||
class="feedback"
|
||||
|
|
|
@ -1,76 +1,39 @@
|
|||
const html = require('choo/html');
|
||||
const MAX_LENGTH = 32;
|
||||
|
||||
module.exports = function(file, state, emit) {
|
||||
const loading = state.settingPassword;
|
||||
const pwd = file.hasPassword;
|
||||
const sectionClass =
|
||||
pwd || state.passwordSetError
|
||||
? 'passwordInput'
|
||||
: 'passwordInput passwordInput--hidden';
|
||||
const inputClass = loading || pwd ? 'input' : 'input input--noBtn';
|
||||
let btnClass = 'inputBtn inputBtn--password inputBtn--hidden';
|
||||
if (loading) {
|
||||
btnClass = 'inputBtn inputBtn--password inputBtn--loading';
|
||||
} else if (pwd) {
|
||||
btnClass = 'inputBtn inputBtn--password';
|
||||
}
|
||||
const action = pwd
|
||||
? state.translate('changePasswordButton')
|
||||
: state.translate('addPasswordButton');
|
||||
module.exports = function(state) {
|
||||
const placeholder =
|
||||
state.route === '/' ? '' : state.translate('unlockInputPlaceholder');
|
||||
const hasPassword = !!state.password;
|
||||
const sectionClass = hasPassword
|
||||
? 'passwordInput'
|
||||
: 'passwordInput passwordInput--hidden';
|
||||
|
||||
return html`
|
||||
<div class="${sectionClass}">
|
||||
<form
|
||||
class="passwordInput__form"
|
||||
onsubmit=${setPassword}
|
||||
onsubmit=${onSubmit}
|
||||
data-no-csrf>
|
||||
|
||||
<input id="password-input"
|
||||
${loading ? 'disabled' : ''}
|
||||
class="${inputClass}"
|
||||
maxlength="${MAX_LENGTH}"
|
||||
class="input passwordInput__fill"
|
||||
autocomplete="off"
|
||||
type="password"
|
||||
oninput=${inputChanged}
|
||||
onfocus=${focused}
|
||||
placeholder="${
|
||||
pwd && !state.passwordSetError
|
||||
? passwordPlaceholder(file.password)
|
||||
: state.translate('unlockInputPlaceholder')
|
||||
}">
|
||||
<input type="submit"
|
||||
id="password-btn"
|
||||
${loading ? 'disabled' : ''}
|
||||
class="${btnClass}"
|
||||
value="${loading ? '' : action}">
|
||||
hasPassword ? passwordPlaceholder(state.password) : placeholder
|
||||
}"
|
||||
>
|
||||
</form>
|
||||
<label
|
||||
class="passwordInput__msg ${
|
||||
state.passwordSetError ? 'passwordInput__msg--error' : ''
|
||||
}"
|
||||
for="password-input">${message(state, pwd)}</label>
|
||||
</div>`;
|
||||
|
||||
function inputChanged() {
|
||||
state.passwordSetError = null;
|
||||
const resetInput = document.getElementById('password-input');
|
||||
const resetBtn = document.getElementById('password-btn');
|
||||
const pwdmsg = document.querySelector('.passwordInput__msg');
|
||||
const length = resetInput.value.length;
|
||||
function onSubmit() {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (length === MAX_LENGTH) {
|
||||
pwdmsg.textContent = state.translate('maxPasswordLength', {
|
||||
length: MAX_LENGTH
|
||||
});
|
||||
} else {
|
||||
pwdmsg.textContent = '';
|
||||
}
|
||||
if (length > 0) {
|
||||
resetBtn.classList.remove('inputBtn--hidden');
|
||||
resetInput.classList.remove('input--noBtn');
|
||||
} else {
|
||||
resetBtn.classList.add('inputBtn--hidden');
|
||||
resetInput.classList.add('input--noBtn');
|
||||
}
|
||||
function inputChanged() {
|
||||
const password = document.getElementById('password-input').value;
|
||||
state.password = password;
|
||||
}
|
||||
|
||||
function focused(event) {
|
||||
|
@ -80,30 +43,8 @@ module.exports = function(file, state, emit) {
|
|||
el.placeholder = '';
|
||||
}
|
||||
}
|
||||
|
||||
function setPassword(event) {
|
||||
event.preventDefault();
|
||||
const el = document.getElementById('password-input');
|
||||
const password = el.value;
|
||||
if (password.length > 0) {
|
||||
emit('password', { password, file });
|
||||
} else {
|
||||
el.focus();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
function passwordPlaceholder(password) {
|
||||
return password ? password.replace(/./g, '●') : '●●●●●●●●●●●●';
|
||||
}
|
||||
|
||||
function message(state, pwd) {
|
||||
if (state.passwordSetError) {
|
||||
return state.translate('passwordSetError');
|
||||
}
|
||||
if (state.settingPassword || !pwd) {
|
||||
return '';
|
||||
}
|
||||
return state.translate('passwordIsSet');
|
||||
return password ? password.replace(/./g, '•') : '••••••••••••';
|
||||
}
|
||||
|
|
|
@ -1,41 +1,31 @@
|
|||
.passwordInput {
|
||||
width: 90%;
|
||||
height: 100px;
|
||||
padding: 10px 5px 5px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.passwordInput--hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.passwordInput__form {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
padding-bottom: 5px;
|
||||
.passwordInput__fill {
|
||||
height: 24px;
|
||||
box-sizing: border-box;
|
||||
padding: 4px;
|
||||
font-size: 18px;
|
||||
border: none;
|
||||
background-color: var(--lightControlBGColor);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.passwordInput__fill:focus {
|
||||
border: 1px solid rgba(12, 12, 13, 0.2);
|
||||
background-color: var(--pageBGColor);
|
||||
}
|
||||
|
||||
.passwordInput__msg {
|
||||
font-size: 15px;
|
||||
font-size: 12px;
|
||||
color: var(--lightTextColor);
|
||||
}
|
||||
|
||||
.passwordInput__msg--error {
|
||||
color: var(--errorColor);
|
||||
}
|
||||
|
||||
.inputBtn--loading {
|
||||
background-image: url('../assets/spinner.svg');
|
||||
background-position: center;
|
||||
background-size: 30px 30px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.inputBtn--password {
|
||||
flex: 0 0 200px;
|
||||
}
|
||||
|
||||
@media (max-device-width: 520px), (max-width: 520px) {
|
||||
.passwordInput__form {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,19 +2,17 @@ const html = require('choo/html');
|
|||
|
||||
module.exports = function(msg, confirmText, cancelText, confirmCallback) {
|
||||
return html`
|
||||
<div class="popup__wrapper">
|
||||
<div class="popup" onblur=${hide} tabindex="-1">
|
||||
<div class="popup__message">${msg}</div>
|
||||
<div class="popup__action">
|
||||
<span class="popup__no" onclick=${hide}>
|
||||
${cancelText}
|
||||
</span>
|
||||
<span class="popup__yes" onclick=${confirmCallback}>
|
||||
${confirmText}
|
||||
</span>
|
||||
<div>
|
||||
<span class="popup__no" onclick=${hide}>${cancelText}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="popup__yes" onclick=${confirmCallback}>${confirmText}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
</div>`;
|
||||
|
||||
function hide(e) {
|
||||
e.stopPropagation();
|
||||
|
|
|
@ -1,122 +1,79 @@
|
|||
.popup {
|
||||
visibility: hidden;
|
||||
min-width: 204px;
|
||||
min-height: 105px;
|
||||
background-color: var(--pageBGColor);
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
background-color: var(--errorColor);
|
||||
color: var(--textColor);
|
||||
border: 1px solid #d7d7db;
|
||||
padding: 15px 24px;
|
||||
box-sizing: content-box;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 20px;
|
||||
left: -40px;
|
||||
border-radius: 4px;
|
||||
transition: opacity 0.5s;
|
||||
opacity: 0;
|
||||
outline: 0;
|
||||
box-shadow: 3px 3px 7px rgba(136, 136, 136, 0.3);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.popup::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -11px;
|
||||
left: 20px;
|
||||
background-color: #fff;
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transform: rotate(45deg);
|
||||
border-radius: 0 0 5px;
|
||||
border-right: 1px solid #d7d7db;
|
||||
border-bottom: 1px solid #d7d7db;
|
||||
border-left: 1px solid #fff;
|
||||
border-top: 1px solid #fff;
|
||||
}
|
||||
|
||||
.popup__wrapper {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 8px solid;
|
||||
border-color: var(--errorColor) transparent transparent;
|
||||
margin-left: -8px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.popup__message {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-bottom: 1px #ebebeb solid;
|
||||
color: var(--textColor);
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
color: var(--primaryControlFGColor);
|
||||
font-size: 15px;
|
||||
font-weight: normal;
|
||||
padding-bottom: 15px;
|
||||
font-style: italic;
|
||||
white-space: nowrap;
|
||||
width: calc(100% + 48px);
|
||||
margin-left: -24px;
|
||||
}
|
||||
|
||||
.popup__action {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.popup__action > div {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.popup__no {
|
||||
color: #4a4a4a;
|
||||
background-color: #fbfbfb;
|
||||
border: 1px #c1c1c1 solid;
|
||||
border-radius: 5px;
|
||||
padding: 5px 25px;
|
||||
font-weight: normal;
|
||||
min-width: 94px;
|
||||
box-sizing: border-box;
|
||||
color: var(--primaryControlFGColor);
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.popup__no:hover {
|
||||
background-color: #efeff1;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.popup__yes {
|
||||
color: var(--primaryControlFGColor);
|
||||
background-color: var(--primaryControlBGColor);
|
||||
border-radius: 5px;
|
||||
padding: 5px 25px;
|
||||
padding: 5px;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
min-width: 94px;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.popup__yes:hover {
|
||||
background-color: var(--primaryControlHoverColor);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.popup--show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (max-device-width: 992px), (max-width: 992px) {
|
||||
.popup {
|
||||
left: auto;
|
||||
right: -40px;
|
||||
}
|
||||
|
||||
.popup::after {
|
||||
left: auto;
|
||||
right: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-device-width: 520px), (max-width: 520px) {
|
||||
.popup::after {
|
||||
left: 125px;
|
||||
}
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
const html = require('choo/html');
|
||||
const percent = require('../../utils').percent;
|
||||
|
||||
const radius = 73;
|
||||
const oRadius = radius + 10;
|
||||
const oDiameter = oRadius * 2;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
|
||||
module.exports = function(progressRatio, indefinite = false) {
|
||||
// HACK - never indefinite for MS Edge
|
||||
if (/edge/i.test(navigator.userAgent)) {
|
||||
indefinite = false;
|
||||
}
|
||||
const p = indefinite ? 0.2 : progressRatio;
|
||||
const dashOffset = (1 - p) * circumference;
|
||||
const progressPercent = html`
|
||||
<text class="progress__percent" text-anchor="middle" x="50%" y="98">
|
||||
${percent(progressRatio)}
|
||||
</text>`;
|
||||
|
||||
return html`
|
||||
<div class="progress">
|
||||
<svg
|
||||
width="${oDiameter}"
|
||||
height="${oDiameter}"
|
||||
viewPort="0 0 ${oDiameter} ${oDiameter}"
|
||||
version="1.1">
|
||||
<circle
|
||||
class="progress__bg"
|
||||
r="${radius}"
|
||||
cx="${oRadius}"
|
||||
cy="${oRadius}"
|
||||
fill="transparent"/>
|
||||
<circle
|
||||
class="progress__indefinite ${indefinite ? '' : 'progress--invisible'}"
|
||||
r="${radius}"
|
||||
cx="${oRadius}"
|
||||
cy="${oRadius}"
|
||||
fill="transparent"
|
||||
transform="rotate(-90 ${oRadius} ${oRadius})"
|
||||
stroke-dasharray="${circumference}"
|
||||
stroke-dashoffset="${dashOffset}"/>
|
||||
<circle
|
||||
class="progress__bar ${indefinite ? 'progress--invisible' : ''}"
|
||||
r="${radius}"
|
||||
cx="${oRadius}"
|
||||
cy="${oRadius}"
|
||||
fill="transparent"
|
||||
transform="rotate(-90 ${oRadius} ${oRadius})"
|
||||
stroke-dasharray="${circumference}"
|
||||
stroke-dashoffset="${dashOffset}"/>
|
||||
${indefinite ? '' : progressPercent}
|
||||
</svg>
|
||||
</div>
|
||||
`;
|
||||
};
|
|
@ -1,43 +0,0 @@
|
|||
.progress {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.progress__bg {
|
||||
stroke: #eee;
|
||||
stroke-width: 0.75em;
|
||||
}
|
||||
|
||||
.progress__bar {
|
||||
stroke: #3b9dff;
|
||||
stroke-width: 0.75em;
|
||||
transition: stroke-dashoffset 300ms linear;
|
||||
}
|
||||
|
||||
.progress__indefinite {
|
||||
stroke: #3b9dff;
|
||||
stroke-width: 0.75em;
|
||||
animation: 1s linear infinite spin;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.progress__percent {
|
||||
font-family: 'Segoe UI', 'SF Pro Text', sans-serif;
|
||||
font-size: 43.2px;
|
||||
letter-spacing: -0.78px;
|
||||
line-height: 58px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.progress--invisible {
|
||||
display: none;
|
||||
}
|
|
@ -5,16 +5,14 @@ module.exports = function(selected, options, translate, changed) {
|
|||
let x = selected;
|
||||
|
||||
return html`
|
||||
<div class="select">
|
||||
<select id="${id}" onchange=${choose}>
|
||||
<select class="selectBox" id="${id}" onchange=${choose}>
|
||||
${options.map(
|
||||
i =>
|
||||
html`<option value="${i}" ${
|
||||
i === selected ? 'selected' : ''
|
||||
}>${translate(i)}</option>`
|
||||
)}
|
||||
</select>
|
||||
</div>`;
|
||||
</select>`;
|
||||
|
||||
function choose(event) {
|
||||
const target = event.target;
|
||||
|
|
|
@ -1,46 +1,22 @@
|
|||
.select {
|
||||
background-color: var(--pageBGColor);
|
||||
overflow: hidden;
|
||||
padding: 4px 2px 4px 2px;
|
||||
border: 1px dotted #0094fb88;
|
||||
border-radius: 4px;
|
||||
display: inline;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.select::after {
|
||||
color: #0094fb;
|
||||
content: '\25BC';
|
||||
pointer-events: none;
|
||||
font-size: 20px;
|
||||
margin-left: -30px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
option {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
select {
|
||||
.selectBox {
|
||||
appearance: none;
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
border: 0;
|
||||
background: #fff;
|
||||
background-image: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background-color: #e6e6e6;
|
||||
font-size: 1em;
|
||||
font-weight: 200;
|
||||
margin: 0;
|
||||
color: #0094fb;
|
||||
padding: 4px 2px 4px 2px;
|
||||
cursor: pointer;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
select:active {
|
||||
background-color: var(--pageBGColor);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
#arrow {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
const html = require('choo/html');
|
||||
const passwordInput = require('../passwordInput');
|
||||
|
||||
module.exports = function(state, emit) {
|
||||
const file = state.storage.getFileById(state.params.id);
|
||||
module.exports = function(state) {
|
||||
const checked = state.password ? 'checked' : '';
|
||||
const label = state.password ? 'addPasswordLabel' : 'addPasswordMessage';
|
||||
|
||||
return html`
|
||||
<div class="setPasswordSection">
|
||||
<div class="checkbox">
|
||||
<input
|
||||
${file.hasPassword ? 'disabled' : ''}
|
||||
${file.hasPassword || state.passwordSetError ? 'checked' : ''}
|
||||
class="checkbox__input"
|
||||
id="add-password"
|
||||
class="checkbox__input" id="add-password"
|
||||
type="checkbox"
|
||||
${checked}
|
||||
autocomplete="off"
|
||||
onchange=${togglePasswordInput}/>
|
||||
<label class="checkbox__label" for="add-password">
|
||||
${state.translate('requirePasswordCheckbox')}
|
||||
${state.translate(label)}
|
||||
</label>
|
||||
</div>
|
||||
${passwordInput(file, state, emit)}
|
||||
|
||||
${passwordInput(state)}
|
||||
|
||||
</div>`;
|
||||
|
||||
function togglePasswordInput(e) {
|
||||
|
@ -28,9 +29,13 @@ module.exports = function(state, emit) {
|
|||
document
|
||||
.querySelector('.passwordInput')
|
||||
.classList.toggle('passwordInput--hidden', !boxChecked);
|
||||
|
||||
const label = document.querySelector('.checkbox__label');
|
||||
if (boxChecked) {
|
||||
label.innerHTML = state.translate('addPasswordLabel');
|
||||
unlockInput.focus();
|
||||
} else {
|
||||
label.innerHTML = state.translate('addPasswordMessage');
|
||||
unlockInput.value = '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
.setPasswordSection {
|
||||
display: flex;
|
||||
padding: 10px 0;
|
||||
max-width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
min-height: 24px;
|
||||
flex: auto;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.checkbox__input {
|
||||
|
@ -14,7 +15,8 @@
|
|||
}
|
||||
|
||||
.checkbox__label {
|
||||
line-height: 23px;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
color: var(--lightTextColor);
|
||||
user-select: none;
|
||||
|
@ -22,27 +24,21 @@
|
|||
|
||||
.checkbox__label::before {
|
||||
content: '';
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-right: 10px;
|
||||
margin-left: 5px;
|
||||
float: left;
|
||||
border: 1px solid rgba(12, 12, 13, 0.3);
|
||||
border-radius: 2px;
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
|
||||
.checkbox__input:focus + .checkbox__label::before,
|
||||
.checkbox:hover .checkbox__label::before {
|
||||
border: 1px solid var(--primaryControlBGColor);
|
||||
}
|
||||
|
||||
.checkbox__input:checked + .checkbox__label {
|
||||
color: var(--textColor);
|
||||
.checkbox__label:hover::before {
|
||||
background-color: #d6d6d6;
|
||||
}
|
||||
|
||||
.checkbox__input:checked + .checkbox__label::before {
|
||||
background-image: url('../assets/check-16-blue.svg');
|
||||
background-position: 2px 1px;
|
||||
background-image: url('../assets/lock.svg');
|
||||
background-position: 2px 2px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.checkbox__input:disabled + .checkbox__label {
|
||||
|
@ -50,20 +46,13 @@
|
|||
}
|
||||
|
||||
.checkbox__input:disabled + .checkbox__label::before {
|
||||
background-image: url('../assets/check-16-blue.svg');
|
||||
background-image: url('../assets/lock.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-size: 26px 26px;
|
||||
border: none;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
@media (max-device-width: 520px), (max-width: 520px) {
|
||||
.setPasswordSection {
|
||||
align-self: center;
|
||||
min-width: 95%;
|
||||
}
|
||||
|
||||
.checkbox__label::before {
|
||||
margin-left: 0;
|
||||
}
|
||||
.setPasswordSection > .passwordInput--hidden {
|
||||
display: none;
|
||||
}
|
||||
|
|
15
app/templates/signupPromo/index.js
Normal file
15
app/templates/signupPromo/index.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
const html = require('choo/html');
|
||||
|
||||
module.exports = function(state) {
|
||||
return html`
|
||||
<div class="signupPromo">
|
||||
<div class="signupPromo__title">${state.translate('signInPromoText')}</div>
|
||||
<div class="signupPromo__info">${state.translate('signInExplanation')}</div>
|
||||
<a href="/signin"
|
||||
class="link signupPromo__link"
|
||||
>
|
||||
${state.translate('signInLearnMore')}
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
};
|
85
app/templates/signupPromo/signupPromo.css
Normal file
85
app/templates/signupPromo/signupPromo.css
Normal file
|
@ -0,0 +1,85 @@
|
|||
.signupPromo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
height: 140px;
|
||||
width: 150px;
|
||||
background: #ffe900;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.signupPromo::before {
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 35px 140px 0;
|
||||
border-color: transparent #ffe900 transparent;
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
}
|
||||
|
||||
.signupPromo::after {
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 150px 35px 0;
|
||||
border-color: transparent #ffe900 transparent;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0%;
|
||||
}
|
||||
|
||||
.signupPromo__title {
|
||||
color: #0a84ff;
|
||||
font-size: 22px;
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.signupPromo__info {
|
||||
color: var(--lightTextColor);
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.signupPromo__link {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.signupPromo__link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-device-width: 750px), (max-width: 750px) {
|
||||
.signupPromo {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.signupPromo::before {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.signupPromo::after {
|
||||
border-width: 15px 50vw 0 50vw;
|
||||
border-color: #ffe900 transparent transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-device-width: 500px), (max-width: 500px) {
|
||||
.signupPromo__link {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.signupPromo {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
11
app/templates/title/index.js
Normal file
11
app/templates/title/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
const html = require('choo/html');
|
||||
|
||||
module.exports = function(state) {
|
||||
return html`
|
||||
<div class="boxTitle">
|
||||
${state.translate('uploadPageHeader')}
|
||||
<div class="boxSubtitle">
|
||||
${state.translate('pageHeaderCredits')}
|
||||
</div>
|
||||
</div>`;
|
||||
};
|
13
app/templates/title/title.css
Normal file
13
app/templates/title/title.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
.boxTitle {
|
||||
font-size: 15px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
margin: 9px 0 19px 0;
|
||||
color: var(--lightTextColor);
|
||||
}
|
||||
|
||||
.boxSubtitle {
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
}
|
64
app/templates/uploadedFile/index.js
Normal file
64
app/templates/uploadedFile/index.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
const html = require('choo/html');
|
||||
const assets = require('../../../common/assets');
|
||||
const bytes = require('../../utils').bytes;
|
||||
const fileIcon = require('../fileIcon');
|
||||
|
||||
module.exports = function(file, state, emit) {
|
||||
const transfer = state.transfer;
|
||||
const transferState = transfer ? transfer.state : null;
|
||||
const transferring = state.uploading || state.downloading;
|
||||
const share = state.route.includes('share/');
|
||||
const complete = share ? 'uploadedFile--completed' : '';
|
||||
|
||||
const cancelVisible =
|
||||
transferring || state.route === '/' ? 'uploadedFile__cancel--visible' : '';
|
||||
|
||||
const stampClass =
|
||||
share || transferState === 'complete' ? 'uploadedFile__stamp--visible' : '';
|
||||
|
||||
function cancel(event) {
|
||||
event.preventDefault();
|
||||
const btn = document.querySelector('.uploadedFile__cancel');
|
||||
btn.disabled = true;
|
||||
if (transferring) {
|
||||
emit('cancel');
|
||||
} else if (state.route === '/') {
|
||||
emit('removeUpload', { file });
|
||||
}
|
||||
}
|
||||
|
||||
//const percent = share ? 100 : Math.floor(progressRatio * 100);
|
||||
/*
|
||||
style="
|
||||
background: linear-gradient(to right,
|
||||
#e8f2fe 0%,
|
||||
#e8f2fe ${percent}%,
|
||||
#fff ${percent}%,
|
||||
#fff 100%);"
|
||||
*/
|
||||
|
||||
return html`
|
||||
<li class="uploadedFile ${complete}" id="${file.id}"
|
||||
>
|
||||
|
||||
${fileIcon(file.name, file._hasPassword)}
|
||||
|
||||
<div class="uploadedFile__cancel ${cancelVisible}"
|
||||
onclick=${cancel}>
|
||||
<img
|
||||
src="${assets.get('close-16.svg')}"
|
||||
alt="cancel"/>
|
||||
</div>
|
||||
|
||||
<div class="uploadedFile__fileData">
|
||||
<p class="uploadedFile__fileName">${file.name}</p>
|
||||
<p class="uploadedFile__fileInfo">
|
||||
<span>${bytes(file.size)}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<img src="${assets.get('sent-done.svg')}"
|
||||
class="uploadedFile__stamp ${stampClass}"/>
|
||||
</li>
|
||||
`;
|
||||
};
|
70
app/templates/uploadedFile/uploadedFile.css
Normal file
70
app/templates/uploadedFile/uploadedFile.css
Normal file
|
@ -0,0 +1,70 @@
|
|||
.uploadedFile {
|
||||
margin: 11px;
|
||||
list-style-type: none;
|
||||
font-size: 11px;
|
||||
line-height: 18px;
|
||||
text-align: initial;
|
||||
color: var(--lightTextColor);
|
||||
background-color: var(--pageBGColor);
|
||||
border: 1px solid #cececf;
|
||||
box-sizing: border-box;
|
||||
height: 53px;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uploadedFile--completed {
|
||||
background-color: #e8f2fe;
|
||||
}
|
||||
|
||||
.uploadedFile__fileData {
|
||||
margin: 8px 16px 8px 44px;
|
||||
}
|
||||
|
||||
.uploadedFile__fileName {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uploadedFile__fileInfo {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.uploadedFile__cancel {
|
||||
float: right;
|
||||
margin: 6px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.uploadedFile:hover .uploadedFile__cancel--visible {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.uploadedFile__stamp {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -8px;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.uploadedFile__stamp--visible {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
animation: stampDown 0.2s linear;
|
||||
}
|
||||
|
||||
@keyframes stampDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
12
app/templates/uploadedFileList/index.js
Normal file
12
app/templates/uploadedFileList/index.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
const html = require('choo/html');
|
||||
const file = require('../uploadedFile');
|
||||
|
||||
module.exports = function(files, state, emit) {
|
||||
//const progressRatio = state.transfer ? state.transfer.progressRatio : 0;
|
||||
|
||||
return html`
|
||||
<ul class="uploadedFiles">
|
||||
${files.map(f => file(f, state, emit))}
|
||||
</ul>
|
||||
`;
|
||||
};
|
10
app/templates/uploadedFileList/uploadedFileList.css
Normal file
10
app/templates/uploadedFileList/uploadedFileList.css
Normal file
|
@ -0,0 +1,10 @@
|
|||
.uploadedFiles {
|
||||
border: 1px solid rgba(12, 12, 13, 0.1);
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
align-content: center;
|
||||
flex: 1;
|
||||
overflow-y: scroll;
|
||||
}
|
24
app/templates/userAccount/index.js
Normal file
24
app/templates/userAccount/index.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
const html = require('choo/html');
|
||||
const assets = require('../../../common/assets');
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
module.exports = function(state) {
|
||||
return html`
|
||||
<div class="account">
|
||||
<img
|
||||
src="${assets.get('user.svg')}"
|
||||
onclick=${onclick}
|
||||
alt="account"/>
|
||||
|
||||
<ul class=account_dropdown>
|
||||
<li class=account_dropdown__item>Placeholder</li>
|
||||
<li class=account_dropdown__item>Placeholder</li>
|
||||
</ul>
|
||||
</div>`;
|
||||
|
||||
function onclick(event) {
|
||||
event.preventDefault();
|
||||
const dropdown = document.querySelector('.account_dropdown');
|
||||
dropdown.classList.toggle('visible');
|
||||
}
|
||||
};
|
33
app/templates/userAccount/userAccount.css
Normal file
33
app/templates/userAccount/userAccount.css
Normal file
|
@ -0,0 +1,33 @@
|
|||
.account {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin: 0 21px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.account_dropdown {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: -10px;
|
||||
width: 150px;
|
||||
list-style-type: none;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
background-color: var(--pageBGColor);
|
||||
box-shadow: 0 5px 12px 0 rgba(0, 0, 0, 0.2);
|
||||
padding: 11px 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.account_dropdown__item {
|
||||
padding: 0 14px;
|
||||
color: var(--lightTextColor);
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.account_dropdown__item:hover {
|
||||
background-color: var(--primaryControlBGColor);
|
||||
color: var(--primaryControlFGColor);
|
||||
}
|
25
app/utils.js
25
app/utils.js
|
@ -1,3 +1,4 @@
|
|||
/* global MAXFILESIZE */
|
||||
const b64 = require('base64-js');
|
||||
|
||||
function arrayToB64(array) {
|
||||
|
@ -128,6 +129,27 @@ function openLinksInNewTab(links, should = true) {
|
|||
return links;
|
||||
}
|
||||
|
||||
function checkSize(files, oldfiles, translate) {
|
||||
function size(arr) {
|
||||
let total = 0;
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
total += arr[i].size;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
const addSize = size(files);
|
||||
if (addSize === 0) {
|
||||
return;
|
||||
}
|
||||
const totalSize = addSize + size(oldfiles);
|
||||
if (totalSize > MAXFILESIZE) {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert(translate('fileTooBig', { size: bytes(MAXFILESIZE) }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fadeOut,
|
||||
delay,
|
||||
|
@ -140,5 +162,6 @@ module.exports = {
|
|||
b64ToArray,
|
||||
loadShim,
|
||||
isFile,
|
||||
openLinksInNewTab
|
||||
openLinksInNewTab,
|
||||
checkSize
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue