Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
@@ -0,0 +1,277 @@
use std::time::Duration;
use crate::modal::Modal;
use crate::ui_components::blended_colors;
use warp_core::ui::appearance::Appearance;
use warpui::elements::{
ChildView, Container, CrossAxisAlignment, Flex, MouseStateHandle, ParentElement, Text,
};
use warpui::fonts::{Properties, Weight};
use warpui::platform::Cursor;
use warpui::r#async::Timer;
use warpui::ui_components::button::ButtonVariant;
use warpui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
use warpui::{
AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle,
};
pub const MODAL_WIDTH: f32 = 400.;
pub const MODAL_PADDING: f32 = 24.;
pub const HEADER_FONT_SIZE: f32 = 16.;
pub const TEXT_FONT_SIZE: f32 = 14.;
pub const BUTTON_WIDTH: f32 = 172.;
pub const BUTTON_HEIGHT: f32 = 40.;
#[derive(Debug, Clone)]
pub enum InactivityModalEvent {
TimedOut,
StopSharing,
ContinueSharing,
}
pub struct InactivityModal {
modal: ViewHandle<Modal<InactivityModalBody>>,
}
impl InactivityModal {
pub fn new(ctx: &mut ViewContext<Self>) -> Self {
let body = ctx.add_typed_action_view(|_| InactivityModalBody::new());
ctx.subscribe_to_view(&body, |me, _, event, ctx| me.handle_body_event(event, ctx));
let modal = ctx.add_typed_action_view(|ctx| {
Modal::new(None, body, ctx)
.with_modal_style(UiComponentStyles {
width: Some(MODAL_WIDTH),
..Default::default()
})
.with_body_style(UiComponentStyles {
padding: Some(Coords {
top: MODAL_PADDING,
bottom: MODAL_PADDING,
left: MODAL_PADDING,
right: MODAL_PADDING,
}),
..Default::default()
})
.with_background_opacity(100)
.close_modal_button_disabled()
});
Self { modal }
}
fn handle_body_event(&mut self, event: &InactivityModalBodyEvent, ctx: &mut ViewContext<Self>) {
match event {
InactivityModalBodyEvent::TimedOut => ctx.emit(InactivityModalEvent::TimedOut),
InactivityModalBodyEvent::StopSharing => ctx.emit(InactivityModalEvent::StopSharing),
InactivityModalBodyEvent::ContinueSharing => {
ctx.emit(InactivityModalEvent::ContinueSharing)
}
}
}
pub fn reset_timer(&mut self, duration: Duration, ctx: &mut ViewContext<Self>) {
self.modal.update(ctx, |modal, ctx| {
modal.body().update(ctx, |body, ctx| {
body.reset_timer(duration, ctx);
});
});
}
}
impl Entity for InactivityModal {
type Event = InactivityModalEvent;
}
impl View for InactivityModal {
fn ui_name() -> &'static str {
"SharerInactivityModal"
}
fn render(&self, _app: &AppContext) -> Box<dyn Element> {
ChildView::new(&self.modal).finish()
}
}
#[derive(Debug, Clone)]
enum InactivityModalBodyAction {
StopSharing,
ContinueSharing,
}
#[derive(Debug, Clone)]
enum InactivityModalBodyEvent {
TimedOut,
StopSharing,
ContinueSharing,
}
struct InactivityModalBody {
stop_sharing_button: MouseStateHandle,
continue_sharing_button: MouseStateHandle,
is_countdown_running: bool,
duration: Duration,
}
impl InactivityModalBody {
fn new() -> Self {
Self {
stop_sharing_button: Default::default(),
continue_sharing_button: Default::default(),
is_countdown_running: false,
duration: Duration::from_secs(0),
}
}
fn reset_timer(&mut self, duration: Duration, ctx: &mut ViewContext<Self>) {
self.is_countdown_running = true;
self.duration = duration;
self.update_countdown(ctx);
}
fn update_countdown(&mut self, ctx: &mut ViewContext<Self>) {
if !self.duration.is_zero() && self.is_countdown_running {
self.duration -= Duration::from_secs(1);
ctx.notify();
let _ = ctx.spawn(
async move {
Timer::after(std::time::Duration::from_secs(1)).await;
},
|me, _, ctx| me.update_countdown(ctx),
);
} else if self.is_countdown_running {
self.is_countdown_running = false;
ctx.emit(InactivityModalBodyEvent::TimedOut);
}
}
fn render_countdown(&self, appearance: &Appearance) -> Box<dyn Element> {
let text = format!(
"Sharing will end in {}:{:02} due to inactivity.",
self.duration.as_secs() / 60,
self.duration.as_secs() % 60,
);
Container::new(
Text::new_inline(text, appearance.ui_font_family(), TEXT_FONT_SIZE)
.with_color(blended_colors::text_main(
appearance.theme(),
appearance.theme().background(),
))
.with_style(Properties::default().weight(Weight::Normal))
.finish(),
)
.with_padding_bottom(MODAL_PADDING)
.finish()
}
fn render_stop_sharing_button(&self, appearance: &Appearance) -> Box<dyn Element> {
Container::new(
appearance
.ui_builder()
.button(ButtonVariant::Outlined, self.stop_sharing_button.clone())
.with_style(UiComponentStyles {
width: Some(BUTTON_WIDTH),
height: Some(BUTTON_HEIGHT),
font_size: Some(TEXT_FONT_SIZE),
font_weight: Some(Weight::Bold),
..Default::default()
})
.with_centered_text_label(String::from("Stop sharing"))
.build()
.with_cursor(Cursor::PointingHand)
.on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(InactivityModalBodyAction::StopSharing)
})
.finish(),
)
.with_padding_right(8.)
.finish()
}
fn render_continue_sharing_button(&self, appearance: &Appearance) -> Box<dyn Element> {
appearance
.ui_builder()
.button(
ButtonVariant::Outlined,
self.continue_sharing_button.clone(),
)
.with_style(UiComponentStyles {
width: Some(BUTTON_WIDTH),
height: Some(BUTTON_HEIGHT),
font_size: Some(TEXT_FONT_SIZE),
font_weight: Some(Weight::Bold),
..Default::default()
})
.with_centered_text_label(String::from("Continue sharing"))
.build()
.with_cursor(Cursor::PointingHand)
.on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(InactivityModalBodyAction::ContinueSharing)
})
.finish()
}
}
impl Entity for InactivityModalBody {
type Event = InactivityModalBodyEvent;
}
impl View for InactivityModalBody {
fn ui_name() -> &'static str {
"SharerInactivityModalBody"
}
fn render(&self, app: &AppContext) -> Box<dyn Element> {
let appearance = Appearance::as_ref(app);
let stop_sharing_button = self.render_stop_sharing_button(appearance);
let continue_sharing_button = self.render_continue_sharing_button(appearance);
let countdown = self.render_countdown(appearance);
let header = Container::new(
Text::new_inline(
"Are you still there?",
appearance.ui_font_family(),
HEADER_FONT_SIZE,
)
.with_color(blended_colors::text_main(
appearance.theme(),
appearance.theme().background(),
))
.with_style(Properties::default().weight(Weight::Bold))
.finish(),
)
.with_padding_bottom(8.)
.finish();
let button_row = Flex::row()
.with_child(stop_sharing_button)
.with_child(continue_sharing_button)
.finish();
Flex::column()
.with_child(header)
.with_child(countdown)
.with_child(button_row)
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.finish()
}
}
impl TypedActionView for InactivityModalBody {
type Action = InactivityModalBodyAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action {
InactivityModalBodyAction::StopSharing => {
self.is_countdown_running = false;
ctx.emit(InactivityModalBodyEvent::StopSharing)
}
InactivityModalBodyAction::ContinueSharing => {
self.is_countdown_running = false;
ctx.emit(InactivityModalBodyEvent::ContinueSharing)
}
}
}
}
@@ -0,0 +1,66 @@
pub mod inactivity_modal;
use inactivity_modal::InactivityModal;
use async_channel::Sender;
use warpui::{
elements::MouseStateHandle, r#async::SpawnedFutureHandle, SingletonEntity, ViewContext,
ViewHandle,
};
use crate::terminal::{shared_session::settings::SharedSessionSettings, TerminalView};
pub struct Sharer {
pub(super) activity_tx: Sender<()>,
pub(super) revoke_all_mouse_state_handle: MouseStateHandle,
pub(super) inactivity_timer_abort_handle: Option<SpawnedFutureHandle>,
pub(super) is_inactivity_warning_modal_open: bool,
pub(super) inactivity_modal: ViewHandle<InactivityModal>,
}
impl Sharer {
pub(super) fn new(activity_tx: Sender<()>, ctx: &mut ViewContext<TerminalView>) -> Self {
let inactivity_modal = ctx.add_view(InactivityModal::new);
ctx.subscribe_to_view(&inactivity_modal, |me, _, event, ctx| {
me.handle_inactivity_modal_event(event, ctx)
});
Self {
activity_tx,
revoke_all_mouse_state_handle: Default::default(),
inactivity_timer_abort_handle: None,
is_inactivity_warning_modal_open: false,
inactivity_modal,
}
}
pub fn activity_tx(&self) -> &Sender<()> {
&self.activity_tx
}
pub fn is_inactivity_warning_modal_open(&self) -> bool {
self.is_inactivity_warning_modal_open
}
/// Opens inactivity warning modal and resets the timer.
pub fn open_inactivity_warning_modal(&mut self, ctx: &mut ViewContext<TerminalView>) {
let duration = SharedSessionSettings::as_ref(ctx)
.inactivity_period_between_warning_and_ending_session();
self.is_inactivity_warning_modal_open = true;
self.inactivity_modal.update(ctx, |modal, ctx| {
modal.reset_timer(duration, ctx);
});
ctx.focus(&self.inactivity_modal);
}
pub fn close_inactivity_warning_modal(&mut self) {
self.is_inactivity_warning_modal_open = false
}
pub fn inactivity_modal(&self) -> &ViewHandle<InactivityModal> {
&self.inactivity_modal
}
pub fn revoke_all_mouse_state_handle(&self) -> &MouseStateHandle {
&self.revoke_all_mouse_state_handle
}
}