1mod base_keymap_picker;
2mod base_keymap_setting;
3
4use client::{telemetry::Telemetry, TelemetrySettings};
5use db::kvp::KEY_VALUE_STORE;
6use gpui::{
7 svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
8 ParentElement, Render, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
9 WindowContext,
10};
11use settings::{Settings, SettingsStore};
12use std::sync::Arc;
13use ui::{prelude::*, Checkbox};
14use vim::VimModeSetting;
15use workspace::{
16 dock::DockPosition,
17 item::{Item, ItemEvent},
18 open_new, AppState, Welcome, Workspace, WorkspaceId,
19};
20
21pub use base_keymap_setting::BaseKeymap;
22
23pub const FIRST_OPEN: &str = "first_open";
24
25pub fn init(cx: &mut AppContext) {
26 BaseKeymap::register(cx);
27
28 cx.observe_new_views(|workspace: &mut Workspace, _cx| {
29 workspace.register_action(|workspace, _: &Welcome, cx| {
30 let welcome_page = WelcomePage::new(workspace, cx);
31 workspace.add_item(Box::new(welcome_page), cx)
32 });
33 })
34 .detach();
35
36 base_keymap_picker::init(cx);
37}
38
39pub fn show_welcome_view(app_state: &Arc<AppState>, cx: &mut AppContext) {
40 open_new(&app_state, cx, |workspace, cx| {
41 workspace.toggle_dock(DockPosition::Left, cx);
42 let welcome_page = WelcomePage::new(workspace, cx);
43 workspace.add_item_to_center(Box::new(welcome_page.clone()), cx);
44 cx.focus_view(&welcome_page);
45 cx.notify();
46 })
47 .detach();
48
49 db::write_and_log(cx, || {
50 KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string())
51 });
52}
53
54pub struct WelcomePage {
55 workspace: WeakView<Workspace>,
56 focus_handle: FocusHandle,
57 telemetry: Arc<Telemetry>,
58 _settings_subscription: Subscription,
59}
60
61impl Render for WelcomePage {
62 fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
63 h_flex().full().track_focus(&self.focus_handle).child(
64 v_flex()
65 .w_96()
66 .gap_4()
67 .mx_auto()
68 .child(
69 svg()
70 .path("icons/logo_96.svg")
71 .text_color(gpui::white())
72 .w(px(96.))
73 .h(px(96.))
74 .mx_auto(),
75 )
76 .child(
77 h_flex()
78 .justify_center()
79 .child(Label::new("Code at the speed of thought")),
80 )
81 .child(
82 v_flex()
83 .gap_2()
84 .child(
85 Button::new("choose-theme", "Choose a theme")
86 .full_width()
87 .on_click(cx.listener(|this, _, cx| {
88 this.telemetry
89 .report_app_event("welcome page: change theme".to_string());
90 this.workspace
91 .update(cx, |workspace, cx| {
92 theme_selector::toggle(
93 workspace,
94 &Default::default(),
95 cx,
96 )
97 })
98 .ok();
99 })),
100 )
101 .child(
102 Button::new("choose-keymap", "Choose a keymap")
103 .full_width()
104 .on_click(cx.listener(|this, _, cx| {
105 this.telemetry.report_app_event(
106 "welcome page: change keymap".to_string(),
107 );
108 this.workspace
109 .update(cx, |workspace, cx| {
110 base_keymap_picker::toggle(
111 workspace,
112 &Default::default(),
113 cx,
114 )
115 })
116 .ok();
117 })),
118 )
119 .child(
120 Button::new("install-cli", "Install the CLI")
121 .full_width()
122 .on_click(cx.listener(|this, _, cx| {
123 this.telemetry
124 .report_app_event("welcome page: install cli".to_string());
125 cx.app_mut()
126 .spawn(
127 |cx| async move { install_cli::install_cli(&cx).await },
128 )
129 .detach_and_log_err(cx);
130 })),
131 ),
132 )
133 .child(
134 v_flex()
135 .p_3()
136 .gap_2()
137 .bg(cx.theme().colors().elevated_surface_background)
138 .border_1()
139 .border_color(cx.theme().colors().border)
140 .rounded_md()
141 .child(
142 h_flex()
143 .gap_2()
144 .child(
145 Checkbox::new(
146 "enable-vim",
147 if VimModeSetting::get_global(cx).0 {
148 ui::Selection::Selected
149 } else {
150 ui::Selection::Unselected
151 },
152 )
153 .on_click(cx.listener(
154 move |this, selection, cx| {
155 this.telemetry.report_app_event(
156 "welcome page: toggle vim".to_string(),
157 );
158 this.update_settings::<VimModeSetting>(
159 selection,
160 cx,
161 |setting, value| *setting = Some(value),
162 );
163 },
164 )),
165 )
166 .child(Label::new("Enable vim mode")),
167 )
168 .child(
169 h_flex()
170 .gap_2()
171 .child(
172 Checkbox::new(
173 "enable-telemetry",
174 if TelemetrySettings::get_global(cx).metrics {
175 ui::Selection::Selected
176 } else {
177 ui::Selection::Unselected
178 },
179 )
180 .on_click(cx.listener(
181 move |this, selection, cx| {
182 this.telemetry.report_app_event(
183 "welcome page: toggle metric telemetry".to_string(),
184 );
185 this.update_settings::<TelemetrySettings>(
186 selection,
187 cx,
188 {
189 let telemetry = this.telemetry.clone();
190
191 move |settings, value| {
192 settings.metrics = Some(value);
193
194 telemetry.report_setting_event(
195 "metric telemetry",
196 value.to_string(),
197 );
198 }
199 },
200 );
201 },
202 )),
203 )
204 .child(Label::new("Send anonymous usage data")),
205 )
206 .child(
207 h_flex()
208 .gap_2()
209 .child(
210 Checkbox::new(
211 "enable-crash",
212 if TelemetrySettings::get_global(cx).diagnostics {
213 ui::Selection::Selected
214 } else {
215 ui::Selection::Unselected
216 },
217 )
218 .on_click(cx.listener(
219 move |this, selection, cx| {
220 this.telemetry.report_app_event(
221 "welcome page: toggle diagnostic telemetry"
222 .to_string(),
223 );
224 this.update_settings::<TelemetrySettings>(
225 selection,
226 cx,
227 {
228 let telemetry = this.telemetry.clone();
229
230 move |settings, value| {
231 settings.diagnostics = Some(value);
232
233 telemetry.report_setting_event(
234 "diagnostic telemetry",
235 value.to_string(),
236 );
237 }
238 },
239 );
240 },
241 )),
242 )
243 .child(Label::new("Send crash reports")),
244 ),
245 ),
246 )
247 }
248}
249
250impl WelcomePage {
251 pub fn new(workspace: &Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
252 let this = cx.new_view(|cx| {
253 cx.on_release(|this: &mut Self, _, _| {
254 this.telemetry
255 .report_app_event("welcome page: close".to_string());
256 })
257 .detach();
258
259 WelcomePage {
260 focus_handle: cx.focus_handle(),
261 workspace: workspace.weak_handle(),
262 telemetry: workspace.client().telemetry().clone(),
263 _settings_subscription: cx
264 .observe_global::<SettingsStore>(move |_, cx| cx.notify()),
265 }
266 });
267
268 this
269 }
270
271 fn update_settings<T: Settings>(
272 &mut self,
273 selection: &Selection,
274 cx: &mut ViewContext<Self>,
275 callback: impl 'static + Send + Fn(&mut T::FileContent, bool),
276 ) {
277 if let Some(workspace) = self.workspace.upgrade() {
278 let fs = workspace.read(cx).app_state().fs.clone();
279 let selection = *selection;
280 settings::update_settings_file::<T>(fs, cx, move |settings| {
281 let value = match selection {
282 Selection::Unselected => false,
283 Selection::Selected => true,
284 _ => return,
285 };
286
287 callback(settings, value)
288 });
289 }
290 }
291}
292
293impl EventEmitter<ItemEvent> for WelcomePage {}
294
295impl FocusableView for WelcomePage {
296 fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
297 self.focus_handle.clone()
298 }
299}
300
301impl Item for WelcomePage {
302 type Event = ItemEvent;
303
304 fn tab_content(&self, _: Option<usize>, selected: bool, _: &WindowContext) -> AnyElement {
305 Label::new("Welcome to Zed!")
306 .color(if selected {
307 Color::Default
308 } else {
309 Color::Muted
310 })
311 .into_any_element()
312 }
313
314 fn telemetry_event_text(&self) -> Option<&'static str> {
315 Some("welcome page")
316 }
317
318 fn show_toolbar(&self) -> bool {
319 false
320 }
321
322 fn clone_on_split(
323 &self,
324 _workspace_id: WorkspaceId,
325 cx: &mut ViewContext<Self>,
326 ) -> Option<View<Self>> {
327 Some(cx.new_view(|cx| WelcomePage {
328 focus_handle: cx.focus_handle(),
329 workspace: self.workspace.clone(),
330 telemetry: self.telemetry.clone(),
331 _settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
332 }))
333 }
334
335 fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
336 f(*event)
337 }
338}