Skip to content

Latest commit

 

History

History
483 lines (424 loc) · 16 KB

project-notes.org

File metadata and controls

483 lines (424 loc) · 16 KB

Crab News

sketchpad for this project. notes, they come and go.
  • When Should You Use Which Collection?

    To get this out of the way: you should probably just use Vec or HashMap. These two collections cover most use cases for generic data storage and processing. They are exceptionally good at doing what they do. All the other collections in the standard library have specific use cases where they are the optimal choice, but these cases are borderline niche in comparison. Even when Vec and HashMap are technically suboptimal, they’re probably a good enough choice to get started.

  • The pub struct ViewModel {} exposes its data via pub. Is this an Adapter?
  • All else have pub struct {} only. Is this a Port?

Elm vs Crux

To help wrap my head around it, hereby collected their similarities and differences.
ElmCruxNotes
ModelModelthe Model holds all the possible states the app can be in
a) ViewViewModelthe ViewModel contains data relevant to the currently displayed UI/view
b) Viewfn view() in Appthe fn view() function populates ViewModel’s data fron the Model
c) Viewsee “Cmd Msg” in Capabilitiesthe Shells will send/receive the data to/from ViewModel via Capabilities
d) Viewno Model -> Html Msg hereunlike Elm, Crux doesn’t render a View but sends data to Shells (see “c”)
Updatefn update() in Apptakes Model, Events, Capabilities and changes Model by invoking Events
a) MsgEventsare all the possible things the user can do
b) Cmd MsgEventsinvoke Capabilities and may also callback more Events
(Model, Msg)fn update() implicit return of?self.update(Event::Update(count), model, caps);
maincrux_core::App entry pointis an implementation of the App trait, exposed via the Core or Bridge
a) init​#[derive(Default)]set Model initial state with Default Trait; impl Default for any custom Types
b) initworks like Elm’s sandboxno request effects during init. You can always add Event::Init if needed
Side EffectsCapabilities/FFINotes
a) Side EffectsCapabilitiesCrux has three types of effects: notifications, requests, and subscriptions
b) Side EffectsCapabilitiesCrux side effects differ by the number of expected responses from the Shell
c) Side EffectsCapabilitiesCrux fn update() in App is the only Capabilities consumer, via Events
Cmd MsgCapabilitiesfrom the perspective of the Shell, they are data oriented messages sent back and forth
Cmd.none?Capabilitiesthe Crux app will send the data to the Shell every time you call caps.render.render();
subscriptionsCapabilitiessubscriptions is a type of an effect in Crux, requested via capabilities
portsCapabilitiescontrary to Elm Ports, Crux requests all side-effects, internally, through Capabilities
flagsEvent::Configurefavor something like Event::Configure to take the configuration options

Model

The Model is an overall state (and the only place for state) of your application, it will hold all the loaded data, and any other kind of in-memory cached things. Everything that needs to live for longer than single run of `update()` goes in the Model.
  • This needs some more love and thinking. It’s a start though
#[derive(Default, Serialize)]
pub struct Model {
    ////////////////////////////
    // preferences UI
    ////////////////////////////
    theme: Theme,
    text_size: TextSize,
    browser: Browser,
    open_method: OpeningMethod,
    refresh_interval: RefreshInterval,
    // accounts: Vec<Account>, // will contain subscriptions in a future version

    ////////////////////////////
    // nain UI
    ////////////////////////////
    // "specials"
    unread_count: u16,
    starred_count: u16, // isUnread && isStarred
    entry_read: ReadStatus,
    entry_star: StarStatus,
    feed_url: String,

    // subscriptions,
    subscriptions: Subscriptions,
    subscription_folder: SubscriptionFolder, // root or folder

    // left column
    feed_view: FeedView, // Smart View = today | all unread | starred | folder | feed
    // for any account,
    account_name: String, // extrapolated from account
    feeds: Vec<Feed>,
    feed_name: String,

    // middle column
    entries_title: String, // folder or feed
    entries: Vec<Entry>,
    entry_title: String,
    entry_line: String, // whatever fits from content 1st line
    entry_date: Date, // dd mm yyyy

    // right column
    content: Option<Content>,
}

ViewModel

the ViewModel is a straight “projection” of the Model – it’s calculated from it (with the view function)
  • This needs some more love and thinking. It’s a start though
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ViewModel {
    ////////////////////////////
    // preferences UI
    ////////////////////////////
    pub theme: Theme,
    pub text_size: TextSize,
    pub use_browser: Browser,
    pub open_method: OpeningMethod,
    pub refresh_interval: RefreshInterval,
    // accounts: Vec<Account>, // contains subscriptions in the future

    ////////////////////////////
    // nain UI
    ////////////////////////////
    // "specials"
    pub subscriptions: OPML,
    pub unread_count: u16,
    pub starred_count: u16,
    pub entry_read: ReadStatus,
    pub entry_star: StarStatus,
    pub feed_url: String,

    // left column
    pub feed_view: FeedView, // Smart View: today | all unread | starred,
    // for any account,
    pub account_name: String, // extrapolated from account
    pub feed_store: FeedStore, // root or folder
    pub feed_name: String, // extrapolated from feed

    // middle column
    pub entries_title: String, // folder or feed
    pub entries: Vec<Entry>,
    pub entry_title: String,
    pub entry_line: String, // whatever fits from content 1st line
    pub entry_date: StarStatus, // dd mm yyyy

    // right column
    pub content: Option<Content>,

    ////////////////////////////
    // modals
    ////////////////////////////
    // subscribe modal
    pub feed_url: String,
    pub feed_name: String,
    pub feed_store: FeedStore,

    // delete feed/folder <T> modal
    pub app_logo: Image,
    pub del_title: String,
    pub del_what: String, // either feed_name or feed_store
    pub button_action: ,
}

Preferences

#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
pub struct Preferences {
    theme: Theme,
    text_size: TextSize,
    browser: Browser,
    open_method: OpeningMethod,
    refresh_interval: RefreshInterval,
    // accounts: Vec<Account>, // will contain subscriptions in a future version
}

#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
pub enum Theme {
    #[default]
    System,
    Light,
    Dark,
}

#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
pub enum TextSize {
    Small { desc: &str, size: u8 },
    #[default]
    Medium { desc: &str, size: u8 },
    Large { desc: &str, size: u8 },
    ExtraLarge { desc: &str, size: u8 },
}

#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
pub enum Browser {
    #[default]
    Default,
    Safari,
    Firefox,
    Brave,
    Chrome,
    Opera,
    Edge,
}

#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
pub enum OpeningMethod {
    #[default]
    Background,
    Foreground,
}

#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
pub enum RefreshInterval {
    MinFifteen { desc: &str, time: u8 },
    #[default]
    MinThirthy { desc: &str, time: u8 },
    HoursOne { desc: &str, time: u8 },
    HoursTwo { desc: &str, time: u8 },
    HoursFour { desc: &str, time: u8 },
    HoursEight { desc: &str, time: u8 },
}

Account

I don’t think you need a crate here nor create a Capability. You can implement all inside the crux app and probably the only use crux_http and crux_kv (key value store) capabilities. You will use crux_http to communicate to the account clouds and probably the crux_kv to store the tokens locally. There are already examples on how to implement the crux_http on Android, iOS and the Web, but, I don’t remember seeing any of the crux_kv shell implementations.

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Account {
    acct: AccountType,
    subs: Subscriptions,
}

#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
pub enum AccountType {
    #[default]
    Local(AccountLocal),
    Native(AccountNative),
    Cloud(AccountCloud),
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum AccountLocal {
    Local { name: String, auth: bool },
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum AccountNative {
    // how do I check for Auth? impl? Capabilities?
    Apple { name: String, auth: bool },
    Google { name: String, auth: bool },
    Microsoft { name: String, auth: bool },
    Canonical { name: String, auth: bool },
    // more?
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum AccountCloud {
    // https://rclone.org
    Dropbox { name: String, auth: bool },
    // more
}

Subscriptions

ImportSubscriptions(OpmlFile), ExportSubscriptions(OpmlName), AddNewFolder(FolderName), DeleteFolder(FolderName), RenameFolder(OldName, NewName), AddNewSubscription(Option<FolderName>, SubscriptionName, SubscriptionURL), DeleteSubscription(Option<FolderName>, SubscriptionName), RenameSubscription(Option<FolderName>, OldName, NewName), MoveSubscriptionToFolder(Subscription, OldFolder, NewFolder),

<!-- Example OPML -->
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- OPML generated by Crab News -->
<opml version="2.0">
  <head>
    <title>Subscriptions.opml</title>
    <dateCreated>Sat, 18 Jun 2005 12:11:52 GMT</dateCreated>
    <ownerName>Crab News</ownerName>
  </head>
  <body>
    <!-- this is an UNGROUPED root level subscription -->
    <outline text="Feed Name" title="Feed Name" description="" type="rss" version="RSS" htmlUrl="https://example.com/" xmlUrl="https://example.com/atom.xml"/>
    <!-- this is a GROUPED 1st level folder subscription -->
    <outline text="Group Name" title="Group Name">
      <outline text="Feed Name" title="Feed Name" description="" type="rss" version="RSS" htmlUrl="https://example.com/" xmlUrl="https://example.com/rss.xml"/>
    </outline>
  </body>
</opml>

Feeds

This crate is to deal with feeds data after subscribtions. The main UI would deal with all data to display “news” in the entry and content columns.

Related to Feeds

#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
pub enum ReadStatus {
    Read,
    #[default]
    Unread,
}

#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
pub enum StarStatus {
    Starred,
    #[default]
    Unstarred,
}

#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
pub enum FeedView {
    Today,
    #[default]
    Unread,
    Starred,
    Folder,
    Feed,
}

Events

  • all the events to start coding, more later?
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum Event {
    // EVENTS FROM THE SHELL
    // ANCHOR: Preferences UI
    // General panel
    SetTheme,
    SetTextSize,
    SetBrowser,
    SetOpeningMethod,
    SetRefreshInterval,
    // Account panel
    AddAccount,
    DeleteAccount,
    // ANCHOR_END: Preferences UI

    // ANCHOR: Menu
    // Shell thingiemageebs better done in UI?
    // File // mostly system related
    // Edit // mostly system related
    // View
    SortEntriesBy, // newest | oldest
    GroupByFeed,
    CleanUpEntries,
    HideRead // entries | feeds
    HideUIItem // sidebar | toolbar
    // Go
    DisplayNextUnreadEntry,
    DisplayToday,
    DisplayAllUnread,
    DisplayStarred,
    // Article -> SEE Entries
    // ANCHOR_END: Mmenu

    // ANCHOR: Main UI
    // Subscriptions live in struct Account {}
    ImportSubscriptions, // shows up in Menu -> File
    ExportSubscriptions, // shows up in Menu -> File

    // FeedStore -> root + 1st level folder. no more
    // THIS ADDS AN OUTLINE TO OPML::Body
    AddNewFolder, // shows up in Menu -> File
    DeleteFolder,
    RenameFolder,

    // FeedView -> today | all unread | starred | folder | feed
    SetFeedView,

    // Feeds
    // TO ADD AN OUTLINE TO ROOT USE https://docs.rs/opml/1.1.6/opml/struct.OPML.html#method.add_feed
    // TO ADD AN OUTLINE TO FOLDER USE https://docs.rs/opml/1.1.6/opml/struct.Outline.html#method.add_feed
    RefreshFeeds, // shows up in Menu -> File
    AddNewFeed, // account | root | folder // shows up in Menu -> File
    DeleteFeed,
    RenameFeed,
    MoveFeedToFolder, // location -> root | folder
    CopyFeedURL,
    CopyFeedHomeURL,
    OpenFeedHomeURL,

    // Entries
    MarkEntryAsRead, // shows up in Menu -> Article
    MarkEntryAsUnread, // shows up in Menu -> Article
    MarkAllEntriesAsRead, // shows up in Menu -> Article
    MarkAllEntriesAsUnread, // shows up in Menu -> Article
    MarkEntryAsStarred, // shows up in Menu -> Article
    MarkEntryAsUnstarred, // shows up in Menu -> Article
    OpenEntryInBrowser, // shows up in Menu -> Article
    CopyEntryURL,

    // Content has no Events associated but system ones

    // ANCHOR_END: Main UI

    // EVENTS LOCAL TO THE CORE
    #[serde(skip)]
    Fetch(crux_http::Result<crux_http::Response<Feed>, Box<dyn Error>>),
}

Database