Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improved UX when opening IPFS URIs #1966

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
534b1aa
wip: ask when opening external protocol
hacdias Jan 28, 2022
715303d
feat: ask when opening external protocol
hacdias Feb 2, 2022
46b20bd
Update assets/locales/en.json
hacdias Feb 9, 2022
98314a9
Update assets/locales/en.json
hacdias Feb 9, 2022
70018f2
Update assets/locales/en.json
hacdias Feb 9, 2022
a39f015
Update assets/locales/en.json
hacdias Feb 9, 2022
03db51d
remove explore option for now and ensure app and i18n are ready
hacdias Feb 9, 2022
d91f453
refactor: i18n export
hacdias Feb 17, 2022
8b21910
refactor: rename setup to ready
hacdias Feb 17, 2022
abdeea2
add IPFS logo to dialog
hacdias Feb 17, 2022
3a5a2ab
fetch public gateway from webui window
hacdias Feb 18, 2022
3b7806e
fix styles
hacdias Mar 11, 2022
7de9e9f
only two options, detect localhost subdomains
hacdias Mar 17, 2022
2aca7be
Merge branch 'main' into feat/ux-protocol-handler
hacdias Apr 1, 2022
92cdc43
rename makeButtons and makeInputs
hacdias May 10, 2022
32e6032
refactor prompt html and jsdocs
hacdias May 10, 2022
24ecde4
refactor: separate protocol handler url tools
hacdias May 10, 2022
c0290bb
revert changes to index and wait on protocol hanlers
hacdias May 10, 2022
9dcf458
revert index.js
hacdias May 10, 2022
e6d8dff
Merge branch 'main' into feat/ux-protocol-handler
hacdias May 27, 2022
91f255e
refactor: remove comments
hacdias Jul 15, 2022
e7cd90b
refactor: rename ask askWhenOpeningUri
hacdias Jul 15, 2022
c4752d7
refactor: add comment and be more explicit
hacdias Jul 15, 2022
a861ffb
refactor: add comment, cleanupo
hacdias Jul 15, 2022
4d54b5a
fix: add null to return types
hacdias Jul 15, 2022
18e8b14
fix: wrap JSON.parse around try catch
hacdias Jul 20, 2022
5af40c7
Merge branch 'main' into feat/ux-protocol-handler
hacdias Jul 20, 2022
270d60d
Update assets/locales/en.json
hacdias Aug 2, 2022
b73c451
Merge branch 'main' into feat/ux-protocol-handler
hacdias Sep 2, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions assets/icons/ipfs.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions assets/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"yes": "Yes",
"no": "No",
"close": "Close",
"continue": "Continue",
SgtPooki marked this conversation as resolved.
Show resolved Hide resolved
"ok": "OK",
"cancel": "Cancel",
"enable": "Enable",
Expand Down Expand Up @@ -173,6 +174,7 @@
"appPreferences": "App Preferences",
"launchOnStartup": "Launch at Login",
"openWebUIAtLaunch": "Open Web UI at Launch",
"askWhenOpeningIpfsURIs": "Ask How to Open IPFS Address",
hacdias marked this conversation as resolved.
Show resolved Hide resolved
"pubsub": "Enable PubSub",
"namesysPubsub": "Enable IPNS over PubSub",
"automaticGC": "Automatic Garbage Collection",
Expand Down Expand Up @@ -220,6 +222,17 @@
"title": "Private Network IPFS Repository",
"message": "The repository at “{ path }” is part of a private network, which is not supported by IPFS Desktop."
},
"migrationFailedDialog": {
"title": "IPFS Desktop Migration Has Failed",
"message": "IPFS has encountered an error and migration could not be completed:"
},
"protocolHandlerDialog": {
"title": "Opening IPFS address",
"message": "How would you like IPFS Desktop to open ipfs:// and ipns:// addresses?",
"openInBrowser": "Open in my default browser",
"openInIpfsDesktop": "Open in IPFS Desktop",
"rememberThisChoice": "Remember this choice for all IPFS addresses"
},
"invalidRepositoryDialog": {
"title": "Invalid IPFS Repository or Configuration File",
"message": "The repository at “{ path }” or its configuration is invalid. The “config” file must be a valid JSON.\n\nBefore starting IPFS Desktop again, please fix the configuration file or rename the old repository to “.ipfs.backup”. Please note that by renaming the old repository, IPFS Desktop will generate a new repository"
Expand Down
14 changes: 8 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@
"it-last": "^1.0.6",
"multiaddr": "10.0.1",
"multiaddr-to-uri": "8.0.0",
"multiformats": "^9.6.4",
"node-fetch": "^2.6.7",
"portfinder": "^1.0.28",
"untildify": "^4.0.0",
"v8-compile-cache": "^2.3.0",
Expand Down
3 changes: 2 additions & 1 deletion src/common/config-keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const CONFIG_KEYS = {
OPEN_WEBUI_LAUNCH: 'openWebUIAtLaunch',
MONOCHROME_TRAY_ICON: 'monochromeTrayIcon',
EXPERIMENT_PUBSUB: 'experiments.pubsub',
EXPERIMENT_PUBSUB_NAMESYS: 'experiments.pubsubNamesys'
EXPERIMENT_PUBSUB_NAMESYS: 'experiments.pubsubNamesys',
ASK_OPENING_IPFS_URIS: 'askWhenOpeningIpfsURIs'
}

module.exports = CONFIG_KEYS
5 changes: 4 additions & 1 deletion src/common/consts.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const os = require('os')
const path = require('path')
const fs = require('fs-extra')
const packageJson = require('../../package.json')

module.exports = Object.freeze({
Expand All @@ -10,5 +12,6 @@ module.exports = Object.freeze({
GO_IPFS_VERSION: packageJson.dependencies['go-ipfs'],
COUNTLY_KEY: process.env.NODE_ENV === 'development'
? '6b00e04fa5370b1ce361d2f24a09c74254eee382'
: '47fbb3db3426d2ae32b3b65fe40c564063d8b55d'
: '47fbb3db3426d2ae32b3b65fe40c564063d8b55d',
IPFS_LOGO_URI: 'data:image/svg+xml;base64,' + fs.readFileSync(path.join(__dirname, '../../assets/icons/ipfs.svg')).toString('base64')
})
122 changes: 122 additions & 0 deletions src/dialogs/prompt/html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
const { IS_MAC, IPFS_LOGO_URI } = require('../../common/consts')
const { styles } = require('./styles')

/**
* Generates an HTML string with the given button labels.
* The order of the buttons is inverted on macOS to match the behavior
* of the OS.
*
* @param {string[]} labels
* @returns {string}
*/
function getButtonComponentsHtml (labels) {
const buttons = labels.map((txt, i) => `<button ${i === 0 ? 'class="default"' : ''} id="${i}">${txt}</button>`)

if (IS_MAC) {
buttons.reverse()
}

return buttons.join('\n')
}

/**
* @typedef InputConfiguration
* @type {object}
* @property {string} type
* @property {string} name
* @property {string} defaultValue
* @property {string | null} label
* @property {string[] | null} labels
*/

/**
* Generates an HTML string with the given configurations.
*
* @param {InputConfiguration[]} inputs
* @returns {string}
*/
function getInputComponentsHtml (inputs) {
return inputs.map(({ type, name, label, defaultValue, labels = {} }) => {
let str = '<div>'

switch (type) {
case 'checkbox':
str += '<div class="inline">'
str += `<input type="checkbox" name="${name}" id="${name}" ${defaultValue} />`
str += `<label for="${name}">${label}</label>`
str += '</div>'
break
case 'radio':
str += '<div class="group">'
for (const key in labels) {
str += '<div class="inline">'
str += `<input type="radio" name="${name}" id="${key}" value="${key}" ${defaultValue === key ? 'checked' : ''} />`
str += `<label for="${key}">${labels[key]}</label>`
str += '</div>'
}
str += '</div>'

break
case 'text':
str += `<input type="text" name="${name}" id="${name}" value="${defaultValue}" />`
}

str += '</div>'
return str
}).join('\n')
}

/**
* Generates a base64 encoded URI with the HTML content of a prompt window.
hacdias marked this conversation as resolved.
Show resolved Hide resolved
*
* @param {PromptConfiguration} config
* @param {string} id
* @returns {string}
*/
function getPromptEncodedHtml (config, id) {
const buttons = getButtonComponentsHtml(config.buttons)
const inputs = getInputComponentsHtml(config.inputs)

const html = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<img src="${IPFS_LOGO_URI}" id="logo">
<p>${config.message}</p>
<form>
${inputs}
<div id="buttons">${buttons}</div>
</form>
</body>
<style>
${styles}
</style>
<script>
const { ipcRenderer } = require('electron')

for (const button of document.querySelectorAll('button')) {
button.addEventListener('click', event => {
ipcRenderer.send('${id}', {
input: Object.fromEntries(new FormData(document.querySelector('form')).entries()),
button: Number(button.id)
})
})
}

document.querySelector('input').addEventListener('keypress', (event) => {
if (event.code === 'Enter') {
event.preventDefault()
document.querySelector('button.default').click()
}
})
</script>
</html>`

return `data:text/html;base64,${Buffer.from(html, 'utf8').toString('base64')}`
}

module.exports = {
getPromptEncodedHtml
}
48 changes: 27 additions & 21 deletions src/dialogs/prompt/index.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
const { BrowserWindow, ipcMain } = require('electron')
const crypto = require('crypto')
const { IS_MAC } = require('../../common/consts')

const dock = require('../../utils/dock')
const makePage = require('./template')
const { getBackgroundColor } = require('./styles')
const { getPromptEncodedHtml } = require('./html')

function generatePage ({ message, defaultValue = '', buttons }, id) {
buttons = buttons.map((txt, i) => `<button ${i === 0 ? 'class="default"' : ''} id="${i}">${txt}</button>`)

if (IS_MAC) {
buttons.reverse()
}

const page = makePage({ message, defaultValue, buttons, id })
return `data:text/html;base64,${Buffer.from(page, 'utf8').toString('base64')}`
}
/**
* @typedef PromptConfiguration
* @type {object}
* @property {string} title
* @property {string} message
* @property {InputConfiguration[]} inputs
* @property {string[]} buttons
* @property {object} window
*/

module.exports = async function showPrompt (options) {
options = Object.assign({}, {
/**
* Displays a prompt to the user according to the given configuration.
*
* @param {PromptConfiguration} config
* @returns
*/
module.exports = async function showPrompt (config) {
config = Object.assign({}, {
window: {},
showDock: true
}, options)
}, config)

const window = new BrowserWindow({
title: options.title,
title: config.title,
show: false,
width: 350,
height: 330,
useContentSize: true,
hacdias marked this conversation as resolved.
Show resolved Hide resolved
resizable: false,
autoHideMenuBar: true,
fullscreenable: false,
Expand All @@ -35,7 +41,7 @@ module.exports = async function showPrompt (options) {
nodeIntegration: true,
contextIsolation: false
},
...options.window
...config.window
})

// Generate random id
Expand All @@ -44,20 +50,20 @@ module.exports = async function showPrompt (options) {
return new Promise(resolve => {
ipcMain.once(id, (_, data) => {
window.destroy()
if (options.showDock) dock.hide()
if (config.showDock) dock.hide()
resolve(data)
})

window.on('close', () => {
if (options.showDock) dock.hide()
if (config.showDock) dock.hide()
resolve({ input: '', button: null })
})

window.once('ready-to-show', () => {
if (options.showDock) dock.show()
if (config.showDock) dock.show()
window.show()
})

window.loadURL(generatePage(options, id))
window.loadURL(getPromptEncodedHtml(config, id))
})
}
18 changes: 18 additions & 0 deletions src/dialogs/prompt/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,27 @@ input {
padding: 0.15rem;
outline: 0;
}
div.group {
margin: 0.5rem 0;
}
div.inline input,
div.inline label {
display: inline-block;
width: auto;
vertical-align: middle;
}
input[type=radio],
input[type=checkbox] {
margin-right: 0.25rem;
}
#buttons {
text-align: right;
}
#logo {
width: 4rem;
margin: 0 auto .5rem;
display: block;
}
SgtPooki marked this conversation as resolved.
Show resolved Hide resolved
button {
margin-left: 0.5rem;
padding: 0.25rem 0.5rem;
Expand Down
35 changes: 0 additions & 35 deletions src/dialogs/prompt/template.js

This file was deleted.

Loading