mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2025-04-23 16:26:17 -04:00
Merge branch 'next' into translate
This commit is contained in:
commit
da56a7b6bc
60 changed files with 2586 additions and 1677 deletions
|
@ -19,42 +19,42 @@ class BrowserTabsConnector {
|
|||
}
|
||||
|
||||
static peerIsSameBrowser(peerId) {
|
||||
let peerIdsBrowser = JSON.parse(localStorage.getItem("peer_ids_browser"));
|
||||
let peerIdsBrowser = JSON.parse(localStorage.getItem('peer_ids_browser'));
|
||||
return peerIdsBrowser
|
||||
? peerIdsBrowser.indexOf(peerId) !== -1
|
||||
: false;
|
||||
}
|
||||
|
||||
static async addPeerIdToLocalStorage() {
|
||||
const peerId = sessionStorage.getItem("peer_id");
|
||||
const peerId = sessionStorage.getItem('peer_id');
|
||||
if (!peerId) return false;
|
||||
|
||||
let peerIdsBrowser = [];
|
||||
let peerIdsBrowserOld = JSON.parse(localStorage.getItem("peer_ids_browser"));
|
||||
let peerIdsBrowserOld = JSON.parse(localStorage.getItem('peer_ids_browser'));
|
||||
|
||||
if (peerIdsBrowserOld) peerIdsBrowser.push(...peerIdsBrowserOld);
|
||||
peerIdsBrowser.push(peerId);
|
||||
peerIdsBrowser = peerIdsBrowser.filter(onlyUnique);
|
||||
localStorage.setItem("peer_ids_browser", JSON.stringify(peerIdsBrowser));
|
||||
localStorage.setItem('peer_ids_browser', JSON.stringify(peerIdsBrowser));
|
||||
|
||||
return peerIdsBrowser;
|
||||
}
|
||||
|
||||
static async removePeerIdFromLocalStorage(peerId) {
|
||||
let peerIdsBrowser = JSON.parse(localStorage.getItem("peer_ids_browser"));
|
||||
let peerIdsBrowser = JSON.parse(localStorage.getItem('peer_ids_browser'));
|
||||
const index = peerIdsBrowser.indexOf(peerId);
|
||||
peerIdsBrowser.splice(index, 1);
|
||||
localStorage.setItem("peer_ids_browser", JSON.stringify(peerIdsBrowser));
|
||||
localStorage.setItem('peer_ids_browser', JSON.stringify(peerIdsBrowser));
|
||||
return peerId;
|
||||
}
|
||||
|
||||
|
||||
static async removeOtherPeerIdsFromLocalStorage() {
|
||||
const peerId = sessionStorage.getItem("peer_id");
|
||||
const peerId = sessionStorage.getItem('peer_id');
|
||||
if (!peerId) return false;
|
||||
|
||||
let peerIdsBrowser = [peerId];
|
||||
localStorage.setItem("peer_ids_browser", JSON.stringify(peerIdsBrowser));
|
||||
localStorage.setItem('peer_ids_browser', JSON.stringify(peerIdsBrowser));
|
||||
return peerIdsBrowser;
|
||||
}
|
||||
}
|
1
public/scripts/heic2any.min.js
vendored
Normal file
1
public/scripts/heic2any.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -2,33 +2,30 @@ class Localization {
|
|||
constructor() {
|
||||
Localization.defaultLocale = "en";
|
||||
Localization.supportedLocales = ["ar", "de", "en", "es", "fr", "id", "it", "ja", "nb", "nl", "ro", "ru", "tr", "zh-CN","pt-BR"];
|
||||
Localization.supportedLocalesRTL = ["ar"];
|
||||
Localization.supportedLocalesRtl = ["ar"];
|
||||
|
||||
Localization.translations = {};
|
||||
Localization.defaultTranslations = {};
|
||||
|
||||
Localization.systemLocale = Localization.getSupportedOrDefault(navigator.languages);
|
||||
|
||||
let storedLanguageCode = localStorage.getItem("language-code");
|
||||
let storedLanguageCode = localStorage.getItem('language_code');
|
||||
|
||||
Localization.initialLocale = storedLanguageCode && Localization.isSupported(storedLanguageCode)
|
||||
? storedLanguageCode
|
||||
: Localization.systemLocale;
|
||||
|
||||
Localization
|
||||
.setTranslation(Localization.initialLocale)
|
||||
.then(_ => {
|
||||
console.log("Initial translation successful.");
|
||||
Events.fire("initial-translation-loaded");
|
||||
});
|
||||
}
|
||||
|
||||
static isSupported(locale) {
|
||||
return Localization.supportedLocales.indexOf(locale) > -1;
|
||||
}
|
||||
|
||||
static isRTLLanguage(locale) {
|
||||
return Localization.supportedLocalesRTL.indexOf(locale) > -1;
|
||||
static isRtlLanguage(locale) {
|
||||
return Localization.supportedLocalesRtl.indexOf(locale) > -1;
|
||||
}
|
||||
|
||||
static isCurrentLocaleRtl() {
|
||||
return Localization.isRtlLanguage(Localization.locale);
|
||||
}
|
||||
|
||||
static getSupportedOrDefault(locales) {
|
||||
|
@ -41,6 +38,10 @@ class Localization {
|
|||
|| Localization.defaultLocale;
|
||||
}
|
||||
|
||||
async setInitialTranslation() {
|
||||
await Localization.setTranslation(Localization.initialLocale)
|
||||
}
|
||||
|
||||
static async setTranslation(locale) {
|
||||
if (!locale) locale = Localization.systemLocale;
|
||||
|
||||
|
@ -49,7 +50,7 @@ class Localization {
|
|||
|
||||
const htmlRootNode = document.querySelector('html');
|
||||
|
||||
if (Localization.isRTLLanguage(locale)) {
|
||||
if (Localization.isRtlLanguage(locale)) {
|
||||
htmlRootNode.setAttribute('dir', 'rtl');
|
||||
}
|
||||
else {
|
||||
|
@ -85,7 +86,7 @@ class Localization {
|
|||
}
|
||||
|
||||
static isSystemLocale() {
|
||||
return !localStorage.getItem('language-code');
|
||||
return !localStorage.getItem('language_code');
|
||||
}
|
||||
|
||||
static async fetchTranslationsFor(newLocale) {
|
||||
|
@ -121,7 +122,7 @@ class Localization {
|
|||
}
|
||||
}
|
||||
|
||||
static getTranslation(key, attr=null, data={}, useDefault=false) {
|
||||
static getTranslation(key, attr = null, data = {}, useDefault = false) {
|
||||
const keys = key.split(".");
|
||||
|
||||
let translationCandidates = useDefault
|
||||
|
@ -142,27 +143,45 @@ class Localization {
|
|||
translation = translationCandidates[lastKey];
|
||||
|
||||
for (let j in data) {
|
||||
translation = translation.replace(`{{${j}}}`, data[j]);
|
||||
if (translation.includes(`{{${j}}}`)) {
|
||||
translation = translation.replace(`{{${j}}}`, data[j]);
|
||||
} else {
|
||||
console.warn(`Translation for your language ${Localization.locale.toUpperCase()} misses at least one data placeholder:`, key, attr, data);
|
||||
Localization.logHelpCallKey(key);
|
||||
Localization.logHelpCall();
|
||||
translation = "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
translation = "";
|
||||
}
|
||||
|
||||
if (!translation) {
|
||||
if (!useDefault) {
|
||||
translation = this.getTranslation(key, attr, data, true);
|
||||
console.warn(`Missing translation entry for your language ${Localization.locale.toUpperCase()}. Using ${Localization.defaultLocale.toUpperCase()} instead.`, key, attr);
|
||||
console.warn(`Translate this string here: https://hosted.weblate.org/browse/pairdrop/pairdrop-spa/${Localization.locale.toLowerCase()}/?q=${key}`)
|
||||
console.log("Help translating PairDrop: https://hosted.weblate.org/engage/pairdrop/");
|
||||
Localization.logHelpCallKey(key);
|
||||
Localization.logHelpCall();
|
||||
translation = this.getTranslation(key, attr, data, true);
|
||||
}
|
||||
else {
|
||||
console.warn("Missing translation in default language:", key, attr);
|
||||
Localization.logHelpCall();
|
||||
}
|
||||
}
|
||||
|
||||
return Localization.escapeHTML(translation);
|
||||
}
|
||||
|
||||
static logHelpCall() {
|
||||
console.log("Help translating PairDrop: https://hosted.weblate.org/engage/pairdrop/");
|
||||
}
|
||||
|
||||
static logHelpCallKey(key) {
|
||||
console.warn(`Translate this string here: https://hosted.weblate.org/browse/pairdrop/pairdrop-spa/${Localization.locale.toLowerCase()}/?q=${key}`);
|
||||
}
|
||||
|
||||
static escapeHTML(unsafeText) {
|
||||
let div = document.createElement('div');
|
||||
div.innerText = unsafeText;
|
||||
|
|
|
@ -1,39 +1,69 @@
|
|||
class PairDrop {
|
||||
|
||||
constructor() {
|
||||
this.$header = $$('header.opacity-0');
|
||||
this.$center = $$('#center');
|
||||
this.$footer = $$('footer');
|
||||
this.$xNoPeers = $$('x-no-peers');
|
||||
this.$headerNotificationButton = $('notification');
|
||||
this.$editPairedDevicesHeaderBtn = $('edit-paired-devices');
|
||||
this.$footerInstructionsPairedDevices = $$('.discovery-wrapper .badge-room-secret');
|
||||
this.$head = $$('head');
|
||||
this.$installBtn = $('install');
|
||||
this.$headerNotificationBtn = $('notification');
|
||||
this.$headerEditPairedDevicesBtn = $('edit-paired-devices');
|
||||
this.$footerPairedDevicesBadge = $$('.discovery-wrapper .badge-room-secret');
|
||||
this.$headerInstallBtn = $('install');
|
||||
|
||||
this.deferredStyles = [
|
||||
"styles/deferred-styles.css"
|
||||
];
|
||||
this.deferredScripts = [
|
||||
"scripts/browser-tabs-connector.js",
|
||||
"scripts/util.js",
|
||||
"scripts/network.js",
|
||||
"scripts/ui.js",
|
||||
"scripts/qr-code.min.js",
|
||||
"scripts/zip.min.js",
|
||||
"scripts/no-sleep.min.js",
|
||||
"scripts/heic2any.min.js"
|
||||
];
|
||||
|
||||
this.registerServiceWorker();
|
||||
|
||||
Events.on('beforeinstallprompt', e => this.onPwaInstallable(e));
|
||||
|
||||
const persistentStorage = new PersistentStorage();
|
||||
const themeUI = new ThemeUI();
|
||||
const backgroundCanvas = new BackgroundCanvas();
|
||||
this.persistentStorage = new PersistentStorage();
|
||||
this.localization = new Localization();
|
||||
this.themeUI = new ThemeUI();
|
||||
this.backgroundCanvas = new BackgroundCanvas();
|
||||
this.headerUI = new HeaderUI();
|
||||
this.centerUI = new CenterUI();
|
||||
this.footerUI = new FooterUI();
|
||||
|
||||
Events.on('initial-translation-loaded', _ => {
|
||||
// FooterUI needs translations
|
||||
const footerUI = new FooterUI();
|
||||
this.initialize()
|
||||
.then(_ => {
|
||||
console.log("Initialization completed.");
|
||||
});
|
||||
}
|
||||
|
||||
Events.on('fade-in-ui', _ => this.fadeInUI())
|
||||
Events.on('fade-in-header', _ => this.fadeInHeader())
|
||||
async initialize() {
|
||||
// Translate page before fading in
|
||||
await this.localization.setInitialTranslation()
|
||||
console.log("Initial translation successful.");
|
||||
|
||||
// Evaluate UI elements and fade in UI
|
||||
this.evaluateUI();
|
||||
// Show "Loading..." until connected to WsServer
|
||||
await this.footerUI.showLoading();
|
||||
|
||||
// Load deferred assets
|
||||
this.loadDeferredAssets();
|
||||
});
|
||||
// Evaluate css shifting UI elements and fade in UI elements
|
||||
await this.evaluatePermissionsAndRoomSecrets();
|
||||
await this.headerUI.evaluateOverflowing();
|
||||
await this.headerUI.fadeIn();
|
||||
await this.footerUI._evaluateFooterBadges();
|
||||
await this.footerUI.fadeIn();
|
||||
await this.centerUI.fadeIn();
|
||||
await this.backgroundCanvas.fadeIn();
|
||||
|
||||
// Translate page -> fires 'initial-translation-loaded' on finish
|
||||
const localization = new Localization();
|
||||
// Load deferred assets
|
||||
await this.loadDeferredAssets();
|
||||
console.log("Loading of deferred assets completed.");
|
||||
|
||||
await this.hydrate();
|
||||
console.log("UI hydrated.");
|
||||
|
||||
// Evaluate url params as soon as ws is connected
|
||||
Events.on('ws-connected', _ => this.evaluateUrlParams(), {once: true});
|
||||
}
|
||||
|
||||
registerServiceWorker() {
|
||||
|
@ -50,130 +80,148 @@ class PairDrop {
|
|||
onPwaInstallable(e) {
|
||||
if (!window.matchMedia('(display-mode: minimal-ui)').matches) {
|
||||
// only display install btn when not installed
|
||||
this.$installBtn.removeAttribute('hidden');
|
||||
this.$installBtn.addEventListener('click', () => {
|
||||
this.$installBtn.setAttribute('hidden', true);
|
||||
this.$headerInstallBtn.removeAttribute('hidden');
|
||||
this.$headerInstallBtn.addEventListener('click', () => {
|
||||
this.$headerInstallBtn.setAttribute('hidden', true);
|
||||
e.prompt();
|
||||
});
|
||||
}
|
||||
return e.preventDefault();
|
||||
}
|
||||
|
||||
evaluateUI() {
|
||||
async evaluatePermissionsAndRoomSecrets() {
|
||||
// Check whether notification permissions have already been granted
|
||||
if ('Notification' in window && Notification.permission !== 'granted') {
|
||||
this.$headerNotificationButton.removeAttribute('hidden');
|
||||
this.$headerNotificationBtn.removeAttribute('hidden');
|
||||
}
|
||||
|
||||
PersistentStorage
|
||||
.getAllRoomSecrets()
|
||||
.then(roomSecrets => {
|
||||
if (roomSecrets.length > 0) {
|
||||
this.$editPairedDevicesHeaderBtn.removeAttribute('hidden');
|
||||
this.$footerInstructionsPairedDevices.removeAttribute('hidden');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
Events.fire('evaluate-footer-badges');
|
||||
Events.fire('fade-in-header');
|
||||
});
|
||||
let roomSecrets = await PersistentStorage.getAllRoomSecrets();
|
||||
if (roomSecrets.length > 0) {
|
||||
this.$headerEditPairedDevicesBtn.removeAttribute('hidden');
|
||||
this.$footerPairedDevicesBadge.removeAttribute('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
fadeInUI() {
|
||||
this.$center.classList.remove('opacity-0');
|
||||
this.$footer.classList.remove('opacity-0');
|
||||
|
||||
// Prevent flickering on load
|
||||
setTimeout(() => {
|
||||
this.$xNoPeers.classList.remove('no-animation-on-load');
|
||||
}, 600);
|
||||
}
|
||||
|
||||
fadeInHeader() {
|
||||
this.$header.classList.remove('opacity-0');
|
||||
}
|
||||
|
||||
loadDeferredAssets() {
|
||||
async loadDeferredAssets() {
|
||||
console.log("Load deferred assets");
|
||||
this.deferredStyles = [
|
||||
"styles/deferred-styles.css"
|
||||
];
|
||||
this.deferredScripts = [
|
||||
"scripts/browser-tabs-connector.js",
|
||||
"scripts/util.js",
|
||||
"scripts/network.js",
|
||||
"scripts/ui.js",
|
||||
"scripts/qr-code.min.js",
|
||||
"scripts/zip.min.js",
|
||||
"scripts/no-sleep.min.js"
|
||||
];
|
||||
this.deferredStyles.forEach(url => this.loadStyleSheet(url, _ => this.onStyleLoaded(url)))
|
||||
this.deferredScripts.forEach(url => this.loadScript(url, _ => this.onScriptLoaded(url)))
|
||||
}
|
||||
|
||||
loadStyleSheet(url, callback) {
|
||||
let stylesheet = document.createElement('link');
|
||||
stylesheet.rel = 'stylesheet';
|
||||
stylesheet.href = url;
|
||||
stylesheet.type = 'text/css';
|
||||
stylesheet.onload = callback;
|
||||
this.$head.appendChild(stylesheet);
|
||||
}
|
||||
|
||||
loadScript(url, callback) {
|
||||
let script = document.createElement("script");
|
||||
script.src = url;
|
||||
script.onload = callback;
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
|
||||
onStyleLoaded(url) {
|
||||
// remove entry from array
|
||||
const index = this.deferredStyles.indexOf(url);
|
||||
if (index !== -1) {
|
||||
this.deferredStyles.splice(index, 1);
|
||||
for (const url of this.deferredStyles) {
|
||||
await this.loadAndApplyStylesheet(url);
|
||||
}
|
||||
this.onAssetLoaded();
|
||||
}
|
||||
|
||||
onScriptLoaded(url) {
|
||||
// remove entry from array
|
||||
const index = this.deferredScripts.indexOf(url);
|
||||
if (index !== -1) {
|
||||
this.deferredScripts.splice(index, 1);
|
||||
for (const url of this.deferredScripts) {
|
||||
await this.loadAndApplyScript(url);
|
||||
}
|
||||
this.onAssetLoaded();
|
||||
}
|
||||
|
||||
onAssetLoaded() {
|
||||
if (this.deferredScripts.length || this.deferredStyles.length) return;
|
||||
loadStyleSheet(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let stylesheet = document.createElement('link');
|
||||
stylesheet.rel = 'stylesheet';
|
||||
stylesheet.href = url;
|
||||
stylesheet.type = 'text/css';
|
||||
stylesheet.onload = resolve;
|
||||
stylesheet.onerror = reject;
|
||||
|
||||
console.log("Loading of deferred assets completed. Start UI hydration.");
|
||||
|
||||
this.hydrate();
|
||||
document.head.appendChild(stylesheet);
|
||||
});
|
||||
}
|
||||
|
||||
hydrate() {
|
||||
const peersUI = new PeersUI();
|
||||
const languageSelectDialog = new LanguageSelectDialog();
|
||||
const receiveFileDialog = new ReceiveFileDialog();
|
||||
const receiveRequestDialog = new ReceiveRequestDialog();
|
||||
const sendTextDialog = new SendTextDialog();
|
||||
const receiveTextDialog = new ReceiveTextDialog();
|
||||
const pairDeviceDialog = new PairDeviceDialog();
|
||||
const clearDevicesDialog = new EditPairedDevicesDialog();
|
||||
const publicRoomDialog = new PublicRoomDialog();
|
||||
const base64ZipDialog = new Base64ZipDialog();
|
||||
const toast = new Toast();
|
||||
const notifications = new Notifications();
|
||||
const networkStatusUI = new NetworkStatusUI();
|
||||
const webShareTargetUI = new WebShareTargetUI();
|
||||
const webFileHandlersUI = new WebFileHandlersUI();
|
||||
const noSleepUI = new NoSleepUI();
|
||||
const broadCast = new BrowserTabsConnector();
|
||||
const server = new ServerConnection();
|
||||
const peers = new PeersManager(server);
|
||||
console.log("UI hydrated.")
|
||||
async loadAndApplyStylesheet(url) {
|
||||
try {
|
||||
await this.loadStyleSheet(url);
|
||||
console.log(`Stylesheet loaded successfully: ${url}`);
|
||||
} catch (error) {
|
||||
console.error('Error loading stylesheet:', error);
|
||||
}
|
||||
}
|
||||
|
||||
loadScript(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let script = document.createElement("script");
|
||||
script.src = url;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
|
||||
document.body.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
async loadAndApplyScript(url) {
|
||||
try {
|
||||
await this.loadScript(url);
|
||||
console.log(`Script loaded successfully: ${url}`);
|
||||
} catch (error) {
|
||||
console.error('Error loading script:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async hydrate() {
|
||||
this.peersUI = new PeersUI();
|
||||
this.languageSelectDialog = new LanguageSelectDialog();
|
||||
this.receiveFileDialog = new ReceiveFileDialog();
|
||||
this.receiveRequestDialog = new ReceiveRequestDialog();
|
||||
this.sendTextDialog = new SendTextDialog();
|
||||
this.receiveTextDialog = new ReceiveTextDialog();
|
||||
this.pairDeviceDialog = new PairDeviceDialog();
|
||||
this.clearDevicesDialog = new EditPairedDevicesDialog();
|
||||
this.publicRoomDialog = new PublicRoomDialog();
|
||||
this.base64Dialog = new Base64Dialog();
|
||||
this.shareTextDialog = new ShareTextDialog();
|
||||
this.toast = new Toast();
|
||||
this.notifications = new Notifications();
|
||||
this.networkStatusUI = new NetworkStatusUI();
|
||||
this.webShareTargetUI = new WebShareTargetUI();
|
||||
this.webFileHandlersUI = new WebFileHandlersUI();
|
||||
this.noSleepUI = new NoSleepUI();
|
||||
this.broadCast = new BrowserTabsConnector();
|
||||
this.server = new ServerConnection();
|
||||
this.peers = new PeersManager(this.server);
|
||||
}
|
||||
|
||||
async evaluateUrlParams() {
|
||||
// get url params
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const hash = window.location.hash.substring(1);
|
||||
|
||||
// evaluate url params
|
||||
if (urlParams.has('pair_key')) {
|
||||
const pairKey = urlParams.get('pair_key');
|
||||
this.pairDeviceDialog._pairDeviceJoin(pairKey);
|
||||
}
|
||||
else if (urlParams.has('room_id')) {
|
||||
const roomId = urlParams.get('room_id');
|
||||
this.publicRoomDialog._joinPublicRoom(roomId);
|
||||
}
|
||||
else if (urlParams.has('base64text')) {
|
||||
const base64Text = urlParams.get('base64text');
|
||||
await this.base64Dialog.evaluateBase64Text(base64Text, hash);
|
||||
}
|
||||
else if (urlParams.has('base64zip')) {
|
||||
const base64Zip = urlParams.get('base64zip');
|
||||
await this.base64Dialog.evaluateBase64Zip(base64Zip, hash);
|
||||
}
|
||||
else if (urlParams.has("share_target")) {
|
||||
const shareTargetType = urlParams.get("share_target");
|
||||
const title = urlParams.get('title') || '';
|
||||
const text = urlParams.get('text') || '';
|
||||
const url = urlParams.get('url') || '';
|
||||
await this.webShareTargetUI.evaluateShareTarget(shareTargetType, title, text, url);
|
||||
}
|
||||
else if (urlParams.has("file_handler")) {
|
||||
await this.webFileHandlersUI.evaluateLaunchQueue();
|
||||
}
|
||||
else if (urlParams.has("init")) {
|
||||
const init = urlParams.get("init");
|
||||
if (init === "pair") {
|
||||
this.pairDeviceDialog._pairDeviceInitiate();
|
||||
}
|
||||
else if (init === "public_room") {
|
||||
this.publicRoomDialog._createPublicRoom();
|
||||
}
|
||||
}
|
||||
|
||||
// remove url params from url
|
||||
const urlWithoutParams = getUrlWithoutArguments();
|
||||
window.history.replaceState({}, "Rewrite URL", urlWithoutParams);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -433,47 +433,6 @@ class Peer {
|
|||
: false;
|
||||
}
|
||||
|
||||
getResizedImageDataUrl(file, width = undefined, height = undefined, quality = 0.7) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let image = new Image();
|
||||
image.src = URL.createObjectURL(file);
|
||||
image.onload = _ => {
|
||||
let imageWidth = image.width;
|
||||
let imageHeight = image.height;
|
||||
let canvas = document.createElement('canvas');
|
||||
|
||||
// resize the canvas and draw the image data into it
|
||||
if (width && height) {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
}
|
||||
else if (width) {
|
||||
canvas.width = width;
|
||||
canvas.height = Math.floor(imageHeight * width / imageWidth)
|
||||
}
|
||||
else if (height) {
|
||||
canvas.width = Math.floor(imageWidth * height / imageHeight);
|
||||
canvas.height = height;
|
||||
}
|
||||
else {
|
||||
canvas.width = imageWidth;
|
||||
canvas.height = imageHeight
|
||||
}
|
||||
|
||||
var ctx = canvas.getContext("2d");
|
||||
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||
|
||||
let dataUrl = canvas.toDataURL("image/jpeg", quality);
|
||||
resolve(dataUrl);
|
||||
}
|
||||
image.onerror = _ => reject(`Could not create an image thumbnail from type ${file.type}`);
|
||||
})
|
||||
.then(dataUrl => {
|
||||
return dataUrl;
|
||||
})
|
||||
.catch(e => console.error(e));
|
||||
}
|
||||
|
||||
async requestFileTransfer(files) {
|
||||
let header = [];
|
||||
let totalSize = 0;
|
||||
|
@ -493,7 +452,11 @@ class Peer {
|
|||
|
||||
let dataUrl = '';
|
||||
if (files[0].type.split('/')[0] === 'image') {
|
||||
dataUrl = await this.getResizedImageDataUrl(files[0], 400, null, 0.9);
|
||||
try {
|
||||
dataUrl = await getThumbnailAsDataUrl(files[0], 400, null, 0.9);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'prepare'})
|
||||
|
@ -1118,20 +1081,9 @@ class PeersManager {
|
|||
this.peers[detail.to]._respondToFileTransferRequest(detail.accepted);
|
||||
}
|
||||
|
||||
_onFilesSelected(message) {
|
||||
let inputFiles = Array.from(message.files);
|
||||
delete message.files;
|
||||
let files = [];
|
||||
const l = inputFiles.length;
|
||||
for (let i=0; i<l; i++) {
|
||||
// when filetype is empty guess via suffix
|
||||
const inputFile = inputFiles.shift();
|
||||
const file = inputFile.type
|
||||
? inputFile
|
||||
: new File([inputFile], inputFile.name, {type: mime.getMimeByFilename(inputFile.name)});
|
||||
files.push(file)
|
||||
}
|
||||
this.peers[message.to].requestFileTransfer(files);
|
||||
async _onFilesSelected(message) {
|
||||
let files = await mime.addMissingMimeTypesToFiles(message.files);
|
||||
await this.peers[message.to].requestFileTransfer(files);
|
||||
}
|
||||
|
||||
_onSendText(message) {
|
||||
|
|
|
@ -4,7 +4,7 @@ class PersistentStorage {
|
|||
PersistentStorage.logBrowserNotCapable();
|
||||
return;
|
||||
}
|
||||
const DBOpenRequest = window.indexedDB.open('pairdrop_store', 4);
|
||||
const DBOpenRequest = window.indexedDB.open('pairdrop_store', 5);
|
||||
DBOpenRequest.onerror = e => {
|
||||
PersistentStorage.logBrowserNotCapable();
|
||||
console.log('Error initializing database: ');
|
||||
|
@ -13,7 +13,7 @@ class PersistentStorage {
|
|||
DBOpenRequest.onsuccess = _ => {
|
||||
console.log('Database initialised.');
|
||||
};
|
||||
DBOpenRequest.onupgradeneeded = e => {
|
||||
DBOpenRequest.onupgradeneeded = async e => {
|
||||
const db = e.target.result;
|
||||
const txn = e.target.transaction;
|
||||
|
||||
|
@ -42,6 +42,14 @@ class PersistentStorage {
|
|||
roomSecretsObjectStore4.createIndex('display_name', 'display_name');
|
||||
roomSecretsObjectStore4.createIndex('auto_accept', 'auto_accept');
|
||||
}
|
||||
if (e.oldVersion <= 4) {
|
||||
// migrate to v5
|
||||
const editedDisplayNameOld = await PersistentStorage.get('editedDisplayName');
|
||||
if (editedDisplayNameOld) {
|
||||
await PersistentStorage.set('edited_display_name', editedDisplayNameOld);
|
||||
await PersistentStorage.delete('editedDisplayName');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,7 +149,7 @@ class PersistentStorage {
|
|||
return(secrets);
|
||||
} catch (e) {
|
||||
this.logBrowserNotCapable();
|
||||
return 0;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -111,15 +111,91 @@ class ThemeUI {
|
|||
}
|
||||
}
|
||||
|
||||
class HeaderUI {
|
||||
|
||||
constructor() {
|
||||
this.$header = $$('header');
|
||||
this.$expandBtn = $('expand');
|
||||
Events.on("resize", _ => this.evaluateOverflowing());
|
||||
this.$expandBtn.addEventListener('click', _ => this.onExpandBtnClick());
|
||||
}
|
||||
|
||||
async fadeIn() {
|
||||
this.$header.classList.remove('opacity-0');
|
||||
}
|
||||
|
||||
async evaluateOverflowing() {
|
||||
// remove and reset bracket icon before evaluating
|
||||
this.$expandBtn.setAttribute('hidden', true);
|
||||
this.$expandBtn.classList.add('flipped');
|
||||
|
||||
const rtlLocale = Localization.isCurrentLocaleRtl();
|
||||
let icon;
|
||||
const $headerIconsShown = document.querySelectorAll('body > header:first-of-type > *:not([hidden])');
|
||||
|
||||
for (let i= 1; i < $headerIconsShown.length; i++) {
|
||||
let isFurtherLeftThanLastIcon = $headerIconsShown[i].offsetLeft >= $headerIconsShown[i-1].offsetLeft;
|
||||
let isFurtherRightThanLastIcon = $headerIconsShown[i].offsetLeft <= $headerIconsShown[i-1].offsetLeft;
|
||||
if ((!rtlLocale && isFurtherLeftThanLastIcon) || (rtlLocale && isFurtherRightThanLastIcon)) {
|
||||
// we have found the first icon on second row. Use previous icon.
|
||||
icon = $headerIconsShown[i-1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (icon) {
|
||||
// overflowing
|
||||
// add overflowing-hidden class
|
||||
this.$header.classList.add('overflow-hidden');
|
||||
// add expand btn 2 before icon
|
||||
this.$expandBtn.removeAttribute('hidden');
|
||||
icon.before(this.$expandBtn);
|
||||
}
|
||||
else {
|
||||
// no overflowing
|
||||
// add overflowing-hidden class
|
||||
this.$header.classList.remove('overflow-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
onExpandBtnClick() {
|
||||
// toggle overflowing-hidden class and flip expand btn icon
|
||||
if (this.$header.classList.contains('overflow-hidden')) {
|
||||
this.$header.classList.remove('overflow-hidden');
|
||||
this.$header.classList.add('overflow-expanded');
|
||||
this.$expandBtn.classList.remove('flipped');
|
||||
}
|
||||
else {
|
||||
this.$header.classList.add('overflow-hidden');
|
||||
this.$header.classList.remove('overflow-expanded');
|
||||
this.$expandBtn.classList.add('flipped');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CenterUI {
|
||||
|
||||
constructor() {
|
||||
this.$center = $$('#center');
|
||||
this.$xNoPeers = $$('x-no-peers');
|
||||
}
|
||||
|
||||
async fadeIn() {
|
||||
this.$center.classList.remove('opacity-0');
|
||||
|
||||
// Prevent flickering on load
|
||||
setTimeout(() => {
|
||||
this.$xNoPeers.classList.remove('no-animation-on-load');
|
||||
}, 600);
|
||||
}
|
||||
}
|
||||
|
||||
class FooterUI {
|
||||
|
||||
constructor() {
|
||||
this.$footer = $$('footer');
|
||||
this.$displayName = $('display-name');
|
||||
this.$discoveryWrapper = $$('footer .discovery-wrapper');
|
||||
|
||||
// Show "Loading…"
|
||||
this.$displayName.setAttribute('placeholder', this.$displayName.dataset.placeholder);
|
||||
|
||||
this.$displayName.addEventListener('keydown', e => this._onKeyDownDisplayName(e));
|
||||
this.$displayName.addEventListener('keyup', e => this._onKeyUpDisplayName(e));
|
||||
this.$displayName.addEventListener('blur', e => this._saveDisplayName(e.target.innerText));
|
||||
|
@ -133,7 +209,15 @@ class FooterUI {
|
|||
Events.on('evaluate-footer-badges', _ => this._evaluateFooterBadges());
|
||||
}
|
||||
|
||||
_evaluateFooterBadges() {
|
||||
async showLoading() {
|
||||
this.$displayName.setAttribute('placeholder', this.$displayName.dataset.placeholder);
|
||||
}
|
||||
|
||||
async fadeIn() {
|
||||
this.$footer.classList.remove('opacity-0');
|
||||
}
|
||||
|
||||
async _evaluateFooterBadges() {
|
||||
if (this.$discoveryWrapper.querySelectorAll('div:last-of-type > span[hidden]').length < 2) {
|
||||
this.$discoveryWrapper.classList.remove('row');
|
||||
this.$discoveryWrapper.classList.add('column');
|
||||
|
@ -143,17 +227,15 @@ class FooterUI {
|
|||
this.$discoveryWrapper.classList.add('row');
|
||||
}
|
||||
Events.fire('redraw-canvas');
|
||||
Events.fire('fade-in-ui');
|
||||
}
|
||||
|
||||
_loadSavedDisplayName() {
|
||||
this._getSavedDisplayName()
|
||||
.then(displayName => {
|
||||
console.log("Retrieved edited display name:", displayName)
|
||||
if (displayName) {
|
||||
Events.fire('self-display-name-changed', displayName);
|
||||
}
|
||||
});
|
||||
async _loadSavedDisplayName() {
|
||||
const displayName = await this._getSavedDisplayName()
|
||||
|
||||
if (!displayName) return;
|
||||
|
||||
console.log("Retrieved edited display name:", displayName)
|
||||
Events.fire('self-display-name-changed', displayName);
|
||||
}
|
||||
|
||||
_onDisplayName(displayName){
|
||||
|
@ -184,13 +266,13 @@ class FooterUI {
|
|||
if (newDisplayName === savedDisplayName) return;
|
||||
|
||||
if (newDisplayName) {
|
||||
PersistentStorage.set('editedDisplayName', newDisplayName)
|
||||
PersistentStorage.set('edited_display_name', newDisplayName)
|
||||
.then(_ => {
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-permanently"));
|
||||
})
|
||||
.catch(_ => {
|
||||
console.log("This browser does not support IndexedDB. Use localStorage instead.");
|
||||
localStorage.setItem('editedDisplayName', newDisplayName);
|
||||
localStorage.setItem('edited_display_name', newDisplayName);
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-temporarily"));
|
||||
})
|
||||
.finally(() => {
|
||||
|
@ -199,10 +281,10 @@ class FooterUI {
|
|||
});
|
||||
}
|
||||
else {
|
||||
PersistentStorage.delete('editedDisplayName')
|
||||
PersistentStorage.delete('edited_display_name')
|
||||
.catch(_ => {
|
||||
console.log("This browser does not support IndexedDB. Use localStorage instead.")
|
||||
localStorage.removeItem('editedDisplayName');
|
||||
localStorage.removeItem('edited_display_name');
|
||||
})
|
||||
.finally(() => {
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-random-again"));
|
||||
|
@ -214,13 +296,13 @@ class FooterUI {
|
|||
|
||||
_getSavedDisplayName() {
|
||||
return new Promise((resolve) => {
|
||||
PersistentStorage.get('editedDisplayName')
|
||||
PersistentStorage.get('edited_display_name')
|
||||
.then(displayName => {
|
||||
if (!displayName) displayName = "";
|
||||
resolve(displayName);
|
||||
})
|
||||
.catch(_ => {
|
||||
let displayName = localStorage.getItem('editedDisplayName');
|
||||
let displayName = localStorage.getItem('edited_display_name');
|
||||
if (!displayName) displayName = "";
|
||||
resolve(displayName);
|
||||
})
|
||||
|
@ -234,16 +316,16 @@ class BackgroundCanvas {
|
|||
this.cCtx = this.c.getContext('2d');
|
||||
this.$footer = $$('footer');
|
||||
|
||||
// fade-in on load
|
||||
Events.on('fade-in-ui', _ => this._fadeIn());
|
||||
|
||||
// redraw canvas
|
||||
Events.on('resize', _ => this.init());
|
||||
Events.on('redraw-canvas', _ => this.init());
|
||||
Events.on('translation-loaded', _ => this.init());
|
||||
|
||||
// ShareMode
|
||||
Events.on('share-mode-changed', e => this.onShareModeChanged(e.detail.active));
|
||||
}
|
||||
|
||||
_fadeIn() {
|
||||
async fadeIn() {
|
||||
this.c.classList.remove('opacity-0');
|
||||
}
|
||||
|
||||
|
@ -263,16 +345,24 @@ class BackgroundCanvas {
|
|||
this.x0 = this.w / 2;
|
||||
this.y0 = this.h - this.offset;
|
||||
this.dw = Math.round(Math.max(this.w, this.h, 1000) / 13);
|
||||
this.baseColor = '165, 165, 165';
|
||||
this.baseOpacity = 0.3;
|
||||
|
||||
this.drawCircles(this.cCtx);
|
||||
}
|
||||
|
||||
onShareModeChanged(active) {
|
||||
this.baseColor = active ? '165, 165, 255' : '165, 165, 165';
|
||||
this.baseOpacity = active ? 0.5 : 0.3;
|
||||
this.drawCircles(this.cCtx);
|
||||
}
|
||||
|
||||
|
||||
drawCircle(ctx, radius) {
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 2;
|
||||
let opacity = Math.max(0, 0.3 * (1 - 1.2 * radius / Math.max(this.w, this.h)));
|
||||
ctx.strokeStyle = `rgba(165, 165, 165, ${opacity})`;
|
||||
let opacity = Math.max(0, this.baseOpacity * (1 - 1.2 * radius / Math.max(this.w, this.h)));
|
||||
ctx.strokeStyle = `rgba(${this.baseColor}, ${opacity})`;
|
||||
ctx.arc(this.x0, this.y0, radius, 0, 2 * Math.PI);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
|
1025
public/scripts/ui.js
1025
public/scripts/ui.js
File diff suppressed because it is too large
Load diff
|
@ -104,305 +104,315 @@ const zipper = (() => {
|
|||
|
||||
const mime = (() => {
|
||||
|
||||
const suffixToMimeMap = {
|
||||
"cpl": "application/cpl+xml",
|
||||
"gpx": "application/gpx+xml",
|
||||
"gz": "application/gzip",
|
||||
"jar": "application/java-archive",
|
||||
"war": "application/java-archive",
|
||||
"ear": "application/java-archive",
|
||||
"class": "application/java-vm",
|
||||
"js": "application/javascript",
|
||||
"mjs": "application/javascript",
|
||||
"json": "application/json",
|
||||
"map": "application/json",
|
||||
"webmanifest": "application/manifest+json",
|
||||
"doc": "application/msword",
|
||||
"dot": "application/msword",
|
||||
"wiz": "application/msword",
|
||||
"bin": "application/octet-stream",
|
||||
"dms": "application/octet-stream",
|
||||
"lrf": "application/octet-stream",
|
||||
"mar": "application/octet-stream",
|
||||
"so": "application/octet-stream",
|
||||
"dist": "application/octet-stream",
|
||||
"distz": "application/octet-stream",
|
||||
"pkg": "application/octet-stream",
|
||||
"bpk": "application/octet-stream",
|
||||
"dump": "application/octet-stream",
|
||||
"elc": "application/octet-stream",
|
||||
"deploy": "application/octet-stream",
|
||||
"img": "application/octet-stream",
|
||||
"msp": "application/octet-stream",
|
||||
"msm": "application/octet-stream",
|
||||
"buffer": "application/octet-stream",
|
||||
"oda": "application/oda",
|
||||
"oxps": "application/oxps",
|
||||
"pdf": "application/pdf",
|
||||
"asc": "application/pgp-signature",
|
||||
"sig": "application/pgp-signature",
|
||||
"prf": "application/pics-rules",
|
||||
"p7c": "application/pkcs7-mime",
|
||||
"cer": "application/pkix-cert",
|
||||
"ai": "application/postscript",
|
||||
"eps": "application/postscript",
|
||||
"ps": "application/postscript",
|
||||
"apk": "application/vnd.android.package-archive",
|
||||
"m3u8": "application/vnd.apple.mpegurl",
|
||||
"pkpass": "application/vnd.apple.pkpass",
|
||||
"kml": "application/vnd.google-earth.kml+xml",
|
||||
"kmz": "application/vnd.google-earth.kmz",
|
||||
"cab": "application/vnd.ms-cab-compressed",
|
||||
"xls": "application/vnd.ms-excel",
|
||||
"xlm": "application/vnd.ms-excel",
|
||||
"xla": "application/vnd.ms-excel",
|
||||
"xlc": "application/vnd.ms-excel",
|
||||
"xlt": "application/vnd.ms-excel",
|
||||
"xlw": "application/vnd.ms-excel",
|
||||
"msg": "application/vnd.ms-outlook",
|
||||
"ppt": "application/vnd.ms-powerpoint",
|
||||
"pot": "application/vnd.ms-powerpoint",
|
||||
"ppa": "application/vnd.ms-powerpoint",
|
||||
"pps": "application/vnd.ms-powerpoint",
|
||||
"pwz": "application/vnd.ms-powerpoint",
|
||||
"mpp": "application/vnd.ms-project",
|
||||
"mpt": "application/vnd.ms-project",
|
||||
"xps": "application/vnd.ms-xpsdocument",
|
||||
"odb": "application/vnd.oasis.opendocument.database",
|
||||
"ods": "application/vnd.oasis.opendocument.spreadsheet",
|
||||
"odt": "application/vnd.oasis.opendocument.text",
|
||||
"osm": "application/vnd.openstreetmap.data+xml",
|
||||
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"pcap": "application/vnd.tcpdump.pcap",
|
||||
"cap": "application/vnd.tcpdump.pcap",
|
||||
"dmp": "application/vnd.tcpdump.pcap",
|
||||
"wpd": "application/vnd.wordperfect",
|
||||
"wasm": "application/wasm",
|
||||
"7z": "application/x-7z-compressed",
|
||||
"dmg": "application/x-apple-diskimage",
|
||||
"bcpio": "application/x-bcpio",
|
||||
"torrent": "application/x-bittorrent",
|
||||
"cbr": "application/x-cbr",
|
||||
"cba": "application/x-cbr",
|
||||
"cbt": "application/x-cbr",
|
||||
"cbz": "application/x-cbr",
|
||||
"cb7": "application/x-cbr",
|
||||
"vcd": "application/x-cdlink",
|
||||
"crx": "application/x-chrome-extension",
|
||||
"cpio": "application/x-cpio",
|
||||
"csh": "application/x-csh",
|
||||
"deb": "application/x-debian-package",
|
||||
"udeb": "application/x-debian-package",
|
||||
"dvi": "application/x-dvi",
|
||||
"arc": "application/x-freearc",
|
||||
"gtar": "application/x-gtar",
|
||||
"hdf": "application/x-hdf",
|
||||
"h5": "application/x-hdf5",
|
||||
"php": "application/x-httpd-php",
|
||||
"iso": "application/x-iso9660-image",
|
||||
"key": "application/x-iwork-keynote-sffkey",
|
||||
"numbers": "application/x-iwork-numbers-sffnumbers",
|
||||
"pages": "application/x-iwork-pages-sffpages",
|
||||
"latex": "application/x-latex",
|
||||
"run": "application/x-makeself",
|
||||
"mif": "application/x-mif",
|
||||
"lnk": "application/x-ms-shortcut",
|
||||
"mdb": "application/x-msaccess",
|
||||
"exe": "application/x-msdownload",
|
||||
"dll": "application/x-msdownload",
|
||||
"com": "application/x-msdownload",
|
||||
"bat": "application/x-msdownload",
|
||||
"msi": "application/x-msdownload",
|
||||
"pub": "application/x-mspublisher",
|
||||
"cdf": "application/x-netcdf",
|
||||
"nc": "application/x-netcdf",
|
||||
"pl": "application/x-perl",
|
||||
"pm": "application/x-perl",
|
||||
"prc": "application/x-pilot",
|
||||
"pdb": "application/x-pilot",
|
||||
"p12": "application/x-pkcs12",
|
||||
"pfx": "application/x-pkcs12",
|
||||
"ram": "application/x-pn-realaudio",
|
||||
"pyc": "application/x-python-code",
|
||||
"pyo": "application/x-python-code",
|
||||
"rar": "application/x-rar-compressed",
|
||||
"rpm": "application/x-redhat-package-manager",
|
||||
"sh": "application/x-sh",
|
||||
"shar": "application/x-shar",
|
||||
"swf": "application/x-shockwave-flash",
|
||||
"sql": "application/x-sql",
|
||||
"srt": "application/x-subrip",
|
||||
"sv4cpio": "application/x-sv4cpio",
|
||||
"sv4crc": "application/x-sv4crc",
|
||||
"gam": "application/x-tads",
|
||||
"tar": "application/x-tar",
|
||||
"tcl": "application/x-tcl",
|
||||
"tex": "application/x-tex",
|
||||
"roff": "application/x-troff",
|
||||
"t": "application/x-troff",
|
||||
"tr": "application/x-troff",
|
||||
"man": "application/x-troff-man",
|
||||
"me": "application/x-troff-me",
|
||||
"ms": "application/x-troff-ms",
|
||||
"ustar": "application/x-ustar",
|
||||
"src": "application/x-wais-source",
|
||||
"xpi": "application/x-xpinstall",
|
||||
"xhtml": "application/xhtml+xml",
|
||||
"xht": "application/xhtml+xml",
|
||||
"xsl": "application/xml",
|
||||
"rdf": "application/xml",
|
||||
"wsdl": "application/xml",
|
||||
"xpdl": "application/xml",
|
||||
"zip": "application/zip",
|
||||
"3gp": "audio/3gp",
|
||||
"3gpp": "audio/3gpp",
|
||||
"3g2": "audio/3gpp2",
|
||||
"3gpp2": "audio/3gpp2",
|
||||
"aac": "audio/aac",
|
||||
"adts": "audio/aac",
|
||||
"loas": "audio/aac",
|
||||
"ass": "audio/aac",
|
||||
"au": "audio/basic",
|
||||
"snd": "audio/basic",
|
||||
"mid": "audio/midi",
|
||||
"midi": "audio/midi",
|
||||
"kar": "audio/midi",
|
||||
"rmi": "audio/midi",
|
||||
"mpga": "audio/mpeg",
|
||||
"mp2": "audio/mpeg",
|
||||
"mp2a": "audio/mpeg",
|
||||
"mp3": "audio/mpeg",
|
||||
"m2a": "audio/mpeg",
|
||||
"m3a": "audio/mpeg",
|
||||
"oga": "audio/ogg",
|
||||
"ogg": "audio/ogg",
|
||||
"spx": "audio/ogg",
|
||||
"opus": "audio/opus",
|
||||
"aif": "audio/x-aiff",
|
||||
"aifc": "audio/x-aiff",
|
||||
"aiff": "audio/x-aiff",
|
||||
"flac": "audio/x-flac",
|
||||
"m4a": "audio/x-m4a",
|
||||
"m3u": "audio/x-mpegurl",
|
||||
"wma": "audio/x-ms-wma",
|
||||
"ra": "audio/x-pn-realaudio",
|
||||
"wav": "audio/x-wav",
|
||||
"otf": "font/otf",
|
||||
"ttf": "font/ttf",
|
||||
"woff": "font/woff",
|
||||
"woff2": "font/woff2",
|
||||
"emf": "image/emf",
|
||||
"gif": "image/gif",
|
||||
"heic": "image/heic",
|
||||
"heif": "image/heif",
|
||||
"ief": "image/ief",
|
||||
"jpeg": "image/jpeg",
|
||||
"jpg": "image/jpeg",
|
||||
"pict": "image/pict",
|
||||
"pct": "image/pict",
|
||||
"pic": "image/pict",
|
||||
"png": "image/png",
|
||||
"svg": "image/svg+xml",
|
||||
"svgz": "image/svg+xml",
|
||||
"tif": "image/tiff",
|
||||
"tiff": "image/tiff",
|
||||
"psd": "image/vnd.adobe.photoshop",
|
||||
"djvu": "image/vnd.djvu",
|
||||
"djv": "image/vnd.djvu",
|
||||
"dwg": "image/vnd.dwg",
|
||||
"dxf": "image/vnd.dxf",
|
||||
"dds": "image/vnd.ms-dds",
|
||||
"webp": "image/webp",
|
||||
"3ds": "image/x-3ds",
|
||||
"ras": "image/x-cmu-raster",
|
||||
"ico": "image/x-icon",
|
||||
"bmp": "image/x-ms-bmp",
|
||||
"pnm": "image/x-portable-anymap",
|
||||
"pbm": "image/x-portable-bitmap",
|
||||
"pgm": "image/x-portable-graymap",
|
||||
"ppm": "image/x-portable-pixmap",
|
||||
"rgb": "image/x-rgb",
|
||||
"tga": "image/x-tga",
|
||||
"xbm": "image/x-xbitmap",
|
||||
"xpm": "image/x-xpixmap",
|
||||
"xwd": "image/x-xwindowdump",
|
||||
"eml": "message/rfc822",
|
||||
"mht": "message/rfc822",
|
||||
"mhtml": "message/rfc822",
|
||||
"nws": "message/rfc822",
|
||||
"obj": "model/obj",
|
||||
"stl": "model/stl",
|
||||
"dae": "model/vnd.collada+xml",
|
||||
"ics": "text/calendar",
|
||||
"ifb": "text/calendar",
|
||||
"css": "text/css",
|
||||
"csv": "text/csv",
|
||||
"html": "text/html",
|
||||
"htm": "text/html",
|
||||
"shtml": "text/html",
|
||||
"markdown": "text/markdown",
|
||||
"md": "text/markdown",
|
||||
"txt": "text/plain",
|
||||
"text": "text/plain",
|
||||
"conf": "text/plain",
|
||||
"def": "text/plain",
|
||||
"list": "text/plain",
|
||||
"log": "text/plain",
|
||||
"in": "text/plain",
|
||||
"ini": "text/plain",
|
||||
"rtx": "text/richtext",
|
||||
"rtf": "text/rtf",
|
||||
"tsv": "text/tab-separated-values",
|
||||
"c": "text/x-c",
|
||||
"cc": "text/x-c",
|
||||
"cxx": "text/x-c",
|
||||
"cpp": "text/x-c",
|
||||
"h": "text/x-c",
|
||||
"hh": "text/x-c",
|
||||
"dic": "text/x-c",
|
||||
"java": "text/x-java-source",
|
||||
"lua": "text/x-lua",
|
||||
"py": "text/x-python",
|
||||
"etx": "text/x-setext",
|
||||
"sgm": "text/x-sgml",
|
||||
"sgml": "text/x-sgml",
|
||||
"vcf": "text/x-vcard",
|
||||
"xml": "text/xml",
|
||||
"xul": "text/xul",
|
||||
"yaml": "text/yaml",
|
||||
"yml": "text/yaml",
|
||||
"ts": "video/mp2t",
|
||||
"mp4": "video/mp4",
|
||||
"mp4v": "video/mp4",
|
||||
"mpg4": "video/mp4",
|
||||
"mpeg": "video/mpeg",
|
||||
"m1v": "video/mpeg",
|
||||
"mpa": "video/mpeg",
|
||||
"mpe": "video/mpeg",
|
||||
"mpg": "video/mpeg",
|
||||
"mov": "video/quicktime",
|
||||
"qt": "video/quicktime",
|
||||
"webm": "video/webm",
|
||||
"flv": "video/x-flv",
|
||||
"m4v": "video/x-m4v",
|
||||
"asf": "video/x-ms-asf",
|
||||
"asx": "video/x-ms-asf",
|
||||
"vob": "video/x-ms-vob",
|
||||
"wmv": "video/x-ms-wmv",
|
||||
"avi": "video/x-msvideo",
|
||||
"*": "video/x-sgi-movie"
|
||||
}
|
||||
|
||||
return {
|
||||
getMimeByFilename(filename) {
|
||||
try {
|
||||
const arr = filename.split('.');
|
||||
const suffix = arr[arr.length - 1].toLowerCase();
|
||||
return {
|
||||
"cpl": "application/cpl+xml",
|
||||
"gpx": "application/gpx+xml",
|
||||
"gz": "application/gzip",
|
||||
"jar": "application/java-archive",
|
||||
"war": "application/java-archive",
|
||||
"ear": "application/java-archive",
|
||||
"class": "application/java-vm",
|
||||
"js": "application/javascript",
|
||||
"mjs": "application/javascript",
|
||||
"json": "application/json",
|
||||
"map": "application/json",
|
||||
"webmanifest": "application/manifest+json",
|
||||
"doc": "application/msword",
|
||||
"dot": "application/msword",
|
||||
"wiz": "application/msword",
|
||||
"bin": "application/octet-stream",
|
||||
"dms": "application/octet-stream",
|
||||
"lrf": "application/octet-stream",
|
||||
"mar": "application/octet-stream",
|
||||
"so": "application/octet-stream",
|
||||
"dist": "application/octet-stream",
|
||||
"distz": "application/octet-stream",
|
||||
"pkg": "application/octet-stream",
|
||||
"bpk": "application/octet-stream",
|
||||
"dump": "application/octet-stream",
|
||||
"elc": "application/octet-stream",
|
||||
"deploy": "application/octet-stream",
|
||||
"img": "application/octet-stream",
|
||||
"msp": "application/octet-stream",
|
||||
"msm": "application/octet-stream",
|
||||
"buffer": "application/octet-stream",
|
||||
"oda": "application/oda",
|
||||
"oxps": "application/oxps",
|
||||
"pdf": "application/pdf",
|
||||
"asc": "application/pgp-signature",
|
||||
"sig": "application/pgp-signature",
|
||||
"prf": "application/pics-rules",
|
||||
"p7c": "application/pkcs7-mime",
|
||||
"cer": "application/pkix-cert",
|
||||
"ai": "application/postscript",
|
||||
"eps": "application/postscript",
|
||||
"ps": "application/postscript",
|
||||
"apk": "application/vnd.android.package-archive",
|
||||
"m3u8": "application/vnd.apple.mpegurl",
|
||||
"pkpass": "application/vnd.apple.pkpass",
|
||||
"kml": "application/vnd.google-earth.kml+xml",
|
||||
"kmz": "application/vnd.google-earth.kmz",
|
||||
"cab": "application/vnd.ms-cab-compressed",
|
||||
"xls": "application/vnd.ms-excel",
|
||||
"xlm": "application/vnd.ms-excel",
|
||||
"xla": "application/vnd.ms-excel",
|
||||
"xlc": "application/vnd.ms-excel",
|
||||
"xlt": "application/vnd.ms-excel",
|
||||
"xlw": "application/vnd.ms-excel",
|
||||
"msg": "application/vnd.ms-outlook",
|
||||
"ppt": "application/vnd.ms-powerpoint",
|
||||
"pot": "application/vnd.ms-powerpoint",
|
||||
"ppa": "application/vnd.ms-powerpoint",
|
||||
"pps": "application/vnd.ms-powerpoint",
|
||||
"pwz": "application/vnd.ms-powerpoint",
|
||||
"mpp": "application/vnd.ms-project",
|
||||
"mpt": "application/vnd.ms-project",
|
||||
"xps": "application/vnd.ms-xpsdocument",
|
||||
"odb": "application/vnd.oasis.opendocument.database",
|
||||
"ods": "application/vnd.oasis.opendocument.spreadsheet",
|
||||
"odt": "application/vnd.oasis.opendocument.text",
|
||||
"osm": "application/vnd.openstreetmap.data+xml",
|
||||
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"pcap": "application/vnd.tcpdump.pcap",
|
||||
"cap": "application/vnd.tcpdump.pcap",
|
||||
"dmp": "application/vnd.tcpdump.pcap",
|
||||
"wpd": "application/vnd.wordperfect",
|
||||
"wasm": "application/wasm",
|
||||
"7z": "application/x-7z-compressed",
|
||||
"dmg": "application/x-apple-diskimage",
|
||||
"bcpio": "application/x-bcpio",
|
||||
"torrent": "application/x-bittorrent",
|
||||
"cbr": "application/x-cbr",
|
||||
"cba": "application/x-cbr",
|
||||
"cbt": "application/x-cbr",
|
||||
"cbz": "application/x-cbr",
|
||||
"cb7": "application/x-cbr",
|
||||
"vcd": "application/x-cdlink",
|
||||
"crx": "application/x-chrome-extension",
|
||||
"cpio": "application/x-cpio",
|
||||
"csh": "application/x-csh",
|
||||
"deb": "application/x-debian-package",
|
||||
"udeb": "application/x-debian-package",
|
||||
"dvi": "application/x-dvi",
|
||||
"arc": "application/x-freearc",
|
||||
"gtar": "application/x-gtar",
|
||||
"hdf": "application/x-hdf",
|
||||
"h5": "application/x-hdf5",
|
||||
"php": "application/x-httpd-php",
|
||||
"iso": "application/x-iso9660-image",
|
||||
"key": "application/x-iwork-keynote-sffkey",
|
||||
"numbers": "application/x-iwork-numbers-sffnumbers",
|
||||
"pages": "application/x-iwork-pages-sffpages",
|
||||
"latex": "application/x-latex",
|
||||
"run": "application/x-makeself",
|
||||
"mif": "application/x-mif",
|
||||
"lnk": "application/x-ms-shortcut",
|
||||
"mdb": "application/x-msaccess",
|
||||
"exe": "application/x-msdownload",
|
||||
"dll": "application/x-msdownload",
|
||||
"com": "application/x-msdownload",
|
||||
"bat": "application/x-msdownload",
|
||||
"msi": "application/x-msdownload",
|
||||
"pub": "application/x-mspublisher",
|
||||
"cdf": "application/x-netcdf",
|
||||
"nc": "application/x-netcdf",
|
||||
"pl": "application/x-perl",
|
||||
"pm": "application/x-perl",
|
||||
"prc": "application/x-pilot",
|
||||
"pdb": "application/x-pilot",
|
||||
"p12": "application/x-pkcs12",
|
||||
"pfx": "application/x-pkcs12",
|
||||
"ram": "application/x-pn-realaudio",
|
||||
"pyc": "application/x-python-code",
|
||||
"pyo": "application/x-python-code",
|
||||
"rar": "application/x-rar-compressed",
|
||||
"rpm": "application/x-redhat-package-manager",
|
||||
"sh": "application/x-sh",
|
||||
"shar": "application/x-shar",
|
||||
"swf": "application/x-shockwave-flash",
|
||||
"sql": "application/x-sql",
|
||||
"srt": "application/x-subrip",
|
||||
"sv4cpio": "application/x-sv4cpio",
|
||||
"sv4crc": "application/x-sv4crc",
|
||||
"gam": "application/x-tads",
|
||||
"tar": "application/x-tar",
|
||||
"tcl": "application/x-tcl",
|
||||
"tex": "application/x-tex",
|
||||
"roff": "application/x-troff",
|
||||
"t": "application/x-troff",
|
||||
"tr": "application/x-troff",
|
||||
"man": "application/x-troff-man",
|
||||
"me": "application/x-troff-me",
|
||||
"ms": "application/x-troff-ms",
|
||||
"ustar": "application/x-ustar",
|
||||
"src": "application/x-wais-source",
|
||||
"xpi": "application/x-xpinstall",
|
||||
"xhtml": "application/xhtml+xml",
|
||||
"xht": "application/xhtml+xml",
|
||||
"xsl": "application/xml",
|
||||
"rdf": "application/xml",
|
||||
"wsdl": "application/xml",
|
||||
"xpdl": "application/xml",
|
||||
"zip": "application/zip",
|
||||
"3gp": "audio/3gp",
|
||||
"3gpp": "audio/3gpp",
|
||||
"3g2": "audio/3gpp2",
|
||||
"3gpp2": "audio/3gpp2",
|
||||
"aac": "audio/aac",
|
||||
"adts": "audio/aac",
|
||||
"loas": "audio/aac",
|
||||
"ass": "audio/aac",
|
||||
"au": "audio/basic",
|
||||
"snd": "audio/basic",
|
||||
"mid": "audio/midi",
|
||||
"midi": "audio/midi",
|
||||
"kar": "audio/midi",
|
||||
"rmi": "audio/midi",
|
||||
"mpga": "audio/mpeg",
|
||||
"mp2": "audio/mpeg",
|
||||
"mp2a": "audio/mpeg",
|
||||
"mp3": "audio/mpeg",
|
||||
"m2a": "audio/mpeg",
|
||||
"m3a": "audio/mpeg",
|
||||
"oga": "audio/ogg",
|
||||
"ogg": "audio/ogg",
|
||||
"spx": "audio/ogg",
|
||||
"opus": "audio/opus",
|
||||
"aif": "audio/x-aiff",
|
||||
"aifc": "audio/x-aiff",
|
||||
"aiff": "audio/x-aiff",
|
||||
"flac": "audio/x-flac",
|
||||
"m4a": "audio/x-m4a",
|
||||
"m3u": "audio/x-mpegurl",
|
||||
"wma": "audio/x-ms-wma",
|
||||
"ra": "audio/x-pn-realaudio",
|
||||
"wav": "audio/x-wav",
|
||||
"otf": "font/otf",
|
||||
"ttf": "font/ttf",
|
||||
"woff": "font/woff",
|
||||
"woff2": "font/woff2",
|
||||
"emf": "image/emf",
|
||||
"gif": "image/gif",
|
||||
"heic": "image/heic",
|
||||
"heif": "image/heif",
|
||||
"ief": "image/ief",
|
||||
"jpeg": "image/jpeg",
|
||||
"jpg": "image/jpeg",
|
||||
"pict": "image/pict",
|
||||
"pct": "image/pict",
|
||||
"pic": "image/pict",
|
||||
"png": "image/png",
|
||||
"svg": "image/svg+xml",
|
||||
"svgz": "image/svg+xml",
|
||||
"tif": "image/tiff",
|
||||
"tiff": "image/tiff",
|
||||
"psd": "image/vnd.adobe.photoshop",
|
||||
"djvu": "image/vnd.djvu",
|
||||
"djv": "image/vnd.djvu",
|
||||
"dwg": "image/vnd.dwg",
|
||||
"dxf": "image/vnd.dxf",
|
||||
"dds": "image/vnd.ms-dds",
|
||||
"webp": "image/webp",
|
||||
"3ds": "image/x-3ds",
|
||||
"ras": "image/x-cmu-raster",
|
||||
"ico": "image/x-icon",
|
||||
"bmp": "image/x-ms-bmp",
|
||||
"pnm": "image/x-portable-anymap",
|
||||
"pbm": "image/x-portable-bitmap",
|
||||
"pgm": "image/x-portable-graymap",
|
||||
"ppm": "image/x-portable-pixmap",
|
||||
"rgb": "image/x-rgb",
|
||||
"tga": "image/x-tga",
|
||||
"xbm": "image/x-xbitmap",
|
||||
"xpm": "image/x-xpixmap",
|
||||
"xwd": "image/x-xwindowdump",
|
||||
"eml": "message/rfc822",
|
||||
"mht": "message/rfc822",
|
||||
"mhtml": "message/rfc822",
|
||||
"nws": "message/rfc822",
|
||||
"obj": "model/obj",
|
||||
"stl": "model/stl",
|
||||
"dae": "model/vnd.collada+xml",
|
||||
"ics": "text/calendar",
|
||||
"ifb": "text/calendar",
|
||||
"css": "text/css",
|
||||
"csv": "text/csv",
|
||||
"html": "text/html",
|
||||
"htm": "text/html",
|
||||
"shtml": "text/html",
|
||||
"markdown": "text/markdown",
|
||||
"md": "text/markdown",
|
||||
"txt": "text/plain",
|
||||
"text": "text/plain",
|
||||
"conf": "text/plain",
|
||||
"def": "text/plain",
|
||||
"list": "text/plain",
|
||||
"log": "text/plain",
|
||||
"in": "text/plain",
|
||||
"ini": "text/plain",
|
||||
"rtx": "text/richtext",
|
||||
"rtf": "text/rtf",
|
||||
"tsv": "text/tab-separated-values",
|
||||
"c": "text/x-c",
|
||||
"cc": "text/x-c",
|
||||
"cxx": "text/x-c",
|
||||
"cpp": "text/x-c",
|
||||
"h": "text/x-c",
|
||||
"hh": "text/x-c",
|
||||
"dic": "text/x-c",
|
||||
"java": "text/x-java-source",
|
||||
"lua": "text/x-lua",
|
||||
"py": "text/x-python",
|
||||
"etx": "text/x-setext",
|
||||
"sgm": "text/x-sgml",
|
||||
"sgml": "text/x-sgml",
|
||||
"vcf": "text/x-vcard",
|
||||
"xml": "text/xml",
|
||||
"xul": "text/xul",
|
||||
"yaml": "text/yaml",
|
||||
"yml": "text/yaml",
|
||||
"ts": "video/mp2t",
|
||||
"mp4": "video/mp4",
|
||||
"mp4v": "video/mp4",
|
||||
"mpg4": "video/mp4",
|
||||
"mpeg": "video/mpeg",
|
||||
"m1v": "video/mpeg",
|
||||
"mpa": "video/mpeg",
|
||||
"mpe": "video/mpeg",
|
||||
"mpg": "video/mpeg",
|
||||
"mov": "video/quicktime",
|
||||
"qt": "video/quicktime",
|
||||
"webm": "video/webm",
|
||||
"flv": "video/x-flv",
|
||||
"m4v": "video/x-m4v",
|
||||
"asf": "video/x-ms-asf",
|
||||
"asx": "video/x-ms-asf",
|
||||
"vob": "video/x-ms-vob",
|
||||
"wmv": "video/x-ms-wmv",
|
||||
"avi": "video/x-msvideo",
|
||||
"*": "video/x-sgi-movie",
|
||||
}[suffix] || '';
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return '';
|
||||
async guessMimeByFilename(filename) {
|
||||
const split = filename.split('.');
|
||||
if (split.length === 1) {
|
||||
// Filename does not include suffix
|
||||
return "";
|
||||
}
|
||||
const suffix = split[split.length - 1].toLowerCase();
|
||||
return suffixToMimeMap[suffix] || "";
|
||||
},
|
||||
async addMissingMimeTypesToFiles(files) {
|
||||
// if filetype is empty guess via suffix otherwise leave unchanged
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (!files[i].type) {
|
||||
files[i] = new File([files[i]], files[i].name, {type: await mime.guessMimeByFilename(files[i].name) || ""});
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -457,4 +467,120 @@ function base64ToArrayBuffer(base64) {
|
|||
bytes[i] = binary_string.charCodeAt(i);
|
||||
}
|
||||
return bytes.buffer;
|
||||
}
|
||||
|
||||
async function fileToBlob (file) {
|
||||
return new Blob([new Uint8Array(await file.arrayBuffer())], {type: file.type});
|
||||
}
|
||||
|
||||
function getThumbnailAsDataUrl(file, width = undefined, height = undefined, quality = 0.7) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
if (file.type === "image/heif" || file.type === "image/heic") {
|
||||
// browsers can't show heic files --> convert to jpeg before creating thumbnail
|
||||
let blob = await fileToBlob(file);
|
||||
file = await heic2any({
|
||||
blob,
|
||||
toType: "image/jpeg",
|
||||
quality: quality
|
||||
});
|
||||
}
|
||||
|
||||
let imageUrl = URL.createObjectURL(file);
|
||||
|
||||
let image = new Image();
|
||||
image.src = imageUrl;
|
||||
|
||||
await waitUntilImageIsLoaded(imageUrl);
|
||||
|
||||
let imageWidth = image.width;
|
||||
let imageHeight = image.height;
|
||||
let canvas = document.createElement('canvas');
|
||||
|
||||
// resize the canvas and draw the image data into it
|
||||
if (width && height) {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
}
|
||||
else if (width) {
|
||||
canvas.width = width;
|
||||
canvas.height = Math.floor(imageHeight * width / imageWidth)
|
||||
}
|
||||
else if (height) {
|
||||
canvas.width = Math.floor(imageWidth * height / imageHeight);
|
||||
canvas.height = height;
|
||||
}
|
||||
else {
|
||||
canvas.width = imageWidth;
|
||||
canvas.height = imageHeight
|
||||
}
|
||||
|
||||
let ctx = canvas.getContext("2d");
|
||||
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||
|
||||
let dataUrl = canvas.toDataURL("image/jpeg", quality);
|
||||
resolve(dataUrl);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
reject(new Error(`Could not create an image thumbnail from type ${file.type}`));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Resolves returned promise when image is loaded and throws error if image cannot be shown
|
||||
function waitUntilImageIsLoaded(imageUrl, timeout = 10000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let image = new Image();
|
||||
image.src = imageUrl;
|
||||
|
||||
const onLoad = () => {
|
||||
cleanup();
|
||||
resolve();
|
||||
};
|
||||
|
||||
const onError = () => {
|
||||
cleanup();
|
||||
reject(new Error('Image failed to load.'));
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
clearTimeout(timeoutId);
|
||||
image.onload = null;
|
||||
image.onerror = null;
|
||||
URL.revokeObjectURL(imageUrl);
|
||||
};
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
cleanup();
|
||||
reject(new Error('Image loading timed out.'));
|
||||
}, timeout);
|
||||
|
||||
image.onload = onLoad;
|
||||
image.onerror = onError;
|
||||
});
|
||||
}
|
||||
|
||||
async function decodeBase64Files(base64) {
|
||||
if (!base64) throw new Error('Base64 is empty');
|
||||
|
||||
let bstr = atob(base64), n = bstr.length, u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
|
||||
const zipBlob = new File([u8arr], 'archive.zip');
|
||||
|
||||
let files = [];
|
||||
const zipEntries = await zipper.getEntries(zipBlob);
|
||||
for (let i = 0; i < zipEntries.length; i++) {
|
||||
let fileBlob = await zipper.getData(zipEntries[i]);
|
||||
files.push(new File([fileBlob], zipEntries[i].filename));
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
async function decodeBase64Text(base64) {
|
||||
if (!base64) throw new Error('Base64 is empty');
|
||||
|
||||
return decodeURIComponent(escape(window.atob(base64)))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue