mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 03:06:41 +00:00
Add an onboarding flow (#1452)
* Add an onboarding flow * Address comments
This commit is contained in:
parent
84b0542fb6
commit
90328cfc33
43
gulp/tasks/gen-onboarding-html.js
Normal file
43
gulp/tasks/gen-onboarding-html.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
const gulp = require('gulp');
|
||||||
|
const path = require('path');
|
||||||
|
const replace = require('gulp-batch-replace');
|
||||||
|
const rename = require('gulp-rename');
|
||||||
|
const md5 = require('../common/md5');
|
||||||
|
const url = require('url');
|
||||||
|
|
||||||
|
const config = require('../config');
|
||||||
|
const minifyStream = require('../common/transform').minifyStream;
|
||||||
|
|
||||||
|
const buildReplaces = {
|
||||||
|
'/frontend_latest/onboarding.js': 'onboarding.js',
|
||||||
|
};
|
||||||
|
|
||||||
|
const es5Extra = "<script src='/static/custom-elements-es5-adapter.js'></script>";
|
||||||
|
|
||||||
|
async function buildOnboarding(es6) {
|
||||||
|
const targetPath = es6 ? config.output : config.output_es5;
|
||||||
|
const targetUrl = es6 ? '/frontend_latest/' : '/frontend_es5/';
|
||||||
|
const frontendPath = es6 ? 'frontend_latest' : 'frontend_es5';
|
||||||
|
const toReplace = [
|
||||||
|
['<!--EXTRA_SCRIPTS-->', es6 ? '' : es5Extra],
|
||||||
|
['/home-assistant-polymer/hass_frontend/onboarding.js', `/${frontendPath}/onboarding.js`],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [replaceSearch, filename] of Object.entries(buildReplaces)) {
|
||||||
|
const parsed = path.parse(filename);
|
||||||
|
const hash = md5(path.resolve(targetPath, filename));
|
||||||
|
toReplace.push([
|
||||||
|
replaceSearch,
|
||||||
|
url.resolve(targetUrl, `${parsed.name}-${hash}${parsed.ext}`)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = gulp.src(path.resolve(config.polymer_dir, 'src/onboarding.html'))
|
||||||
|
.pipe(replace(toReplace));
|
||||||
|
|
||||||
|
return minifyStream(stream, /* es6= */ es6)
|
||||||
|
.pipe(rename('onboarding.html'))
|
||||||
|
.pipe(gulp.dest(es6 ? config.output : config.output_es5));
|
||||||
|
}
|
||||||
|
|
||||||
|
gulp.task('gen-onboarding-html-es5', () => buildOnboarding(/* es6= */ false));
|
||||||
|
gulp.task('gen-onboarding-html', () => buildOnboarding(/* es6= */ true));
|
@ -29,5 +29,5 @@ echo "CREATED_AT = `date +%s`" >> $OUTPUT_DIR_ES5/__init__.py
|
|||||||
# Generate index.htmls with the MD5 hash of the builds
|
# Generate index.htmls with the MD5 hash of the builds
|
||||||
./node_modules/.bin/gulp \
|
./node_modules/.bin/gulp \
|
||||||
gen-index-html gen-index-html-es5 \
|
gen-index-html gen-index-html-es5 \
|
||||||
gen-authorize-html gen-authorize-html-es5
|
gen-authorize-html gen-authorize-html-es5 \
|
||||||
|
gen-onboarding-html gen-onboarding-html-es5
|
||||||
|
@ -15,5 +15,6 @@ cp -r public/__init__.py $OUTPUT_DIR_ES5/
|
|||||||
|
|
||||||
./node_modules/.bin/gulp build-translations gen-icons
|
./node_modules/.bin/gulp build-translations gen-icons
|
||||||
cp src/authorize.html $OUTPUT_DIR
|
cp src/authorize.html $OUTPUT_DIR
|
||||||
|
cp src/onboarding.html $OUTPUT_DIR
|
||||||
|
|
||||||
./node_modules/.bin/webpack --watch --progress
|
./node_modules/.bin/webpack --watch --progress
|
||||||
|
@ -4,6 +4,17 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Home Assistant</title>
|
<title>Home Assistant</title>
|
||||||
<!--EXTRA_SCRIPTS-->
|
<!--EXTRA_SCRIPTS-->
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Roboto', 'Noto', sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<ha-authorize>Loading</ha-authorize>
|
<ha-authorize>Loading</ha-authorize>
|
||||||
|
@ -24,7 +24,7 @@ import '../resources/ha-style.js';
|
|||||||
import '../util/ha-pref-storage.js';
|
import '../util/ha-pref-storage.js';
|
||||||
import { getActiveTranslation, getTranslation } from '../util/hass-translation.js';
|
import { getActiveTranslation, getTranslation } from '../util/hass-translation.js';
|
||||||
import '../util/legacy-support';
|
import '../util/legacy-support';
|
||||||
import '../util/roboto.js';
|
import '../resources/roboto.js';
|
||||||
import hassCallApi from '../util/hass-call-api.js';
|
import hassCallApi from '../util/hass-call-api.js';
|
||||||
import makeDialogManager from '../dialogs/dialog-manager.js';
|
import makeDialogManager from '../dialogs/dialog-manager.js';
|
||||||
import registerServiceWorker from '../util/register-service-worker.js';
|
import registerServiceWorker from '../util/register-service-worker.js';
|
||||||
|
@ -5,6 +5,7 @@ import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
|||||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||||
|
|
||||||
import '../components/ha-iconset-svg.js';
|
import '../components/ha-iconset-svg.js';
|
||||||
|
import '../resources/roboto.js';
|
||||||
|
|
||||||
import '../auth/ha-auth-flow.js';
|
import '../auth/ha-auth-flow.js';
|
||||||
import '../auth/ha-pick-auth-provider.js';
|
import '../auth/ha-pick-auth-provider.js';
|
||||||
|
3
src/entrypoints/onboarding.js
Normal file
3
src/entrypoints/onboarding.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import '../components/ha-iconset-svg.js';
|
||||||
|
import '../resources/roboto.js';
|
||||||
|
import '../onboarding/ha-onboarding.js';
|
34
src/onboarding.html
Normal file
34
src/onboarding.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Home Assistant</title>
|
||||||
|
<!--EXTRA_SCRIPTS-->
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Roboto', 'Noto', sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<ha-onboarding>Loading</ha-onboarding>
|
||||||
|
<script>
|
||||||
|
var webComponentsSupported = (
|
||||||
|
'customElements' in window &&
|
||||||
|
'content' in document.createElement('template'));
|
||||||
|
if (!webComponentsSupported) {
|
||||||
|
var e = document.createElement('script');
|
||||||
|
e.src = '/static/webcomponents-bundle.js';
|
||||||
|
document.write(e.outerHTML);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="/frontend_latest/onboarding.js"></script>
|
||||||
|
<script src='/frontend_latest/hass-icons.js' async></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
125
src/onboarding/ha-onboarding.js
Normal file
125
src/onboarding/ha-onboarding.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
|
||||||
|
import '@polymer/polymer/lib/elements/dom-if.js';
|
||||||
|
import '@polymer/polymer/lib/elements/dom-repeat.js';
|
||||||
|
import '@polymer/paper-input/paper-input.js';
|
||||||
|
import '@polymer/paper-button/paper-button.js';
|
||||||
|
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||||
|
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||||
|
import hassCallApi from '../util/hass-call-api.js';
|
||||||
|
|
||||||
|
const callApi = (method, path, data) => hassCallApi('', {}, method, path, data);
|
||||||
|
|
||||||
|
class HaOnboarding extends PolymerElement {
|
||||||
|
static get template() {
|
||||||
|
return html`
|
||||||
|
<style include="iron-flex iron-positioning"></style>
|
||||||
|
<style>
|
||||||
|
.layout {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="layout vertical center fit">
|
||||||
|
<img src="/static/icons/favicon-192x192.png" height="192">
|
||||||
|
|
||||||
|
<p>Create your owner user account.</p>
|
||||||
|
<p><i>It is not possible yet to change your password. Coming soon!</i></p>
|
||||||
|
|
||||||
|
<template is='dom-if' if='[[_error]]'>
|
||||||
|
<p class='error'>[[_error]]</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<paper-input
|
||||||
|
autofocus
|
||||||
|
label='Name'
|
||||||
|
value='{{_name}}'
|
||||||
|
required
|
||||||
|
auto-validate
|
||||||
|
error-message='Required'
|
||||||
|
on-blur='_maybePopulateUsername'
|
||||||
|
></paper-input>
|
||||||
|
|
||||||
|
<paper-input
|
||||||
|
label='Username'
|
||||||
|
value='{{_username}}'
|
||||||
|
required
|
||||||
|
auto-validate
|
||||||
|
error-message='Required'
|
||||||
|
></paper-input>
|
||||||
|
|
||||||
|
<paper-input
|
||||||
|
label='Password'
|
||||||
|
value='{{_password}}'
|
||||||
|
required
|
||||||
|
type='password'
|
||||||
|
auto-validate
|
||||||
|
error-message='Required'
|
||||||
|
></paper-input>
|
||||||
|
|
||||||
|
<template is='dom-if' if='[[!_loading]]'>
|
||||||
|
<p>
|
||||||
|
<paper-button on-click='_submitForm'>Submit</paper-button>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
_name: String,
|
||||||
|
_username: String,
|
||||||
|
_password: String,
|
||||||
|
_loading: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async ready() {
|
||||||
|
super.ready();
|
||||||
|
this.addEventListener('keypress', (ev) => {
|
||||||
|
if (ev.keyCode === 13) {
|
||||||
|
this._submitForm();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const steps = await callApi('get', 'onboarding');
|
||||||
|
if (steps.every(step => step.done)) {
|
||||||
|
// Onboarding is done!
|
||||||
|
document.location = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_maybePopulateUsername() {
|
||||||
|
if (!this._username) {
|
||||||
|
this._username = this._name.toLowerCase().replace(/ /g, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _submitForm() {
|
||||||
|
if (!this._name || !this._username || !this._password) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await callApi('post', 'onboarding/users', {
|
||||||
|
name: this._name,
|
||||||
|
username: this._username,
|
||||||
|
password: this._password,
|
||||||
|
});
|
||||||
|
|
||||||
|
document.location = '/';
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
console.error(err);
|
||||||
|
this.setProperties({
|
||||||
|
_loading: false,
|
||||||
|
_error: err.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('ha-onboarding', HaOnboarding);
|
@ -20,6 +20,7 @@ function createConfig(isProdBuild, latestBuild) {
|
|||||||
const entry = {
|
const entry = {
|
||||||
app: './src/entrypoints/app.js',
|
app: './src/entrypoints/app.js',
|
||||||
authorize: './src/entrypoints/authorize.js',
|
authorize: './src/entrypoints/authorize.js',
|
||||||
|
onboarding: './src/entrypoints/onboarding.js',
|
||||||
core: './src/entrypoints/core.js',
|
core: './src/entrypoints/core.js',
|
||||||
compatibility: './src/entrypoints/compatibility.js',
|
compatibility: './src/entrypoints/compatibility.js',
|
||||||
'custom-panel': './src/entrypoints/custom-panel.js',
|
'custom-panel': './src/entrypoints/custom-panel.js',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user