1
use serde::{Deserialize, Serialize};
2
use wasm_bindgen::prelude::*;
3

            
4
pub mod autocomplete;
5
pub mod data_attrs;
6
pub mod entity_form_tag;
7
pub mod entity_tag;
8
pub mod htmx;
9
pub mod report;
10
pub mod transaction;
11
pub mod types;
12

            
13
pub use entity_form_tag::{add_entity_form_tag, load_existing_entity_tags, remove_entity_form_tag};
14
pub use entity_tag::{
15
    add_entity_tag, cancel_entity_tag, delete_entity_tag, edit_entity_tag, filter_entity_tags,
16
};
17
pub use report::{
18
    add_activity_group, add_report_account_filter, add_report_tag_filter, add_report_tag_group,
19
    init_column_sorting, init_report_charts, init_tree_collapse, remove_activity_group,
20
    remove_report_filter_item, restore_all_filters, restore_all_groups, sync_activity_groups_mode,
21
    sync_tag_filter_mode,
22
};
23
pub use transaction::{add_split_tag, get_split_count, load_existing_split_tags, remove_split_tag};
24
pub use types::*;
25

            
26
#[derive(Serialize, Deserialize)]
27
pub struct WasmStatus {
28
    loaded: bool,
29
    version: String,
30
    message: String,
31
}
32

            
33
#[wasm_bindgen]
34
#[must_use]
35
pub fn get_wasm_status() -> JsValue {
36
    let status = WasmStatus {
37
        loaded: true,
38
        version: env!("CARGO_PKG_VERSION").to_string(),
39
        message: "WASM module loaded successfully".to_string(),
40
    };
41
    serde_wasm_bindgen::to_value(&status).unwrap()
42
}
43

            
44
#[cfg(feature = "auto-init")]
45
#[wasm_bindgen(start)]
46
pub fn main() {
47
    console_error_panic_hook::set_once();
48
    register_global_functions();
49
    htmx::setup_htmx_validation();
50
    setup_htmx_listener();
51

            
52
    let Some(document) = web_sys::window().and_then(|w| w.document()) else {
53
        return;
54
    };
55

            
56
    if document.ready_state() == "loading" {
57
        let callback = wasm_bindgen::closure::Closure::wrap(Box::new(|| {
58
            restore_all_filters();
59
            restore_all_groups();
60
            autocomplete::init_all();
61
            init_transaction_form();
62
            init_entity_tag_forms();
63
            init_report_controls();
64
        }) as Box<dyn Fn()>);
65

            
66
        let _ = document.add_event_listener_with_callback(
67
            "DOMContentLoaded",
68
            callback.as_ref().unchecked_ref(),
69
        );
70
        callback.forget();
71
    } else {
72
        restore_all_filters();
73
        restore_all_groups();
74
        autocomplete::init_all();
75
        init_transaction_form();
76
        init_entity_tag_forms();
77
        init_report_controls();
78
    }
79
}
80

            
81
#[cfg(feature = "auto-init")]
82
fn init_report_controls() {
83
    let Some(document) = web_sys::window().and_then(|w| w.document()) else {
84
        return;
85
    };
86
    let Some(body) = document.body() else {
87
        return;
88
    };
89
    let root: &web_sys::Element = body.as_ref();
90
    init_tree_collapse(root);
91
    init_column_sorting(root);
92
    init_report_charts(root);
93
}
94

            
95
#[cfg(feature = "auto-init")]
96
fn register_global_functions() {
97
    use wasm_bindgen::JsCast;
98

            
99
    let Some(window) = web_sys::window() else {
100
        return;
101
    };
102

            
103
    let get_split_count = Closure::wrap(Box::new(get_split_count) as Box<dyn Fn() -> u32>);
104
    let _ = js_sys::Reflect::set(
105
        &window,
106
        &"getSplitCount".into(),
107
        get_split_count.as_ref().unchecked_ref(),
108
    );
109
    get_split_count.forget();
110

            
111
    let add_split_tag = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
112
        add_split_tag(&el);
113
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
114
    let _ = js_sys::Reflect::set(
115
        &window,
116
        &"addSplitTag".into(),
117
        add_split_tag.as_ref().unchecked_ref(),
118
    );
119
    add_split_tag.forget();
120

            
121
    let remove_split_tag = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
122
        remove_split_tag(&el);
123
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
124
    let _ = js_sys::Reflect::set(
125
        &window,
126
        &"removeSplitTag".into(),
127
        remove_split_tag.as_ref().unchecked_ref(),
128
    );
129
    remove_split_tag.forget();
130

            
131
    let add_tag_filter = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
132
        add_report_tag_filter(&el);
133
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
134
    let _ = js_sys::Reflect::set(
135
        &window,
136
        &"addReportTagFilter".into(),
137
        add_tag_filter.as_ref().unchecked_ref(),
138
    );
139
    add_tag_filter.forget();
140

            
141
    let add_account_filter = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
142
        add_report_account_filter(&el);
143
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
144
    let _ = js_sys::Reflect::set(
145
        &window,
146
        &"addReportAccountFilter".into(),
147
        add_account_filter.as_ref().unchecked_ref(),
148
    );
149
    add_account_filter.forget();
150

            
151
    let add_tag_group = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
152
        add_report_tag_group(&el);
153
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
154
    let _ = js_sys::Reflect::set(
155
        &window,
156
        &"addReportTagGroup".into(),
157
        add_tag_group.as_ref().unchecked_ref(),
158
    );
159
    add_tag_group.forget();
160

            
161
    let remove_filter_item = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
162
        remove_report_filter_item(&el);
163
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
164
    let _ = js_sys::Reflect::set(
165
        &window,
166
        &"removeReportFilterItem".into(),
167
        remove_filter_item.as_ref().unchecked_ref(),
168
    );
169
    remove_filter_item.forget();
170

            
171
    let sync_mode = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
172
        sync_tag_filter_mode(&el);
173
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
174
    let _ = js_sys::Reflect::set(
175
        &window,
176
        &"syncTagFilterMode".into(),
177
        sync_mode.as_ref().unchecked_ref(),
178
    );
179
    sync_mode.forget();
180

            
181
    let add_activity_group_fn = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
182
        add_activity_group(&el);
183
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
184
    let _ = js_sys::Reflect::set(
185
        &window,
186
        &"addActivityGroup".into(),
187
        add_activity_group_fn.as_ref().unchecked_ref(),
188
    );
189
    add_activity_group_fn.forget();
190

            
191
    let remove_activity_group_fn = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
192
        remove_activity_group(&el);
193
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
194
    let _ = js_sys::Reflect::set(
195
        &window,
196
        &"removeActivityGroup".into(),
197
        remove_activity_group_fn.as_ref().unchecked_ref(),
198
    );
199
    remove_activity_group_fn.forget();
200

            
201
    let sync_groups_mode = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
202
        sync_activity_groups_mode(&el);
203
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
204
    let _ = js_sys::Reflect::set(
205
        &window,
206
        &"syncActivityGroupsMode".into(),
207
        sync_groups_mode.as_ref().unchecked_ref(),
208
    );
209
    sync_groups_mode.forget();
210

            
211
    let add_entity_tag_fn = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
212
        add_entity_tag(&el);
213
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
214
    let _ = js_sys::Reflect::set(
215
        &window,
216
        &"addEntityTag".into(),
217
        add_entity_tag_fn.as_ref().unchecked_ref(),
218
    );
219
    add_entity_tag_fn.forget();
220

            
221
    let edit_entity_tag_fn = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
222
        edit_entity_tag(&el);
223
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
224
    let _ = js_sys::Reflect::set(
225
        &window,
226
        &"editEntityTag".into(),
227
        edit_entity_tag_fn.as_ref().unchecked_ref(),
228
    );
229
    edit_entity_tag_fn.forget();
230

            
231
    let cancel_entity_tag_fn = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
232
        cancel_entity_tag(&el);
233
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
234
    let _ = js_sys::Reflect::set(
235
        &window,
236
        &"cancelEntityTag".into(),
237
        cancel_entity_tag_fn.as_ref().unchecked_ref(),
238
    );
239
    cancel_entity_tag_fn.forget();
240

            
241
    let delete_entity_tag_fn = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
242
        delete_entity_tag(&el);
243
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
244
    let _ = js_sys::Reflect::set(
245
        &window,
246
        &"deleteEntityTag".into(),
247
        delete_entity_tag_fn.as_ref().unchecked_ref(),
248
    );
249
    delete_entity_tag_fn.forget();
250

            
251
    let filter_entity_tags_fn = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
252
        filter_entity_tags(&el);
253
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
254
    let _ = js_sys::Reflect::set(
255
        &window,
256
        &"filterEntityTags".into(),
257
        filter_entity_tags_fn.as_ref().unchecked_ref(),
258
    );
259
    filter_entity_tags_fn.forget();
260

            
261
    let add_entity_form_tag_fn = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
262
        add_entity_form_tag(&el);
263
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
264
    let _ = js_sys::Reflect::set(
265
        &window,
266
        &"addEntityFormTag".into(),
267
        add_entity_form_tag_fn.as_ref().unchecked_ref(),
268
    );
269
    add_entity_form_tag_fn.forget();
270

            
271
    let remove_entity_form_tag_fn = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
272
        remove_entity_form_tag(&el);
273
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
274
    let _ = js_sys::Reflect::set(
275
        &window,
276
        &"removeEntityFormTag".into(),
277
        remove_entity_form_tag_fn.as_ref().unchecked_ref(),
278
    );
279
    remove_entity_form_tag_fn.forget();
280
}
281

            
282
#[cfg(feature = "auto-init")]
283
fn init_transaction_form() {
284
    let Some(document) = web_sys::window().and_then(|w| w.document()) else {
285
        return;
286
    };
287

            
288
    let is_transaction_page = document
289
        .query_selector("#splits-container")
290
        .ok()
291
        .flatten()
292
        .is_some();
293

            
294
    if is_transaction_page {
295
        transaction::setup_field_validation();
296
        transaction::setup_form_submit_handler();
297
        transaction::setup_split_handlers();
298
        transaction::initialize_transaction_form();
299
        transaction::load_existing_split_tags();
300
    }
301
}
302

            
303
#[cfg(feature = "auto-init")]
304
fn init_entity_tag_forms() {
305
    let Some(document) = web_sys::window().and_then(|w| w.document()) else {
306
        return;
307
    };
308

            
309
    let has_entity_tags = document
310
        .query_selector(".entity-tags-container")
311
        .ok()
312
        .flatten()
313
        .is_some();
314

            
315
    if has_entity_tags {
316
        transaction::setup_form_submit_handler();
317
        entity_form_tag::load_existing_entity_tags();
318
    }
319
}
320

            
321
#[cfg(feature = "auto-init")]
322
fn setup_htmx_listener() {
323
    let Some(document) = web_sys::window().and_then(|w| w.document()) else {
324
        return;
325
    };
326

            
327
    let callback = Closure::wrap(Box::new(|_: web_sys::Event| {
328
        restore_all_filters();
329
        restore_all_groups();
330
        autocomplete::init_all();
331
        init_report_controls();
332
    }) as Box<dyn FnMut(_)>);
333

            
334
    let _ = document
335
        .add_event_listener_with_callback("htmx:afterSwap", callback.as_ref().unchecked_ref());
336
    callback.forget();
337
}