Ajout de l'aide
parent
6cbc951c59
commit
6b09a15e1a
18
kikikoz.css
18
kikikoz.css
|
|
@ -2,10 +2,26 @@ body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
details.aide[open] {
|
||||||
|
columns: 2;
|
||||||
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
|
border-radius: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
details.aide > summary {
|
||||||
|
font-weight: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
details.configuration[open] {
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 1ex;
|
||||||
|
border-radius: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
body > div {
|
body > div {
|
||||||
background-color: aliceblue;
|
background-color: aliceblue;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
166
src/main.rs
166
src/main.rs
|
|
@ -136,7 +136,9 @@ fn ConfigQueue<'a, G: Html>(cx: Scope<'a>, q: QueueProp<'a>) -> View<G> {
|
||||||
});
|
});
|
||||||
|
|
||||||
section()
|
section()
|
||||||
.c(h3().dyn_t(move || nom.clone()))
|
.c(h3().dyn_t(move || format!("{} ", nom)).c(button()
|
||||||
|
.t("Supprimer")
|
||||||
|
.on("click", move |_ev| (queue.à_détruire.set(true)))))
|
||||||
.c(input()
|
.c(input()
|
||||||
.attr("type", "checkbox")
|
.attr("type", "checkbox")
|
||||||
.dyn_attr("id", {
|
.dyn_attr("id", {
|
||||||
|
|
@ -166,9 +168,6 @@ fn ConfigQueue<'a, G: Html>(cx: Scope<'a>, q: QueueProp<'a>) -> View<G> {
|
||||||
move || Some(format!("prio_queue_{}", &suffix.clone()))
|
move || Some(format!("prio_queue_{}", &suffix.clone()))
|
||||||
})
|
})
|
||||||
.dyn_t(|| format!("Priorité: {}", queue.priorité.get())))
|
.dyn_t(|| format!("Priorité: {}", queue.priorité.get())))
|
||||||
.c(button()
|
|
||||||
.t("Supprimer")
|
|
||||||
.on("click", move |ev| (queue.à_détruire.set(true))))
|
|
||||||
.view(cx)
|
.view(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,6 +191,76 @@ fn Queue<'a, G: Html>(cx: Scope<'a>, queue: QueueProp<'a>) -> View<G> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Prop, Clone, Copy)]
|
||||||
|
struct BarreCôtéProp<'a> {
|
||||||
|
queues: &'a Signal<Vec<ÉtatQueue<'a>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn classe_visible(b: bool) -> &'static str {
|
||||||
|
if b {
|
||||||
|
"visible"
|
||||||
|
} else {
|
||||||
|
"hidden"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn Explications<'a, G: Html>(cx: Scope<'a>) -> View<G> {
|
||||||
|
details()
|
||||||
|
.class("aide")
|
||||||
|
.c(summary().t("Comment utiliser Kikikoz"))
|
||||||
|
.c(p().t("Kikikoz est un outil qui vous aide à répartir la parole dans une réunion."))
|
||||||
|
.c(p().t("Quand vous lancez Kikikoz, l'interface que vous voyez correspond à une réunion avec un tour de parole divisé en deux file. C'est une pratique que l'on observe souvent, notamment pour répartir équitablement la parole entre femmes et hommes."))
|
||||||
|
.c(p().t("Quand une personne lève la main, vous allez inscrire son nom dans le champ de texte correspondant à la file d'attente idoine."))
|
||||||
|
.c(p().t("Pour donner la parole à la personne suivante, il suffit de cliquer sur le bouton \"suivantə\", ou de taper CTRL+ENTRÉE. Son nom et son temps de parole apparaissent alors en gros caractères"))
|
||||||
|
.c(p().t("La parole alterne entre les deux files. Vous l'avez peut-être remarqué, quand une personne lève la main, elle ne va pas à la fin de la file, mais à une position aléatoire. C'est inhabituel, mais pas moins juste. Il y a des chances que ce qu'elle à a dire soit plus pertinent, puisqu'elle lève la main après avoir entendu plus de choses. Vous pouvez changer ce comportement pour chaque file."))
|
||||||
|
.c(p().t("Pour faire une pause dans la réunion (quand personne n'a la parole), cliquez sur \"pause\"."))
|
||||||
|
.c(p().t("Vous pouvez configurer toutes sortes de détails via le panneau configaration. Vous pouvez y ajouter ou supprimer des files, décider si l'ajout de personnes dans chaque file doit se faire à une position aléatoire ou à la fin. Vous pouvez également attribuer une priorité différente aux files, si vous décidez que la parole doit revenir à l'une d'entre elles plus souvent qu'aux autres."))
|
||||||
|
.view(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn BarreCôté<'a, G: Html>(cx: Scope<'a>, p: BarreCôtéProp<'a>) -> View<G> {
|
||||||
|
let conf_visible = create_signal(cx, false);
|
||||||
|
let nom_nouvelle_file = create_signal(cx, String::new());
|
||||||
|
let nouvelle_file = {
|
||||||
|
let queues = p.queues.clone();
|
||||||
|
move |_ev| {
|
||||||
|
let q = ÉtatQueue::new(cx, &nom_nouvelle_file.get());
|
||||||
|
queues.modify().push(q);
|
||||||
|
*nom_nouvelle_file.modify() = String::new();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let nouvelle_file_clav = move |ev: web_sys::Event| {
|
||||||
|
let event: web_sys::KeyboardEvent = ev.clone().unchecked_into();
|
||||||
|
if event.key_code() == 13 && !event.ctrl_key() {
|
||||||
|
nouvelle_file(ev)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {cx,
|
||||||
|
aside {
|
||||||
|
|
||||||
|
details(class="configuration") {
|
||||||
|
summary { "Configuration " }
|
||||||
|
|
||||||
|
Keyed {
|
||||||
|
iterable: p.queues,
|
||||||
|
key: |q| q.id,
|
||||||
|
view: |cx, q| view!{cx, ConfigQueue{ q }}
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
h3 {"Nouvelle file" }
|
||||||
|
input(type="text", bind:value=nom_nouvelle_file, on:keypress=nouvelle_file_clav) {}
|
||||||
|
button(on:click=nouvelle_file) { "Créer" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
sycamore::render(|cx| {
|
sycamore::render(|cx| {
|
||||||
let heure_courante = create_signal(cx, 0.0);
|
let heure_courante = create_signal(cx, 0.0);
|
||||||
|
|
@ -207,21 +276,17 @@ fn main() {
|
||||||
let date_prise_parole = create_signal(cx, 0.0);
|
let date_prise_parole = create_signal(cx, 0.0);
|
||||||
let courantə = create_signal(cx, None);
|
let courantə = create_signal(cx, None);
|
||||||
// let courantə2 = create_signal(cx, None).clone();
|
// let courantə2 = create_signal(cx, None).clone();
|
||||||
let conf_visible = create_signal(cx, false);
|
|
||||||
let queues = create_signal(
|
let queues = create_signal(
|
||||||
cx,
|
cx,
|
||||||
vec![ÉtatQueue::new(cx, "Gauche"), ÉtatQueue::new(cx, "Droite")],
|
vec![ÉtatQueue::new(cx, "Gauche"), ÉtatQueue::new(cx, "Droite")],
|
||||||
);
|
);
|
||||||
let supprime_queue = create_effect(cx, || {
|
create_effect(cx, || {
|
||||||
for q in queues.get().iter() {
|
for q in queues.get().iter() {
|
||||||
q.à_détruire.track()
|
q.à_détruire.track()
|
||||||
}
|
}
|
||||||
queues.modify().retain(|q| !(*q.à_détruire.get()));
|
queues.modify().retain(|q| !(*q.à_détruire.get()));
|
||||||
});
|
});
|
||||||
|
|
||||||
let index_queues = create_memo(cx, {
|
|
||||||
|| (0..queues.get().len()).into_iter().collect::<Vec<_>>()
|
|
||||||
});
|
|
||||||
let total_priorités: &ReadSignal<usize> = create_memo(cx, {
|
let total_priorités: &ReadSignal<usize> = create_memo(cx, {
|
||||||
let queues = queues.clone();
|
let queues = queues.clone();
|
||||||
move || {
|
move || {
|
||||||
|
|
@ -233,14 +298,13 @@ fn main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let queue_courante: &ReadSignal<(usize, usize)> = create_memo(cx, {
|
let queue_courante: &ReadSignal<(usize, usize)> = create_memo(cx, {
|
||||||
let queues = queues.clone();
|
|
||||||
move || {
|
move || {
|
||||||
let (idx, (id, _q)) = queues
|
let (idx, (id, _q)) = queues
|
||||||
.get()
|
.get()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|q| (q.id, *q.crédit_parole.get()))
|
.map(|q| (q.id, *q.crédit_parole.get()))
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.max_by(|(ia, (id_a, a)), (ib, (id_b, b))| {
|
.max_by(|(_ia, (_id_a, a)), (_ib, (_id_b, b))| {
|
||||||
a.partial_cmp(b).expect("Tried to compare a NaN")
|
a.partial_cmp(b).expect("Tried to compare a NaN")
|
||||||
})
|
})
|
||||||
.expect("bla");
|
.expect("bla");
|
||||||
|
|
@ -248,33 +312,24 @@ fn main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
provide_context_ref(cx, queue_courante);
|
provide_context_ref(cx, queue_courante);
|
||||||
let next = {
|
let pause = move |_ev| {
|
||||||
let mut queues = queues.clone();
|
courantə.set(None);
|
||||||
let queue_courante = queue_courante.clone();
|
};
|
||||||
move |_ev| {
|
let next = move |_ev| {
|
||||||
//js_sys::alert("coucou");
|
//js_sys::alert("coucou");
|
||||||
let mut queue: ÉtatQueue = queues.get()[queue_courante.get().0].clone();
|
let mut queue: ÉtatQueue = queues.get()[queue_courante.get().0].clone();
|
||||||
match queue.dequeue() {
|
match queue.dequeue() {
|
||||||
Some(p) => {
|
Some(p) => {
|
||||||
courantə.set(Some(p));
|
courantə.set(Some(p));
|
||||||
date_prise_parole.set(js_sys::Date::now())
|
date_prise_parole.set(js_sys::Date::now())
|
||||||
}
|
|
||||||
None => (),
|
|
||||||
}
|
}
|
||||||
for q in queues.get().iter() {
|
None => {
|
||||||
*q.crédit_parole.modify() +=
|
courantə.set(None);
|
||||||
(*q.priorité.get() as f32) / (*total_priorités.get() as f32)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
for q in queues.get().iter() {
|
||||||
|
*q.crédit_parole.modify() +=
|
||||||
let nom_nouvelle_file = create_signal(cx, String::new());
|
(*q.priorité.get() as f32) / (*total_priorités.get() as f32)
|
||||||
let nouvelle_file = {
|
|
||||||
let mut queues = queues.clone();
|
|
||||||
move |_ev| {
|
|
||||||
let q = ÉtatQueue::new(cx, &nom_nouvelle_file.get());
|
|
||||||
queues.modify().push(q);
|
|
||||||
*nom_nouvelle_file.modify() = String::new();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -294,14 +349,16 @@ fn main() {
|
||||||
match &*courantə.get() {
|
match &*courantə.get() {
|
||||||
None => "En attente".into(),
|
None => "En attente".into(),
|
||||||
Some(p) => {
|
Some(p) => {
|
||||||
let temps_parole = std::time::Duration::from_millis((*heure_courante.get() - *date_prise_parole.get()) as u64);
|
let temps_parole = std::time::Duration::from_millis((*heure_courante.get() - *date_prise_parole.get()) as u64).as_secs();
|
||||||
format!("On écoute {} depuis {:2}:{:02}", p, 0, temps_parole.as_secs())
|
format!("On écoute {} depuis {:2}:{:02}", p, temps_parole / 60, temps_parole % 60)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
br {}
|
br {}
|
||||||
button(on:click=next) { "Suivantə" }
|
button(on:click=next) { "Suivantə" }
|
||||||
|
" "
|
||||||
|
button(on:click=pause) { "Pause" }
|
||||||
}
|
}
|
||||||
section(id="les_queues") {
|
section(id="les_queues") {
|
||||||
Keyed {
|
Keyed {
|
||||||
|
|
@ -319,37 +376,14 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
br {}
|
||||||
aside {
|
|
||||||
button(on:click=|_| conf_visible.set(! *conf_visible.get())) {
|
Explications {}
|
||||||
(
|
|
||||||
if *conf_visible.get() {
|
|
||||||
"cacher"
|
|
||||||
} else {
|
|
||||||
"configuration"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
div(class=if *conf_visible.get() { "visible" } else { "hidden" }) {
|
|
||||||
h2 { "Configuration" }
|
|
||||||
Keyed {
|
|
||||||
iterable: queues,
|
|
||||||
key: |q| q.id,
|
|
||||||
view: |cx, q| view!{cx, ConfigQueue{ q }}
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
h3 {"Nouvelle file" }
|
|
||||||
input(type="text", bind:value=nom_nouvelle_file, on:keypress=move |ev: web_sys::Event| {
|
|
||||||
let event : web_sys::KeyboardEvent = ev.clone().unchecked_into();
|
|
||||||
if event.key_code() == 13 && !event.ctrl_key() {
|
|
||||||
nouvelle_file(ev)
|
|
||||||
}
|
|
||||||
}) {}
|
|
||||||
button(on:click=nouvelle_file) { "Créer" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
BarreCôté {
|
||||||
|
queues
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue