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