Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions assets/droid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { droid, modUrls } from 'https://esm.sh/@kbve/droid';
import * as comlink from 'https://esm.sh/comlink';

(async () => {
console.log('[DROID] init Library');

const workerStrings = {
canvasWorker: 'https://esm.sh/@kbve/droid/lib/workers/canvas-worker.js',
dbWorker: 'https://esm.sh/@kbve/droid/lib/workers/db-worker.js',
wsWorker: 'https://esm.sh/@kbve/droid/lib/workers/ws-worker.js',
};

await droid({ workerURLs: workerStrings });

const mod = window.kbve?.mod;
const emitFromWorker = window.kbve?.uiux?.emitFromWorker;

if (!mod) {
console.error('[KBVE] Mod manager not available');
return;
}

const bentoMod = await mod.load(modUrls.bento);

if (bentoMod?.instance?.init && typeof emitFromWorker === 'function') {
await bentoMod.instance.init(comlink.proxy({ emitFromWorker }));
}

if (bentoMod?.meta) {
window.kbve?.events?.emit('droid-mod-ready', {
meta: bentoMod.meta,
timestamp: Date.now(),
});
}

console.log('[KBVE] Bento mod loaded');
})();
5 changes: 5 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

<link data-trunk rel="icon" href="assets/favicon.ico" />


<!-- Worker -> SupaBase -->
<!--
Anon Key : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFtcGRydWl0emxvd25ubm5qbXBrIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDk2NjA0NTYsImV4cCI6MjA2NTIzNjQ1Nn0.OhD3qN4dq0TMA65qVGvry_QsZEeLKK7RbwYP3QzAvcY
Expand Down Expand Up @@ -225,6 +226,10 @@
})();
</script>

<link data-trunk rel="copy-file" href="assets/droid.js" />
<script type="module">
import './droid.js';
</script>
<link data-trunk rel="copy-file" href="assets/sw.js" />
<link data-trunk rel="copy-file" href="assets/manifest.json" />
<link
Expand Down
122 changes: 73 additions & 49 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ pub struct TemplateApp {
show_auth_window: bool, // Track if auth window should be shown
#[serde(skip)]
show_welcome: bool, // Track if welcome window should be shown
#[serde(skip)]
needs_initial_state_check: bool, // Flag to check initial state
}

impl Default for TemplateApp {
Expand All @@ -94,6 +96,7 @@ impl Default for TemplateApp {
user: User::default(),
show_auth_window: false,
show_welcome: true, // Show welcome window by default
needs_initial_state_check: true, // Check initial state on new
}
}
}
Expand All @@ -112,6 +115,8 @@ impl TemplateApp {
app.load_filtered_repos_from_idb(&cc.egui_ctx);
// --- Call JSRust to request user info when app is ready ---
crate::erust::uiux::javascript_interop::request_user_from_js();
// Instead of unsafe async pointer, set a flag for initial state check
app.needs_initial_state_check = true;
app
}

Expand All @@ -129,42 +134,69 @@ impl TemplateApp {
});
}

async fn check_empty_and_update_state_async(&mut self) {
use crate::db::idb;
use crate::db::github::Repository;
if let Ok(db) = idb::open_waffle_db().await {
let language = self.db.get_language();
let key = format!("latest_{}", language.to_lowercase());
match idb::get_repo::<Vec<Repository>>(&db, &language, &key).await {
Ok(Some(repos)) => {
if repos.is_empty() {
self.pending_app_state = Some(AppState::Empty);
} else {
self.pending_app_state = Some(AppState::Normal);
pub fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Perform initial async state check if needed
if self.needs_initial_state_check {
self.needs_initial_state_check = false;
let ctx2 = ctx.clone();
wasm_bindgen_futures::spawn_local(async move {
use crate::db::idb;
use crate::db::github::Repository;
use crate::db::idb::LANGUAGES;
// use std::collections::HashMap;
let mut has_data = false;
if let Ok(db) = idb::open_waffle_db().await {
for &lang in LANGUAGES.iter() {
if let Ok(Some(r)) = idb::get_repo::<Vec<Repository>>(&db, lang, lang).await {
if !r.is_empty() {
has_data = true;
break;
}
}
}
},
Ok(None) => {
self.pending_app_state = Some(AppState::Empty);
},
Err(_) => {
self.pending_app_state = Some(AppState::Empty);
}
let new_state = if has_data { AppState::Normal } else { AppState::Empty };
// Store state in temp data for later use
ctx2.data_mut(|d| {
d.insert_temp(Id::new("waffle_initial_state"), new_state.clone());
// Set filtered_repos for the current language (will be loaded on demand)
d.insert_temp(Id::new("waffle_filtered_repos"), Vec::<Repository>::new());
// --- DEBUG: Add a toast message with state ---
d.insert_temp(Id::new("waffle_debug_toast"), format!("Initial state: {:?}", new_state));
});
ctx2.request_repaint();
});
}
// Apply initial state if set
let mut maybe_new_state = None;
ctx.data(|d| {
if let Some(new_state) = d.get_temp::<AppState>(Id::new("waffle_initial_state")) {
maybe_new_state = Some(new_state.clone());
}
});
if let Some(new_state) = maybe_new_state {
let was_empty = self.app_state == AppState::Empty;
self.app_state = new_state.clone();
// If we just transitioned to Normal, load repos for current language
if was_empty && new_state == AppState::Normal {
self.load_filtered_repos_from_idb(ctx);
}
} else {
self.pending_app_state = Some(AppState::Empty);
}
}

pub fn filter_repos_async(&mut self, query: &str, ctx: &egui::Context) {
self.filter_loading = true;
self.filtered_repos = None;
if let Some(widget) = &mut self.search_widget {
widget.query = query.to_string();
widget.search(&self.db.get_language(), ctx);
// --- Ensure filtered_repos is loaded if app_state is Normal and filtered_repos is empty ---
if self.app_state == AppState::Normal && (self.filtered_repos.is_none() || self.filtered_repos.as_ref().unwrap().is_empty()) {
let mut all_repos: Option<std::collections::HashMap<String, Vec<Repository>>> = None;
ctx.data(|d| {
if let Some(r) = d.get_temp::<std::collections::HashMap<String, Vec<Repository>>>(Id::new("waffle_all_repos")) {
all_repos = Some(r.clone());
}
});
if let Some(map) = all_repos {
let lang = self.db.get_language();
let repos = map.get(&lang).cloned().unwrap_or_default();
self.waffle_state.set_ready(repos.clone());
self.filtered_repos = Some(repos);
}
}
}

pub fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Toast timer logic
if let Some(_) = self.toast_message {
let dt = ctx.input(|i| i.unstable_dt);
Expand All @@ -175,6 +207,7 @@ impl TemplateApp {
}
// Loading state machine
let mut error_to_trigger: Option<String> = None;
let mut should_load_repos = None;
match &mut self.loading_state {
LoadingState::Idle => {},
LoadingState::Loading { kind, message: _, pending_language } => {
Expand All @@ -183,17 +216,8 @@ impl TemplateApp {
if let Some(lang) = pending_language.take() {
self.db.set_language(&lang);
self.db.load_from_indexeddb();
// Immediately reload filtered repos for the new language
let language = self.db.get_language();
let ctx = ctx.clone();
wasm_bindgen_futures::spawn_local(async move {
let result = match crate::db::idb::open_waffle_db().await {
Ok(db_conn) => crate::db::idb::filter_repos_in_idb::<Repository>(&db_conn, &language, "").await.unwrap_or_default(),
Err(_) => vec![],
};
ctx.data_mut(|d| d.insert_temp(Id::new("waffle_filtered_repos"), result));
ctx.request_repaint();
});
// Set flag to reload filtered repos for the new language after borrow ends
should_load_repos = Some(ctx.clone());
}
},
LoadingKind::Sync => {
Expand All @@ -203,6 +227,7 @@ impl TemplateApp {
db.sync_and_store();
db.load_from_indexeddb();
// After sync, reload filtered repos
crate::erust::uiux::javascript_interop::request_user_from_js();
let language = db.get_language();
let result = match crate::db::idb::open_waffle_db().await {
Ok(db_conn) => crate::db::idb::filter_repos_in_idb::<Repository>(&db_conn, &language, "").await.unwrap_or_default(),
Expand All @@ -215,6 +240,8 @@ impl TemplateApp {
LoadingKind::ClearCache => {
self.db.clear_indexeddb();
self.db.load_from_indexeddb();
// After clearing, clear filtered_repos too
self.filtered_repos = Some(Vec::new());
},
}
let message = match kind {
Expand All @@ -225,13 +252,6 @@ impl TemplateApp {
self.loading_state = LoadingState::Finishing { message: message.to_string() };
},
LoadingState::Finishing { .. } => {
let app_ptr = self as *mut TemplateApp;
wasm_bindgen_futures::spawn_local(async move {
// SAFETY: This is safe because update() is single-threaded in egui/eframe
unsafe {
(*app_ptr).check_empty_and_update_state_async().await;
}
});
// Show toast only if there is data after sync
if self.app_state == AppState::Normal {
self.toast_message = Some("Repositories synced!".to_owned());
Expand All @@ -252,6 +272,10 @@ impl TemplateApp {
if let Some(new_state) = self.pending_app_state.take() {
self.app_state = new_state;
}
// After match, do the actual repo load if needed
if let Some(ctx) = should_load_repos {
self.load_filtered_repos_from_idb(&ctx);
}
// Show loading spinner overlay if loading or finishing
match &self.loading_state {
LoadingState::Loading { message, .. } | LoadingState::Finishing { message } => {
Expand Down
Loading