hook multifile to ui
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
|
||||
};
|
||||
|
10
assets/addfile.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="33px" height="33px" viewBox="0 0 33 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Shape</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M13.799417,16.627417 L9.213417,21.213417 C8.45547258,21.9981748 8.46631225,23.2455866 9.23777985,24.0170541 C10.0092474,24.7885217 11.2566592,24.7993614 12.041417,24.041417 L16.627417,19.455417 L21.213417,24.041417 C21.9981748,24.7993614 23.2455866,24.7885217 24.0170541,24.0170541 C24.7885217,23.2455866 24.7993614,21.9981748 24.041417,21.213417 L19.455417,16.627417 L24.041417,12.041417 C24.5613302,11.5392681 24.7698422,10.7956612 24.5868113,10.0964024 C24.4037804,9.39714349 23.8576905,8.85105357 23.1584316,8.66802268 C22.4591728,8.4849918 21.7155659,8.69350383 21.213417,9.213417 L16.627417,13.799417 L12.041417,9.213417 C11.2566592,8.45547258 10.0092474,8.46631225 9.23777985,9.23777985 C8.46631225,10.0092474 8.45547258,11.2566592 9.213417,12.041417 L13.799417,16.627417 Z M16.627417,0.627416998 C25.463973,0.627416998 32.627417,7.790861 32.627417,16.627417 C32.627417,25.463973 25.463973,32.627417 16.627417,32.627417 C7.790861,32.627417 0.627416998,25.463973 0.627416998,16.627417 C0.627416998,7.790861 7.790861,0.627416998 16.627417,0.627416998 Z" id="Shape" fill="#737373" transform="translate(16.627417, 16.627417) rotate(45.000000) translate(-16.627417, -16.627417) "></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
4
assets/back-arrow.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M15.707 4.293a1 1 0 0 1 0 1.414L9.414 12l6.293 6.293a1 1 0 1 1-1.414 1.414l-7-7a1 1 0 0 1 0-1.414l7-7a1 1 0 0 1 1.414 0z" fill="#0A84FF" fill-opacity="1"></path></svg>
|
After Width: | Height: | Size: 523 B |
13
assets/blue_file.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="22px" height="32px" viewBox="0 0 22 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Group 8</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" fill-opacity="0.5">
|
||||
<g id="Group-8" fill="#0A84FF">
|
||||
<path d="M2,0 L11.9428571,0 L21.665206,8.66172902 C21.8781869,8.85147558 22,9.12314407 22,9.40838886 L22,30 C22,31.1045695 21.1045695,32 20,32 L2,32 C0.8954305,32 1.3527075e-16,31.1045695 0,30 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z" id="Rectangle-14"></path>
|
||||
<path d="M12,0 L22,9 L15,9 C13.3431458,9 12,7.65685425 12,6 L12,0 Z" id="Triangle"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 917 B |
4
assets/lock-white.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M9 7a3 3 0 1 1 6 0v3H9V7zm-2 3V7a5 5 0 1 1 10 0v3h1a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h1z" fill="#FFFFFF" fill-opacity="1"></path></svg>
|
After Width: | Height: | Size: 520 B |
4
assets/lock.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M9 7a3 3 0 1 1 6 0v3H9V7zm-2 3V7a5 5 0 1 1 10 0v3h1a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h1z" fill="#0A84FF" fill-opacity="1"></path></svg>
|
After Width: | Height: | Size: 520 B |
13
assets/red_file.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="22px" height="32px" viewBox="0 0 22 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Group 8</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" fill-opacity="0.5">
|
||||
<g id="Group-8" fill="#D70022">
|
||||
<path d="M2,0 L11.9428571,0 L21.665206,8.66172902 C21.8781869,8.85147558 22,9.12314407 22,9.40838886 L22,30 C22,31.1045695 21.1045695,32 20,32 L2,32 C0.8954305,32 1.3527075e-16,31.1045695 0,30 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z" id="Rectangle-14"></path>
|
||||
<path d="M12,0 L22,9 L15,9 C13.3431458,9 12,7.65685425 12,6 L12,0 Z" id="Triangle"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 917 B |
19
assets/sent-done.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="118px" height="59px" viewBox="0 0 118 59" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Group</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group" transform="translate(2.000000, -5.000000)">
|
||||
<g id="Group-5" transform="translate(86.841503, 34.526577) rotate(3.000000) translate(-86.841503, -34.526577) translate(54.341503, 2.526577)">
|
||||
<circle id="Oval-2" stroke-opacity="0.5" stroke="#0C0C0D" stroke-width="3" transform="translate(32.499214, 31.954331) rotate(11.000000) translate(-32.499214, -31.954331) " cx="32.4992143" cy="31.9543312" r="27.0650466"></circle>
|
||||
<path d="M28.020207,44.1875925 C27.4974152,44.1874763 26.9960763,43.9712589 26.6264624,43.5864984 L20.7124121,37.431951 C19.9653252,36.6269796 19.9760096,35.3474371 20.736426,34.5560981 C21.4968423,33.7647591 22.726385,33.7536403 23.4999012,34.5311077 L27.7521034,38.9562273 L40.2031506,20.4454003 C40.831247,19.5292815 42.051282,19.3107606 42.9375457,19.9556419 C43.8238095,20.6005231 44.0454042,21.868032 43.4341935,22.7964374 L29.6347427,43.3115953 C29.3022783,43.8117461 28.7737147,44.1326132 28.1917144,44.1875925 C28.1345816,44.1905933 28.0773398,44.1905933 28.020207,44.1875925 Z" id="Shape" fill="#12BC00" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
<path d="M0,19.8728801 C3.80326861,19.2140466 7.96993528,19.2140466 12.5,19.8728801 C19.2950971,20.8611303 28.3355757,25.3728801 35,25.3728801 C41.6644243,25.3728801 47.8955852,19.5814393 55.5,18.3728801 C60.5696098,17.5671739 65.4029432,18.0671739 70,19.8728801" id="Line" stroke-opacity="0.5" stroke="#0C0C0D" stroke-width="3" stroke-linecap="square"></path>
|
||||
<path d="M0,29.8728801 C3.80326861,29.2140466 7.96993528,29.2140466 12.5,29.8728801 C19.2950971,30.8611303 28.3355757,35.3728801 35,35.3728801 C41.6644243,35.3728801 47.8955852,29.5814393 55.5,28.3728801 C60.5696098,27.5671739 65.4029432,28.0671739 70,29.8728801" id="Line" stroke-opacity="0.5" stroke="#0C0C0D" stroke-width="3" stroke-linecap="square"></path>
|
||||
<path d="M0,38.8728801 C3.80326861,38.2140466 7.96993528,38.2140466 12.5,38.8728801 C19.2950971,39.8611303 28.3355757,44.3728801 35,44.3728801 C41.6644243,44.3728801 47.8955852,38.5814393 55.5,37.3728801 C60.5696098,36.5671739 65.4029432,37.0671739 70,38.8728801" id="Line" stroke-opacity="0.5" stroke="#0C0C0D" stroke-width="3" stroke-linecap="square"></path>
|
||||
<path d="M0,48.8728801 C3.80326861,48.2140466 7.96993528,48.2140466 12.5,48.8728801 C19.2950971,49.8611303 28.3355757,54.3728801 35,54.3728801 C41.6644243,54.3728801 47.8955852,48.5814393 55.5,47.3728801 C60.5696098,46.5671739 65.4029432,47.0671739 70,48.8728801" id="Line" stroke-opacity="0.5" stroke="#0C0C0D" stroke-width="3" stroke-linecap="square"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
@ -1 +0,0 @@
|
||||
<svg width="57" height="57" viewBox="0 0 57 57" xmlns="http://www.w3.org/2000/svg"><title>upload</title><g transform="translate(1 1)" stroke-width="2" stroke="#7FC9FD" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path d="M18 24l10-9 10 9M28 39.545V15"/><circle cx="27.5" cy="27.5" r="27.5"/></g></svg>
|
Before Width: | Height: | Size: 336 B |
1
assets/user.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="194 -104 1000 1000"><style>.st0{fill:#C3CFD8;} .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}</style><path class="st0" d="M694-104.3c276.1 0 500 223.9 500 500s-223.9 500-500 500-500-223.9-500-500c0-276.2 223.9-500 500-500z"/><path class="st1" d="M892.4 585.9c10 3.1 19.1 5.7 27.5 8.2 34.5 10 44.8 54.6 17.5 78.1-65.4 56.5-150.7 90.8-244 90.8-92.8 0-177.6-33.8-242.9-89.8-27.4-23.5-17.3-68.2 17.4-78.3 9.2-2.7 19.2-5.5 30.2-9 62.6-19.5 92.6-43.7 98.2-68.7 0-.1 0-.2.1-.2 3.6-16.1-2.8-32.9-15.5-43.5-26.4-22.1-37.1-59.8-44.1-87.5-.8-3.2-1.7-6.5-2.5-9.8-12.1-2.1-25.4-17.3-32.2-38.5-8.2-25.5-3.9-49.8 9.6-54.1 1.3-.4 2.6-.4 3.9-.5-3.1-18.2-6.9-45.4-7.3-69.3-.1-5.2-.2-10.9-.2-16.9 0-3 .1-6.1.1-9.3 0-1.6.1-3.2.2-4.8.1-1.6.2-3.2.3-4.9.9-13.1 2.9-26.8 7-40 7.4-23.7 21.6-45.4 47.4-57.3 5.8-2.7 11-6.4 15.1-11.3 22.4-26.4 49.1-39.6 74.2-45.4 6.9-1.6 13.6-2.6 20.1-3.2 3.2-.3 6.4-.5 9.5-.6 1.6-.1 3.1-.1 4.6-.1h4.5c11.7.3 22 1.8 29.6 3.7 50 12.3 89.2 38 116.4 69.5 13.5 15.8 23.9 33 30.7 50.7 3.4 8.9 5.9 17.9 7.4 26.9.8 4.5 1.3 9 1.6 13.5.3 4.5.3 8.9.1 13.4-1.5 27.1-4.4 45.9-7.3 60.1-2.3 11.1.1 22.2 5 32.4 4.9 10.3 5.3 26.7.2 43.9-6.1 20.3-18.3 35.3-29.8 38.7-2.2 8.1-3.8 13.5-3.9 13.5-3.8 29-10.7 59.8-35.3 82.9-10.5 9.8-15 24.5-13.1 38.7.5 3.5 1 6.6 1.6 9.2 5.6 25.1 35.5 49.3 98.1 68.8z"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
@ -26,6 +26,7 @@ uploadSuccessConfirmHeader = Ready to Send
|
||||
uploadSvgAlt = Upload
|
||||
uploadSuccessTimingHeader = The link to your file will expire after 1 download or in 24 hours.
|
||||
expireInfo = The link to your file will expire after { $downloadCount } or { $timespan }.
|
||||
frontPageExpireInfo = Expires after { $downloadCount } or { $timespan }
|
||||
downloadCount = { $num ->
|
||||
[one] 1 download
|
||||
*[other] { $num } downloads
|
||||
@ -34,6 +35,14 @@ timespanHours = { $num ->
|
||||
[one] 1 hour
|
||||
*[other] { $num } hours
|
||||
}
|
||||
timespanMinutes = { $num ->
|
||||
[one] 1 minute
|
||||
*[other] { $num } minutes
|
||||
}
|
||||
timespanWeeks = { $num ->
|
||||
[one] 1 week
|
||||
*[other] { $num } weeks
|
||||
}
|
||||
copyUrlFormLabelWithName = Copy and share the link to send your file: { $filename }
|
||||
copyUrlFormButton = Copy to clipboard
|
||||
copiedUrl = Copied!
|
||||
@ -117,3 +126,30 @@ passwordIsSet = Password set
|
||||
maxPasswordLength = Maximum password length: { $length }
|
||||
# A short status message shown when there was an error setting the password
|
||||
passwordSetError = This password could not be set
|
||||
pageHeaderCredits = from the makers of Firefox
|
||||
addFilesButton = Add file(s)
|
||||
uploadFilesButton = Send
|
||||
uploadFileProgress = Sending
|
||||
uploadDropDragMessage = Drop files here
|
||||
uploadDropClickMessage = or click to select a file
|
||||
addPasswordMessage = Protect with password
|
||||
addPasswordLabel = Password:
|
||||
copyUrlLabel = Copy and share this link:
|
||||
passwordReminder = don't forget the password too
|
||||
signInPromoText = Sign In/Up!
|
||||
signInExplanation = It's free and gives you many more Send options
|
||||
signInLearnMore = Learn more!
|
||||
downloadProgressButton = Downloading... { $progress }
|
||||
downloadMessage2 = Firefox Send lets you share files with a safe, private, and encrypted link that automatically expires to ensure your stuff does not remain online forever.
|
||||
signInEmailEnter = Enter your Email
|
||||
emailEntryPlaceholder = Email
|
||||
signInContinueMessage = to continue to Firefox Send
|
||||
signInContinueButton = Continue
|
||||
accountBenefitTitle = With a free Firefox Account with Send you can:
|
||||
accountBenefitMultiFile = Send multiple files at once
|
||||
accountBenefitLargeFiles = Upload larger files (up to { $size } GB)
|
||||
accountBenefitExpiry = Have more expiry options
|
||||
accountBenefitSync = Manage your uploads across devices
|
||||
accountBenefitNotify = Be notified when your files are downloaded
|
||||
accountBenefitMore = Do a lot more!
|
||||
|
||||
|
@ -5,7 +5,7 @@ module.exports = async (req, res) => {
|
||||
const meta = await storage.metadata(req.params.id);
|
||||
res.set('WWW-Authenticate', `send-v1 ${meta.nonce}`);
|
||||
res.send({
|
||||
password: meta.pwd
|
||||
requiresPassword: meta.pwd
|
||||
});
|
||||
} catch (e) {
|
||||
res.sendStatus(404);
|
||||
|
@ -49,7 +49,7 @@ module.exports = function(app) {
|
||||
next();
|
||||
});
|
||||
app.use(express.json());
|
||||
app.get('/', language, pages.index);
|
||||
app.get('/', language, pages.blank);
|
||||
app.get('/legal', language, pages.legal);
|
||||
app.get('/jsconfig.js', require('./jsconfig'));
|
||||
app.get(`/share/:id${ID_REGEX}`, language, pages.blank);
|
||||
|
@ -19,16 +19,15 @@ module.exports = {
|
||||
|
||||
download: async function(req, res, next) {
|
||||
const id = req.params.id;
|
||||
|
||||
try {
|
||||
const { nonce, pwd } = await storage.metadata(id);
|
||||
res.set('WWW-Authenticate', `send-v1 ${nonce}`);
|
||||
res.send(
|
||||
stripEvents(
|
||||
routes.toString(
|
||||
`/download/${req.params.id}`,
|
||||
`/download/${id}`,
|
||||
Object.assign(state(req), {
|
||||
fileInfo: { nonce, requiresPassword: +pwd }
|
||||
fileInfo: { nonce, requiresPassword: pwd }
|
||||
})
|
||||
)
|
||||
)
|
||||
|