From 4317ac27e3d5dd81029efa21e0174cb6b8e34bce Mon Sep 17 00:00:00 2001 From: dr-frmr Date: Sun, 22 Sep 2024 00:56:49 -0400 Subject: [PATCH] persist ordering of apps as designed by user between boots --- kinode/packages/homepage/homepage/src/lib.rs | 38 ++++++--- .../homepage/ui/src/components/AllApps.tsx | 49 +++++++---- .../homepage/ui/src/components/AppDisplay.tsx | 8 +- .../homepage/ui/src/components/AppsDock.tsx | 82 ------------------- kinode/packages/homepage/ui/src/index.css | 2 +- .../homepage/ui/src/pages/Homepage.tsx | 10 +-- 6 files changed, 69 insertions(+), 120 deletions(-) delete mode 100644 kinode/packages/homepage/ui/src/components/AppsDock.tsx diff --git a/kinode/packages/homepage/homepage/src/lib.rs b/kinode/packages/homepage/homepage/src/lib.rs index 2894569f..4208eb24 100644 --- a/kinode/packages/homepage/homepage/src/lib.rs +++ b/kinode/packages/homepage/homepage/src/lib.rs @@ -1,10 +1,9 @@ -#![feature(let_chains)] use crate::kinode::process::homepage::{AddRequest, Request as HomepageRequest}; use kinode_process_lib::{ await_message, call_init, get_blob, http, http::server, println, Address, LazyLoadBlob, }; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; /// Fetching OS version from main package const CARGO_TOML: &str = include_str!("../../../../Cargo.toml"); @@ -26,9 +25,11 @@ struct HomepageApp { base64_icon: Option, widget: Option, order: u32, - favorite: bool, + favorite: bool, // **not currently used on frontend** } +type PersistedAppOrder = HashMap; + wit_bindgen::generate!({ path: "target/wit", world: "homepage-sys-v0", @@ -162,6 +163,11 @@ fn init(our: Address) { .bind_http_path("/order", http_config) .expect("failed to bind /order"); + // load persisted app order + let mut persisted_app_order = + kinode_process_lib::get_typed_state(|bytes| serde_json::from_slice(bytes)) + .unwrap_or(PersistedAppOrder::new()); + loop { let Ok(ref message) = await_message() else { // we never send requests, so this will never happen @@ -238,11 +244,16 @@ fn init(our: Address) { None, ); }; - for (app_id, order) in order_list { - if let Some(app) = app_data.get_mut(&app_id) { - app.order = order; + for (app_id, order) in &order_list { + if let Some(app) = app_data.get_mut(app_id) { + app.order = *order; } } + persisted_app_order = order_list.into_iter().collect(); + println!("updated order of apps: {:?}", persisted_app_order); + kinode_process_lib::set_state( + &serde_json::to_vec(&persisted_app_order).unwrap(), + ); (server::HttpResponse::new(http::StatusCode::OK), None) } _ => (server::HttpResponse::new(http::StatusCode::NOT_FOUND), None), @@ -264,10 +275,11 @@ fn init(our: Address) { path, widget, }) => { + let id = message.source().process.to_string(); app_data.insert( - message.source().process.to_string(), + id.clone(), HomepageApp { - id: message.source().process.to_string(), + id: id.clone(), process: message.source().process().to_string(), package: message.source().package().to_string(), publisher: message.source().publisher().to_string(), @@ -281,14 +293,20 @@ fn init(our: Address) { label, base64_icon: icon, widget, - order: app_data.len() as u32, + order: if let Some(order) = persisted_app_order.get(&id) { + *order + } else { + app_data.len() as u32 + }, favorite: DEFAULT_FAVES .contains(&message.source().process.to_string().as_str()), }, ); } HomepageRequest::Remove => { - app_data.remove(&message.source().process.to_string()); + let id = message.source().process.to_string(); + app_data.remove(&id); + persisted_app_order.remove(&id); } HomepageRequest::SetStylesheet(new_stylesheet_string) => { // ONLY settings:settings:sys may call this request diff --git a/kinode/packages/homepage/ui/src/components/AllApps.tsx b/kinode/packages/homepage/ui/src/components/AllApps.tsx index fbb8019d..aed89c73 100644 --- a/kinode/packages/homepage/ui/src/components/AllApps.tsx +++ b/kinode/packages/homepage/ui/src/components/AllApps.tsx @@ -1,14 +1,13 @@ import React, { useState, useEffect, useMemo } from "react"; -import useHomepageStore from "../store/homepageStore"; -import usePersistentStore from "../store/persistentStore"; +import useHomepageStore, { HomepageApp } from "../store/homepageStore"; import AppDisplay from "./AppDisplay"; const AllApps: React.FC = () => { const { apps } = useHomepageStore(); - const { appOrder, setAppOrder } = usePersistentStore(); const [expanded, setExpanded] = useState(false); const [isMobile, setIsMobile] = useState(false); const [visibleApps, setVisibleApps] = useState(5); + const [orderedApps, setOrderedApps] = useState([]); const [draggedIndex, setDraggedIndex] = useState(null); const [dragOverIndex, setDragOverIndex] = useState(null); @@ -26,13 +25,14 @@ const AllApps: React.FC = () => { // Sort apps based on persisted order const sortedApps = useMemo(() => { - const orderedApps = [...apps].sort((a, b) => { - return appOrder.indexOf(a.id) - appOrder.indexOf(b.id); + if (!orderedApps.length) { + setOrderedApps(apps); + } + const o = [...orderedApps].sort((a, b) => { + return a.order - b.order; }); - // Ensure all apps are included in the order - const missingApps = apps.filter((app) => !appOrder.includes(app.id)); - return [...orderedApps, ...missingApps]; - }, [apps, appOrder]); + return o; + }, [orderedApps, apps]); const displayedApps = expanded ? sortedApps @@ -67,29 +67,42 @@ const AllApps: React.FC = () => { const [movedApp] = newSortedApps.splice(dragIndex, 1); newSortedApps.splice(dropIndex, 0, movedApp); - const newAppOrder = newSortedApps.map((app) => app.id); - setAppOrder(newAppOrder); + const updatedApps = newSortedApps.map((app, index) => ({ + ...app, + order: index + })); + + setOrderedApps(updatedApps); + + // Sync the order with the backend + fetch('/order', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + credentials: 'include', + body: JSON.stringify(newSortedApps.map((app, index) => [app.id, index])) + }); + handleDragEnd(); }; return (
{displayedApps.map((app, index) => (
handleDragStart(e, index)} onDragOver={(e) => handleDragOver(e, index)} onDragEnd={handleDragEnd} onDrop={(e) => handleDrop(e, index)} - className={`app-wrapper ${ - draggedIndex === index ? "dragging" : "" - } ${dragOverIndex === index ? "drag-over" : ""}`} + className={`app-wrapper ${draggedIndex === index ? "dragging" : "" + } ${dragOverIndex === index ? "drag-over" : ""}`} >
⋮⋮
diff --git a/kinode/packages/homepage/ui/src/components/AppDisplay.tsx b/kinode/packages/homepage/ui/src/components/AppDisplay.tsx index 011a392d..84096b4d 100644 --- a/kinode/packages/homepage/ui/src/components/AppDisplay.tsx +++ b/kinode/packages/homepage/ui/src/components/AppDisplay.tsx @@ -14,10 +14,10 @@ const AppDisplay: React.FC = ({ app }) => { style={ !app?.path ? { - pointerEvents: "none", - textDecoration: "none !important", - filter: "grayscale(100%)", - } + pointerEvents: "none", + textDecoration: "none !important", + filter: "grayscale(100%)", + } : {} } > diff --git a/kinode/packages/homepage/ui/src/components/AppsDock.tsx b/kinode/packages/homepage/ui/src/components/AppsDock.tsx deleted file mode 100644 index 8604c144..00000000 --- a/kinode/packages/homepage/ui/src/components/AppsDock.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import useHomepageStore, { HomepageApp } from "../store/homepageStore" -import usePersistentStore from "../store/persistentStore" -import AppDisplay from "./AppDisplay" -import { useEffect, useState } from "react" -import { DragDropContext, Draggable, DropResult, Droppable } from '@hello-pangea/dnd' - -const AppsDock: React.FC = () => { - const { apps } = useHomepageStore() - const { appOrder, setAppOrder } = usePersistentStore() - const [dockedApps, setDockedApps] = useState([]) - - useEffect(() => { - // Sort apps based on persisted order - const orderedApps = apps.filter(app => app.favorite).sort((a, b) => { - return appOrder.indexOf(a.id) - appOrder.indexOf(b.id); - }); - setDockedApps(orderedApps); - - // Sync the order with the backend - fetch('/order', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'include', - body: JSON.stringify(orderedApps.map(app => [app.id, appOrder.indexOf(app.id)])) - }); - }, [apps, appOrder]) - - const onDragEnd = (result: DropResult) => { - if (!result.destination) { - return; - } - - const reorderedApps = Array.from(dockedApps); - const [reorderedItem] = reorderedApps.splice(result.source.index, 1); - reorderedApps.splice(result.destination.index, 0, reorderedItem); - - const newAppOrder = reorderedApps.map(app => app.id); - setAppOrder(newAppOrder); - setDockedApps(reorderedApps); - - fetch('/order', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'include', - body: JSON.stringify(reorderedApps.map((app, index) => [app.id, index])) - }); - } - - return - - {(provided, _snapshot) => ( -
- {dockedApps.map((app, index) => - {(provided, _snapshot) => ( -
- -
- )} -
)} -
- )} -
-
-} - -export default AppsDock \ No newline at end of file diff --git a/kinode/packages/homepage/ui/src/index.css b/kinode/packages/homepage/ui/src/index.css index feac298b..54a9e770 100644 --- a/kinode/packages/homepage/ui/src/index.css +++ b/kinode/packages/homepage/ui/src/index.css @@ -174,7 +174,7 @@ footer { border-radius: 0 0 1em 1em; border: 0.5px solid rgba(255, 255, 255, 0.2); padding: 1em; - color: light-dark(var(--white), var(--tasteful-dark)); + color: var(--white); } @media (max-width: 1024px) { diff --git a/kinode/packages/homepage/ui/src/pages/Homepage.tsx b/kinode/packages/homepage/ui/src/pages/Homepage.tsx index 96c53237..e6a775b1 100644 --- a/kinode/packages/homepage/ui/src/pages/Homepage.tsx +++ b/kinode/packages/homepage/ui/src/pages/Homepage.tsx @@ -46,13 +46,13 @@ function Homepage() {

{new Date().getHours() < 4 - ? "Good evening" + ? "Good evening" // midnight to 4am : new Date().getHours() < 12 - ? "Good morning" + ? "Good morning" // 4am to 11am : new Date().getHours() < 18 - ? "Good afternoon" - : "Good evening"} - , {our} + ? "Good afternoon" // 12pm to 5pm + : "Good evening" // 5pm to midnight + }, {our}