Add hold and special inputs to combos
All checks were successful
Deploy Sokko G / deploy (push) Successful in 6s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 6s
This commit is contained in:
parent
9b72f4934c
commit
1a35dcae7b
15 changed files with 575 additions and 26 deletions
|
|
@ -442,8 +442,8 @@ Type : `combos`
|
|||
"device": "playstation",
|
||||
"inputs": [
|
||||
[
|
||||
{ "kind": "direction", "value": "down" },
|
||||
{ "kind": "button", "value": "triangle" }
|
||||
{ "kind": "direction", "value": "down", "holdMs": 2000 },
|
||||
{ "kind": "button", "value": "triangle", "hold": true }
|
||||
]
|
||||
]
|
||||
},
|
||||
|
|
@ -470,6 +470,8 @@ Notes :
|
|||
- `inputs` est une liste d'étapes successives.
|
||||
- chaque étape contient une ou plusieurs entrées simultanées.
|
||||
- `kind` vaut `button`, `direction`, `key` ou `mouse`.
|
||||
- `hold` est facultatif et indique qu'une entrée doit être maintenue sans durée précise.
|
||||
- `holdMs` est facultatif et indique qu'une entrée doit être maintenue pendant cette durée en millisecondes ; l'interface limite la saisie à des secondes entières de `1` à `99`.
|
||||
- `categoryOrder` stocke l'ordre d'affichage des catégories utilisées.
|
||||
- `collapsedCategories` stocke les noms des catégories repliées.
|
||||
- les catégories restent locales à l'outil ; l'utilisateur peut ajouter plusieurs outils Combos pour séparer des usages.
|
||||
|
|
@ -478,6 +480,8 @@ Stockage compact :
|
|||
|
||||
- `device` est omis si la valeur vaut `playstation`.
|
||||
- `category` est omise si vide.
|
||||
- `hold` est omis si absent ou faux.
|
||||
- `holdMs` est omis si absent ou nul.
|
||||
- `categoryOrder` est omis si aucune catégorie n'est utilisée.
|
||||
- `collapsedCategories` est omis si aucune catégorie n'est repliée.
|
||||
- `combos` est omis si aucun combo valide n'existe.
|
||||
|
|
|
|||
|
|
@ -189,6 +189,8 @@ export function validateSiteContent(site) {
|
|||
"toolboxes.modules.combos.simultaneousModeTitle",
|
||||
"toolboxes.modules.combos.successiveModeLabel",
|
||||
"toolboxes.modules.combos.simultaneousModeLabel",
|
||||
"toolboxes.modules.combos.holdLabel",
|
||||
"toolboxes.modules.combos.holdToggleLabel",
|
||||
"toolboxes.modules.combos.removeStepTitle",
|
||||
"toolboxes.modules.combos.removeLastInputTitle",
|
||||
"toolboxes.modules.combos.addComboButton",
|
||||
|
|
|
|||
|
|
@ -75,6 +75,14 @@ test("style entrypoint and theme layers are wired", async () => {
|
|||
assert.match(styleIcons, /ui-icon-remove-column/);
|
||||
assert.match(styleIcons, /ui-icon-table/);
|
||||
assert.match(styleIcons, /ui-icon-map/);
|
||||
assert.match(styleIcons, /ui-icon-grab/);
|
||||
assert.match(styleIcons, /ui-icon-shield/);
|
||||
assert.match(styleIcons, /ui-icon-gun/);
|
||||
assert.match(styleToolboxes, /\.combo-hold-controls/);
|
||||
assert.match(styleToolboxes, /\.combo-input-direction/);
|
||||
assert.match(styleToolboxes, /\.combo-device-n64\.combo-value-stick-up/);
|
||||
assert.match(styleToolboxes, /\.combo-device-n64\.combo-value-up/);
|
||||
assert.match(styleToolboxes, /\.combo-value-grab \.ui-icon/);
|
||||
assert.match(styleGames, /\.game-home-card/);
|
||||
assert.match(styleGames, /\.game-layout/);
|
||||
assert.match(styleGames, /\.game-grid/);
|
||||
|
|
|
|||
|
|
@ -159,6 +159,13 @@ test("toolbox module registry and modules expose expected behavior", async () =>
|
|||
assert.match(combosModule, /ComboSequence/);
|
||||
assert.match(combosModule, /keyboardLayout/);
|
||||
assert.match(combosModule, /simultaneousMode/);
|
||||
assert.match(combosModule, /SPECIAL_INPUT_GROUP/);
|
||||
assert.match(combosModule, /grab/);
|
||||
assert.match(combosModule, /protect/);
|
||||
assert.match(combosModule, /shoot/);
|
||||
assert.match(combosModule, /holdMs/);
|
||||
assert.match(combosModule, /toggleSelectedInputHold/);
|
||||
assert.match(combosModule, /combo-hold-field/);
|
||||
assert.match(combosModule, /useInlineEdit/);
|
||||
assert.match(combosModule, /useGroupedReorder/);
|
||||
assert.match(combosModule, /reorderFeatures/);
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ test("combos storage normalizes devices, steps and compact export ids", () => {
|
|||
id: "rootCombo",
|
||||
name: "Root",
|
||||
device: "switch",
|
||||
inputs: [[{ kind: "button", value: "triangle" }]]
|
||||
inputs: [[{ kind: "button", value: "triangle", hold: true }]]
|
||||
},
|
||||
{
|
||||
id: "combo1",
|
||||
|
|
@ -267,7 +267,8 @@ test("combos storage normalizes devices, steps and compact export ids", () => {
|
|||
inputs: [
|
||||
[
|
||||
{ kind: "direction", value: "down" },
|
||||
{ kind: "button", value: "cross" },
|
||||
{ kind: "button", value: "cross", hold: false, holdMs: 2000.4 },
|
||||
{ kind: "button", value: "circle", holdMs: 100 },
|
||||
{ kind: "bad", value: "ignored" }
|
||||
],
|
||||
[],
|
||||
|
|
@ -281,6 +282,7 @@ test("combos storage normalizes devices, steps and compact export ids", () => {
|
|||
assert.equal(normalized.device, "playstation");
|
||||
assert.equal(normalized.combos.length, 2);
|
||||
assert.equal(normalized.combos[0].device, "switch");
|
||||
assert.equal(normalized.combos[0].inputs[0][0].hold, true);
|
||||
assert.equal(normalized.combos[1].category.length, 80);
|
||||
assert.equal(normalized.combos[1].name.length, 80);
|
||||
assert.equal("note" in normalized.combos[1], false);
|
||||
|
|
@ -289,7 +291,8 @@ test("combos storage normalizes devices, steps and compact export ids", () => {
|
|||
assert.deepEqual(normalized.combos[1].inputs, [
|
||||
[
|
||||
{ kind: "direction", value: "down" },
|
||||
{ kind: "button", value: "cross" }
|
||||
{ kind: "button", value: "cross", hold: true, holdMs: 2000 },
|
||||
{ kind: "button", value: "circle" }
|
||||
],
|
||||
[{ kind: "key", value: "space" }]
|
||||
]);
|
||||
|
|
@ -300,9 +303,12 @@ test("combos storage normalizes devices, steps and compact export ids", () => {
|
|||
assert.deepEqual(compact.collapsedCategories, [longText.slice(0, 80)]);
|
||||
assert.equal(compact.combos[0].id, "rootCombo");
|
||||
assert.equal(compact.combos[0].device, "switch");
|
||||
assert.equal(compact.combos[0].inputs[0][0].hold, true);
|
||||
assert.equal(compact.combos[1].device, "n64");
|
||||
assert.equal(compact.combos[1].id, "combo1");
|
||||
assert.equal(compact.combos[1].category.length, 80);
|
||||
assert.equal(compact.combos[1].inputs[0][1].holdMs, 2000);
|
||||
assert.equal(compact.combos[1].inputs[0][1].hold, true);
|
||||
|
||||
const exported = createToolboxExportPayload(
|
||||
{ id: "toolbox1", name: "Combos", modules: [{ id: "module1", type: "combos" }], updatedAt: "2026-01-01T00:00:00.000Z" },
|
||||
|
|
|
|||
|
|
@ -348,6 +348,8 @@
|
|||
"simultaneousModeTitle": "Ajouter dans l'étape active",
|
||||
"successiveModeLabel": "A › B",
|
||||
"simultaneousModeLabel": "A + B",
|
||||
"holdLabel": "Maintien (s)",
|
||||
"holdToggleLabel": "Hold",
|
||||
"removeStepTitle": "Supprimer l'étape",
|
||||
"removeLastInputTitle": "Retirer la dernière touche",
|
||||
"addComboButton": "Ajouter le combo",
|
||||
|
|
|
|||
2
website/public/static/icons/fist.svg
Normal file
2
website/public/static/icons/fist.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:none;stroke:#020202;stroke-miterlimit:10;stroke-width:1.91px;}</style></defs><path class="cls-1" d="M6.17,10.86h0A8.16,8.16,0,0,1,14.33,19h0"/><rect class="cls-1" x="17.73" y="2.48" width="4.77" height="7.64" rx="2.39"/><rect class="cls-1" x="12.95" y="1.52" width="4.77" height="8.59" rx="2.39"/><path class="cls-1" d="M11.56,9.89A2.37,2.37,0,0,0,13,7.73V3.91a2.39,2.39,0,1,0-4.77,0V6.19"/><path class="cls-1" d="M3.41,7.79V3.91a2.39,2.39,0,1,1,4.77,0V6.19"/><path class="cls-1" d="M6.27,23.48V21.57l-.75-.64c-2.57-2.58-4-5.12-4-8.77h0A6,6,0,0,1,3.25,7.94h0A6,6,0,0,1,7.47,6.19h2.2A2.33,2.33,0,0,1,12,8.52h0a2.33,2.33,0,0,1-2.33,2.34H6.17"/><path class="cls-1" d="M17.73,23.48V21.57l.75-.64c2.57-2.58,4-5.12,4-8.77h0V7.36"/></svg>
|
||||
|
After Width: | Height: | Size: 997 B |
36
website/public/static/icons/foot.svg
Normal file
36
website/public/static/icons/foot.svg
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 511.997 511.997" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M317.276,116.749c-60.041-37.086-139.059-18.406-176.145,41.643L13.344,365.291
|
||||
c-25.958,42.035-12.885,97.348,29.15,123.307c42.035,25.958,97.348,12.885,123.307-29.15l33.63-54.443
|
||||
c15.693-25.412,39.62-34.799,67.319-45.67c31.642-12.425,67.507-26.496,92.169-66.424
|
||||
C395.996,232.854,377.317,153.835,317.276,116.749z M337.133,279.446c-40.354,65.34-119.142,46.763-159.488,112.102
|
||||
c-13.449,21.777-17.357,28.109-33.63,54.443c-18.569,30.071-58.001,39.39-88.073,20.821
|
||||
c-30.071-18.569-39.39-58.001-20.821-88.073C46.087,360.982,149.349,193.788,162.9,171.841
|
||||
c29.713-48.111,92.8-63.027,140.919-33.314C351.939,168.24,366.846,231.335,337.133,279.446z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M484.683,132.895c-8.841-5.461-18.594-7.979-28.305-8.294c3.703-17.92-3.703-37.052-20.156-47.206
|
||||
c-9.233-5.7-19.729-7.441-29.722-6.025c-1.434-11.076-7.492-21.47-17.715-27.785c-10.214-6.306-22.212-7.074-32.759-3.405
|
||||
c-3.029-6.784-8.047-12.766-14.839-16.964c-13.577-8.38-30.37-7.253-42.59,1.519c-2.697-3.43-5.922-6.536-9.847-8.96
|
||||
c-18.014-11.128-41.719-5.521-52.847,12.493c-11.128,18.014-5.521,41.719,12.493,52.847c13.577,8.38,30.37,7.245,42.59-1.519
|
||||
c2.705,3.43,5.922,6.536,9.847,8.96c10.214,6.306,22.212,7.074,32.76,3.405c3.029,6.784,8.047,12.766,14.839,16.964
|
||||
c6.135,3.789,12.928,5.495,19.661,5.572c-0.324,15.352,7.066,30.49,21.052,39.125c4.062,2.509,8.397,4.07,12.8,5.154
|
||||
c-11.392,25.865-2.5,56.858,22.212,72.124c27.025,16.691,62.575,8.286,79.266-18.739
|
||||
C520.105,185.144,511.7,149.586,484.683,132.895z M279.465,55.164c-3.712,6.007-11.597,7.885-17.613,4.164
|
||||
c-6.016-3.712-7.876-11.605-4.164-17.613c3.712-6.016,11.605-7.876,17.613-4.164C281.317,41.264,283.177,49.157,279.465,55.164z
|
||||
M331.902,62.605c-3.712,6.016-11.597,7.885-17.613,4.164c-6.016-3.712-7.876-11.605-4.164-17.613
|
||||
c3.712-6.016,11.605-7.876,17.613-4.164C333.754,48.705,335.614,56.598,331.902,62.605z M379.501,82.975
|
||||
c-3.712,6.016-11.605,7.885-17.613,4.164c-6.016-3.712-7.876-11.605-4.164-17.613c3.712-6.016,11.605-7.876,17.613-4.164
|
||||
C381.344,69.074,383.213,76.967,379.501,82.975z M402.601,131.836c-9.02-5.564-11.819-17.399-6.246-26.419
|
||||
c5.572-9.02,17.408-11.819,26.419-6.246c9.02,5.572,11.819,17.399,6.246,26.419C423.448,134.61,411.621,137.409,402.601,131.836z
|
||||
M481.645,198.712c-9.284,15.036-29.005,19.695-44.041,10.411c-15.027-9.284-19.695-29.005-10.411-44.041
|
||||
s29.005-19.695,44.041-10.411C486.27,163.956,490.929,183.676,481.645,198.712z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
3
website/public/static/icons/grab.svg
Normal file
3
website/public/static/icons/grab.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path fill="#000" d="M7.4 3.1c.9 0 1.6.7 1.6 1.6v5.1h.6V3.6c0-.9.7-1.6 1.6-1.6s1.6.7 1.6 1.6v6.2h.6V4.2c0-.9.7-1.6 1.6-1.6s1.6.7 1.6 1.6v6h.6V6.1c0-.9.7-1.6 1.6-1.6s1.6.7 1.6 1.6v7.2c0 5.2-2.7 8.7-7.1 8.7h-2.6c-2.5 0-4.4-1.2-5.7-3.3L2.9 15c-.6-.8-.4-1.8.4-2.4.8-.6 1.8-.4 2.4.3l1.1 1.3V4.7c0-.9.7-1.6 1.6-1.6z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 383 B |
57
website/public/static/icons/gun.svg
Normal file
57
website/public/static/icons/gun.svg
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg version="1.1" id="designs" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="800px" height="800px" viewBox="0 0 32 32" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.sketchy_een{fill:#111918;}
|
||||
</style>
|
||||
<path class="sketchy_een" d="M29.919,11.654c0.009-0.303,0.028-0.605,0.033-0.908c0.004-0.308-0.007-0.62-0.009-0.928
|
||||
c-0.007-0.655,0.019-1.309,0.056-1.964c0.022-0.427-0.375-0.783-0.783-0.783c-0.034,0-0.063,0.015-0.096,0.019
|
||||
c-0.152-0.157-0.362-0.257-0.594-0.26c-0.376-0.004-0.751-0.011-1.127-0.016c0.004-0.165,0.007-0.331,0.01-0.497
|
||||
c0.005-0.276-0.153-0.51-0.371-0.652c-0.154-0.182-0.375-0.305-0.63-0.311c-0.871-0.018-1.741-0.018-2.612-0.043
|
||||
c-0.819-0.024-1.637-0.058-2.458-0.072c-1.662-0.028-3.323-0.056-4.987-0.063c-0.728-0.002-1.454,0-2.181,0
|
||||
c-0.895,0.002-1.79,0.004-2.685-0.004C10.689,5.166,9.89,5.123,9.092,5.11c-0.273-0.004-0.547-0.005-0.82-0.005
|
||||
c-0.526,0-1.052,0.005-1.579,0.009C6.129,5.12,5.563,5.121,4.998,5.125C4.755,5.126,4.545,5.248,4.401,5.426
|
||||
C4.165,5.51,3.967,5.696,3.908,5.95C3.741,6.676,3.559,7.4,3.418,8.132c-0.136,0.706-0.301,1.402-0.472,2.1
|
||||
c-0.173,0.706-0.29,1.426-0.473,2.13c-0.162,0.616-0.317,1.231-0.453,1.851c-0.058,0.264,0.015,0.524,0.175,0.716
|
||||
c0.041,0.42,0.384,0.723,0.803,0.742c0.269,0.013,0.539,0.01,0.809,0.014c-0.154,0.853-0.305,1.706-0.478,2.554
|
||||
c-0.163,0.798-0.316,1.6-0.447,2.404c-0.132,0.798-0.286,1.593-0.425,2.39c-0.137,0.795-0.265,1.591-0.382,2.387
|
||||
c-0.02,0.132-0.025,0.253-0.009,0.372c-0.01,0.056-0.033,0.109-0.033,0.166c0,0.483,0.427,0.973,0.936,0.936
|
||||
c0.769-0.056,1.537-0.115,2.308-0.15c0.653-0.028,1.311-0.033,1.964-0.03c0.681,0.002,1.361,0.002,2.042,0.022
|
||||
c0.654,0.018,1.303,0.08,1.957,0.044c0.369-0.021,0.674-0.265,0.759-0.614c0.084-0.095,0.151-0.206,0.171-0.322
|
||||
c0.132-0.776,0.342-1.537,0.509-2.306c0.163-0.756,0.254-1.524,0.405-2.284c0.041-0.208,0.085-0.416,0.127-0.624
|
||||
c0.616-0.059,1.226-0.116,1.83-0.263c0.291-0.072,0.568-0.201,0.837-0.33c0.252-0.121,0.498-0.252,0.737-0.397
|
||||
c0.152-0.093,0.292-0.21,0.427-0.321c0.13-0.108,0.225-0.252,0.327-0.384c0.147-0.191,0.236-0.433,0.336-0.65
|
||||
c0.271-0.594,0.516-1.203,0.748-1.814c0.026-0.07,0.02-0.15,0.018-0.229c0.709-0.005,1.418-0.012,2.125-0.031
|
||||
c0.819-0.022,1.637-0.054,2.456-0.054c0.904,0,1.808,0.017,2.714,0.02c0.258,0.001,0.484-0.128,0.639-0.317
|
||||
c0.024,0.003,0.046,0.015,0.071,0.015c0.21,0,0.414-0.084,0.564-0.232c0.158-0.158,0.217-0.347,0.232-0.564
|
||||
c0.05-0.667,0.049-1.338,0.08-2.006c0.598,0.023,1.19,0.073,1.784,0.128c0.21,0.021,0.447-0.1,0.59-0.245
|
||||
c0.156-0.156,0.245-0.37,0.245-0.59c0-0.145-0.05-0.281-0.121-0.405C29.891,11.865,29.916,11.762,29.919,11.654z M16.363,17.328
|
||||
c-0.117,0.274-0.236,0.555-0.4,0.802c-0.042,0.045-0.087,0.086-0.134,0.127c-0.269,0.184-0.561,0.328-0.859,0.46
|
||||
c-0.316,0.113-0.642,0.186-0.972,0.244c-0.15,0.016-0.301,0.01-0.452,0.013c0.016-0.077,0.031-0.154,0.047-0.23
|
||||
c0.175-0.825,0.373-1.646,0.565-2.469c0.866-0.012,1.733-0.02,2.599-0.023C16.628,16.612,16.506,16.974,16.363,17.328z
|
||||
M20.663,14.53c-1.652,0.028-3.305,0.074-4.957,0.083c-0.78,0.004-1.56-0.004-2.341-0.013c-0.459-0.005-0.841,0.388-0.841,0.843
|
||||
c0,0.095,0.025,0.186,0.057,0.273c-0.381,1.672-0.717,3.353-1.067,5.033c-0.16,0.769-0.28,1.547-0.446,2.313
|
||||
c-0.152,0.691-0.336,1.377-0.476,2.07c-1.091-0.101-2.181-0.184-3.278-0.208c-0.35-0.007-0.7-0.011-1.05-0.011
|
||||
c-0.795,0-1.589,0.025-2.383,0.063c0.011-0.082,0.024-0.164,0.036-0.245c0.061-0.405,0.13-0.81,0.199-1.212
|
||||
c0.136-0.784,0.278-1.565,0.394-2.354c0.117-0.811,0.24-1.625,0.401-2.428c0.178-0.889,0.36-1.777,0.525-2.67
|
||||
c0.025-0.134,0.023-0.261,0.001-0.385c0.219-0.004,0.439-0.007,0.658-0.01c0.436-0.008,0.873-0.017,1.309-0.021
|
||||
c0.388-0.003,0.782-0.051,1.17-0.026c0.21,0.037,0.407,0.025,0.599-0.087c0.182-0.108,0.317-0.284,0.373-0.487
|
||||
c0.058-0.21,0.028-0.44-0.082-0.628c-0.152-0.258-0.412-0.386-0.702-0.403c-0.124-0.008-0.249-0.01-0.374-0.01
|
||||
c-0.284,0-0.57,0.015-0.85,0.025c-0.176,0.005-0.351,0.026-0.525,0.037c-0.267,0.017-0.531,0.02-0.798,0.026
|
||||
c-0.507,0.013-1.016,0.017-1.522,0.008c-0.341-0.006-0.679-0.028-1.018-0.046c0.093-0.417,0.186-0.834,0.283-1.251
|
||||
c0.165-0.722,0.282-1.456,0.451-2.176c0.16-0.68,0.31-1.361,0.455-2.044C4.999,7.951,5.177,7.32,5.34,6.686
|
||||
c1.464,0.018,2.928,0.019,4.393,0.045c0.789,0.013,1.576,0.045,2.365,0.043c0.834-0.002,1.667,0,2.501,0
|
||||
c3.273-0.004,6.546,0.05,9.818,0.178c0.469,0.018,0.938,0.031,1.407,0.045c0.004,0.578,0.011,1.156,0.013,1.736
|
||||
c0.004,0.737-0.02,1.474-0.032,2.211c-0.002,0.121-0.011,0.241-0.015,0.362c-0.272-0.001-0.544-0.003-0.816-0.003
|
||||
c-0.36,0.002-0.722,0.004-1.084-0.002c-0.728-0.009-1.457-0.032-2.185-0.032c-0.778,0-1.558,0.013-2.336,0.017
|
||||
c-0.743,0.006-1.485-0.009-2.226-0.017c-1.539-0.011-3.076-0.015-4.615-0.002c-0.743,0.008-1.487,0.008-2.232,0
|
||||
c-0.694-0.005-1.387-0.031-2.081-0.035c-0.403-0.004-0.806-0.007-1.21-0.009c-0.001,0-0.002,0-0.003,0
|
||||
c-0.409,0-0.749,0.345-0.749,0.754c0,0.401,0.342,0.765,0.752,0.752c0.631-0.018,1.264-0.009,1.894,0.002
|
||||
c0.711,0.011,1.42,0.03,2.129,0.041c1.513,0.024,3.028,0.03,4.541,0.035c0.789,0.002,1.576,0.021,2.365,0.039
|
||||
c0.744,0.019,1.489,0.021,2.233,0.021c0.759,0,1.521-0.009,2.28-0.013c0.739-0.004,1.482-0.015,2.222,0.004
|
||||
c0.217,0.005,0.433,0.015,0.65,0.023c0.116,0.084,0.25,0.142,0.401,0.155c-0.025,0.485-0.03,0.97-0.036,1.455
|
||||
C24.011,14.495,22.335,14.5,20.663,14.53z M27.384,11.476c0.001-0.178,0.01-0.355,0.011-0.533c0-0.737-0.035-1.474-0.032-2.211
|
||||
c0.001-0.071,0.003-0.142,0.004-0.213c0.353-0.007,0.707-0.015,1.06-0.026c0,0.432,0.004,0.864-0.004,1.295
|
||||
c-0.004,0.31-0.002,0.618-0.015,0.928c-0.011,0.262-0.027,0.524-0.036,0.787c-0.287-0.01-0.574-0.029-0.862-0.029
|
||||
C27.468,11.474,27.426,11.476,27.384,11.476z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.7 KiB |
4
website/public/static/icons/shield.svg
Normal file
4
website/public/static/icons/shield.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.302 21.6149C11.5234 21.744 11.6341 21.8086 11.7903 21.8421C11.9116 21.8681 12.0884 21.8681 12.2097 21.8421C12.3659 21.8086 12.4766 21.744 12.698 21.6149C14.646 20.4784 20 16.9084 20 12V6.6C20 6.04207 20 5.7631 19.8926 5.55048C19.7974 5.36198 19.6487 5.21152 19.4613 5.11409C19.25 5.00419 18.9663 5.00084 18.3988 4.99413C15.4272 4.95899 13.7136 4.71361 12 3C10.2864 4.71361 8.57279 4.95899 5.6012 4.99413C5.03373 5.00084 4.74999 5.00419 4.53865 5.11409C4.35129 5.21152 4.20259 5.36198 4.10739 5.55048C4 5.7631 4 6.04207 4 6.6V12C4 16.9084 9.35396 20.4784 11.302 21.6149Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 897 B |
|
|
@ -16,26 +16,32 @@ const DEVICE_OPTIONS = [
|
|||
{ value: "keyboardMouse", label: "Clavier/Souris" }
|
||||
];
|
||||
|
||||
const SPECIAL_INPUT_GROUP = { title: "Spéciale", column: "actions", inputs: [["button", "fist"], ["button", "foot"], ["button", "grab"], ["button", "protect"], ["button", "shoot"]] };
|
||||
|
||||
const DEVICE_GROUPS = {
|
||||
playstation: [
|
||||
{ title: "Directions", kind: "directions", column: "movement", inputs: [["direction", "up-left"], ["direction", "up"], ["direction", "up-right"], ["direction", "left"], null, ["direction", "right"], ["direction", "down-left"], ["direction", "down"], ["direction", "down-right"]] },
|
||||
{ title: "Actions", column: "actions", inputs: [["button", "triangle"], ["button", "circle"], ["button", "cross"], ["button", "square"], ["button", "l1"], ["button", "r1"], ["button", "l2"], ["button", "r2"]] },
|
||||
SPECIAL_INPUT_GROUP,
|
||||
{ title: "Système", inputs: [["button", "options"], ["button", "share"], ["button", "l3"], ["button", "r3"]] }
|
||||
],
|
||||
xbox: [
|
||||
{ title: "Directions", kind: "directions", column: "movement", inputs: [["direction", "up-left"], ["direction", "up"], ["direction", "up-right"], ["direction", "left"], null, ["direction", "right"], ["direction", "down-left"], ["direction", "down"], ["direction", "down-right"]] },
|
||||
{ title: "Actions", column: "actions", inputs: [["button", "a"], ["button", "b"], ["button", "x"], ["button", "y"], ["button", "lb"], ["button", "rb"], ["button", "lt"], ["button", "rt"]] },
|
||||
SPECIAL_INPUT_GROUP,
|
||||
{ title: "Système", inputs: [["button", "menu"], ["button", "view"], ["button", "ls"], ["button", "rs"]] }
|
||||
],
|
||||
switch: [
|
||||
{ title: "Directions", kind: "directions", column: "movement", inputs: [["direction", "up-left"], ["direction", "up"], ["direction", "up-right"], ["direction", "left"], null, ["direction", "right"], ["direction", "down-left"], ["direction", "down"], ["direction", "down-right"]] },
|
||||
{ title: "Actions", column: "actions", inputs: [["button", "a"], ["button", "b"], ["button", "x"], ["button", "y"], ["button", "l"], ["button", "r"], ["button", "zl"], ["button", "zr"]] },
|
||||
SPECIAL_INPUT_GROUP,
|
||||
{ title: "Système", inputs: [["button", "plus"], ["button", "minus"], ["button", "ls"], ["button", "rs"]] }
|
||||
],
|
||||
n64: [
|
||||
{ title: "Stick", kind: "directions", column: "movement", inputs: [["direction", "stick-up-left"], ["direction", "stick-up"], ["direction", "stick-up-right"], ["direction", "stick-left"], null, ["direction", "stick-right"], ["direction", "stick-down-left"], ["direction", "stick-down"], ["direction", "stick-down-right"]] },
|
||||
{ title: "D-Pad", kind: "directions", column: "movement", inputs: [["direction", "up-left"], ["direction", "up"], ["direction", "up-right"], ["direction", "left"], null, ["direction", "right"], ["direction", "down-left"], ["direction", "down"], ["direction", "down-right"]] },
|
||||
{ title: "Actions", column: "actions", inputs: [["button", "a"], ["button", "b"], ["button", "z"], ["button", "start"], ["button", "l"], ["button", "r"]] },
|
||||
SPECIAL_INPUT_GROUP,
|
||||
{ title: "C-buttons", kind: "directions", inputs: [["button", "c-up"], ["button", "c-down"], ["button", "c-left"], ["button", "c-right"]] }
|
||||
]
|
||||
};
|
||||
|
|
@ -45,12 +51,14 @@ const KEYBOARD_GROUPS = {
|
|||
{ title: "ZQSD", kind: "directions", column: "movement", inputs: [null, ["key", "z"], null, ["key", "q"], ["key", "s"], ["key", "d"], null, null, null] },
|
||||
{ title: "Flèches", kind: "directions", column: "movement", inputs: [null, ["direction", "up"], null, ["direction", "left"], ["direction", "down"], ["direction", "right"], null, null, null] },
|
||||
{ title: "Touches", column: "actions", inputs: [["key", "ctrl"], ["key", "alt"], ["key", "shift"], ["key", "space"], ["key", "enter"], ["key", "tab"], ["key", "e"], ["key", "a"], ["key", "r"], ["key", "f"]] },
|
||||
SPECIAL_INPUT_GROUP,
|
||||
{ title: "Souris", inputs: [["mouse", "left"], ["mouse", "right"], ["mouse", "middle"], ["mouse", "wheel-up"], ["mouse", "wheel-down"]] }
|
||||
],
|
||||
qwerty: [
|
||||
{ title: "WASD", kind: "directions", column: "movement", inputs: [null, ["key", "w"], null, ["key", "a"], ["key", "s"], ["key", "d"], null, null, null] },
|
||||
{ title: "Flèches", kind: "directions", column: "movement", inputs: [null, ["direction", "up"], null, ["direction", "left"], ["direction", "down"], ["direction", "right"], null, null, null] },
|
||||
{ title: "Touches", column: "actions", inputs: [["key", "ctrl"], ["key", "alt"], ["key", "shift"], ["key", "space"], ["key", "enter"], ["key", "tab"], ["key", "e"], ["key", "q"], ["key", "r"], ["key", "f"]] },
|
||||
SPECIAL_INPUT_GROUP,
|
||||
{ title: "Souris", inputs: [["mouse", "left"], ["mouse", "right"], ["mouse", "middle"], ["mouse", "wheel-up"], ["mouse", "wheel-down"]] }
|
||||
]
|
||||
};
|
||||
|
|
@ -84,9 +92,22 @@ const INPUT_LABELS = {
|
|||
"c-down": "C↓",
|
||||
"c-left": "C←",
|
||||
"c-right": "C→",
|
||||
fist: "Poing",
|
||||
foot: "Pied",
|
||||
grab: "Grab",
|
||||
protect: "Protect",
|
||||
shoot: "Shoot",
|
||||
space: "Espace"
|
||||
};
|
||||
|
||||
const INPUT_ICONS = {
|
||||
fist: "fist",
|
||||
foot: "foot",
|
||||
grab: "grab",
|
||||
protect: "shield",
|
||||
shoot: "gun"
|
||||
};
|
||||
|
||||
const MOUSE_LABELS = {
|
||||
left: "Clic G",
|
||||
right: "Clic D",
|
||||
|
|
@ -103,11 +124,33 @@ function getInputLabel(input) {
|
|||
}
|
||||
|
||||
function inputKey(input) {
|
||||
return `${input.kind}:${input.value}`;
|
||||
return `${input.kind}:${input.value}:${input.hold ? "hold" : ""}:${input.holdMs || 0}`;
|
||||
}
|
||||
|
||||
function createInput(kind, value) {
|
||||
return { kind, value };
|
||||
function createInput(kind, value, holdMs = 0) {
|
||||
const input = { kind, value };
|
||||
if (holdMs > 0) input.holdMs = holdMs;
|
||||
return input;
|
||||
}
|
||||
|
||||
function getHoldSeconds(input) {
|
||||
return Math.round((Number(input?.holdMs) || 0) / 1000);
|
||||
}
|
||||
|
||||
function formatHoldLabel(input) {
|
||||
const seconds = getHoldSeconds(input);
|
||||
if (!seconds) return input?.hold ? "hold" : "";
|
||||
return `${seconds}s`;
|
||||
}
|
||||
|
||||
function parseHoldSeconds(value) {
|
||||
const seconds = Number.parseInt(String(value || "").replace(/[^\d]/g, ""), 10);
|
||||
if (!Number.isInteger(seconds) || seconds <= 0) return 0;
|
||||
return Math.min(99, seconds);
|
||||
}
|
||||
|
||||
function isDirectionInput(input) {
|
||||
return input?.kind === "direction";
|
||||
}
|
||||
|
||||
function createCombo(context, id, name, category, inputs, fallbackName, device) {
|
||||
|
|
@ -153,9 +196,13 @@ function setComboItemCategory(combo, category) {
|
|||
|
||||
function ComboInputToken({ input, device, palette = false, onClick, onDragStart }) {
|
||||
const label = getInputLabel(input);
|
||||
const iconName = INPUT_ICONS[input.value];
|
||||
const holdLabel = !palette ? formatHoldLabel(input) : "";
|
||||
const classNames = [
|
||||
"combo-input-token",
|
||||
`combo-input-${input.kind}`,
|
||||
iconName ? "combo-input-icon" : "",
|
||||
holdLabel ? "has-hold" : "",
|
||||
input.kind === "key" && label.length === 1 ? "combo-input-short" : "",
|
||||
`combo-device-${device}`,
|
||||
`combo-value-${String(input.value || "").replace(/[^a-z0-9]+/gi, "-").toLowerCase()}`
|
||||
|
|
@ -164,19 +211,20 @@ function ComboInputToken({ input, device, palette = false, onClick, onDragStart
|
|||
if (palette) {
|
||||
return (
|
||||
<button className={classNames} type="button" draggable onClick={onClick} onDragStart={onDragStart} title={label} aria-label={label}>
|
||||
<span>{label}</span>
|
||||
<span>{iconName ? <Icon name={iconName} /> : label}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={classNames} draggable={Boolean(onDragStart)} onDragStart={onDragStart} title={label}>
|
||||
<span>{label}</span>
|
||||
<span className={classNames} draggable={Boolean(onDragStart)} onClick={onClick} onDragStart={onDragStart} title={holdLabel ? `${label} · ${holdLabel}` : label}>
|
||||
<span>{iconName ? <Icon name={iconName} /> : label}</span>
|
||||
{holdLabel && <em className="combo-input-hold">{holdLabel}</em>}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function ComboStep({ step, stepIndex, device, active = false, onClick, onDropInput, onDragInput }) {
|
||||
function ComboStep({ step, stepIndex, device, active = false, selectedInputIndex = -1, onClick, onSelectInput, onDropInput, onDragInput }) {
|
||||
const className = [
|
||||
"combo-step-inputs",
|
||||
step.length > 1 ? "is-simultaneous" : "",
|
||||
|
|
@ -184,9 +232,17 @@ function ComboStep({ step, stepIndex, device, active = false, onClick, onDropInp
|
|||
active ? "is-active" : ""
|
||||
].filter(Boolean).join(" ");
|
||||
const content = step.length ? step.map((input, inputIndex) => (
|
||||
<span className="combo-step-input" key={`${inputKey(input)}-${inputIndex}`}>
|
||||
<span className={`combo-step-input ${selectedInputIndex === inputIndex ? "is-selected" : ""}`} key={`${inputKey(input)}-${inputIndex}`}>
|
||||
{inputIndex > 0 && <span className="combo-plus" aria-hidden="true">+</span>}
|
||||
<ComboInputToken input={input} device={device} onDragStart={onDragInput ? (event) => onDragInput(event, stepIndex, inputIndex, input) : null} />
|
||||
<ComboInputToken
|
||||
input={input}
|
||||
device={device}
|
||||
onClick={onSelectInput ? (event) => {
|
||||
event.stopPropagation();
|
||||
onSelectInput(stepIndex, inputIndex);
|
||||
} : null}
|
||||
onDragStart={onDragInput ? (event) => onDragInput(event, stepIndex, inputIndex, input) : null}
|
||||
/>
|
||||
</span>
|
||||
)) : <span className="combo-empty-step">Étape vide</span>;
|
||||
|
||||
|
|
@ -214,7 +270,7 @@ function ComboStep({ step, stepIndex, device, active = false, onClick, onDropInp
|
|||
return <span className={className}>{content}</span>;
|
||||
}
|
||||
|
||||
function ComboSequence({ inputs, device, emptyLabel, activeStepIndex = -1, onSelectStep, onDropInput, onDropNewStep, onDragInput }) {
|
||||
function ComboSequence({ inputs, device, emptyLabel, activeStepIndex = -1, selectedInput, onSelectStep, onSelectInput, onDropInput, onDropNewStep, onDragInput }) {
|
||||
const visibleInputs = inputs.map((step, index) => ({ step, index })).filter(({ step }) => step.length || onSelectStep);
|
||||
if (!visibleInputs.length) return <span className="combo-empty-sequence">{emptyLabel}</span>;
|
||||
return (
|
||||
|
|
@ -230,12 +286,26 @@ function ComboSequence({ inputs, device, emptyLabel, activeStepIndex = -1, onSel
|
|||
onDropNewStep(event);
|
||||
}}
|
||||
>
|
||||
{visibleInputs.map(({ step, index: stepIndex }, visibleIndex) => (
|
||||
{visibleInputs.map(({ step, index: stepIndex }, visibleIndex) => {
|
||||
const nextStep = visibleInputs[visibleIndex + 1]?.step || [];
|
||||
const hideSeparator = step.every(isDirectionInput) && nextStep.every(isDirectionInput);
|
||||
return (
|
||||
<span className="combo-step" key={`${stepIndex}-${step.map(inputKey).join("+")}`}>
|
||||
<ComboStep step={step} stepIndex={stepIndex} device={device} active={activeStepIndex === stepIndex} onClick={onSelectStep ? () => onSelectStep(stepIndex) : null} onDropInput={onDropInput ? (event) => onDropInput(event, stepIndex) : null} onDragInput={onDragInput} />
|
||||
{visibleIndex < visibleInputs.length - 1 && <span className="combo-step-separator" aria-hidden="true">›</span>}
|
||||
<ComboStep
|
||||
step={step}
|
||||
stepIndex={stepIndex}
|
||||
device={device}
|
||||
active={activeStepIndex === stepIndex}
|
||||
selectedInputIndex={selectedInput?.stepIndex === stepIndex ? selectedInput.inputIndex : -1}
|
||||
onClick={onSelectStep ? () => onSelectStep(stepIndex) : null}
|
||||
onSelectInput={onSelectInput}
|
||||
onDropInput={onDropInput ? (event) => onDropInput(event, stepIndex) : null}
|
||||
onDragInput={onDragInput}
|
||||
/>
|
||||
{visibleIndex < visibleInputs.length - 1 && !hideSeparator && <span className="combo-step-separator" aria-hidden="true">›</span>}
|
||||
</span>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -265,6 +335,7 @@ export function CombosModule({ toolboxId, moduleId, context, editing }) {
|
|||
const [comboName, setComboName] = useState("");
|
||||
const [draftInputs, setDraftInputs] = useState([[]]);
|
||||
const [activeStepIndex, setActiveStepIndex] = useState(0);
|
||||
const [selectedInput, setSelectedInput] = useState(null);
|
||||
const [simultaneousMode, setSimultaneousMode] = useState(false);
|
||||
const [keyboardLayout, setKeyboardLayout] = useState("azerty");
|
||||
const [customKey, setCustomKey] = useState("");
|
||||
|
|
@ -272,6 +343,9 @@ export function CombosModule({ toolboxId, moduleId, context, editing }) {
|
|||
const paletteGroups = useMemo(() => getPaletteGroups(data.device, keyboardLayout), [data.device, keyboardLayout]);
|
||||
const paletteColumns = useMemo(() => getPaletteColumns(paletteGroups), [paletteGroups]);
|
||||
const cleanDraftInputs = draftInputs.map((step) => step.filter(Boolean)).filter((step) => step.length);
|
||||
const selectedDraftInput = Number.isInteger(selectedInput?.stepIndex) && Number.isInteger(selectedInput?.inputIndex)
|
||||
? draftInputs[selectedInput.stepIndex]?.[selectedInput.inputIndex] || null
|
||||
: null;
|
||||
const entries = getGroupedEntries(data.combos, {
|
||||
groupOrder: data.categoryOrder,
|
||||
getItemGroup: getComboCategory,
|
||||
|
|
@ -344,6 +418,7 @@ export function CombosModule({ toolboxId, moduleId, context, editing }) {
|
|||
const nextSteps = steps.length ? steps.map((step) => [...step]) : [[]];
|
||||
const targetIndex = Math.min(Math.max(0, stepIndex), nextSteps.length - 1);
|
||||
nextSteps[targetIndex] = [...nextSteps[targetIndex], input];
|
||||
setSelectedInput({ stepIndex: targetIndex, inputIndex: nextSteps[targetIndex].length - 1 });
|
||||
return nextSteps;
|
||||
});
|
||||
}
|
||||
|
|
@ -359,6 +434,7 @@ export function CombosModule({ toolboxId, moduleId, context, editing }) {
|
|||
const filledSteps = steps.filter((step) => step.length);
|
||||
const nextSteps = [...filledSteps, [input]];
|
||||
setActiveStepIndex(nextSteps.length - 1);
|
||||
setSelectedInput({ stepIndex: nextSteps.length - 1, inputIndex: 0 });
|
||||
return nextSteps;
|
||||
});
|
||||
}
|
||||
|
|
@ -417,6 +493,7 @@ export function CombosModule({ toolboxId, moduleId, context, editing }) {
|
|||
const [input] = nextSteps[sourceStepIndex]?.splice(sourceInputIndex, 1) || [];
|
||||
if (!input || !nextSteps[targetStepIndex]) return steps;
|
||||
nextSteps[targetStepIndex].push(input);
|
||||
setSelectedInput({ stepIndex: targetStepIndex, inputIndex: nextSteps[targetStepIndex].length - 1 });
|
||||
return nextSteps.filter((step, index) => step.length || index === targetStepIndex);
|
||||
});
|
||||
}
|
||||
|
|
@ -428,6 +505,7 @@ export function CombosModule({ toolboxId, moduleId, context, editing }) {
|
|||
if (!input) return steps;
|
||||
const safeSteps = nextSteps.filter((step) => step.length);
|
||||
setActiveStepIndex(safeSteps.length);
|
||||
setSelectedInput({ stepIndex: safeSteps.length, inputIndex: 0 });
|
||||
return [...safeSteps, [input]];
|
||||
});
|
||||
}
|
||||
|
|
@ -437,6 +515,7 @@ export function CombosModule({ toolboxId, moduleId, context, editing }) {
|
|||
const nextSteps = steps.filter((_, index) => index !== activeStepIndex);
|
||||
const safeSteps = nextSteps.length ? nextSteps : [[]];
|
||||
setActiveStepIndex(Math.min(activeStepIndex, safeSteps.length - 1));
|
||||
setSelectedInput(null);
|
||||
return safeSteps;
|
||||
});
|
||||
}
|
||||
|
|
@ -455,10 +534,46 @@ export function CombosModule({ toolboxId, moduleId, context, editing }) {
|
|||
nextSteps[targetIndex].pop();
|
||||
const safeSteps = nextSteps.filter((step) => step.length);
|
||||
setActiveStepIndex(Math.max(0, safeSteps.length - 1));
|
||||
setSelectedInput(null);
|
||||
return safeSteps.length ? safeSteps : [[]];
|
||||
});
|
||||
}
|
||||
|
||||
function updateSelectedInputHold(value) {
|
||||
if (!selectedDraftInput) return;
|
||||
const holdMs = Math.round(parseHoldSeconds(value) * 1000);
|
||||
setDraftInputs((steps) => steps.map((step, stepIndex) => step.map((input, inputIndex) => {
|
||||
if (stepIndex !== selectedInput.stepIndex || inputIndex !== selectedInput.inputIndex) return input;
|
||||
const nextInput = { ...input };
|
||||
if (holdMs > 0) {
|
||||
nextInput.hold = true;
|
||||
nextInput.holdMs = holdMs;
|
||||
}
|
||||
else delete nextInput.holdMs;
|
||||
return nextInput;
|
||||
})));
|
||||
}
|
||||
|
||||
function toggleSelectedInputHold() {
|
||||
if (!selectedDraftInput) return;
|
||||
setDraftInputs((steps) => steps.map((step, stepIndex) => step.map((input, inputIndex) => {
|
||||
if (stepIndex !== selectedInput.stepIndex || inputIndex !== selectedInput.inputIndex) return input;
|
||||
const nextInput = { ...input };
|
||||
if (nextInput.hold || nextInput.holdMs) {
|
||||
delete nextInput.hold;
|
||||
delete nextInput.holdMs;
|
||||
} else {
|
||||
nextInput.hold = true;
|
||||
}
|
||||
return nextInput;
|
||||
})));
|
||||
}
|
||||
|
||||
function selectDraftStep(stepIndex) {
|
||||
setActiveStepIndex(stepIndex);
|
||||
setSelectedInput(null);
|
||||
}
|
||||
|
||||
function addCustomKey(event) {
|
||||
event.preventDefault();
|
||||
const value = normalizeCustomKey(customKey);
|
||||
|
|
@ -485,6 +600,7 @@ export function CombosModule({ toolboxId, moduleId, context, editing }) {
|
|||
setComboName(combo.name);
|
||||
setDraftInputs(combo.inputs.length ? combo.inputs.map((step) => step.map((input) => ({ ...input }))) : [[]]);
|
||||
setActiveStepIndex(0);
|
||||
setSelectedInput(null);
|
||||
}
|
||||
|
||||
function cancelComboEdit() {
|
||||
|
|
@ -493,6 +609,7 @@ export function CombosModule({ toolboxId, moduleId, context, editing }) {
|
|||
setComboCategory("");
|
||||
setDraftInputs([[]]);
|
||||
setActiveStepIndex(0);
|
||||
setSelectedInput(null);
|
||||
setSimultaneousMode(false);
|
||||
}
|
||||
|
||||
|
|
@ -708,8 +825,47 @@ export function CombosModule({ toolboxId, moduleId, context, editing }) {
|
|||
)}
|
||||
|
||||
<div className="combo-draft-panel">
|
||||
<ComboSequence inputs={draftInputs} device={data.device} emptyLabel={textContent.emptyDraft || "Aucune touche sélectionnée."} activeStepIndex={activeStepIndex} onSelectStep={setActiveStepIndex} onDropInput={dropInputOnStep} onDropNewStep={dropInputAsNewStep} onDragInput={dragDraftInput} />
|
||||
<ComboSequence
|
||||
inputs={draftInputs}
|
||||
device={data.device}
|
||||
emptyLabel={textContent.emptyDraft || "Aucune touche sélectionnée."}
|
||||
activeStepIndex={activeStepIndex}
|
||||
selectedInput={selectedInput}
|
||||
onSelectStep={selectDraftStep}
|
||||
onSelectInput={(stepIndex, inputIndex) => {
|
||||
setActiveStepIndex(stepIndex);
|
||||
setSelectedInput({ stepIndex, inputIndex });
|
||||
}}
|
||||
onDropInput={dropInputOnStep}
|
||||
onDropNewStep={dropInputAsNewStep}
|
||||
onDragInput={dragDraftInput}
|
||||
/>
|
||||
<div className="combo-draft-actions">
|
||||
<label className="combo-hold-field">
|
||||
<span className="combo-hold-controls">
|
||||
<button
|
||||
type="button"
|
||||
className={selectedDraftInput?.hold || selectedDraftInput?.holdMs ? "active" : ""}
|
||||
onClick={toggleSelectedInputHold}
|
||||
disabled={!selectedDraftInput}
|
||||
aria-pressed={Boolean(selectedDraftInput?.hold || selectedDraftInput?.holdMs)}
|
||||
>
|
||||
{textContent.holdToggleLabel || "Hold"}
|
||||
</button>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="99"
|
||||
step="1"
|
||||
inputMode="numeric"
|
||||
value={selectedDraftInput ? getHoldSeconds(selectedDraftInput) || "" : ""}
|
||||
onChange={(event) => updateSelectedInputHold(event.target.value)}
|
||||
disabled={!selectedDraftInput}
|
||||
placeholder="0"
|
||||
aria-label={textContent.holdLabel || "Maintien en secondes"}
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
className={`combos-simultaneous-toggle ${simultaneousMode ? "active" : ""}`}
|
||||
|
|
|
|||
|
|
@ -362,7 +362,15 @@ export function normalizeCountersData(data) {
|
|||
function normalizeComboInput(input) {
|
||||
const kind = COMBO_INPUT_KINDS.has(input?.kind) ? input.kind : "";
|
||||
const value = String(input?.value || "").trim().slice(0, 24);
|
||||
return kind && value ? { kind, value } : null;
|
||||
if (!kind || !value) return null;
|
||||
const holdMs = Number(input?.holdMs);
|
||||
const normalizedHoldMs = Number.isFinite(holdMs) && holdMs > 0
|
||||
? Math.min(99000, Math.round(holdMs / 1000) * 1000)
|
||||
: 0;
|
||||
const normalized = { kind, value };
|
||||
if (input?.hold === true || normalizedHoldMs > 0) normalized.hold = true;
|
||||
if (normalizedHoldMs > 0) normalized.holdMs = normalizedHoldMs;
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function normalizeComboSteps(inputs) {
|
||||
|
|
|
|||
|
|
@ -252,6 +252,31 @@
|
|||
-webkit-mask-image: url("/static/icons/back.svg");
|
||||
}
|
||||
|
||||
.ui-icon-fist {
|
||||
mask-image: url("/static/icons/fist.svg");
|
||||
-webkit-mask-image: url("/static/icons/fist.svg");
|
||||
}
|
||||
|
||||
.ui-icon-foot {
|
||||
mask-image: url("/static/icons/foot.svg");
|
||||
-webkit-mask-image: url("/static/icons/foot.svg");
|
||||
}
|
||||
|
||||
.ui-icon-grab {
|
||||
mask-image: url("/static/icons/grab.svg");
|
||||
-webkit-mask-image: url("/static/icons/grab.svg");
|
||||
}
|
||||
|
||||
.ui-icon-shield {
|
||||
mask-image: url("/static/icons/shield.svg");
|
||||
-webkit-mask-image: url("/static/icons/shield.svg");
|
||||
}
|
||||
|
||||
.ui-icon-gun {
|
||||
mask-image: url("/static/icons/gun.svg");
|
||||
-webkit-mask-image: url("/static/icons/gun.svg");
|
||||
}
|
||||
|
||||
.ui-icon-trash {
|
||||
mask-image: url("/static/icons/trashcan.svg");
|
||||
-webkit-mask-image: url("/static/icons/trashcan.svg");
|
||||
|
|
|
|||
|
|
@ -2732,6 +2732,20 @@ textarea:focus {
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
.combo-step-input.is-selected .combo-input-token {
|
||||
box-shadow:
|
||||
var(--combo-token-shadow),
|
||||
0 0 0 2px rgba(246, 196, 83, 0.22);
|
||||
}
|
||||
|
||||
.combo-step-input.is-selected .combo-input-token.has-hold {
|
||||
box-shadow:
|
||||
var(--combo-token-shadow),
|
||||
0 0 0 2px rgba(246, 196, 83, 0.22),
|
||||
7px 0 0 -1px var(--combo-hold-shadow),
|
||||
9px 0 6px -3px var(--combo-hold-shadow);
|
||||
}
|
||||
|
||||
.combo-step-inputs {
|
||||
min-height: 42px;
|
||||
padding: 3px;
|
||||
|
|
@ -2775,10 +2789,18 @@ button.combo-step-inputs {
|
|||
|
||||
.combo-step-inputs.is-single.is-active .combo-input-token {
|
||||
box-shadow:
|
||||
inset 0 -2px 0 rgba(0, 0, 0, 0.2),
|
||||
var(--combo-token-shadow),
|
||||
0 0 0 2px rgba(139, 92, 246, 0.18);
|
||||
}
|
||||
|
||||
.combo-step-inputs.is-single.is-active .combo-input-token.has-hold {
|
||||
box-shadow:
|
||||
var(--combo-token-shadow),
|
||||
0 0 0 2px rgba(139, 92, 246, 0.18),
|
||||
7px 0 0 -1px var(--combo-hold-shadow),
|
||||
9px 0 6px -3px var(--combo-hold-shadow);
|
||||
}
|
||||
|
||||
.combo-step-separator,
|
||||
.combo-plus {
|
||||
color: var(--color-text-muted);
|
||||
|
|
@ -2807,6 +2829,8 @@ button.combo-step-inputs {
|
|||
--combo-token-bg: rgba(21, 26, 48, 0.82);
|
||||
--combo-token-border: rgba(165, 180, 252, 0.22);
|
||||
--combo-token-color: var(--color-text-primary);
|
||||
--combo-token-shadow: inset 0 -2px 0 rgba(0, 0, 0, 0.2);
|
||||
--combo-hold-shadow: rgba(125, 135, 159, 0.34);
|
||||
display: inline-flex;
|
||||
box-sizing: border-box;
|
||||
width: 34px;
|
||||
|
|
@ -2825,7 +2849,7 @@ button.combo-step-inputs {
|
|||
font-size: 12px;
|
||||
font-weight: 950;
|
||||
line-height: 1;
|
||||
box-shadow: inset 0 -2px 0 rgba(0, 0, 0, 0.2);
|
||||
box-shadow: var(--combo-token-shadow);
|
||||
}
|
||||
|
||||
button.combo-input-token {
|
||||
|
|
@ -2852,6 +2876,36 @@ button.combo-input-token:hover {
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.combo-input-token .ui-icon {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
}
|
||||
|
||||
.combo-input-token.has-hold {
|
||||
position: relative;
|
||||
box-shadow:
|
||||
var(--combo-token-shadow),
|
||||
7px 0 0 -1px var(--combo-hold-shadow),
|
||||
9px 0 6px -3px var(--combo-hold-shadow);
|
||||
}
|
||||
|
||||
.combo-input-hold {
|
||||
position: absolute;
|
||||
right: -7px;
|
||||
bottom: -6px;
|
||||
min-width: 20px;
|
||||
padding: 1px 4px;
|
||||
border: 1px solid rgba(246, 196, 83, 0.34);
|
||||
border-radius: var(--radius-pill);
|
||||
background: rgba(5, 7, 17, 0.92);
|
||||
color: var(--color-accent-gold);
|
||||
font-size: 9px;
|
||||
font-style: normal;
|
||||
font-weight: 950;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.combo-input-key,
|
||||
.combo-input-mouse,
|
||||
.combo-value-options,
|
||||
|
|
@ -2967,9 +3021,86 @@ button.combo-input-token.combo-input-mouse {
|
|||
}
|
||||
|
||||
.combo-input-direction {
|
||||
--combo-token-bg: rgba(34, 211, 238, 0.12);
|
||||
--combo-token-border: rgba(34, 211, 238, 0.3);
|
||||
--combo-token-color: var(--color-accent-cyan);
|
||||
--combo-token-bg:
|
||||
radial-gradient(circle at 50% 42%, rgba(34, 211, 238, 0.26), transparent 64%),
|
||||
rgba(34, 211, 238, 0.12);
|
||||
--combo-token-border: rgba(34, 211, 238, 0.52);
|
||||
--combo-token-color: #67e8f9;
|
||||
--combo-token-shadow:
|
||||
inset 0 -2px 0 rgba(0, 0, 0, 0.24),
|
||||
inset 0 0 0 1px rgba(34, 211, 238, 0.16);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
button.combo-input-token.combo-input-direction:hover {
|
||||
border-color: rgba(34, 211, 238, 0.72);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.12), transparent),
|
||||
radial-gradient(circle at 50% 42%, rgba(34, 211, 238, 0.34), transparent 64%),
|
||||
rgba(18, 52, 70, 0.76);
|
||||
}
|
||||
|
||||
.combo-device-n64.combo-value-stick-up-left,
|
||||
.combo-device-n64.combo-value-stick-up,
|
||||
.combo-device-n64.combo-value-stick-up-right,
|
||||
.combo-device-n64.combo-value-stick-left,
|
||||
.combo-device-n64.combo-value-stick-right,
|
||||
.combo-device-n64.combo-value-stick-down-left,
|
||||
.combo-device-n64.combo-value-stick-down,
|
||||
.combo-device-n64.combo-value-stick-down-right {
|
||||
--combo-token-bg:
|
||||
radial-gradient(circle at 38% 30%, rgba(245, 247, 255, 0.2), transparent 28%),
|
||||
radial-gradient(circle at 50% 54%, rgba(34, 211, 238, 0.3), transparent 66%),
|
||||
rgba(14, 75, 94, 0.68);
|
||||
--combo-token-border: rgba(34, 211, 238, 0.62);
|
||||
--combo-token-color: #a5f3fc;
|
||||
--combo-token-shadow:
|
||||
inset 0 -3px 0 rgba(0, 0, 0, 0.24),
|
||||
inset 0 0 0 1px rgba(34, 211, 238, 0.18);
|
||||
}
|
||||
|
||||
.combo-device-n64.combo-value-up-left,
|
||||
.combo-device-n64.combo-value-up,
|
||||
.combo-device-n64.combo-value-up-right,
|
||||
.combo-device-n64.combo-value-left,
|
||||
.combo-device-n64.combo-value-right,
|
||||
.combo-device-n64.combo-value-down-left,
|
||||
.combo-device-n64.combo-value-down,
|
||||
.combo-device-n64.combo-value-down-right {
|
||||
--combo-token-bg:
|
||||
linear-gradient(180deg, rgba(246, 196, 83, 0.12), transparent),
|
||||
rgba(31, 34, 54, 0.92);
|
||||
--combo-token-border: rgba(246, 196, 83, 0.48);
|
||||
--combo-token-color: #f8d982;
|
||||
--combo-token-shadow:
|
||||
inset 0 -3px 0 rgba(0, 0, 0, 0.28),
|
||||
inset 0 0 0 1px rgba(246, 196, 83, 0.14);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
button.combo-device-n64.combo-value-stick-up-left:hover,
|
||||
button.combo-device-n64.combo-value-stick-up:hover,
|
||||
button.combo-device-n64.combo-value-stick-up-right:hover,
|
||||
button.combo-device-n64.combo-value-stick-left:hover,
|
||||
button.combo-device-n64.combo-value-stick-right:hover,
|
||||
button.combo-device-n64.combo-value-stick-down-left:hover,
|
||||
button.combo-device-n64.combo-value-stick-down:hover,
|
||||
button.combo-device-n64.combo-value-stick-down-right:hover {
|
||||
border-color: rgba(34, 211, 238, 0.78);
|
||||
}
|
||||
|
||||
button.combo-device-n64.combo-value-up-left:hover,
|
||||
button.combo-device-n64.combo-value-up:hover,
|
||||
button.combo-device-n64.combo-value-up-right:hover,
|
||||
button.combo-device-n64.combo-value-left:hover,
|
||||
button.combo-device-n64.combo-value-right:hover,
|
||||
button.combo-device-n64.combo-value-down-left:hover,
|
||||
button.combo-device-n64.combo-value-down:hover,
|
||||
button.combo-device-n64.combo-value-down-right:hover {
|
||||
border-color: rgba(246, 196, 83, 0.7);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(246, 196, 83, 0.16), transparent),
|
||||
rgba(38, 40, 62, 0.96);
|
||||
}
|
||||
|
||||
.combo-device-playstation.combo-value-triangle {
|
||||
|
|
@ -3066,6 +3197,104 @@ button.combo-input-token.combo-input-mouse {
|
|||
--combo-token-color: #fca5a5;
|
||||
}
|
||||
|
||||
.combo-value-fist,
|
||||
.combo-value-foot,
|
||||
.combo-value-grab,
|
||||
.combo-value-protect,
|
||||
.combo-value-shoot {
|
||||
--combo-token-bg: rgba(217, 70, 239, 0.14);
|
||||
--combo-token-border: rgba(217, 70, 239, 0.34);
|
||||
--combo-token-color: var(--color-accent-pink);
|
||||
}
|
||||
|
||||
.combo-value-grab .ui-icon {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
button.combo-input-token.combo-value-grab .ui-icon {
|
||||
width: 23px;
|
||||
height: 23px;
|
||||
}
|
||||
|
||||
.combo-hold-field {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.combo-hold-controls {
|
||||
display: inline-flex;
|
||||
width: 92px;
|
||||
min-width: 92px;
|
||||
min-height: 34px;
|
||||
align-items: center;
|
||||
justify-content: stretch;
|
||||
gap: 3px;
|
||||
padding: 3px;
|
||||
border: 1px solid rgba(165, 180, 252, 0.12);
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(5, 7, 17, 0.26);
|
||||
}
|
||||
|
||||
.combo-hold-controls button {
|
||||
min-width: 50px;
|
||||
min-height: 26px;
|
||||
padding: 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.combo-hold-controls button.active {
|
||||
border-color: rgba(246, 196, 83, 0.34);
|
||||
background: rgba(246, 196, 83, 0.12);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.combo-hold-controls button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.48;
|
||||
}
|
||||
|
||||
.combo-hold-field input {
|
||||
box-sizing: border-box;
|
||||
width: 26px;
|
||||
min-width: 26px;
|
||||
height: 26px;
|
||||
min-height: 26px;
|
||||
margin: 0;
|
||||
padding: 0 3px;
|
||||
border: 1px solid transparent;
|
||||
border-color: transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.combo-hold-field input:focus {
|
||||
border-color: rgba(246, 196, 83, 0.34);
|
||||
}
|
||||
|
||||
.combo-hold-field input {
|
||||
appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.combo-hold-field input::-webkit-outer-spin-button,
|
||||
.combo-hold-field input::-webkit-inner-spin-button {
|
||||
margin: 0;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.counters-add-form {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue