Skip to content

Commit

Permalink
new feature: quick view
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksey-hoffman committed May 27, 2021
1 parent e620976 commit 939d95c
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 27 deletions.
154 changes: 154 additions & 0 deletions public/quickViewWindow.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<!-- SPDX-License-Identifier: GPL-3.0-or-later
License: GNU GPLv3 or later. See the license file in the project root for more information.
Copyright © 2021 - present Aleksey Hoffman. All rights reserved.
-->

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sigma file manager | Quick view</title>
<style>
body {
padding: 0;
margin: 0;
width: 100vw;
height: 100vh;
}

#content-container,
webview,
video,
img {
width: 100%;
height: 100%;
}

video {
outline: none;
}

img {
object-fit: contain;
}

/* #window-toolbar {
height: 36px;
width: 100%;
background: #607d8b;
display: grid;
grid-template-columns: 32px 1fr 32px;
grid-auto-rows: 36px;
user-select: none;
}

.window-toolbar__draggable-area {
-webkit-app-region: drag;
height: 100%;
width: 100%;
user-select: none;
} */
</style>
</head>
<body>
<div id="content-container"></div>
<script>
const { remote, ipcRenderer } = require('electron')
const PATH = require('path')
const MIME = require('mime-types')
const currentWindow = remote.getCurrentWindow()
const contentContainerNode = document.querySelector('#content-container')
let status = {
isCanceled: false
}
/** Removes URL-unsafe symbols like '#'
* @param {string} params.prop1
* @returns {string}
*/
function getUrlSafePath (path) {
const safePath = path
.replace(/#/g, '%23')
.replace(/'/g, '%27')
return safePath

}
function adjastWindowToContentSize (w, h) {
if (w && h && w > 0 && h > 0) {
try {
if (w > h) {
const ratio = w / h
currentWindow.setContentSize(1280, Math.round(1280 / ratio))
currentWindow.center()
}
else {
const ratio = h / w
currentWindow.setContentSize(Math.round(720 / ratio), 720)
currentWindow.center()
}
}
catch (error) {
console.log(error)
}
}
}

// Init listeners
window.addEventListener('keydown', (event) => {
if (event.code === 'Space' || event.code === 'Escape') {
currentWindow.close()
}
})
ipcRenderer.on('load:webview', (event, data) => {
const ext = PATH.extname(data.path)
const mime = MIME.lookup(ext) || ''
const isImage = mime.includes('image/')
const isVideo = mime.includes('video/')
const isAudio = mime.includes('audio/')
const isText = mime.includes('text/')
const isArchive = mime.includes('/x-7z') || mime.includes('/zip') || mime.includes('/x-tar')
// Load the file and adjust window size
if (isVideo) {
const videoNode = document.createElement('video')
videoNode.setAttribute('src', `file://${getUrlSafePath(data.path)}`)
videoNode.setAttribute('controls', true)
videoNode.setAttribute('autoplay', true)
contentContainerNode.appendChild(videoNode)
videoNode.addEventListener('resize', event => {
let w = videoNode.videoWidth
let h = videoNode.videoHeight
adjastWindowToContentSize(w, h)
}, false)
}
else if (isImage) {
const imageNode = document.createElement('img')
imageNode.setAttribute('src', `file://${getUrlSafePath(data.path)}`)
contentContainerNode.appendChild(imageNode)
imageNode.addEventListener('load', event => {
let w = imageNode.naturalWidth
let h = imageNode.naturalHeight
adjastWindowToContentSize(w, h)
}, false)
}
else {
const webviewNode = document.createElement('webview')
webviewNode.setAttribute('id', 'content-webview')
webviewNode.setAttribute('src', `file://${getUrlSafePath(data.path)}`)
contentContainerNode.appendChild(webviewNode)
}
// Wait before showing the window in case the file is not supported
// Otherwise window will flash (close soon after being opened)
setTimeout(() => {
if (!status.isCanceled) {
currentWindow.show()
}
}, 200)
})
ipcRenderer.on('load:webview::cancel', (event, data) => {
status.isCanceled = true
console.log('cancel', status.isCanceled)
})
</script>
</body>
</html>
108 changes: 93 additions & 15 deletions src/electronMain.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ const windows = {
main: null,
hiddenGame: null,
errorWindow: null,
trashManager: null
trashManager: null,
quickViewWindow: null
}
const globalShortcuts = {}
let storageData
Expand Down Expand Up @@ -94,7 +95,32 @@ function createMainWindow () {
// Otherwise the main window will halt during loading
openMainWindowDevTools()
loadWindow('main')
initWindowListeners()
initWindowListeners('main')
createQuickViewWindow()
}

function createQuickViewWindow () {
// Create window
windows.quickViewWindow = new electron.BrowserWindow({
title: 'Sigma file manager | Quick view',
icon: PATH.join(__static, '/icons/logo-1024x1024.png'),
show: false,
// Set content size to 16:9 ratio by default since it's the most common one.
// Window dimensions are adjusted in the quickViewWindow.html, when the content is loaded
useContentSize: true,
width: 1280,
height: 720,
minWidth: 300,
minHeight: 200,
webPreferences: {
webviewTag: true,
enableRemoteModule: true,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
}
})
loadWindow('quickViewWindow')
initWindowListeners('quickViewWindow')
}

function createHiddenGameWindow () {
Expand Down Expand Up @@ -145,9 +171,17 @@ function loadWindow (windowName) {
else if (windowName === 'errorWindow') {
filePath = 'errorWindow.html'
}
else if (windowName === 'quickViewWindow') {
filePath = 'quickViewWindow.html'
developmentPath = `file://${__static}/quickViewWindow.html`
}
// Get window URL
developmentPath = `${process.env.WEBPACK_DEV_SERVER_URL}${filePath}`
productionPath = `app://./${filePath}`
if (!developmentPath) {
developmentPath = `${process.env.WEBPACK_DEV_SERVER_URL}${filePath}`
}
if (!productionPath) {
productionPath = `app://./${filePath}`
}
// Load window URL
if (process.env.WEBPACK_DEV_SERVER_URL) {
windows[windowName].loadURL(developmentPath)
Expand All @@ -161,17 +195,42 @@ function loadWindow (windowName) {
}
}

function initWindowListeners () {
windows.main.on('focus', () => {
windows.main.setSkipTaskbar(false)
windows.main.webContents.send('window:focus')
})
windows.main.on('blur', () => {
windows.main.webContents.send('window:blur')
})
windows.main.on('closed', () => {
windows.main = null
})
function initWindowListeners (name) {
if (name === 'main') {
windows.main.on('focus', () => {
windows.main.setSkipTaskbar(false)
windows.main.webContents.send('window:focus')
})
windows.main.on('blur', () => {
windows.main.webContents.send('window:blur')
})
windows.main.on('closed', () => {
windows.main = null
})
}
else if (name === 'quickViewWindow') {
// Init listeners
windows.quickViewWindow.webContents.session.once('will-download', _willDownloadHandler)
windows.quickViewWindow.once('close', () => {
// Remove listener to avoid multiple listeners
// Without it, a duplicate listener is created every time the windows is closed
windows.quickViewWindow.webContents.session.removeListener('will-download', _willDownloadHandler)
})
windows.quickViewWindow.once('closed', () => {
createQuickViewWindow()
})
function _willDownloadHandler (event, item, webContents) {
event.preventDefault()
console.log('main::load:webview::failed')
const fileURL = item.getURL()
windows.quickViewWindow.webContents.send('load:webview::cancel')
windows.main.webContents.send('load:webview::failed', {path: fileURL})
// Note: close the window even if file is not supported and window.show()
// wasn't called, in order to reset the listeners. Otherwise, unsupported
// files will break the window for all consecutive runs by throwing an error
windows.quickViewWindow.close()
}
}
}

function createUtilWindow (fileName) {
Expand Down Expand Up @@ -326,6 +385,10 @@ function initIPCListeners () {
electron.ipcMain.on('open-hidden-game', (event) => {
createHiddenGameWindow()
})

electron.ipcMain.on('quick-view::open-file', (event, path) => {
openFileInQuickViewWindow(path)
})
}

function registerSafeFileProtocol () {
Expand Down Expand Up @@ -387,6 +450,21 @@ function showBuiltInNotificationUpdateAvailable (payload) {
}, 5000)
}

function openFileInQuickViewWindow (path) {
function _load () {
windows.quickViewWindow.webContents.send('load:webview', {path})
}
if (!windows.quickViewWindow) {
createQuickViewWindow()
windows.quickViewWindow.webContents.once('did-finish-load', () => {
_load()
})
}
else {
_load()
}
}

// TODO: finish update auto install
// async function handleAutoUpdate (payload) {
// const autoDownload = storageData['storageData.settings.appUpdates.autoDownload']
Expand Down
15 changes: 15 additions & 0 deletions src/shortcuts.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ export default {
size: '22px',
description: 'Close opened dialog / overlay; dismiss items prepared for copying or moving, deselect items'
},
openWithQuickView: {
isGlobal: false,
isReadOnly: false,
conditions: {
inputFieldIsActive: false,
dialogIsOpened: false,
},
preventDefaultType: '!inputFieldIsActive',
routes: ['navigator', 'dashboard', 'home'],
icon: 'mdi-card-search-outline',
action: { name: 'OPEN_WITH_QUICK_VIEW' },
shortcut: 'Space',
size: '22px',
description: `Quick view lets you quickly open selected file in a preview window. Supported files: images, videos, audio, PDF, text / plain files`
},
newDirectory: {
isGlobal: false,
isReadOnly: false,
Expand Down
26 changes: 14 additions & 12 deletions src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,17 @@ export default new Vuex.Store({
askForArguments: false
},
defaultItems: [
{
name: 'Quick view',
action: () => {
eventHub.$emit('openWithQuickView')
},
readonly: true,
path: '',
icon: 'mdi-card-search-outline',
askForArguments: false,
targetTypes: ['file', 'file-symlink'],
},
{
name: 'Default file manager',
action: () => {
Expand Down Expand Up @@ -2746,14 +2757,8 @@ export default new Vuex.Store({
}
},
OPEN_FILE ({ state, commit, dispatch, getters }, path) {
if (state.inputState.alt) {
// Open in the preview window
electron.ipcRenderer.send('quick-view::open-file', path)
}
else {
// Open in the default external program
electron.shell.openPath(PATH.normalize(path))
}
// Open in the default external program
electron.shell.openPath(PATH.normalize(path))
dispatch('ADD_TO_DIR_ITEMS_TIMELINE', path)
},
AUTO_FOCUS_FILTER (store) {
Expand Down Expand Up @@ -5501,10 +5506,7 @@ export default new Vuex.Store({
path: state.navigatorView.selectedDirItems.getLast().path
}
payload = {...defaultPayload, ...payload}
// TODO: finish in v1.1.0 (quick file preview)
// Add the "open with::quick file preview" options into context menu
// And add it to the shortcuts list so users know they can open files this way
// Open in the preview window

if (getters.dirItemsSelectionStats.fileCount > 0) {
electron.ipcRenderer.send('quick-view::open-file', payload.path)
}
Expand Down

0 comments on commit 939d95c

Please sign in to comment.