hook multifile to ui

This commit is contained in:
Emily 2018-07-31 11:09:18 -07:00
parent e42ad175db
commit c9ae76b209
77 changed files with 1528 additions and 1111 deletions

View file

@ -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;
}

View file

@ -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);
}
}
};

View file

@ -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;
}

View file

@ -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) {

View 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;
};

View file

@ -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;
}
}

View file

@ -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;
}
};

View 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;
}

View 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>`;
};

View file

@ -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;
}
}

View file

@ -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>
`;
}
};

View file

@ -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;
}

View file

@ -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>`;

View file

@ -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;
}
}

View file

@ -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"

View file

@ -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, '•') : '••••••••••••';
}

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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;
}

View file

@ -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>
`;
};

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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 = '';
}
}

View file

@ -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;
}

View 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>
`;
};

View 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;
}
}

View 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>`;
};

View 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;
}

View 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>
`;
};

View 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;
}
}

View 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>
`;
};

View 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;
}

View 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');
}
};

View 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);
}