Skip to content

Commit d00adab

Browse files
committed
feat: Permettre d'envoyer du texte.
1 parent 03f48d7 commit d00adab

File tree

8 files changed

+192
-0
lines changed

8 files changed

+192
-0
lines changed

src/_locales/en/messages.json

+24
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,18 @@
185185
"popup_osd_title": {
186186
"message": "Show OSD [M]"
187187
},
188+
"popup_home_title": {
189+
"message": "Go to home"
190+
},
191+
"popup_fullscreen_title": {
192+
"message": "Toogle fullscreen [Tab]"
193+
},
194+
"popup_opensendtext_title": {
195+
"message": "Send text"
196+
},
197+
"popup_playerprocessinfo_title": {
198+
"message": "Show player process information [O]"
199+
},
188200
"popup_empty_textcontent": {
189201
"message": "Playlist empty"
190202
},
@@ -194,6 +206,18 @@
194206
"popup_removeItem_title": {
195207
"message": "Remove"
196208
},
209+
"popup_text_placeholder": {
210+
"message": "Text to send"
211+
},
212+
"popup_done_textcontent": {
213+
"message": "Finish after send"
214+
},
215+
"popup_cancel_textcontent": {
216+
"message": "Cancel"
217+
},
218+
"popup_sendtext_textcontent": {
219+
"message": "Send"
220+
},
197221
"popup_configure_textcontent": {
198222
"message": "Configure"
199223
},

src/_locales/fr/messages.json

+24
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,18 @@
185185
"popup_osd_title": {
186186
"message": "Afficher le menu à l'écran [M]"
187187
},
188+
"popup_home_title": {
189+
"message": "Aller à la page d'accueil"
190+
},
191+
"popup_fullscreen_title": {
192+
"message": "Basculer en plein écran [Tab]"
193+
},
194+
"popup_opensendtext_title": {
195+
"message": "Envoyer du texte"
196+
},
197+
"popup_playerprocessinfo_title": {
198+
"message": "Afficher les informations sur le processus de lecture [O]"
199+
},
188200
"popup_empty_textcontent": {
189201
"message": "Liste de lecture vide"
190202
},
@@ -194,6 +206,18 @@
194206
"popup_removeItem_title": {
195207
"message": "Retirer"
196208
},
209+
"popup_text_placeholder": {
210+
"message": "Texte à envoyer"
211+
},
212+
"popup_done_textcontent": {
213+
"message": "Terminer après l'envoi"
214+
},
215+
"popup_cancel_textcontent": {
216+
"message": "Annuler"
217+
},
218+
"popup_sendtext_textcontent": {
219+
"message": "Envoyer"
220+
},
197221
"popup_configure_textcontent": {
198222
"message": "Configurer"
199223
},

src/core/jsonrpc/input.js

+12
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ export const Input = class {
9191
return this.kodi.send("Input.Select");
9292
}
9393

94+
/**
95+
* Envoie du texte.
96+
*
97+
* @param {string} text Le texte envoyé.
98+
* @param {boolean} done La marque indiquant s'il faut fermer la boite de
99+
* saisie.
100+
* @returns {Promise.<string>} Une promesse contenant <code>"OK"</code>.
101+
*/
102+
sendText(text, done) {
103+
return this.kodi.send("Input.SendText", { text, done });
104+
}
105+
94106
/**
95107
* Affiche le <em>menu à l'écran</em> (<em>On Screen Display</em>) du
96108
* lecteur courant.

src/popup/img/sendtext.svg

+10
Loading

src/popup/index.html

+28
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<meta charset="utf-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>Cast Kodi</title>
7+
<link href="../lib/dialog-polyfill/style.css" rel="stylesheet" />
78
<link href="../photon/style.css" rel="stylesheet" />
89
<link href="style.css" rel="stylesheet" />
910
<link href="/img/icon.svg" rel="shortcut icon" />
@@ -147,6 +148,11 @@
147148
<object data="img/fullscreen.svg"></object>
148149
</button>
149150

151+
<button class="ghost" id="opensendtext" disabled autocomplete="off"
152+
data-i18n-title>
153+
<object data="img/sendtext.svg"></object>
154+
</button>
155+
150156
<button class="ghost" id="playerprocessinfo" disabled autocomplete="off"
151157
data-i18n-title>
152158
<object data="img/playerprocessinfo.svg"></object>
@@ -223,6 +229,28 @@
223229
</button>
224230
</section>
225231

232+
<dialog id="dialogsendtext">
233+
<form method="dialog">
234+
<p>
235+
<label>
236+
<input name="text" type="text" autocomplete="off"
237+
data-i18n-placeholder="text" />
238+
</label>
239+
</p>
240+
<p>
241+
<label data-i18n-textcontent="done">
242+
<input name="done" type="checkbox" checked autocomplete="off" />
243+
{}
244+
</label>
245+
</p>
246+
<menu>
247+
<button id="cancel" value="cancel" data-i18n-textcontent></button>
248+
<button class="primary" id="sendtext" value="send"
249+
data-i18n-textcontent></button>
250+
</menu>
251+
</form>
252+
</dialog>
253+
226254
<article id="splash">
227255
<h1></h1>
228256
<p></p>

src/popup/script.js

+51
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* @module
33
*/
44

5+
import dialogPolyfill from "../lib/dialog-polyfill/script.js";
56
import { cast, kodi } from "../core/index.js";
67
import { complete } from "../core/labellers.js";
78
import { notify } from "../core/notify.js";
@@ -333,6 +334,39 @@ const setFullscreen = function () {
333334
kodi.gui.setFullscreen().catch(splash);
334335
};
335336

337+
const openSendText = function () {
338+
// Annuler l'action (venant d'un raccourci clavier) si le bouton est
339+
// désactivé.
340+
if (document.querySelector("#opensendtext").disabled) {
341+
return;
342+
}
343+
344+
const dialog = document.querySelector("#dialogsendtext");
345+
dialog.querySelector(`input[name="text"]`).value = "";
346+
// Utiliser une prothèse en attendant que les boites de dialogue soit
347+
// implémentées dans Firefox.
348+
// https://bugzilla.mozilla.org/show_bug.cgi?id=840640
349+
dialogPolyfill.registerDialog(dialog);
350+
dialog.showModal();
351+
};
352+
353+
const closeDialog = function (event) {
354+
// Fermer la boite de dialogue si l'utilisateur clique en dehors de la
355+
// boite.
356+
if (event.explicitOriginalTarget.classList.contains("backdrop")) {
357+
event.target.close();
358+
}
359+
};
360+
361+
const sendText = function (event) {
362+
const dialog = event.target;
363+
if ("send" === dialog.returnValue) {
364+
const text = dialog.querySelector(`input[name="text"]`).value;
365+
const done = dialog.querySelector(`input[name="done"]`).checked;
366+
kodi.input.sendText(text, done).catch(splash);
367+
}
368+
};
369+
336370
const showPlayerProcessInfo = function () {
337371
// Annuler l'action (venant d'un raccourci clavier) si le bouton est
338372
// désactivé.
@@ -663,6 +697,7 @@ const load = async function () {
663697

664698
document.querySelector("#home").disabled = false;
665699
document.querySelector("#fullscreen").disabled = false;
700+
document.querySelector("#opensendtext").disabled = false;
666701
document.querySelector("#playerprocessinfo").disabled = false;
667702

668703
document.querySelector("#clear").disabled = false;
@@ -721,6 +756,7 @@ document.querySelector("#osd").addEventListener("click", showOSD);
721756

722757
document.querySelector("#home").addEventListener("click", home);
723758
document.querySelector("#fullscreen").addEventListener("click", setFullscreen);
759+
document.querySelector("#opensendtext").addEventListener("click", openSendText);
724760
document.querySelector("#playerprocessinfo").addEventListener(
725761
"click",
726762
showPlayerProcessInfo,
@@ -737,6 +773,10 @@ document.querySelector("#donate").addEventListener("click", donate);
737773
document.querySelector("#rate").addEventListener("click", rate);
738774
document.querySelector("#preferences").addEventListener("click", preferences);
739775

776+
document.querySelector("#dialogsendtext").addEventListener("close", sendText);
777+
document.querySelector("#dialogsendtext").addEventListener("click",
778+
closeDialog);
779+
740780
document.querySelector("#configure").addEventListener("click", preferences);
741781

742782
// Attention ! La popup n'a pas automatiquement le focus quand elle est ouverte
@@ -748,6 +788,17 @@ globalThis.addEventListener("keydown", (event) => {
748788
return;
749789
}
750790

791+
// Ignorer les raccourcis clavier quand une boite de dialogue est ouverte
792+
// (sauf pour la touche Entrée qui valide le formulaire).
793+
const dialog = event.target.closest("dialog");
794+
if (null !== dialog) {
795+
if ("Enter" === event.key) {
796+
dialog.close(dialog.querySelector(".primary").value);
797+
event.preventDefault();
798+
}
799+
return;
800+
}
801+
751802
// Écrire normalement les caractères dans la zone de texte (sauf pour la
752803
// touche Entrée qui envoi l'URL saisie).
753804
if ("TEXTAREA" === event.target.tagName) {

src/popup/style.css

+13
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,19 @@ input[type="range"].disabled::-moz-range-progress {
7575
background-color: #737373;
7676
}
7777

78+
#dialogsendtext {
79+
width: calc(100% - 56px);
80+
}
81+
#dialogsendtext p:first-child {
82+
margin-top: 0;
83+
}
84+
#dialogsendtext input[type="text"] {
85+
width: calc(100% - 16px);
86+
}
87+
#dialogsendtext input[type="checkbox"] {
88+
display: inline-block;
89+
}
90+
7891
#cast {
7992
border-bottom: 1px solid #737373;
8093
display: flex;

test/unit/core/jsonrpc/input.js

+30
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,36 @@ describe("core/jsonrpc/input.js", function () {
107107
});
108108
});
109109

110+
describe("sendText()", function () {
111+
it("should send request", async function () {
112+
const fake = sinon.fake.resolves("OK");
113+
114+
const input = new Input({ send: fake });
115+
const result = await input.sendText("foo", false);
116+
assert.strictEqual(result, "OK");
117+
118+
assert.strictEqual(fake.callCount, 1);
119+
assert.deepStrictEqual(fake.firstCall.args, [
120+
"Input.SendText",
121+
{ text: "foo", done: false },
122+
]);
123+
});
124+
125+
it("should send request and finish", async function () {
126+
const fake = sinon.fake.resolves("OK");
127+
128+
const input = new Input({ send: fake });
129+
const result = await input.sendText("foo", true);
130+
assert.strictEqual(result, "OK");
131+
132+
assert.strictEqual(fake.callCount, 1);
133+
assert.deepStrictEqual(fake.firstCall.args, [
134+
"Input.SendText",
135+
{ text: "foo", done: true },
136+
]);
137+
});
138+
});
139+
110140
describe("showOSD()", function () {
111141
it("should send request", async function () {
112142
const fake = sinon.fake.resolves("OK");

0 commit comments

Comments
 (0)