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