Some Layout + successfull pass data between main and render + Toast system

This commit is contained in:
Jérémy Munsch 2024-01-10 18:14:33 +01:00
parent 27c13c5fa8
commit c3f639315d
No known key found for this signature in database
GPG key ID: 18361AF3BFEC0FEC
25 changed files with 646 additions and 70 deletions

61
app.vue
View file

@ -1,12 +1,21 @@
<template>
<div class="app-component">
<NuxtLink to="/">
Home
</NuxtLink>
<NuxtLink to="/ranges">
Ranges
</NuxtLink>
<NuxtPage :page-key="(route: RouteLocation) => route.fullPath" />
<AppHeader />
<div class="container-fluid overflow-y-scroll">
<div class="row justify-content-center p-3">
<div class="col-12 col-md-10 col-lg-8 p-md-0 pt-3">
<NuxtLink to="/">
Home
</NuxtLink>
<NuxtLink to="/ranges">
Ranges
</NuxtLink>
<NuxtPage :page-key="(route: RouteLocation) => route.fullPath" />
</div>
</div>
</div>
<AppFooter />
<ToastContainer />
<!-- <RangesShow :data-json="data" @selected="itemClicked($event.item)" />
<ProductModal
v-if="selectedItem"
@ -14,8 +23,8 @@
:item="selectedItem"
:settings="data.modalSettings"
@saved="SaveContact"
/>
<AdministrativeModal :id="adminModalId" :contacts="contacts" /> -->
/> -->
<AdministrativeModal :id="adminModalId" :contacts="contacts" />
</div>
</template>
@ -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<Contact>;
@ -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<Contact>() : 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")
});
},
},
}
});
</script>
<style lang="scss">
.page-enter-active,
.page-leave-active {
transition: all 0.2s;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
filter: blur(1rem);
}
</style>

View file

BIN
assets/Logo-LDC.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
assets/layout/ldc-intl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View file

@ -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<TypeBrand> {
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<TypeRange> => brand.ranges);
}
// * --- END BRANDS
// * --- RANGES
public ranges(): Array<TypeRange> {
return useState("brands", (): Array<TypeRange> => []).value;
}
// * --- END RANGES
}

42
classes/Toast.ts Normal file
View file

@ -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);
}
}
}

View file

@ -0,0 +1,74 @@
<template>
<div class="position-fixed bottom-0 end-0 p-3">
<div
ref="toastMessage"
:class="{
'toast': true,
'd-flex': !title,
'text-bg-body': type === ToastType.default,
'text-bg-success': type === ToastType.success,
'text-bg-danger': type === ToastType.error,
'text-bg-info': type === ToastType.info,
'text-bg-warning': type === ToastType.warning,
}"
role="alert"
aria-live="assertive"
aria-atomic="true"
>
<div v-if="title" class="toast-header">
<strong class="me-auto">{{ title }}</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Fermer" />
</div>
<div class="toast-body">
<span>{{ message }}</span>
</div>
<button
v-if="!title"
type="button"
:class="{
'btn-close me-2 m-auto': true,
'btn-close-white': [
ToastType.success,
ToastType.error
].find((toastType) => toastType === type)
}"
data-bs-dismiss="toast"
aria-label="Fermer"
/>
</div>
</div>
</template>
<script lang="ts">
import { Toast } from "bootstrap";
import { defineComponent } from "vue";
import { ToastType } from "~/classes/Toast";
export default defineComponent({
name: "ToastContainer",
data(): {
ToastType: typeof ToastType;
type: ToastTypeEnum;
class: string;
title: string|null;
message: string;
} {
return {
ToastType,
type: ToastType.default,
class: "text-bg-body",
title: null,
message: "",
};
},
mounted() {
useState("toastMessage", () => (message:string, type:ToastTypeEnum, title: string|null = null) => {
const myToastEl = this.$refs.toastMessage as HTMLElement;
this.title = title;
this.type = type;
this.message = message;
Toast.getOrCreateInstance(myToastEl).show();
});
}
});
</script>

View file

@ -0,0 +1,54 @@
<template>
<footer id="footer" class="bg-body-tertiary px-0 text-center">
<div v-if="router.name === 'ranges'">
ranges
</div>
<NuxtLink to="/ranges/charcuterie">
charcuterie
</NuxtLink>
<div v-if="versions.isDebug" class="p-2 text-center">
<span class="text-reset">
<span>{{ appInfo.licence }}</span>&nbsp;-&nbsp;
<span>{{ appInfo.name }}</span>&nbsp;-&nbsp;
<span>{{ appInfo.version }}</span>
</span>
<span>&nbsp;-&nbsp;</span>
<span class="text-reset fw-bold">UWIL</span>
© {{ (new Date()).getFullYear() }}
</div>
</footer>
</template>
<script lang="ts">
import log from "electron-log";
import { defineComponent } from "vue";
import { useRoute, type RouteLocationNormalizedLoaded } from "vue-router";
import type { appInfo } from "~/electron/main";
export default defineComponent({
name: "AppFooter",
data(): {
versions: typeof window.versions;
appInfo: typeof appInfo;
router: RouteLocationNormalizedLoaded,
ranges: Array<TypeRange>
} {
return {
router: useRoute(),
versions: window.versions,
appInfo: {
version: "",
name: "",
licence: ""
},
ranges: useState("brands").value as Array<TypeRange>
};
},
mounted() {
window.ipcRendererObject.getAppInfo().then((appInfo) => {this.appInfo = appInfo;});
window.ipcRendererObject.getJsonData().then((appData) => { log.info(appData); });
},
methods: {
}
});
</script>

View file

@ -0,0 +1,81 @@
<template>
<nav id="top-navbar" class="navbar navbar-expand-lg bg-body-tertiary shadow-sm">
<div class="container align-items-start">
<div class="navbar-brand mx-auto">
<NuxtLink
to="/"
class="d-inline-block"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
:title="settings.title"
>
<img src="assets/layout/ldc-intl.png" alt="Logo LDC" class="img-fluid main-logo">
</NuxtLink>
</div>
<button
class="navbar-toggler"
data-bs-toggle="collapse"
data-bs-target="#navbar-menu"
type="button"
aria-controls="navbar-menu"
aria-expanded="false"
aria-label="Afficher / Masquer les langues"
>
<span class="navbar-toggler-icon" />
</button>
<div class="navbar-collapse collapse flex-grow-0">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<button class="btn btn-link" title="Francais" data-bs-toggle="tooltip">
<span class="fi fi-fr" />
</button>
<button class="btn btn-link" title="Allemand" data-bs-toggle="tooltip">
<span class="fi fi-de" />
</button>
<button class="btn btn-link" title="Suédois" data-bs-toggle="tooltip">
<span class="fi fi-gr" />
</button>
</li>
</ul>
</div>
</div>
</nav>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import bsTooltips from "~/vueModules/bsTooltips";
export default defineComponent({
name: "AppHeader",
data(): {
versions: typeof window.versions;
settings: TypeSettings;
} {
return {
versions: window.versions,
settings: {} as TypeSettings
};
},
mounted() {
window.ipcRendererObject.getJsonData().then((appData) => { this.settings = appData.settings; });
bsTooltips.methods.initTooltips();
},
});
</script>
<style lang="postcss">
@import "flag-icons/sass/flag-icons.scss";
</style>
<style lang="scss">
#top-navbar {
.main-logo {
max-height: 20rem;
height: 20vh;
}
}
</style>

View file

@ -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<BrowserWindow>}
*/
created () {
return new Promise(resolve => {
this.onCreated(() => resolve(this.browserWindow))
})
return this.browserWindow.loadURL(fullPath);
}
/**

View file

@ -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);

22
electron/preload.ts Normal file
View file

@ -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<typeof appInfo> => { return ipcRenderer.invoke("appInfo"); },
getJsonData: (): Promise<TypeApp> => { return ipcRenderer.invoke("channel"); }
};
process.once("loaded", () => {
// This runs in sandboxed mode.
contextBridge.exposeInMainWorld("versions", versions);
contextBridge.exposeInMainWorld("ipcRendererObject", ipcRendererObject);
});

View file

@ -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
});

5
index.d.ts vendored
View file

@ -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
}
}

View file

@ -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"
}
},
},

139
package-lock.json generated
View file

@ -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",

View file

@ -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",

52
pages/brands/[slug].vue Normal file
View file

@ -0,0 +1,52 @@
<template>
<div class="page-brands-index">
<h1>Brand {{ route.params.slug }}</h1>
<br>
<div class="container">
<div class="row">
<div v-for="(range, rangeIndex) of brand.ranges" :key="rangeIndex" class="col-12 col-md-6">
<div>
<img class="img-fluid" :src="`/assets/${range.picture}`" :alt="`Gamme ${range.name}`">
</div>
<span class="fs-2 fw-bold">{{ range.name }}</span>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import log from "electron-log";
import { defineComponent } from "vue";
import { useRoute, type RouteLocationNormalizedLoaded } from "vue-router";
import type AppState from "~/classes/AppState";
import Toast, { ToastTypeEnum } from "~/classes/Toast";
export default defineComponent({
name: "BrandShow",
data(): {
appState: AppState;
route: RouteLocationNormalizedLoaded;
brand: TypeBrand;
} {
return {
appState: useState("dataJson").value as AppState,
route: useRoute(),
brand: {} as TypeBrand
};
},
mounted() {
const route: RouteLocationNormalizedLoaded = useRoute();
log.info(route.params.slug);
const brand = this.appState.brand(String(route.params.slug as string));
Toast.toast("sdsd", ToastTypeEnum.default);
if(!brand) {
Toast.toast("sdsd", ToastTypeEnum.error, "Une erreur est survenue");
return ;
}
this.brand = brand;
},
methods: {
}
});
</script>

View file

@ -4,7 +4,7 @@
<br>
<div class="container">
<div
v-for="(brand, brandIndex) of dataJson.brands()"
v-for="(brand, brandIndex) of appState.brands()"
:key="brandIndex"
:class="{
'row': true,
@ -13,11 +13,15 @@
}"
:style="{ 'background-color': brand.color }"
>
<div class="col-6">
{{ brand.name }}
<div class="col-6 d-flex justify-content-center align-items-center text-center">
<span class="fs-2 fw-bold" role="button" :title="`Voir les gammes de la marque ${brand.name}`" data-bs-toggle="tooltip" @click="gotoBrand(brand)">
{{ brand.name }}
</span>
</div>
<div class="col-6">
<img class="img-fluid" :src="`/assets/${brand.picture}`" :alt="`Marque ${brand.name}`" :title="`Le logo de la marque ${brand.name}`">
<div class="d-inline-block" role="button" :title="`Voir les gammes de la marque ${brand.name}`" data-bs-toggle="tooltip" @click="gotoBrand(brand)">
<img class="img-fluid" :src="`/assets/${brand.picture}`" :alt="`Marque ${brand.name}`" :title="`Le logo de la marque ${brand.name}`">
</div>
</div>
</div>
</div>
@ -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}`);
}
},
});
</script>

View file

@ -1,6 +1,6 @@
<template>
<div class="page-ranges-index">
<h1>Range {{ router.params.slug }}</h1>
<h1>Range {{ route.params.slug }}</h1>
</div>
</template>
@ -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: {

View file

@ -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<bootstrap.Tooltip> = [];
// 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
}
};
});

View file

@ -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;

View file

@ -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";

View file

@ -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;
}
}

26
vueModules/bsTooltips.ts Normal file
View file

@ -0,0 +1,26 @@
import { Tooltip } from "bootstrap";
let tooltips: Array<Tooltip> = [];
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,
},
});
});
},
},
};