Ranges display with dynamic color and animations
This commit is contained in:
parent
78cb1b609a
commit
ed37de4cfa
12 changed files with 208 additions and 9 deletions
1
assets/scss/bootstrap.scss
vendored
1
assets/scss/bootstrap.scss
vendored
|
|
@ -37,6 +37,7 @@
|
|||
@import "bootstrap/scss/modal";
|
||||
@import "bootstrap/scss/accordion";
|
||||
@import "bootstrap/scss/toasts";
|
||||
@import "bootstrap/scss/badge";
|
||||
|
||||
// 6. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss`
|
||||
@import "bootstrap/scss/utilities/api";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
@import "./variables";
|
||||
@import "./bootstrap";
|
||||
@import "./sweetalert-custom";
|
||||
@import 'animate.css';
|
||||
|
||||
|
||||
// * App
|
||||
@import "./layout";
|
||||
|
|
|
|||
|
|
@ -4,9 +4,6 @@ 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;
|
||||
}
|
||||
|
|
@ -47,7 +44,7 @@ export default class AppState {
|
|||
}
|
||||
return brandItem;
|
||||
});
|
||||
return ranges;
|
||||
return ranges.sort((rangeA, rangeB) => rangeA.order - rangeB.order);
|
||||
}
|
||||
|
||||
public range(slug:string): TypeRange|undefined {
|
||||
|
|
|
|||
33
classes/ToolboxHelper.ts
Normal file
33
classes/ToolboxHelper.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
export default class ToolboxHelper {
|
||||
/**
|
||||
* Get the contrast for any color
|
||||
*
|
||||
* @param hexcolor An HTML Hex color
|
||||
* @returns
|
||||
*/
|
||||
public static getContrastYIQ(hexcolor: string): "#000000"|"#FFFFFF" {
|
||||
const r = parseInt(hexcolor.substring(1,3),16);
|
||||
const g = parseInt(hexcolor.substring(3,5),16);
|
||||
const b = parseInt(hexcolor.substring(5,7),16);
|
||||
const yiq = ((r*299)+(g*587)+(b*114))/1000;
|
||||
return (yiq >= 128) ? "#000000" : "#FFFFFF";
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HTML Hex color to RGBA
|
||||
*
|
||||
* @param hexcolor An HTML Hex color
|
||||
* @param alpha The transparency to apply
|
||||
* @returns
|
||||
*/
|
||||
public static toRGBA(hexcolor: string, alpha: number = 1): string {
|
||||
const r = parseInt(hexcolor.substring(1,3),16);
|
||||
const g = parseInt(hexcolor.substring(3,5),16);
|
||||
const b = parseInt(hexcolor.substring(5,7),16);
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||
}
|
||||
|
||||
public static randomInteger(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
}
|
||||
22
index.d.ts
vendored
22
index.d.ts
vendored
|
|
@ -29,9 +29,21 @@ type SettingsModel = {
|
|||
updated_at: string;
|
||||
};
|
||||
|
||||
/** A font from Showroom Bo */
|
||||
type FontModel = {
|
||||
/** The unique identifier for a font */
|
||||
id: number;
|
||||
/** Nom de la marque */
|
||||
font_name: string;
|
||||
/** Chemin vers fichier de la font, chemin tel que présent dans le zip*/
|
||||
font: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
/** A brand from Showroom Bo */
|
||||
type BrandModel = {
|
||||
/** The unique identifier for a product */
|
||||
/** The unique identifier for a brand */
|
||||
id: number;
|
||||
/** Nom de la marque */
|
||||
name: string;
|
||||
|
|
@ -56,8 +68,16 @@ type RangeModel = {
|
|||
slug: string;
|
||||
/** Logo de la gamme, chemin tel que présent dans le zip */
|
||||
picture: string;
|
||||
/** Couleur de la gamme */
|
||||
color: string;
|
||||
/** Largeur de la gamme 1 colonne ou 2 colonnes (pleine largeur)e */
|
||||
width: number;
|
||||
/** Ordre d'affichage de la gamme */
|
||||
order: number;
|
||||
/** Clé étrangère de la marque */
|
||||
brand_id: number;
|
||||
/** Clé étrangère de la police */
|
||||
font_id: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
products: Array<ProductModel>;
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ export default defineNuxtConfig({
|
|||
// Global CSS: https://go.nuxtjs.dev/config-css
|
||||
css: ["~/assets/scss/style.scss", "@fortawesome/fontawesome-svg-core/styles.css"],
|
||||
plugins: [
|
||||
{ src: "~/plugins/toolboxhelper.ts", mode: "client" },
|
||||
{ src: "~/plugins/boostrap.ts", mode: "client" },
|
||||
{ src: "~/plugins/fontawesome.ts", mode: "client" },
|
||||
{ src: "~/plugins/localStorage.ts", mode: "client" },
|
||||
|
|
|
|||
7
package-lock.json
generated
7
package-lock.json
generated
|
|
@ -25,6 +25,7 @@
|
|||
"adm-zip": "^0.5.10",
|
||||
"ajv": "^8.12.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"animate.css": "^4.1.1",
|
||||
"bootstrap": "^5.3.2",
|
||||
"dompurify": "^3.0.6",
|
||||
"electron": "^28.1.1",
|
||||
|
|
@ -5314,6 +5315,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/animate.css": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz",
|
||||
"integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ansi-colors": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
"adm-zip": "^0.5.10",
|
||||
"ajv": "^8.12.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"animate.css": "^4.1.1",
|
||||
"bootstrap": "^5.3.2",
|
||||
"dompurify": "^3.0.6",
|
||||
"electron": "^28.1.1",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
<template>
|
||||
<div class="page-brands-show">
|
||||
<div class="container">
|
||||
<div v-if="brand.ranges" class="row">
|
||||
<div v-for="(range, rangeIndex) of brand.ranges" :key="rangeIndex" class="col-12 col-md-6">
|
||||
<div v-if="brand.ranges" class="row justify-content-center">
|
||||
<div
|
||||
v-for="(range, rangeIndex) of brand.ranges"
|
||||
:key="rangeIndex"
|
||||
:class="{
|
||||
'col-12 text-center my-5 py-5': true,
|
||||
'col-md-6': range.width === 1
|
||||
}"
|
||||
>
|
||||
<button
|
||||
:class="{
|
||||
'd-inline-block btn position-relative': true,
|
||||
|
|
@ -14,7 +21,22 @@
|
|||
<div>
|
||||
<img class="img-fluid" :src="`/data/${range.picture}`" :alt="`Gamme ${range.name}`">
|
||||
</div>
|
||||
<span class="fs-2 fw-bold">{{ range.name }}</span>
|
||||
<span
|
||||
:class="{
|
||||
...{
|
||||
'range-title fs-2 fw-bold mt-5 d-inline-block px-5 py-3 rounded text-capitalize animate__animated': true,
|
||||
'text-dark': getContrastYIQ(range.color) === '#000000',
|
||||
'text-light': getContrastYIQ(range.color) === '#FFFFFF',
|
||||
},
|
||||
...titleAnimations(rangeIndex)
|
||||
}"
|
||||
:style="{
|
||||
'background-color': range.color,
|
||||
'text-shadow': `0 0 0.2rem ${toRGBA(getContrastYIQ(range.color), 1)}`,
|
||||
}"
|
||||
>
|
||||
{{ range.name }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -32,6 +54,7 @@ import { defineComponent } from "vue";
|
|||
import { useRoute, type RouteLocationNormalizedLoaded } from "vue-router";
|
||||
import type AppState from "~/classes/AppState";
|
||||
import Toast, { ToastTypeEnum } from "~/classes/Toast";
|
||||
import ToolboxHelper from "~/classes/ToolboxHelper";
|
||||
import { useAppStore } from "~/stores/useAppStore";
|
||||
import bsTooltips from "~/vueModules/bsTooltips";
|
||||
|
||||
|
|
@ -41,11 +64,17 @@ export default defineComponent({
|
|||
appState: AppState;
|
||||
route: RouteLocationNormalizedLoaded;
|
||||
brand: TypeBrand;
|
||||
animateTimeout: NodeJS.Timeout|undefined;
|
||||
animateIndex: number|null;
|
||||
animations: Array<string>;
|
||||
} {
|
||||
return {
|
||||
appState: {} as AppState,
|
||||
route: useRoute(),
|
||||
brand: {} as TypeBrand
|
||||
brand: {} as TypeBrand,
|
||||
animateTimeout: undefined,
|
||||
animateIndex: null,
|
||||
animations: ["heartBeat", "tada", "wooble", "rubberBand"]
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -58,8 +87,42 @@ export default defineComponent({
|
|||
return ;
|
||||
}
|
||||
this.brand = brand;
|
||||
|
||||
// * ANIMATIONS
|
||||
clearTimeout(this.animateTimeout);
|
||||
this.$nextTick(() => {
|
||||
[...document.getElementsByClassName("range-title")].forEach(el => {
|
||||
el.classList.add("animate__bounce");
|
||||
});
|
||||
this.animateTimeout = setTimeout(() => {
|
||||
[...document.getElementsByClassName("range-title")].forEach(el => {
|
||||
el.classList.remove("animate__bounce");
|
||||
});
|
||||
this.setRangeToAnimate();
|
||||
}, ToolboxHelper.randomInteger(4000, 8000));
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
clearTimeout(this.animateTimeout);
|
||||
},
|
||||
methods: {
|
||||
titleAnimations(rangeIndex: number) {
|
||||
const animation = this.animations.at(ToolboxHelper.randomInteger(0, this.animations.length -1));
|
||||
const out: Map<string, boolean> = new Map<string, boolean>();
|
||||
out.set(`animate__${animation}`, this.animateIndex === rangeIndex);
|
||||
return Object.fromEntries(out.entries());
|
||||
},
|
||||
setRangeToAnimate(): void {
|
||||
clearTimeout(this.animateTimeout);
|
||||
this.animateIndex = Math.ceil(Math.random() * this.brand.ranges.length) -1;
|
||||
this.animateTimeout = setTimeout(this.setRangeToAnimate, ToolboxHelper.randomInteger(2000, 6000));
|
||||
},
|
||||
getContrastYIQ(hexcolor: string): string {
|
||||
return ToolboxHelper.getContrastYIQ(hexcolor);
|
||||
},
|
||||
toRGBA(hexcolor: string, alpha: number = 1): string {
|
||||
return ToolboxHelper.toRGBA(hexcolor, alpha);
|
||||
},
|
||||
gotoRange(range: TypeRange) {
|
||||
useAppStore().setActiveRange(range);
|
||||
navigateTo(`/ranges/${range.slug}`);
|
||||
|
|
|
|||
9
plugins/toolboxhelper.ts
Normal file
9
plugins/toolboxhelper.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import ToolboxHelper from "./../classes/ToolboxHelper";
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
return {
|
||||
provide: {
|
||||
ToolboxHelper
|
||||
}
|
||||
};
|
||||
});
|
||||
37
schemas/font.schema.json
Normal file
37
schemas/font.schema.json
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"$id": "schemas/font.schema.json",
|
||||
"title": "Font",
|
||||
"description": "A font from Showroom Bo",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The unique identifier for a font",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"font_name": {
|
||||
"description": "Nom de la font",
|
||||
"type": "string"
|
||||
},
|
||||
"font": {
|
||||
"description": "Chemin vers fichier de la font, chemin tel que présent dans le zip",
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"font_name",
|
||||
"font",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"ranges"
|
||||
]
|
||||
}
|
||||
|
|
@ -21,11 +21,31 @@
|
|||
"description": "Logo de la gamme, chemin tel que présent dans le zip",
|
||||
"type": "string"
|
||||
},
|
||||
"color": {
|
||||
"description": "Couleur de la gamme",
|
||||
"$ref": "#/$defs/hexcolor"
|
||||
},
|
||||
"width": {
|
||||
"description": "Largeur de la gamme 1 colonne ou 2 colonnes (pleine largeur)",
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 2
|
||||
},
|
||||
"order": {
|
||||
"description": "Ordre d'affichage de la gamme",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"brand_id": {
|
||||
"description": "Clé étrangère de la marque",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"font_id": {
|
||||
"description": "Clé étrangère de la police",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
|
|
@ -41,12 +61,20 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"hexcolor": {
|
||||
"description": "HEX color",
|
||||
"pattern": "^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"slug",
|
||||
"picture",
|
||||
"brand_id",
|
||||
"font_id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"products"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue