Revert "refactor(client/windows): page-only state climbs back down from root, on the measured rules"
This reverts commit 427f99a06647fd99e5c72d18cb7a710d8a7ac885.
This commit is contained in:
@@ -40,18 +40,14 @@ const TILE_GAP: f64 = 12.0;
|
||||
/// Props for the hosts page: the services plus the changing discovery/status data that must
|
||||
/// drive its re-render (compared by value, so a new host list or error refreshes the page).
|
||||
///
|
||||
/// `forget`, `rename` and `hover` used to live here too, hoisted into root on the belief that
|
||||
/// MenuFlyout item clicks and pointer enter/exit — wired straight in the reactor backend
|
||||
/// (`add_Click` etc.) — bypassed the render flush, so a *sync* child `SetState` from such a
|
||||
/// handler would never repaint. The characterization suite disproved that at the current pin
|
||||
/// (`tests/reactor_semantics.rs`: `sync_state_from_backend_fired_event_rerenders`,
|
||||
/// `sync_state_child_under_element_equal_border_rerenders` — a sync `use_state` write
|
||||
/// re-renders its owner, from backend-fired handlers and under element-equal wrappers alike),
|
||||
/// so they moved back down into `hosts_page`'s own `use_state` on 2026-07-29.
|
||||
///
|
||||
/// `show_add` is the one that STAYS root, for a different reason: the add-modal entrance
|
||||
/// tween in `app/mod.rs` is keyed on it, and the tween worker thread writes root async state
|
||||
/// (`add_anim`) — root can only start the tween if it owns the trigger.
|
||||
/// `forget` and `rename` are the per-host action state, and they live in ROOT (not this page's
|
||||
/// own `use_state`) on purpose: the "…" overflow is a WinUI `MenuFlyout`, whose item clicks are
|
||||
/// wired directly in the reactor backend (`add_Click`) and so bypass the normal event-dispatch
|
||||
/// flush — a *sync* child `SetState` from that handler marks state dirty but never pumps the
|
||||
/// reconciler, so nothing re-renders. Root `AsyncSetState` re-renders the whole tree; because
|
||||
/// these values are props, the changed value propagates back into this page (a child's own async
|
||||
/// state would be memoised away when its props are unchanged). `(fp_hex, _)` in each identifies
|
||||
/// the target saved host; `rename`'s second field is the in-progress draft name.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct HostsProps {
|
||||
pub(crate) svc: Svc,
|
||||
@@ -64,12 +60,21 @@ pub(crate) struct HostsProps {
|
||||
/// Connected-controller count (root state, mirrored from the gamepad service) — a
|
||||
/// pad plus a paired host surfaces the "Open console UI" hint card.
|
||||
pub(crate) pads: usize,
|
||||
/// Whether the "Add host" modal is open. Root state (see the struct docs: the entrance
|
||||
/// tween in `app/mod.rs` keys on it).
|
||||
pub(crate) forget: Option<(String, String)>,
|
||||
pub(crate) rename: Option<(String, String)>,
|
||||
/// Whether the "Add host" modal is open. Root state (like `forget`/`rename`), not the page's
|
||||
/// own `use_state`: a child component's sync `SetState` marks its slot dirty but does not
|
||||
/// re-render when its props are otherwise unchanged, so the toggle wouldn't take.
|
||||
pub(crate) show_add: bool,
|
||||
/// The modal's entrance-tween progress (0 → 1, root-driven): opacity + slide-up offset.
|
||||
pub(crate) add_anim: f64,
|
||||
/// The hovered tile's stable id (saved: fp_hex, discovered: `addr:port`) — root state because
|
||||
/// the pointer enter/exit handlers bypass the reconciler flush, like the flyout clicks above.
|
||||
pub(crate) hover: Option<String>,
|
||||
pub(crate) set_forget: AsyncSetState<Option<(String, String)>>,
|
||||
pub(crate) set_rename: AsyncSetState<Option<(String, String)>>,
|
||||
pub(crate) set_show_add: AsyncSetState<bool>,
|
||||
pub(crate) set_hover: AsyncSetState<Option<String>>,
|
||||
}
|
||||
|
||||
impl PartialEq for HostsProps {
|
||||
@@ -80,8 +85,11 @@ impl PartialEq for HostsProps {
|
||||
&& self.probed == other.probed
|
||||
&& self.status == other.status
|
||||
&& self.pads == other.pads
|
||||
&& self.forget == other.forget
|
||||
&& self.rename == other.rename
|
||||
&& self.show_add == other.show_add
|
||||
&& self.add_anim == other.add_anim
|
||||
&& self.hover == other.hover
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,12 +161,10 @@ fn host_tile(
|
||||
.into()
|
||||
}
|
||||
|
||||
/// The hover-tracking pair `host_tile` needs: the currently hovered tile id + the page's own
|
||||
/// sync setter (pointer enter/exit handlers re-render this component reliably — measured by
|
||||
/// `sync_state_from_backend_fired_event_rerenders`).
|
||||
/// The hover-tracking pair `host_tile` needs: the currently hovered tile id + its root setter.
|
||||
pub(crate) struct Hover {
|
||||
pub(crate) current: Option<String>,
|
||||
pub(crate) set: SetState<Option<String>>,
|
||||
pub(crate) set: AsyncSetState<Option<String>>,
|
||||
}
|
||||
|
||||
/// The status row at the bottom of a tile: the host's OS mark (when advertised), presence
|
||||
@@ -253,7 +259,7 @@ fn edit_editor(
|
||||
port_draft: HookRef<String>,
|
||||
mac_draft: HookRef<String>,
|
||||
clip_draft: HookRef<bool>,
|
||||
set_edit: SetState<Option<(String, String)>>,
|
||||
set_edit: AsyncSetState<Option<(String, String)>>,
|
||||
) -> Element {
|
||||
let commit = {
|
||||
let (fp, se) = (fp.to_string(), set_edit.clone());
|
||||
@@ -472,19 +478,15 @@ pub(crate) fn hosts_page(props: &HostsProps, cx: &mut RenderCx) -> Element {
|
||||
// would connect to the empty mount-time value. Mirror every keystroke into this stable ref (the
|
||||
// pair-screen PIN pattern). `manual` still drives the text box's displayed value.
|
||||
let manual_live = cx.use_ref(String::new());
|
||||
// "Add host" modal open state lives in ROOT (see `HostsProps`: the entrance tween keys on it).
|
||||
// "Add host" modal open state lives in ROOT (see `HostsProps`).
|
||||
let show_add = props.show_add;
|
||||
let set_show_add = &props.set_show_add;
|
||||
// Forget confirmation and in-progress edit target, `(fp_hex, name)` each. Page-local sync
|
||||
// state (moved back down from root 2026-07-29): the flyout's item clicks fire through the
|
||||
// backend attach path, and a sync `use_state` write from such a handler re-renders this
|
||||
// component reliably (`sync_state_from_backend_fired_event_rerenders`).
|
||||
let (forget, set_forget) = cx.use_state(Option::<(String, String)>::None);
|
||||
let (rename, set_rename) = cx.use_state(Option::<(String, String)>::None);
|
||||
// The hovered tile's stable id (saved: fp_hex, discovered: `addr:port`), driving the
|
||||
// WinUI-style card hover fill. Same measured rule as above for pointer enter/exit.
|
||||
let (hover_id, set_hover) = cx.use_state(Option::<String>::None);
|
||||
let (set_forget, set_rename) = (&set_forget, &set_rename);
|
||||
// Forget confirmation and in-progress rename live in ROOT state (see `HostsProps`) — the
|
||||
// overflow menu's flyout clicks can't re-render off a sync setter. Both are `(fp_hex, _)`.
|
||||
let forget = props.forget.clone();
|
||||
let rename = props.rename.clone();
|
||||
let set_forget = &props.set_forget;
|
||||
let set_rename = &props.set_rename;
|
||||
// The live edit drafts, read at Save time (see `edit_editor`). Root `rename` carries only
|
||||
// the target's fingerprint + initial name, so typing never round-trips through a
|
||||
// re-render. Every draft is re-seeded from the STORED host whenever the edit target
|
||||
@@ -523,8 +525,8 @@ pub(crate) fn hosts_page(props: &HostsProps, cx: &mut RenderCx) -> Element {
|
||||
}
|
||||
}
|
||||
let hover = Hover {
|
||||
current: hover_id,
|
||||
set: set_hover,
|
||||
current: props.hover.clone(),
|
||||
set: props.set_hover.clone(),
|
||||
};
|
||||
let known = KnownHosts::load();
|
||||
// The experimental library gate ("Show game library" in Settings) — GTK/Apple parity.
|
||||
|
||||
@@ -30,10 +30,6 @@
|
||||
//! (`async_state_child_under_element_equal_border_rerenders` asserts the drop). So
|
||||
//! everything THREAD-driven (discovery, HUD stats, speed-test results, spawn events)
|
||||
//! is held as *root* state and passed down as props.
|
||||
//! * Corollary: state a root tween is KEYED on (`screen`, the settings section, `show_add`)
|
||||
//! also stays in root even though it is user-event-driven — the tween workers are root
|
||||
//! `use_effect`s writing root async state, and root can only observe the trigger (and
|
||||
//! start the tween) if it owns it.
|
||||
|
||||
mod connect;
|
||||
mod help;
|
||||
@@ -104,8 +100,8 @@ pub(crate) struct Target {
|
||||
}
|
||||
|
||||
/// Stable app services handed to the page components as props. Each routed screen that uses
|
||||
/// hooks (`hosts_page`/`settings_page`/`pair_page`/`speed_page`/`library_page`) is mounted as
|
||||
/// its own `component(...)`, so its hooks live in an isolated slot list — calling them on the shared
|
||||
/// hooks (`hosts_page`/`pair_page`/`speed_page`/`library_page`) is mounted as its own
|
||||
/// `component(...)`, so its hooks live in an isolated slot list — calling them on the shared
|
||||
/// parent `cx` would change the hook order whenever the screen changes (reactor's
|
||||
/// Rules-of-Hooks guard aborts).
|
||||
///
|
||||
@@ -263,23 +259,40 @@ fn root(cx: &mut RenderCx, ctx: &Arc<AppCtx>) -> Element {
|
||||
let (status, set_status) = cx.use_async_state(String::new());
|
||||
let (hud, set_hud) = cx.use_async_state(stream::HudSample::default());
|
||||
let (speed, set_speed) = cx.use_async_state(SpeedState::Running);
|
||||
// "Add host" modal open state. Root, not hosts-page-local — NOT for a re-render reason
|
||||
// (a page-local sync setter would repaint fine per the measured rules) but because the
|
||||
// modal's entrance tween below is keyed on it, and the tween worker thread writes root
|
||||
// async state (`add_anim`): root can only start the tween if it owns the trigger. The
|
||||
// page's other action state (forget/rename/hover) moved into `hosts_page` 2026-07-29.
|
||||
// Per-host action state for the hosts page. Root, not page-local: the "…" overflow is a WinUI
|
||||
// MenuFlyout whose item clicks are wired straight in the reactor backend, bypassing the normal
|
||||
// event-dispatch flush — a sync page-local setter marks state dirty but never re-renders. See
|
||||
// `hosts::HostsProps`.
|
||||
let (forget, set_forget) = cx.use_async_state(Option::<(String, String)>::None);
|
||||
let (rename, set_rename) = cx.use_async_state(Option::<(String, String)>::None);
|
||||
let (show_add, set_show_add) = cx.use_async_state(false);
|
||||
// Hovered host tile (its stable id), driving the WinUI-style card hover fill. Root state for
|
||||
// the same reason as `forget`/`rename`: pointer enter/exit handlers are wired straight in the
|
||||
// reactor backend, so only a root `AsyncSetState` reliably re-renders the page.
|
||||
let (hover, set_hover) = cx.use_async_state(Option::<String>::None);
|
||||
// Which Settings section the NavigationView shows (persists across visits this run).
|
||||
// Opens on General — the first sidebar item, matching the Apple client's landing category.
|
||||
// Root for the same tween-coupling reason as `show_add`: the section-switch entrance tween
|
||||
// below keys on it. The page's other UI state (scope/delete/edit/revision) is its own
|
||||
// sync `use_state` — see `settings::SettingsProps`.
|
||||
let (settings_nav, set_settings_nav) = cx.use_async_state("general".to_string());
|
||||
// Which LAYER the settings screen edits: "" = the global defaults, else a profile id
|
||||
// (design/client-settings-profiles.md §5.1). Root state for the same reason as the section
|
||||
// above — the ComboBox's change handler is wired in the reactor backend.
|
||||
let (settings_scope, set_settings_scope) = cx.use_async_state(String::new());
|
||||
// The profile a Delete… click is asking about; `Some` renders the confirmation. Root state
|
||||
// because this page stays hook-free (its handlers are wired in the reactor backend).
|
||||
let (settings_delete, set_settings_delete) = cx.use_async_state(Option::<String>::None);
|
||||
// Whether the Edit-profile modal is up. Root state for the reactor-backend-handler reason
|
||||
// above; guarded in the page so it only renders while a profile is actually in scope.
|
||||
let (settings_edit, set_settings_edit) = cx.use_async_state(false);
|
||||
// Window size, read at ROOT: the settings screen places its scope switcher by the same
|
||||
// width threshold WinUI's NavigationView uses to collapse its pane (reactor exposes no
|
||||
// pane-opened/closed event). Registering the size here re-renders the tree on resize,
|
||||
// which the per-screen `use_inner_size` readers (hosts, library) already caused anyway.
|
||||
let window = cx.use_inner_size();
|
||||
// Bumped when a settings edit changes what the page should SHOW without changing any state
|
||||
// it already reads — ANY edit through `settings::commit` (creating an override must surface
|
||||
// its marker as immediately as resetting one clears it), a reset, a profile colour change.
|
||||
// Root state comparison makes same-value calls free, so a counter is what forces the pass.
|
||||
let (settings_rev, set_settings_rev) = cx.use_async_state(0u64);
|
||||
// `punktfunk://` links: the receiver thread queues them (from this launch's argv, or from a
|
||||
// later instance over WM_COPYDATA) and this poll pulls them onto the UI thread. Thread-fed
|
||||
// state must be root state, like the pad count below.
|
||||
@@ -597,26 +610,37 @@ fn root(cx: &mut RenderCx, ctx: &Arc<AppCtx>) -> Element {
|
||||
probed,
|
||||
status,
|
||||
pads,
|
||||
forget,
|
||||
rename,
|
||||
show_add,
|
||||
add_anim,
|
||||
hover,
|
||||
set_forget,
|
||||
set_rename,
|
||||
set_show_add,
|
||||
set_hover,
|
||||
},
|
||||
),
|
||||
// connecting_page / request_access_page / waking_page / licenses_page / help_page use
|
||||
// no hooks (they never touch `cx`), so calling them inline is sound.
|
||||
// connecting_page / request_access_page / waking_page / settings_page / licenses_page /
|
||||
// help_page use no hooks (they never touch `cx`), so calling them inline is sound.
|
||||
Screen::Connecting => connect::connecting_page(ctx, &status),
|
||||
Screen::RequestAccess => connect::request_access_page(ctx, &set_screen),
|
||||
Screen::Waking => connect::waking_page(ctx, &set_screen),
|
||||
Screen::Settings => component(
|
||||
settings::settings_page,
|
||||
settings::SettingsProps {
|
||||
ctx: ctx.clone(),
|
||||
set_screen: set_screen.clone(),
|
||||
section: settings_nav.clone(),
|
||||
set_section: set_settings_nav.clone(),
|
||||
progress: nav_progress,
|
||||
window_width: window.width,
|
||||
},
|
||||
Screen::Settings => settings::settings_page(
|
||||
ctx,
|
||||
&set_screen,
|
||||
&settings_nav,
|
||||
&set_settings_nav,
|
||||
&settings_scope,
|
||||
&set_settings_scope,
|
||||
&settings_delete,
|
||||
&set_settings_delete,
|
||||
settings_edit,
|
||||
&set_settings_edit,
|
||||
settings_rev,
|
||||
&set_settings_rev,
|
||||
nav_progress,
|
||||
window.width,
|
||||
),
|
||||
Screen::Licenses => licenses::licenses_page(&set_screen),
|
||||
Screen::Help => help::help_page(&set_screen),
|
||||
|
||||
@@ -147,7 +147,7 @@ pub(crate) fn hex_color(hex: &str) -> Option<Color> {
|
||||
}
|
||||
|
||||
/// The colour row: one tappable swatch per palette entry, the current one ringed.
|
||||
fn colour_swatches(profile: &StreamProfile, rev: u64, set_rev: &SetState<u64>) -> Element {
|
||||
fn colour_swatches(profile: &StreamProfile, rev: u64, set_rev: &AsyncSetState<u64>) -> Element {
|
||||
let current = profile.accent.clone().unwrap_or_default();
|
||||
let mut row: Vec<Element> = vec![text_block("Colour")
|
||||
.font_size(12.0)
|
||||
@@ -202,11 +202,11 @@ fn colour_swatches(profile: &StreamProfile, rev: u64, set_rev: &SetState<u64>) -
|
||||
/// under the user mid-keystroke.
|
||||
fn edit_profile_modal(
|
||||
profile: &StreamProfile,
|
||||
set_scope: &SetState<String>,
|
||||
set_delete: &SetState<Option<String>>,
|
||||
set_edit: &SetState<bool>,
|
||||
set_scope: &AsyncSetState<String>,
|
||||
set_delete: &AsyncSetState<Option<String>>,
|
||||
set_edit: &AsyncSetState<bool>,
|
||||
rev: u64,
|
||||
set_rev: &SetState<u64>,
|
||||
set_rev: &AsyncSetState<u64>,
|
||||
) -> Element {
|
||||
let id = profile.id.clone();
|
||||
let name_box = {
|
||||
@@ -320,7 +320,7 @@ fn edit_profile_modal(
|
||||
fn commit(
|
||||
ctx: &Arc<AppCtx>,
|
||||
scope: &str,
|
||||
rev: (u64, &SetState<u64>),
|
||||
rev: (u64, &AsyncSetState<u64>),
|
||||
edit: impl FnOnce(&mut Settings),
|
||||
) {
|
||||
if scope.is_empty() {
|
||||
@@ -405,7 +405,7 @@ fn active_profile(scope: &str) -> Option<StreamProfile> {
|
||||
fn setting_combo(
|
||||
ctx: &Arc<AppCtx>,
|
||||
scope: &str,
|
||||
rev: (u64, &SetState<u64>),
|
||||
rev: (u64, &AsyncSetState<u64>),
|
||||
header: &str,
|
||||
names: Vec<String>,
|
||||
current: usize,
|
||||
@@ -435,7 +435,7 @@ fn presets<V>(table: &[(V, &str)], is_current: impl Fn(&V) -> bool) -> (Vec<Stri
|
||||
fn setting_toggle(
|
||||
ctx: &Arc<AppCtx>,
|
||||
scope: &str,
|
||||
rev: (u64, &SetState<u64>),
|
||||
rev: (u64, &AsyncSetState<u64>),
|
||||
header: &str,
|
||||
on: bool,
|
||||
apply: impl Fn(&mut Settings, bool) + 'static,
|
||||
@@ -466,7 +466,7 @@ fn setting_toggle(
|
||||
/// its row, and "not overridden" needs an explicit Reset. (Linux marks a literal no-op
|
||||
/// touch too — unobservable here, the one intentional divergence.)
|
||||
fn described_overridable(
|
||||
rev: (u64, &SetState<u64>),
|
||||
rev: (u64, &AsyncSetState<u64>),
|
||||
scope: &str,
|
||||
field: &'static str,
|
||||
overridden: bool,
|
||||
@@ -559,77 +559,32 @@ fn group(header: Option<&str>, fields: Vec<Element>, footer: Option<&str>) -> Ve
|
||||
out
|
||||
}
|
||||
|
||||
/// Props for the settings page (mounted as its own `component(...)` so its hooks live in an
|
||||
/// isolated slot list — see `Svc`'s docs in `app/mod.rs`).
|
||||
///
|
||||
/// What is a prop here (vs the page's own `use_state`) is deliberate:
|
||||
/// * `section` + `set_section` — the selected pane tag stays ROOT state because the
|
||||
/// section-switch entrance tween in `app/mod.rs` is keyed on it: the tween worker thread
|
||||
/// writes root async state (`nav_anim`), and root can only start it if it owns the trigger.
|
||||
/// The NavigationView's `on_selection_changed` therefore keeps calling the root setter.
|
||||
/// * `progress` — that tween's value (0 → 1), root-driven for the same reason.
|
||||
/// * `window_width` — read at root (`use_inner_size`) so resize re-renders the tree; the page
|
||||
/// only compares it against WinUI's pane-collapse threshold.
|
||||
///
|
||||
/// Everything else the page shows (scope, delete confirmation, edit modal, revision) is its
|
||||
/// own sync `use_state` — see `settings_page`.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SettingsProps {
|
||||
pub(crate) ctx: Arc<AppCtx>,
|
||||
pub(crate) set_screen: AsyncSetState<Screen>,
|
||||
pub(crate) section: String,
|
||||
pub(crate) set_section: AsyncSetState<String>,
|
||||
pub(crate) progress: f64,
|
||||
pub(crate) window_width: f64,
|
||||
}
|
||||
|
||||
impl PartialEq for SettingsProps {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// Setters are identity-stable; only the data fields drive re-render.
|
||||
Arc::ptr_eq(&self.ctx, &other.ctx)
|
||||
&& self.section == other.section
|
||||
&& self.progress == other.progress
|
||||
&& self.window_width == other.window_width
|
||||
}
|
||||
}
|
||||
|
||||
/// The settings screen: a stock WinUI `NavigationView` (the Windows-Settings sidebar pattern) —
|
||||
/// one pane item per section, the section's card as the content, the built-in back arrow
|
||||
/// returning to the host list.
|
||||
///
|
||||
/// The page-only UI state (which layer is edited, the delete confirmation, the Edit-profile
|
||||
/// modal, the repaint revision) lives HERE as sync `use_state`, de-hoisted from root
|
||||
/// 2026-07-29: every writer is a UI-thread handler (ComboBox `on_selection_changed`, button
|
||||
/// clicks, swatch taps), and the characterization suite measured that a sync `use_state`
|
||||
/// write from a backend-wired handler re-renders its owning component
|
||||
/// (`tests/reactor_semantics.rs::sync_state_from_backend_fired_event_rerenders`). The section
|
||||
/// tag is the exception — root-owned, tween-coupled; see `SettingsProps`.
|
||||
pub(crate) fn settings_page(props: &SettingsProps, cx: &mut RenderCx) -> Element {
|
||||
// Hooks first, unconditionally, in a stable order (reactor's Rules-of-Hooks guard aborts
|
||||
// on a changed order).
|
||||
// Which LAYER the screen edits: "" = the global defaults, else a profile id
|
||||
// (design/client-settings-profiles.md §5.1).
|
||||
let (scope_id, set_scope) = cx.use_state(String::new());
|
||||
// The profile a Delete… click is asking about; `Some` renders the confirmation.
|
||||
let (delete_pending, set_delete) = cx.use_state(Option::<String>::None);
|
||||
// Whether the Edit-profile modal is up; guarded below so it only renders while a profile
|
||||
// is actually in scope.
|
||||
let (edit_open, set_edit) = cx.use_state(false);
|
||||
// Bumped when an edit changes what the page should SHOW without changing any state it
|
||||
// already reads — ANY edit through `commit` (creating an override must surface its marker
|
||||
// as immediately as resetting one clears it), a reset, a profile colour change. The
|
||||
// same-value compare in `call` makes no-op bumps free; the counter is what forces the pass.
|
||||
let (rev, set_rev) = cx.use_state(0u64);
|
||||
let (set_scope, set_delete, set_edit, set_rev) = (&set_scope, &set_delete, &set_edit, &set_rev);
|
||||
let ctx = &props.ctx;
|
||||
let set_screen = &props.set_screen;
|
||||
let section = props.section.as_str();
|
||||
let set_section = &props.set_section;
|
||||
let progress = props.progress;
|
||||
let window_width = props.window_width;
|
||||
/// returning to the host list. `section`/`set_section` are the selected pane tag, held in ROOT
|
||||
/// state (this page stays hook-free): `on_selection_changed` is wired in the reactor backend, so
|
||||
/// only a root `AsyncSetState` reliably re-renders the new section in. `progress` is the
|
||||
/// section-switch entrance tween (0 → 1), mapped onto the content column's opacity + offset.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn settings_page(
|
||||
ctx: &Arc<AppCtx>,
|
||||
set_screen: &AsyncSetState<Screen>,
|
||||
section: &str,
|
||||
set_section: &AsyncSetState<String>,
|
||||
scope_id: &str,
|
||||
set_scope: &AsyncSetState<String>,
|
||||
delete_pending: &Option<String>,
|
||||
set_delete: &AsyncSetState<Option<String>>,
|
||||
edit_open: bool,
|
||||
set_edit: &AsyncSetState<bool>,
|
||||
rev: u64,
|
||||
set_rev: &AsyncSetState<u64>,
|
||||
progress: f64,
|
||||
window_width: f64,
|
||||
) -> Element {
|
||||
// The layer being edited. A scope pointing at a deleted profile degrades to the defaults,
|
||||
// the same rule a dangling host binding follows.
|
||||
let active = active_profile(&scope_id);
|
||||
let active = active_profile(scope_id);
|
||||
let scope: &str = match &active {
|
||||
Some(p) => &p.id,
|
||||
None => "",
|
||||
|
||||
Reference in New Issue
Block a user