diff --git a/.eslintrc b/.eslintrc index eab35926..acd00de8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -13,6 +13,9 @@ "plugin:jest/recommended", "plugin:prettier/recommended" ], + "globals": { + "process": true + }, "plugins": ["jest", "prettier"], "rules": { "max-len": ["error", 80], diff --git a/rollup.config.mjs b/rollup.config.mjs index 320dbb71..bd2716b1 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -28,6 +28,8 @@ export default [ preventAssignment: true, /* workaround for missing env variable */ "process.env.NODE_ENV": JSON.stringify("production"), + "process.env.APP_VERSION": JSON.stringify(pkg.version), + "process.env.BUILD_DATE": JSON.stringify(new Date().toISOString()), }), babel({ exclude: "node_modules/**", babelHelpers: "bundled" }), ], diff --git a/src/css/tales.css b/src/css/tales.css index 6f648347..240e3963 100644 --- a/src/css/tales.css +++ b/src/css/tales.css @@ -341,6 +341,7 @@ label.range input { flex-direction: column; width: 40rem; max-width: 90%; + min-height: 100vh; margin: 0 auto; } @@ -456,6 +457,14 @@ label.range input { flex: 1 1 auto; } +#home footer { + margin-top: auto; + padding: 1rem 0; + text-align: center; + font-size: var(--font-size-small); + font-style: italic; +} + #editor { display: flex; height: 100%; diff --git a/src/js/editor/index.js b/src/js/editor/index.js index 980f35c7..00c8c6c4 100644 --- a/src/js/editor/index.js +++ b/src/js/editor/index.js @@ -10,7 +10,7 @@ import { uploader } from "./upload"; import { slideBounds } from "./slide-bounds"; import { settings } from "../settings"; import { normalizeRect } from "../util/geometry"; -import i18n from "../i18n"; +import { i18n } from "../i18n"; /** * The speed in which the editor zoom level changes when the user zooms in/out. diff --git a/src/js/editor/preview.js b/src/js/editor/preview.js index 164192b4..20c6dc72 100644 --- a/src/js/editor/preview.js +++ b/src/js/editor/preview.js @@ -2,7 +2,7 @@ import { connect, trigger, withInputSignals } from "flyps"; import { h } from "flyps-dom-snabbdom"; import { intersectRects, padRect } from "../util/geometry"; import * as previewMove from "./preview-move"; -import i18n from "../i18n"; +import { i18n } from "../i18n"; export function previewRect( rect, diff --git a/src/js/editor/upload.js b/src/js/editor/upload.js index 331eaff3..4f880ffc 100644 --- a/src/js/editor/upload.js +++ b/src/js/editor/upload.js @@ -1,6 +1,6 @@ import { signal, trigger } from "flyps"; import { h } from "flyps-dom-snabbdom"; -import i18n from "../i18n"; +import { i18n } from "../i18n"; let dropTarget = signal(); diff --git a/src/js/i18n/de.json b/src/js/i18n/de.json index a199440a..dcce28f2 100644 --- a/src/js/i18n/de.json +++ b/src/js/i18n/de.json @@ -1,5 +1,6 @@ { "home.tale-name-prompt": "Gib einen Namen für deine Geschichte ein", + "home.build-info": "Version %{version} (%{buildDate})", "editor.unwritten-tale": "Eine ungeschriebene Geschichte…", "editor.tell": "Erzählen", diff --git a/src/js/i18n/en.json b/src/js/i18n/en.json index 645ad9ea..764b78ae 100644 --- a/src/js/i18n/en.json +++ b/src/js/i18n/en.json @@ -1,5 +1,6 @@ { "home.tale-name-prompt": "Enter the name of your tale", + "home.build-info": "Version %{version} (%{buildDate})", "editor.unwritten-tale": "An unwritten tale…", "editor.tell": "Tell", diff --git a/src/js/i18n/index.js b/src/js/i18n/index.js index 6b503803..07665b6d 100644 --- a/src/js/i18n/index.js +++ b/src/js/i18n/index.js @@ -19,4 +19,9 @@ const polyglot = new Polyglot({ }); export const i18n = (...args) => polyglot.t(...args); -export default i18n; + +export const dateFormatter = new Intl.DateTimeFormat(language, { + year: "numeric", + month: "2-digit", + day: "2-digit", +}); diff --git a/src/js/index.js b/src/js/index.js index 0842a2cb..94a1a668 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -21,7 +21,7 @@ import "./slide"; import { editor } from "./editor/index"; import { presenter } from "./presenter/index"; import * as router from "./router"; -import i18n from "./i18n/index"; +import { i18n, dateFormatter } from "./i18n/index"; import { arrowRight } from "./icons"; handler( @@ -97,6 +97,17 @@ let taleList = withInputSignals( tales => h("ul.tale-list", tales.map(taleItem)), ); +connector("app/version", () => process.env.APP_VERSION || "dev"); +connector("app/build-date", () => + dateFormatter.format(new Date(process.env.BUILD_DATE)), +); + +let footer = () => { + const version = connect("app/version").value(); + const buildDate = connect("app/build-date").value(); + return h("footer", i18n("home.build-info", { version, buildDate })); +}; + let home = () => { let onKeydown = e => { switch (e.which) { @@ -139,8 +150,8 @@ let home = () => { arrowRight(), ), ]), - taleList(), + footer(), ]); }; diff --git a/src/js/presenter/index.js b/src/js/presenter/index.js index 01e016d7..db7fc8dd 100644 --- a/src/js/presenter/index.js +++ b/src/js/presenter/index.js @@ -1,7 +1,7 @@ import { h } from "flyps-dom-snabbdom"; import { viewport } from "../viewport"; -import i18n from "../i18n"; +import { i18n } from "../i18n"; const notFound = () => h("div", i18n("editor.unwritten-tale")); diff --git a/src/js/settings/export.js b/src/js/settings/export.js index ed74ec8a..21eb8b10 100644 --- a/src/js/settings/export.js +++ b/src/js/settings/export.js @@ -1,6 +1,6 @@ import { h } from "flyps-dom-snabbdom"; import { connect, trigger, withInputSignals } from "flyps"; -import i18n from "../i18n"; +import { i18n } from "../i18n"; /** * Export of a Tale as self-contained HTML file. diff --git a/src/js/settings/index.js b/src/js/settings/index.js index 12607806..34a2e15c 100644 --- a/src/js/settings/index.js +++ b/src/js/settings/index.js @@ -13,7 +13,7 @@ import "../util/effects"; import { posterDimSetting } from "./poster"; import { themeSetting } from "./theme"; import { transitionSettings } from "./transition"; -import i18n from "../i18n"; +import { i18n } from "../i18n"; import { exportSettings } from "./export"; const DEFAULT_SETTINGS = { diff --git a/src/js/settings/poster.js b/src/js/settings/poster.js index 664585b3..e4543d5b 100644 --- a/src/js/settings/poster.js +++ b/src/js/settings/poster.js @@ -7,7 +7,7 @@ import { trigger, withInputSignals, } from "flyps"; -import i18n from "../i18n"; +import { i18n } from "../i18n"; /** * Settings for the poster in the editor. diff --git a/src/js/settings/theme.js b/src/js/settings/theme.js index 86349de2..58c8e943 100644 --- a/src/js/settings/theme.js +++ b/src/js/settings/theme.js @@ -7,7 +7,7 @@ import { trigger, withInputSignals, } from "flyps"; -import i18n from "../i18n"; +import { i18n } from "../i18n"; /** * The application UI theme (light/dark). diff --git a/src/js/settings/transition.js b/src/js/settings/transition.js index 79ba4a83..e3cea3a4 100644 --- a/src/js/settings/transition.js +++ b/src/js/settings/transition.js @@ -3,7 +3,7 @@ import { connect, handler, signal, trigger, withInputSignals } from "flyps"; import { easingAnimator } from "../animation"; import { vec3 } from "gl-matrix"; import { DEFAULT_SLIDE_TRANSITION_DURATION } from "../config"; -import i18n from "../i18n"; +import { i18n } from "../i18n"; /** * Possible values (in ms) for transition duration setting.