mpc_valet/components/
layer_select_form.rs

1use crate::components::{Icon, LayerSelect};
2use crate::model::LayerFile;
3use gloo_storage::{LocalStorage, Storage};
4use itertools::Itertools;
5use serde::{Deserialize, Serialize};
6use yew::{html, Callback, Component, Context, Html, Properties};
7
8use crate::model::SampleFile;
9
10#[derive(Properties, PartialEq)]
11pub struct LayerSelectFormProps {
12    #[prop_or_default]
13    pub files: Vec<SampleFile>,
14
15    pub on_selected: Callback<Vec<LayerFile>>,
16
17    pub on_cancel: Callback<()>,
18}
19
20pub enum LayerSelectFormMessages {
21    LayerChanged(usize, usize),
22    AllLayerChanged(usize),
23    Swap(usize, usize),
24    Done,
25    Reset,
26    Cancel,
27}
28
29/// Select the layer for a list of sample files.
30#[derive(Default, Serialize, Deserialize)]
31pub struct LayerSelectForm {
32    pub layer_files: Vec<LayerFile>,
33}
34
35impl From<Vec<SampleFile>> for LayerSelectForm {
36    fn from(sample_files: Vec<SampleFile>) -> Self {
37        // Initiate the list of layer files from the list of files with roots
38        let layer_files: Vec<LayerFile> = sample_files
39            .iter()
40            // Sort by root note (group_by needs it)
41            .sorted_by(|a, b| a.root.cmp(&b.root))
42            // group by root note
43            .group_by(|f| f.root)
44            .into_iter()
45            .flat_map(|(_, group)| {
46                group
47                    // Sort each note with the same root per file name
48                    .sorted_by(|a, b| a.file.cmp(&b.file))
49                    .enumerate()
50                    // Assign a different layer to each note with the same root,
51                    // based on the sample alphabetical order
52                    .map(|(index, file)| LayerFile::from_sample_file(file.clone(), index % 4))
53            })
54            .sorted_by(|a, b| a.file.cmp(&b.file))
55            .collect();
56        Self { layer_files }
57    }
58}
59
60impl Component for LayerSelectForm {
61    type Message = LayerSelectFormMessages;
62    type Properties = LayerSelectFormProps;
63
64    fn create(ctx: &Context<Self>) -> Self {
65        LocalStorage::get("layer_select_form").unwrap_or_else(|_| ctx.props().files.clone().into())
66    }
67
68    fn destroy(&mut self, _ctx: &Context<Self>) {
69        LocalStorage::delete("layer_select_form");
70    }
71
72    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
73        let redraw = match msg {
74            LayerSelectFormMessages::LayerChanged(index, layer) => {
75                self.layer_files[index].layer = layer;
76                true
77            }
78            LayerSelectFormMessages::AllLayerChanged(layer) => {
79                self.layer_files.iter_mut().for_each(|l| l.layer = layer);
80                true
81            }
82            LayerSelectFormMessages::Swap(layer1, layer2) => {
83                self.layer_files.iter_mut().for_each(|f| {
84                    f.layer = if f.layer == layer1 {
85                        layer2
86                    } else if f.layer == layer2 {
87                        layer1
88                    } else {
89                        f.layer
90                    };
91                });
92                true
93            }
94            LayerSelectFormMessages::Done => {
95                ctx.props().on_selected.emit(self.layer_files.clone());
96                false
97            }
98
99            LayerSelectFormMessages::Cancel => {
100                ctx.props().on_cancel.emit(());
101                false
102            }
103            LayerSelectFormMessages::Reset => {
104                *self = ctx.props().files.clone().into();
105                true
106            }
107        };
108        LocalStorage::set("layer_select_form", self).unwrap_or_else(|e| {
109            log::error!("{e}");
110        });
111        redraw
112    }
113
114    fn view(&self, ctx: &Context<Self>) -> Html {
115        let samples: Vec<Html> = self
116            .layer_files
117            .iter()
118            .enumerate()
119            .map(|(index, sample)| {
120                html! {
121                    <LayerSelect
122                        label={sample.file.clone()}
123                        initial={sample.layer}
124                        selection_changed={ctx.link().callback(move |layer: usize| LayerSelectFormMessages::LayerChanged(index, layer))}
125                    />
126                }
127            })
128            .collect();
129
130        let used_layers: Vec<usize> = self
131            .layer_files
132            .iter()
133            .map(|f| f.layer)
134            .unique()
135            .collect_vec();
136
137        let all_layers = match used_layers.len() {
138            1 => Some(used_layers[0]),
139            _ => None,
140        };
141
142        html! {
143            <div class="modal is-active">
144                <div class="modal-background"></div>
145                <div class="modal-card">
146                    <header class="modal-card-head">
147                        <Icon icon="layers" text="Select layers" text_class="modal-card-title" />
148                        <button class="delete" aria-label="close" onclick={ctx.link().callback(|_| LayerSelectFormMessages::Cancel)}></button>
149                    </header>
150                    <section class="modal-card-body">
151                        <LayerSelect
152                            label={"All"}
153                            initial={all_layers}
154                            selection_changed={ctx.link().callback(LayerSelectFormMessages::AllLayerChanged)}
155                        />
156                        {samples}
157                        <div class="columns">
158                            <div class="column is-one-quarter">
159                                {"Swap Layers"}
160                            </div>
161                            <div class="column">
162                                <div class="buttons has-addons is-centered">
163                                    <button class="button" onclick={ctx.link().callback(|_| LayerSelectFormMessages::Swap(0,1))}>
164                                        <Icon icon="swap-horizontal-outline" text="Swap 1-2" />
165                                    </button>
166                                    <button class="button" onclick={ctx.link().callback(|_| LayerSelectFormMessages::Swap(1,2))}>
167                                        <Icon icon="swap-horizontal-outline" text="Swap 2-3" />
168                                    </button>
169                                    <button class="button" onclick={ctx.link().callback(|_| LayerSelectFormMessages::Swap(2,3))}>
170                                        <Icon icon="swap-horizontal-outline" text="Swap 3-4" />
171                                    </button>
172                                </div>
173                            </div>
174                        </div>
175                    </section>
176                    <footer class="modal-card-foot">
177                        <div class="buttons has-addons">
178                            <button class="button" onclick={ctx.link().callback(|_| LayerSelectFormMessages::Cancel)}>
179                                <Icon icon="trash" text ="Cancel" />
180                            </button>
181                            <button class="button" onclick={ctx.link().callback(|_| LayerSelectFormMessages::Reset)}>
182                                <Icon icon="refresh" text="Reset" />
183                            </button>
184                            <button class="button is-success" onclick={ctx.link().callback(|_| LayerSelectFormMessages::Done)}>{"Ok"}</button>
185                        </div>
186                    </footer>
187                </div>
188            </div>
189        }
190    }
191
192    fn changed(&mut self, ctx: &Context<Self>) -> bool {
193        *self = ctx.props().files.clone().into();
194        true
195    }
196}