updated password input UI

This commit is contained in:
Danny Coates 2018-02-16 12:56:53 -08:00
parent 8d41111cd6
commit 346e604f34
No known key found for this signature in database
GPG Key ID: 4C442633C62E00CB
37 changed files with 282 additions and 288 deletions

View File

@ -34,7 +34,6 @@ body {
position: relative; position: relative;
} }
pre,
input, input,
select, select,
textarea, textarea,
@ -43,13 +42,6 @@ button {
margin: 0; margin: 0;
} }
pre {
font-family: monospace;
font-size: 18px;
font-weight: 600;
display: inline-block;
}
a { a {
text-decoration: none; text-decoration: none;
} }
@ -69,7 +61,7 @@ a {
.btn { .btn {
font-size: 15px; font-size: 15px;
font-weight: 500; font-weight: 500;
color: white; color: var(--primaryControlFGColor);
cursor: pointer; cursor: pointer;
text-align: center; text-align: center;
background: var(--primaryControlBGColor); background: var(--primaryControlBGColor);
@ -113,7 +105,7 @@ a {
background: var(--primaryControlBGColor); background: var(--primaryControlBGColor);
border-radius: 0 6px 6px 0; border-radius: 0 6px 6px 0;
border: 1px solid var(--primaryControlBGColor); border: 1px solid var(--primaryControlBGColor);
color: white; color: var(--primaryControlFGColor);
cursor: pointer; cursor: pointer;
/* Force flat button look */ /* Force flat button look */
@ -177,7 +169,7 @@ a {
} }
.progressSection__text { .progressSection__text {
color: rgba(0, 0, 0, 0.5); color: var(--lightTextColor);
letter-spacing: -0.4px; letter-spacing: -0.4px;
margin-top: 24px; margin-top: 24px;
margin-bottom: 74px; margin-bottom: 74px;

View File

@ -130,9 +130,13 @@ export default function(state, emitter) {
emitter.on('password', async ({ password, file }) => { emitter.on('password', async ({ password, file }) => {
try { try {
state.settingPassword = true;
render();
await file.setPassword(password); await file.setPassword(password);
state.storage.writeFile(file); state.storage.writeFile(file);
metrics.addedPassword({ size: file.size }); metrics.addedPassword({ size: file.size });
await delay(1000);
state.settingPassword = false;
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }

View File

@ -5,7 +5,6 @@
@import './templates/passwordInput/passwordInput.css'; @import './templates/passwordInput/passwordInput.css';
@import './templates/downloadPassword/downloadPassword.css'; @import './templates/downloadPassword/downloadPassword.css';
@import './templates/setPasswordSection/setPasswordSection.css'; @import './templates/setPasswordSection/setPasswordSection.css';
@import './templates/changePasswordSection/changePasswordSection.css';
@import './templates/footer/footer.css'; @import './templates/footer/footer.css';
@import './templates/fxPromo/fxPromo.css'; @import './templates/fxPromo/fxPromo.css';
@import './templates/selectbox/selectbox.css'; @import './templates/selectbox/selectbox.css';

View File

@ -1,6 +1,5 @@
const html = require('choo/html'); const html = require('choo/html');
module.exports = function() { module.exports = function() {
const div = html`<div></div>`; return html`<div></div>`;
return div;
}; };

View File

@ -3,7 +3,7 @@ const progress = require('../../templates/progress');
const { fadeOut } = require('../../utils'); const { fadeOut } = require('../../utils');
module.exports = function(state, emit) { module.exports = function(state, emit) {
const div = html` return html`
<div class="page effect--fadeIn"> <div class="page effect--fadeIn">
<div class="title"> <div class="title">
${state.translate('downloadFinish')} ${state.translate('downloadFinish')}
@ -23,6 +23,4 @@ module.exports = function(state, emit) {
await fadeOut('.page'); await fadeOut('.page');
emit('pushState', '/'); emit('pushState', '/');
} }
return div;
}; };

View File

@ -13,7 +13,7 @@ module.exports = function(state, emit) {
${state.translate('deletePopupCancel')} ${state.translate('deletePopupCancel')}
</button>`; </button>`;
const div = html` return html`
<div class="page effect--fadeIn"> <div class="page effect--fadeIn">
<div class="title"> <div class="title">
${state.translate('downloadingPageProgress', { ${state.translate('downloadingPageProgress', {
@ -39,5 +39,4 @@ module.exports = function(state, emit) {
btn.remove(); btn.remove();
emit('cancel'); emit('cancel');
} }
return div;
}; };

View File

@ -1,17 +1,8 @@
const html = require('choo/html'); const html = require('choo/html');
const raw = require('choo/html/raw'); const raw = require('choo/html/raw');
function replaceLinks(str, urls) {
let i = -1;
const s = str.replace(/<a>([^<]+)<\/a>/g, (m, v) => {
i++;
return `<a href="${urls[i]}">${v}</a>`;
});
return `<div class="description">${s}</div>`;
}
module.exports = function(state) { module.exports = function(state) {
const div = html` return html`
<div id="legal"> <div id="legal">
<div class="title">${state.translate('legalHeader')}</div> <div class="title">${state.translate('legalHeader')}</div>
${raw( ${raw(
@ -29,5 +20,13 @@ module.exports = function(state) {
)} )}
</div> </div>
`; `;
return div;
}; };
function replaceLinks(str, urls) {
let i = 0;
const s = str.replace(
/<a>([^<]+)<\/a>/g,
(m, v) => `<a href="${urls[i++]}">${v}</a>`
);
return `<div class="description">${s}</div>`;
}

View File

@ -2,7 +2,7 @@ const html = require('choo/html');
const assets = require('../../../common/assets'); const assets = require('../../../common/assets');
module.exports = function(state) { module.exports = function(state) {
const div = html` return html`
<div class="notFoundPage"> <div class="notFoundPage">
<div class="title">${state.translate('expiredPageHeader')}</div> <div class="title">${state.translate('expiredPageHeader')}</div>
<div class="notFoundPage__img"> <div class="notFoundPage__img">
@ -15,5 +15,4 @@ module.exports = function(state) {
${state.translate('sendYourFilesLink')} ${state.translate('sendYourFilesLink')}
</a> </a>
</div>`; </div>`;
return div;
}; };

View File

@ -20,7 +20,7 @@ module.exports = function(state, pageAction) {
if (!pageAction) { if (!pageAction) {
return info; return info;
} }
const div = html` return html`
<div class="page"> <div class="page">
<div class="title"> <div class="title">
<span>${title}</span> <span>${title}</span>
@ -38,5 +38,4 @@ module.exports = function(state, pageAction) {
${info} ${info}
</div> </div>
`; `;
return div;
}; };

View File

@ -3,41 +3,18 @@ const html = require('choo/html');
const raw = require('choo/html/raw'); const raw = require('choo/html/raw');
const assets = require('../../../common/assets'); const assets = require('../../../common/assets');
const notFound = require('../notFound'); const notFound = require('../notFound');
const changePasswordSection = require('../../templates/changePasswordSection');
const setPasswordSection = require('../../templates/setPasswordSection'); const setPasswordSection = require('../../templates/setPasswordSection');
const selectbox = require('../../templates/selectbox'); const selectbox = require('../../templates/selectbox');
const deletePopup = require('../../templates/popup'); const deletePopup = require('../../templates/popup');
const { allowedCopy, delay, fadeOut } = require('../../utils'); const { allowedCopy, delay, fadeOut } = require('../../utils');
function expireInfo(file, translate, emit) {
const hours = Math.floor(EXPIRE_SECONDS / 60 / 60);
const el = html`<div>${raw(
translate('expireInfo', {
downloadCount: '<select></select>',
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 });
select.parentNode.replaceChild(
selectbox(file.dlimit || 1, options, t, changed),
select
);
return el;
}
module.exports = function(state, emit) { module.exports = function(state, emit) {
const file = state.storage.getFileById(state.params.id); const file = state.storage.getFileById(state.params.id);
if (!file) { if (!file) {
return notFound(state, emit); return notFound(state, emit);
} }
const passwordSection = file.hasPassword return html`
? changePasswordSection(state, emit)
: setPasswordSection(state, emit);
const div = html`
<div id="shareWrapper" class="effect--fadeIn"> <div id="shareWrapper" class="effect--fadeIn">
<div class="title">${expireInfo(file, state.translate, emit)}</div> <div class="title">${expireInfo(file, state.translate, emit)}</div>
<div class="sharePage"> <div class="sharePage">
@ -56,7 +33,7 @@ module.exports = function(state, emit) {
title="${state.translate('copyUrlFormButton')}" title="${state.translate('copyUrlFormButton')}"
onclick=${copyLink}>${state.translate('copyUrlFormButton')}</button> onclick=${copyLink}>${state.translate('copyUrlFormButton')}</button>
</div> </div>
${passwordSection} ${setPasswordSection(state, emit)}
<button <button
class="btn btn--delete" class="btn btn--delete"
title="${state.translate('deleteFileButton')}" title="${state.translate('deleteFileButton')}"
@ -94,6 +71,7 @@ module.exports = function(state, emit) {
emit('copy', { url: file.url, location: 'success-screen' }); emit('copy', { url: file.url, location: 'success-screen' });
const input = document.getElementById('fileUrl'); const input = document.getElementById('fileUrl');
input.disabled = true; input.disabled = true;
input.classList.add('input--copied');
const copyBtn = document.getElementById('copyBtn'); const copyBtn = document.getElementById('copyBtn');
copyBtn.disabled = true; copyBtn.disabled = true;
copyBtn.classList.add('inputBtn--copied'); copyBtn.classList.add('inputBtn--copied');
@ -103,6 +81,7 @@ module.exports = function(state, emit) {
); );
await delay(2000); await delay(2000);
input.disabled = false; input.disabled = false;
input.classList.remove('input--copied');
copyBtn.disabled = false; copyBtn.disabled = false;
copyBtn.classList.remove('inputBtn--copied'); copyBtn.classList.remove('inputBtn--copied');
copyBtn.textContent = state.translate('copyUrlFormButton'); copyBtn.textContent = state.translate('copyUrlFormButton');
@ -114,5 +93,23 @@ module.exports = function(state, emit) {
await fadeOut('#shareWrapper'); await fadeOut('#shareWrapper');
emit('pushState', '/'); emit('pushState', '/');
} }
return div;
}; };
function expireInfo(file, translate, emit) {
const hours = Math.floor(EXPIRE_SECONDS / 60 / 60);
const el = html`<div>${raw(
translate('expireInfo', {
downloadCount: '<select></select>',
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 });
select.parentNode.replaceChild(
selectbox(file.dlimit || 1, options, t, changed),
select
);
return el;
}

View File

@ -40,7 +40,6 @@
line-height: 23px; line-height: 23px;
font-weight: 300; font-weight: 300;
padding-left: 10px; padding-left: 10px;
padding-right: 10px;
} }
.copySection__url:disabled { .copySection__url:disabled {
@ -53,6 +52,10 @@
padding-bottom: 4px; padding-bottom: 4px;
} }
.input--copied {
border-color: var(--successControlBGColor);
}
.inputBtn--copied, .inputBtn--copied,
.inputBtn--copied:hover { .inputBtn--copied:hover {
background: var(--successControlBGColor); background: var(--successControlBGColor);

View File

@ -1,24 +1,6 @@
const html = require('choo/html'); const html = require('choo/html');
const assets = require('../../../common/assets'); const assets = require('../../../common/assets');
function outdatedStrings(state) {
return {
title: state.translate('notSupportedHeader'),
description: state.translate('notSupportedOutdatedDetail'),
button: state.translate('updateFirefox'),
explainer: state.translate('uploadPageExplainer')
};
}
function unsupportedStrings(state) {
return {
title: state.translate('notSupportedHeader'),
description: state.translate('notSupportedDetail'),
button: state.translate('downloadFirefoxButtonSub'),
explainer: state.translate('uploadPageExplainer')
};
}
module.exports = function(state) { module.exports = function(state) {
let strings = {}; let strings = {};
let why = ''; let why = '';
@ -46,7 +28,7 @@ module.exports = function(state) {
${strings.button} ${strings.button}
</div>`; </div>`;
} }
const div = html` return html`
<div class="unsupportedPage"> <div class="unsupportedPage">
<div class="title">${strings.title}</div> <div class="title">${strings.title}</div>
<div class="description"> <div class="description">
@ -64,5 +46,22 @@ module.exports = function(state) {
${strings.explainer} ${strings.explainer}
</div> </div>
</div>`; </div>`;
return div;
}; };
function outdatedStrings(state) {
return {
title: state.translate('notSupportedHeader'),
description: state.translate('notSupportedOutdatedDetail'),
button: state.translate('updateFirefox'),
explainer: state.translate('uploadPageExplainer')
};
}
function unsupportedStrings(state) {
return {
title: state.translate('notSupportedHeader'),
description: state.translate('notSupportedDetail'),
button: state.translate('downloadFirefoxButtonSub'),
explainer: state.translate('uploadPageExplainer')
};
}

View File

@ -9,7 +9,7 @@
font-size: 13px; font-size: 13px;
line-height: 23px; line-height: 23px;
text-align: center; text-align: center;
color: #7d7d7d; color: var(--lightTextColor);
margin: 0 auto 23px; margin: 0 auto 23px;
} }
@ -23,7 +23,7 @@
box-shadow: 0 5px 3px rgb(234, 234, 234); box-shadow: 0 5px 3px rgb(234, 234, 234);
font-family: 'Fira Sans', 'segoe ui', sans-serif; font-family: 'Fira Sans', 'segoe ui', sans-serif;
font-weight: 500; font-weight: 500;
color: #fff; color: var(--primaryControlFGColor);
font-size: 26px; font-size: 26px;
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@ -5,7 +5,7 @@ const { bytes } = require('../../utils');
module.exports = function(state, emit) { module.exports = function(state, emit) {
const transfer = state.transfer; const transfer = state.transfer;
const div = html` return html`
<div class="page effect--fadeIn"> <div class="page effect--fadeIn">
<div class="title"> <div class="title">
${state.translate('uploadingPageProgress', { ${state.translate('uploadingPageProgress', {
@ -36,5 +36,4 @@ module.exports = function(state, emit) {
btn.textContent = state.translate('uploadCancelNotification'); btn.textContent = state.translate('uploadCancelNotification');
emit('cancel'); emit('cancel');
} }
return div;
}; };

View File

@ -7,7 +7,7 @@ const { bytes, fadeOut } = require('../../utils');
module.exports = function(state, emit) { module.exports = function(state, emit) {
// the page flickers if both the server and browser set 'effect--fadeIn' // the page flickers if both the server and browser set 'effect--fadeIn'
const fade = state.layout ? '' : 'effect--fadeIn'; const fade = state.layout ? '' : 'effect--fadeIn';
const div = html` return html`
<div id="page-one" class="${fade}"> <div id="page-one" class="${fade}">
<div class="title">${state.translate('uploadPageHeader')}</div> <div class="title">${state.translate('uploadPageHeader')}</div>
<div class="description"> <div class="description">
@ -82,5 +82,4 @@ module.exports = function(state, emit) {
await fadeOut('#page-one'); await fadeOut('#page-one');
emit('upload', { file, type: 'click' }); emit('upload', { file, type: 'click' });
} }
return div;
}; };

View File

@ -1,29 +0,0 @@
.changePasswordSection {
padding: 10px 0;
align-self: left;
max-width: 100%;
overflow-wrap: break-word;
}
.btn--reset {
width: 80px;
height: 30px;
background: #fff;
border-color: rgba(12, 12, 13, 0.3);
margin-top: 5px;
margin-left: 15px;
margin-bottom: 12px;
line-height: 24px;
color: #313131;
}
.btn--reset:hover {
background: #efeff1;
}
@media (max-device-width: 520px), (max-width: 520px) {
.changePasswordSection {
align-self: center;
min-width: 95%;
}
}

View File

@ -1,52 +0,0 @@
const html = require('choo/html');
const raw = require('choo/html/raw');
const passwordInput = require('../passwordInput');
module.exports = function(state, emit) {
const file = state.storage.getFileById(state.params.id);
return html`<div class="changePasswordSection">
${passwordSpan(file.password)}
<button
class="btn btn--reset"
onclick=${toggleResetInput}
>${state.translate('changePasswordButton')}</button>
${passwordInput(
state.translate('unlockInputPlaceholder'),
state.translate('changePasswordButton'),
changePassword
)}
</div>`;
function passwordSpan(password) {
password = password || '●●●●●';
const span = html`<span>${raw(
state.translate('passwordResult', {
password: '<pre class="passwordMask"></pre>'
})
)}</span>`;
const masked = span.querySelector('.passwordMask');
masked.textContent = password.replace(/./g, '●');
return span;
}
function changePassword(event) {
event.preventDefault();
const password = document.getElementById('password-input').value;
if (password.length > 0) {
emit('password', { password, file });
}
return false;
}
function toggleResetInput(event) {
const form = event.target.parentElement.querySelector('form.passwordInput');
const input = document.getElementById('password-input');
if (form.style.visibility === 'hidden' || form.style.visibility === '') {
form.style.visibility = 'visible';
input.focus();
} else {
form.style.visibility = 'hidden';
}
}
};

View File

@ -1,15 +1,13 @@
const html = require('choo/html'); const html = require('choo/html');
module.exports = function(state, emit) { module.exports = function(state, emit) {
return html`
<button class="btn btn--download"
onclick=${download}>${state.translate('downloadButtonLabel')}
</button>`;
function download(event) { function download(event) {
event.preventDefault(); event.preventDefault();
emit('download', state.fileInfo); emit('download', state.fileInfo);
} }
return html`
<div>
<button class="btn btn--download"
onclick=${download}>${state.translate('downloadButtonLabel')}
</button>
</div>`;
}; };

View File

@ -1,7 +1,7 @@
.fileData { .fileData {
font-size: 15px; font-size: 15px;
vertical-align: top; vertical-align: top;
color: #4a4a4a; color: var(--lightTextColor);
padding: 17px 19px 0; padding: 17px 19px 0;
line-height: 23px; line-height: 23px;
position: relative; position: relative;

View File

@ -3,30 +3,13 @@ const assets = require('../../../common/assets');
const number = require('../../utils').number; const number = require('../../utils').number;
const deletePopup = require('../popup'); const deletePopup = require('../popup');
function timeLeft(milliseconds, state) {
const minutes = Math.floor(milliseconds / 1000 / 60);
const hours = Math.floor(minutes / 60);
if (hours >= 1) {
return state.translate('expiresHoursMinutes', {
hours,
minutes: minutes % 60
});
} else if (hours === 0) {
if (minutes === 0) {
return state.translate('expiresMinutes', { minutes: '< 1' });
}
return state.translate('expiresMinutes', { minutes });
}
return null;
}
module.exports = function(file, state, emit) { module.exports = function(file, state, emit) {
const ttl = file.expiresAt - Date.now(); const ttl = file.expiresAt - Date.now();
const remainingTime = const remainingTime =
timeLeft(ttl, state) || state.translate('linkExpiredAlt'); timeLeft(ttl, state) || state.translate('linkExpiredAlt');
const downloadLimit = file.dlimit || 1; const downloadLimit = file.dlimit || 1;
const totalDownloads = file.dtotal || 0; const totalDownloads = file.dtotal || 0;
const row = html` return html`
<tr id="${file.id}"> <tr id="${file.id}">
<td class="fileData fileData--overflow" title="${file.name}"> <td class="fileData fileData--overflow" title="${file.name}">
<a class="link" href="/share/${file.id}">${file.name}</a> <a class="link" href="/share/${file.id}">${file.name}</a>
@ -84,6 +67,21 @@ module.exports = function(file, state, emit) {
emit('delete', { file, location: 'upload-list' }); emit('delete', { file, location: 'upload-list' });
emit('render'); emit('render');
} }
return row;
}; };
function timeLeft(milliseconds, state) {
const minutes = Math.floor(milliseconds / 1000 / 60);
const hours = Math.floor(minutes / 60);
if (hours >= 1) {
return state.translate('expiresHoursMinutes', {
hours,
minutes: minutes % 60
});
} else if (hours === 0) {
if (minutes === 0) {
return state.translate('expiresMinutes', { minutes: '< 1' });
}
return state.translate('expiresMinutes', { minutes });
}
return null;
}

View File

@ -7,7 +7,7 @@
.fileList__header { .fileList__header {
font-size: 16px; font-size: 16px;
color: #858585; color: var(--lightTextColor);
font-weight: lighter; font-weight: lighter;
text-align: left; text-align: left;
background: rgba(0, 148, 251, 0.05); background: rgba(0, 148, 251, 0.05);

View File

@ -2,9 +2,8 @@ const html = require('choo/html');
const file = require('../file'); const file = require('../file');
module.exports = function(state, emit) { module.exports = function(state, emit) {
let table = '';
if (state.storage.files.length) { if (state.storage.files.length) {
table = html` return html`
<table class="fileList"> <table class="fileList">
<thead> <thead>
<tr> <tr>
@ -31,5 +30,4 @@ module.exports = function(state, emit) {
</table> </table>
`; `;
} }
return table;
}; };

View File

@ -20,7 +20,7 @@
} }
.legalSection__link { .legalSection__link {
color: #858585; color: var(--lightTextColor);
opacity: 0.9; opacity: 0.9;
white-space: nowrap; white-space: nowrap;
margin-right: 2vw; margin-right: 2vw;
@ -31,7 +31,7 @@
} }
.legalSection__link:visited { .legalSection__link:visited {
color: #858585; color: var(--lightTextColor);
} }
.legalSection__mozLogo { .legalSection__mozLogo {

View File

@ -2,9 +2,6 @@ const html = require('choo/html');
const assets = require('../../../common/assets'); const assets = require('../../../common/assets');
module.exports = function(state, emit) { module.exports = function(state, emit) {
function clicked() {
emit('experiment', { cd3: 'promo' });
}
let classes = 'fxPromo'; let classes = 'fxPromo';
switch (state.promo) { switch (state.promo) {
case 'blue': case 'blue':
@ -30,4 +27,8 @@ module.exports = function(state, emit) {
>Download Firefox now </a></span> >Download Firefox now </a></span>
</div> </div>
</div>`; </div>`;
function clicked() {
emit('experiment', { cd3: 'promo' });
}
}; };

View File

@ -59,7 +59,7 @@
border-radius: 3px; border-radius: 3px;
border: 1px solid var(--primaryControlBGColor); border: 1px solid var(--primaryControlBGColor);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
color: #fff; color: var(--primaryControlFGColor);
cursor: pointer; cursor: pointer;
display: block; display: block;
float: right; float: right;
@ -85,7 +85,7 @@
} }
.feedback:active { .feedback:active {
background-color: #0277d8; background-color: var(--primaryControlHoverColor);
} }
@media (max-device-width: 520px), (max-width: 520px) { @media (max-device-width: 520px), (max-width: 520px) {

View File

@ -11,6 +11,28 @@ const assets = require('../../../common/assets');
string with the value from package.json. 🤢 string with the value from package.json. 🤢
*/ */
const version = require('../../../package.json').version || 'VERSION'; const version = require('../../../package.json').version || 'VERSION';
const browser = browserName();
module.exports = function(state) {
return html`<header class="header">
<div class="logo">
<a class="logo__link" href="/">
<img
src="${assets.get('send_logo.svg')}"
alt="Send"/>
<h1 class="logo__title">Send</h1>
</a>
<div class="logo__subtitle">
<a class="logo__subtitle-link" href="https://testpilot.firefox.com">Firefox Test Pilot</a>
<div>${state.translate('siteSubtitle')}</div>
</div>
</div>
<a href="https://qsurvey.mozilla.com/s3/txp-firefox-send?ver=${version}&browser=${browser}"
rel="noreferrer noopener"
class="feedback"
target="_blank">${state.translate('siteFeedback')}</a>
</header>`;
};
function browserName() { function browserName() {
try { try {
@ -34,26 +56,3 @@ function browserName() {
return 'unknown'; return 'unknown';
} }
} }
const browser = browserName();
module.exports = function(state) {
return html`<header class="header">
<div class="logo">
<a class="logo__link" href="/">
<img
src="${assets.get('send_logo.svg')}"
alt="Send"/>
<h1 class="logo__title">Send</h1>
</a>
<div class="logo__subtitle">
<a class="logo__subtitle-link" href="https://testpilot.firefox.com">Firefox Test Pilot</a>
<div>${state.translate('siteSubtitle')}</div>
</div>
</div>
<a href="https://qsurvey.mozilla.com/s3/txp-firefox-send?ver=${version}&browser=${browser}"
rel="noreferrer noopener"
class="feedback"
target="_blank">${state.translate('siteFeedback')}</a>
</header>`;
};

View File

@ -1,26 +1,54 @@
const html = require('choo/html'); const html = require('choo/html');
module.exports = function(placeholder, action, submit) { module.exports = function(file, state, emit) {
const setting = state.settingPassword;
const formClass = file.hasPassword
? 'passwordInput'
: 'passwordInput passwordInput--hidden';
const inputClass = setting ? 'input input--copied' : 'input input--noBtn';
const btnClass = setting
? 'inputBtn inputBtn--loading'
: 'inputBtn inputBtn--hidden';
const action = file.hasPassword
? state.translate('changePasswordButton')
: state.translate('addPasswordButton');
return html` return html`
<div>
<form <form
class="passwordInput passwordInput--hidden" class="${formClass}"
onsubmit=${submit} onsubmit=${setPassword}
data-no-csrf> data-no-csrf>
<input id="password-input" <input id="password-input"
class="input input--noBtn" ${setting ? 'disabled' : ''}
class="${inputClass}"
maxlength="32" maxlength="32"
autocomplete="off" autocomplete="off"
type="password" type="password"
oninput=${inputChanged} oninput=${inputChanged}
placeholder="${placeholder}"> placeholder="${
file.hasPassword
? passwordPlaceholder(file.password)
: state.translate('unlockInputPlaceholder')
}">
<input type="submit" <input type="submit"
id="password-btn" id="password-btn"
class="inputBtn inputBtn--hidden" ${setting ? 'disabled' : ''}
value="${action}"/> class="${btnClass}"
value="${setting ? '' : action}">
</form> </form>
<div class="passwordInput__msg">${message(
setting,
file.hasPassword,
state.translate('passwordIsSet')
)}</div>
</div>
`; `;
function inputChanged() { function inputChanged() {
const pwdmsg = document.querySelector('.passwordInput__msg');
if (pwdmsg) {
pwdmsg.textContent = '';
}
const resetInput = document.getElementById('password-input'); const resetInput = document.getElementById('password-input');
const resetBtn = document.getElementById('password-btn'); const resetBtn = document.getElementById('password-btn');
if (resetInput.value.length > 0) { if (resetInput.value.length > 0) {
@ -31,4 +59,24 @@ module.exports = function(placeholder, action, submit) {
resetInput.classList.add('input--noBtn'); resetInput.classList.add('input--noBtn');
} }
} }
function setPassword(event) {
event.preventDefault();
const password = document.getElementById('password-input').value;
if (password.length > 0) {
emit('password', { password, file });
}
return false;
}
}; };
function passwordPlaceholder(password) {
return password ? password.replace(/./g, '●') : '●●●●●●●●●●●●';
}
function message(setting, pwd, deflt) {
if (setting || !pwd) {
return '';
}
return deflt;
}

View File

@ -3,16 +3,42 @@
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
width: 80%; width: 80%;
padding: 10px 5px; padding: 10px 5px 5px;
}
.passwordInput__msg {
height: 100px;
margin: 0 5px;
font-size: 15px;
color: var(--lightTextColor);
} }
.passwordInput--hidden { .passwordInput--hidden {
visibility: hidden; visibility: hidden;
} }
.inputBtn--loading {
background-image: url('../assets/spinner.svg');
background-position: center;
background-size: 30px 30px;
background-repeat: no-repeat;
background-color: var(--successControlBGColor);
border: 1px solid var(--successControlBGColor);
color: var(--successControlFGColor);
width: 10em;
}
.inputBtn--loading:hover {
background-color: var(--successControlBGColor);
}
@media (max-device-width: 520px), (max-width: 520px) { @media (max-device-width: 520px), (max-width: 520px) {
.passwordInput { .passwordInput {
flex-direction: column; flex-direction: column;
width: inherit; width: inherit;
} }
.inputBtn--loading {
width: inherit;
}
} }

View File

@ -1,14 +1,6 @@
const html = require('choo/html'); const html = require('choo/html');
module.exports = function(msg, confirmText, cancelText, confirmCallback) { module.exports = function(msg, confirmText, cancelText, confirmCallback) {
function hide(e) {
e.stopPropagation();
const popup = document.querySelector('.popup.popup--show');
if (popup) {
popup.classList.remove('popup--show');
}
}
return html` return html`
<div class="popup__wrapper"> <div class="popup__wrapper">
<div class="popup" onblur=${hide} tabindex="-1"> <div class="popup" onblur=${hide} tabindex="-1">
@ -23,4 +15,12 @@ module.exports = function(msg, confirmText, cancelText, confirmCallback) {
</div> </div>
</div> </div>
</div>`; </div>`;
function hide(e) {
e.stopPropagation();
const popup = document.querySelector('.popup.popup--show');
if (popup) {
popup.classList.remove('popup--show');
}
}
}; };

View File

@ -2,8 +2,8 @@
visibility: hidden; visibility: hidden;
min-width: 204px; min-width: 204px;
min-height: 105px; min-height: 105px;
background-color: #fff; background-color: var(--pageBGColor);
color: #000; color: var(--textColor);
border: 1px solid #d7d7db; border: 1px solid #d7d7db;
padding: 15px 24px; padding: 15px 24px;
box-sizing: content-box; box-sizing: content-box;
@ -82,7 +82,7 @@
} }
.popup__yes { .popup__yes {
color: #fff; color: var(--primaryControlFGColor);
background-color: var(--primaryControlBGColor); background-color: var(--primaryControlBGColor);
border-radius: 5px; border-radius: 5px;
padding: 5px 25px; padding: 5px 25px;

View File

@ -9,7 +9,7 @@ const circumference = 2 * Math.PI * radius;
module.exports = function(progressRatio) { module.exports = function(progressRatio) {
const dashOffset = (1 - progressRatio) * circumference; const dashOffset = (1 - progressRatio) * circumference;
const percentComplete = percent(progressRatio); const percentComplete = percent(progressRatio);
const div = html` return html`
<div class="progress"> <div class="progress">
<svg <svg
width="${oDiameter}" width="${oDiameter}"
@ -37,5 +37,4 @@ module.exports = function(progressRatio) {
</svg> </svg>
</div> </div>
`; `;
return div;
}; };

View File

@ -5,6 +5,25 @@ module.exports = function(selected, options, translate, changed) {
const id = `select-${Math.random()}`; const id = `select-${Math.random()}`;
let x = selected; let x = selected;
return html`
<div class="selectbox">
<div onclick=${toggle}>
<span class="link">${translate(selected)}</span>
<svg width="32" height="32">
<polygon points="8 18 17 28 26 18" fill="#0094fb"/>
</svg>
</div>
<ul id="${id}" class="selectbox__options">
${options.map(
i => html`
<li
class="selectbox__option"
onclick=${choose}
data-value="${i}">${number(i)}</li>`
)}
</ul>
</div>`;
function close() { function close() {
const ul = document.getElementById(id); const ul = document.getElementById(id);
const body = document.querySelector('body'); const body = document.querySelector('body');
@ -37,21 +56,4 @@ module.exports = function(selected, options, translate, changed) {
} }
close(); close();
} }
return html`
<div class="selectbox">
<div onclick=${toggle}>
<span class="link">${translate(selected)}</span>
<svg width="32" height="32">
<polygon points="8 18 17 28 26 18" fill="#0094fb"/>
</svg>
</div>
<ul id="${id}" class="selectbox__options">
${options.map(
i =>
html`<li class="selectbox__option" onclick=${choose} data-value="${i}">${number(
i
)}</li>`
)}
</ul>
</div>`;
}; };

View File

@ -15,7 +15,7 @@
left: 0; left: 0;
padding: 0; padding: 0;
margin: 40px 0; margin: 40px 0;
background-color: white; background-color: var(--pageBGColor);
border: 1px solid rgba(12, 12, 13, 0.3); border: 1px solid rgba(12, 12, 13, 0.3);
border-radius: 4px; border-radius: 4px;
box-shadow: 1px 2px 4px rgba(12, 12, 13, 0.3); box-shadow: 1px 2px 4px rgba(12, 12, 13, 0.3);

View File

@ -3,10 +3,13 @@ const passwordInput = require('../passwordInput');
module.exports = function(state, emit) { module.exports = function(state, emit) {
const file = state.storage.getFileById(state.params.id); const file = state.storage.getFileById(state.params.id);
const div = html`
return html`
<div class="setPasswordSection"> <div class="setPasswordSection">
<div class="checkbox"> <div class="checkbox">
<input <input
${file.hasPassword ? 'disabled' : ''}
${file.hasPassword ? 'checked' : ''}
class="checkbox__input" class="checkbox__input"
id="add-password" id="add-password"
type="checkbox" type="checkbox"
@ -16,22 +19,9 @@ module.exports = function(state, emit) {
${state.translate('requirePasswordCheckbox')} ${state.translate('requirePasswordCheckbox')}
</label> </label>
</div> </div>
${passwordInput( ${passwordInput(file, state, emit)}
state.translate('unlockInputPlaceholder'),
state.translate('addPasswordButton'),
addPassword
)}
</div>`; </div>`;
function addPassword(event) {
event.preventDefault();
const password = document.getElementById('password-input').value;
if (password.length > 0) {
emit('password', { password, file });
}
return false;
}
function togglePasswordInput(e) { function togglePasswordInput(e) {
const unlockInput = document.getElementById('password-input'); const unlockInput = document.getElementById('password-input');
const boxChecked = e.target.checked; const boxChecked = e.target.checked;
@ -44,6 +34,4 @@ module.exports = function(state, emit) {
unlockInput.value = ''; unlockInput.value = '';
} }
} }
return div;
}; };

View File

@ -11,7 +11,7 @@
.checkbox__input { .checkbox__input {
position: absolute; position: absolute;
visibility: collapse; opacity: 0;
} }
.checkbox__label { .checkbox__label {
@ -31,12 +31,13 @@
border-radius: 2px; border-radius: 2px;
} }
.checkbox__input:focus + .checkbox__label::before,
.checkbox:hover .checkbox__label::before { .checkbox:hover .checkbox__label::before {
border: 1px solid var(--primaryControlBGColor); border: 1px solid var(--primaryControlBGColor);
} }
.checkbox__input:checked + .checkbox__label { .checkbox__input:checked + .checkbox__label {
color: #000; color: var(--textColor);
} }
.checkbox__input:checked + .checkbox__label::before { .checkbox__input:checked + .checkbox__label::before {
@ -44,6 +45,19 @@
background-position: 2px 1px; background-position: 2px 1px;
} }
.checkbox__input:disabled + .checkbox__label {
cursor: auto;
}
.checkbox__input:disabled + .checkbox__label::before {
background-image: url('../assets/check-16.svg');
background-repeat: no-repeat;
background-size: 18px 18px;
border-color: var(--successControlBGColor);
background-color: var(--successControlBGColor);
cursor: auto;
}
@media (max-device-width: 520px), (max-width: 520px) { @media (max-device-width: 520px), (max-width: 520px) {
.setPasswordSection { .setPasswordSection {
align-self: center; align-self: center;

17
assets/spinner.svg Normal file
View File

@ -0,0 +1,17 @@
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#fff">
<g fill="none" fill-rule="evenodd">
<g transform="translate(1 1)" stroke-width="2">
<circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"/>
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 694 B

View File

@ -113,3 +113,5 @@ enableJavascript = Please enable JavaScript and try again.
expiresHoursMinutes = { $hours }h { $minutes }m expiresHoursMinutes = { $hours }h { $minutes }m
# A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m" # A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m"
expiresMinutes = { $minutes }m expiresMinutes = { $minutes }m
# A short status message shown when a password is successfully set
passwordIsSet = Password set