const elNavBar = $('#navbar'); const elControls = $('#controls'); const elPreview = $('#preview'); const btnDownload = $('#download'); const query = new URLSearchParams(window.location.search); let path = query.get('path'); activeConnection = connections[query.get('con')]; let fileStats = null; let editor; const updatePreview = async() => { // Make sure the file is viewable const extInfo = getFileExtInfo(path); if (!extInfo.isViewable) { return setStatus(`Error: File isn't viewable!`, true); } let fileUrl; try { const startTime = Date.now(); let lastUpdate = 0; const blob = await api.request('get', 'files/get/single', { path: path }, null, e => { if ((Date.now()-lastUpdate) < 100) return; lastUpdate = Date.now(); const progress = Math.round((e.loaded / fileStats.size) * 100); const bps = Math.round(e.loaded / ((Date.now() - startTime) / 1000)); setStatus(`Downloaded ${formatSize(e.loaded)} of ${formatSize(fileStats.size)} (${formatSize(bps)}/s)`, false, progress); }, 'blob'); fileUrl = URL.createObjectURL(blob); } catch (error) { return setStatus(`Error: ${error}`, true); } elPreview.classList.add(extInfo.type); const statusHtmlSegments = []; switch (extInfo.type) { case 'image': { const image = document.createElement('img'); image.src = fileUrl; await new Promise(resolve => { image.addEventListener('load', resolve); }); elControls.insertAdjacentHTML('beforeend', `
0%
`); const btnZoomOut = $('.btn.zoomOut', elControls); const btnZoomIn = $('.btn.zoomIn', elControls); const btnFit = $('.btn.fit', elControls); const btnReal = $('.btn.real', elControls); const elZoom = $('.zoom', elControls); let fitPercent = 100; const setZoom = percent => { const minZoom = fitPercent; const maxZoom = 1000; const newZoom = Math.min(Math.max(percent, minZoom), maxZoom); elZoom.innerText = `${Math.round(newZoom)}%`; const scaledSize = { width: image.naturalWidth * (newZoom/100), height: image.naturalHeight * (newZoom/100) }; image.style.width = `${scaledSize.width}px`; image.style.height = `${scaledSize.height}px`; }; const changeZoom = percentChange => { const zoom = parseInt(elZoom.innerText.replace('%', '')); setZoom(zoom+percentChange); }; const fitImage = () => { const previewRect = elPreview.getBoundingClientRect(); const previewRatio = previewRect.width / previewRect.height; const imageRatio = image.naturalWidth / image.naturalHeight; fitPercent = 100; if (imageRatio > previewRatio) { fitPercent = (previewRect.width / image.naturalWidth) * 100; } else { fitPercent = (previewRect.height / image.naturalHeight) * 100; } fitPercent = Math.min(fitPercent, 100); setZoom(fitPercent); image.style.marginTop = ''; image.style.marginLeft = ''; }; btnZoomIn.addEventListener('click', () => { changeZoom(10); }); btnZoomOut.addEventListener('click', () => { changeZoom(-10); }); btnFit.addEventListener('click', () => { fitImage(); }); btnReal.addEventListener('click', () => { setZoom(100); }); elPreview.addEventListener('wheel', e => { if (getIsMobileDevice()) return; e.preventDefault(); const previewRect = elPreview.getBoundingClientRect(); const relativePos = { x: (e.clientX - previewRect.left) + elPreview.scrollLeft, y: (e.clientY - previewRect.top) + elPreview.scrollTop }; const percentage = { x: relativePos.x / elPreview.scrollWidth, y: relativePos.y / elPreview.scrollHeight }; changeZoom(e.deltaY > 0 ? -10 : 10); const newScroll = { x: (elPreview.scrollWidth * percentage.x) - relativePos.x, y: (elPreview.scrollHeight * percentage.y) - relativePos.y }; elPreview.scrollLeft += newScroll.x; elPreview.scrollTop += newScroll.y; }); /* let startTouchDistance = 0; elPreview.addEventListener('touchstart', e => { if (!getIsMobileDevice()) return; if (e.touches.length == 2) { e.preventDefault(); const touch1 = e.touches[0]; const touch2 = e.touches[1]; const distance = Math.sqrt( Math.pow(touch1.clientX - touch2.clientX, 2) + Math.pow(touch1.clientY - touch2.clientY, 2) ); startTouchDistance = distance; } }); elPreview.addEventListener('touchmove', e => { if (!getIsMobileDevice()) return; if (e.touches.length == 2) { e.preventDefault(); const touch1 = e.touches[0]; const touch2 = e.touches[1]; const distance = Math.sqrt( Math.pow(touch1.clientX - touch2.clientX, 2) + Math.pow(touch1.clientY - touch2.clientY, 2) ); const percentChange = (distance - startTouchDistance) / 10; changeZoom(percentChange); startTouchDistance = distance; } }); elPreview.addEventListener('touchend', e => { if (!getIsMobileDevice()) return; startTouchDistance = 0; }); */ let startCoords = {}; let startScroll = {}; let isMouseDown = false; elPreview.addEventListener('mousedown', e => { if (getIsMobileDevice()) return; e.preventDefault(); startCoords = { x: e.clientX, y: e.clientY }; startScroll = { x: elPreview.scrollLeft, y: elPreview.scrollTop }; isMouseDown = true; elPreview.style.cursor = 'grabbing'; }); elPreview.addEventListener('dragstart', e => { if (getIsMobileDevice()) return; e.preventDefault(); }); elPreview.addEventListener('mousemove', e => { if (getIsMobileDevice()) return; e.preventDefault(); if (!isMouseDown) return; const newScroll = { x: startCoords.x - e.clientX + startScroll.x, y: startCoords.y - e.clientY + startScroll.y }; // Update preview scroll elPreview.scrollLeft = newScroll.x; elPreview.scrollTop = newScroll.y; }); elPreview.addEventListener('mouseup', e => { if (getIsMobileDevice()) return; e.preventDefault(); isMouseDown = false; elPreview.style.cursor = ''; }); elPreview.addEventListener('mouseleave', e => { if (getIsMobileDevice()) return; e.preventDefault(); isMouseDown = false; elPreview.style.cursor = ''; }); elControls.style.display = ''; elPreview.innerHTML = ''; elPreview.appendChild(image); statusHtmlSegments.push(`${image.naturalWidth}x${image.naturalHeight}`); fitImage(); window.addEventListener('resize', fitImage); break; } case 'video': { const video = document.createElement('video'); video.src = fileUrl; await new Promise(resolve => { video.addEventListener('loadedmetadata', resolve); }); video.controls = true; elPreview.innerHTML = ''; elPreview.appendChild(video); video.play(); statusHtmlSegments.push(`${formatSeconds(video.duration)}`); statusHtmlSegments.push(`${video.videoWidth}x${video.videoHeight}`); break; } case 'audio': { const audio = document.createElement('audio'); audio.src = fileUrl; await new Promise(resolve => { audio.addEventListener('loadedmetadata', resolve); }); audio.controls = true; elPreview.innerHTML = ''; elPreview.appendChild(audio); audio.play(); statusHtmlSegments.push(`${formatSeconds(audio.duration)}`); break; } case 'markdown': case 'text': { // Initialize the textarea const text = await (await fetch(fileUrl)).text(); //const textarea = document.createElement('textarea'); // Initialize CodeMirror elPreview.innerHTML = ''; editor = CodeMirror(elPreview, { value: text, lineNumbers: true, lineWrapping: true, scrollPastEnd: true, styleActiveLine: true, autoCloseBrackets: true, mode: extInfo.codeMirrorMode }); const elEditor = $('.CodeMirror', elPreview); // Load CodeMirror mode if (extInfo.codeMirrorMode) { let mode; CodeMirror.requireMode(extInfo.codeMirrorMode, () => {}, { path: determinedMode => { mode = determinedMode; return `https://codemirror.net/5/mode/${determinedMode}/${determinedMode}.js`; } }); CodeMirror.autoLoadMode(editor, mode); } // Add HTML elControls.insertAdjacentHTML('beforeend', `
18
`); // Set up the save button const btnSave = $('.btn.save', elControls); const btnSaveText = $('span', btnSave); btnSave.addEventListener('click', async() => { if (btnSave.disabled) return; btnSaveText.innerText = 'Saving...'; btnSave.disabled = true; btnSave.classList.remove('info'); // const res1 = await api.delete('files/delete', { // path: path // }); const res1 = {}; const res2 = await api.post('files/create', { path: path //}, textarea.value); }, editor.getValue()); if (res1.error || res2.error) { setStatus(`Error: ${res2.error || res1.error}`, true); btnSaveText.innerText = 'Save'; btnSave.disabled = false; btnSave.classList.add('info'); } else { btnSaveText.innerText = 'Saved!'; await getUpdatedStats(); setStatusWithDetails(); } }); //textarea.addEventListener('input', () => { editor.on('change', () => { btnSave.disabled = false; btnSave.classList.add('info'); btnSaveText.innerText = 'Save'; }); window.addEventListener('keydown', e => { if (e.ctrlKey && e.code == 'KeyS') { e.preventDefault(); btnSave.click(); } if (e.ctrlKey && e.code == 'Minus') { e.preventDefault(); btnTextSmaller.click(); } if (e.ctrlKey && e.code == 'Equal') { e.preventDefault(); btnTextBigger.click(); } }); window.addEventListener('beforeunload', e => { if (!btnSave.disabled) { e.preventDefault(); e.returnValue = ''; } }); // Set up the word wrap checkbox const wrapCheckbox = $('input[type="checkbox"]', elControls); wrapCheckbox.addEventListener('change', () => { const isChecked = wrapCheckbox.checked; //textarea.style.whiteSpace = isChecked ? 'pre-wrap' : 'pre'; editor.setOption('lineWrapping', isChecked); window.localStorage.setItem('wrapTextEditor', isChecked); }); wrapCheckbox.checked = window.localStorage.getItem('wrapTextEditor') == 'true'; wrapCheckbox.dispatchEvent(new Event('change')); // Set up markdown controls let elRendered; if (extInfo.type == 'markdown') { elRendered = document.createElement('div'); elRendered.classList = 'rendered'; elRendered.style.display = 'none'; const btnPreview = $('.btn.view', elControls); const btnEdit = $('.btn.edit', elControls); // Set up the markdown preview button btnPreview.addEventListener('click', async() => { btnPreview.style.display = 'none'; btnEdit.style.display = ''; elRendered.style.display = ''; //textarea.style.display = 'none'; elEditor.style.display = 'none'; //elRendered.innerHTML = marked.parse(textarea.value); elRendered.innerHTML = marked.parse(editor.getValue()); // Make all links open in a new tab const links = $$('a', elRendered); for (const link of links) { link.target = '_blank'; } }); // Set up the markdown edit button btnEdit.addEventListener('click', async() => { btnPreview.style.display = ''; btnEdit.style.display = 'none'; elRendered.style.display = 'none'; //textarea.style.display = ''; elEditor.style.display = ''; }); // View file by default btnEdit.click(); } // Set up text size buttons const btnTextSmaller = $('.btn.textSmaller', elControls); const btnTextBigger = $('.btn.textBigger', elControls); const elTextSize = $('.textSize', elControls); let size = parseInt(window.localStorage.getItem('textEditorSize')) || 18; const updateTextSize = () => { //textarea.style.fontSize = `${size}px`; elEditor.style.fontSize = `${size}px`; elTextSize.innerText = size; window.localStorage.setItem('textEditorSize', size); } updateTextSize(); btnTextSmaller.addEventListener('click', () => { size--; updateTextSize(); }); btnTextBigger.addEventListener('click', () => { size++; updateTextSize(); }); // Finalize elements elControls.style.display = ''; //elPreview.appendChild(textarea); if (extInfo.type == 'markdown') elPreview.appendChild(elRendered); break; } default: { elPreview.innerHTML = `

Error!

`; break; } } const setStatusWithDetails = () => { setStatus(`
${formatSize(fileStats.size)} ${statusHtmlSegments.join('\n')} ${extInfo.mime} ${getRelativeDate(fileStats.modifyTime)}
`) }; setStatusWithDetails(); setTimeout(setStatusWithDetails, 60*1000); } const getUpdatedStats = async() => { // Stat file const res = await api.get('files/stat', { path: path }); fileStats = res.stats; return res; } window.addEventListener('load', async() => { const res = await getUpdatedStats(); if (!res.error) { // Update navbar path = res.path; document.title = `${activeConnection.name} - ${path}`; const pathSplit = path.split('/'); const folderPath = `${pathSplit.slice(0, pathSplit.length - 1).join('/')}/`; const fileName = pathSplit[pathSplit.length - 1]; $('.path', elNavBar).innerText = folderPath; $('.name', elNavBar).innerText = fileName; updatePreview(fileName); } else { return setStatus(`Error: ${res.error}`, true); } }); btnDownload.addEventListener('click', async() => { const fileName = $('.name', elNavBar).innerText; const elSrc = $('img, video, audio', elPreview); const elText = $('textarea', elPreview); if (elSrc) { console.log(`Starting download using downloaded blob`); return downloadUrl(elSrc.src, fileName); } else if (elText && editor) { console.log(`Starting download using text editor value`); const value = editor.getValue(); const dataUrl = `data:text/plain;base64,${btoa(value)}`; return downloadUrl(dataUrl, fileName); } else { console.log(`Starting download using URL API`); const url = await getFileDownloadUrl(path) downloadUrl(url); } }); // Let the window finish displaying itself before saving size setTimeout(() => { window.addEventListener('resize', () => { window.localStorage.setItem('viewerWidth', window.innerWidth); window.localStorage.setItem('viewerHeight', window.innerHeight); }); }, 2000);