fox-send/app/ui/archiveTile.js

544 lines
15 KiB
JavaScript
Raw Normal View History

/* global Android */
2019-01-16 17:05:39 +00:00
2018-10-25 02:07:10 +00:00
const html = require('choo/html');
const raw = require('choo/html/raw');
const assets = require('../../common/assets');
2019-01-16 21:20:15 +00:00
const {
bytes,
copyToClipboard,
list,
percent,
2019-01-23 23:10:09 +00:00
platform,
2019-01-16 21:20:15 +00:00
timeLeft
} = require('../utils');
2018-10-25 02:07:10 +00:00
const expiryOptions = require('./expiryOptions');
function expiryInfo(translate, archive) {
const l10n = timeLeft(archive.expiresAt - Date.now());
return raw(
translate('archiveExpiryInfo', {
2018-10-25 02:07:10 +00:00
downloadCount: translate('downloadCount', {
num: archive.dlimit - archive.dtotal
}),
timespan: translate(l10n.id, l10n)
})
);
}
2018-10-30 02:06:15 +00:00
function password(state) {
const MAX_LENGTH = 32;
return html`
2019-02-13 03:14:01 +00:00
<div class="mb-2 px-1">
2018-11-09 00:24:32 +00:00
<div class="checkbox inline-block mr-3">
<input
id="add-password"
type="checkbox"
2019-02-12 19:50:06 +00:00
${state.archive.password ? 'checked' : ''}
2018-11-09 00:24:32 +00:00
autocomplete="off"
onchange="${togglePasswordInput}"
/>
<label for="add-password">
${state.translate('addPassword')}
2018-11-09 00:24:32 +00:00
</label>
</div>
2018-10-30 02:06:15 +00:00
<input
2018-11-09 00:24:32 +00:00
id="password-input"
2019-02-12 19:50:06 +00:00
class="${state.archive.password
2019-01-23 23:10:09 +00:00
? ''
2019-02-13 19:14:53 +00:00
: 'invisible'} border rounded focus:border-blue-dark leading-normal my-1 py-1 px-2 h-8"
2018-10-30 02:06:15 +00:00
autocomplete="off"
2018-11-09 00:24:32 +00:00
maxlength="${MAX_LENGTH}"
type="password"
oninput="${inputChanged}"
onfocus="${focused}"
placeholder="${state.translate('unlockInputPlaceholder')}"
2019-02-12 19:50:06 +00:00
value="${state.archive.password || ''}"
2018-11-09 00:24:32 +00:00
/>
<label
id="password-msg"
for="password-input"
2019-02-13 03:14:01 +00:00
class="block text-xs text-grey-darker"
2018-11-09 00:24:32 +00:00
></label>
2018-10-30 02:06:15 +00:00
</div>
2018-11-09 00:24:32 +00:00
`;
2018-10-30 02:06:15 +00:00
function togglePasswordInput(event) {
event.stopPropagation();
const checked = event.target.checked;
const input = document.getElementById('password-input');
if (checked) {
input.classList.remove('invisible');
input.focus();
} else {
input.classList.add('invisible');
input.value = '';
document.getElementById('password-msg').textContent = '';
2019-02-12 19:50:06 +00:00
state.archive.password = null;
2018-10-30 02:06:15 +00:00
}
}
function inputChanged() {
const passwordInput = document.getElementById('password-input');
const pwdmsg = document.getElementById('password-msg');
const password = passwordInput.value;
const length = password.length;
if (length === MAX_LENGTH) {
pwdmsg.textContent = state.translate('maxPasswordLength', {
length: MAX_LENGTH
});
} else {
pwdmsg.textContent = '';
}
2019-02-12 19:50:06 +00:00
state.archive.password = password;
2018-10-30 02:06:15 +00:00
}
function focused(event) {
event.preventDefault();
const el = document.getElementById('password-input');
if (el.placeholder !== state.translate('unlockInputPlaceholder')) {
el.placeholder = '';
}
}
}
2018-10-25 02:07:10 +00:00
function fileInfo(file, action) {
return html`
<send-file class="flex flex-row items-center p-3 w-full">
2019-02-14 20:01:14 +00:00
<img class="h-8" src="${assets.get('blue_file.svg')}"/>
2018-11-02 09:27:59 +00:00
<p class="ml-4 w-full">
2019-02-26 22:52:37 +00:00
<h1 class="text-base font-medium word-break-all">${file.name}</h1>
<div class="text-sm font-normal opacity-75 pt-1">${bytes(
2018-11-02 09:27:59 +00:00
file.size
)}</div>
2018-10-25 02:07:10 +00:00
</p>
${action}
</send-file>`;
2018-10-25 02:07:10 +00:00
}
2019-02-27 01:22:45 +00:00
function archiveInfo(archive, action) {
return html`
<p class="w-full flex items-center">
<img class="mr-3 flex-no-shrink" src="${assets.get('blue_file.svg')}"/>
2019-02-27 01:22:45 +00:00
<p class="flex-grow">
<h1 class="text-base font-medium word-break-all">${archive.name}</h1>
<div class="text-sm font-normal opacity-75 pt-1">${bytes(
archive.size
)}</div>
</p>
${action}
</p>`;
}
2018-10-25 02:07:10 +00:00
function archiveDetails(translate, archive) {
if (archive.manifest.files.length > 1) {
return html`
2018-11-09 00:24:32 +00:00
<details
2019-01-29 20:06:23 +00:00
class="w-full pb-1"
2018-11-09 00:24:32 +00:00
${archive.open ? 'open' : ''}
ontoggle="${toggled}"
>
<summary
class="flex items-center text-blue-dark text-sm cursor-pointer outline-none"
2018-11-09 00:24:32 +00:00
>
<svg
class="fill-current w-4 h-4 mr-1"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path
d="M12.95 10.707l.707-.707L8 4.343 6.586 5.757 10.828 10l-4.242 4.243L8 15.657l4.95-4.95z"
/>
</svg>
${translate('fileCount', {
num: archive.manifest.files.length
})}
</summary>
2019-02-19 19:25:40 +00:00
${list(archive.manifest.files.map(f => fileInfo(f)), 'list-reset')}
2018-11-09 00:24:32 +00:00
</details>
`;
2018-10-25 02:07:10 +00:00
}
2018-10-30 18:37:33 +00:00
function toggled(event) {
event.stopPropagation();
archive.open = event.target.open;
}
2018-10-25 02:07:10 +00:00
}
module.exports = function(state, emit, archive) {
2019-01-16 17:05:39 +00:00
const copyOrShare =
2019-03-10 04:40:06 +00:00
state.capabilities.share || platform() === 'android'
2019-01-16 17:05:39 +00:00
? html`
<button
2019-03-10 04:40:06 +00:00
class="text-blue-dark hover:text-blue-darker focus:text-blue-darker self-end flex items-end"
onclick=${share}
2019-03-10 04:56:33 +00:00
title="Share link"
2019-01-16 17:05:39 +00:00
>
2019-03-10 04:56:33 +00:00
<img src="${assets.get('share-24.svg')}" class="mr-2" />Share link
2019-01-16 17:05:39 +00:00
</button>
`
: html`
<button
2019-03-10 04:40:06 +00:00
class="text-blue-dark hover:text-blue-darker focus:text-blue-darker focus:outline self-end flex items-center"
onclick=${copy}
title="${state.translate('copyLinkButton')}"
2019-01-16 17:05:39 +00:00
>
2019-03-10 04:40:06 +00:00
<img src="${assets.get('copy-16.svg')}" class="mr-2" />
${state.translate('copyLinkButton')}
2019-01-16 17:05:39 +00:00
</button>
`;
2019-01-23 23:10:09 +00:00
const dl =
platform() === 'web'
? html`
<a
2019-02-14 20:23:27 +00:00
class="flex items-baseline text-blue-dark hover:text-blue-darker focus:text-blue-darker"
2019-01-23 23:10:09 +00:00
href="${archive.url}"
2019-02-21 17:24:43 +00:00
title="${state.translate('downloadButtonLabel')}"
2019-02-20 23:58:44 +00:00
tabindex="0"
2019-01-23 23:10:09 +00:00
>
2019-02-11 21:48:06 +00:00
<img src="${assets.get('dl.svg')}" class="mr-2" />
2019-01-23 23:10:09 +00:00
${state.translate('downloadButtonLabel')}
</a>
`
: html`
<div></div>
`;
2018-10-25 02:07:10 +00:00
return html`
2019-02-27 01:22:45 +00:00
<send-archive
id="archive-${archive.id}"
class="flex flex-col items-start rounded shadow-light bg-white p-4 w-full"
>
${archiveInfo(
archive,
html`
<input
type="image"
class="self-start flex-no-shrink text-white hover:opacity-75 focus:outline"
2019-02-27 01:22:45 +00:00
alt="${state.translate('deleteButtonHover')}"
title="${state.translate('deleteButtonHover')}"
src="${assets.get('close-16.svg')}"
onclick=${del}
/>
`
)}
<div class="text-sm opacity-75 w-full mt-2 mb-2">
${expiryInfo(state.translate, archive)}
</div>
${archiveDetails(state.translate, archive)}
<hr class="w-full border-t my-4" />
<div class="flex justify-between w-full">
${dl} ${copyOrShare}
</div>
</send-archive>
`;
2018-10-25 02:07:10 +00:00
function copy(event) {
event.stopPropagation();
copyToClipboard(archive.url);
const text = event.target.lastChild;
text.textContent = state.translate('copiedUrl');
setTimeout(
() => (text.textContent = state.translate('copyLinkButton')),
1000
);
2018-10-25 02:07:10 +00:00
}
function del(event) {
event.stopPropagation();
2019-02-12 19:50:06 +00:00
emit('delete', archive);
2018-10-25 02:07:10 +00:00
}
2019-01-16 17:05:39 +00:00
2019-03-10 04:40:06 +00:00
async function share(event) {
2019-01-16 17:05:39 +00:00
event.stopPropagation();
if (platform() === 'android') {
Android.shareUrl(archive.url);
} else {
2019-03-10 04:40:06 +00:00
try {
await navigator.share({
title: state.translate('-send-brand'),
text: `Download "${
archive.name
}" with Firefox Send: simple, safe file sharing`,
//state.translate('shareMessage', { name }),
url: archive.url
});
} catch (e) {
// ignore
}
}
2019-01-16 17:05:39 +00:00
}
2018-10-25 02:07:10 +00:00
};
module.exports.wip = function(state, emit) {
return html`
2019-02-19 19:25:40 +00:00
<send-upload-area class="flex flex-col bg-white h-full w-full" id="wip">
2019-01-23 23:10:09 +00:00
${list(
Array.from(state.archive.files)
.reverse()
2019-02-21 03:59:29 +00:00
.map(f =>
fileInfo(f, remove(f, state.translate('deleteButtonHover')))
),
2019-03-05 22:27:55 +00:00
'flex-shrink bg-grey-lightest rounded-t list-reset overflow-y-auto px-6 py-4 md:h-full md:max-h-half-screen',
2019-02-11 21:48:06 +00:00
'bg-white px-2 my-2 shadow-light rounded'
2019-01-23 23:10:09 +00:00
)}
2019-01-30 15:59:22 +00:00
<div
2019-03-05 22:27:55 +00:00
class="flex-no-shrink flex-grow flex items-end p-4 bg-grey-lightest rounded-b mb-1 font-medium"
2019-01-30 15:59:22 +00:00
>
2018-11-09 00:24:32 +00:00
<input
id="file-upload"
class="opacity-0 w-0 h-0 appearance-none absolute overflow-hidden"
2018-11-09 00:24:32 +00:00
type="file"
multiple
2019-02-20 23:58:44 +00:00
onfocus="${focus}"
onblur="${blur}"
2018-11-09 00:24:32 +00:00
onchange="${add}"
/>
<div
2019-02-22 15:42:08 +00:00
for="file-upload"
class="flex flex-row items-center justify-between w-full p-2"
2018-11-09 00:24:32 +00:00
>
2019-02-21 03:59:29 +00:00
<label
for="file-upload"
class="flex items-center cursor-pointer"
title="${state.translate('addFilesButton')}"
>
2019-01-23 23:10:09 +00:00
<img src="${assets.get('addfiles.svg')}" class="w-6 h-6 mr-2" />
${state.translate('addFilesButton')}
</label>
2018-11-17 01:17:57 +00:00
<div class="font-normal text-sm text-grey-darker">
${state.translate('totalSize', {
2019-02-22 15:42:08 +00:00
size: bytes(state.archive.size)
})}
2018-11-17 01:17:57 +00:00
</div>
2019-02-21 03:59:29 +00:00
</div>
2018-11-09 00:24:32 +00:00
</div>
${expiryOptions(state, emit)} ${password(state, emit)}
<button
id="upload-btn"
2019-02-20 23:58:44 +00:00
class="btn rounded-lg flex-no-shrink focus:outline"
title="${state.translate('uploadButton')}"
2018-11-09 00:24:32 +00:00
onclick="${upload}"
>
${state.translate('uploadButton')}
2018-11-09 00:24:32 +00:00
</button>
</send-upload-area>
2018-11-09 00:24:32 +00:00
`;
2018-10-25 02:07:10 +00:00
2019-02-20 23:58:44 +00:00
function focus(event) {
event.target.nextElementSibling.firstElementChild.classList.add('outline');
}
function blur(event) {
event.target.nextElementSibling.firstElementChild.classList.remove(
'outline'
);
}
2018-10-25 02:07:10 +00:00
function upload(event) {
2018-11-05 08:12:40 +00:00
window.scrollTo(0, 0);
2018-10-25 02:07:10 +00:00
event.preventDefault();
event.target.disabled = true;
if (!state.uploading) {
2019-02-12 19:50:06 +00:00
emit('upload');
2018-10-25 02:07:10 +00:00
}
}
function add(event) {
event.preventDefault();
const newFiles = Array.from(event.target.files);
emit('addFiles', { files: newFiles });
setTimeout(() => {
document
.querySelector('#wip > ul > li:first-child')
.scrollIntoView({ block: 'center' });
});
2018-10-25 02:07:10 +00:00
}
2019-02-21 03:59:29 +00:00
function remove(file, desc) {
2018-10-25 02:07:10 +00:00
return html`
2018-11-09 00:24:32 +00:00
<input
type="image"
2019-02-20 23:58:44 +00:00
class="self-center text-white ml-4 h-4 hover:opacity-75 focus:outline"
2019-02-21 03:59:29 +00:00
alt="${desc}"
title="${desc}"
2018-11-09 00:24:32 +00:00
src="${assets.get('close-16.svg')}"
onclick="${del}"
/>
`;
2018-10-25 02:07:10 +00:00
function del(event) {
event.stopPropagation();
emit('removeUpload', file);
}
}
};
module.exports.uploading = function(state, emit) {
const progress = state.transfer.progressRatio;
const progressPercent = percent(progress);
const archive = state.archive;
return html`
2019-02-27 01:22:45 +00:00
<send-upload-area
id="${archive.id}"
class="flex flex-col items-start rounded shadow-light bg-white p-4 w-full"
>
${archiveInfo(archive)}
<div class="text-xs text-grey-dark w-full mt-2 mb-2">
${expiryInfo(state.translate, {
dlimit: state.archive.dlimit,
dtotal: 0,
expiresAt: Date.now() + 500 + state.archive.timeLimit * 1000
})}
</div>
<div class="text-blue-dark text-sm font-medium mt-2">
${progressPercent}
</div>
<progress class="my-3" value="${progress}">${progressPercent}</progress>
<button
class="text-blue-dark hover:text-blue-darker focus:text-blue-darker self-end font-medium"
onclick=${cancel}
2019-03-05 22:44:06 +00:00
title="${state.translate('deletePopupCancel')}"
2019-02-27 01:22:45 +00:00
>
2019-03-05 22:44:06 +00:00
${state.translate('deletePopupCancel')}
2019-02-27 01:22:45 +00:00
</button>
</send-upload-area>
`;
2018-10-25 02:07:10 +00:00
function cancel(event) {
event.stopPropagation();
event.target.disabled = true;
emit('cancel');
}
};
module.exports.empty = function(state, emit) {
const upsell =
state.user.loggedIn || !state.capabilities.account
? ''
: html`
<button
class="center font-medium text-sm text-blue-dark hover:text-blue-darker focus:text-blue-darker mt-4 mb-2"
onclick="${event => {
event.stopPropagation();
emit('signup-cta', 'drop');
}}"
>
${state.translate('signInSizeBump', {
size: bytes(state.LIMITS.MAX_FILE_SIZE)
})}
</button>
`;
2018-10-25 02:07:10 +00:00
return html`
<send-upload-area
2019-02-11 21:48:06 +00:00
class="flex flex-col items-center justify-center border-2 border-dashed border-grey rounded px-6 py-16 h-full w-full"
2019-01-23 23:10:09 +00:00
onclick="${e => {
if (e.target.tagName !== 'LABEL') {
document.getElementById('file-upload').click();
2018-11-09 00:24:32 +00:00
}
2019-01-23 23:10:09 +00:00
}}"
2018-11-09 00:24:32 +00:00
>
<img src="${assets.get('addfiles.svg')}" width="48" height="48" />
<div class="pt-6 pb-2 text-center text-lg font-bold tracking-wide">
${state.translate('dragAndDropFiles')}
2018-11-09 00:24:32 +00:00
</div>
<div class="pb-6 text-center text-base">
${state.translate('orClickWithSize', {
size: bytes(state.user.maxSize)
})}
2018-11-09 00:24:32 +00:00
</div>
<input
id="file-upload"
2019-02-27 01:22:45 +00:00
class="opacity-0 w-0 h-0 appearance-none absolute"
2018-11-09 00:24:32 +00:00
type="file"
multiple
onfocus="${focus}"
onblur="${blur}"
2018-11-09 00:24:32 +00:00
onchange="${add}"
onclick="${e => e.stopPropagation()}"
/>
<label
for="file-upload"
role="button"
2019-02-11 21:48:06 +00:00
class="btn rounded-lg flex items-center mt-4"
title="${state.translate('addFilesButton', {
2019-03-01 00:31:37 +00:00
size: bytes(state.user.maxSize)
2019-02-22 15:42:08 +00:00
})}"
2018-11-09 00:24:32 +00:00
>
${state.translate('addFilesButton')}
2018-11-09 00:24:32 +00:00
</label>
${upsell}
</send-upload-area>
2018-11-09 00:24:32 +00:00
`;
2018-10-25 02:07:10 +00:00
function focus(event) {
2019-02-20 23:58:44 +00:00
event.target.nextElementSibling.classList.add('bg-blue-darker', 'outline');
}
function blur(event) {
2019-02-20 23:58:44 +00:00
event.target.nextElementSibling.classList.remove(
'bg-blue-darker',
'outline'
);
}
2018-10-25 02:07:10 +00:00
function add(event) {
event.preventDefault();
const newFiles = Array.from(event.target.files);
emit('addFiles', { files: newFiles });
}
};
module.exports.preview = function(state, emit) {
const archive = state.fileInfo;
2018-10-30 18:37:33 +00:00
if (archive.open === undefined) {
archive.open = true;
}
2019-02-26 22:52:37 +00:00
const single = archive.manifest.files.length === 1;
const details = single
? ''
: html`
<div class="mt-4 h-full md:h-48 overflow-y-auto">
${archiveDetails(state.translate, archive)}
</div>
`;
2018-10-25 02:07:10 +00:00
return html`
2019-02-27 01:22:45 +00:00
<send-archive class="flex flex-col max-h-full bg-white p-4 w-full md:w-128">
<div class="border rounded py-3 px-6">
${archiveInfo(archive)} ${details}
</div>
<button
id="download-btn"
class="btn rounded-lg mt-4 w-full flex-no-shrink focus:outline"
title="${state.translate('downloadButtonLabel')}"
onclick=${download}
>
${state.translate('downloadButtonLabel')}
</button>
</send-archive>
`;
2018-10-25 02:07:10 +00:00
function download(event) {
event.preventDefault();
event.target.disabled = true;
emit('download', archive);
}
};
2019-01-30 15:50:17 +00:00
module.exports.downloading = function(state) {
2018-10-25 02:07:10 +00:00
const archive = state.fileInfo;
const progress = state.transfer.progressRatio;
const progressPercent = percent(progress);
return html`
2019-02-27 01:22:45 +00:00
<send-archive
class="flex flex-col bg-white rounded shadow-light p-4 w-full max-w-sm md:w-128"
>
${archiveInfo(archive)}
<div class="text-blue-dark text-sm font-medium mt-2">
${progressPercent}
</div>
<progress class="my-3" value="${progress}">${progressPercent}</progress>
</send-archive>
`;
2018-10-25 02:07:10 +00:00
};