diff options
| -rw-r--r-- | README.md | 13 | ||||
| -rw-r--r-- | cli/app/controllers/graph_controller.py | 6 | ||||
| -rw-r--r-- | cli/app/site/export.py | 4 | ||||
| -rw-r--r-- | frontend/app/views/index/components/graph.form.js | 46 | ||||
| -rw-r--r-- | frontend/site/index.js | 2 | ||||
| -rw-r--r-- | frontend/site/viewer/viewer.container.js | 25 | ||||
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | static/site.html | 20 | ||||
| -rw-r--r-- | webpack.config.site.js | 124 |
9 files changed, 161 insertions, 81 deletions
@@ -55,6 +55,19 @@ yarn demo To programmatically create pages, modify `cli/commands/site/populate.py` +## Customizing builds + +If the functionality of the live site needs to change, make a new router and go from there. See `frontend/site/projects/museum/app.js` for an example. + +``` +# Building the museum site (development) +yarn build:museum:dev + +# Building the museum site (production) +yarn build:museum:production +rsync -rlptuvz ./exports/last-museum/ lens@garden:swimmer/data_store/exports/last-museum/ +``` + ## Deploying a site A hypothetical rsync command: diff --git a/cli/app/controllers/graph_controller.py b/cli/app/controllers/graph_controller.py index fcca50a..9169818 100644 --- a/cli/app/controllers/graph_controller.py +++ b/cli/app/controllers/graph_controller.py @@ -13,6 +13,12 @@ class GraphView(CrudView): model = Graph form = GraphForm + def on_create(self, session, form, item): + item.settings = form['settings'] + + def on_update(self, session, form, item): + item.settings = form['settings'] + def on_destroy(self, session, item): for item in item.pages: session.query(Tile).filter(Tile.page_id == item.id).delete(synchronize_session=False) diff --git a/cli/app/site/export.py b/cli/app/site/export.py index aa74165..4ddd01d 100644 --- a/cli/app/site/export.py +++ b/cli/app/site/export.py @@ -25,6 +25,7 @@ def export_site(opt_graph_path, opt_output_dir=app_cfg.DIR_EXPORTS, opt_build_js # load site index index_html = load_text(join(app_cfg.DIR_STATIC, 'site.html'), split=False) + index_html = index.html.replace('CUSTOM_HEADER', graph.settings.get('custom_header', '')) index_html = index_html.replace('SITE_PATH', '/' + graph.path) # write site JSON data @@ -44,7 +45,8 @@ def export_site(opt_graph_path, opt_output_dir=app_cfg.DIR_EXPORTS, opt_build_js print("Homepage not set! Shift-click a page on the graph to make it the homepage.") session.close() return - write_text(f'<meta http-equiv="refresh" content="0; url={home_page}">', join(graph_dir, 'index.html')) + # write_text(f'<meta http-equiv="refresh" content="0; url={home_page}">', join(graph_dir, 'index.html')) + write_index(graph=graph, page=None, index_html=index_html, fp_out=join(graph_dir, 'index.html')) index_path = "" for page in graph.pages: diff --git a/frontend/app/views/index/components/graph.form.js b/frontend/app/views/index/components/graph.form.js index 4b3a7af..5710beb 100644 --- a/frontend/app/views/index/components/graph.form.js +++ b/frontend/app/views/index/components/graph.form.js @@ -10,6 +10,9 @@ const newGraph = () => ({ title: '', username: session('username'), description: '', + settings: { + custom_header: "", + } }) export default class GraphForm extends Component { @@ -20,6 +23,14 @@ export default class GraphForm extends Component { errorFields: new Set([]), } + constructor(props){ + super(props) + this.handleChange = this.handleChange.bind(this) + this.handleSettingsChange = this.handleSettingsChange.bind(this) + this.handleSettingsSelect = this.handleSettingsSelect.bind(this) + this.handleSubmit = this.handleSubmit.bind(this) + } + componentDidMount() { const { data, isNew } = this.props const title = isNew ? 'new project' : 'editing ' + data.title @@ -54,6 +65,23 @@ export default class GraphForm extends Component { }) } + handleSettingsChange(e) { + const { name, value } = e.target + this.handleSettingsSelect(name, value) + } + + handleSettingsSelect(name, value) { + this.setState({ + data: { + ...this.state.data, + settings: { + ...this.state.data.settings, + [name]: value, + } + } + }) + } + handleSelect(name, value) { const { errorFields } = this.state if (errorFields.has(name)) { @@ -73,7 +101,7 @@ export default class GraphForm extends Component { const { isNew, onSubmit } = this.props const { data } = this.state const requiredKeys = "title username path description".split(" ") - const validKeys = "title username path description".split(" ") + const validKeys = "title username path description settings".split(" ") const validData = validKeys.reduce((a,b) => { a[b] = data[b]; return a }, {}) const errorFields = requiredKeys.filter(key => !validData[key]) if (errorFields.length) { @@ -104,7 +132,7 @@ export default class GraphForm extends Component { required data={data} error={errorFields.has('path')} - onChange={this.handleChange.bind(this)} + onChange={this.handleChange} autoComplete="off" /> <LabelDescription> @@ -118,7 +146,7 @@ export default class GraphForm extends Component { required data={data} error={errorFields.has('title')} - onChange={this.handleChange.bind(this)} + onChange={this.handleChange} autoComplete="off" /> <TextInput @@ -127,18 +155,24 @@ export default class GraphForm extends Component { required data={data} error={errorFields.has('username')} - onChange={this.handleChange.bind(this)} + onChange={this.handleChange} autoComplete="off" /> <TextArea title="Description" name="description" data={data} - onChange={this.handleChange.bind(this)} + onChange={this.handleChange} + /> + <TextArea + title="Header tags" + name="custom_header" + data={data.settings} + onChange={this.handleSettingsChange} /> <SubmitButton title={submitTitle} - onClick={this.handleSubmit.bind(this)} + onClick={this.handleSubmit} /> {!!errorFields.size && <label> diff --git a/frontend/site/index.js b/frontend/site/index.js index 337d362..6d80528 100644 --- a/frontend/site/index.js +++ b/frontend/site/index.js @@ -4,7 +4,7 @@ import { Provider } from 'react-redux' import './site.css' -import App from 'site/app' +import App from './APP_TARGET' import { store, history } from 'site/store' diff --git a/frontend/site/viewer/viewer.container.js b/frontend/site/viewer/viewer.container.js index 52eb114..7d8a71b 100644 --- a/frontend/site/viewer/viewer.container.js +++ b/frontend/site/viewer/viewer.container.js @@ -15,6 +15,7 @@ class ViewerContainer extends Component { page: {}, bounds: { width: window.innerWidth, height: window.innerHeight }, roadblock: false, + unloaded: false, popups: {}, hidden: {}, time: 0, @@ -36,6 +37,11 @@ class ViewerContainer extends Component { if (this.props.graph !== prevProps.graph || this.props.location.pathname !== prevProps.location.pathname) { this.load() } + if (this.props.interactive && (this.props.interactive !== prevProps.interactive)) { + this.setState({ roadblock: false }) + this.props.audio.player.playPage(this.state.page) + this.resetTimer(this.state.page) + } } componentWillUnmount() { @@ -44,14 +50,18 @@ class ViewerContainer extends Component { } load() { - const { graph_name, page_name } = this.props.match.params + const { page_name } = this.props.match.params + const { pages, home_page, path: graph_name } = this.props.graph const page_path = ["", graph_name, page_name].join('/') - const { pages, home_page } = this.props.graph + // if ((!page_path in pages) || page_name === 'index.html') { + // this.setState({ unloaded: true }) + // return + // } const page = pages[page_path] || pages[home_page] if (!this.props.interactive && hasAutoplay(page)) { - this.setState({ page, popups: {}, hidden: {}, roadblock: true }) + this.setState({ page, popups: {}, hidden: {}, roadblock: true, unloaded: false }) } else { - this.setState({ page, popups: {}, hidden: {}, roadblock: false }) + this.setState({ page, popups: {}, hidden: {}, roadblock: false, unloaded: false }) actions.site.interact() this.props.audio.player.playPage(page) this.resetTimer(page) @@ -144,6 +154,9 @@ class ViewerContainer extends Component { render() { const { page, audio, popups, hidden, time } = this.state + if (this.state.unloaded) { + return null + } if (this.state.roadblock) { return this.renderRoadblock() } @@ -193,11 +206,7 @@ class ViewerContainer extends Component { } removeRoadblock() { - console.log("remove roadblock") actions.site.interact() - this.setState({ roadblock: false }) - this.props.audio.player.playPage(this.state.page) - this.resetTimer(this.state.page) } renderRoadblock() { diff --git a/package.json b/package.json index 308d26c..c9765a8 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ "watch": "NODE_ENV=development webpack --config ./webpack.config.dev.js --colors --watch", "build:dev": "NODE_ENV=development webpack --config ./webpack.config.dev.js --colors", "build:production": "NODE_ENV=production webpack --config ./webpack.config.prod.js", + "build:museum:dev": "node ./node_modules/webpack-cli/bin/cli.js --config ./webpack.config.site.dev.js -o ./data_store/exports/last-museum/bundle.js --watch --env.APP_TARGET projects/museum/app", + "build:museum:production": "node ./node_modules/webpack-cli/bin/cli.js --config ./webpack.config.site.js -o ./data_store/exports/last-museum/bundle.js --env.APP_TARGET projects/museum/app", "server": "cd cli && ./cli.py flask run --port 5000", "demo": "cd cli && ./cli.py demo run --port 3000" }, diff --git a/static/site.html b/static/site.html index 1412da5..9a41081 100644 --- a/static/site.html +++ b/static/site.html @@ -1,18 +1,18 @@ <!DOCTYPE html> <html> <head> - <meta charset="UTF-8"> - <title>PAGE_TITLE</title> - <meta name="viewport" content="width=device-width,initial-scale=1.0"> - <link rel="stylesheet" type="text/css" href="SITE_PATH/site.css" /> - <style> - html { background: #000; } - </style> +<meta charset="UTF-8"> +<title>PAGE_TITLE</title> +<meta name="viewport" content="width=device-width,initial-scale=1.0"> +CUSTOM_HEADER <link rel="stylesheet" type="text/css" href="SITE_PATH/site.css" /> +<style> +html { background: #000; } +</style> </head> <body> <script> - var s = document.createElement('script'); - s.setAttribute('src', 'BUNDLE_PATH?' + Math.round(Date.now() / 30000)) - document.body.appendChild(s) +var s = document.createElement('script'); +s.setAttribute('src', 'BUNDLE_PATH?' + Math.round(Date.now() / 30000)) +document.body.appendChild(s) </script> </html> diff --git a/webpack.config.site.js b/webpack.config.site.js index 80d84d2..16d35a3 100644 --- a/webpack.config.site.js +++ b/webpack.config.site.js @@ -4,64 +4,78 @@ const webpack = require("webpack"); const path = require("path"); const TerserPlugin = require("terser-webpack-plugin"); -module.exports = { - mode: "production", - entry: { - main: "./frontend/site/index.js", - }, - output: { - path: path.resolve(__dirname, "static/js/dist"), - filename: "bundle.js", - }, - plugins: [ - new webpack.DefinePlugin({ - "process.env.NODE_ENV": JSON.stringify("production"), - // 'process.env.S3_HOST': JSON.stringify(process.env.S3_HOST || ""), - // 'process.env.API_HOST': JSON.stringify(process.env.API_HOST || ""), - __REACT_DEVTOOLS_GLOBAL_HOOK__: "({ isDisabled: true })", - }), - new TerserPlugin(), - new webpack.optimize.AggressiveMergingPlugin(), - // new Visualizer({ - // filename: './statistics.html' - // }) - ], - optimization: { - minimize: true, - minimizer: [ - new TerserPlugin({ - terserOptions: { - compress: { - // drop_console: true, - }, - }, +module.exports = function (env) { + console.log("Building live site (production)"); + console.log(env); + const appTarget = (env && env.APP_TARGET) || "app"; + return { + mode: "production", + entry: { + main: "./frontend/site/index.js", + }, + output: { + path: path.resolve(__dirname, "static/js/dist"), + filename: "bundle.js", + }, + plugins: [ + new webpack.NormalModuleReplacementPlugin( + /(.*)APP_TARGET(\.*)/, + function (resource) { + resource.request = resource.request.replace( + /APP_TARGET/, + `${appTarget}` + ); + } + ), + new webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify("production"), + // 'process.env.S3_HOST': JSON.stringify(process.env.S3_HOST || ""), + // 'process.env.API_HOST': JSON.stringify(process.env.API_HOST || ""), + __REACT_DEVTOOLS_GLOBAL_HOOK__: "({ isDisabled: true })", }), + new TerserPlugin(), + new webpack.optimize.AggressiveMergingPlugin(), + // new Visualizer({ + // filename: './statistics.html' + // }) ], - }, - resolve: { - alias: { - // "react": "preact/compat", - // "react-dom/test-utils": "preact/test-utils", - // "react-dom": "preact/compat", + optimization: { + minimize: true, + minimizer: [ + new TerserPlugin({ + terserOptions: { + compress: { + // drop_console: true, + }, + }, + }), + ], }, - }, - devtool: "cheap-module-source-map", - module: { - rules: [ - { - test: /\.css$/, - use: ["style-loader", "css-loader"], + resolve: { + alias: { + // "react": "preact/compat", + // "react-dom/test-utils": "preact/test-utils", + // "react-dom": "preact/compat", }, - { - test: /\.js$/, - // include: path.resolve(__dirname, 'client'), - exclude: /(node_modules|bower_components|build)/, - loader: "babel-loader", - options: { - presets: ["@babel/preset-react"], - plugins: ["@babel/plugin-transform-runtime"], + }, + devtool: "cheap-module-source-map", + module: { + rules: [ + { + test: /\.css$/, + use: ["style-loader", "css-loader"], }, - }, - ], - }, + { + test: /\.js$/, + // include: path.resolve(__dirname, 'client'), + exclude: /(node_modules|bower_components|build)/, + loader: "babel-loader", + options: { + presets: ["@babel/preset-react"], + plugins: ["@babel/plugin-transform-runtime"], + }, + }, + ], + }, + }; }; |
