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_report_account_filter, add_report_tag_filter, add_report_tag_group,
19
    remove_report_filter_item, restore_all_filters, sync_tag_filter_mode,
20
};
21
pub use transaction::{add_split_tag, get_split_count, load_existing_split_tags, remove_split_tag};
22
pub use types::*;
23

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

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

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

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

            
54
    if document.ready_state() == "loading" {
55
        let callback = wasm_bindgen::closure::Closure::wrap(Box::new(|| {
56
            restore_all_filters();
57
            autocomplete::init_all();
58
            init_transaction_form();
59
        }) as Box<dyn Fn()>);
60

            
61
        let _ = document.add_event_listener_with_callback(
62
            "DOMContentLoaded",
63
            callback.as_ref().unchecked_ref(),
64
        );
65
        callback.forget();
66
    } else {
67
        restore_all_filters();
68
        autocomplete::init_all();
69
        init_transaction_form();
70
    }
71
}
72

            
73
#[cfg(feature = "auto-init")]
74
fn register_global_functions() {
75
    use wasm_bindgen::JsCast;
76

            
77
    let Some(window) = web_sys::window() else {
78
        return;
79
    };
80

            
81
    let get_split_count = Closure::wrap(Box::new(get_split_count) as Box<dyn Fn() -> u32>);
82
    let _ = js_sys::Reflect::set(
83
        &window,
84
        &"getSplitCount".into(),
85
        get_split_count.as_ref().unchecked_ref(),
86
    );
87
    get_split_count.forget();
88

            
89
    let add_split_tag = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
90
        add_split_tag(&el);
91
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
92
    let _ = js_sys::Reflect::set(
93
        &window,
94
        &"addSplitTag".into(),
95
        add_split_tag.as_ref().unchecked_ref(),
96
    );
97
    add_split_tag.forget();
98

            
99
    let remove_split_tag = Closure::wrap(Box::new(|el: web_sys::HtmlElement| {
100
        remove_split_tag(&el);
101
    }) as Box<dyn Fn(web_sys::HtmlElement)>);
102
    let _ = js_sys::Reflect::set(
103
        &window,
104
        &"removeSplitTag".into(),
105
        remove_split_tag.as_ref().unchecked_ref(),
106
    );
107
    remove_split_tag.forget();
108

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

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

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

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

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

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

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

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

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

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

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

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

            
230
#[cfg(feature = "auto-init")]
231
fn init_transaction_form() {
232
    let Some(document) = web_sys::window().and_then(|w| w.document()) else {
233
        return;
234
    };
235

            
236
    let is_transaction_page = document
237
        .query_selector("#splits-container")
238
        .ok()
239
        .flatten()
240
        .is_some();
241

            
242
    if is_transaction_page {
243
        transaction::setup_field_validation();
244
        transaction::setup_form_submit_handler();
245
        transaction::setup_split_handlers();
246
        transaction::initialize_transaction_form();
247
        transaction::load_existing_split_tags();
248
    }
249
}
250

            
251
#[cfg(feature = "auto-init")]
252
fn init_entity_tag_forms() {
253
    let Some(document) = web_sys::window().and_then(|w| w.document()) else {
254
        return;
255
    };
256

            
257
    let has_entity_tags = document
258
        .query_selector(".entity-tags-container")
259
        .ok()
260
        .flatten()
261
        .is_some();
262

            
263
    if has_entity_tags {
264
        transaction::setup_form_submit_handler();
265
        entity_form_tag::load_existing_entity_tags();
266
    }
267
}
268

            
269
#[cfg(feature = "auto-init")]
270
fn setup_htmx_listener() {
271
    let Some(document) = web_sys::window().and_then(|w| w.document()) else {
272
        return;
273
    };
274

            
275
    let callback = Closure::wrap(Box::new(|_: web_sys::Event| {
276
        restore_all_filters();
277
        autocomplete::init_all();
278
    }) as Box<dyn FnMut(_)>);
279

            
280
    let _ = document
281
        .add_event_listener_with_callback("htmx:afterSwap", callback.as_ref().unchecked_ref());
282
    callback.forget();
283
}