Skip to content

Commit

Permalink
wip: ask when opening external protocol
Browse files Browse the repository at this point in the history
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
  • Loading branch information
hacdias committed Jan 28, 2022
1 parent 5d79b37 commit 30ba843
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 21 deletions.
2 changes: 2 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",
"ok": "OK",
"cancel": "Cancel",
"enable": "Enable",
Expand Down Expand Up @@ -212,6 +213,7 @@
"appPreferences": "App Preferences",
"launchOnStartup": "Launch at Login",
"openWebUIAtLaunch": "Open Web UI at Launch",
"askWhenOpeningIpfsURIs": "Ask When Opening IPFS URIs",
"pubsub": "Enable PubSub",
"namesysPubsub": "Enable IPNS over PubSub",
"automaticGC": "Automatic Garbage Collection",
Expand Down
42 changes: 40 additions & 2 deletions src/dialogs/prompt/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,52 @@ const pallette = {
}
}

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

if (IS_MAC) {
buttons.reverse()
}

const page = makePage({ pallette, message, defaultValue, buttons, id })
return buttons.join('\n')
}

function makeInputs (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')
}

function generatePage ({ message, inputs, defaultValue = '', buttons }, id) {
buttons = makeButtons(buttons)
inputs = makeInputs(inputs)

const page = makePage({ pallette, message, inputs, defaultValue, buttons, id })
return `data:text/html;base64,${Buffer.from(page, 'utf8').toString('base64')}`
}

Expand Down
23 changes: 19 additions & 4 deletions src/dialogs/prompt/template.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
module.exports = ({ pallette, message, defaultValue, buttons, id }) => (`<!DOCTYPE html>
module.exports = ({ pallette, message, inputs, buttons, id }) => (`<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<p>${message}</p>
<input type="text" value="${defaultValue}" />
<div id="buttons">${buttons.join('\n')}</div>
<form>
${inputs}
<div id="buttons">${buttons}</div>
</form>
</body>
<style>
:root {
Expand Down Expand Up @@ -53,6 +55,19 @@ module.exports = ({ pallette, message, defaultValue, buttons, id }) => (`<!DOCTY
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;
}
Expand Down Expand Up @@ -82,7 +97,7 @@ module.exports = ({ pallette, message, defaultValue, buttons, id }) => (`<!DOCTY
for (const button of document.querySelectorAll('button')) {
button.addEventListener('click', event => {
ipcRenderer.send('${id}', {
input: document.querySelector('input').value,
input: Object.fromEntries(new FormData(document.querySelector('form')).entries()),
button: Number(button.id)
})
})
Expand Down
10 changes: 8 additions & 2 deletions src/download-cid.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,13 @@ async function getCID () {
const { button, input } = await showPrompt({
title: i18n.t('downloadCidContentDialog.title'),
message: i18n.t('downloadCidContentDialog.message'),
defaultValue: (isIPFS.cid(text) || isIPFS.path(text)) ? text : '',
inputs: [
{
type: 'text',
name: 'path',
defaultValue: (isIPFS.cid(text) || isIPFS.path(text)) ? text : ''
}
],
buttons: [
i18n.t('downloadCidContentDialog.action'),
i18n.t('cancel')
Expand All @@ -69,7 +75,7 @@ async function getCID () {
return
}

return input
return input.path || ''
}

async function downloadCid (ctx) {
Expand Down
133 changes: 121 additions & 12 deletions src/protocol-handlers.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,130 @@
const { app, shell } = require('electron')
const toUri = require('multiaddr-to-uri')
const i18n = require('i18next')
const store = require('./common/store')
const { showPrompt } = require('./dialogs')

const CONFIG_KEY = 'askWhenOpeningIpfsURIs'
const CONFIG_KEY_ACTION = 'openIpfsURIsAction'

const ACTION_OPTIONS = {
BROWSER_PUBLIC_GATEWAY: 'browserPublicGateway',
BROWSER_LOCAL_GATEWAY: 'browserLocalGateway',
FILES_SCREEN: 'filesScreen',
EXPLORE_SCREEN: 'exploreScreen'
}

const DEFAULT_ACTION = ACTION_OPTIONS.BROWSER_PUBLIC_GATEWAY

async function getAction () {
const ask = store.get(CONFIG_KEY, true)
if (!ask) {
return store.get(CONFIG_KEY_ACTION, DEFAULT_ACTION)
}

const { button, input } = await showPrompt({
title: 'Opening IPFS URIs',
message: 'How would you like IPFS Desktop to open IPFS URIs?',
inputs: [
{
type: 'radio',
name: 'action',
defaultValue: DEFAULT_ACTION,
labels: {
[ACTION_OPTIONS.BROWSER_PUBLIC_GATEWAY]: 'In my default browser using a public gateway',
[ACTION_OPTIONS.BROWSER_LOCAL_GATEWAY]: 'In my default browser using a local gateway',
[ACTION_OPTIONS.FILES_SCREEN]: 'In the Files screen',
[ACTION_OPTIONS.EXPLORE_SCREEN]: 'In the Explore screen'
}
},
{
type: 'checkbox',
name: 'remember',
defaultValue: 'checked',
label: 'Remember this choice for all IPFS URIs'
}
],
buttons: [
i18n.t('continue'),
i18n.t('cancel')
],
window: {
width: 500,
height: 240
}
})

if (button !== 0) {
return
}

const action = input.action || DEFAULT_ACTION

if (input.remember === 'on') {
store.set(CONFIG_KEY, false)
store.set(CONFIG_KEY_ACTION, action)
}

return action
}

function openLink (protocol, part, base) {
shell.openExternal(`${base}/${protocol}/${part}`)
return true
}

function parseAddr (addr) {
return toUri(addr.toString().includes('/http') ? addr : addr.encapsulate('/http'))
}

async function parseUrl (url, ctx) {
const ipfsd = ctx.getIpfsd ? await ctx.getIpfsd(true) : null
let base = 'https://ipfs.io'

if (ipfsd && ipfsd.gatewayAddr) {
base = parseAddr(ipfsd.gatewayAddr)
}
let protocol = ''
let part = ''

if (url.startsWith('ipfs://')) {
return openLink('ipfs', url.slice(7), base)
protocol = 'ipfs'
part = url.slice(7)
} else if (url.startsWith('ipns://')) {
return openLink('ipns', url.slice(7), base)
protocol = 'ipns'
part = url.slice(7)
} else if (url.startsWith('dweb:/ipfs/')) {
return openLink('ipfs', url.slice(11), base)
protocol = 'ipfs'
part = url.slice(11)
} else if (url.startsWith('dweb:/ipns/')) {
return openLink('ipns', url.slice(11), base)
protocol = 'ipns'
part = url.slice(11)
} else {
return false
}

const action = store.get(CONFIG_KEY_ACTION, DEFAULT_ACTION)
let base = 'https://dweb.link'

switch (action) {
case ACTION_OPTIONS.BROWSER_PUBLIC_GATEWAY:
openLink(protocol, part, base)
break
case ACTION_OPTIONS.BROWSER_LOCAL_GATEWAY:
const ipfsd = ctx.getIpfsd ? await ctx.getIpfsd(true) : null

// Best effort. Defaults to public gateway if not available.
if (ipfsd && ipfsd.gatewayAddr) {
base = parseAddr(ipfsd.gatewayAddr)
}

openLink(protocol, part, base)
break
case ACTION_OPTIONS.FILES_SCREEN:
ctx.launchWebUI(`/${protocol}/${part}`, { focus: true })
break
case ACTION_OPTIONS.EXPLORE_SCREEN:
ctx.launchWebUI(`/explore/${protocol}/${part}`, { focus: true })
break
default:
return false
}

return false
openLink(protocol, part, base)
return true
}

async function argvHandler (argv, ctx) {
Expand All @@ -44,6 +140,17 @@ async function argvHandler (argv, ctx) {
}

module.exports = function (ctx) {
// By default, ask. We need to change this to ensure the
// tray option shows a 'tick'.
if (store.get(CONFIG_KEY, null) === null) {
store.set(CONFIG_KEY, true)
}

setTimeout(async () => {
const action = await getAction()
console.log(action)
}, 5000)

// Handle if the app started running now, and a link
// was sent to be handled.
argvHandler(process.argv, ctx)
Expand All @@ -56,3 +163,5 @@ module.exports = function (ctx) {
}

module.exports.argvHandler = argvHandler

module.exports.CONFIG_KEY = CONFIG_KEY
5 changes: 4 additions & 1 deletion src/tray.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const { CONFIG_KEY: NAMESYS_PUBSUB_KEY } = require('./enable-namesys-pubsub')
const { CONFIG_KEY: AUTO_GC_KEY } = require('./automatic-gc')
const { CONFIG_KEY: IPFS_PATH_KEY } = require('./ipfs-on-path')
const { CONFIG_KEY: AUTO_LAUNCH_WEBUI_KEY } = require('./webui')
const { CONFIG_KEY: ASK_OPENING_IPFS_URIS } = require('./protocol-handlers')

const CONFIG_KEYS = [
AUTO_LAUNCH_KEY,
Expand All @@ -27,7 +28,8 @@ const CONFIG_KEYS = [
SCREENSHOT_KEY,
DOWNLOAD_KEY,
PUBSUB_KEY,
NAMESYS_PUBSUB_KEY
NAMESYS_PUBSUB_KEY,
ASK_OPENING_IPFS_URIS
]

// We show them if user enabled them before, but hide when off
Expand Down Expand Up @@ -133,6 +135,7 @@ function buildMenu (ctx) {
},
buildCheckbox(AUTO_LAUNCH_KEY, 'settings.launchOnStartup'),
buildCheckbox(AUTO_LAUNCH_WEBUI_KEY, 'settings.openWebUIAtLaunch'),
buildCheckbox(ASK_OPENING_IPFS_URIS, 'settings.askWhenOpeningIpfsURIs'),
buildCheckbox(AUTO_GC_KEY, 'settings.automaticGC'),
buildCheckbox(IPFS_PATH_KEY, 'settings.ipfsCommandLineTools'),
buildCheckbox(SCREENSHOT_KEY, 'settings.takeScreenshotShortcut'),
Expand Down

0 comments on commit 30ba843

Please sign in to comment.