Hi, it's Takuya.
I'm publishing YouTube content, sharing my dev workflows.
Want a side-by-side preview in a terminal app
For web coding tutorials, it'd be great to show your coding and output side-by-side.
Typically, you would do that by simply arranging two windows: An editor and a browser.
But that's annoying because every time you make a tutorial, you have to align the windows side-by-side.
Hyper, a terminal app built with web standards, once had a built-in webview feature:
As you can see above, it displays a preview on the right side of the terminal window.
It looks pretty neat.
So, you can make tutorials without having a separate window for preview.
But it looks like the feature has been removed for security reasons:
It was 3 years ago, a quite old issue.
I understand the risk of loading web pages in Electron apps.
However, webview
of the recent Electron prohibits NodeJS integration by default. So, I think it's safe to embed in Hyper, especially when you understand what you do.
Hacking Hyper to get back the built-in webview feature
So, I decided to get that feature back to Hyper, and successfully did it.
Here is how it looks like:
Demo video:
How to build
I've made built-in-webview
branch for the hack:
https://github.com/craftzdog/hyper/tree/built-in-webview
Clone this repo and built it yourself as following.
npm i
npm run dev
# On another terminal session
npm run app
How to use
Split the window right by pressing Cmd-D
.
Then, type echo <URL-to-open>
and hit Return
.
Click the URL in the terminal.
Then, the pane becomes a webview that loads the URL.
How I hacked Hyper
Check out the diffs
First, you have to allow webview
tags in app/ui/window.ts
:
const winOpts: BrowserWindowConstructorOptions = {
minWidth: 370,
minHeight: 190,
backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'),
titleBarStyle: 'hiddenInset',
title: 'Hyper.app',
// we want to go frameless on Windows and Linux
frame: process.platform === 'darwin',
transparent: process.platform === 'darwin',
icon,
show: Boolean(process.env.HYPER_DEBUG || process.env.HYPERTERM_DEBUG || isDev),
acceptFirstMouse: true,
webPreferences: {
nodeIntegration: true,
navigateOnDragDrop: true,
enableRemoteModule: true,
- contextIsolation: false
+ contextIsolation: false,
+ webviewTag: true
},
...options_
};
Hyper partially remains the old implementations. You can reuse them.
The terminal component already has url
prop.
You can display webview
when the component has url
prop.
In lib/components/term.tsx
, change the terminal component class like so:
@@ -430,18 +436,35 @@ export default class Term extends React.PureComponent<TermProps> {
style={{padding: this.props.padding}}
onMouseUp={this.onMouseUp}
>
- {this.props.customChildrenBefore}
- <div ref={this.onTermWrapperRef} className="term_fit term_wrapper" />
- {this.props.customChildren}
- {this.props.search ? (
- <SearchBox
- search={this.search}
- next={this.searchNext}
- prev={this.searchPrevious}
- close={this.closeSearchBox}
+ {this.props.url ? (
+ <webview
+ src={this.props.url}
+ style={{
+ background: '#fff',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ display: 'inline-flex',
+ width: '100%',
+ height: '100%'
+ }}
/>
) : (
- ''
+ <>
+ {this.props.customChildrenBefore}
+ <div ref={this.onTermWrapperRef} className="term_fit term_wrapper" />
+ {this.props.customChildren}
+ {this.props.search ? (
+ <SearchBox
+ search={this.search}
+ next={this.searchNext}
+ prev={this.searchPrevious}
+ close={this.closeSearchBox}
+ />
+ ) : (
+ ''
+ )}
+ </>
)}
<style jsx global>{`
And, change the URL click handler to dispatch an action instead of opening up the page in an external browser:
@@ -160,7 +160,13 @@ export default class Term extends React.PureComponent<TermProps> {
this.term.loadAddon(
new WebLinksAddon(
(event: MouseEvent | undefined, uri: string) => {
- if (shallActivateWebLink(event)) void shell.openExternal(uri);
+ // if (shallActivateWebLink(event)) void shell.openExternal(uri);
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
+ store.dispatch({
+ type: 'SESSION_URL_SET',
+ uid: props.uid,
+ url: uri
+ });
},
{
// prevent default electron link handling to allow selection, e.g. via double-click
In lib/reducers/sessions.ts
, add the reducer for SESSION_URL_SET
like so:
@@ -11,7 +11,8 @@ import {
SESSION_SET_XTERM_TITLE,
SESSION_SET_CWD,
SESSION_SEARCH,
- SESSION_SEARCH_CLOSE
+ SESSION_SEARCH_CLOSE,
+ SESSION_URL_SET
} from '../constants/sessions';
import {sessionState, session, Mutable, ISessionReducer} from '../hyper';
@@ -129,6 +130,9 @@ const reducer: ISessionReducer = (state = initialState, action) => {
}
return state;
+ case SESSION_URL_SET:
+ return state.setIn(['sessions', action.uid, 'url'], action.url);
+
default:
return state;
}
It works like a charm!
Follow me online
- Check out my app called Inkdrop - A Markdown note-taking app
- Subscribe Newsletter http://eepurl.com/dNgJo6
- Twitter https://twitter.com/inkdrop_app
- YouTube channel https://youtube.com/c/devaslife
- Blog https://blog.inkdrop.app/
- Discord community https://discord.gg/QfsG5Kj
- Instagram https://instagram.com/craftzdog