Compare commits

...

12 commits

686 changed files with 1418 additions and 959 deletions

View file

@ -24,15 +24,13 @@ return new Config()
'blank_line_after_namespace' => true,
'blank_lines_before_namespace' => ['min_line_breaks' => 1, 'max_line_breaks' => 2],
'cast_spaces' => true,
'class_attributes_separation' => [
'elements' => [
'class_attributes_separation' => ['elements' => [
'case' => 'none',
'const' => 'none',
'method' => 'one',
'property' => 'one',
'trait_import' => 'none',
],
],
]],
'class_reference_name_casing' => true,
'clean_namespace' => true,
'combine_consecutive_issets' => true,
@ -56,7 +54,11 @@ return new Config()
'full_opening_tag' => true,
'fully_qualified_strict_types' => ['import_symbols' => true],
'function_to_constant' => true,
'global_namespace_import' => ['import_classes' => true, 'import_constants' => true, 'import_functions' => true],
'global_namespace_import' => [
'import_classes' => true,
'import_constants' => true,
'import_functions' => true,
],
'heredoc_to_nowdoc' => true,
'integer_literal_case' => true,
'lambda_not_used_import' => true,
@ -75,7 +77,11 @@ return new Config()
'multiline_comment_opening_closing' => true,
'native_constant_invocation' => true,
'native_function_casing' => true,
'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'namespaced', 'strict' => true],
'native_function_invocation' => [
'include' => ['@compiler_optimized'],
'scope' => 'namespaced',
'strict' => true,
],
'native_type_declaration_casing' => true,
'new_expression_parentheses' => true,
'no_alias_functions' => ['sets' => ['@all']],
@ -95,8 +101,7 @@ return new Config()
'no_trailing_comma_in_singleline' => true,
'no_trailing_whitespace_in_comment' => true,
'no_unneeded_braces' => ['namespaces' => true],
'no_unneeded_control_parentheses' => [
'statements' => [
'no_unneeded_control_parentheses' => ['statements' => [
'break',
'clone',
'continue',
@ -107,8 +112,7 @@ return new Config()
'switch_case',
'yield',
'yield_from',
],
],
]],
'no_unneeded_final_method' => true,
'no_unneeded_import_alias' => true,
'no_unreachable_default_argument_value' => true,
@ -139,9 +143,11 @@ return new Config()
'pow_to_exponentiation' => true,
'protected_to_private' => true,
'psr_autoloading' => true,
'random_api_migration' => [
'replacements' => ['getrandmax' => 'mt_getrandmax', 'rand' => 'mt_rand', 'srand' => 'mt_srand'],
],
'random_api_migration' => ['replacements' => [
'getrandmax' => 'mt_getrandmax',
'rand' => 'mt_rand',
'srand' => 'mt_srand',
]],
'return_assignment' => true,
'self_accessor' => true,
'self_static_accessor' => true,
@ -216,8 +222,7 @@ return new Config()
// The type of @return annotations of methods returning a reference to itself must the configured one.
'phpdoc_return_self_reference' => true,
// Scalar types should always be written in the same form. int not integer, bool not boolean, float not real or double.
'phpdoc_scalar' => [
'types' => [
'phpdoc_scalar' => ['types' => [
'boolean',
'callback',
'double',
@ -227,8 +232,7 @@ return new Config()
'no-return',
'real',
'str',
],
],
]],
// Annotations in PHPDoc should be grouped together so that annotations of the same type immediately follow each other. Annotations of a different type are separated by a single blank line.
'phpdoc_separation' => [
'groups' => [

View file

@ -1,11 +1,5 @@
{
"$schema": "./phpactor.schema.json",
"indexer.exclude_patterns": [
"/var/cache/**/*",
"/vendor/**/tests/**/*",
"/vendor/**/Tests/**/*",
"/vendor/composer/**/*"
],
"language_server.diagnostic_outsource_timeout": 5,
"language_server.diagnostics_on_save": true,
"language_server.diagnostics_on_update": true,

19
.phpantom.toml Normal file
View file

@ -0,0 +1,19 @@
# :schema: https://github.com/AJenbo/phpantom_lsp/raw/main/config-schema.json
[php]
# Override the detected PHP version (default: inferred from composer.json, or 8.5).
# version = "8.5"
#
[diagnostics]
extra-arguments = true
# Report member access on subjects whose type could not be resolved.
# Useful for discovering gaps in type coverage. Off by default.
unresolved-member-access = false
[indexing]
# How PHPantom discovers classes across the workspace.
# "composer" (default) - use Composer classmap, self-scan on fallback
# "self" - always self-scan, ignore Composer classmap
# "none" - no proactive scanning, Composer classmap only
strategy = "composer"

View file

@ -9,6 +9,7 @@
"!oxc",
"!oxfmt",
"!oxlint",
"!phptools",
"!prettier",
"!tailwindcss-language-server",
"!tsgo",

View file

@ -1,33 +1,17 @@
import { defineConfig, devices } from "@playwright/test";
import { defineConfig, devices, PlaywrightTestConfig } from "@playwright/test";
export default defineConfig({
const playwrightConfig: PlaywrightTestConfig = defineConfig({
fullyParallel: true,
projects: [
{
name: "desktop-chromium-1920",
use: { ...devices["Desktop Chrome"], viewport: { height: 1080, width: 1920 } },
},
// {
// name: "desktop-chromium-1536",
// use: { ...devices["Desktop Chrome"], viewport: { width: 1536, height: 864 } },
// },
// {
// name: "desktop-chromium-1366",
// use: { ...devices["Desktop Chrome"], viewport: { width: 1366, height: 768 } },
// },
{
name: "desktop-firefox-1920",
use: { ...devices["Desktop Firefox"], viewport: { height: 1080, width: 1920 } },
},
// {
// name: "desktop-firefox-1536",
// use: { ...devices["Desktop Firefox"], viewport: { width: 1536, height: 864 } },
// },
// {
// name: "desktop-firefox-1366",
// use: { ...devices["Desktop Firefox"], viewport: { width: 1366, height: 768 } },
// },
// {
// name: "tablet-chromium-portrait",
// use: { ...devices["Galaxy Tab S9"] },
// },
@ -49,7 +33,6 @@ export default defineConfig({
testDir: "../tests",
timeout: 10_000,
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "https://haikuatelier.gcch.local",
clientCertificates: [
{
@ -61,5 +44,7 @@ export default defineConfig({
ignoreHTTPSErrors: true,
trace: "retry-with-trace",
},
workers: "100%",
workers: "50%",
});
export default playwrightConfig;

View file

@ -3,8 +3,7 @@
"authors": [],
"autoload": {
"psr-4": {
"HaikuAtelier\\": "web/app/themes/haiku-atelier-2024/src/inc/",
"WooCommerce\\": "web/app/plugins/woocommerce"
"HaikuAtelier\\": "web/app/themes/haiku-atelier-2024/src/inc/"
}
},
"config": {

View file

@ -8,6 +8,7 @@ declare(strict_types=1);
use Roots\WPConfig\Config;
use function base64_encode;
use function Env\env;
Config::define('SAVEQUERIES', true);
@ -25,6 +26,10 @@ Config::define('DISALLOW_FILE_MODS', false);
// WooCommerce
Config::define('WOOCOMMERCE_API_CONSUMER_KEY', env('WOOCOMMERCE_API_CONSUMER_KEY'));
Config::define('WOOCOMMERCE_API_CONSUMER_SECRET', env('WOOCOMMERCE_API_CONSUMER_SECRET'));
Config::define(
'WOOCOMMERCE_API_AUTH_STRING',
base64_encode(env('WOOCOMMERCE_API_CONSUMER_KEY') . ':' . env('WOOCOMMERCE_API_CONSUMER_SECRET')),
);
// Stripe
Config::define('STRIPE_API_SECRET', env('STRIPE_API_SECRET'));

View file

@ -8,6 +8,7 @@ declare(strict_types=1);
use Roots\WPConfig\Config;
use function base64_encode;
use function Env\env;
Config::define('WP_DEBUG', true);
@ -20,6 +21,10 @@ Config::define('DISALLOW_FILE_MODS', false);
Config::define('WOOCOMMERCE_API_CONSUMER_KEY', env('WOOCOMMERCE_API_CONSUMER_KEY'));
Config::define('WOOCOMMERCE_API_CONSUMER_SECRET', env('WOOCOMMERCE_API_CONSUMER_SECRET'));
Config::define(
'WOOCOMMERCE_API_AUTH_STRING',
base64_encode(env('WOOCOMMERCE_API_CONSUMER_KEY') . ':' . env('WOOCOMMERCE_API_CONSUMER_SECRET')),
);
// Stripe
Config::define('STRIPE_API_SECRET', env('STRIPE_API_SECRET'));

View file

@ -8,8 +8,13 @@ declare(strict_types=1);
use Roots\WPConfig\Config;
use function base64_encode;
use function Env\env;
Config::define('DISALLOW_INDEXING', true);
Config::define('WOOCOMMERCE_API_CONSUMER_KEY', env('WOOCOMMERCE_API_CONSUMER_KEY'));
Config::define('WOOCOMMERCE_API_CONSUMER_SECRET', env('WOOCOMMERCE_API_CONSUMER_SECRET'));
Config::define(
'WOOCOMMERCE_API_AUTH_STRING',
base64_encode(env('WOOCOMMERCE_API_CONSUMER_KEY') . ':' . env('WOOCOMMERCE_API_CONSUMER_SECRET')),
);

View file

@ -90,6 +90,11 @@ watch-css:
build-js:
aube x vite build --config "cfg/vite.config.ts"
# Compile TypeScript à chaque changement de fichier.
[group('js')]
watch-js:
@watchexec -w "web/app/themes/haiku-atelier-2024/src/scripts" -w "web/app/themes/haiku-atelier-2024/src/scripts-effect" -- just build-js treefmt
# Compile tout.
[group('css')]
[group('js')]
@ -98,11 +103,6 @@ build-all:
@just build-js
@just format
# Compile TypeScript à chaque changement de fichier.
[group('js')]
watch-js:
aube x vite build --config "cfg/vite.config.ts" --watch
# Vérifie le code TypeScript avec des analyseurs statiques.
[group('js')]
[group('qualité')]
@ -170,3 +170,6 @@ pull-images:
export_production_db:
fish "scripts/déclenche-sauvegarde-bdd-production.fish"
ui_tests:
aube x playwright test --config cfg/playwright.config.ts --ui

File diff suppressed because it is too large Load diff

View file

@ -36,6 +36,7 @@ threads = 0
no-else-clause = { enabled = false }
[analyzer]
allow-implicit-pipe-callable-types = false
allow-possibly-undefined-array-keys = false
allow-side-effects-in-conditions = true
analyze-dead-code = true

2
mise.toml Normal file
View file

@ -0,0 +1,2 @@
[tools]
"github:AJenbo/phpantom_lsp" = "latest"

View file

@ -236,6 +236,21 @@
"default": 1610612736,
"description": "Ensure that PHP has a memory_limit of at least this amount in bytes"
},
"core.project_config_candidates": {
"default": [],
"description": "(internal) list of potential project-level configuration files"
},
"core.trust": {
"default": {
"path": null,
"trust": []
},
"description": "(internal) map of trusted project directories"
},
"core.trusted": {
"default": false,
"description": "(internal) if the configuration is trusted"
},
"file_path_resolver.app_name": {
"default": "phpactor",
"description": null
@ -278,7 +293,8 @@
"default": [
"/vendor/**/tests/**/*",
"/vendor/**/Tests/**/*",
"/vendor/composer/**/*"
"/vendor/composer/**/*",
"/vendor/rector/rector/stubs-rector"
],
"description": "Glob patterns to exclude while indexing",
"type": [
@ -316,6 +332,13 @@
"string"
]
},
"indexer.max_filesize_to_index": {
"default": 1000000,
"description": "Files larger than this will not be indexed. (Size in bytes)",
"type": [
"integer"
]
},
"indexer.poll_time": {
"default": 5000,
"description": "For polling indexers only: the time, in milliseconds, between polls (e.g. filesystem scans)",
@ -337,6 +360,13 @@
"boolean"
]
},
"indexer.search_include_patterns": {
"default": [],
"description": "When searching the index exclude records whose fully qualified names match any of these regex patterns (use to exclude suggestions from search results). Namespace separators must be escaped as `\\\\\\\\` for example `^Foo\\\\\\\\` to include all namespaces whose first segment is `Foo`",
"type": [
"object"
]
},
"indexer.stub_paths": {
"default": [],
"description": "Paths to external folders to index. They will be indexed only once, if you want to take any changes into account you will have to reindex your project manually.",
@ -362,6 +392,10 @@
"default": [],
"description": "List of paths to exclude from diagnostics, e.g. `vendor/**/*`"
},
"language_server.diagnostic_ignore_codes": {
"default": [],
"description": "Ignore diagnostics that have the codes listed here, e.g. [\"fix_namespace_class_name\"]. The codes match those shown in the LSP client."
},
"language_server.diagnostic_outsource": {
"default": true,
"description": "If applicable diagnostics should be \"outsourced\" to a different process"
@ -389,6 +423,10 @@
"default": true,
"description": "Perform diagnostics when the text document is updated"
},
"language_server.enable_trust_check": {
"default": true,
"description": "Check to see if project path is trusted before loading configurations from it"
},
"language_server.enable_workspace": {
"default": true,
"description": "If workspace management / text synchronization should be enabled (this isn't required for some language server implementations, e.g. static analyzers)"
@ -450,6 +488,17 @@
"boolean"
]
},
"language_server_highlight.enabled": {
"default": true,
"description": "Enable or disable the highlighter (can be expensive on large documents)"
},
"language_server_indexer.optimiser_timeout": {
"default": 3600,
"description": "Optimise the index every N seconds",
"type": [
"integer"
]
},
"language_server_indexer.reindex_timeout": {
"default": 300,
"description": "Unconditionally reindex modified files every N seconds"
@ -467,15 +516,17 @@
},
"language_server_php_cs_fixer.env": {
"default": {
"PHP_CS_FIXER_IGNORE_ENV": true,
"XDEBUG_MODE": "off"
},
"description": "Environment for PHP CS Fixer (e.g. to set PHP_CS_FIXER_IGNORE_ENV)"
"description": "Environment for PHP CS Fixer"
},
"language_server_php_cs_fixer.show_diagnostics": {
"default": true,
"description": "Whether PHP CS Fixer diagnostics are shown"
},
"language_server_php_cs_fixer.version": {
"description": "Arbitrary version (if not provided, phpactor tries to detect it - only to run it on unsupported PHP versions)"
},
"language_server_phpstan.bin": {
"default": "%project_root%/vendor/bin/phpstan",
"description": "Path to the PHPStan executable"
@ -483,12 +534,24 @@
"language_server_phpstan.config": {
"description": "Override the PHPStan configuration file"
},
"language_server_phpstan.editor_mode": {
"default": false,
"description": "DEPRECATED. Editor mode of Phpstan is used automatically when it's supported."
},
"language_server_phpstan.level": {
"description": "Override the PHPStan level"
},
"language_server_phpstan.mem_limit": {
"description": "Override the PHPStan memory limit"
},
"language_server_phpstan.severity": {
"default": 1,
"description": "Severity at which PHPStan diagnostics should be reported. Ranges from 1 (error) to 4 (hint)."
},
"language_server_phpstan.tmp_file_disabled": {
"default": false,
"description": "Disable the use of temporary files when. This prevents as-you-type diagnostics, but ensures paths in phpstan config are respected. See https://github.com/phpactor/phpactor/issues/2763"
},
"language_server_psalm.bin": {
"default": "%project_root%/vendor/bin/psalm",
"description": "Path to psalm if different from vendor/bin/psalm",
@ -496,6 +559,13 @@
"string"
]
},
"language_server_psalm.config": {
"default": "",
"description": "Path to psalm config. Like %project_root%/psalm.xml",
"type": [
"string"
]
},
"language_server_psalm.error_level": {
"description": "Override level at which Psalm should report errors (lower => more errors)"
},
@ -527,6 +597,10 @@
"boolean"
]
},
"language_server_reference_finder.soft_timeout": {
"default": 10,
"description": "Interupt and ask for confirmation to continue after this timeout (in seconds)"
},
"language_server_reference_reference_finder.reference_timeout": {
"default": 60,
"description": "Stop searching for references after this time (in seconds) has expired"
@ -658,6 +732,10 @@
"default": "%project_root%/var/cache/dev/App_KernelDevDebugContainer.xml",
"description": "Path to the Symfony container XML dump file"
},
"worse_reflection.additive_stubs": {
"default": [],
"description": "Additive stubs files relative to the project root. These stubs augment existing defininitions."
},
"worse_reflection.cache_dir": {
"default": "%cache%/worse-reflection",
"description": "Cache directory for stubs"

View file

@ -1,3 +1,4 @@
/** @effect-diagnostics asyncFunction:skip-file */
import type { APIRequestContext, Locator, Page, Response } from "@playwright/test";
import { expect, test } from "@playwright/test";
import type { WCV3Products } from "../../web/app/themes/haiku-atelier-2024/src/scripts/lib/types/api/v3/products";
@ -10,7 +11,7 @@ test("can scroll to the end of the grid", async ({ page }): Promise<void> => {
await scrollToGridsEnd(page);
});
test.skip("can access all Products' pages", async ({ page, request }): Promise<void> => {
test("can access all Products' pages", async ({ page, request }): Promise<void> => {
await page["goto"]("https://haikuatelier.gcch.local/shop/");
const links = await getAllProductsLinks(page, request);

View file

@ -17,7 +17,6 @@ use WC_Product;
use function add_action;
use function array_map;
use function assert;
use function base64_encode;
use function is_string;
use function wc_get_products;
use function wp_create_nonce;
@ -33,12 +32,7 @@ $products = array_map(callback: Product::from_wc_product(...), array: $wc_produc
$context['products'] = $products;
// Injecte les états initiaux des données du Produit sous forme de JSON dans le contexte.
$page_states = [
'nonce' => wp_create_nonce('wc_store_api'),
'authString' => base64_encode(
Config::get('WOOCOMMERCE_API_CONSUMER_KEY') . ':' . Config::get('WOOCOMMERCE_API_CONSUMER_SECRET'),
),
]
$page_states = ['authString' => Config::get('WOOCOMMERCE_API_AUTH_STRING'), 'nonce' => wp_create_nonce('wc_store_api')]
|> wp_json_encode(...);
assert(is_string($page_states));
$context['page_states'] = $page_states;

View file

@ -292,9 +292,12 @@ button {
button.bouton-case-pleine {
width: 100%;
height: 100%;
font-style: italic;
text-transform: uppercase;
letter-spacing: var(--espacement-inter-lettres-etendu-m);
}
@media (hover: hover) {
button.bouton-case-pleine:hover {
button.bouton-case-pleine:not(:disabled):hover {
color: var(--couleur-noir);
background: var(--arriere-plan-points);
}
@ -314,7 +317,7 @@ button.bouton-inverse:disabled {
color: var(--couleur-blanc);
}
@media (hover: hover) {
button.bouton-inverse:hover {
button.bouton-inverse:not(:disabled):hover {
color: var(--couleur-noir);
background: var(--arriere-plan-points);
}
@ -344,13 +347,13 @@ button.bouton-retour-haut[data-actif] {
opacity: 50%;
}
@media (hover: hover) {
button.bouton-retour-haut[data-actif]:hover {
button.bouton-retour-haut[data-actif]:not(:disabled):hover {
opacity: 100%;
background: var(--couleur-gris-fond);
}
}
@media (hover: hover) {
button:hover {
button:not(:disabled):hover {
color: var(--couleur-blanc);
background: var(--couleur-gris-fonce);
}
@ -462,8 +465,8 @@ input[type="checkbox"], input[type="radio"] {
transition: 0.2s background;
}
input[type="checkbox"]:checked, input[type="radio"]:checked {
color: var(--couleur-blanc);
background: var(--couleur-gris-fonce);
color: var(--couleur-noir);
background: var(--arriere-plan-points);
}
input[type="checkbox"]:checked::before, input[type="radio"]:checked::before {
content: "x";
@ -492,7 +495,7 @@ input[type="radio"] + label {
}
@media (hover: hover) {
input[type="checkbox"]:hover, input[type="radio"]:hover {
background: var(--couleur-gris-fonce);
background: var(--arriere-plan-points);
}
}
@ -1474,8 +1477,6 @@ body:has(#menu-mobile:not([aria-hidden="true"])) {
}
.details-produit__actions button {
padding: var(--section-marges-internes);
font-style: italic;
text-transform: uppercase;
}
@media (hover: hover) {
.details-produit__actions button:not([disabled]):hover {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,12 +4,7 @@
text-align: center;
}
#page-boutique .actions button {
height: initial;
margin: auto;
padding: var(--espace-xl) 0;
font-style: italic;
text-transform: uppercase;
letter-spacing: var(--espacement-inter-lettres-etendu-m);
}
/* # sourceMappingURL=page-boutique.css.map */

View file

@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["../../../src/sass/pages/page-boutique.scss"],"names":[],"mappings":"AAGE;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA","file":"page-boutique.css"}
{"version":3,"sourceRoot":"","sources":["../../../src/sass/pages/page-boutique.scss"],"names":[],"mappings":"AAGE;EACE;EACA;EACA;;AAEA;EACE","file":"page-boutique.css"}

View file

@ -1 +1 @@
#page-boutique .actions{text-align:center;align-content:center;width:100%}#page-boutique .actions button{height:initial;padding:var(--espace-xl) 0;text-transform:uppercase;letter-spacing:var(--espacement-inter-lettres-etendu-m);margin:auto;font-style:italic}
#page-boutique .actions{text-align:center;align-content:center;width:100%}#page-boutique .actions button{padding:var(--espace-xl) 0}

View file

@ -385,11 +385,23 @@
text-align: center;
text-transform: uppercase;
letter-spacing: 2px;
background: var(--couleur-noir);
border-block: 1px solid var(--couleur-noir);
border-block-end: 0;
background: var(--arriere-plan-points);
}
#panneau-informations-client .panneau__pied-de-page:has(button[disabled]) {
background: var(--couleur-gris-fond);
}
#panneau-informations-client .panneau__pied-de-page button {
padding: var(--espace-l) 0;
}
@media (hover: hover) {
#panneau-informations-client .panneau__pied-de-page button:not([disabled]):hover {
font-weight: 600;
color: var(--couleur-blanc);
background: var(--couleur-gris-fonce-fond);
}
}
@media (width <= 500px) {
#panneau-informations-client .panneau__formulaires {
padding: var(--espace-xl) 0;

View file

@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["../../../src/sass/layouts/_panneau-panier.scss","../../../src/sass/layouts/_panneau-informations-client.scss","../../../src/sass/pages/page-panier.scss"],"names":[],"mappings":";AAEA;EACE;;AAEA;EACE;;AAIF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGA;EACE;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;AAEA;;AACA;EACE;EACA;EACA;EACA;EACA;;AAbJ;AAgBE;AAAA;AAAA;AAAA;;AAIA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AA9BN;AAkCE;;AACA;EACE;EACA;EACA;EACA;EACA;;AAxCJ;AA2CE;;AACA;EACE;EACA;EACA;EACA;EACA;;AAOR;EACE;EACA;EACA;EACA;AAEA;AAAA;AAAA;AAAA;;AAIA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;AAEA;AAAA;AAAA;;AAGA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAKA;EACE;;AAEA;EACE;;AAMR;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAMJ;EACE;EACA;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;IACE;;EAGF;IACE;;EAEA;IACE;;EAIJ;IACE;;EAGE;IACE;;EAKN;IACE;;;;ACpRN;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIF;EAEE;EACA;EACA;EAGA;EAEA;EACA;;AAEA;EACE;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAMN;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGE;EACE;;AAMR;EACE;EACA;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;IACE;;;;ACjKN;AAAA;AAAA;AAAA;AAIA;AACE;EACA;AAEA;EACA;EAEA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAKN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;;AAKF;EACE;;AAGF;EACE","file":"page-panier.css"}
{"version":3,"sourceRoot":"","sources":["../../../src/sass/layouts/_panneau-panier.scss","../../../src/sass/layouts/_panneau-informations-client.scss","../../../src/sass/pages/page-panier.scss"],"names":[],"mappings":";AAEA;EACE;;AAEA;EACE;;AAIF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGA;EACE;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;AAEA;;AACA;EACE;EACA;EACA;EACA;EACA;;AAbJ;AAgBE;AAAA;AAAA;AAAA;;AAIA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AA9BN;AAkCE;;AACA;EACE;EACA;EACA;EACA;EACA;;AAxCJ;AA2CE;;AACA;EACE;EACA;EACA;EACA;EACA;;AAOR;EACE;EACA;EACA;EACA;AAEA;AAAA;AAAA;AAAA;;AAIA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;AAEA;AAAA;AAAA;;AAGA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAKA;EACE;;AAEA;EACE;;AAMR;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAMJ;EACE;EACA;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;IACE;;EAGF;IACE;;EAEA;IACE;;EAIJ;IACE;;EAGE;IACE;;EAKN;IACE;;;;ACpRN;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIF;EAEE;EACA;EACA;EAGA;EAEA;EACA;;AAEA;EACE;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAMN;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGE;EACE;;AAMR;EACE;EACA;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;;AAGF;EACE;;AAGE;EACE;IACE;IACA;IACA;;;AAOV;EACE;IACE;;;;AClLN;AAAA;AAAA;AAAA;AAIA;AACE;EACA;AAEA;EACA;EAEA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAKN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;;AAKF;EACE;;AAGF;EACE","file":"page-panier.css"}

File diff suppressed because one or more lines are too long

View file

@ -95,8 +95,7 @@ final class StarterSite extends Site {
$url_courante = URLHelper::get_current_url();
$context['page_courante'] = $url_courante;
$context['est_page_tous_produits'] = preg_match(pattern: '/(\bshop\b)/', subject: $url_courante);
$context['est_page_boutique'] =
preg_match(pattern: '/(\bshop\b)/', subject: $url_courante)
$context['est_page_boutique'] = preg_match(pattern: '/(\bshop\b)/', subject: $url_courante)
|| preg_match(pattern: '/(\bproduct\b)/', subject: $url_courante)
|| preg_match(pattern: '/(\bproduct-category\b)/', subject: $url_courante);

View file

@ -26,11 +26,13 @@ final readonly class Attribute {
public static function new(WC_Product_Attribute $attribute): self {
$name = wc_attribute_label($attribute->get_name());
$slug = $attribute->get_name();
/** @var list<WP_Term> */
$terms = $attribute->get_terms() ?? [];
/** @var list<AttributeOption> */
$options = Arr::map($terms, AttributeOption::new(...))
|> (static fn($options) => Arr::sort($options, static fn($attribute) => $attribute->id))
|> (static fn($options) => Arr::sort($options, static fn($attribute) => $attribute->name))
|> array_values(...);
return new self(name: $name, slug: $slug, options: $options);

View file

@ -18,9 +18,12 @@ use function Psl\Option\from_nullable;
use function wc_get_products;
use function wpautop;
/**
* Représente un **Produit** (selon _WooCommerce_) avec de nombreuses données d'intérêt pour les opérations courantes.
*/
final readonly class Product {
/**
* @param list<Attribute> $attributes
* @param list<Attribute> $attributes La liste des `Attribute` appliquées.
* @param list<string> $left_column_photos
* @param list<string> $right_column_photos
* @param array<ProductVariation> $variations

View file

@ -58,6 +58,9 @@ function retire_styles_core_block(): void {
add_filter('async_update_translation', '__return_false');
add_filter('auto_update_translation', '__return_false');
// Désactive la génération automatique de sitemaps.
add_filter('wp_sitemaps_enabled', '__return_false');
add_action('init', desactive_wpautop(...));
add_filter('tiny_mce_before_init', desactive_transformation_contenu_tinymce(...));
add_filter('upload_mimes', autorise_import_svg_mediatheque(...));

View file

@ -131,7 +131,8 @@ function retire_merdes_wc(): void {
*/
function genere_balises_img_dans_produit_dans_reponse_rest(
WP_REST_Response $response,
mixed $_product,
WC_Data $_product,
WP_REST_Request $_request,
): WP_REST_Response {
// Vérifie que la Réponse a des données
if (empty($response->data)) {
@ -169,37 +170,34 @@ function genere_balises_img_dans_produit_dans_reponse_rest(
return $response;
}
add_filter('woocommerce_rest_prepare_product_object', 'genere_balises_img_dans_produit_dans_reponse_rest', 10, 2);
/**
* TODO.
*/
function genere_prix_maximal_produit_variable_dans_reponse_rest(
WP_REST_Response $reponse,
WC_Data $_produit,
WP_REST_Response $response,
WC_Data $_product,
WP_REST_Request $_request,
): WP_REST_Response {
// Vérifie que la Réponse a des données
if (empty($reponse->data)) {
return $reponse;
if (empty($response->data)) {
return $response;
}
// Si le Produit n'est pas Variable, assigner le prix du Produit comme prix maximal
if ('variable' !== $reponse->data['type']) {
$reponse->data['prix_maximal'] = $reponse->data['regular_price'];
if ('variable' !== $response->data['type']) {
$response->data['prix_maximal'] = $response->data['regular_price'];
return $reponse;
return $response;
}
// Assigne le prix de la Variation la plus chère dans la Réponse
$reponse->data['prix_maximal'] = collect($reponse->data['variations'])
$response->data['prix_maximal'] = collect($response->data['variations'])
->map(wc_get_product(...))
->map(static fn($p) => $p->get_price())->max();
return $reponse;
return $response;
}
add_filter('woocommerce_rest_prepare_product_object', 'genere_prix_maximal_produit_variable_dans_reponse_rest', 10, 2);
/**
* Retire la propagande commerciale de WooCommerce du menu.
*/
@ -216,3 +214,8 @@ add_action('init', 'retire_script_galerie');
add_action('template_redirect', 'retire_merdes_wc');
add_action('wp_enqueue_scripts', 'dequeue_woocommerce_styles_scripts');
add_filter('woocommerce_enqueue_styles', '__return_empty_array');
add_filter('woocommerce_rest_prepare_product_object', 'genere_balises_img_dans_produit_dans_reponse_rest', 10, 3);
add_filter('woocommerce_rest_prepare_product_object', 'genere_prix_maximal_produit_variable_dans_reponse_rest', 10, 3);
// DEBUG
// add_filter('woocommerce_store_api_disable_nonce_check', '__return_true');

View file

@ -84,8 +84,7 @@ final readonly class Resource {
static fn($tableau): array => array_map(array: $tableau, callback: static fn($chemin_format): array => [
'format' => pathinfo((string) $chemin_format)['extension'],
'taille' => filesize($chemin_format),
'url' =>
pathinfo($url)['dirname']
'url' => pathinfo($url)['dirname']
. '/'
. pathinfo($url)['filename']
. '.'

View file

@ -31,13 +31,16 @@ button {
/* Particularismes. */
// Bouton prenant toute l'espace disponible.
// Bouton occuputant tout l'espace disponible.
&.bouton-case-pleine {
width: 100%;
height: 100%;
font-style: italic;
text-transform: uppercase;
letter-spacing: var(--espacement-inter-lettres-etendu-m);
@media (hover: hover) {
&:hover {
&:not(:disabled):hover {
color: var(--couleur-noir);
background: var(--arriere-plan-points);
}
@ -63,7 +66,7 @@ button {
}
@media (hover: hover) {
&:hover {
&:not(:disabled):hover {
color: var(--couleur-noir);
background: var(--arriere-plan-points);
}
@ -97,7 +100,7 @@ button {
opacity: 50%;
@media (hover: hover) {
&:hover {
&:not(:disabled):hover {
opacity: 100%;
background: var(--couleur-gris-fond);
}
@ -106,7 +109,7 @@ button {
}
@media (hover: hover) {
&:hover {
&:not(:disabled):hover {
color: var(--couleur-blanc);
background: var(--couleur-gris-fonce);
}

View file

@ -66,8 +66,8 @@ input[type="checkbox"], input[type="radio"] {
transition: 0.2s background;
&:checked {
color: var(--couleur-blanc);
background: var(--couleur-gris-fonce);
color: var(--couleur-noir);
background: var(--arriere-plan-points);
// TODO: Utiliser un SVG plutôt qu'un « x » ?
&::before {
@ -98,7 +98,7 @@ input[type="checkbox"], input[type="radio"] {
@media (hover: hover) {
&:hover {
background: var(--couleur-gris-fonce);
background: var(--arriere-plan-points);
}
}
}

View file

@ -216,17 +216,15 @@
background: var(--arriere-plan-points);
transition: 0.2s background;
// Indique un Bouton désactivé avec un fond gris en-dessous du fond à motif
// Indique un Bouton désactivé avec un fond gris en-dessous du fond à motif.
&:has(button[disabled]) {
background: var(--couleur-gris-fond);
}
button {
padding: var(--section-marges-internes);
font-style: italic;
text-transform: uppercase;
// Change la casse de la police au survol quand le Bouton n'est pas désactivé
// Change la casse de la police au survol quand le Bouton n'est pas désactivé.
&:not([disabled]) {
@media (hover: hover) {
&:hover {

View file

@ -155,10 +155,27 @@
text-align: center;
text-transform: uppercase;
letter-spacing: 2px;
background: var(--couleur-noir);
border-block: 1px solid var(--couleur-noir);
border-block-end: 0;
background: var(--arriere-plan-points);
// Indique un Bouton désactivé avec un fond gris en-dessous du fond à motif
&:has(button[disabled]) {
background: var(--couleur-gris-fond);
}
button {
padding: var(--espace-l) 0;
&:not([disabled]) {
@media (hover: hover) {
&:hover {
font-weight: 600;
color: var(--couleur-blanc);
background: var(--couleur-gris-fonce-fond);
}
}
}
}
}

View file

@ -7,12 +7,7 @@
text-align: center;
button {
height: initial;
margin: auto;
padding: var(--espace-xl) 0;
font-style: italic;
text-transform: uppercase;
letter-spacing: var(--espacement-inter-lettres-etendu-m);
}
}
}

View file

@ -1,4 +1,4 @@
import { Console, Context, Effect, Layer, Match, pipe, References, Schedule, Schema, SchemaIssue } from "effect";
import { Console, Context, Effect, Layer, Match, pipe, Schedule, Schema, SchemaIssue } from "effect";
import type { SchemaError } from "effect/Schema";
import type {
HttpClientError,
@ -13,6 +13,7 @@ import { HttpClientErrorSchema } from "effect/unstable/http/HttpClientError";
import type { CartProduct, GetProducts } from "../schemas/api.ts";
import { Product } from "../schemas/api.ts";
import { WooCommerceCart } from "../schemas/cart.ts";
import { AppConfig, Provider } from "./config.ts";
/** Le nombre maximal d'essais pour une Requête. */
const MAX_RETRIES = 3;
@ -81,6 +82,8 @@ class APIClient extends Context.Service<APIClient>()("haikuatelier.fr/APIClient"
}),
);
const config = yield* AppConfig.parse(Provider);
const matchAPIError = (error: HttpClientError.HttpClientError | SchemaError): APIError => {
if (error._tag === "SchemaError") {
return new APIRequestError({
@ -126,9 +129,6 @@ class APIClient extends Context.Service<APIClient>()("haikuatelier.fr/APIClient"
Effect.flatMap(HttpClientResponse.schemaBodyJson(WooCommerceCart)),
Effect.mapError(error => matchAPIError(error)),
Effect.tapError(error => printErrorAsSuccinctMessage(error)),
// Effect.catchTag("APIResponseError", error => {
// if (error.cause.)
// }),
);
return response;
@ -142,8 +142,8 @@ class APIClient extends Context.Service<APIClient>()("haikuatelier.fr/APIClient"
HttpClientRequest.setHeader("Nonce", nonce),
// TODO: Utiliser l'environnement
HttpClientRequest.basicAuth(
"ck_ed966a2265099a6dfe9915db692cbd2450cceed6",
"cs_a046c91647af95188a3e39a736ebe02f2024e430",
config.WOOCOMMERCE_API_CONSUMER_KEY,
config.WOOCOMMERCE_API_CONSUMER_SECRET,
),
// Le corps de la Requête a été validée en amont, on peut utiliser Unsafe.
HttpClientRequest.setUrlParams(queryParams),

View file

@ -0,0 +1,17 @@
/**
* `Config<A>` décrit la Configuration nécessaire. `ConfigProvider` est le _backend_ qui la charge. Par défault, la Configuration est lue depuis les variables d'environnement, mais d'autres sources peuvent être utilisées.
*/
import { Config, ConfigProvider } from "effect";
const AppConfig = Config.all({
WOOCOMMERCE_API_CONSUMER_KEY: Config.redacted("WOOCOMMERCE_API_CONSUMER_KEY"),
WOOCOMMERCE_API_CONSUMER_SECRET: Config.redacted("WOOCOMMERCE_API_CONSUMER_SECRET"),
});
const Provider = ConfigProvider.fromUnknown({
WOOCOMMERCE_API_CONSUMER_KEY: "ck_329c944b248aa7cc837c7662d9c6e09d638802df",
WOOCOMMERCE_API_CONSUMER_SECRET: "cs_5687d0c694bd519b231145afa7177c0c987f7155",
});
export { AppConfig, Provider };

View file

@ -11,7 +11,7 @@ const ShopPageRuntime = ManagedRuntime.make(
Layer.provideMerge(ShopPageMessages.Live),
Layer.provideMerge(ShopPageElements.Live),
Layer.provide(APIClient.Live),
Layer.tapError(error => Console.error("ProductPageRuntime", "Impossible de créer le Layer :", error)),
Layer.tapError(error => Console.error("ProductPageRuntime", "Impossible de créer le Layer :", error.message)),
),
);
export default ShopPageRuntime;

View file

@ -1,6 +1,5 @@
import {
Array as FxArray,
Console,
Context,
Effect,
Layer,
@ -118,8 +117,6 @@ class ShopPageDOM extends Context.Service<ShopPageDOM>()("haikuatelier.fr/Shop/S
};
const onMoreProductsWantedHandler = Effect.fn("onMoreProductsWantedHandler")(function*() {
yield* Console.debug("onMoreProductsWantedHandler");
/** Le numéro de page souhaitée. */
const newPageNumber = yield* Ref.updateAndGet(PageNumber, pageNumber => pageNumber + 1);
/** L'ID de la Catégorie de Produits affichée dans la page si elle existe. */
@ -137,7 +134,6 @@ class ShopPageDOM extends Context.Service<ShopPageDOM>()("haikuatelier.fr/Shop/S
yield* SubscriptionRef.set(ShowMoreButtonText, "Getting Products...");
const newProducts = yield* API.GetProducts(nonce, requestBody);
yield* Console.debug("onMoreProductsWantedHandler", newProducts);
// Rétablis le texte du Bouton et réactive les interactions.
yield* SubscriptionRef.set(ShowMoreButtonText, "Show more");

View file

@ -9,17 +9,14 @@ import ShopPageElements from "./page-boutique/service-elements.ts";
import ShopPageMessages from "./page-boutique/service-messages.ts";
document.addEventListener("DOMContentLoaded", (): void => {
console.debug("scripts-page-boutique");
// initialisePageBoutique();
ShopPageRuntime.runFork(Effect.gen(function*() {
const Elements = yield* ShopPageElements;
const DOM = yield* ShopPageDOM;
const Messages = yield* ShopPageMessages;
yield* Effect.all([DOM.initLoadMoreProductsOnButtonClick(), Messages.initShowMoreButtonUpdates()], {
yield* Effect.all([DOM.initMoreProductsOnButtonClick(), Messages.initShowMoreButtonUpdates()], {
concurrency: "unbounded",
}).pipe(Effect.tapCause(Console.error));
console.debug(Elements.ProductsGrid);
}));
});

View file

@ -260,7 +260,7 @@
<footer class="panneau__pied-de-page">
<button
class="bouton-case-pleine bouton-inverse" form="formulaire-commande"
class="bouton-case-pleine" form="formulaire-commande"
type="submit"
>
Calculate shipping

View file

@ -31,7 +31,10 @@ $commande = $order;
$date = new Carbon($commande->get_date_created());
$email = [
'adresses' => ['facturation' => $commande->get_address('billing'), 'livraison' => $commande->get_address('shipping')],
'adresses' => [
'facturation' => $commande->get_address('billing'),
'livraison' => $commande->get_address('shipping'),
],
'commande' => ['date' => $date->toDateString(), 'id' => $commande->get_id()],
'livraison' => [
'methode' => $commande->get_shipping_method(),

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more