From 51c7eb353a609153615374221051a1ab75616069 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Wed, 24 May 2017 16:38:36 +1000 Subject: [PATCH 1/2] Add extensions modal --- src/Extensions.css | 68 +++++++++++++++++++++++++++++++++ src/Extensions.js | 94 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 src/Extensions.css create mode 100644 src/Extensions.js diff --git a/src/Extensions.css b/src/Extensions.css new file mode 100644 index 0000000..1ae1c30 --- /dev/null +++ b/src/Extensions.css @@ -0,0 +1,68 @@ +.Extensions { + display: flex; + flex-direction: column; +} +.Extensions .Header .search { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 4px; + color: #fff; + font-size: 1rem; + padding: 0.3rem 0.4rem; +} +.Extensions .Header .search::-webkit-input-placeholder { + color: rgba(255, 255, 255, 0.75); +} +.Extensions .Header .search:focus { + outline: 0; + border-color: rgba(255, 255, 255, 0.5); +} +.extensions-list { + list-style: none; + padding: 0.5rem 0.5rem 0; + margin: 0; + + overflow-y: scroll; + flex-grow: 1; + + font-size: 14px; + display: flex; + flex-direction: column; +} +.extensions-list li { + border: 1px solid #ccc; + border-radius: 0.3em; + padding: 1em; + margin-bottom: 1em; + flex-shrink: 0; + transition: transform 100ms, opacity 130ms; + display: flex; +} +.extensions-list li > :first-child { + flex-grow: 1; +} +.extensions-list li.no-match { + // transform: scale(0.5, 0.5); + opacity: 0; + order: 5; +} +.extensions-list h2 { + margin: 0 0 0.3em; + font-size: 1.3em; +} +.extensions-list p { + margin: 0 0 0.6em; +} +.extensions-list .author { + color: #888; + margin-bottom: 0; +} +.extensions-list .author img { + width: 28px; + height: 28px; + background: var(--color-primary); + padding: 2px; + vertical-align: middle; + margin-right: 0.5em; + border-radius: 4px; +} diff --git a/src/Extensions.js b/src/Extensions.js new file mode 100644 index 0000000..393fbd0 --- /dev/null +++ b/src/Extensions.js @@ -0,0 +1,94 @@ +import React from 'react'; + +import Button from './Button'; +import Header from './Header'; + +import './Extensions.css'; + +const EXTENSIONS_INDEX = 'http://beta.chassis.io/extensions.json'; + +export default class Extensions extends React.Component { + constructor( props ) { + super( props ); + + this.state = { + available: [], + loading: true, + search: '', + }; + + this.loadExtensions(); + } + + loadExtensions() { + fetch( EXTENSIONS_INDEX ) + .then( resp => resp.json() ) + .then( available => this.setState({ available, loading: false }) ); + } + + render() { + const { onDismiss } = this.props; + const { available, loading, search } = this.state; + + const withAuthors = available.map(item => { + const author = { + name: 'Chassis', + avatar: '/logo.png', + }; + + return { ...item, author }; + }) + + const filtered = search === '' ? withAuthors : withAuthors.map( item => { + const searchable = item.name + ' ' + item.description; + console.log( searchable.toLowerCase().indexOf( search.toLowerCase() ) !== -1 ); + return { ...item, matched: searchable.toLowerCase().indexOf( search.toLowerCase() ) !== -1 }; + }); + filtered.map( item => console.log( item.name, search && ! item.matched ) ); + + return
+
+ this.setState({ search: e.target.value }) } + /> + +
+ + { loading ? +

Loading…

+ : + + } +
+ } +} From 5e193b3f5caa4de591e68af1788a2062e63b2c66 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Wed, 24 May 2017 16:42:52 +1000 Subject: [PATCH 2/2] Trigger the extensions modal when opening URLs --- electron.js | 6 ++++++ src/App.js | 5 +++++ src/lib/configure.js | 4 ++++ src/lib/openInternal.js | 27 +++++++++++++++++++++++++++ 4 files changed, 42 insertions(+) create mode 100644 src/lib/openInternal.js diff --git a/electron.js b/electron.js index 214dbe1..8fafc22 100644 --- a/electron.js +++ b/electron.js @@ -143,6 +143,12 @@ app.on('ready', () => { } ]); Menu.setApplicationMenu(mainMenu); + + app.on('open-url', (event, url) => { + event.preventDefault(); + + win.webContents.send('open-url', url); + }); }); // Quit when all windows are closed. diff --git a/src/App.js b/src/App.js index 4c2c0dd..060d60f 100644 --- a/src/App.js +++ b/src/App.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import { hideModal, showModal } from './lib/actions'; import CreateModal from './CreateModal'; +import Extensions from './Extensions'; import Header from './Header'; import Installer from './Installer'; import MachineList from './MachineList'; @@ -33,6 +34,10 @@ class App extends Component { modalComponent = ; break; + case 'extensions': + modalComponent = ; + break; + default: // No-op break; diff --git a/src/lib/configure.js b/src/lib/configure.js index fa14d9c..f08a96d 100644 --- a/src/lib/configure.js +++ b/src/lib/configure.js @@ -2,11 +2,13 @@ * Setup for global handlers. */ import ansiHTML from 'ansi-html'; +import { ipcRenderer } from 'electron'; import which from 'which'; import * as actions from './actions'; import { loadAllConfig } from './actions/loadConfig'; import Keys from './keys'; +import openInternal from './openInternal'; // Refresh every 10 seconds. const REFRESH_INTERVAL = 10000; @@ -38,4 +40,6 @@ export default store => { // Refresh configuration. store.dispatch(loadAllConfig()); } + + ipcRenderer.on('open-url', (event, url) => openInternal(store)(url)); }; diff --git a/src/lib/openInternal.js b/src/lib/openInternal.js new file mode 100644 index 0000000..9e95bd8 --- /dev/null +++ b/src/lib/openInternal.js @@ -0,0 +1,27 @@ +import url from 'url'; + +import { showModal } from './actions'; + +const parseURL = internalURL => { + // Parse URL into parts. + const parsed = url.parse( internalURL, true ); + if ( parsed.protocol !== 'chassis:' ) { + return null; + } + + return parsed; +}; + +export default store => url => { + const parsed = parseURL( url ); + + switch ( parsed.host ) { + case 'install-extension': + store.dispatch( showModal( 'extensions' ) ); + break; + + default: + console.log( 'unknown action' ); + break; + } +};