iframe-coordinator-cli 5.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.browserslistrc +2 -0
- package/README.md +34 -0
- package/ifc-cli.js +191 -0
- package/index.html +20 -0
- package/package.json +35 -0
- package/public/favicon.ico +0 -0
- package/src/App.vue +39 -0
- package/src/assets/logo.png +0 -0
- package/src/example-ifc.config.js +36 -0
- package/src/main.ts +41 -0
- package/src/router.ts +17 -0
- package/src/shims-tsx.d.ts +13 -0
- package/src/shims-vue.d.ts +4 -0
- package/src/store.ts +16 -0
- package/src/views/IframeEmbed.vue +157 -0
- package/tsconfig.json +36 -0
- package/tslint.json +17 -0
- package/vite.config.js +5 -0
package/.browserslistrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# wrapper-app
|
|
2
|
+
|
|
3
|
+
## Project setup
|
|
4
|
+
```
|
|
5
|
+
npm install
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
### Compiles and hot-reloads for development
|
|
9
|
+
```
|
|
10
|
+
npm run serve
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Compiles and minifies for production
|
|
14
|
+
```
|
|
15
|
+
npm run build
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Run your tests
|
|
19
|
+
```
|
|
20
|
+
npm run test
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Lints and fixes files
|
|
24
|
+
```
|
|
25
|
+
npm run lint
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Run your unit tests
|
|
29
|
+
```
|
|
30
|
+
npm run test:unit
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Customize configuration
|
|
34
|
+
See [Configuration Reference](https://cli.vuejs.org/config/).
|
package/ifc-cli.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const commander = require('commander');
|
|
4
|
+
const program = new commander.Command();
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const findRoot = require('find-root');
|
|
8
|
+
const express = require('express');
|
|
9
|
+
const cheerio = require('cheerio');
|
|
10
|
+
const https = require('https');
|
|
11
|
+
const devCertAuthority = require('dev-cert-authority');
|
|
12
|
+
const { createProxyMiddleware } = require('http-proxy-middleware');
|
|
13
|
+
|
|
14
|
+
const appPath = path.join(__dirname, './dist/');
|
|
15
|
+
|
|
16
|
+
main();
|
|
17
|
+
|
|
18
|
+
// MAIN
|
|
19
|
+
function main() {
|
|
20
|
+
const opts = parseProgramOptions();
|
|
21
|
+
const indexContent = generateIndex(appPath, opts.clientConfigFile);
|
|
22
|
+
|
|
23
|
+
app = express();
|
|
24
|
+
app.use(/^\/$/, serveIndex(indexContent));
|
|
25
|
+
app.use(
|
|
26
|
+
'/proxy',
|
|
27
|
+
createProxyMiddleware({
|
|
28
|
+
router: extractTargetHost,
|
|
29
|
+
pathRewrite: rewritePath,
|
|
30
|
+
onError: (err, req, res) => {
|
|
31
|
+
console.log('ERROR', err);
|
|
32
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
33
|
+
res.end(err.message);
|
|
34
|
+
},
|
|
35
|
+
target: `http://localhost:${opts.port}` //Required by middleware, but should be always overriden by the previous options
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
app.use(express.static(appPath));
|
|
39
|
+
|
|
40
|
+
if (opts.ssl) {
|
|
41
|
+
const options = getSslOpts(opts.sslCert, opts.sslKey);
|
|
42
|
+
https.createServer(options, app).listen(opts.port);
|
|
43
|
+
} else {
|
|
44
|
+
app.listen(opts.port);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const localhostUrl =
|
|
48
|
+
(opts.ssl ? 'https' : 'http') + '://localhost:' + opts.port + '/';
|
|
49
|
+
console.log(`Listening on port ${opts.port}...`);
|
|
50
|
+
console.log(`Visit host app at: ${localhostUrl}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// HELPER FUNCTIONS
|
|
54
|
+
|
|
55
|
+
function parseProgramOptions() {
|
|
56
|
+
const projRoot = findRoot(process.cwd());
|
|
57
|
+
const defaultJsConfig = path.join(projRoot, 'ifc-cli.config.js');
|
|
58
|
+
|
|
59
|
+
program
|
|
60
|
+
.option(
|
|
61
|
+
'-f, --config-file <file>',
|
|
62
|
+
'iframe client configuration file',
|
|
63
|
+
defaultJsConfig
|
|
64
|
+
)
|
|
65
|
+
.option('-p, --port <port_num>', 'port number to host on', 3000)
|
|
66
|
+
.option('-s, --ssl', 'serve over https')
|
|
67
|
+
.option('--ssl-cert <cert_path>', 'certificate file to use for https')
|
|
68
|
+
.option('--ssl-key <key_path>', 'key file to use for https');
|
|
69
|
+
program.on('--help', showHelpText);
|
|
70
|
+
|
|
71
|
+
program.parse(process.argv);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
clientConfigFile: findConfigFile(program.configFile),
|
|
75
|
+
port: program.port,
|
|
76
|
+
ssl: program.ssl,
|
|
77
|
+
sslCert: program.sslCert,
|
|
78
|
+
sslKey: program.sslKey
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function showHelpText() {
|
|
83
|
+
const configExample = path.join(__dirname, 'example-ifc.config.js');
|
|
84
|
+
console.log(`
|
|
85
|
+
This program will start a server for a basic iframe-coordinator host app. In
|
|
86
|
+
order to configure the frame-router element and any other custom logic needed
|
|
87
|
+
in the host app, a config file must be provided which should assign a
|
|
88
|
+
function to \`module.exports\` that will be passed the frame-router element
|
|
89
|
+
as an input once it has been mounted. The function should return a config
|
|
90
|
+
object with the following fields:
|
|
91
|
+
|
|
92
|
+
- publishTopics: A list of messaging topics the client publishes on
|
|
93
|
+
|
|
94
|
+
Keep in mind that the config file is not a true commonJS module, and
|
|
95
|
+
will be evaluated directly inside the browser in an immediately invoked
|
|
96
|
+
function expression.
|
|
97
|
+
|
|
98
|
+
Here is an example config file:
|
|
99
|
+
`);
|
|
100
|
+
console.log(fs.readFileSync(configExample).toString());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function serveIndex(indexContent) {
|
|
104
|
+
return function(req, res) {
|
|
105
|
+
res.send(indexContent);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function generateIndex(appPath, clientConfigFile) {
|
|
110
|
+
const baseIndex = fs
|
|
111
|
+
.readFileSync(path.join(appPath, 'index.html'))
|
|
112
|
+
.toString();
|
|
113
|
+
const $ = cheerio.load(baseIndex);
|
|
114
|
+
$('head').append(configScript(clientConfigFile));
|
|
115
|
+
return $.html();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function configScript(scriptFile) {
|
|
119
|
+
const scriptContents = fs.readFileSync(scriptFile).toString();
|
|
120
|
+
// This is a bit of a kludge but it should suffice for now.
|
|
121
|
+
return `
|
|
122
|
+
<script type="text/javascript">
|
|
123
|
+
(function () {
|
|
124
|
+
let module = {};
|
|
125
|
+
${scriptContents}
|
|
126
|
+
window.routerSetup = module.exports;
|
|
127
|
+
})()
|
|
128
|
+
</script>
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Find the configuration file
|
|
133
|
+
function findConfigFile(cliPath) {
|
|
134
|
+
let configPath = relativizePath(cliPath);
|
|
135
|
+
if (fs.existsSync(configPath)) {
|
|
136
|
+
return configPath;
|
|
137
|
+
} else {
|
|
138
|
+
console.log(`No client configuration file found @ ${cliPath}.`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getSslOpts(certPath, keyPath) {
|
|
144
|
+
if (!certPath || !keyPath) {
|
|
145
|
+
return devCertAuthority('localhost');
|
|
146
|
+
}
|
|
147
|
+
if (fs.existsSync(certPath) && fs.existsSync(keyPath)) {
|
|
148
|
+
return {
|
|
149
|
+
cert: fs.readFileSync(certPath),
|
|
150
|
+
key: fs.readFileSync(keyPath)
|
|
151
|
+
};
|
|
152
|
+
} else {
|
|
153
|
+
console.log(`Certificate files not found @ ${certPath}, and ${keyPath}`);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Make sure a path isn't interpreted as a module when required.
|
|
159
|
+
function relativizePath(inPath) {
|
|
160
|
+
let outPath = path.relative(process.cwd(), inPath);
|
|
161
|
+
if (!outPath.startsWith('.')) {
|
|
162
|
+
outPath = './' + outPath;
|
|
163
|
+
}
|
|
164
|
+
return outPath;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function extractTargetHost(req) {
|
|
168
|
+
return extractProxyUrl(req.path).origin;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function rewritePath(path) {
|
|
172
|
+
return extractProxyUrl(path).pathname;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function extractProxyUrl(path) {
|
|
176
|
+
const proxyPath = path.replace(/^\/proxy\//, '');
|
|
177
|
+
let newUrl;
|
|
178
|
+
try {
|
|
179
|
+
newUrl = new URL(decodeURIComponent(proxyPath));
|
|
180
|
+
} catch (e) {
|
|
181
|
+
// It would be nice to failed more gracefully here and return a 500, but that doesn't
|
|
182
|
+
// seem to be possible with the way the proxy middleware works.
|
|
183
|
+
// See: https://github.com/chimurai/http-proxy-middleware/issues/411
|
|
184
|
+
console.error(`
|
|
185
|
+
**** INVALID URL IN PROXY PATH ****
|
|
186
|
+
${e.message}
|
|
187
|
+
`);
|
|
188
|
+
throw e;
|
|
189
|
+
}
|
|
190
|
+
return newUrl;
|
|
191
|
+
}
|
package/index.html
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
6
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
|
7
|
+
<link rel="icon" href="/favicon.ico" />
|
|
8
|
+
<title>ifc-wrapper</title>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<noscript>
|
|
12
|
+
<strong
|
|
13
|
+
>We're sorry but ifc-wrapper doesn't work properly without JavaScript
|
|
14
|
+
enabled. Please enable it to continue.</strong
|
|
15
|
+
>
|
|
16
|
+
</noscript>
|
|
17
|
+
<div id="app"></div>
|
|
18
|
+
<script type="module" src="/src/main.ts"></script>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "iframe-coordinator-cli",
|
|
3
|
+
"version": "5.0.0-beta.1",
|
|
4
|
+
"description": "CLI for local iframe-coordinator development",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"cheerio": "^1.0.0-rc.10",
|
|
7
|
+
"commander": "^2.20.3",
|
|
8
|
+
"custom-event-polyfill": "^1.0.7",
|
|
9
|
+
"dev-cert-authority": "^1.1.1",
|
|
10
|
+
"express": "^4.17.3",
|
|
11
|
+
"find-root": "^1.1.0",
|
|
12
|
+
"http-proxy-middleware": "^1.3.1",
|
|
13
|
+
"iframe-coordinator": "file:../iframe-coordinator",
|
|
14
|
+
"vue": "^2.7.10",
|
|
15
|
+
"vue-class-component": "^7.0.2",
|
|
16
|
+
"vue-notification": "^1.3.16",
|
|
17
|
+
"vue-property-decorator": "^8.1.0",
|
|
18
|
+
"vue-router": "^3.0.3",
|
|
19
|
+
"vuex": "^3.0.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@vitejs/plugin-vue2": "^2.0.0",
|
|
23
|
+
"typescript": "^4.8.3",
|
|
24
|
+
"vite": "^3.1.3"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"ifc-cli": "ifc-cli.js"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"serve": "vite",
|
|
31
|
+
"build": "vite build"
|
|
32
|
+
},
|
|
33
|
+
"author": "",
|
|
34
|
+
"license": "MIT"
|
|
35
|
+
}
|
|
Binary file
|
package/src/App.vue
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div id="app">
|
|
3
|
+
<notifications group="toast" id="toastNotifications" position="top right"></notifications>
|
|
4
|
+
<notifications group="pubsub" id="pubsubEvents" position="top left"></notifications>
|
|
5
|
+
<notifications group="keydown" id="keydownEvents" position="bottom left"></notifications>
|
|
6
|
+
<router-view/>
|
|
7
|
+
</div>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<style>
|
|
11
|
+
html {
|
|
12
|
+
height: 100%;
|
|
13
|
+
}
|
|
14
|
+
body {
|
|
15
|
+
padding: 0;
|
|
16
|
+
margin: 0;
|
|
17
|
+
height: 100%;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
#app {
|
|
21
|
+
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
|
22
|
+
-webkit-font-smoothing: antialiased;
|
|
23
|
+
-moz-osx-font-smoothing: grayscale;
|
|
24
|
+
text-align: center;
|
|
25
|
+
color: #2c3e50;
|
|
26
|
+
height: 100%;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.vue-notification.toast {
|
|
30
|
+
background-color: #2a60c8;
|
|
31
|
+
color: #fdfdfd;
|
|
32
|
+
border-color: #75a8ff;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.vue-notification.pubsub {
|
|
36
|
+
background-color: #444a52;
|
|
37
|
+
border-color: #ff4f1f;
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
Binary file
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module.exports = function(frameRouter) {
|
|
2
|
+
frameRouter.setupFrames(
|
|
3
|
+
{
|
|
4
|
+
app1: {
|
|
5
|
+
url: 'http://localhost:8080/client-app-1/#/',
|
|
6
|
+
assignedRoute: '/app1'
|
|
7
|
+
},
|
|
8
|
+
app2: {
|
|
9
|
+
url: 'http://localhost:8080/client-app-2/#/',
|
|
10
|
+
assignedRoute: '/app2',
|
|
11
|
+
sandbox: 'allow-presentation', // optional
|
|
12
|
+
allow: 'microphone http://localhost:8080;' // optional
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
locale: 'en-US',
|
|
17
|
+
hostRootUrl: window.location.origin,
|
|
18
|
+
registeredKeys: [
|
|
19
|
+
{ key: 'a', ctrlKey: true },
|
|
20
|
+
{ key: 'b', altKey: true },
|
|
21
|
+
{ key: 'a', ctrlKey: true, shiftKey: true }
|
|
22
|
+
],
|
|
23
|
+
custom: getCustomClientData()
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
// These are the topics that the host app should display payloads for when
|
|
29
|
+
// the client publishes on them.
|
|
30
|
+
publishTopics: ['publish.topic']
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function getCustomClientData() {
|
|
35
|
+
return { test: 'This is only a test' };
|
|
36
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { registerCustomElements } from 'iframe-coordinator/dist';
|
|
2
|
+
|
|
3
|
+
import Vue from 'vue';
|
|
4
|
+
import Notifications from 'vue-notification';
|
|
5
|
+
import App from './App.vue';
|
|
6
|
+
import router from './router';
|
|
7
|
+
import store from './store';
|
|
8
|
+
|
|
9
|
+
registerCustomElements();
|
|
10
|
+
|
|
11
|
+
window.addEventListener('error', evt => {
|
|
12
|
+
console.error('Uncaught Error:', evt.error);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
Vue.config.productionTip = false;
|
|
16
|
+
|
|
17
|
+
Vue.use(Notifications);
|
|
18
|
+
|
|
19
|
+
new Vue({
|
|
20
|
+
router,
|
|
21
|
+
store,
|
|
22
|
+
render: h => h(App),
|
|
23
|
+
created() {
|
|
24
|
+
// IE11 routing workaround
|
|
25
|
+
if (
|
|
26
|
+
'-ms-scroll-limit' in document.documentElement.style &&
|
|
27
|
+
'-ms-ime-align' in document.documentElement.style
|
|
28
|
+
) {
|
|
29
|
+
window.addEventListener(
|
|
30
|
+
'hashchange',
|
|
31
|
+
event => {
|
|
32
|
+
const currentPath = window.location.hash.slice(1);
|
|
33
|
+
if (this.$route.path !== currentPath) {
|
|
34
|
+
this.$router.push(currentPath);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
false
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}).$mount('#app');
|
package/src/router.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Vue from 'vue';
|
|
2
|
+
import Router from 'vue-router';
|
|
3
|
+
import IframeEmbed from './views/IframeEmbed.vue';
|
|
4
|
+
|
|
5
|
+
Vue.use(Router);
|
|
6
|
+
|
|
7
|
+
export default new Router({
|
|
8
|
+
routes: [
|
|
9
|
+
{
|
|
10
|
+
path: '*',
|
|
11
|
+
component: IframeEmbed,
|
|
12
|
+
props: router => ({
|
|
13
|
+
frameRoute: router.fullPath
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Vue, { VNode } from 'vue';
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
namespace JSX {
|
|
5
|
+
// tslint:disable no-empty-interface
|
|
6
|
+
interface Element extends VNode {}
|
|
7
|
+
// tslint:disable no-empty-interface
|
|
8
|
+
interface ElementClass extends Vue {}
|
|
9
|
+
interface IntrinsicElements {
|
|
10
|
+
[elem: string]: any;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div id="routerLayout">
|
|
3
|
+
<div class="explainer">
|
|
4
|
+
Showing
|
|
5
|
+
<span class="app-route">{{ frameRoute }}</span>
|
|
6
|
+
as
|
|
7
|
+
<span class="frame-url">{{ frameUrl }}</span>
|
|
8
|
+
</div>
|
|
9
|
+
<div v-if="showMenu" id="appMenu">
|
|
10
|
+
<h2>No app is registered for {{ frameRoute }}</h2>
|
|
11
|
+
|
|
12
|
+
<div v-if="Object.keys(clientConfig).length > 0">
|
|
13
|
+
<p>To see your embedded apps, use one of the links below.</p>
|
|
14
|
+
<nav>
|
|
15
|
+
<ul>
|
|
16
|
+
<li v-for="(client, id) in clientConfig" v-bind:key="id">
|
|
17
|
+
<a v-bind:href="'#' + client.assignedRoute">{{id}} @ {{client.assignedRoute}}</a>
|
|
18
|
+
</li>
|
|
19
|
+
</ul>
|
|
20
|
+
</nav>
|
|
21
|
+
</div>
|
|
22
|
+
<div v-else>
|
|
23
|
+
<p>I couldn't find any registered client applications. Please check your ifc-cli configuration file.</p>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<frame-router
|
|
27
|
+
id="frameRouter"
|
|
28
|
+
v-bind:route="frameRoute"
|
|
29
|
+
v-on:notifyRequest="displayToast"
|
|
30
|
+
v-on:registeredKeyFired="handleKeyEvent"
|
|
31
|
+
v-on:navRequest="handleNav"
|
|
32
|
+
v-on:frameTransition="updateFrameUrl"
|
|
33
|
+
></frame-router>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<script>
|
|
38
|
+
export default {
|
|
39
|
+
name: 'iframeEmbed',
|
|
40
|
+
props: ['frameRoute'],
|
|
41
|
+
data() {
|
|
42
|
+
return {
|
|
43
|
+
frameUrl: '',
|
|
44
|
+
showMenu: true,
|
|
45
|
+
clientConfig: {}
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
methods: {
|
|
49
|
+
displayToast(event) {
|
|
50
|
+
const customJson = JSON.stringify(event.detail.custom, null, 2);
|
|
51
|
+
const messageHtml = `<div class="message">${event.detail.message}</div>
|
|
52
|
+
<pre class="customData">${customJson}</pre>`;
|
|
53
|
+
this.$notify({
|
|
54
|
+
group: 'toast',
|
|
55
|
+
title: event.detail.title,
|
|
56
|
+
text: messageHtml,
|
|
57
|
+
duration: event.detail.custom.duration || -1,
|
|
58
|
+
type: 'toast'
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
handleNav(event) {
|
|
62
|
+
// TODO: detect and handle external URLs properly
|
|
63
|
+
const requestedUrl = new URL(event.detail.url);
|
|
64
|
+
window.location.hash = requestedUrl.hash;
|
|
65
|
+
},
|
|
66
|
+
handleKeyEvent(event) {
|
|
67
|
+
this.$notify({
|
|
68
|
+
group: 'keydown',
|
|
69
|
+
title: `registeredKeyFired event from ${event.detail.clientId}`,
|
|
70
|
+
text: `<pre>${JSON.stringify(event.detail, null, 2)}</pre>`,
|
|
71
|
+
duration: 3000,
|
|
72
|
+
type: 'registeredKeyFired'
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
notifyPubSub(event) {
|
|
76
|
+
const jsonStr = JSON.stringify(event, null, 2);
|
|
77
|
+
this.$notify({
|
|
78
|
+
group: 'pubsub',
|
|
79
|
+
title: `${event.clientId} on ${event.topic}`,
|
|
80
|
+
text: `<pre>${jsonStr}</pre>`,
|
|
81
|
+
duration: -1,
|
|
82
|
+
type: 'pubsub'
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
updateFrameUrl(event) {
|
|
86
|
+
this.frameUrl = event.detail;
|
|
87
|
+
this.showMenu = this.frameUrl === 'about:blank';
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
mounted() {
|
|
91
|
+
// Call the custom config set up on the CLI.
|
|
92
|
+
const oldSetupFrames = frameRouter.setupFrames;
|
|
93
|
+
frameRouter.setupFrames = (...args) => {
|
|
94
|
+
this.clientConfig = args[0];
|
|
95
|
+
oldSetupFrames.apply(frameRouter, args);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (window.routerSetup && typeof window.routerSetup === 'function') {
|
|
99
|
+
const clientConfig = window.routerSetup(frameRouter);
|
|
100
|
+
if (clientConfig.publishTopics) {
|
|
101
|
+
clientConfig.publishTopics.forEach(topic => {
|
|
102
|
+
frameRouter.messaging.addListener(topic, publication => {
|
|
103
|
+
this.notifyPubSub(publication);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
// tslint:disable-next-line
|
|
109
|
+
console.log(`====== ERROR ======
|
|
110
|
+
Could not find a function to set up the frame-router element with. Your
|
|
111
|
+
JS configuration file must assign a set-up function to \`module.exports\`.
|
|
112
|
+
Run:
|
|
113
|
+
ifc-cli --help
|
|
114
|
+
for more details.
|
|
115
|
+
`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
</script>
|
|
120
|
+
|
|
121
|
+
<style>
|
|
122
|
+
#routerLayout {
|
|
123
|
+
display: flex;
|
|
124
|
+
flex-direction: column;
|
|
125
|
+
height: 100%;
|
|
126
|
+
}
|
|
127
|
+
#frameRouter {
|
|
128
|
+
flex-grow: 1;
|
|
129
|
+
flex-shrink: 1;
|
|
130
|
+
flex-basis: 100vh;
|
|
131
|
+
}
|
|
132
|
+
#routerLayout .explainer {
|
|
133
|
+
padding: 20px;
|
|
134
|
+
background-color: #33383d;
|
|
135
|
+
color: #fdfdfd;
|
|
136
|
+
border-bottom: 2px solid #ff4f1f;
|
|
137
|
+
}
|
|
138
|
+
#routerLayout .app-route,
|
|
139
|
+
#routerLayout .frame-url {
|
|
140
|
+
color: #ff4f1f;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#appMenu {
|
|
144
|
+
max-width: 80ch;
|
|
145
|
+
margin: auto;
|
|
146
|
+
}
|
|
147
|
+
#appMenu nav {
|
|
148
|
+
text-align: left;
|
|
149
|
+
}
|
|
150
|
+
#appMenu ul {
|
|
151
|
+
margin: 0;
|
|
152
|
+
padding: 0;
|
|
153
|
+
}
|
|
154
|
+
#appMenu li {
|
|
155
|
+
list-style: none;
|
|
156
|
+
}
|
|
157
|
+
</style>
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "esnext",
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"strict": true,
|
|
6
|
+
"jsx": "preserve",
|
|
7
|
+
"importHelpers": true,
|
|
8
|
+
"moduleResolution": "node",
|
|
9
|
+
"experimentalDecorators": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"allowSyntheticDefaultImports": true,
|
|
12
|
+
"sourceMap": true,
|
|
13
|
+
"baseUrl": ".",
|
|
14
|
+
"paths": {
|
|
15
|
+
"@/*": [
|
|
16
|
+
"src/*"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"lib": [
|
|
20
|
+
"esnext",
|
|
21
|
+
"dom",
|
|
22
|
+
"dom.iterable",
|
|
23
|
+
"scripthost"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"include": [
|
|
27
|
+
"src/**/*.ts",
|
|
28
|
+
"src/**/*.tsx",
|
|
29
|
+
"src/**/*.vue",
|
|
30
|
+
"tests/**/*.ts",
|
|
31
|
+
"tests/**/*.tsx"
|
|
32
|
+
],
|
|
33
|
+
"exclude": [
|
|
34
|
+
"node_modules"
|
|
35
|
+
]
|
|
36
|
+
}
|
package/tslint.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"defaultSeverity": "warning",
|
|
3
|
+
"extends": ["tslint:recommended"],
|
|
4
|
+
"linterOptions": {
|
|
5
|
+
"exclude": ["node_modules/**"]
|
|
6
|
+
},
|
|
7
|
+
"rules": {
|
|
8
|
+
"quotemark": [true, "single"],
|
|
9
|
+
"indent": [true, "spaces", 2],
|
|
10
|
+
"interface-name": false,
|
|
11
|
+
"ordered-imports": false,
|
|
12
|
+
"object-literal-sort-keys": false,
|
|
13
|
+
"no-consecutive-blank-lines": false,
|
|
14
|
+
"trailing-comma": false,
|
|
15
|
+
"arrow-parens": false
|
|
16
|
+
}
|
|
17
|
+
}
|