diff --git a/README.md b/README.md
index b87991b..8bd77e7 100644
--- a/README.md
+++ b/README.md
@@ -167,6 +167,9 @@ versions publiées. La mise à jour automatique d'Android fonctionne à partir d
- *Aviation* : 36 pictogrammes fournis, classés par groupes (train et commandes de vol,
feux, pilote automatique, systèmes, radios et divers) ;
- *Image* : vos propres images, rangées dans la **bibliothèque d'icônes**.
+ Cet onglet propose aussi des visuels fournis : interrupteurs à positions, et **point de vue**
+ (vue cockpit, vue extérieure, déplacements du siège Haut / Bas / Gauche / Droite / Avant /
+ Arrière en trois styles).
La bibliothèque est un dossier du PC (`icons/` dans le dossier des données, un sous-dossier
par thème : `icons/mon-avion/train.png`), servi aux Decks à `/user-icons/…`. Menu du profil →
**Bibliothèque d'icônes** : ajout (glisser-déposer, plusieurs images à la fois), dossiers,
diff --git a/package-lock.json b/package-lock.json
index cee8ba3..0112806 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "streamsim",
- "version": "0.12.15",
+ "version": "0.12.16",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "streamsim",
- "version": "0.12.15",
+ "version": "0.12.16",
"license": "MIT",
"dependencies": {
"electron-updater": "^6.8.9",
diff --git a/package.json b/package.json
index e8653c3..f9e27fe 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "streamsim",
"productName": "StreamSim",
- "version": "0.12.15",
+ "version": "0.12.16",
"description": "Clone logiciel de Stream Deck : serveur PC, application de configuration et client Android.",
"author": "StreamSim",
"license": "MIT",
diff --git a/public/docs.html b/public/docs.html
index ce34d00..cfda470 100644
--- a/public/docs.html
+++ b/public/docs.html
@@ -149,6 +149,7 @@
Icônes et images
Emoji : une liste, ou un caractère tapé au clavier.
Aviation : 36 pictogrammes fournis avec l'application, classés par groupes (train et commandes de vol, feux, pilote automatique, systèmes, radios et divers). Ils sont blancs et prennent la couleur de la touche.
Image : vos propres images (PNG, JPG, WebP, GIF, SVG) en couleurs, rangées dans la bibliothèque d'icônes.
+
Dans l'onglet Image, des visuels fournis prêts à poser : les interrupteurs à positions et le point de vue (vue cockpit, vue extérieure, et les déplacements du siège Haut, Bas, Gauche, Droite, Avant, Arrière en trois styles : croix de flèches, pilote, fauteuil). Ce sont des touches entières, avec leur nom dessiné : décochez « Afficher le titre » pour ne pas le doubler.
La bibliothèque d'icônes
C'est un dossier sur le PC : icons/ dans le dossier des données de StreamSim (%APPDATA%\StreamSim\data\icons sous Windows), avec un sous-dossier par thème, par exemple icons/mon-avion/train.png. Les Decks les affichent en les demandant au serveur (/user-icons/…). Une même image sert à autant de touches que vous voulez.
diff --git a/public/icons/README.md b/public/icons/README.md
index 069924d..c0cca93 100644
--- a/public/icons/README.md
+++ b/public/icons/README.md
@@ -12,6 +12,9 @@ public/icons/
├── interrupteurs/ visuels d'interrupteurs à positions
│ ├── levier-de-cote/ bascule-de-face/ glissiere/ haut, milieu, bas
│ └── selecteur-8-positions/ 1 à 8
+ ├── point-de-vue/ vues et déplacements du siège (choix d'icône → Image)
+ │ ├── vues/ cockpit, exterieure
+ │ └── siege-croix/ siege-pilote/ siege-fauteuil/ haut, bas, gauche, droite, avant, arriere
└── rafale/
├── selecteur-5k/ off, test, stby, norm, l, r
├── apu/ ecs/ off, on
@@ -21,9 +24,9 @@ public/icons/
- **aviation/** : SVG 64 × 64, traits blancs de 4 (`stroke="#fff"`), extrémités arrondies, sans texte.
Ajouter une icône : déposer le fichier dans le bon dossier puis la déclarer dans
- `shared/icons.js` (`AVIATION_ICON_GROUPS`) pour qu'elle apparaisse dans le choix d'icône.
+ `shared/icons.js` (`AVIATION_ICON_GROUPS`) pour qu'elle apparaisse dans le choix d'icône. Les visuels du point de vue se déclarent de même dans `VIEW_FACE_GROUPS`.
- **touches-completes/** : SVG 144 × 144 affichés en plein cadre à la place de la touche.
- Un dossier par avion, un sous-dossier par commande, un fichier par état ou position.
+ Un dossier par thème (avion, point de vue…), un sous-dossier par commande, un fichier par état ou position.
Ces icônes sont **dans l'application** et mises à jour avec elle. Vos propres images ne vont pas ici :
elles sont rangées dans la **bibliothèque d'icônes** (dossier `icons/` des données de StreamSim,
diff --git a/public/icons/touches-completes/point-de-vue/siege-croix/arriere.svg b/public/icons/touches-completes/point-de-vue/siege-croix/arriere.svg
new file mode 100644
index 0000000..44553f5
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-croix/arriere.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-croix/avant.svg b/public/icons/touches-completes/point-de-vue/siege-croix/avant.svg
new file mode 100644
index 0000000..fadff95
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-croix/avant.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-croix/bas.svg b/public/icons/touches-completes/point-de-vue/siege-croix/bas.svg
new file mode 100644
index 0000000..4b38a48
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-croix/bas.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-croix/droite.svg b/public/icons/touches-completes/point-de-vue/siege-croix/droite.svg
new file mode 100644
index 0000000..026c382
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-croix/droite.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-croix/gauche.svg b/public/icons/touches-completes/point-de-vue/siege-croix/gauche.svg
new file mode 100644
index 0000000..401167e
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-croix/gauche.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-croix/haut.svg b/public/icons/touches-completes/point-de-vue/siege-croix/haut.svg
new file mode 100644
index 0000000..85f86d3
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-croix/haut.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-fauteuil/arriere.svg b/public/icons/touches-completes/point-de-vue/siege-fauteuil/arriere.svg
new file mode 100644
index 0000000..a2aa46d
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-fauteuil/arriere.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-fauteuil/avant.svg b/public/icons/touches-completes/point-de-vue/siege-fauteuil/avant.svg
new file mode 100644
index 0000000..006f66f
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-fauteuil/avant.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-fauteuil/bas.svg b/public/icons/touches-completes/point-de-vue/siege-fauteuil/bas.svg
new file mode 100644
index 0000000..89fc00c
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-fauteuil/bas.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-fauteuil/droite.svg b/public/icons/touches-completes/point-de-vue/siege-fauteuil/droite.svg
new file mode 100644
index 0000000..386f4f1
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-fauteuil/droite.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-fauteuil/gauche.svg b/public/icons/touches-completes/point-de-vue/siege-fauteuil/gauche.svg
new file mode 100644
index 0000000..c107338
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-fauteuil/gauche.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-fauteuil/haut.svg b/public/icons/touches-completes/point-de-vue/siege-fauteuil/haut.svg
new file mode 100644
index 0000000..dc6f3e4
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-fauteuil/haut.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-pilote/arriere.svg b/public/icons/touches-completes/point-de-vue/siege-pilote/arriere.svg
new file mode 100644
index 0000000..0e1ec58
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-pilote/arriere.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-pilote/avant.svg b/public/icons/touches-completes/point-de-vue/siege-pilote/avant.svg
new file mode 100644
index 0000000..52840be
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-pilote/avant.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-pilote/bas.svg b/public/icons/touches-completes/point-de-vue/siege-pilote/bas.svg
new file mode 100644
index 0000000..e2ef85c
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-pilote/bas.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-pilote/droite.svg b/public/icons/touches-completes/point-de-vue/siege-pilote/droite.svg
new file mode 100644
index 0000000..f3e1566
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-pilote/droite.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-pilote/gauche.svg b/public/icons/touches-completes/point-de-vue/siege-pilote/gauche.svg
new file mode 100644
index 0000000..d4fd89a
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-pilote/gauche.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/siege-pilote/haut.svg b/public/icons/touches-completes/point-de-vue/siege-pilote/haut.svg
new file mode 100644
index 0000000..101fcdd
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/siege-pilote/haut.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/vues/cockpit.svg b/public/icons/touches-completes/point-de-vue/vues/cockpit.svg
new file mode 100644
index 0000000..0cde09a
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/vues/cockpit.svg
@@ -0,0 +1,40 @@
+
diff --git a/public/icons/touches-completes/point-de-vue/vues/exterieure.svg b/public/icons/touches-completes/point-de-vue/vues/exterieure.svg
new file mode 100644
index 0000000..eb21880
--- /dev/null
+++ b/public/icons/touches-completes/point-de-vue/vues/exterieure.svg
@@ -0,0 +1,30 @@
+
diff --git a/public/js/app.js b/public/js/app.js
index 835b5d5..2357b7e 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -8,7 +8,7 @@ import {
import { computeCells, placementError, findFreeSlot, keySpan, stateKey } from '/shared/layout.js';
import { SIMHUB_PROPERTIES } from '/shared/simhub.js';
import { switchCount } from '/shared/controls.js';
-import { AVIATION_ICON_GROUPS, aviationIcon, SWITCH_FACE_GROUPS, switchFaces } from '/shared/icons.js';
+import { AVIATION_ICON_GROUPS, aviationIcon, SWITCH_FACE_GROUPS, switchFaces, VIEW_FACE_GROUPS, viewFaces } from '/shared/icons.js';
import { library, refreshIcons, uploadIconFile, openIconLibrary, exportIcons, pickAndImportIcons } from './icons.js';
import { MSFS_EVENTS, MSFS_EVENT_LABELS, MSFS_SIMVARS, MSFS_NUMERIC_SIMVARS, MSFS_UNITS, FBW_EVENTS, FBW_PRESETS, isLocalVar } from '/shared/msfs.js';
@@ -2015,6 +2015,17 @@ function appearanceSection(i) {
),
),
]),
+ h('div', { class: 'icon-group-title' }, 'Point de vue : vues et siège (fournis)'),
+ ...VIEW_FACE_GROUPS.flatMap((g) => [
+ h('p', { class: 'hint' }, g.label),
+ h(
+ 'div',
+ { class: 'emoji-grid user-icon-grid' },
+ ...viewFaces(g).map((src, i) =>
+ h('button', { class: key.icon === src ? 'on' : '', title: `${g.label} : ${g.files[i][1]}`, onclick: () => setFace({ icon: src }, { render: 'all' }) }, h('img', { src, alt: g.files[i][1], draggable: 'false' })),
+ ),
+ ),
+ ]),
h('div', { class: 'icon-group-title' }, library.icons.length ? `Ma bibliothèque (${library.icons.length})` : 'Ma bibliothèque'),
recent.length
? h(
diff --git a/shared/icons.js b/shared/icons.js
index 00dd88b..bd53ab5 100644
--- a/shared/icons.js
+++ b/shared/icons.js
@@ -39,6 +39,18 @@ export const SWITCH_FACE_GROUPS = [
/** Chemins des visuels d'un groupe, dans l'ordre des positions. */
export const switchFaces = (g) => g.positions.map((n) => `${SWITCH_FACES}/${g.folder}/${n}.svg`);
+/** Visuels du point de vue : vues cockpit / extérieure et déplacements du siège (haut, bas, gauche…). */
+export const VIEW_FACES = '/public/icons/touches-completes/point-de-vue';
+const SEAT_MOVES = [['haut', 'Haut'], ['bas', 'Bas'], ['gauche', 'Gauche'], ['droite', 'Droite'], ['avant', 'Avant'], ['arriere', 'Arrière']];
+export const VIEW_FACE_GROUPS = [
+ { folder: 'vues', label: 'Vues : cockpit et extérieure', files: [['cockpit', 'Cockpit'], ['exterieure', 'Extérieure']] },
+ { folder: 'siege-croix', label: 'Siège : croix de flèches', files: SEAT_MOVES },
+ { folder: 'siege-pilote', label: 'Siège : pilote', files: SEAT_MOVES },
+ { folder: 'siege-fauteuil', label: 'Siège : fauteuil', files: SEAT_MOVES },
+];
+/** Chemins des visuels d'un groupe, dans l'ordre. */
+export const viewFaces = (g) => g.files.map(([n]) => `${VIEW_FACES}/${g.folder}/${n}.svg`);
+
/** Visuel entier d'une touche : /public/icons/touches-completes/... (affiché en plein cadre). */
export const isFacePath = (icon) => typeof icon === 'string' && /^\/public\/icons\/touches-completes\/[\w/-]+\.svg$/.test(icon);
diff --git a/test/icons.test.js b/test/icons.test.js
index 172e92b..1e25084 100644
--- a/test/icons.test.js
+++ b/test/icons.test.js
@@ -152,3 +152,20 @@ test('icônes intégrées : visuels des interrupteurs à positions présents', a
}
}
});
+
+test('icônes du point de vue : vues et siège, chaque visuel listé existe et le dossier ne contient rien d’autre', async () => {
+ const { VIEW_FACE_GROUPS, VIEW_FACES, viewFaces, isFacePath } = await import('../shared/icons.js');
+ const root = path.join(ROOT, 'public/icons/touches-completes/point-de-vue');
+ assert.deepEqual((await fs.readdir(root)).sort(), VIEW_FACE_GROUPS.map((g) => g.folder).sort());
+ for (const g of VIEW_FACE_GROUPS) {
+ assert.deepEqual((await fs.readdir(path.join(root, g.folder))).sort(), g.files.map(([n]) => `${n}.svg`).sort(), g.folder);
+ for (const f of viewFaces(g)) {
+ assert.ok(isFacePath(f) && f.startsWith(VIEW_FACES), f);
+ const svg = await fs.readFile(path.join(ROOT, f), 'utf8');
+ assert.match(svg, /^