2026-04-01
This commit is contained in:
parent
ef19ba2b72
commit
5f332f4068
34 changed files with 9392 additions and 391 deletions
|
|
@ -8,15 +8,5 @@
|
||||||
"!vtsls",
|
"!vtsls",
|
||||||
"..."
|
"..."
|
||||||
],
|
],
|
||||||
"languages": {
|
"languages": {}
|
||||||
"PHP": {
|
|
||||||
"format_on_save": "on",
|
|
||||||
"formatter": {
|
|
||||||
"external": {
|
|
||||||
"command": "mago",
|
|
||||||
"arguments": ["format", "--stdin-input"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
cfg/oxlint.config.ts
Normal file
3
cfg/oxlint.config.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import config from "@gcch/configuration-oxlint";
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
|
@ -1,37 +1,25 @@
|
||||||
import { defineConfig, devices } from "@playwright/test";
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir: "./tests",
|
|
||||||
/* Run tests in files in parallel */
|
|
||||||
fullyParallel: true,
|
fullyParallel: true,
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
||||||
forbidOnly: !!process.env.CI,
|
|
||||||
/* Retry on CI only */
|
|
||||||
retries: process.env.CI ? 2 : 0,
|
|
||||||
/* Opt out of parallel tests on CI. */
|
|
||||||
workers: process.env.CI ? 1 : undefined,
|
|
||||||
// Définis un délai d'exécution.
|
|
||||||
timeout: 30000,
|
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
||||||
reporter: "list",
|
reporter: "list",
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
retries: 1,
|
||||||
|
testDir: "../tests",
|
||||||
|
timeout: 10_000,
|
||||||
|
workers: "100%",
|
||||||
use: {
|
use: {
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
baseURL: "https://haikuatelier.gcch.local",
|
baseURL: "https://haikuatelier.gcch.local",
|
||||||
|
trace: "retry-with-trace",
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
||||||
trace: "on-first-retry",
|
|
||||||
clientCertificates: [
|
clientCertificates: [
|
||||||
{
|
{
|
||||||
origin: "https://haikuatelier.gcch.local",
|
origin: "https://haikuatelier.gcch.local",
|
||||||
certPath: "./containers/data/certs/_wildcard.gcch.local.pem",
|
certPath: "../containers/data/certs/_wildcard.gcch.local.pem",
|
||||||
keyPath: "./containers/data/certs/_wildcard.gcch.local-key.pem",
|
keyPath: "../containers/data/certs/_wildcard.gcch.local-key.pem",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: "desktop-chromium-1920",
|
name: "desktop-chromium-1920",
|
||||||
|
|
@ -74,10 +62,4 @@ export default defineConfig({
|
||||||
// use: { ...devices["Pixel 7 landscape"] },
|
// use: { ...devices["Pixel 7 landscape"] },
|
||||||
// },
|
// },
|
||||||
],
|
],
|
||||||
/* Run your local dev server before starting the tests */
|
|
||||||
// webServer: {
|
|
||||||
// command: 'npm run start',
|
|
||||||
// url: 'http://localhost:3000',
|
|
||||||
// reuseExistingServer: !process.env.CI,
|
|
||||||
// },
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,6 @@
|
||||||
"php-standard-library/phpstan-extension": "^2.1",
|
"php-standard-library/phpstan-extension": "^2.1",
|
||||||
"phpstan/extension-installer": "^1.4.3",
|
"phpstan/extension-installer": "^1.4.3",
|
||||||
"phpstan/phpstan": "^2.1.45",
|
"phpstan/phpstan": "^2.1.45",
|
||||||
"rector/rector": "^2.3.9",
|
|
||||||
"roave/security-advisories": "dev-latest",
|
"roave/security-advisories": "dev-latest",
|
||||||
"szepeviktor/phpstan-wordpress": "2.x-dev",
|
"szepeviktor/phpstan-wordpress": "2.x-dev",
|
||||||
"vincentlanglet/twig-cs-fixer": "^3.14"
|
"vincentlanglet/twig-cs-fixer": "^3.14"
|
||||||
|
|
|
||||||
7381
composer.lock
generated
Normal file
7381
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
4
justfile
4
justfile
|
|
@ -101,7 +101,9 @@ watch-js:
|
||||||
[group('qualité')]
|
[group('qualité')]
|
||||||
lint-js:
|
lint-js:
|
||||||
-bun eslint "web/app/themes/haiku-atelier-2024/src/scripts"
|
-bun eslint "web/app/themes/haiku-atelier-2024/src/scripts"
|
||||||
-bun oxlint "web/app/themes/haiku-atelier-2024/src/scripts"
|
bun --bun oxlint \
|
||||||
|
--config cfg/oxlint.config.ts \
|
||||||
|
--format stylish
|
||||||
|
|
||||||
# Vérifie le code Sass avec Stylelint.
|
# Vérifie le code Sass avec Stylelint.
|
||||||
[group('css')]
|
[group('css')]
|
||||||
|
|
|
||||||
11
package.json
11
package.json
|
|
@ -7,16 +7,15 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"scripts": { "knip": "knip" },
|
"scripts": {
|
||||||
|
"knip": "knip"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/language-service": "^0.60.0",
|
|
||||||
"@mobily/ts-belt": "v4.0.0-rc.5",
|
"@mobily/ts-belt": "v4.0.0-rc.5",
|
||||||
"@sentry/browser": "^10.47.0",
|
"@sentry/browser": "^10.47.0",
|
||||||
"a11y-dialog": "^8.1.4",
|
"a11y-dialog": "^8.1.4",
|
||||||
"chalk": "^5.6.2",
|
|
||||||
"effect": "^3.21.0",
|
"effect": "^3.21.0",
|
||||||
"lit-html": "^3.3.1",
|
"lit-html": "^3.3.1",
|
||||||
"optics-ts": "^2.4.1",
|
|
||||||
"purify-ts": "2.1.2",
|
"purify-ts": "2.1.2",
|
||||||
"ts-pattern": "^5.9.0",
|
"ts-pattern": "^5.9.0",
|
||||||
"valibot": "1.1.0"
|
"valibot": "1.1.0"
|
||||||
|
|
@ -28,7 +27,6 @@
|
||||||
"@gcch/configuration-prettier": "git+https://git.gcch.fr/gcch/configuration-prettier#8de937e801",
|
"@gcch/configuration-prettier": "git+https://git.gcch.fr/gcch/configuration-prettier#8de937e801",
|
||||||
"@playwright/test": "^1.59.0",
|
"@playwright/test": "^1.59.0",
|
||||||
"@sentry/core": "^10.47.0",
|
"@sentry/core": "^10.47.0",
|
||||||
"@swc/cli": "0.7.8",
|
|
||||||
"@types/bun": "^1.3.11",
|
"@types/bun": "^1.3.11",
|
||||||
"@types/node": "^25.5.0",
|
"@types/node": "^25.5.0",
|
||||||
"@vitejs/plugin-legacy": "^8.0.1",
|
"@vitejs/plugin-legacy": "^8.0.1",
|
||||||
|
|
@ -49,8 +47,9 @@
|
||||||
"lightningcss-cli": "^1.32.0",
|
"lightningcss-cli": "^1.32.0",
|
||||||
"oxlint": "^1.58.0",
|
"oxlint": "^1.58.0",
|
||||||
"oxlint-tsgolint": "^0.19.0",
|
"oxlint-tsgolint": "^0.19.0",
|
||||||
|
"playwright": "^1.59.0",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"prettier-plugin-pkg": "^0.21.2",
|
"prettier-plugin-pkg": "^0.22.1",
|
||||||
"prettier-plugin-sh": "^0.18.0",
|
"prettier-plugin-sh": "^0.18.0",
|
||||||
"sass-embedded": "^1.98.0",
|
"sass-embedded": "^1.98.0",
|
||||||
"stylelint": "^17.6.0",
|
"stylelint": "^17.6.0",
|
||||||
|
|
|
||||||
26
rector.php
26
rector.php
|
|
@ -1,26 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Rector\Config\RectorConfig;
|
|
||||||
|
|
||||||
return RectorConfig::configure()
|
|
||||||
->withPaths([__DIR__ . '/web/app/themes/haiku-atelier-2024'])
|
|
||||||
->withSkip([__DIR__ . '/vendor', __DIR__ . '/node_modules'])
|
|
||||||
->withPhpSets(php85: true)
|
|
||||||
->withCodeQualityLevel(10)
|
|
||||||
->withCodingStyleLevel(10)
|
|
||||||
->withDeadCodeLevel(10)
|
|
||||||
->withTypeCoverageDocblockLevel(10)
|
|
||||||
->withTypeCoverageLevel(10)
|
|
||||||
->withImportNames(
|
|
||||||
importDocBlockNames: true,
|
|
||||||
importNames: true,
|
|
||||||
importShortClasses: true,
|
|
||||||
removeUnusedImports: true
|
|
||||||
)
|
|
||||||
->withPreparedSets(
|
|
||||||
carbon: true,
|
|
||||||
instanceOf: true,
|
|
||||||
privatization: true
|
|
||||||
);
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
#!/usr/bin/fish
|
|
||||||
|
|
||||||
for image in *.png
|
|
||||||
gm convert -resize 1000 $image ok-$image.png
|
|
||||||
end
|
|
||||||
|
|
||||||
flaca -p *
|
|
||||||
|
|
@ -1,2 +1,4 @@
|
||||||
|
#!/usr/bin/fish
|
||||||
|
|
||||||
ssh ade -- fish /srv/haikuatelier.com/scripts/sauvegarde-bdd-production.fish
|
ssh ade -- fish /srv/haikuatelier.com/scripts/sauvegarde-bdd-production.fish
|
||||||
rclone copy --check-first --progress --multi-thread-streams 8 ade:/srv/haikuatelier.com/db /home/gcch/Répertoires/git.gcch.fr/gcch/haiku-atelier-2024/db
|
rclone copy --check-first --progress --multi-thread-streams 8 ade:/srv/haikuatelier.com/db /home/gcch/Répertoires/git.gcch.fr/gcch/haiku-atelier-2024/db
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
#!/usr/bin/fish
|
||||||
|
|
||||||
set -f fichiers_toml (fd --glob "*.toml")
|
set -f fichiers_toml (fd --glob "*.toml")
|
||||||
set -f fichiers_angie (fd --glob "*.conf" containers/conf/angie)
|
set -f fichiers_angie (fd --glob "*.conf" containers/conf/angie)
|
||||||
|
|
||||||
|
|
|
||||||
40
scripts/importe-dernier-export-bdd.ts
Normal file
40
scripts/importe-dernier-export-bdd.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { $ } from "bun";
|
||||||
|
import { Array, Option, Order, pipe } from "effect";
|
||||||
|
import { readdir } from "node:fs/promises";
|
||||||
|
|
||||||
|
const launchContainers = async (): Promise<string> => {
|
||||||
|
return await $`podman compose up -d`.text();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLatestDbExport = async (): Promise<string> => {
|
||||||
|
return pipe(
|
||||||
|
await readdir(`../db`),
|
||||||
|
(paths: ReadonlyArray<string>) => Array.sort(paths, Order.string),
|
||||||
|
(sortedPaths: ReadonlyArray<string>) => Array.last(sortedPaths),
|
||||||
|
(last: Option.Option<string>) =>
|
||||||
|
Option.getOrThrowWith(last, () => new Error("Aucun export de BDD n'est disponible.")),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const importLatestDbInWordpressContainer = async (exportPath: string) => {
|
||||||
|
await $`podman exec -it haikuatelier.fr-wordpress fish -c "cd web && wp --allow-root db import ${exportPath}"`;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// S'assure que les conteneurs soient lancées.
|
||||||
|
await launchContainers();
|
||||||
|
|
||||||
|
const latestExportPath: string = `../db/${await getLatestDbExport()}`;
|
||||||
|
console.log(`Dernier export : ${latestExportPath}`);
|
||||||
|
|
||||||
|
// Exécute l'opération d'import dans le conteneur WordPress via wp-cli.
|
||||||
|
await importLatestDbInWordpressContainer(latestExportPath);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof $.ShellError) {
|
||||||
|
console.error(`Commande échouée avec code d'erreur: ${error.exitCode}`);
|
||||||
|
console.log(error.stdout.toString());
|
||||||
|
console.log(error.stderr.toString());
|
||||||
|
} else {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
#!/usr/bin/fish
|
||||||
|
|
||||||
cd /srv/haikuatelier.com/web
|
cd /srv/haikuatelier.com/web
|
||||||
sudo -S wp-cli --allow-root db export
|
sudo -S wp-cli --allow-root db export
|
||||||
sudo -S mv -v /srv/haikuatelier.com/web/*.sql ../db
|
sudo -S mv -v /srv/haikuatelier.com/web/*.sql ../db
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
#!/usr/bin/fish
|
||||||
|
|
||||||
pyftsubset \
|
pyftsubset \
|
||||||
lato-variable-italic.ttf \
|
lato-variable-italic.ttf \
|
||||||
--desubroutinize \
|
--desubroutinize \
|
||||||
|
|
@ -8,7 +8,7 @@ test("can scroll to the end of the grid", async ({ page }): Promise<void> => {
|
||||||
await scrollToGridsEnd(page);
|
await scrollToGridsEnd(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("can access all Products' pages", async ({ page, request }): Promise<void> => {
|
test.skip("can access all Products' pages", async ({ page, request }): Promise<void> => {
|
||||||
await page.goto("https://haikuatelier.gcch.local/shop/");
|
await page.goto("https://haikuatelier.gcch.local/shop/");
|
||||||
const links = await getAllProductsLinks(page, request);
|
const links = await getAllProductsLinks(page, request);
|
||||||
|
|
||||||
|
|
@ -46,7 +46,7 @@ const scrollToGridsEnd = async (page: Page): Promise<void> => {
|
||||||
await expect(showMoreButton, "The 'Show more' button is visible").toBeVisible();
|
await expect(showMoreButton, "The 'Show more' button is visible").toBeVisible();
|
||||||
|
|
||||||
while (hasMoreProducts) {
|
while (hasMoreProducts) {
|
||||||
const newProductsResponse: Promise<Response> = page.waitForResponse(new RegExp(".*wp-json\/wc\/v3\/products.*"));
|
const newProductsResponse: Promise<Response> = page.waitForResponse(new RegExp(".*wp-json/wc/v3/products.*"));
|
||||||
await showMoreButton.click();
|
await showMoreButton.click();
|
||||||
await newProductsResponse;
|
await newProductsResponse;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -321,6 +321,7 @@ button.bouton-retour-haut {
|
||||||
background: var(--couleur-fond);
|
background: var(--couleur-fond);
|
||||||
box-shadow: initial;
|
box-shadow: initial;
|
||||||
transition: 0.2s background, 0.2s opacity, 0.2s visibility;
|
transition: 0.2s background, 0.2s opacity, 0.2s visibility;
|
||||||
|
z-index: 500;
|
||||||
}
|
}
|
||||||
button.bouton-retour-haut img {
|
button.bouton-retour-haut img {
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -16,28 +16,28 @@ require_once __DIR__ . '/src/inc/TraitementInformations.php';
|
||||||
$context = Timber::context();
|
$context = Timber::context();
|
||||||
$templates = ['produit.twig'];
|
$templates = ['produit.twig'];
|
||||||
|
|
||||||
$product = wc_get_product();
|
$raw_product = wc_get_product();
|
||||||
|
|
||||||
// Le Produit DOIT exister.
|
// Le Produit DOIT exister.
|
||||||
if ($product === null || is_bool($product)) {
|
if ($raw_product === null || is_bool($raw_product)) {
|
||||||
throw new Exception("Le Produit n'existe pas.");
|
throw new Exception("Le Produit n'existe pas.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assemble les données d'intérêt pour la page au sein d'une Classe.
|
// Assemble les données d'intérêt pour la page au sein d'une Classe.
|
||||||
$donnees_produit = Product::new($product);
|
$product = Product::new($raw_product);
|
||||||
|
|
||||||
/** @var int $prix_maximal Le prix de la Variation la plus chère */
|
/** @var int $prix_maximal Le prix de la Variation la plus chère */
|
||||||
$prix_maximal = collect($donnees_produit->variations)->max('price');
|
$maximum_price = collect($product->variations)->max('price');
|
||||||
|
|
||||||
$produits_meme_collection = array_map(
|
$same_collection_products = array_map(
|
||||||
array: recupere_produits_meme_collection($donnees_produit->collection)($donnees_produit->id),
|
array: recupere_produits_meme_collection($product->collection)($product->id),
|
||||||
callback: Product::new(...)
|
callback: Product::new(...)
|
||||||
);
|
);
|
||||||
|
|
||||||
$context['produit'] = $donnees_produit;
|
$context['product'] = $product;
|
||||||
$context['product_json'] = wp_json_encode($donnees_produit);
|
$context['product_json'] = wp_json_encode($product);
|
||||||
$context['prix_maximal'] = $prix_maximal;
|
$context['maximum_price'] = $maximum_price;
|
||||||
$context['produits_meme_collection'] = $produits_meme_collection;
|
$context['same_collection_products'] = $same_collection_products;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Charge les Scripts nécessaires pour la page Produit.
|
* Charge les Scripts nécessaires pour la page Produit.
|
||||||
|
|
@ -59,9 +59,6 @@ function charge_scripts_page_produit(): void {
|
||||||
|
|
||||||
add_action('wp_enqueue_scripts', 'charge_scripts_page_produit');
|
add_action('wp_enqueue_scripts', 'charge_scripts_page_produit');
|
||||||
|
|
||||||
$lal = wp_json_encode($context);
|
|
||||||
echo "<script>console.debug({$lal});</script>";
|
|
||||||
|
|
||||||
// Rendu
|
// Rendu
|
||||||
Timber::render(
|
Timber::render(
|
||||||
filenames: $templates,
|
filenames: $templates,
|
||||||
|
|
|
||||||
|
|
@ -21,74 +21,78 @@ use function Crell\fp\pipe;
|
||||||
*
|
*
|
||||||
* @return string TODO
|
* @return string TODO
|
||||||
*/
|
*/
|
||||||
function genere_balise_img_multiformats($id, bool $lazy = false): string
|
function genere_balise_img_multiformats(string $id, bool $lazy = false): string {
|
||||||
{
|
$int_id = (int) $id;
|
||||||
$int_id = (int) $id;
|
|
||||||
|
|
||||||
if (-1 === $id) {
|
if (-1 === $int_id) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = wp_get_attachment_image_url($int_id, 'full');
|
$url = wp_get_attachment_image_url($int_id, 'full');
|
||||||
$chemin = realpath(get_attached_file($int_id)) ?: realpath(get_attached_file($int_id));
|
$chemin = realpath(get_attached_file($int_id)) ?: realpath(get_attached_file($int_id));
|
||||||
$alt = get_post_meta($int_id, '_wp_attachment_image_alt', true);
|
$alt = get_post_meta($int_id, '_wp_attachment_image_alt', true);
|
||||||
$dimensions = $chemin ? getimagesize($chemin) : ['', ''];
|
$dimensions = $chemin ? getimagesize($chemin) : ['', ''];
|
||||||
|
|
||||||
$avif = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.avif') : false;
|
$avif = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.avif') : false;
|
||||||
$jxl = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.jxl') : false;
|
$jxl = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.jxl') : false;
|
||||||
$webp = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.webp') : false;
|
$webp = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.webp') : false;
|
||||||
|
|
||||||
// Génère un tableau avec les différents formats valides
|
// Génère un tableau avec les différents formats valides
|
||||||
$formats = pipe(
|
$formats = pipe(
|
||||||
[$avif, $jxl, $webp],
|
[$avif, $jxl, $webp],
|
||||||
static fn($tableau): array => array_filter(
|
static fn($tableau): array => array_filter(
|
||||||
array: $tableau,
|
array: $tableau,
|
||||||
callback: static fn($chemin_format): bool => false !== $chemin_format,
|
callback: static fn($chemin_format): bool => false !== $chemin_format
|
||||||
),
|
),
|
||||||
static fn($tableau): array => array_map(array: $tableau, callback: static fn($chemin_format): array => [
|
static fn($tableau): array => array_map(
|
||||||
'format' => pathinfo((string) $chemin_format)['extension'],
|
array: $tableau,
|
||||||
'taille' => filesize($chemin_format),
|
callback: static fn($chemin_format): array => [
|
||||||
'url' =>
|
'format' => pathinfo((string) $chemin_format)['extension'],
|
||||||
pathinfo($url)['dirname']
|
'taille' => filesize($chemin_format),
|
||||||
. '/'
|
'url' =>
|
||||||
. pathinfo($url)['filename']
|
pathinfo($url)['dirname']
|
||||||
. '.'
|
. '/'
|
||||||
. pathinfo((string) $chemin_format)['extension'],
|
. pathinfo($url)['filename']
|
||||||
]),
|
. '.'
|
||||||
);
|
. pathinfo((string) $chemin_format)['extension']
|
||||||
usort(array: $formats, callback: static fn($a, $b): int => $a['taille'] <=> $b['taille']);
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
usort(
|
||||||
|
array: $formats,
|
||||||
|
callback: static fn($a, $b): int => $a['taille'] <=> $b['taille']
|
||||||
|
);
|
||||||
|
|
||||||
// Construis les balises <source> avec les formats valides
|
// Construis les balises <source> avec les formats valides
|
||||||
$sources = '';
|
$sources = '';
|
||||||
foreach ($formats as $format) {
|
foreach ($formats as $format) {
|
||||||
$height = $dimensions[0];
|
$height = $dimensions[0];
|
||||||
$width = $dimensions[1];
|
$width = $dimensions[1];
|
||||||
$sources .= "<source height='{$height}' srcset='{$format['url']}' type='image/{$format['format']}' width='{$width}' />\n";
|
$sources .= "<source height='{$height}' srcset='{$format['url']}' type='image/{$format['format']}' width='{$width}' />\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
$loading = $lazy ? 'lazy' : 'eager';
|
$loading = $lazy ? 'lazy' : 'eager';
|
||||||
|
|
||||||
return <<<EOD
|
return <<<EOD
|
||||||
{$sources}
|
{$sources}
|
||||||
|
|
||||||
<img
|
<img
|
||||||
alt="{$alt}"
|
alt="{$alt}"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
height="{$dimensions[0]}"
|
height="{$dimensions[0]}"
|
||||||
loading="{$loading}"
|
loading="{$loading}"
|
||||||
onload="this.style.opacity=1"
|
onload="this.style.opacity=1"
|
||||||
src="{$url}"
|
src="{$url}"
|
||||||
width="{$dimensions[1]}"
|
width="{$dimensions[1]}"
|
||||||
/>
|
/>
|
||||||
EOD;
|
EOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO.
|
* TODO.
|
||||||
*/
|
*/
|
||||||
function tri_variations_par_prix_descendant(WC_Product $a, WC_Product $b): int
|
function tri_variations_par_prix_descendant(WC_Product $a, WC_Product $b): int {
|
||||||
{
|
return $b->get_price() <=> $a->get_price();
|
||||||
return $b->get_price() <=> $a->get_price();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -97,50 +101,52 @@ function tri_variations_par_prix_descendant(WC_Product $a, WC_Product $b): int
|
||||||
*
|
*
|
||||||
* @return mixed un tableau avec uniquement les informations pour la Grille de Produits
|
* @return mixed un tableau avec uniquement les informations pour la Grille de Produits
|
||||||
*/
|
*/
|
||||||
function recupere_informations_produit_shop(WC_Product $produit): mixed
|
function recupere_informations_produit_shop(WC_Product $produit): mixed {
|
||||||
{
|
/** @var int $prix_maximal Le prix maximal du Produit. */
|
||||||
/** @var int $prix_maximal Le prix maximal du Produit. */
|
$prix_maximal = pipe(
|
||||||
$prix_maximal = pipe(
|
// Récupère les Variations
|
||||||
// Récupère les Variations
|
$produit->get_children(),
|
||||||
$produit->get_children(),
|
// Récupère les informations de chaque Variation
|
||||||
// Récupère les informations de chaque Variation
|
static fn($enfants): array => array_map(
|
||||||
static fn($enfants): array => array_map(callback: wc_get_product(...), array: $enfants),
|
callback: wc_get_product(...),
|
||||||
// Trie les Variations par prix descendant
|
array: $enfants
|
||||||
static fn($variations): array => array_map(
|
),
|
||||||
callback: static fn($variation) => $variation->get_price(),
|
// Trie les Variations par prix descendant
|
||||||
array: $variations,
|
static fn($variations): array => array_map(
|
||||||
),
|
callback: static fn($variation) => $variation->get_price(),
|
||||||
// Récupère le Prix de la Variation la plus chère
|
array: $variations
|
||||||
static fn($prix) => collect($prix)->max(),
|
),
|
||||||
// Récupère le Prix pour la Variation la plus chère OU le prix du Produit simple
|
// Récupère le Prix de la Variation la plus chère
|
||||||
static fn($prix_variation_maximale) => $prix_variation_maximale ?? $produit->get_price(),
|
static fn($prix) => collect($prix)->max(),
|
||||||
);
|
// Récupère le Prix pour la Variation la plus chère OU le prix du Produit simple
|
||||||
|
static fn($prix_variation_maximale) => $prix_variation_maximale ?? $produit->get_price()
|
||||||
|
);
|
||||||
|
|
||||||
// TEMP: Cas de la Carte Cadeau où aucun prix ne doit être affiché. Idéalement utiliser un système d'étiquettes pour ces cas là.
|
// TEMP: Cas de la Carte Cadeau où aucun prix ne doit être affiché. Idéalement utiliser un système d'étiquettes pour ces cas là.
|
||||||
if ($produit->get_sku() === 'GIFTcard') {
|
if ($produit->get_sku() === 'GIFTcard') {
|
||||||
$prix_maximal = '';
|
$prix_maximal = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
// Identifiant du Produit
|
// Identifiant du Produit
|
||||||
'id' => $produit->get_id(),
|
'id' => $produit->get_id(),
|
||||||
// Nom affiché du Produit
|
// Nom affiché du Produit
|
||||||
'nom' => $produit->get_name(),
|
'nom' => $produit->get_name(),
|
||||||
// Prix affiché du Produit
|
// Prix affiché du Produit
|
||||||
'prix' => "{$prix_maximal}",
|
'prix' => "{$prix_maximal}",
|
||||||
// Photo du Produit affichée par défaut
|
// Photo du Produit affichée par défaut
|
||||||
'photo_repos' => genere_balise_img_multiformats(
|
'photo_repos' => genere_balise_img_multiformats(
|
||||||
get_post_meta($post_id = $produit->get_id(), $key = '_photos_colonne_gauche|||0|value')[0] ?? -1,
|
get_post_meta($post_id = $produit->get_id(), $key = '_photos_colonne_gauche|||0|value')[0] ?? -1,
|
||||||
false,
|
false
|
||||||
),
|
),
|
||||||
// Photo du Produit affichée au survol de l'image
|
// Photo du Produit affichée au survol de l'image
|
||||||
'photo_survol' => genere_balise_img_multiformats(
|
'photo_survol' => genere_balise_img_multiformats(
|
||||||
get_post_meta($post_id = $produit->get_id(), $key = '_photos_colonne_droite|||0|value')[0] ?? -1,
|
get_post_meta($post_id = $produit->get_id(), $key = '_photos_colonne_droite|||0|value')[0] ?? -1,
|
||||||
true,
|
true
|
||||||
),
|
),
|
||||||
// URL du Produit pour les liens vers celui-ci
|
// URL du Produit pour les liens vers celui-ci
|
||||||
'url' => $produit->get_permalink(),
|
'url' => $produit->get_permalink()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page Produit
|
// Page Produit
|
||||||
|
|
@ -148,51 +154,50 @@ function recupere_informations_produit_shop(WC_Product $produit): mixed
|
||||||
/**
|
/**
|
||||||
* Retourne un tableau associatif des informations affichées sur la page Produit depuis les données brutes d'un Produit.
|
* Retourne un tableau associatif des informations affichées sur la page Produit depuis les données brutes d'un Produit.
|
||||||
*/
|
*/
|
||||||
function recupere_informations_produit_page_produit(WC_Product $product): mixed
|
function recupere_informations_produit_page_produit(WC_Product $product): mixed {
|
||||||
{
|
/** @var list<Attribute> */
|
||||||
/** @var list<Attribute> */
|
$attributs = Product::get_attributes_for_product($product);
|
||||||
$attributs = Product::get_attributes_for_product($product);
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
// Attributs du Produit
|
// Attributs du Produit
|
||||||
'attributs' => $attributs,
|
'attributs' => $attributs,
|
||||||
// Catégorie du Produit
|
// Catégorie du Produit
|
||||||
'categorie' => pipe($product->get_id(), wc_get_product_category_list(...), strtolower(...)),
|
'categorie' => pipe($product->get_id(), wc_get_product_category_list(...), strtolower(...)),
|
||||||
// Slug de la Collection - Peut ne pas avoir été défini
|
// Slug de la Collection - Peut ne pas avoir été défini
|
||||||
'collection' => get_the_terms($product->get_id(), 'collection')[0]->slug ?? '',
|
'collection' => get_the_terms($product->get_id(), 'collection')[0]->slug ?? '',
|
||||||
// Détails (Description) du Produit
|
// Détails (Description) du Produit
|
||||||
'details' => wpautop($product->get_description()),
|
'details' => wpautop($product->get_description()),
|
||||||
// Identifiant du Produit
|
// Identifiant du Produit
|
||||||
'id' => $product->get_id(),
|
'id' => $product->get_id(),
|
||||||
// Nom affiché du Produit
|
// Nom affiché du Produit
|
||||||
'nom' => $product->get_name(),
|
'nom' => $product->get_name(),
|
||||||
// Prix affiché du Produit
|
// Prix affiché du Produit
|
||||||
'prix' => $product->get_price(),
|
'prix' => $product->get_price(),
|
||||||
'photos_colonne_gauche' => array_map(
|
'photos_colonne_gauche' => array_map(
|
||||||
callback: genere_balise_img_multiformats(...),
|
callback: genere_balise_img_multiformats(...),
|
||||||
array: get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_gauche|||0|value'),
|
array: get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_gauche|||0|value')
|
||||||
),
|
),
|
||||||
'photos_colonne_droite' => array_map(
|
'photos_colonne_droite' => array_map(
|
||||||
callback: genere_balise_img_multiformats(...),
|
callback: genere_balise_img_multiformats(...),
|
||||||
array: carbon_get_the_post_meta('photos_colonne_droite'),
|
array: carbon_get_the_post_meta('photos_colonne_droite')
|
||||||
),
|
),
|
||||||
'photo_repos' => genere_balise_img_multiformats(
|
'photo_repos' => genere_balise_img_multiformats(
|
||||||
get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_gauche|||0|value')[0] ?? -1,
|
get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_gauche|||0|value')[0] ?? -1,
|
||||||
false,
|
false
|
||||||
),
|
),
|
||||||
'photo_survol' => genere_balise_img_multiformats(
|
'photo_survol' => genere_balise_img_multiformats(
|
||||||
get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_droite|||0|value')[0] ?? -1,
|
get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_droite|||0|value')[0] ?? -1,
|
||||||
true,
|
true
|
||||||
),
|
),
|
||||||
// Slug du Produit
|
// Slug du Produit
|
||||||
'slug' => $product->get_slug(),
|
'slug' => $product->get_slug(),
|
||||||
// Quantité de Produit en stock
|
// Quantité de Produit en stock
|
||||||
'stock' => $product->get_stock_quantity() ?? 1,
|
'stock' => $product->get_stock_quantity() ?? 1,
|
||||||
// Variations du Produit
|
// Variations du Produit
|
||||||
'variations_ids' => $product->get_children(),
|
'variations_ids' => $product->get_children(),
|
||||||
// URL du Produit
|
// URL du Produit
|
||||||
'url' => $product->get_permalink(),
|
'url' => $product->get_permalink()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -201,26 +206,24 @@ function recupere_informations_produit_page_produit(WC_Product $product): mixed
|
||||||
*
|
*
|
||||||
* Pour faciliter l'usage avec `array_map`, utilise une fonction avec curryfication.
|
* Pour faciliter l'usage avec `array_map`, utilise une fonction avec curryfication.
|
||||||
*/
|
*/
|
||||||
function recupere_produits_meme_collection(string $slug_collection): mixed
|
function recupere_produits_meme_collection(string $slug_collection): mixed {
|
||||||
{
|
// @param int $id_produit
|
||||||
// @param int $id_produit
|
return static fn($id_produit) => wc_get_products([
|
||||||
return static fn($id_produit) => wc_get_products([
|
'exclude' => [$id_produit],
|
||||||
'exclude' => [$id_produit],
|
'limit' => 4,
|
||||||
'limit' => 4,
|
'order' => 'DESC',
|
||||||
'order' => 'DESC',
|
'orderby' => 'date',
|
||||||
'orderby' => 'date',
|
'status' => 'publish',
|
||||||
'status' => 'publish',
|
'tax_query' => [['taxonomy' => 'collection', 'field' => 'slug', 'terms' => $slug_collection]]
|
||||||
'tax_query' => [['taxonomy' => 'collection', 'field' => 'slug', 'terms' => $slug_collection]],
|
]);
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page Panier
|
// Page Panier
|
||||||
|
|
||||||
function recupere_et_formate_attributs_produit(mixed $attributs_produit): mixed
|
function recupere_et_formate_attributs_produit(mixed $attributs_produit): mixed {
|
||||||
{
|
return [
|
||||||
return [
|
'taille' => ['nom' => 'Size', 'valeur' => $attributs_produit['pa_size'] ?? false],
|
||||||
'taille' => ['nom' => 'Size', 'valeur' => $attributs_produit['pa_size'] ?? false],
|
'pierre' => ['nom' => 'Stone', 'valeur' => $attributs_produit['pa_stone'] ?? false],
|
||||||
'pierre' => ['nom' => 'Stone', 'valeur' => $attributs_produit['pa_stone'] ?? false],
|
'cote' => ['nom' => 'Side', 'valeur' => $attributs_produit['pa_side'] ?? false]
|
||||||
'cote' => ['nom' => 'Side', 'valeur' => $attributs_produit['pa_side'] ?? false],
|
];
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ button {
|
||||||
background: var(--couleur-fond);
|
background: var(--couleur-fond);
|
||||||
box-shadow: initial;
|
box-shadow: initial;
|
||||||
transition: 0.2s background, 0.2s opacity, 0.2s visibility;
|
transition: 0.2s background, 0.2s opacity, 0.2s visibility;
|
||||||
|
z-index: 500;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ import { getOptionOrThrowWithError } from "./utils";
|
||||||
export type ParentElement = Document | Element;
|
export type ParentElement = Document | Element;
|
||||||
|
|
||||||
export const getFirstSelectorFromParent =
|
export const getFirstSelectorFromParent =
|
||||||
(parent: ParentElement) => <E extends Element = Element>(selector: string): Option.Option<NonNullable<E>> =>
|
(parent: ParentElement) =>
|
||||||
Option.fromNullishOr(parent.querySelector<E>(selector));
|
<E extends Element = Element>(selector: string): Option.Option<NonNullable<E>> =>
|
||||||
|
Option.fromNullable(parent.querySelector<E>(selector));
|
||||||
|
|
||||||
export const getFirstSelectorFromDocument = <E extends Element = Element>(
|
export const getFirstSelectorFromDocument = <E extends Element = Element>(
|
||||||
selector: string,
|
selector: string,
|
||||||
|
|
@ -21,12 +22,13 @@ export const getFirstSelectorFromDocumentOrThrow = <E extends Element = Element>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getAllSelectorFromParent =
|
export const getAllSelectorFromParent =
|
||||||
(parent: ParentElement) => <E extends Element = Element>(selector: string): Option.Option<NonEmptyReadonlyArray<E>> =>
|
(parent: ParentElement) =>
|
||||||
|
<E extends Element = Element>(selector: string): Option.Option<NonEmptyReadonlyArray<E>> =>
|
||||||
pipe(
|
pipe(
|
||||||
parent.querySelectorAll<E>(selector),
|
parent.querySelectorAll<E>(selector),
|
||||||
// Convertis NodeListOf en Array.
|
// Convertis NodeListOf en Array.
|
||||||
Array.from<E>,
|
Array.from<E>,
|
||||||
(xs: Array<E>) => Option.liftPredicate(EffectArray.isReadonlyArrayNonEmpty)(xs),
|
(xs: Array<E>) => Option.liftPredicate(EffectArray.isNonEmptyReadonlyArray)(xs),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getAllSelectorFromDocument = <E extends Element = Element>(
|
export const getAllSelectorFromDocument = <E extends Element = Element>(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { pipe, Option } from "effect";
|
import { pipe, Option } from "effect";
|
||||||
|
|
||||||
export const getOptionOrThrowWithError = (message: string) => <T>(option: Option.Option<T>): T =>
|
export const getOptionOrThrowWithError =
|
||||||
pipe(
|
(message: string) =>
|
||||||
option,
|
<T>(option: Option.Option<T>): T =>
|
||||||
Option.getOrThrowWith(() => new Error(message)),
|
pipe(
|
||||||
);
|
option,
|
||||||
|
Option.getOrThrowWith(() => new Error(message)),
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,7 @@ import { match } from "ts-pattern";
|
||||||
import type { HttpCodeErrors, SimplifiedResponse } from "./types/reseau";
|
import type { HttpCodeErrors, SimplifiedResponse } from "./types/reseau";
|
||||||
|
|
||||||
import { ENTETE_WC_NONCE } from "../constantes/api.ts";
|
import { ENTETE_WC_NONCE } from "../constantes/api.ts";
|
||||||
import {
|
import { BadRequestError, ForbiddenError, NotFoundError, ServerError, UnauthorizedError } from "./erreurs.ts";
|
||||||
BadRequestError,
|
|
||||||
ForbiddenError,
|
|
||||||
NotFoundError,
|
|
||||||
ServerError,
|
|
||||||
UnauthorizedError,
|
|
||||||
} from "./erreurs.ts";
|
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
|
|
||||||
|
|
@ -59,9 +53,7 @@ export const getBackend = (args: ArgumentsGetBackendWC): Promise<Response> =>
|
||||||
signal: AbortSignal.timeout(5000),
|
signal: AbortSignal.timeout(5000),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getBackendAvecParametresUrl = (
|
export const getBackendAvecParametresUrl = (args: ArgumentsGetBackendWC): Promise<Response> =>
|
||||||
args: ArgumentsGetBackendWC,
|
|
||||||
): Promise<Response> =>
|
|
||||||
fetch(`${args.route}?${args.searchParams}`, {
|
fetch(`${args.route}?${args.searchParams}`, {
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -76,9 +68,7 @@ export const getBackendAvecParametresUrl = (
|
||||||
signal: AbortSignal.timeout(5000),
|
signal: AbortSignal.timeout(5000),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const deleteBackend = (
|
export const deleteBackend = (args: ArgumentsDeleteBackendWC): Promise<Response> =>
|
||||||
args: ArgumentsDeleteBackendWC,
|
|
||||||
): Promise<Response> =>
|
|
||||||
fetch(args.route, {
|
fetch(args.route, {
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -111,11 +101,7 @@ export const postBackend = (args: ArgumentsPostBackendWC): Promise<Response> =>
|
||||||
|
|
||||||
export const prefilledPostBackend =
|
export const prefilledPostBackend =
|
||||||
(nonce: string, authString?: string) =>
|
(nonce: string, authString?: string) =>
|
||||||
(
|
(route: string, body: BodyInit, needsAuthString: boolean): Promise<Response> =>
|
||||||
route: string,
|
|
||||||
body: BodyInit,
|
|
||||||
needsAuthString: boolean,
|
|
||||||
): Promise<Response> =>
|
|
||||||
fetch(route, {
|
fetch(route, {
|
||||||
body: body,
|
body: body,
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
|
|
@ -123,32 +109,25 @@ export const prefilledPostBackend =
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
[ENTETE_WC_NONCE]: nonce,
|
[ENTETE_WC_NONCE]: nonce,
|
||||||
...(authString &&
|
...(authString && needsAuthString && { Authorization: `Basic ${authString}` }),
|
||||||
needsAuthString && { Authorization: `Basic ${authString}` }),
|
|
||||||
},
|
},
|
||||||
method: "POST",
|
method: "POST",
|
||||||
mode: "same-origin",
|
mode: "same-origin",
|
||||||
signal: AbortSignal.timeout(5000),
|
signal: AbortSignal.timeout(5000),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const safeFetch = (
|
export const safeFetch = (f: Promise<Response>): EitherAsync<DOMException | TypeError, Response> =>
|
||||||
f: Promise<Response>,
|
|
||||||
): EitherAsync<DOMException | TypeError, Response> =>
|
|
||||||
EitherAsync<DOMException | TypeError, Response>(async () => await f);
|
EitherAsync<DOMException | TypeError, Response>(async () => await f);
|
||||||
|
|
||||||
// Réponses Simplifiées
|
// Réponses Simplifiées
|
||||||
export const newPartialResponse = async (
|
export const newPartialResponse = async (reponse: Response): Promise<SimplifiedResponse> => {
|
||||||
reponse: Response,
|
|
||||||
): Promise<SimplifiedResponse> => {
|
|
||||||
return {
|
return {
|
||||||
body: await reponse.json(),
|
body: await reponse.json(),
|
||||||
status: reponse.status,
|
status: reponse.status,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const traiteErreursBackendWooCommerce = (
|
export const traiteErreursBackendWooCommerce = (rs: SimplifiedResponse): HttpCodeErrors => {
|
||||||
rs: SimplifiedResponse,
|
|
||||||
): HttpCodeErrors => {
|
|
||||||
return match(rs)
|
return match(rs)
|
||||||
.with({ status: 400 }, () => new BadRequestError())
|
.with({ status: 400 }, () => new BadRequestError())
|
||||||
.with({ status: 401 }, () => new UnauthorizedError())
|
.with({ status: 401 }, () => new UnauthorizedError())
|
||||||
|
|
|
||||||
|
|
@ -2,28 +2,18 @@
|
||||||
|
|
||||||
import { Array as EffectArray, Match, Predicate } from "effect";
|
import { Array as EffectArray, Match, Predicate } from "effect";
|
||||||
|
|
||||||
import {
|
import { DOM_ENTREES_MENU_CATEGORIES_PRODUITS, DOM_MENU_CATEGORIES_PRODUITS } from "./constantes/dom.ts";
|
||||||
DOM_ENTREES_MENU_CATEGORIES_PRODUITS,
|
import { getAllSelectorFromDocumentOrThrow, getFirstSelectorFromDocumentOrThrow } from "../scripts-effect/lib/dom.ts";
|
||||||
DOM_MENU_CATEGORIES_PRODUITS,
|
|
||||||
} from "./constantes/dom.ts";
|
|
||||||
import {
|
|
||||||
getAllSelectorFromDocumentOrThrow,
|
|
||||||
getFirstSelectorFromDocumentOrThrow,
|
|
||||||
} from "../scripts-effect/lib/dom.ts";
|
|
||||||
|
|
||||||
// Initialise les attributs HTML pour l'affichage initiale des flèches de défilement du menu de catégories de Produits.
|
// Initialise les attributs HTML pour l'affichage initiale des flèches de défilement du menu de catégories de Produits.
|
||||||
document.addEventListener("DOMContentLoaded", (): void => {
|
document.addEventListener("DOMContentLoaded", (): void => {
|
||||||
const productsCategoriesMenu: HTMLElement =
|
const productsCategoriesMenu: HTMLElement =
|
||||||
getFirstSelectorFromDocumentOrThrow<HTMLElement>(
|
getFirstSelectorFromDocumentOrThrow<HTMLElement>(DOM_MENU_CATEGORIES_PRODUITS);
|
||||||
DOM_MENU_CATEGORIES_PRODUITS,
|
const menuEntries: ReadonlyArray<HTMLAnchorElement> = getAllSelectorFromDocumentOrThrow(
|
||||||
);
|
DOM_ENTREES_MENU_CATEGORIES_PRODUITS,
|
||||||
const menuEntries: ReadonlyArray<HTMLAnchorElement> =
|
);
|
||||||
getAllSelectorFromDocumentOrThrow(DOM_ENTREES_MENU_CATEGORIES_PRODUITS);
|
|
||||||
|
|
||||||
const firstAndLastEntries: Array<HTMLAnchorElement | undefined> = [
|
const firstAndLastEntries: Array<HTMLAnchorElement | undefined> = [menuEntries.at(0), menuEntries.at(-1)];
|
||||||
menuEntries.at(0),
|
|
||||||
menuEntries.at(-1),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Créé un nouvel Observer pour la première et dernière entrée.
|
// Créé un nouvel Observer pour la première et dernière entrée.
|
||||||
EffectArray.forEach(firstAndLastEntries, (menuEntry, _index) => {
|
EffectArray.forEach(firstAndLastEntries, (menuEntry, _index) => {
|
||||||
|
|
@ -35,28 +25,10 @@ document.addEventListener("DOMContentLoaded", (): void => {
|
||||||
if (intersectionEntry.boundingClientRect.top <= 0) return;
|
if (intersectionEntry.boundingClientRect.top <= 0) return;
|
||||||
|
|
||||||
Match.value([intersectionEntry.isIntersecting]).pipe(
|
Match.value([intersectionEntry.isIntersecting]).pipe(
|
||||||
Match.when([true, 0], () =>
|
Match.when([true, 0], () => productsCategoriesMenu.removeAttribute("data-entrees-presentes-debut")),
|
||||||
productsCategoriesMenu.removeAttribute(
|
Match.when([true, 1], () => productsCategoriesMenu.removeAttribute("data-entrees-presentes-fin")),
|
||||||
"data-entrees-presentes-debut",
|
Match.when([false, 0], () => productsCategoriesMenu.setAttribute("data-entrees-presentes-debut", "")),
|
||||||
),
|
Match.when([false, 1], () => productsCategoriesMenu.setAttribute("data-entrees-presentes-fin", "")),
|
||||||
),
|
|
||||||
Match.when([true, 1], () =>
|
|
||||||
productsCategoriesMenu.removeAttribute(
|
|
||||||
"data-entrees-presentes-fin",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Match.when([false, 0], () =>
|
|
||||||
productsCategoriesMenu.setAttribute(
|
|
||||||
"data-entrees-presentes-debut",
|
|
||||||
"",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Match.when([false, 1], () =>
|
|
||||||
productsCategoriesMenu.setAttribute(
|
|
||||||
"data-entrees-presentes-fin",
|
|
||||||
"",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Match.orElse(() => {}),
|
Match.orElse(() => {}),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -173,8 +173,7 @@ const ajouteProduitAuPanier = (event: MouseEvent): void => {
|
||||||
|
|
||||||
// Construis les arguments de la requête au backend
|
// Construis les arguments de la requête au backend
|
||||||
const argsRequete: WCStoreCartAddItemArgs = {
|
const argsRequete: WCStoreCartAddItemArgs = {
|
||||||
id: E.DOM_VARIATION
|
id: E.DOM_VARIATION.map((selecteur: HTMLSelectElement): number => Number(selecteur.value))
|
||||||
.map((selecteur: HTMLSelectElement): number => Number(selecteur.value))
|
|
||||||
// Récupère l'ID du Produit de la Page pour les Produits simples
|
// Récupère l'ID du Produit de la Page pour les Produits simples
|
||||||
.orDefault(ETATS_PAGE.idProduit),
|
.orDefault(ETATS_PAGE.idProduit),
|
||||||
// id: ETATS_PAGE.idProduit,
|
// id: ETATS_PAGE.idProduit,
|
||||||
|
|
|
||||||
|
|
@ -6,61 +6,62 @@ declare(strict_types=1);
|
||||||
* Le modèle de la Page d'Archive d'une Catégorie de Produits.
|
* Le modèle de la Page d'Archive d'une Catégorie de Produits.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use HaikuAtelier\Data\Product;
|
||||||
|
use HaikuAtelier\WP\Resource;
|
||||||
use Timber\Timber;
|
use Timber\Timber;
|
||||||
|
|
||||||
require_once __DIR__ . '/src/inc/TraitementInformations.php';
|
require_once __DIR__ . '/src/inc/TraitementInformations.php';
|
||||||
|
|
||||||
// Contexte et modèles
|
// Contexte et modèles
|
||||||
$contexte = Timber::context();
|
$context = Timber::context();
|
||||||
$modeles = ['boutique.twig'];
|
$templates = ['boutique.twig'];
|
||||||
|
|
||||||
/** @var list<WC_Product> $informations_produits Les informations brutes des Produits. */
|
/** @var WP_Term */
|
||||||
$informations_produits = wc_get_products([
|
$current_term = get_queried_object();
|
||||||
'category' => [get_queried_object()?->slug],
|
$category_slug = $current_term->slug;
|
||||||
|
|
||||||
|
/** @var list<WC_Product> $raw_products Les informations brutes des Produits. */
|
||||||
|
$raw_products = wc_get_products([
|
||||||
|
'category' => [$category_slug],
|
||||||
'limit' => 12,
|
'limit' => 12,
|
||||||
'order' => 'DESC',
|
'order' => 'DESC',
|
||||||
'orderby' => 'date',
|
'orderby' => 'date',
|
||||||
'status' => 'publish'
|
'status' => 'publish'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/** @var InformationsProduitShop $produits Les informations strictement nécessaires pour la grille des Produits. */
|
$products = array_map(
|
||||||
$produits = array_map(
|
callback: Product::new(...),
|
||||||
callback: recupere_informations_produit_shop(...),
|
array: $raw_products
|
||||||
array: $informations_produits
|
|
||||||
);
|
);
|
||||||
$contexte['produits'] = $produits;
|
$context['products'] = $products;
|
||||||
$id_categorie_produits = array_shift($informations_produits)?->get_category_ids()[0] ?? '';
|
$products_category_id = array_shift($raw_products)?->get_category_ids()[0] ?? '';
|
||||||
$contexte['id_categorie_produits'] = $id_categorie_produits;
|
$context['products_category_id'] = $products_category_id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Charge les Scripts nécessaires pour la page d'Archive.
|
* Charge les ressources nécessaires pour la page d'Archive.
|
||||||
*/
|
*/
|
||||||
function charge_scripts_page_archive_produits(): void {
|
function load_page_resources(): void {
|
||||||
wp_enqueue_style(
|
Resource::enqueue_style_file(
|
||||||
handle: 'haiku-atelier-2024-styles-page-boutique',
|
handle: 'haiku-atelier-2024-styles-page-boutique',
|
||||||
src: get_template_directory_uri() . '/assets/css/pages/page-boutique.css',
|
path: '/assets/css/pages/page-boutique.css'
|
||||||
deps: [],
|
|
||||||
ver: filemtime(get_template_directory() . '/assets/css/pages/page-boutique.css'),
|
|
||||||
media: 'all'
|
|
||||||
);
|
);
|
||||||
wp_enqueue_script_module(
|
Resource::enqueue_script_module_file(
|
||||||
id: 'haiku-atelier-2024-scripts-page-boutique',
|
id: 'haiku-atelier-2024-scripts-page-boutique',
|
||||||
src: get_template_directory_uri() . '/assets/js/scripts-page-boutique.js',
|
path: '/assets/js/scripts-page-boutique.js'
|
||||||
deps: [],
|
|
||||||
version: filemtime(get_template_directory() . '/assets/js/scripts-page-boutique.js')
|
|
||||||
);
|
);
|
||||||
wp_enqueue_script_module(
|
Resource::enqueue_script_module_file(
|
||||||
id: 'haiku-atelier-2024-scripts-menu-categories',
|
id: 'haiku-atelier-2024-scripts-menu-categories',
|
||||||
src: get_template_directory_uri() . '/assets/js/scripts-menu-categories.js',
|
path: '/assets/js/scripts-menu-categories.js'
|
||||||
deps: [],
|
|
||||||
version: filemtime(get_template_directory() . '/assets/js/scripts-menu-categories.js')
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
add_action('wp_enqueue_scripts', 'charge_scripts_page_archive_produits');
|
add_action('wp_enqueue_scripts', 'load_page_resources');
|
||||||
|
|
||||||
|
$lal = wp_json_encode($context);
|
||||||
|
echo "<script>console.debug({$lal});</script>";
|
||||||
|
|
||||||
// Rendu
|
// Rendu
|
||||||
Timber::render(
|
Timber::render(
|
||||||
filenames: $modeles,
|
filenames: $templates,
|
||||||
data: $contexte
|
data: $context
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ const _etats = {
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button
|
<button
|
||||||
{{ produits|length == 12 ? '' : 'hidden' }}
|
{{ products|length == 12 ? '' : 'hidden' }}
|
||||||
class="bouton-case-pleine bouton-blanc-sur-noir"
|
class="bouton-case-pleine bouton-blanc-sur-noir"
|
||||||
id="bouton-plus-de-produits"
|
id="bouton-plus-de-produits"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
<div class="grille-produits-similaires">
|
<div class="grille-produits-similaires">
|
||||||
{% for produit in produits_meme_collection %}
|
{% for product in same_collection_products %}
|
||||||
{# TODO: Trouver une meilleure arborescence et des noms de classe #}
|
{# TODO: Trouver une meilleure arborescence et des noms de classe #}
|
||||||
<article class="produit">
|
<article class="produit">
|
||||||
<figure role="figure">
|
<figure role="figure">
|
||||||
<a href="{{ produit.url }}">
|
<a href="{{ product.url }}">
|
||||||
<picture class="produit__illustration produit__illustration__principale">
|
<picture class="produit__illustration produit__illustration__principale">
|
||||||
{{ produit.default_photo }}
|
{{ product.default_photo }}
|
||||||
</picture>
|
</picture>
|
||||||
|
|
||||||
<picture class="produit__illustration produit__illustration__survol">
|
<picture class="produit__illustration produit__illustration__survol">
|
||||||
{{ produit.hover_photo }}
|
{{ product.hover_photo }}
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<figcaption class="produit__textuel">
|
<figcaption class="produit__textuel">
|
||||||
<h3 class="produit__textuel__titre">
|
<h3 class="produit__textuel__titre">
|
||||||
<a href="{{ produit.url }}">{{ produit.name }}</a>
|
<a href="{{ product.url }}">{{ product.name }}</a>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="produit__textuel__prix">
|
<p class="produit__textuel__prix">
|
||||||
{{ produit.price }}€
|
{{ product.price }}€
|
||||||
</p>
|
</p>
|
||||||
</figcaption>
|
</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@
|
||||||
id="variation-choice"
|
id="variation-choice"
|
||||||
name="variation-choice"
|
name="variation-choice"
|
||||||
>
|
>
|
||||||
<h3 class="selecteur-produit__nom">{{ produit.name }}</h3>
|
<h3 class="selecteur-produit__nom">{{ product.name }}</h3>
|
||||||
|
|
||||||
<div class="selecteur-produit__attribut-variation">
|
<div class="selecteur-produit__attribut-variation">
|
||||||
{% if produit.attributes %}
|
{% if product.attributes %}
|
||||||
{% for attribut in produit.attributes %}
|
{% for attribut in product.attributes %}
|
||||||
<div class="test">
|
<div class="test">
|
||||||
{{ include('parts/pages/produit/selecteur-attributs-produit.twig') }}
|
{{ include('parts/pages/produit/selecteur-attributs-produit.twig') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
#}
|
#}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="selecteur-produit__prix">{{ prix_maximal ?? produit.price }}€</p>
|
<p class="selecteur-produit__prix">{{ maximum_price ?? product.price }}€</p>
|
||||||
</form>
|
</form>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
class="section-textuelle__contenu"
|
class="section-textuelle__contenu"
|
||||||
id="section-details-produit"
|
id="section-details-produit"
|
||||||
>
|
>
|
||||||
{{ produit.details }}
|
{{ product.details }}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -122,7 +122,7 @@
|
||||||
|
|
||||||
<div class="details-produit__actions">
|
<div class="details-produit__actions">
|
||||||
{# Désactive le bouton d'ajout au panier en cas d'absence de stock. #}
|
{# Désactive le bouton d'ajout au panier en cas d'absence de stock. #}
|
||||||
{% if produit.stock > 0 %}
|
{% if product.stock > 0 %}
|
||||||
<button
|
<button
|
||||||
class="bouton-case-pleine"
|
class="bouton-case-pleine"
|
||||||
disabled
|
disabled
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
aria-label="Photo of the Product alone"
|
aria-label="Photo of the Product alone"
|
||||||
class="colonne colonne-gauche"
|
class="colonne colonne-gauche"
|
||||||
>
|
>
|
||||||
{% for photo in produit.left_column_photos %}
|
{% for photo in product.left_column_photos %}
|
||||||
<figure
|
<figure
|
||||||
data-index="0"
|
data-index="0"
|
||||||
role="figure"
|
role="figure"
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
aria-label="Photos of the Product worn"
|
aria-label="Photos of the Product worn"
|
||||||
class="colonne colonne-droite"
|
class="colonne colonne-droite"
|
||||||
>
|
>
|
||||||
{% for photo in produit.right_column_photos %}
|
{% for photo in product.right_column_photos %}
|
||||||
<figure
|
<figure
|
||||||
data-index="{{ loop.index }}"
|
data-index="{{ loop.index }}"
|
||||||
role="figure"
|
role="figure"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<div
|
<div
|
||||||
class="grille-produits"
|
class="grille-produits"
|
||||||
data-page="1"
|
data-page="1"
|
||||||
{% if id_categorie_produits %}data-id-categorie-produits="{{ id_categorie_produits }}"{% endif %}
|
{% if products_category_id %}data-id-categorie-produits="{{ products_category_id }}"{% endif %}
|
||||||
>
|
>
|
||||||
{% if products|length > 0 %}
|
{% if products|length > 0 %}
|
||||||
{% for product in products %}
|
{% for product in products %}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
/** @type {Etats} */
|
/** @type {Etats} */
|
||||||
const _etats = {
|
const _etats = {
|
||||||
idProduit: {{ produit.id }},
|
idProduit: {{ product.id }},
|
||||||
nonce: "{{ nonce_wc }}",
|
nonce: "{{ nonce_wc }}",
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
{{ include('parts/pages/produit/informations-produit.twig') }}
|
{{ include('parts/pages/produit/informations-produit.twig') }}
|
||||||
|
|
||||||
{# Produits de la même Collection (Produits similaires) #}
|
{# Produits de la même Collection (Produits similaires) #}
|
||||||
{% if produit.collection != '' %}
|
{% if product.collection != '' and same_collection_products|length > 0 %}
|
||||||
{{ include('parts/pages/produit/produits-similaires.twig') }}
|
{{ include('parts/pages/produit/produits-similaires.twig') }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock contenu %}
|
{% endblock contenu %}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue