Some Layout + successfull pass data between main and render + Toast system
This commit is contained in:
parent
27c13c5fa8
commit
c3f639315d
25 changed files with 646 additions and 70 deletions
61
app.vue
61
app.vue
|
|
@ -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>
|
||||
|
|
|
|||
BIN
assets/Logo-LDC.jpg
Normal 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
BIN
assets/layout/ldc-intl.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
|
|
@ -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
42
classes/Toast.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
components/ToastContainer.vue
Normal file
74
components/ToastContainer.vue
Normal 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>
|
||||
54
components/layout/AppFooter.vue
Normal file
54
components/layout/AppFooter.vue
Normal 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> -
|
||||
<span>{{ appInfo.name }}</span> -
|
||||
<span>{{ appInfo.version }}</span>
|
||||
</span>
|
||||
<span> - </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>
|
||||
81
components/layout/AppHeader.vue
Normal file
81
components/layout/AppHeader.vue
Normal 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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
22
electron/preload.ts
Normal 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);
|
||||
});
|
||||
|
|
@ -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
5
index.d.ts
vendored
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
139
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
52
pages/brands/[slug].vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
1
static/scss/bootstrap.scss
vendored
1
static/scss/bootstrap.scss
vendored
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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
26
vueModules/bsTooltips.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue