summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2021-03-31 19:41:58 +0200
committerJules Laplace <julescarbon@gmail.com>2021-03-31 19:41:58 +0200
commit87586f73fc5741070466d189dfab807a190753e5 (patch)
tree33fefbcb3478c8eb35896b7c08cde27c2ca0b1c9
parentcda9c115283be8e4e224f6036ba07e5eca243289 (diff)
add potential to generate custom headers and custom pages
-rw-r--r--README.md13
-rw-r--r--cli/app/controllers/graph_controller.py6
-rw-r--r--cli/app/site/export.py4
-rw-r--r--frontend/app/views/index/components/graph.form.js46
-rw-r--r--frontend/site/index.js2
-rw-r--r--frontend/site/viewer/viewer.container.js25
-rw-r--r--package.json2
-rw-r--r--static/site.html20
-rw-r--r--webpack.config.site.js124
9 files changed, 161 insertions, 81 deletions
diff --git a/README.md b/README.md
index 9e8cca6..c1411c8 100644
--- a/README.md
+++ b/README.md
@@ -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"],
+ },
+ },
+ ],
+ },
+ };
};