2026-04-10
This commit is contained in:
parent
00f87fedcd
commit
7d3251747b
4 changed files with 164 additions and 140 deletions
|
|
@ -0,0 +1,132 @@
|
|||
import { Array as FxArray, Effect, pipe, Option, ServiceMap, Layer, ManagedRuntime, Console, HashMap } from "effect";
|
||||
import { getAllSelectorFromDocument, getFirstSelectorFromDocument } from "../scripts-effect/lib/dom.ts";
|
||||
import { NonEmptyReadonlyArray } from "effect/Array";
|
||||
import { NoSuchElementError } from "effect/Cause";
|
||||
import {
|
||||
DOM_BOUTON_AJOUT_PANIER,
|
||||
DOM_BOUTONS_ACCORDEON,
|
||||
DOM_CONTENUS_ACCORDEON,
|
||||
DOM_PRIX_PRODUIT,
|
||||
ATTRIBUT_ARIA_CONTROLS,
|
||||
ATTRIBUT_ARIA_EXPANDED,
|
||||
ATTRIBUT_HIDDEN,
|
||||
} from "./constantes/dom.ts";
|
||||
import { WCStoreCartAddItemArgsItems } from "./lib/types/api/cart-add-item";
|
||||
|
||||
/** Représente un ensemble bouton-contenu d'une Section dans la description du Produit. */
|
||||
type DetailEnsemble = {
|
||||
button: HTMLButtonElement;
|
||||
content: HTMLDivElement;
|
||||
};
|
||||
|
||||
class ProductPageElements extends ServiceMap.Service<
|
||||
ProductPageElements,
|
||||
{
|
||||
AddProductButton: HTMLButtonElement;
|
||||
DetailsButtons: NonEmptyReadonlyArray<HTMLButtonElement>;
|
||||
DetailsContents: NonEmptyReadonlyArray<HTMLDivElement>;
|
||||
Details: HashMap.HashMap<string, DetailEnsemble>;
|
||||
ProductPrice: HTMLParagraphElement;
|
||||
ProductRawJson: HTMLScriptElement;
|
||||
VariationChoiceForm: HTMLFormElement;
|
||||
VariationSelectors: ReadonlyArray<HTMLSelectElement>;
|
||||
}
|
||||
>()("haikuatelier.fr/Produit/ProductPageElements") {
|
||||
static readonly layer = Layer.effect(
|
||||
ProductPageElements,
|
||||
Effect.gen(function* () {
|
||||
const AddProductButton = yield* getFirstSelectorFromDocument<HTMLButtonElement>(DOM_BOUTON_AJOUT_PANIER);
|
||||
const DetailsButtons = yield* getAllSelectorFromDocument<HTMLButtonElement>(DOM_BOUTONS_ACCORDEON);
|
||||
const DetailsContents = yield* getAllSelectorFromDocument<HTMLDivElement>(DOM_CONTENUS_ACCORDEON);
|
||||
const ProductPrice = yield* getFirstSelectorFromDocument<HTMLParagraphElement>(DOM_PRIX_PRODUIT);
|
||||
const ProductRawJson = yield* getFirstSelectorFromDocument<HTMLScriptElement>("#product-json");
|
||||
const VariationChoiceForm = yield* getFirstSelectorFromDocument<HTMLFormElement>("#variation-choice");
|
||||
const VariationSelectors = yield* pipe(
|
||||
getAllSelectorFromDocument<HTMLSelectElement>(".selecteur-produit select"),
|
||||
Option.orElseSome(() => FxArray.empty<HTMLSelectElement>()),
|
||||
);
|
||||
|
||||
const Details = yield* pipe(
|
||||
DetailsButtons,
|
||||
FxArray.map(
|
||||
(button: HTMLButtonElement, index: number): Effect.Effect<[string, DetailEnsemble], NoSuchElementError> =>
|
||||
Effect.gen(function* () {
|
||||
const contentId = yield* Option.fromNullishOr(button.getAttribute(ATTRIBUT_ARIA_CONTROLS));
|
||||
const content = yield* FxArray.get(DetailsContents, index);
|
||||
|
||||
return [contentId, { button, content } satisfies DetailEnsemble];
|
||||
}),
|
||||
),
|
||||
Effect.all,
|
||||
Effect.map(HashMap.fromIterable<string, DetailEnsemble>),
|
||||
);
|
||||
|
||||
return ProductPageElements.of({
|
||||
AddProductButton,
|
||||
DetailsButtons,
|
||||
DetailsContents,
|
||||
Details,
|
||||
ProductPrice,
|
||||
ProductRawJson,
|
||||
VariationChoiceForm,
|
||||
VariationSelectors,
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
class ProductPageDOM extends ServiceMap.Service<
|
||||
ProductPageDOM,
|
||||
{
|
||||
/**
|
||||
* Replie toutes les sections de la description du Produit.
|
||||
*/
|
||||
toggleAllDetails: () => Effect.Effect<void>;
|
||||
/**
|
||||
* Récupère les Attributs du Produit depuis les Elements au sein du DOM.
|
||||
*/
|
||||
getProductAttributesFromDOM: () => Effect.Effect<ReadonlyArray<WCStoreCartAddItemArgsItems>>;
|
||||
}
|
||||
>()("haikuatelier.fr/Produit/ProductPageDOM") {
|
||||
static readonly layer = Layer.effect(
|
||||
ProductPageDOM,
|
||||
Effect.gen(function* () {
|
||||
const { Details, VariationSelectors } = yield* ProductPageElements;
|
||||
|
||||
const toggleAllDetails: () => Effect.Effect<void> = () =>
|
||||
Effect.sync((): void => {
|
||||
pipe(
|
||||
// Récupère les Sections sous forme d'Ensembles.
|
||||
[...HashMap.values(Details)],
|
||||
FxArray.forEach((detail: DetailEnsemble) => {
|
||||
detail.button.toggleAttribute(ATTRIBUT_ARIA_EXPANDED, false);
|
||||
detail.content.toggleAttribute(ATTRIBUT_HIDDEN, true);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
const getProductAttributesFromDOM: () => Effect.Effect<ReadonlyArray<WCStoreCartAddItemArgsItems>> = () =>
|
||||
Effect.sync(() =>
|
||||
FxArray.map(VariationSelectors, (select: HTMLSelectElement) => ({
|
||||
attribute: select.id,
|
||||
value: select.value,
|
||||
})),
|
||||
);
|
||||
|
||||
return ProductPageDOM.of({
|
||||
getProductAttributesFromDOM,
|
||||
toggleAllDetails,
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const ProductPageRuntime = ManagedRuntime.make(
|
||||
pipe(
|
||||
ProductPageDOM.layer,
|
||||
Layer.provide(ProductPageElements.layer),
|
||||
Layer.tapError((error) => Console.error("ManagedRuntime", "Impossible de créer le Layer :", error.name)),
|
||||
),
|
||||
);
|
||||
|
||||
export { type DetailEnsemble, ProductPageElements, ProductPageDOM, ProductPageRuntime };
|
||||
|
|
@ -3,25 +3,14 @@
|
|||
import { pipe } from "@mobily/ts-belt";
|
||||
import { get as dictGet } from "@mobily/ts-belt/Dict";
|
||||
import { tap as optionTap } from "@mobily/ts-belt/Option";
|
||||
import {
|
||||
Array as FxArray,
|
||||
Effect,
|
||||
pipe as epipe,
|
||||
Option,
|
||||
Stream,
|
||||
ServiceMap,
|
||||
Layer,
|
||||
ManagedRuntime,
|
||||
Console,
|
||||
HashMap,
|
||||
} from "effect";
|
||||
import { Array as FxArray, Effect, pipe as epipe, Option, Stream, Console, HashMap } from "effect";
|
||||
import { EitherAsync } from "purify-ts";
|
||||
import { match, P } from "ts-pattern";
|
||||
import { ValiError } from "valibot";
|
||||
import type { AnySchema } from "valibot";
|
||||
|
||||
import type { WCStoreCart } from "./lib/types/api/cart.ts";
|
||||
import type { WCStoreCartAddItemArgs, WCStoreCartAddItemArgsItems } from "./lib/types/api/cart-add-item.ts";
|
||||
import type { WCStoreCartAddItemArgs } from "./lib/types/api/cart-add-item.ts";
|
||||
import type { FetchErrors } from "./lib/types/reseau.ts";
|
||||
|
||||
import { ROUTE_API_AJOUTE_ARTICLE_PANIER } from "./constantes/api.ts";
|
||||
|
|
@ -45,14 +34,7 @@ import { newPartialResponse, postBackend, safeFetch } from "./lib/reseau.ts";
|
|||
import { WCStoreCartAddItemArgsSchema } from "./lib/schemas/api/cart-add-item.ts";
|
||||
import { WCStoreCartSchema } from "./lib/schemas/api/cart.ts";
|
||||
import { safeSchemaParse } from "./lib/validation";
|
||||
import { getAllSelectorFromDocument, getFirstSelectorFromDocument } from "../scripts-effect/lib/dom.ts";
|
||||
import { NonEmptyReadonlyArray } from "effect/Array";
|
||||
import { NoSuchElementError } from "effect/Cause";
|
||||
|
||||
type DetailEnsemble = {
|
||||
button: HTMLButtonElement;
|
||||
content: HTMLDivElement;
|
||||
};
|
||||
import { ProductPageElements, ProductPageRuntime } from "./scripts-page-produit-service.ts";
|
||||
|
||||
/** États utiles pour les scripts de la page. */
|
||||
type EtatsPage = {
|
||||
|
|
@ -65,69 +47,6 @@ type EtatsPage = {
|
|||
// @ts-expect-error -- États injectés par le modèle PHP
|
||||
const ETATS_PAGE: EtatsPage = _etats;
|
||||
|
||||
class ProductPageElements extends ServiceMap.Service<
|
||||
ProductPageElements,
|
||||
{
|
||||
AddProductButton: HTMLButtonElement;
|
||||
DetailsButtons: NonEmptyReadonlyArray<HTMLButtonElement>;
|
||||
DetailsContents: NonEmptyReadonlyArray<HTMLDivElement>;
|
||||
Details: HashMap.HashMap<string, DetailEnsemble>;
|
||||
ProductPrice: HTMLParagraphElement;
|
||||
ProductRawJson: HTMLScriptElement;
|
||||
VariationChoiceForm: HTMLFormElement;
|
||||
VariationSelectors: ReadonlyArray<HTMLSelectElement>;
|
||||
}
|
||||
>()("haikuatelier.fr/Produit/ProductPageElements") {
|
||||
static readonly layer = Layer.effect(
|
||||
ProductPageElements,
|
||||
Effect.gen(function* () {
|
||||
const AddProductButton = yield* getFirstSelectorFromDocument<HTMLButtonElement>(DOM_BOUTON_AJOUT_PANIER);
|
||||
const DetailsButtons = yield* getAllSelectorFromDocument<HTMLButtonElement>(DOM_BOUTONS_ACCORDEON);
|
||||
const DetailsContents = yield* getAllSelectorFromDocument<HTMLDivElement>(DOM_CONTENUS_ACCORDEON);
|
||||
const ProductPrice = yield* getFirstSelectorFromDocument<HTMLParagraphElement>(DOM_PRIX_PRODUIT);
|
||||
const ProductRawJson = yield* getFirstSelectorFromDocument<HTMLScriptElement>("#product-json");
|
||||
const VariationChoiceForm = yield* getFirstSelectorFromDocument<HTMLFormElement>("#variation-choice");
|
||||
const VariationSelectors = yield* pipe(
|
||||
getAllSelectorFromDocument<HTMLSelectElement>(".selecteur-produit select"),
|
||||
Option.orElseSome(() => FxArray.empty<HTMLSelectElement>()),
|
||||
);
|
||||
|
||||
const Details = yield* pipe(
|
||||
DetailsButtons,
|
||||
FxArray.map(
|
||||
(button: HTMLButtonElement, index: number): Effect.Effect<[string, DetailEnsemble], NoSuchElementError> =>
|
||||
Effect.gen(function* () {
|
||||
const contentId = yield* Option.fromNullishOr(button.getAttribute(ATTRIBUT_ARIA_CONTROLS));
|
||||
const content = yield* FxArray.get(DetailsContents, index);
|
||||
|
||||
return [contentId, { button, content } satisfies DetailEnsemble];
|
||||
}),
|
||||
),
|
||||
Effect.all,
|
||||
Effect.map(HashMap.fromIterable<string, DetailEnsemble>),
|
||||
);
|
||||
|
||||
return {
|
||||
AddProductButton,
|
||||
DetailsButtons,
|
||||
DetailsContents,
|
||||
Details,
|
||||
ProductPrice,
|
||||
ProductRawJson,
|
||||
VariationChoiceForm,
|
||||
VariationSelectors,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const ProductPageRuntime = ManagedRuntime.make(
|
||||
pipe(
|
||||
ProductPageElements.layer,
|
||||
Layer.tapError((error) => Console.error("ManagedRuntime", "Impossible de créer le Layer :", error.name)),
|
||||
),
|
||||
);
|
||||
|
||||
// Éléments d'intérêt
|
||||
const E = {
|
||||
BOUTON_AJOUT_PANIER: mustGetEleInDocument<HTMLButtonElement>(DOM_BOUTON_AJOUT_PANIER),
|
||||
|
|
@ -139,35 +58,6 @@ const E = {
|
|||
VARIATION_CHOICE_FORM: mustGetEleInDocument<HTMLFormElement>("#variation-choice"),
|
||||
};
|
||||
|
||||
const toggleAllDetails = Effect.fn("toggleAllDetails")(function* () {
|
||||
const PageElements = yield* ProductPageElements;
|
||||
// Récupère les Ensembles sous forme de tableau.
|
||||
const details = [...HashMap.values(PageElements.Details)];
|
||||
|
||||
FxArray.forEach(details, (detail: DetailEnsemble) => {
|
||||
detail.button.toggleAttribute(ATTRIBUT_ARIA_EXPANDED, false);
|
||||
detail.content.toggleAttribute(ATTRIBUT_HIDDEN, true);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Utiliser Effect.
|
||||
const getAttributesFromDom = (): ReadonlyArray<WCStoreCartAddItemArgsItems> => {
|
||||
const selectElements = epipe(
|
||||
document.querySelectorAll<HTMLSelectElement>(".selecteur-produit select"),
|
||||
Array.from<HTMLSelectElement>,
|
||||
);
|
||||
if (selectElements.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const attributes = selectElements.map((select: HTMLSelectElement) => ({
|
||||
attribute: select.id,
|
||||
value: select.value,
|
||||
}));
|
||||
|
||||
return attributes;
|
||||
};
|
||||
|
||||
const areArraysEqual = <T>(array1: Array<T>, array2: Array<T>): boolean => {
|
||||
if (array1 !== array2) {
|
||||
const a1 = JSON.stringify(array1.toSorted());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue