mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 22:10:15 +03:00
New Top Bar (#7488)
Fixes #7411 So far, this branch removes window control buttons and go-to dashboard (hamburger icon), and adds option for dashboard to set offset of the rest of top bar panels.
This commit is contained in:
parent
b656b336c7
commit
59329bd59a
@ -268,10 +268,6 @@ pub fn register_views(app: &Application) {
|
||||
// ListView we use below.
|
||||
type PlaceholderEntryType = ensogl_component::list_view::entry::Label;
|
||||
app.views.register::<ensogl_component::list_view::ListView<PlaceholderEntryType>>();
|
||||
|
||||
if enso_config::ARGS.groups.startup.options.platform.value == "web" {
|
||||
app.views.register::<ide_view::project_view_top_bar::window_control_buttons::View>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,95 +0,0 @@
|
||||
//! Provides a button to switch back from the project view to the dashboard.
|
||||
|
||||
use ensogl::prelude::*;
|
||||
|
||||
use ensogl::application::tooltip;
|
||||
use ensogl::application::Application;
|
||||
use ensogl::display;
|
||||
use ensogl::display::style;
|
||||
use ensogl_component::toggle_button;
|
||||
use ensogl_component::toggle_button::ToggleButton;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
/// The width and height of the button.
|
||||
pub const SIZE: f32 = 16.0;
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Icon ===
|
||||
// ============
|
||||
|
||||
/// Defines an icon for returning to the dashboard. It looks like a hamburger button.
|
||||
mod icon {
|
||||
use super::*;
|
||||
|
||||
use ensogl::data::color;
|
||||
use ensogl_component::toggle_button::ColorableShape;
|
||||
|
||||
ensogl::shape! {
|
||||
(style: Style, color_rgba: Vector4<f32>) {
|
||||
let fill_color = Var::<color::Rgba>::from(color_rgba);
|
||||
let width = Var::<Pixels>::from("input_size.x");
|
||||
let height = Var::<Pixels>::from("input_size.y");
|
||||
let unit = &width / SIZE;
|
||||
let mid_bar = Rect((&unit * 12.0, &unit * 3.0)).corners_radius(&unit);
|
||||
let top_bar = mid_bar.translate_y(&unit * -5.0);
|
||||
let bottom_bar = mid_bar.translate_y(&unit * 5.0);
|
||||
let all_bars = top_bar + mid_bar + bottom_bar;
|
||||
let shape = all_bars.fill(fill_color);
|
||||
let hover_area = Rect((&width, &height)).fill(INVISIBLE_HOVER_COLOR);
|
||||
(shape + hover_area).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorableShape for Shape {
|
||||
fn set_color(&self, color: color::Rgba) {
|
||||
self.color_rgba.set(Vector4::new(color.red, color.green, color.blue, color.alpha));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === View ===
|
||||
// ============
|
||||
|
||||
/// Provides a button to switch back from the project view to the dashboard.
|
||||
#[derive(Debug, Clone, CloneRef, Deref, display::Object)]
|
||||
pub struct View {
|
||||
button: ToggleButton<icon::Shape>,
|
||||
}
|
||||
|
||||
impl View {
|
||||
/// Constructor.
|
||||
pub fn new(app: &Application) -> Self {
|
||||
let scene = &app.display.default_scene;
|
||||
|
||||
let tooltip_style = tooltip::Style::set_label("Dashboard".to_owned())
|
||||
.with_placement(tooltip::Placement::Right);
|
||||
let button = ToggleButton::<icon::Shape>::new(app, tooltip_style);
|
||||
scene.layers.panel.add(&button);
|
||||
|
||||
button.set_color_scheme(Self::color_scheme(&scene.style_sheet));
|
||||
button.set_size(Vector2(SIZE, SIZE));
|
||||
|
||||
Self { button }
|
||||
}
|
||||
|
||||
fn color_scheme(style_sheet: &style::Sheet) -> toggle_button::ColorScheme {
|
||||
let default_color_scheme = toggle_button::default_color_scheme(style_sheet);
|
||||
toggle_button::ColorScheme {
|
||||
// Make it look like a normal button (as opposed to a toggle button) by not having a
|
||||
// toggled state visually.
|
||||
toggled: default_color_scheme.non_toggled,
|
||||
toggled_hovered: default_color_scheme.hovered,
|
||||
..default_color_scheme
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,6 @@
|
||||
|
||||
use ensogl::prelude::*;
|
||||
|
||||
use enso_config::ARGS;
|
||||
use ensogl::application::Application;
|
||||
use ensogl::display;
|
||||
use ensogl::display::shape::compound::rectangle::Rectangle;
|
||||
@ -32,14 +31,10 @@ use project_name::ProjectName;
|
||||
// ==============
|
||||
|
||||
pub mod project_name;
|
||||
pub mod window_control_buttons;
|
||||
|
||||
pub use breadcrumbs::LocalCall;
|
||||
|
||||
|
||||
|
||||
mod breadcrumbs;
|
||||
mod go_to_dashboard_button;
|
||||
|
||||
|
||||
|
||||
@ -116,9 +111,6 @@ impl ProjectNameWithEnvironmentSelector {
|
||||
pub struct ProjectViewTopBar {
|
||||
#[display_object]
|
||||
root: display::object::Instance,
|
||||
/// These buttons are only visible in a cloud environment.
|
||||
pub window_control_buttons: window_control_buttons::View,
|
||||
pub go_to_dashboard_button: go_to_dashboard_button::View,
|
||||
pub breadcrumbs: breadcrumbs::Breadcrumbs,
|
||||
pub project_name_with_environment_selector: ProjectNameWithEnvironmentSelector,
|
||||
network: frp::Network,
|
||||
@ -128,15 +120,9 @@ impl ProjectViewTopBar {
|
||||
/// Constructor.
|
||||
pub fn new(app: &Application) -> Self {
|
||||
let root = display::object::Instance::new_named("ProjectViewTopBar");
|
||||
let window_control_buttons = app.new_view::<window_control_buttons::View>();
|
||||
let go_to_dashboard_button = go_to_dashboard_button::View::new(app);
|
||||
let breadcrumbs = breadcrumbs::Breadcrumbs::new(app);
|
||||
let project_name_with_environment_selector = ProjectNameWithEnvironmentSelector::new(app);
|
||||
|
||||
if ARGS.groups.startup.options.platform.value == "web" {
|
||||
root.add_child(&window_control_buttons);
|
||||
}
|
||||
root.add_child(&go_to_dashboard_button);
|
||||
root.add_child(&project_name_with_environment_selector);
|
||||
root.add_child(&breadcrumbs);
|
||||
root.use_auto_layout().set_children_alignment_center();
|
||||
@ -145,15 +131,7 @@ impl ProjectViewTopBar {
|
||||
|
||||
let network = frp::Network::new("ProjectViewTopBar");
|
||||
|
||||
Self {
|
||||
root,
|
||||
window_control_buttons,
|
||||
go_to_dashboard_button,
|
||||
breadcrumbs,
|
||||
project_name_with_environment_selector,
|
||||
network,
|
||||
}
|
||||
.init()
|
||||
Self { root, breadcrumbs, project_name_with_environment_selector, network }.init()
|
||||
}
|
||||
|
||||
fn init(self) -> Self {
|
||||
@ -165,10 +143,13 @@ impl ProjectViewTopBar {
|
||||
init <- source_();
|
||||
let gap = style_watch.get_number(theme::gap);
|
||||
let padding_left = style_watch.get_number(theme::padding_left);
|
||||
let padding_top = style_watch.get_number(theme::padding_top);
|
||||
gap <- all(init, gap)._1();
|
||||
padding_left <- all(init, padding_left)._1();
|
||||
padding_top <- all(init, padding_top)._1();
|
||||
eval gap([root](g) { root.set_gap((*g, 0.0)); });
|
||||
eval padding_left([root](p) { root.set_padding_left(*p); });
|
||||
eval padding_top([root](p) { root.set_padding_top(*p); });
|
||||
}
|
||||
init.emit(());
|
||||
self
|
||||
|
@ -1,207 +0,0 @@
|
||||
//! The component with buttons in the top left corner. See [[View]].
|
||||
|
||||
use ensogl::display::shape::*;
|
||||
use ensogl::prelude::*;
|
||||
|
||||
use enso_frp as frp;
|
||||
use ensogl::application;
|
||||
use ensogl::application::Application;
|
||||
use ensogl::display;
|
||||
use ensogl::display::object::ObjectOps;
|
||||
use ensogl::shape;
|
||||
use ensogl_hardcoded_theme::application::window_control_buttons as theme;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
/// Width of the buttons panel.
|
||||
pub const MACOS_TRAFFIC_LIGHTS_CONTENT_WIDTH: f32 = 52.0;
|
||||
/// Height of the buttons panel.
|
||||
pub const MACOS_TRAFFIC_LIGHTS_CONTENT_HEIGHT: f32 = 12.0;
|
||||
/// Horizontal and vertical offset between traffic lights and window border
|
||||
pub const MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET: f32 = 13.0;
|
||||
/// The vertical center of the traffic lights, relative to the window border.
|
||||
pub const MACOS_TRAFFIC_LIGHTS_VERTICAL_CENTER: f32 =
|
||||
-MACOS_TRAFFIC_LIGHTS_SIDE_OFFSET - MACOS_TRAFFIC_LIGHTS_CONTENT_HEIGHT / 2.0;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub mod close;
|
||||
pub mod fullscreen;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Shapes ===
|
||||
// ==============
|
||||
|
||||
mod shape {
|
||||
use super::*;
|
||||
shape! {
|
||||
alignment = center;
|
||||
(style: Style) {
|
||||
Plane().fill(INVISIBLE_HOVER_COLOR).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Model ===
|
||||
// =============
|
||||
|
||||
/// An internal model of Status Bar component
|
||||
#[derive(Clone, CloneRef, Debug, display::Object)]
|
||||
pub struct Model {
|
||||
display_object: display::object::Instance,
|
||||
shape: shape::View,
|
||||
close: close::View,
|
||||
fullscreen: fullscreen::View,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
/// Constructor.
|
||||
pub fn new(app: &Application) -> Self {
|
||||
let display_object = display::object::Instance::new_named("WindowControlButtons");
|
||||
|
||||
ensogl::shapes_order_dependencies! {
|
||||
app.display.default_scene => {
|
||||
shape -> close::shape;
|
||||
shape -> fullscreen::shape;
|
||||
}
|
||||
};
|
||||
let close = close::View::new(app);
|
||||
display_object.add_child(&close);
|
||||
|
||||
let fullscreen = fullscreen::View::new(app);
|
||||
display_object.add_child(&fullscreen);
|
||||
|
||||
let shape = shape::View::new();
|
||||
display_object.add_child(&shape);
|
||||
|
||||
app.display.default_scene.layers.panel.add(&display_object);
|
||||
|
||||
Self { display_object, shape, close, fullscreen }
|
||||
}
|
||||
|
||||
/// Updates positions of the buttons and sizes of the mouse area.
|
||||
pub fn set_layout(&self, spacing: f32) {
|
||||
let close_size = self.close.size.value();
|
||||
let fullscreen_size = self.fullscreen.size.value();
|
||||
let fullscreen_offset = Vector2(close_size.x + spacing, 0.0);
|
||||
self.fullscreen.set_xy(fullscreen_offset);
|
||||
let width = fullscreen_offset.x + fullscreen_size.x;
|
||||
let height = max(close_size.y, fullscreen_size.y);
|
||||
let size = Vector2(width, height);
|
||||
self.shape.set_size(size);
|
||||
self.display_object.set_size(size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===========
|
||||
// === FRP ===
|
||||
// ===========
|
||||
|
||||
ensogl::define_endpoints! {
|
||||
Input {
|
||||
enabled (bool),
|
||||
}
|
||||
Output {
|
||||
close(),
|
||||
fullscreen(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === View ===
|
||||
// ============
|
||||
|
||||
/// The Top Buttons Panel component.
|
||||
///
|
||||
/// The panel contains two buttons: one for closing IDE and one for toggling the fullscreen mode.
|
||||
/// The panel is meant to be displayed only when IDE runs in a cloud environment.
|
||||
#[derive(Clone, CloneRef, Debug, display::Object)]
|
||||
pub struct View {
|
||||
#[allow(missing_docs)]
|
||||
pub frp: Frp,
|
||||
#[display_object]
|
||||
model: Model,
|
||||
style: StyleWatchFrp,
|
||||
}
|
||||
|
||||
impl View {
|
||||
/// Constructor.
|
||||
pub fn new(app: &Application) -> Self {
|
||||
let frp = Frp::new();
|
||||
let model = Model::new(app);
|
||||
let network = &frp.network;
|
||||
|
||||
let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
|
||||
let radius = style.get_number(theme::radius);
|
||||
let spacing = style.get_number(theme::spacing);
|
||||
|
||||
frp::extend! { network
|
||||
// Layout
|
||||
button_size <- radius.map(|&r| Vector2(2.0 * r, 2.0 * r));
|
||||
model.close.set_size <+ button_size;
|
||||
model.fullscreen.set_size <+ button_size;
|
||||
button_resized <- any_(&model.close.size, &model.fullscreen.size);
|
||||
_eval <- all_with(&button_resized, &spacing,
|
||||
f!((_, spacing) model.set_layout(*spacing))
|
||||
);
|
||||
|
||||
// Handle the panel-wide hover
|
||||
mouse_near_buttons <- bool(
|
||||
&model.shape.events_deprecated.mouse_out,
|
||||
&model.shape.events_deprecated.mouse_over
|
||||
);
|
||||
mouse_on_any_buttton <- model.close.is_hovered.or(&model.fullscreen.is_hovered);
|
||||
mouse_nearby <- mouse_near_buttons.or(&mouse_on_any_buttton);
|
||||
model.close.mouse_nearby <+ mouse_nearby;
|
||||
model.fullscreen.mouse_nearby <+ mouse_nearby;
|
||||
|
||||
// === Handle buttons' clicked events ===
|
||||
frp.source.close <+ model.close.clicked;
|
||||
frp.source.fullscreen <+ model.fullscreen.clicked;
|
||||
}
|
||||
|
||||
model.set_layout(spacing.value());
|
||||
|
||||
Self { frp, model, style }
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for View {
|
||||
type Target = Frp;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.frp
|
||||
}
|
||||
}
|
||||
|
||||
impl FrpNetworkProvider for View {
|
||||
fn network(&self) -> &frp::Network {
|
||||
&self.frp.network
|
||||
}
|
||||
}
|
||||
|
||||
impl application::View for View {
|
||||
fn label() -> &'static str {
|
||||
"TopButtons"
|
||||
}
|
||||
|
||||
fn new(app: &Application) -> Self {
|
||||
View::new(app)
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
//! The close button in the Top Button panel.
|
||||
|
||||
use ensogl_component::button::prelude::*;
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub use ensogl_hardcoded_theme::application::window_control_buttons::close as theme;
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Shape ===
|
||||
// =============
|
||||
|
||||
/// The shape for "close" button. It places X-lie cross on a circle.
|
||||
pub mod shape {
|
||||
use super::*;
|
||||
|
||||
ensogl::shape! {
|
||||
(style: Style, background_color: Vector4<f32>, icon_color: Vector4<f32>) {
|
||||
let size = Var::canvas_size();
|
||||
let radius = Min::min(size.x(),size.y()) / 2.0;
|
||||
let angle = Radians::from(45.0.degrees());
|
||||
let bar_length = &radius * 4.0 / 3.0;
|
||||
let bar_width = &bar_length / 6.5;
|
||||
#[allow(clippy::disallowed_names)] // The `bar` name here is totally legit.
|
||||
let bar = Rect((bar_length, &bar_width)).corners_radius(bar_width);
|
||||
let cross = (bar.rotate(angle) + bar.rotate(-angle)).into();
|
||||
shape(background_color, icon_color, cross, radius)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ButtonShape for shape::Shape {
|
||||
fn debug_name() -> &'static str {
|
||||
"CloseButton"
|
||||
}
|
||||
|
||||
fn background_color_path(state: State) -> StaticPath {
|
||||
match state {
|
||||
State::Unconcerned => theme::normal::background_color,
|
||||
State::Hovered => theme::hovered::background_color,
|
||||
State::Pressed => theme::pressed::background_color,
|
||||
}
|
||||
}
|
||||
|
||||
fn icon_color_path(state: State) -> StaticPath {
|
||||
match state {
|
||||
State::Unconcerned => theme::normal::icon_color,
|
||||
State::Hovered => theme::hovered::icon_color,
|
||||
State::Pressed => theme::pressed::icon_color,
|
||||
}
|
||||
}
|
||||
|
||||
fn background_color(&self) -> &ProxyParam<Attribute<Vector4<f32>>> {
|
||||
&self.background_color
|
||||
}
|
||||
|
||||
fn icon_color(&self) -> &ProxyParam<Attribute<Vector4<f32>>> {
|
||||
&self.icon_color
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === View ===
|
||||
// ============
|
||||
|
||||
/// The view component with the close button.
|
||||
///
|
||||
/// The button styled after macOS, i.e. consists of an icon shape placed on top of a circle.
|
||||
/// The icon is visible when button or its neighborhood (as provided by `mouse_nearby` input) is
|
||||
/// hovered.
|
||||
pub type View = ensogl_component::button::View<shape::Shape>;
|
@ -1,77 +0,0 @@
|
||||
//! The fullscreen button in the Top Button panel.
|
||||
|
||||
use ensogl_component::button::prelude::*;
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub use ensogl_hardcoded_theme::application::window_control_buttons::fullscreen as theme;
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Shape ===
|
||||
// =============
|
||||
|
||||
/// The shape for "fullscreen" button. The icon consists if two triangles ◤◢ centered around single
|
||||
/// point.
|
||||
pub mod shape {
|
||||
use super::*;
|
||||
ensogl::shape! {
|
||||
(style: Style, background_color: Vector4<f32>, icon_color: Vector4<f32>) {
|
||||
let size = Var::canvas_size();
|
||||
let radius = Min::min(size.x(),size.y()) / 2.0;
|
||||
let round = &radius / 6.0;
|
||||
let rect = Rect((&radius,&radius)).corners_radius(round);
|
||||
let strip_sizes = (&radius * 2.0 / 9.0, &radius*2.0);
|
||||
let strip = Rect(strip_sizes).rotate(Radians::from(45.0.degrees()));
|
||||
let icon = rect - strip;
|
||||
shape(background_color, icon_color, icon.into(), radius)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ButtonShape for shape::Shape {
|
||||
fn debug_name() -> &'static str {
|
||||
"FullscreenButton"
|
||||
}
|
||||
|
||||
fn background_color_path(state: State) -> StaticPath {
|
||||
match state {
|
||||
State::Unconcerned => theme::normal::background_color,
|
||||
State::Hovered => theme::hovered::background_color,
|
||||
State::Pressed => theme::pressed::background_color,
|
||||
}
|
||||
}
|
||||
|
||||
fn icon_color_path(state: State) -> StaticPath {
|
||||
match state {
|
||||
State::Unconcerned => theme::normal::icon_color,
|
||||
State::Hovered => theme::hovered::icon_color,
|
||||
State::Pressed => theme::pressed::icon_color,
|
||||
}
|
||||
}
|
||||
|
||||
fn background_color(&self) -> &ProxyParam<Attribute<Vector4<f32>>> {
|
||||
&self.background_color
|
||||
}
|
||||
|
||||
fn icon_color(&self) -> &ProxyParam<Attribute<Vector4<f32>>> {
|
||||
&self.icon_color
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === View ===
|
||||
// ============
|
||||
|
||||
/// The view component with the fullscreen button.
|
||||
///
|
||||
/// The button styled after macOS, i.e. consists of an icon shape placed on top of a circle.
|
||||
/// The icon is visible when button or its neighborhood (as provided by `mouse_nearby` input) is
|
||||
/// hovered.
|
||||
pub type View = ensogl_component::button::View<shape::Shape>;
|
@ -26,7 +26,6 @@ use ensogl_component::text;
|
||||
use ensogl_component::text::selection::Selection;
|
||||
use ensogl_hardcoded_theme::Theme;
|
||||
use ide_view_graph_editor::NodeSource;
|
||||
use ide_view_project_view_top_bar::window_control_buttons;
|
||||
use ide_view_project_view_top_bar::ProjectViewTopBar;
|
||||
|
||||
|
||||
@ -285,20 +284,12 @@ impl Model {
|
||||
project_view_top_bar_size: Vector2,
|
||||
) {
|
||||
let top_left = Vector2(-scene_shape.width, scene_shape.height) / 2.0;
|
||||
let buttons_y = window_control_buttons::MACOS_TRAFFIC_LIGHTS_VERTICAL_CENTER;
|
||||
let y = buttons_y - project_view_top_bar_size.y / 2.0;
|
||||
let project_view_top_bar_origin = Vector2(0.0, y);
|
||||
let y = -project_view_top_bar_size.y;
|
||||
let x = ARGS.groups.window.options.top_bar_offset.value;
|
||||
let project_view_top_bar_origin = Vector2(x as f32, y);
|
||||
self.top_bar.set_xy(top_left + project_view_top_bar_origin);
|
||||
}
|
||||
|
||||
fn on_close_clicked(&self) {
|
||||
js::close(enso_config::window_app_scope_name);
|
||||
}
|
||||
|
||||
fn on_fullscreen_clicked(&self) {
|
||||
js::fullscreen();
|
||||
}
|
||||
|
||||
fn show_project_list(&self) {
|
||||
self.display_object.add_child(&*self.project_list);
|
||||
}
|
||||
@ -318,43 +309,6 @@ impl Model {
|
||||
|
||||
|
||||
|
||||
mod js {
|
||||
// use super::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen(inline_js = "
|
||||
export function close(windowAppScopeConfigName) {
|
||||
try { window[windowAppScopeConfigName].close(); }
|
||||
catch(e) {
|
||||
console.error(`Exception thrown from window.${windowAppScopeConfigName}.close:`,e)
|
||||
}
|
||||
}")]
|
||||
extern "C" {
|
||||
#[allow(unsafe_code)]
|
||||
pub fn close(window_app_scope_name: &str);
|
||||
}
|
||||
|
||||
|
||||
#[wasm_bindgen(inline_js = "
|
||||
export function fullscreen() {
|
||||
try {
|
||||
if(document.fullscreenElement === null)
|
||||
document.documentElement.requestFullscreen()
|
||||
else
|
||||
document.exitFullscreen()
|
||||
} catch (e) {
|
||||
console.error('Exception thrown when toggling fullscreen display mode:',e)
|
||||
}
|
||||
}
|
||||
")]
|
||||
extern "C" {
|
||||
#[allow(unsafe_code)]
|
||||
pub fn fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === View ===
|
||||
// ============
|
||||
@ -424,12 +378,6 @@ impl View {
|
||||
let project_view_top_bar = &model.top_bar;
|
||||
frp::extend! { network
|
||||
init <- source_();
|
||||
let window_control_buttons = &project_view_top_bar.window_control_buttons;
|
||||
eval_ window_control_buttons.close (model.on_close_clicked());
|
||||
eval_ window_control_buttons.fullscreen (model.on_fullscreen_clicked());
|
||||
let go_to_dashboard_button = &project_view_top_bar.go_to_dashboard_button;
|
||||
frp.source.go_to_dashboard_button_pressed <+
|
||||
go_to_dashboard_button.is_pressed.on_true();
|
||||
|
||||
let project_view_top_bar_display_object = project_view_top_bar.display_object();
|
||||
_eval <- all_with3(
|
||||
|
@ -1,67 +0,0 @@
|
||||
//! Defines a UI container for the window control buttons and the "go to dashboard" button. This is
|
||||
//! merely here to make use of the auto-layout functionality.
|
||||
|
||||
use ensogl::prelude::*;
|
||||
|
||||
use enso_config::ARGS;
|
||||
use ensogl::application::Application;
|
||||
use ensogl::display;
|
||||
|
||||
|
||||
|
||||
mod go_to_dashboard_button;
|
||||
pub mod window_control_buttons;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
/// The gap in pixels between the various components of the project view top bar.
|
||||
const GAP: f32 = 16.0;
|
||||
/// The padding left of the project view top bar.
|
||||
const PADDING_LEFT: f32 = 19.0;
|
||||
|
||||
|
||||
|
||||
// ============================
|
||||
// === Project View Top Bar ===
|
||||
// ============================
|
||||
|
||||
/// Defines a UI container for the window control buttons and the "go to dashboard" button. This is
|
||||
/// merely here to make use of the auto-layout functionality.
|
||||
#[derive(Clone, CloneRef, Debug, display::Object)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct ProjectViewTopBar {
|
||||
display_object: display::object::Instance,
|
||||
/// These buttons are only visible in a cloud environment.
|
||||
pub window_control_buttons: window_control_buttons::View,
|
||||
pub go_to_dashboard_button: go_to_dashboard_button::View,
|
||||
}
|
||||
|
||||
impl ProjectViewTopBar {
|
||||
/// Constructor.
|
||||
pub fn new(app: &Application) -> Self {
|
||||
let display_object = display::object::Instance::new_named("ProjectViewTopBar");
|
||||
let window_control_buttons = app.new_view::<window_control_buttons::View>();
|
||||
let go_to_dashboard_button = go_to_dashboard_button::View::new(app);
|
||||
|
||||
if ARGS.groups.startup.options.platform.value == "web" {
|
||||
display_object.add_child(&window_control_buttons);
|
||||
}
|
||||
display_object.add_child(&go_to_dashboard_button);
|
||||
display_object
|
||||
.use_auto_layout()
|
||||
.set_gap((GAP, 0.0))
|
||||
.set_padding_left(PADDING_LEFT)
|
||||
// We use `GAP` as the right padding since it delimits the space to the part of the top
|
||||
// bar that's defined in the graph editor.
|
||||
.set_padding_right(GAP)
|
||||
.set_children_alignment_center();
|
||||
|
||||
app.display.default_scene.layers.panel.add(&display_object);
|
||||
|
||||
Self { display_object, window_control_buttons, go_to_dashboard_button }
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ export enum Platform {
|
||||
linux = 'Linux',
|
||||
}
|
||||
|
||||
/** Returns the platform the app is currently running on.
|
||||
/** Return the platform the app is currently running on.
|
||||
* This is used to determine whether `metaKey` or `ctrlKey` is used in shortcuts. */
|
||||
export function platform(): Platform {
|
||||
if (/windows/i.test(navigator.userAgent)) {
|
||||
@ -37,3 +37,23 @@ export function platform(): Platform {
|
||||
return Platform.unknown
|
||||
}
|
||||
}
|
||||
|
||||
/** Return whether the device is running Windows. */
|
||||
export function isOnWindows() {
|
||||
return platform() === Platform.windows
|
||||
}
|
||||
|
||||
/** Return whether the device is running macOS. */
|
||||
export function isOnMacOS() {
|
||||
return platform() === Platform.macOS
|
||||
}
|
||||
|
||||
/** Return whether the device is running Linux. */
|
||||
export function isOnLinux() {
|
||||
return platform() === Platform.linux
|
||||
}
|
||||
|
||||
/** Return whether the device is running an unknown OS. */
|
||||
export function isOnUnknownOS() {
|
||||
return platform() === Platform.unknown
|
||||
}
|
||||
|
@ -28,6 +28,11 @@
|
||||
"value": true,
|
||||
"defaultDescription": "false on MacOS, true otherwise",
|
||||
"description": "Draw window frame."
|
||||
},
|
||||
"topBarOffset": {
|
||||
"value": 0,
|
||||
"description": "The offset of rust-rendered toolbar from window's left edge.",
|
||||
"primary": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -17,7 +17,7 @@ export interface AssetInfoBarProps {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export default function AssetInfoBar(_props: AssetInfoBarProps) {
|
||||
return (
|
||||
<div className="flex items-center shrink-0 bg-frame-bg rounded-full gap-3 h-8 px-2">
|
||||
<div className="flex items-center shrink-0 bg-frame-bg rounded-full gap-3 h-8 px-2 cursor-default pointer-events-auto">
|
||||
<Button
|
||||
active={false}
|
||||
disabled
|
||||
|
@ -8,12 +8,13 @@ export interface ButtonProps {
|
||||
image: string
|
||||
/** A title that is only shown when `disabled` is true. */
|
||||
error?: string | null
|
||||
className?: string
|
||||
onClick: (event: React.MouseEvent) => void
|
||||
}
|
||||
|
||||
/** A styled button. */
|
||||
export default function Button(props: ButtonProps) {
|
||||
const { active = false, disabled = false, image, error, onClick } = props
|
||||
const { active = false, disabled = false, image, error, className, onClick } = props
|
||||
|
||||
return (
|
||||
<button
|
||||
@ -21,7 +22,7 @@ export default function Button(props: ButtonProps) {
|
||||
{...(disabled && error != null ? { title: error } : {})}
|
||||
className={`cursor-pointer disabled:cursor-default disabled:opacity-50 disabled:cursor-not-allowed hover:opacity-100 ${
|
||||
active ? '' : 'opacity-50'
|
||||
}`}
|
||||
} ${className ?? ''}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<img src={image} />
|
||||
|
@ -77,6 +77,18 @@ export default function Dashboard(props: DashboardProps) {
|
||||
unsetModal()
|
||||
}, [page, /* should never change */ unsetModal])
|
||||
|
||||
React.useEffect(() => {
|
||||
const onClick = () => {
|
||||
if (getSelection()?.type !== 'Range') {
|
||||
unsetModal()
|
||||
}
|
||||
}
|
||||
document.addEventListener('click', onClick)
|
||||
return () => {
|
||||
document.removeEventListener('click', onClick)
|
||||
}
|
||||
}, [/* should never change */ unsetModal])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
supportsLocalBackend &&
|
||||
@ -91,16 +103,6 @@ export default function Dashboard(props: DashboardProps) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
const goToDrive = () => {
|
||||
setPage(pageSwitcher.Page.drive)
|
||||
}
|
||||
document.addEventListener('show-dashboard', goToDrive)
|
||||
return () => {
|
||||
document.removeEventListener('show-dashboard', goToDrive)
|
||||
}
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
// The types come from a third-party API and cannot be changed.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
@ -206,101 +208,113 @@ export default function Dashboard(props: DashboardProps) {
|
||||
setProject(null)
|
||||
}, [])
|
||||
|
||||
const closeModalIfExists = React.useCallback(() => {
|
||||
if (getSelection()?.type !== 'Range') {
|
||||
unsetModal()
|
||||
}
|
||||
}, [/* should never change */ unsetModal])
|
||||
|
||||
const driveHiddenClass = page === pageSwitcher.Page.drive ? '' : 'hidden'
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col gap-2 relative select-none text-primary text-xs h-screen pb-2 ${
|
||||
page === pageSwitcher.Page.drive ? '' : 'hidden'
|
||||
}`}
|
||||
onContextMenu={event => {
|
||||
event.preventDefault()
|
||||
unsetModal()
|
||||
}}
|
||||
onClick={closeModalIfExists}
|
||||
>
|
||||
<TopBar
|
||||
supportsLocalBackend={supportsLocalBackend}
|
||||
projectName={project?.name ?? null}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
asset={null}
|
||||
isEditorDisabled={project == null}
|
||||
isHelpChatOpen={isHelpChatOpen}
|
||||
setIsHelpChatOpen={setIsHelpChatOpen}
|
||||
setBackendType={setBackendType}
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
{isListingRemoteDirectoryWhileOffline ? (
|
||||
<div className="grow grid place-items-center mx-2">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="text-base text-center">You are not signed in.</div>
|
||||
<button
|
||||
className="text-base text-white bg-help rounded-full self-center leading-170 h-8 py-px w-16"
|
||||
onClick={() => {
|
||||
navigate(app.LOGIN_PATH)
|
||||
}}
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : isListingLocalDirectoryAndWillFail ? (
|
||||
<div className="grow grid place-items-center mx-2">
|
||||
<div className="text-base text-center">
|
||||
Could not connect to the Project Manager. Please try restarting{' '}
|
||||
{common.PRODUCT_NAME}, or manually launching the Project Manager.
|
||||
</div>
|
||||
</div>
|
||||
) : isListingRemoteDirectoryAndWillFail ? (
|
||||
<div className="grow grid place-items-center mx-2">
|
||||
<div className="text-base text-center">
|
||||
We will review your user details and enable the cloud experience for you
|
||||
shortly.
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Templates onTemplateClick={doCreateProject} />
|
||||
<DriveView
|
||||
page={page}
|
||||
initialProjectName={initialProjectName}
|
||||
directoryId={directoryId}
|
||||
setDirectoryId={setDirectoryId}
|
||||
assetListEvents={assetListEvents}
|
||||
dispatchAssetListEvent={dispatchAssetListEvent}
|
||||
query={query}
|
||||
doCreateProject={doCreateProject}
|
||||
doOpenEditor={openEditor}
|
||||
doCloseEditor={closeEditor}
|
||||
appRunner={appRunner}
|
||||
loadingProjectManagerDidFail={loadingProjectManagerDidFail}
|
||||
isListingRemoteDirectoryWhileOffline={isListingRemoteDirectoryWhileOffline}
|
||||
isListingLocalDirectoryAndWillFail={isListingLocalDirectoryAndWillFail}
|
||||
isListingRemoteDirectoryAndWillFail={isListingRemoteDirectoryAndWillFail}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<TheModal />
|
||||
<Editor
|
||||
visible={page === pageSwitcher.Page.editor}
|
||||
project={project}
|
||||
appRunner={appRunner}
|
||||
/>
|
||||
{/* `session.accessToken` MUST be present in order for the `Chat` component to work. */}
|
||||
{isHelpChatVisible && session.accessToken != null && (
|
||||
<Chat
|
||||
isOpen={isHelpChatOpen}
|
||||
doClose={() => {
|
||||
setIsHelpChatOpen(false)
|
||||
<>
|
||||
<div
|
||||
className={`flex flex-col gap-2 relative select-none text-primary text-xs h-screen pb-2 ${
|
||||
page === pageSwitcher.Page.editor ? 'cursor-none pointer-events-none' : ''
|
||||
}`}
|
||||
onContextMenu={event => {
|
||||
event.preventDefault()
|
||||
unsetModal()
|
||||
}}
|
||||
>
|
||||
<TopBar
|
||||
supportsLocalBackend={supportsLocalBackend}
|
||||
projectName={project?.name ?? null}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
asset={null}
|
||||
isEditorDisabled={project == null}
|
||||
isHelpChatOpen={isHelpChatOpen}
|
||||
setIsHelpChatOpen={setIsHelpChatOpen}
|
||||
setBackendType={setBackendType}
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
onSignOut={() => {
|
||||
if (page === pageSwitcher.Page.editor) {
|
||||
setPage(pageSwitcher.Page.drive)
|
||||
}
|
||||
setProject(null)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isListingRemoteDirectoryWhileOffline ? (
|
||||
<div className={`grow grid place-items-center mx-2 ${driveHiddenClass}`}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="text-base text-center">You are not signed in.</div>
|
||||
<button
|
||||
className="text-base text-white bg-help rounded-full self-center leading-170 h-8 py-px w-16"
|
||||
onClick={() => {
|
||||
navigate(app.LOGIN_PATH)
|
||||
}}
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : isListingLocalDirectoryAndWillFail ? (
|
||||
<div className={`grow grid place-items-center mx-2 ${driveHiddenClass}`}>
|
||||
<div className="text-base text-center">
|
||||
Could not connect to the Project Manager. Please try restarting{' '}
|
||||
{common.PRODUCT_NAME}, or manually launching the Project Manager.
|
||||
</div>
|
||||
</div>
|
||||
) : isListingRemoteDirectoryAndWillFail ? (
|
||||
<div className={`grow grid place-items-center mx-2 ${driveHiddenClass}`}>
|
||||
<div className="text-base text-center">
|
||||
We will review your user details and enable the cloud experience for you
|
||||
shortly.
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Templates
|
||||
hidden={page !== pageSwitcher.Page.drive}
|
||||
onTemplateClick={doCreateProject}
|
||||
/>
|
||||
<DriveView
|
||||
hidden={page !== pageSwitcher.Page.drive}
|
||||
page={page}
|
||||
initialProjectName={initialProjectName}
|
||||
directoryId={directoryId}
|
||||
setDirectoryId={setDirectoryId}
|
||||
assetListEvents={assetListEvents}
|
||||
dispatchAssetListEvent={dispatchAssetListEvent}
|
||||
query={query}
|
||||
doCreateProject={doCreateProject}
|
||||
doOpenEditor={openEditor}
|
||||
doCloseEditor={closeEditor}
|
||||
appRunner={appRunner}
|
||||
loadingProjectManagerDidFail={loadingProjectManagerDidFail}
|
||||
isListingRemoteDirectoryWhileOffline={
|
||||
isListingRemoteDirectoryWhileOffline
|
||||
}
|
||||
isListingLocalDirectoryAndWillFail={isListingLocalDirectoryAndWillFail}
|
||||
isListingRemoteDirectoryAndWillFail={
|
||||
isListingRemoteDirectoryAndWillFail
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Editor
|
||||
visible={page === pageSwitcher.Page.editor}
|
||||
project={project}
|
||||
appRunner={appRunner}
|
||||
/>
|
||||
{/* `session.accessToken` MUST be present in order for the `Chat` component to work. */}
|
||||
{isHelpChatVisible && session.accessToken != null && (
|
||||
<Chat
|
||||
isOpen={isHelpChatOpen}
|
||||
doClose={() => {
|
||||
setIsHelpChatOpen(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-primary select-none">
|
||||
<TheModal />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ const DIRECTORY_STACK_KEY = `${common.PRODUCT_NAME.toLowerCase()}-dashboard-dire
|
||||
/** Props for a {@link DriveView}. */
|
||||
export interface DriveViewProps {
|
||||
page: pageSwitcher.Page
|
||||
hidden: boolean
|
||||
initialProjectName: string | null
|
||||
directoryId: backendModule.DirectoryId | null
|
||||
setDirectoryId: (directoryId: backendModule.DirectoryId) => void
|
||||
@ -51,6 +52,7 @@ export interface DriveViewProps {
|
||||
export default function DriveView(props: DriveViewProps) {
|
||||
const {
|
||||
page,
|
||||
hidden,
|
||||
initialProjectName,
|
||||
directoryId,
|
||||
setDirectoryId,
|
||||
@ -255,7 +257,11 @@ export default function DriveView(props: DriveViewProps) {
|
||||
}, [page])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-1 overflow-hidden gap-2.5 px-3.25">
|
||||
<div
|
||||
className={`flex flex-col flex-1 overflow-hidden gap-2.5 px-3.25 ${
|
||||
hidden ? 'hidden' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-col self-start gap-3">
|
||||
<h1 className="text-xl font-bold h-9.5 pl-1.5">
|
||||
{backend.type === backendModule.BackendType.remote
|
||||
|
@ -11,6 +11,8 @@ import GLOBAL_CONFIG from '../../../../../../../../gui/config.yaml' assert { typ
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
/** The horizontal offset of the editor's top bar from the left edge of the window. */
|
||||
const TOP_BAR_X_OFFSET_PX = 96
|
||||
/** The `id` attribute of the element into which the IDE will be rendered. */
|
||||
const IDE_ELEMENT_ID = 'root'
|
||||
const IDE_CDN_URL = 'https://cdn.enso.org/ide'
|
||||
@ -125,6 +127,9 @@ export default function Editor(props: EditorProps) {
|
||||
startup: {
|
||||
project: project.packageName,
|
||||
},
|
||||
window: {
|
||||
topBarOffset: `${TOP_BAR_X_OFFSET_PX}`,
|
||||
},
|
||||
},
|
||||
// Here we actually need explicit undefined.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
@ -163,9 +168,6 @@ export default function Editor(props: EditorProps) {
|
||||
return () => {
|
||||
appRunner.stopApp()
|
||||
}
|
||||
// The backend MUST NOT be a dependency, since the IDE should only be recreated when a new
|
||||
// project is opened, and a local project does not exist on the cloud and vice versa.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}
|
||||
}, [project, /* should never change */ appRunner])
|
||||
|
||||
|
@ -23,7 +23,7 @@ export default function Modal(props: ModalProps) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`inset-0 bg-primary ${
|
||||
className={`inset-0 bg-primary z-10 ${
|
||||
centered ? 'fixed w-screen h-screen grid place-items-center ' : ''
|
||||
}${className ?? ''}`}
|
||||
onClick={event => {
|
||||
|
@ -60,6 +60,7 @@ export default function PageSwitcher(props: PageSwitcherProps) {
|
||||
active={page === pageData.page}
|
||||
disabled={isDisabled}
|
||||
error={ERRORS[pageData.page]}
|
||||
className="pointer-events-auto"
|
||||
onClick={() => {
|
||||
setPage(pageData.page)
|
||||
}}
|
||||
|
@ -242,6 +242,7 @@ function TemplatesRender(props: InternalTemplatesRenderProps) {
|
||||
|
||||
/** Props for a {@link Templates}. */
|
||||
export interface TemplatesProps {
|
||||
hidden: boolean
|
||||
onTemplateClick: (
|
||||
name: string | null,
|
||||
onSpinnerStateChange: (state: spinner.SpinnerState | null) => void
|
||||
@ -250,7 +251,7 @@ export interface TemplatesProps {
|
||||
|
||||
/** A container for a {@link TemplatesRender} which passes it a list of templates. */
|
||||
export default function Templates(props: TemplatesProps) {
|
||||
const { onTemplateClick } = props
|
||||
const { hidden, onTemplateClick } = props
|
||||
|
||||
const [shadowClass, setShadowClass] = React.useState(
|
||||
window.innerWidth <= MAX_WIDTH_NEEDING_SCROLL ? ShadowClass.bottom : ShadowClass.none
|
||||
@ -316,7 +317,7 @@ export default function Templates(props: TemplatesProps) {
|
||||
}, [isOpen])
|
||||
|
||||
return (
|
||||
<div className="mx-2">
|
||||
<div className={`mx-2 ${hidden ? 'hidden' : ''}`}>
|
||||
<div className="flex items-center my-2">
|
||||
<div className="w-4">
|
||||
<div
|
||||
|
@ -28,6 +28,7 @@ export interface TopBarProps {
|
||||
setIsHelpChatOpen: (isHelpChatOpen: boolean) => void
|
||||
query: string
|
||||
setQuery: (value: string) => void
|
||||
onSignOut: () => void
|
||||
}
|
||||
|
||||
/** The {@link TopBarProps.setQuery} parameter is used to communicate with the parent component,
|
||||
@ -44,33 +45,44 @@ export default function TopBar(props: TopBarProps) {
|
||||
setIsHelpChatOpen,
|
||||
query,
|
||||
setQuery,
|
||||
onSignOut,
|
||||
} = props
|
||||
|
||||
return (
|
||||
<div className="relative flex ml-4.75 mr-2.25 mt-2.25 h-8 gap-6">
|
||||
<div className="relative flex ml-4.75 mr-2.25 mt-2.25 h-8 gap-6 z-10">
|
||||
<PageSwitcher page={page} setPage={setPage} isEditorDisabled={isEditorDisabled} />
|
||||
{supportsLocalBackend && <BackendSwitcher setBackendType={setBackendType} />}
|
||||
<div className="grow" />
|
||||
<div className="search-bar absolute flex items-center text-primary bg-frame-bg rounded-full -translate-x-1/2 gap-2.5 left-1/2 h-8 w-98.25 px-2">
|
||||
<label htmlFor="search">
|
||||
<img src={FindIcon} className="opacity-80" />
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
size={1}
|
||||
id="search"
|
||||
placeholder="Type to search for projects, data connectors, users, and more."
|
||||
value={query}
|
||||
onChange={event => {
|
||||
setQuery(event.target.value)
|
||||
}}
|
||||
className="grow bg-transparent leading-5 h-6 py-px"
|
||||
/>
|
||||
</div>
|
||||
{supportsLocalBackend && page !== pageSwitcher.Page.editor && (
|
||||
<BackendSwitcher setBackendType={setBackendType} />
|
||||
)}
|
||||
<div className="grow" />
|
||||
{page !== pageSwitcher.Page.editor && (
|
||||
<>
|
||||
<div className="search-bar absolute flex items-center text-primary bg-frame-bg rounded-full -translate-x-1/2 gap-2.5 left-1/2 h-8 w-98.25 px-2">
|
||||
<label htmlFor="search">
|
||||
<img src={FindIcon} className="opacity-80" />
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
size={1}
|
||||
id="search"
|
||||
placeholder="Type to search for projects, data connectors, users, and more."
|
||||
value={query}
|
||||
onChange={event => {
|
||||
setQuery(event.target.value)
|
||||
}}
|
||||
className="grow bg-transparent leading-5 h-6 py-px"
|
||||
/>
|
||||
</div>
|
||||
<div className="grow" />
|
||||
</>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<AssetInfoBar asset={asset} />
|
||||
<UserBar isHelpChatOpen={isHelpChatOpen} setIsHelpChatOpen={setIsHelpChatOpen} />
|
||||
<UserBar
|
||||
isHelpChatOpen={isHelpChatOpen}
|
||||
setIsHelpChatOpen={setIsHelpChatOpen}
|
||||
onSignOut={onSignOut}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -17,14 +17,15 @@ import UserMenu from './userMenu'
|
||||
export interface UserBarProps {
|
||||
isHelpChatOpen: boolean
|
||||
setIsHelpChatOpen: (isHelpChatOpen: boolean) => void
|
||||
onSignOut: () => void
|
||||
}
|
||||
|
||||
/** A toolbar containing chat and the user menu. */
|
||||
export default function UserBar(props: UserBarProps) {
|
||||
const { isHelpChatOpen, setIsHelpChatOpen } = props
|
||||
const { isHelpChatOpen, setIsHelpChatOpen, onSignOut } = props
|
||||
const { updateModal } = modalProvider.useSetModal()
|
||||
return (
|
||||
<div className="flex shrink-0 items-center bg-frame-bg rounded-full gap-3 h-8 pl-2 pr-0.75">
|
||||
<div className="flex shrink-0 items-center bg-frame-bg rounded-full gap-3 h-8 pl-2 pr-0.75 cursor-default pointer-events-auto">
|
||||
<Button
|
||||
active={isHelpChatOpen}
|
||||
image={ChatIcon}
|
||||
@ -35,7 +36,9 @@ export default function UserBar(props: UserBarProps) {
|
||||
<button
|
||||
onClick={event => {
|
||||
event.stopPropagation()
|
||||
updateModal(oldModal => (oldModal?.type === UserMenu ? null : <UserMenu />))
|
||||
updateModal(oldModal =>
|
||||
oldModal?.type === UserMenu ? null : <UserMenu onSignOut={onSignOut} />
|
||||
)
|
||||
}}
|
||||
>
|
||||
<img src={DefaultUserIcon} height={28} width={28} />
|
||||
|
@ -37,8 +37,14 @@ function UserMenuItem(props: React.PropsWithChildren<UserMenuItemProps>) {
|
||||
)
|
||||
}
|
||||
|
||||
/** Props for a {@link UserMenu}. */
|
||||
export interface UserMenuProps {
|
||||
onSignOut: () => void
|
||||
}
|
||||
|
||||
/** Handling the UserMenuItem click event logic and displaying its content. */
|
||||
export default function UserMenu() {
|
||||
export default function UserMenu(props: UserMenuProps) {
|
||||
const { onSignOut } = props
|
||||
const { signOut } = auth.useAuth()
|
||||
const { accessToken, organization } = auth.useNonPartialUserSession()
|
||||
const navigate = hooks.useNavigate()
|
||||
@ -84,7 +90,17 @@ export default function UserMenu() {
|
||||
Change your password
|
||||
</UserMenuItem>
|
||||
)}
|
||||
<UserMenuItem onClick={signOut}>Sign out</UserMenuItem>
|
||||
<UserMenuItem
|
||||
onClick={() => {
|
||||
onSignOut()
|
||||
// Wait until React has switched back to drive view, before signing out.
|
||||
window.setTimeout(() => {
|
||||
void signOut()
|
||||
}, 0)
|
||||
}}
|
||||
>
|
||||
Sign out
|
||||
</UserMenuItem>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
@ -12,6 +12,12 @@ body {
|
||||
animation-duration: 0.2s;
|
||||
}
|
||||
|
||||
.enso-dashboard {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* These styles MUST still be copied
|
||||
* as `.enso-dashboard body` and `.enso-dashboard html` make no sense. */
|
||||
.enso-dashboard,
|
||||
@ -150,4 +156,10 @@ body {
|
||||
.pointer-events-none-recursive * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* The class is repeated to make this take precedence over most Tailwind classes. */
|
||||
.cursor-none-recursive.cursor-none-recursive,
|
||||
.cursor-none-recursive.cursor-none-recursive * {
|
||||
cursor: none;
|
||||
}
|
||||
}
|
||||
|
@ -194,6 +194,7 @@ define_themes! { [light:0, dark:1]
|
||||
|
||||
top_bar {
|
||||
padding_left = 19.0, 19.0;
|
||||
padding_top = 9.0, 9.0;
|
||||
gap = 16.0, 16.0;
|
||||
background {
|
||||
color = Rgba(1.0, 1.0, 1.0, 0.44), Rgba(0.0, 0.0, 0.0, 0.44);
|
||||
|
Loading…
Reference in New Issue
Block a user