diff --git a/app.vue b/app.vue index d242ed5..f69777f 100644 --- a/app.vue +++ b/app.vue @@ -1,12 +1,21 @@ @@ -26,7 +35,6 @@ import KonamiCode from "konami-code-js"; import Swal from "sweetalert2"; import { defineComponent } from "vue"; import AppState from "./classes/AppState"; -import dataJson from "./src/assets/data.json"; import data from "data/test.json"; export default defineComponent({ @@ -37,7 +45,6 @@ export default defineComponent({ modal: Modal; adminModal: Modal; data: typeof data; - dataJson: TypeApp; selectedItem: Item | null; konami: KonamiCode; contacts: Array; @@ -46,7 +53,6 @@ export default defineComponent({ modalId: "productModal", adminModalId: "adminModal", data, - dataJson, selectedItem: null, modal: {} as Modal, adminModal: {} as Modal, @@ -54,9 +60,8 @@ export default defineComponent({ contacts: [] }; }, - computed: { - }, - watch :{ + computed: {}, + watch: { selectedItem() { this.$nextTick(() => { if (!Object.entries(this.modal).length) { @@ -74,14 +79,12 @@ export default defineComponent({ // * Make sure array does not start using 0 index const items: typeof data.items = []; this.data.items.forEach((item, itemIndex) => { - items[itemIndex +1] = item; + items[itemIndex + 1] = item; }); this.data.items = items; - // * Init contacts (Fetch form local storage or new) const serializedContacts = window.localStorage.getItem("contacts"); this.contacts = !serializedContacts ? new Array() : JSON.parse(serializedContacts); - // * Hidden action and modal show this.konami = new KonamiCode(() => { this.$nextTick(() => { @@ -97,20 +100,20 @@ export default defineComponent({ }); // * Set Data State - useState("dataJson", () => new AppState(this.dataJson)); + window.ipcRendererObject.getJsonData().then((dataJson) => + useState("dataJson", () => new AppState(dataJson))); + useState("brands", () => []); }, methods: { itemClicked(item: Item): void { this.selectedItem = item; }, - isMultOf(numerator:number, divider: number):boolean { + isMultOf(numerator: number, divider: number): boolean { return numerator % divider === 0; }, SaveContact(contact: Contact) { this.contacts.push(contact); - window.localStorage.setItem("contacts", JSON.stringify(this.contacts)); - this.modal.hide(); this.$nextTick(() => this.ShowConfirmedInscription()); }, @@ -134,6 +137,18 @@ export default defineComponent({ background: getComputedStyle(document.body).getPropertyValue("--bs-body-bg") }); }, - }, + } }); + + diff --git a/assets/.gitkeep b/assets/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/assets/Logo-LDC.jpg b/assets/Logo-LDC.jpg new file mode 100644 index 0000000..ac357fe Binary files /dev/null and b/assets/Logo-LDC.jpg differ diff --git a/assets/layout/ldc-intl.png b/assets/layout/ldc-intl.png new file mode 100644 index 0000000..8e3300b Binary files /dev/null and b/assets/layout/ldc-intl.png differ diff --git a/classes/AppState.ts b/classes/AppState.ts index d811c36..9c2dd68 100644 --- a/classes/AppState.ts +++ b/classes/AppState.ts @@ -2,6 +2,9 @@ export default class AppState { /** Json data as exported from zip and compliant with schemas */ private jsonData: TypeApp; + /** The selected brand */ + private selectedBrand: TypeBrand | null = null; + constructor(jsonData: TypeApp) { this.jsonData = jsonData; } @@ -10,11 +13,29 @@ export default class AppState { return this.jsonData.settings; } + // * --- BRANDS + public brands(): Array { return this.jsonData.items; } - public brand(id:number): TypeBrand|undefined { - return this.jsonData.items.find(brand => brand.id === id); + public brand(slug:string): TypeBrand|undefined { + return this.jsonData.items.find(brand => brand.slug === slug); } + + public useBrand(brand: TypeBrand) { + this.selectedBrand = brand; + clearNuxtState("brands"); + useState("brands", (): Array => brand.ranges); + } + + // * --- END BRANDS + + // * --- RANGES + + public ranges(): Array { + return useState("brands", (): Array => []).value; + } + + // * --- END RANGES } diff --git a/classes/Toast.ts b/classes/Toast.ts new file mode 100644 index 0000000..da85443 --- /dev/null +++ b/classes/Toast.ts @@ -0,0 +1,42 @@ +import log from "electron-log"; + +export enum ToastTypeEnum { + default = "default", + success = "success", + info = "info", + error = "error", + warning = "warning", +} + +export const ToastType = Object.freeze({ + default: ToastTypeEnum.default, + success: ToastTypeEnum.success, + info: ToastTypeEnum.info, + error: ToastTypeEnum.error, + warning: ToastTypeEnum.warning, +}); + +export default abstract class Toast { + + /** Affiche un toast message */ + public static toast(message: string, type: ToastTypeEnum = ToastType.default, title: string|null = null):void { + const toast = useState("toastMessage").value as (message: string, type: ToastTypeEnum, title:string|null) => void; + toast(message, type, title); + const prefix: string = `${type.toLowerCase()} : `; + switch (type) { + case ToastType.info: + log.info(prefix, message); + break; + case ToastType.warning: + log.warn(prefix, message); + break; + case ToastType.error: + log.error(prefix, message); + break; + case ToastType.default: + case ToastType.success: + default: + log.verbose(prefix, message); + } + } +} diff --git a/components/ToastContainer.vue b/components/ToastContainer.vue new file mode 100644 index 0000000..966ebf9 --- /dev/null +++ b/components/ToastContainer.vue @@ -0,0 +1,74 @@ + + + diff --git a/components/layout/AppFooter.vue b/components/layout/AppFooter.vue new file mode 100644 index 0000000..266a6f6 --- /dev/null +++ b/components/layout/AppFooter.vue @@ -0,0 +1,54 @@ + + + diff --git a/components/layout/AppHeader.vue b/components/layout/AppHeader.vue new file mode 100644 index 0000000..f48580d --- /dev/null +++ b/components/layout/AppHeader.vue @@ -0,0 +1,81 @@ + + + + + + + + diff --git a/electron/BrowserWinHandler.ts b/electron/BrowserWinHandler.ts index 54da004..3d80384 100644 --- a/electron/BrowserWinHandler.ts +++ b/electron/BrowserWinHandler.ts @@ -83,17 +83,7 @@ export default class BrowserWinHandler { if (!this.browserWindow) return Promise.reject(new Error('The page could not be loaded before win \'created\' event')) const serverUrl = isDev ? DEV_SERVER_URL : 'app://./index.html' const fullPath = serverUrl + '/' + pagePath; - await this.browserWindow.loadURL(fullPath) - } - - /** - * - * @returns {Promise} - */ - created () { - return new Promise(resolve => { - this.onCreated(() => resolve(this.browserWindow)) - }) + return this.browserWindow.loadURL(fullPath); } /** diff --git a/electron/main.ts b/electron/main.ts index ba3f039..a54db9c 100755 --- a/electron/main.ts +++ b/electron/main.ts @@ -1,3 +1,4 @@ +import fs from "node:fs"; import path from "node:path"; import { app, ipcMain } from "electron"; import log from "electron-log/main"; @@ -7,6 +8,17 @@ import UnzipData from "./UnzipData"; // TODO: take inspiration form https://github.com/michalzaq12/electron-nuxt/tree/master/template/src/main. // Or https://github.com/EternalC0der/electron-nuxt3 +const packageJson = JSON.parse(fs.readFileSync( + path.join(__dirname, "../package.json"), + { encoding: "utf8", flag: "r" }) +); + +export const appInfo = { + version: String(packageJson.version), + name: String(packageJson.name), + licence: String(packageJson.licence) +}; + log.initialize(); // Quit when all windows are closed. @@ -19,13 +31,16 @@ app.on("window-all-closed", function () { app.whenReady().then(() => { // * Run an Electron window ? const winHandler = new BrowserWinHandler({ - kiosk: false, + // width: 1280, + // height: 900, + center: true, + kiosk: true, closable: true, alwaysOnTop: false, - fullscreen: false, + fullscreen: true, autoHideMenuBar: false, webPreferences: { - preload: path.join(__dirname, "versions.js") + preload: path.join(__dirname, "preload.js") } }); @@ -35,13 +50,10 @@ app.whenReady().then(() => { if (!appData) { throw new Error("No zip file to load"); } - // this is the event listener that will respond when we will request it in the web page - ipcMain.on("get-config", (event) => { - log.info("read json app config"); - event.returnValue = appData; - }); winHandler.loadPage("ranges"); + ipcMain.handle("appInfo", () => appInfo); + ipcMain.handle("channel", () => appData); winHandler.openDevTool(); } catch (err) { log.error(err); diff --git a/electron/preload.ts b/electron/preload.ts new file mode 100644 index 0000000..416591d --- /dev/null +++ b/electron/preload.ts @@ -0,0 +1,22 @@ +import { contextBridge, ipcRenderer } from "electron"; +import type { appInfo } from "./main"; + +export const versions = { + node: (): string => process.versions.node, + chrome: (): string => process.versions.chrome, + electron: (): string => process.versions.electron, + // Nous pouvons exposer des variables en plus des fonctions + isDebug: process.env.NODE_ENV === "development" +}; + +export const ipcRendererObject = { + getAppInfo: (): Promise => { return ipcRenderer.invoke("appInfo"); }, + getJsonData: (): Promise => { return ipcRenderer.invoke("channel"); } +}; + + +process.once("loaded", () => { + // This runs in sandboxed mode. + contextBridge.exposeInMainWorld("versions", versions); + contextBridge.exposeInMainWorld("ipcRendererObject", ipcRendererObject); +}); diff --git a/electron/preload/versions.ts b/electron/preload/versions.ts deleted file mode 100644 index 565fc15..0000000 --- a/electron/preload/versions.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { contextBridge } from "electron"; - -// This runs in sandboxed mode. -contextBridge.exposeInMainWorld("versions", { - node: () => process.versions.node, - chrome: () => process.versions.chrome, - electron: () => process.versions.electron - // Nous pouvons exposer des variables en plus des fonctions -}); diff --git a/index.d.ts b/index.d.ts index 02820dd..3f55927 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,6 +1,7 @@ export { }; import type { RouteLocation as NRouteLocation } from "vue-router"; + import type { ipcRendererObject, versions } from "./electron/preload"; // * --- MODELS From JSON Schema @@ -125,10 +126,14 @@ declare global { type TypeRange = RangeModel; type TypeProduct = ProductModel; + type ToastTypeEnum = ToastType; + // Nuxt type RouteLocation = NRouteLocation; interface Window { + versions: typeof versions + ipcRendererObject: typeof ipcRendererObject } } diff --git a/nuxt.config.ts b/nuxt.config.ts index ef5e64f..b4ad78b 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -5,6 +5,9 @@ export default defineNuxtConfig({ experimental: { polyfillVueUseHead: false }, + components: [ + { path: "~/components", pathPrefix: false } + ], modules: ["nuxt-electron"], electron: { build: [ @@ -13,7 +16,7 @@ export default defineNuxtConfig({ entry: "electron/main.ts" }, { - entry: "electron/preload/versions.ts", + entry: "electron/preload.ts", onstart(args) { // Notify the Renderer-Process to reload the page when the Preload-Scripts build is complete, // instead of restarting the entire Electron App. @@ -25,6 +28,7 @@ export default defineNuxtConfig({ }, devtools: { enabled: false }, app: { + pageTransition: { name: "page", mode: "out-in" }, // Global page headers: https://go.nuxtjs.dev/config-head head: { title: "showroom", @@ -38,6 +42,9 @@ export default defineNuxtConfig({ { name: "format-detection", content: "telephone=no" }, ], link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" }], + bodyAttrs: { + "data-bs-theme": "light" + } }, }, diff --git a/package-lock.json b/package-lock.json index c07d15a..c6bc472 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "nuxt-app", + "name": "app-kiosk", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "nuxt-app", + "name": "app-kiosk", "version": "1.0.0", "hasInstallScript": true, "devDependencies": { @@ -33,6 +33,7 @@ "eslint-config-prettier": "latest", "eslint-plugin-nuxt": "latest", "eslint-plugin-vue": "^9.17.0", + "flag-icons": "^7.1.0", "husky": "^8.0.3", "jest": "^29.7.0", "konami-code-js": "^0.8.1", @@ -41,6 +42,8 @@ "nuxt-electron": "^0.7.0", "postcss": "^8.4.31", "postcss-html": "^1.5.0", + "postcss-import": "^16.0.0", + "postcss-url": "^10.1.3", "prettier": "latest", "sass": "latest", "stylelint": "^15.11.0", @@ -7377,6 +7380,12 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true }, + "node_modules/cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==", + "dev": true + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -9321,6 +9330,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flag-icons": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-7.1.0.tgz", + "integrity": "sha512-AH4v++19bpC5P3Wh767top4wylJYJCWkFnvNiDqGHDxqSqdMZ49jpLXp8PWBHTTXaNQ+/A+QPrOwyiIGaiIhmw==", + "dev": true + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -14474,6 +14489,23 @@ "integrity": "sha512-Olnt+V7xYdvGze9YTbGFZIfQXuGV4R3nQwwl8BrtgaPE/wq8UFpUHWuTNc05saowhSr1ZO6tx+V6RjE9D5YQog==", "dev": true }, + "node_modules/postcss-import": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.0.0.tgz", + "integrity": "sha512-e77lhVvrD1I2y7dYmBv0k9ULTdArgEYZt97T4w6sFIU5uxIHvDFQlKgUUyY7v7Barj0Yf/zm5A4OquZN7jKm5Q==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, "node_modules/postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", @@ -14859,6 +14891,82 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-url": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-10.1.3.tgz", + "integrity": "sha512-FUzyxfI5l2tKmXdYc6VTu3TWZsInayEKPbiyW+P6vmmIrrb4I6CGX0BFoewgYHLK+oIL5FECEK02REYRpBvUCw==", + "dev": true, + "dependencies": { + "make-dir": "~3.1.0", + "mime": "~2.5.2", + "minimatch": "~3.0.4", + "xxhashjs": "~0.2.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-url/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/postcss-url/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postcss-url/node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/postcss-url/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/postcss-url/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -15130,6 +15238,24 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/read-config-file": { "version": "6.3.2", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", @@ -19320,6 +19446,15 @@ "node": ">=8.0" } }, + "node_modules/xxhashjs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", + "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "dev": true, + "dependencies": { + "cuint": "^0.2.2" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 0349f29..6555752 100755 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "private": true, "type": "commonjs", "main": "dist-electron/main.js", + "licence": "MIT", "scripts": { "build": "nuxi build --prerender && electron-builder", "dev": "nuxt dev", @@ -47,6 +48,7 @@ "eslint-config-prettier": "latest", "eslint-plugin-nuxt": "latest", "eslint-plugin-vue": "^9.17.0", + "flag-icons": "^7.1.0", "husky": "^8.0.3", "jest": "^29.7.0", "konami-code-js": "^0.8.1", @@ -55,6 +57,8 @@ "nuxt-electron": "^0.7.0", "postcss": "^8.4.31", "postcss-html": "^1.5.0", + "postcss-import": "^16.0.0", + "postcss-url": "^10.1.3", "prettier": "latest", "sass": "latest", "stylelint": "^15.11.0", diff --git a/pages/brands/[slug].vue b/pages/brands/[slug].vue new file mode 100644 index 0000000..16f7219 --- /dev/null +++ b/pages/brands/[slug].vue @@ -0,0 +1,52 @@ + + + diff --git a/pages/index.vue b/pages/index.vue index 6069a64..00e655c 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -4,7 +4,7 @@
-
- {{ brand.name }} +
+ + {{ brand.name }} +
- +
+ +
@@ -28,20 +32,25 @@ import log from "electron-log"; import { defineComponent } from "vue"; import type AppState from "~/classes/AppState"; +import bsTooltips from "~/vueModules/bsTooltips"; export default defineComponent({ name: "HomePage", data() : { - dataJson: AppState; + appState: AppState; } { return { - dataJson: useState("dataJson").value as AppState + appState: useState("dataJson").value as AppState }; }, mounted() { log.info("index page"); + bsTooltips.methods.initTooltips(); }, methods: { + gotoBrand(brand: TypeBrand) { + navigateTo(`/brands/${brand.slug}`); + } }, }); diff --git a/pages/ranges/[slug].vue b/pages/ranges/[slug].vue index 5a9a80a..cc284c0 100644 --- a/pages/ranges/[slug].vue +++ b/pages/ranges/[slug].vue @@ -1,6 +1,6 @@ @@ -15,14 +15,14 @@ export default defineComponent({ props: { }, data(): { - router: RouteLocationNormalizedLoaded + route: RouteLocationNormalizedLoaded } { return { - router: useRoute() + route: useRoute() }; }, mounted() { - const route = useRoute(); + const route: RouteLocationNormalizedLoaded = useRoute(); log.info(route); }, methods: { diff --git a/plugins/boostrap.ts b/plugins/boostrap.ts index 316f84a..c27784e 100644 --- a/plugins/boostrap.ts +++ b/plugins/boostrap.ts @@ -1,10 +1,29 @@ -import { Modal } from "bootstrap"; -import "bootstrap/dist/js/bootstrap"; +import { Alert, Button, Carousel, Collapse, Dropdown, Modal, Offcanvas, Popover, ScrollSpy, Tab, Toast, Tooltip } from "bootstrap"; export default defineNuxtPlugin(() => { + let tooltips: Array = []; + + // Disable tooltips on reboot. + tooltips.forEach((tooltip) => { + tooltip.disable(); + tooltip.hide(); + }); + tooltips = [].slice + .call(document.querySelectorAll("[data-bs-toggle=\"tooltip\"]")) + .map(function (tooltipTriggerEl) { + return new window.bootstrap.Tooltip(tooltipTriggerEl, { + customClass: "large-tooltip", + delay: { + show: 1250, + hide: 0, + }, + }); + }); return { provide: { - Modal + Alert, Button, Carousel, Collapse, Dropdown, + Modal, Offcanvas, Popover, ScrollSpy, Tab, + Toast, Tooltip } }; }); diff --git a/static/scss/_variables.scss b/static/scss/_variables.scss index 0608781..e29feb5 100644 --- a/static/scss/_variables.scss +++ b/static/scss/_variables.scss @@ -14,6 +14,6 @@ $font-default-family-base: system-ui, -apple-system, "Segoe UI", "Roboto", "Helvetica Neue", "Arial", "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; // - The default site font family. // Display tres haute résolution, augmentation de font size 1rem => 1.8rem -$font-default-size-base: 2.2rem; // Assumes the browser default, typically `16px` +$font-default-size-base: 1.4rem; // Assumes the browser default, typically `16px` $line-default-height-base: 1.5; diff --git a/static/scss/bootstrap.scss b/static/scss/bootstrap.scss index c603d45..0fe2e19 100644 --- a/static/scss/bootstrap.scss +++ b/static/scss/bootstrap.scss @@ -36,6 +36,7 @@ @import "bootstrap/scss/close"; @import "bootstrap/scss/modal"; @import "bootstrap/scss/accordion"; +@import "bootstrap/scss/toasts"; // 6. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss` @import "bootstrap/scss/utilities/api"; diff --git a/static/scss/layout.scss b/static/scss/layout.scss index ac4f5b2..454141f 100644 --- a/static/scss/layout.scss +++ b/static/scss/layout.scss @@ -6,4 +6,20 @@ html { body { min-height: 100vh; background-color: unset; + + .app-component { + display: flex; + height: 100vh; + flex-direction: column; + + > .container-fluid { + flex-grow: 1; + padding-top: 1rem; + padding-bottom: 1rem; + } + } + + > footer { + box-shadow: 0 -0.125rem 0.25rem rgb(0 0 0 / 7.5%) !important; + } } diff --git a/vueModules/bsTooltips.ts b/vueModules/bsTooltips.ts new file mode 100644 index 0000000..a2fd14b --- /dev/null +++ b/vueModules/bsTooltips.ts @@ -0,0 +1,26 @@ +import { Tooltip } from "bootstrap"; + +let tooltips: Array = []; + +export default { + methods: { + initTooltips() { + // Disable tooltips on reboot. + tooltips.forEach((tooltip) => { + tooltip.disable(); + tooltip.hide(); + }); + tooltips = [].slice + .call(document.querySelectorAll("[data-bs-toggle=\"tooltip\"]")) + .map(function (tooltipTriggerEl) { + return new Tooltip(tooltipTriggerEl, { + customClass: "large-tooltip", + delay: { + show: 1250, + hide: 0, + }, + }); + }); + }, + }, +};