storybook.rs

  1mod actions;
  2mod app_menus;
  3mod assets;
  4mod stories;
  5mod story_selector;
  6
  7use clap::Parser;
  8use dialoguer::FuzzySelect;
  9use gpui::{
 10    div, px, size, AnyView, AppContext, Bounds, Render, ViewContext, VisualContext, WindowOptions,
 11};
 12use log::LevelFilter;
 13use settings::{default_settings, KeymapFile, Settings, SettingsStore};
 14use simplelog::SimpleLogger;
 15use strum::IntoEnumIterator;
 16use theme::{ThemeRegistry, ThemeSettings};
 17use ui::prelude::*;
 18
 19use crate::app_menus::app_menus;
 20use crate::assets::Assets;
 21use crate::story_selector::{ComponentStory, StorySelector};
 22use actions::Quit;
 23pub use indoc::indoc;
 24
 25#[derive(Parser)]
 26#[command(author, version, about, long_about = None)]
 27struct Args {
 28    #[arg(value_enum)]
 29    story: Option<StorySelector>,
 30
 31    /// The name of the theme to use in the storybook.
 32    ///
 33    /// If not provided, the default theme will be used.
 34    #[arg(long)]
 35    theme: Option<String>,
 36}
 37
 38fn main() {
 39    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
 40
 41    menu::init();
 42    let args = Args::parse();
 43
 44    let story_selector = args.story.unwrap_or_else(|| {
 45        let stories = ComponentStory::iter().collect::<Vec<_>>();
 46
 47        ctrlc::set_handler(move || {}).unwrap();
 48
 49        let result = FuzzySelect::new()
 50            .with_prompt("Choose a story to run:")
 51            .items(&stories)
 52            .interact();
 53
 54        let Ok(selection) = result else {
 55            dialoguer::console::Term::stderr().show_cursor().unwrap();
 56            std::process::exit(0);
 57        };
 58
 59        StorySelector::Component(stories[selection])
 60    });
 61    let theme_name = args.theme.unwrap_or("One Dark".to_string());
 62
 63    gpui::App::new().with_assets(Assets).run(move |cx| {
 64        load_embedded_fonts(cx).unwrap();
 65
 66        let mut store = SettingsStore::default();
 67        store
 68            .set_default_settings(default_settings().as_ref(), cx)
 69            .unwrap();
 70        cx.set_global(store);
 71
 72        theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
 73
 74        let selector = story_selector;
 75
 76        let theme_registry = ThemeRegistry::global(cx);
 77        let mut theme_settings = ThemeSettings::get_global(cx).clone();
 78        theme_settings.active_theme = theme_registry.get(&theme_name).unwrap();
 79        ThemeSettings::override_global(theme_settings, cx);
 80
 81        language::init(cx);
 82        editor::init(cx);
 83        init(cx);
 84        load_storybook_keymap(cx);
 85        cx.set_menus(app_menus());
 86
 87        let size = size(px(1500.), px(780.));
 88        let bounds = Bounds::centered(None, size, cx);
 89        let _window = cx.open_window(
 90            WindowOptions {
 91                bounds: Some(bounds),
 92                ..Default::default()
 93            },
 94            move |cx| {
 95                let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
 96                cx.set_rem_size(ui_font_size);
 97
 98                cx.new_view(|cx| StoryWrapper::new(selector.story(cx)))
 99            },
100        );
101
102        cx.activate(true);
103    });
104}
105
106#[derive(Clone)]
107pub struct StoryWrapper {
108    story: AnyView,
109}
110
111impl StoryWrapper {
112    pub(crate) fn new(story: AnyView) -> Self {
113        Self { story }
114    }
115}
116
117impl Render for StoryWrapper {
118    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
119        div()
120            .flex()
121            .flex_col()
122            .size_full()
123            .font("Zed Mono")
124            .child(self.story.clone())
125    }
126}
127
128fn load_embedded_fonts(cx: &AppContext) -> gpui::Result<()> {
129    let font_paths = cx.asset_source().list("fonts")?;
130    let mut embedded_fonts = Vec::new();
131    for font_path in font_paths {
132        if font_path.ends_with(".ttf") {
133            let font_bytes = cx.asset_source().load(&font_path)?;
134            embedded_fonts.push(font_bytes);
135        }
136    }
137
138    cx.text_system().add_fonts(embedded_fonts)
139}
140
141fn load_storybook_keymap(cx: &mut AppContext) {
142    KeymapFile::load_asset("keymaps/storybook.json", cx).unwrap();
143}
144
145pub fn init(cx: &mut AppContext) {
146    cx.on_action(quit);
147}
148
149fn quit(_: &Quit, cx: &mut AppContext) {
150    cx.spawn(|cx| async move {
151        cx.update(|cx| cx.quit())?;
152        anyhow::Ok(())
153    })
154    .detach_and_log_err(cx);
155}