`, so we use this wrapper for convenience
36 | PerseusRoot()
37 | // Note that elements in here can't be selectively removed from one page, it's all-or-nothing in the index view (it wraps your whole app)
38 | // Note also that this won't be reloaded, even when the user switches pages
39 | }
40 | }
41 | })
42 | .global_state_creator(crate::global_state::get_global_state_creator())
43 | }
44 |
--------------------------------------------------------------------------------
/loremaster-web-interface/src/utility/constants.rs:
--------------------------------------------------------------------------------
1 | pub const API_BASE_URL: &str = "https://localhost:8000";
2 | pub const API_REGISTER_URL: &str = "https://localhost:8000/authentication/register";
3 | pub const API_LOGIN_URL: &str = "https://localhost:8000/authentication/authenticate";
4 | pub const API_CHRONICLE_TODAY_URL: &str = "https://localhost:8000/chronicle/today";
5 | pub const API_PERSON_META_DATA_ROUTE: &str = "person/meta";
6 | pub const API_PERSON_META_UPDATE_ROUTE: &str = "person/update/meta";
7 | pub const API_PERSON_SLEEP_SCHEDULE_UPDATE_ROUTE: &str = "person/update/sleep-schedule";
8 | pub const API_PERSON_SLEEP_SCHEDULE_ROUTE: &str = "person/sleep-schedule";
9 | pub const API_PERSON_EMAIL_ADDRESS_UPDATE_ROUTE: &str = "person/update/email_address";
10 | pub const API_ACTION_NEW_ROUTE: &str = "person/action-new";
11 | pub const API_ACTION_LIST_ROUTE: &str = "person/action-list";
12 | pub const API_FREQUENCY_LIST_ROUTE: &str = "person/frequency-list";
13 | pub const API_LOGOUT_ROUTE: &str = "authentication/logout";
14 | pub const API_GOAL_NEW_ROUTE: &str = "person/goal-new";
15 | pub const API_GOAL_LIST_ROUTE: &str = "person/goal-list";
16 | pub const API_WEBAUTHN_START_ROUTE: &str = "authentication/webauthn/start";
17 | pub const API_WEBAUTHN_FINISH_ROUTE: &str = "authentication/webauthn/finish";
18 | pub const API_WEBAUTHN_LOGIN_START_ROUTE: &str = "authentication/webauthn/login/start";
19 | pub const API_WEBAUTHN_LOGIN_END_ROUTE: &str =
20 | "authentication/webauthn/login/finish/person_public_key_credential.json";
21 | pub const HTTP_HEADER_CONTENT_TYPE: &str = "Content-Type";
22 | pub const HTTP_HEADER_CONTENT_TYPE_FORM: &str = "application/x-www-form-urlencoded";
23 | pub const EMAIL_ADDRESS_FIELD: &str = "email_address";
24 | pub const PASSWORD_FIELD: &str = "password";
25 |
26 | pub const OK_HTTP_STATUS_CODE: u16 = 200;
27 | pub const ACCEPTED_HTTP_STATUS_CODE: u16 = 202;
28 |
29 | pub const JANUARY: &str = "January";
30 | pub const FEBRUARY: &str = "February";
31 | pub const MARCH: &str = "March";
32 | pub const APRIL: &str = "April";
33 | pub const MAY: &str = "May";
34 | pub const JUNE: &str = "June";
35 | pub const JULY: &str = "July";
36 | pub const AUGUST: &str = "August";
37 | pub const SEPTEMBER: &str = "September";
38 | pub const OCTOBER: &str = "October";
39 | pub const NOVEMBER: &str = "November";
40 | pub const DECEMBER: &str = "December";
41 |
42 | pub const DAYS_OF_WEEK: &[time::Weekday; 7] = &[
43 | time::Weekday::Sunday,
44 | time::Weekday::Monday,
45 | time::Weekday::Tuesday,
46 | time::Weekday::Wednesday,
47 | time::Weekday::Thursday,
48 | time::Weekday::Friday,
49 | time::Weekday::Saturday,
50 | ];
51 |
--------------------------------------------------------------------------------
/loremaster-web-interface/src/components/accordion.rs:
--------------------------------------------------------------------------------
1 | use perseus::prelude::spawn_local_scoped;
2 | use sycamore::prelude::*;
3 | use web_sys::Event;
4 |
5 | use crate::components::icon::{CHEVRON_DOWN_SVG_HTML, CHEVRON_UP_SVG_HTML};
6 |
7 | #[derive(Prop)]
8 | pub struct AccordionProperties<'a, G: Html> {
9 | pub id: &'a ReadSignal
,
10 | pub children: Children<'a, G>,
11 | }
12 |
13 | #[component]
14 | pub fn Accordion<'a, G: Html>(
15 | context: Scope<'a>,
16 | AccordionProperties { id, children }: AccordionProperties<'a, G>,
17 | ) -> View {
18 | let children = children.call(context);
19 | view! {context,
20 | div(
21 | id=id.get(),
22 | class="accordion"
23 | ) { (children) }
24 | }
25 | }
26 |
27 | #[derive(Prop)]
28 | pub struct AccordionItemProperties<'a, G: Html> {
29 | pub title: &'a Signal,
30 | pub children: Children<'a, G>,
31 | }
32 |
33 | #[component]
34 | pub fn AccordionItem<'a, G: Html>(
35 | context: Scope<'a>,
36 | AccordionItemProperties { title, children }: AccordionItemProperties<'a, G>,
37 | ) -> View {
38 | let children = children.call(context);
39 | let collapsed: &Signal = create_signal(context, true);
40 | let item_css_classes: &Signal<&str> = create_signal(context, "accordion-collapse collapse");
41 | let svg: &Signal<&str> = create_signal(context, CHEVRON_UP_SVG_HTML);
42 | let handle_click = move |event: Event| {
43 | event.prevent_default();
44 | if G::IS_BROWSER {
45 | spawn_local_scoped(context, async move {
46 | collapsed.set(!*collapsed.get());
47 | match *collapsed.get() {
48 | true => {
49 | item_css_classes.set("accordion-collapse collapse");
50 | svg.set(CHEVRON_UP_SVG_HTML);
51 | }
52 | false => {
53 | item_css_classes.set("accordion-collapse collapse show");
54 | svg.set(CHEVRON_DOWN_SVG_HTML);
55 | }
56 | }
57 | });
58 | }
59 | };
60 |
61 | view! {context,
62 | div(class="accordion-item") {
63 | h2(class="accordion-item-title") {
64 | button(on:click=handle_click, class="accordion-toggle") {
65 | span() { (title.get()) }
66 | span(class="accordion-shevron", dangerously_set_inner_html=*svg.get()) { }
67 | }
68 | }
69 | div(class=item_css_classes) {
70 | div(class="accordion-item-body") {
71 | (children)
72 | }
73 | }
74 | }
75 |
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/documentation/features/action_planning.md:
--------------------------------------------------------------------------------
1 | # Action Planning
2 |
3 | - [`chronicle`]
4 | - [`action`]
5 | - [`feature`]
6 |
7 | Chronicles represent all the lore associated with a given date in time.
8 |
9 | Being able to track and plan actions/tasks/routines is one of the most important features for me currently.
10 |
11 | I think actions is still a good encompassing word for the concept I am trying to capture.
12 |
13 | Actions have multiple dimensions to them.
14 |
15 | Examples:
16 |
17 | - `duration`
18 | - actions can occur over a period of time or in an instant
19 | - `frequency`
20 | - actions can be taken yearly, monthly, daily, hourly, etc.
21 | - could be every Thursday and Sunday
22 | - could be the first day of every quarter of the year
23 | - could be a desired number of completions within a repeated time-frame
24 | - could use a time range since the last completion time
25 | - `procedure`
26 | - a series of actions completed in a certain order or manner
27 | - `routine`
28 | - a collection of actions taken on a recurring basis
29 | - `contingent actions`
30 | - occurring or existing only if (certain circumstances) are the case; dependent on something else
31 | - `outcome`
32 | - what is the result of an action taken, if there is one
33 | - positive/negatives -- pros/cons -- risk/reward
34 | - anticipated vs actual outcome (pros/cons)
35 | - confidence level in outcome before taking the action
36 | - `category`
37 | - human actions tend to fall under general categories
38 | - wellbeing/rest
39 | - sleeping
40 | - napping
41 | - meditation
42 | - spa
43 | - health/bodily maintenance
44 | - strength training
45 | - cardio training
46 | - stretching
47 | - organization/inventory upkeep
48 | - keeping inventory
49 | - entertainment/enjoyment
50 | - gaming
51 | - sports
52 | - movies
53 | - social media
54 | - food/beverage
55 | - experiential/observational
56 | - bird watching
57 | - hiking
58 | - exploring
59 | - touring
60 | - traveling
61 | - skill work
62 | - programming
63 | - design
64 | - painting
65 | - drawing
66 | - sports
67 | - woodwork
68 | - learning
69 | - studying
70 | - lectures
71 | - school
72 | - educational content
73 | - books
74 | - videos
75 | - social
76 | - party
77 | - events
78 | - gatherings
79 | - holidays
80 | - financial
81 | - paying taxes
82 | - work
83 | - side-hustle
84 | - investing
85 | - real estate
86 | - can fall under more than one
87 |
88 | In order to fully implement `action planning`, I think most of these concepts ought to be included.
89 |
--------------------------------------------------------------------------------
/loremaster-web-interface/src/components/widget/date_time.rs:
--------------------------------------------------------------------------------
1 | use futures_util::{future::ready, stream::StreamExt};
2 | use gloo_timers::future::IntervalStream;
3 | use js_sys::{Array, Object};
4 | use perseus::prelude::spawn_local_scoped;
5 | use sycamore::prelude::*;
6 | use time::{macros::format_description, OffsetDateTime};
7 | use wasm_bindgen::JsValue;
8 |
9 | const ONE_SECOND_IN_MILLISECONDS: u32 = 1_000;
10 |
11 | #[component]
12 | pub fn DateTime(context: Scope) -> View {
13 | let short_date: &Signal = create_signal(context, String::new());
14 | let day_month: &Signal = create_signal(context, String::new());
15 | let time: &Signal = create_signal(context, String::new());
16 | let time_zone: &Signal = create_signal(context, String::new());
17 |
18 | if G::IS_BROWSER {
19 | let short_format = format_description!(
20 | "[year]/[month]/[day] [hour repr:12 padding:space]:[minute]:[second] [period]"
21 | );
22 | let time_format = format_description!("[hour repr:12 padding:space]:[minute] [period]");
23 | let rust_time: OffsetDateTime = time::OffsetDateTime::now_local().unwrap();
24 | short_date.set(rust_time.format(short_format).unwrap());
25 | time.set(rust_time.format(time_format).unwrap());
26 | day_month.set(format!(
27 | "{}, {} {}",
28 | rust_time.weekday(),
29 | rust_time.month(),
30 | rust_time.date().day()
31 | ));
32 | spawn_local_scoped(context, async move {
33 | let options =
34 | js_sys::Intl::DateTimeFormat::new(&Array::new(), &Object::new()).resolved_options();
35 | time_zone.set(
36 | js_sys::Reflect::get(&options, &JsValue::from("timeZone"))
37 | .unwrap()
38 | .as_string()
39 | .unwrap(),
40 | );
41 |
42 | IntervalStream::new(ONE_SECOND_IN_MILLISECONDS)
43 | .for_each(|_| {
44 | let rust_time = time::OffsetDateTime::now_local().unwrap();
45 | short_date.set(rust_time.format(short_format).unwrap());
46 | time.set(rust_time.format(time_format).unwrap());
47 | day_month.set(format!(
48 | "{}, {} {}",
49 | rust_time.weekday(),
50 | rust_time.month(),
51 | rust_time.date().day()
52 | ));
53 | ready(())
54 | })
55 | .await;
56 | });
57 | }
58 | let widget_classes = "date-time-widget";
59 | view! {context,
60 | section(class=widget_classes) {
61 | div() { (day_month.get()) }
62 | div() { (short_date.get()) }
63 | div() { (time_zone.get()) }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/loremaster-web-interface/src/components/navigation.rs:
--------------------------------------------------------------------------------
1 | use super::icon::{
2 | SvgIcon, ARCHIVE_SVG_HTML, BOOK_SVG_HTML, CLOCK_SVG_HTML, FEATHER_SVG_HTML,
3 | HELP_CIRCLE_SVG_HTML, LAYOUT_SVG_HTML, LOGIN_SVG_HTML, USER_PLUS_SVG_HTML, USER_SVG_HTML,
4 | };
5 |
6 | pub mod side_nav_bar;
7 | pub mod tab;
8 | pub mod top_nav_bar;
9 |
10 | #[derive(Debug, Clone, PartialEq, Eq)]
11 | pub struct NavigationLink<'a> {
12 | pub html_id: &'a str,
13 | pub html_href: &'a str,
14 | pub display_text: &'a str,
15 | pub svg_html: SvgIcon,
16 | }
17 |
18 | pub fn get_home_link() -> NavigationLink<'static> {
19 | NavigationLink {
20 | html_id: "index-link",
21 | html_href: "/",
22 | display_text: "Loremaster",
23 | svg_html: "",
24 | }
25 | }
26 |
27 | pub fn get_navigation_links() -> Vec> {
28 | vec![
29 | NavigationLink {
30 | html_id: "you-link",
31 | html_href: "/you/",
32 | display_text: "You",
33 | svg_html: USER_SVG_HTML,
34 | },
35 | NavigationLink {
36 | html_id: "chronicle-link",
37 | html_href: "/chronicle/",
38 | display_text: "Chronicle",
39 | svg_html: FEATHER_SVG_HTML,
40 | },
41 | NavigationLink {
42 | html_id: "lore-link",
43 | html_href: "/lore/",
44 | display_text: "Lore",
45 | svg_html: BOOK_SVG_HTML,
46 | },
47 | NavigationLink {
48 | html_id: "timeline-link",
49 | html_href: "/timeline/",
50 | display_text: "Timeline",
51 | svg_html: CLOCK_SVG_HTML,
52 | },
53 | NavigationLink {
54 | html_id: "ownership-link",
55 | html_href: "/ownership/",
56 | display_text: "Ownership",
57 | svg_html: ARCHIVE_SVG_HTML,
58 | },
59 | NavigationLink {
60 | html_id: "registration-link",
61 | html_href: "/registration/",
62 | display_text: "Registration",
63 | svg_html: USER_PLUS_SVG_HTML,
64 | },
65 | NavigationLink {
66 | html_id: "login-link",
67 | html_href: "/login/",
68 | display_text: "Login",
69 | svg_html: LOGIN_SVG_HTML,
70 | },
71 | NavigationLink {
72 | html_id: "about-link",
73 | html_href: "/about/",
74 | display_text: "About",
75 | svg_html: HELP_CIRCLE_SVG_HTML,
76 | },
77 | ]
78 | }
79 |
80 | pub fn get_navigation_link_by_name(name: String) -> Option> {
81 | get_navigation_links()
82 | .iter()
83 | .find(|link| link.display_text == name)
84 | .map(|link| link.to_owned())
85 | }
86 |
87 | pub const DESIGN_SYSTEM_LINK: NavigationLink = NavigationLink {
88 | html_id: "design-system-link",
89 | html_href: "/design-system/",
90 | display_text: "Design System",
91 | svg_html: LAYOUT_SVG_HTML,
92 | };
93 |
--------------------------------------------------------------------------------
/loremaster-web-interface/src/components/combobox.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 | use sycamore::prelude::*;
3 | use uuid::Uuid;
4 | use web_sys::Event;
5 |
6 | pub const COMBOBOX_OPTION_CSS_CLASSES: &str = "";
7 | pub const COMBOBOX_QUERY_INPUT_CSS_CLASSES: &str = "";
8 |
9 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
10 | pub struct ComboBoxOption {
11 | pub id: Uuid,
12 | pub display_text: String,
13 | pub description: String,
14 | }
15 |
16 | #[derive(Prop)]
17 | pub struct ComboBoxProperties<'combobox> {
18 | pub classes: &'combobox ReadSignal,
19 | pub label: String,
20 | pub options: Vec,
21 | pub query: &'combobox Signal,
22 | pub selected: &'combobox Signal