feat(plugins): optionally move plugin to focused tab (#2725)
* feat(plugins): move_to_focused_tab attribute for LaunchOrFocusPlugin * style(fmt): rustfmt
This commit is contained in:
parent
877c467f9f
commit
b44ba85895
16 changed files with 129 additions and 12 deletions
|
|
@ -240,7 +240,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<Key>)> {
|
||||||
action_key(&km, &[A::SearchToggleOption(SOpt::WholeWord)])),
|
action_key(&km, &[A::SearchToggleOption(SOpt::WholeWord)])),
|
||||||
]} else if mi.mode == IM::Session { vec![
|
]} else if mi.mode == IM::Session { vec![
|
||||||
(s("Detach"), s("Detach"), action_key(&km, &[Action::Detach])),
|
(s("Detach"), s("Detach"), action_key(&km, &[Action::Detach])),
|
||||||
(s("Session Manager"), s("Manager"), action_key(&km, &[A::LaunchOrFocusPlugin(Default::default(), true), TO_NORMAL])), // not entirely accurate
|
(s("Session Manager"), s("Manager"), action_key(&km, &[A::LaunchOrFocusPlugin(Default::default(), true, true), TO_NORMAL])), // not entirely accurate
|
||||||
(s("Select pane"), s("Select"), to_normal_key),
|
(s("Select pane"), s("Select"), to_normal_key),
|
||||||
]} else if mi.mode == IM::Tmux { vec![
|
]} else if mi.mode == IM::Tmux { vec![
|
||||||
(s("Move focus"), s("Move"), action_key_group(&km, &[
|
(s("Move focus"), s("Move"), action_key_group(&km, &[
|
||||||
|
|
|
||||||
|
|
@ -621,11 +621,12 @@ pub(crate) fn route_action(
|
||||||
.send_to_screen(ScreenInstruction::StartOrReloadPluginPane(run_plugin, None))
|
.send_to_screen(ScreenInstruction::StartOrReloadPluginPane(run_plugin, None))
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
},
|
},
|
||||||
Action::LaunchOrFocusPlugin(run_plugin, should_float) => {
|
Action::LaunchOrFocusPlugin(run_plugin, should_float, move_to_focused_tab) => {
|
||||||
senders
|
senders
|
||||||
.send_to_screen(ScreenInstruction::LaunchOrFocusPlugin(
|
.send_to_screen(ScreenInstruction::LaunchOrFocusPlugin(
|
||||||
run_plugin,
|
run_plugin,
|
||||||
should_float,
|
should_float,
|
||||||
|
move_to_focused_tab,
|
||||||
client_id,
|
client_id,
|
||||||
))
|
))
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
|
|
|
||||||
|
|
@ -281,9 +281,9 @@ pub enum ScreenInstruction {
|
||||||
StartPluginLoadingIndication(u32, LoadingIndication), // u32 - plugin_id
|
StartPluginLoadingIndication(u32, LoadingIndication), // u32 - plugin_id
|
||||||
ProgressPluginLoadingOffset(u32), // u32 - plugin id
|
ProgressPluginLoadingOffset(u32), // u32 - plugin id
|
||||||
RequestStateUpdateForPlugins,
|
RequestStateUpdateForPlugins,
|
||||||
LaunchOrFocusPlugin(RunPlugin, bool, ClientId), // bool is should_float
|
LaunchOrFocusPlugin(RunPlugin, bool, bool, ClientId), // bools are: should_float, move_to_focused_tab
|
||||||
SuppressPane(PaneId, ClientId), // bool is should_float
|
SuppressPane(PaneId, ClientId), // bool is should_float
|
||||||
FocusPaneWithId(PaneId, bool, ClientId), // bool is should_float
|
FocusPaneWithId(PaneId, bool, ClientId), // bool is should_float
|
||||||
RenamePane(PaneId, Vec<u8>),
|
RenamePane(PaneId, Vec<u8>),
|
||||||
RenameTab(usize, Vec<u8>),
|
RenameTab(usize, Vec<u8>),
|
||||||
RequestPluginPermissions(
|
RequestPluginPermissions(
|
||||||
|
|
@ -1618,18 +1618,47 @@ impl Screen {
|
||||||
&mut self,
|
&mut self,
|
||||||
run_plugin: &RunPlugin,
|
run_plugin: &RunPlugin,
|
||||||
should_float: bool,
|
should_float: bool,
|
||||||
|
move_to_focused_tab: bool,
|
||||||
client_id: ClientId,
|
client_id: ClientId,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
// true => found and focused, false => not
|
// true => found and focused, false => not
|
||||||
let err_context = || format!("failed to focus_plugin_pane");
|
let err_context = || format!("failed to focus_plugin_pane");
|
||||||
let mut tab_index_and_plugin_pane_id = None;
|
let mut tab_index_and_plugin_pane_id = None;
|
||||||
|
let mut plugin_pane_to_move_to_active_tab = None;
|
||||||
|
let focused_tab_index = *self.active_tab_indices.get(&client_id).unwrap_or(&0);
|
||||||
let all_tabs = self.get_tabs_mut();
|
let all_tabs = self.get_tabs_mut();
|
||||||
for (tab_index, tab) in all_tabs.iter_mut() {
|
for (tab_index, tab) in all_tabs.iter_mut() {
|
||||||
if let Some(plugin_pane_id) = tab.find_plugin(&run_plugin) {
|
if let Some(plugin_pane_id) = tab.find_plugin(&run_plugin) {
|
||||||
tab_index_and_plugin_pane_id = Some((*tab_index, plugin_pane_id));
|
tab_index_and_plugin_pane_id = Some((*tab_index, plugin_pane_id));
|
||||||
|
if move_to_focused_tab && focused_tab_index != *tab_index {
|
||||||
|
plugin_pane_to_move_to_active_tab =
|
||||||
|
tab.extract_pane(plugin_pane_id, Some(client_id));
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(plugin_pane_to_move_to_active_tab) = plugin_pane_to_move_to_active_tab.take() {
|
||||||
|
let pane_id = plugin_pane_to_move_to_active_tab.pid();
|
||||||
|
let new_active_tab = self.get_active_tab_mut(client_id)?;
|
||||||
|
|
||||||
|
if should_float {
|
||||||
|
new_active_tab.show_floating_panes();
|
||||||
|
new_active_tab.add_floating_pane(
|
||||||
|
plugin_pane_to_move_to_active_tab,
|
||||||
|
pane_id,
|
||||||
|
Some(client_id),
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
new_active_tab.hide_floating_panes();
|
||||||
|
new_active_tab.add_tiled_pane(
|
||||||
|
plugin_pane_to_move_to_active_tab,
|
||||||
|
pane_id,
|
||||||
|
Some(client_id),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
match tab_index_and_plugin_pane_id {
|
match tab_index_and_plugin_pane_id {
|
||||||
Some((tab_index, plugin_pane_id)) => {
|
Some((tab_index, plugin_pane_id)) => {
|
||||||
self.go_to_tab(tab_index + 1, client_id)?;
|
self.go_to_tab(tab_index + 1, client_id)?;
|
||||||
|
|
@ -2969,7 +2998,12 @@ pub(crate) fn screen_thread_main(
|
||||||
screen.log_and_report_session_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::LaunchOrFocusPlugin(run_plugin, should_float, client_id) => {
|
ScreenInstruction::LaunchOrFocusPlugin(
|
||||||
|
run_plugin,
|
||||||
|
should_float,
|
||||||
|
move_to_focused_tab,
|
||||||
|
client_id,
|
||||||
|
) => {
|
||||||
let client_id = if screen.active_tab_indices.contains_key(&client_id) {
|
let client_id = if screen.active_tab_indices.contains_key(&client_id) {
|
||||||
Some(client_id)
|
Some(client_id)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2983,7 +3017,12 @@ pub(crate) fn screen_thread_main(
|
||||||
});
|
});
|
||||||
match client_id_and_focused_tab {
|
match client_id_and_focused_tab {
|
||||||
Some((tab_index, client_id)) => {
|
Some((tab_index, client_id)) => {
|
||||||
if screen.focus_plugin_pane(&run_plugin, should_float, client_id)? {
|
if screen.focus_plugin_pane(
|
||||||
|
&run_plugin,
|
||||||
|
should_float,
|
||||||
|
move_to_focused_tab,
|
||||||
|
client_id,
|
||||||
|
)? {
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.log_and_report_session_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -2245,6 +2245,49 @@ impl Tab {
|
||||||
closed_pane
|
closed_pane
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn extract_pane(
|
||||||
|
&mut self,
|
||||||
|
id: PaneId,
|
||||||
|
client_id: Option<ClientId>,
|
||||||
|
) -> Option<Box<dyn Pane>> {
|
||||||
|
if self.floating_panes.panes_contain(&id) {
|
||||||
|
let closed_pane = self.floating_panes.remove_pane(id);
|
||||||
|
self.floating_panes.move_clients_out_of_pane(id);
|
||||||
|
if !self.floating_panes.has_panes() {
|
||||||
|
self.hide_floating_panes();
|
||||||
|
}
|
||||||
|
self.set_force_render();
|
||||||
|
self.floating_panes.set_force_render();
|
||||||
|
if self.auto_layout
|
||||||
|
&& !self.swap_layouts.is_floating_damaged()
|
||||||
|
&& self.floating_panes.visible_panes_count() > 0
|
||||||
|
{
|
||||||
|
self.swap_layouts.set_is_floating_damaged();
|
||||||
|
// only relayout if the user is already "in" a layout, otherwise this might be
|
||||||
|
// confusing
|
||||||
|
let _ = self.next_swap_layout(client_id, false);
|
||||||
|
}
|
||||||
|
closed_pane
|
||||||
|
} else if self.tiled_panes.panes_contain(&id) {
|
||||||
|
if self.tiled_panes.fullscreen_is_active() {
|
||||||
|
self.tiled_panes.unset_fullscreen();
|
||||||
|
}
|
||||||
|
let closed_pane = self.tiled_panes.remove_pane(id);
|
||||||
|
self.set_force_render();
|
||||||
|
self.tiled_panes.set_force_render();
|
||||||
|
if self.auto_layout && !self.swap_layouts.is_tiled_damaged() {
|
||||||
|
self.swap_layouts.set_is_tiled_damaged();
|
||||||
|
// only relayout if the user is already "in" a layout, otherwise this might be
|
||||||
|
// confusing
|
||||||
|
let _ = self.next_swap_layout(client_id, false);
|
||||||
|
}
|
||||||
|
closed_pane
|
||||||
|
} else if self.suppressed_panes.contains_key(&id) {
|
||||||
|
self.suppressed_panes.remove(&id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn hold_pane(
|
pub fn hold_pane(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: PaneId,
|
id: PaneId,
|
||||||
|
|
|
||||||
|
|
@ -2564,6 +2564,7 @@ pub fn send_cli_launch_or_focus_plugin_action() {
|
||||||
);
|
);
|
||||||
let cli_action = CliAction::LaunchOrFocusPlugin {
|
let cli_action = CliAction::LaunchOrFocusPlugin {
|
||||||
floating: true,
|
floating: true,
|
||||||
|
move_to_focused_tab: true,
|
||||||
url: url::Url::parse("file:/path/to/fake/plugin").unwrap(),
|
url: url::Url::parse("file:/path/to/fake/plugin").unwrap(),
|
||||||
configuration: Default::default(),
|
configuration: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
@ -2621,6 +2622,7 @@ pub fn send_cli_launch_or_focus_plugin_action_when_plugin_is_already_loaded() {
|
||||||
);
|
);
|
||||||
let cli_action = CliAction::LaunchOrFocusPlugin {
|
let cli_action = CliAction::LaunchOrFocusPlugin {
|
||||||
floating: true,
|
floating: true,
|
||||||
|
move_to_focused_tab: true,
|
||||||
url: url::Url::parse("file:/path/to/fake/plugin").unwrap(),
|
url: url::Url::parse("file:/path/to/fake/plugin").unwrap(),
|
||||||
configuration: Default::default(),
|
configuration: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ keybinds {
|
||||||
bind "w" {
|
bind "w" {
|
||||||
LaunchOrFocusPlugin "zellij:session-manager" {
|
LaunchOrFocusPlugin "zellij:session-manager" {
|
||||||
floating true
|
floating true
|
||||||
|
move_to_focused_tab true
|
||||||
};
|
};
|
||||||
SwitchToMode "Normal"
|
SwitchToMode "Normal"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -384,6 +384,8 @@ pub enum CliAction {
|
||||||
LaunchOrFocusPlugin {
|
LaunchOrFocusPlugin {
|
||||||
#[clap(short, long, value_parser)]
|
#[clap(short, long, value_parser)]
|
||||||
floating: bool,
|
floating: bool,
|
||||||
|
#[clap(short, long, value_parser)]
|
||||||
|
move_to_focused_tab: bool,
|
||||||
url: Url,
|
url: Url,
|
||||||
#[clap(short, long, value_parser)]
|
#[clap(short, long, value_parser)]
|
||||||
configuration: Option<PluginUserConfiguration>,
|
configuration: Option<PluginUserConfiguration>,
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,7 @@ pub enum Action {
|
||||||
LeftClick(Position),
|
LeftClick(Position),
|
||||||
RightClick(Position),
|
RightClick(Position),
|
||||||
MiddleClick(Position),
|
MiddleClick(Position),
|
||||||
LaunchOrFocusPlugin(RunPlugin, bool), // bool => should float
|
LaunchOrFocusPlugin(RunPlugin, bool, bool), // bools => should float, move_to_focused_tab
|
||||||
LeftMouseRelease(Position),
|
LeftMouseRelease(Position),
|
||||||
RightMouseRelease(Position),
|
RightMouseRelease(Position),
|
||||||
MiddleMouseRelease(Position),
|
MiddleMouseRelease(Position),
|
||||||
|
|
@ -501,6 +501,7 @@ impl Action {
|
||||||
CliAction::LaunchOrFocusPlugin {
|
CliAction::LaunchOrFocusPlugin {
|
||||||
url,
|
url,
|
||||||
floating,
|
floating,
|
||||||
|
move_to_focused_tab,
|
||||||
configuration,
|
configuration,
|
||||||
} => {
|
} => {
|
||||||
let current_dir = get_current_dir();
|
let current_dir = get_current_dir();
|
||||||
|
|
@ -511,7 +512,11 @@ impl Action {
|
||||||
_allow_exec_host_cmd: false,
|
_allow_exec_host_cmd: false,
|
||||||
configuration: configuration.unwrap_or_default(),
|
configuration: configuration.unwrap_or_default(),
|
||||||
};
|
};
|
||||||
Ok(vec![Action::LaunchOrFocusPlugin(run_plugin, floating)])
|
Ok(vec![Action::LaunchOrFocusPlugin(
|
||||||
|
run_plugin,
|
||||||
|
floating,
|
||||||
|
move_to_focused_tab,
|
||||||
|
)])
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -906,6 +906,9 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
|
||||||
let should_float = command_metadata
|
let should_float = command_metadata
|
||||||
.and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating"))
|
.and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating"))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
let move_to_focused_tab = command_metadata
|
||||||
|
.and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "move_to_focused_tab"))
|
||||||
|
.unwrap_or(false);
|
||||||
let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
||||||
let location = RunPluginLocation::parse(&plugin_path, Some(current_dir))?;
|
let location = RunPluginLocation::parse(&plugin_path, Some(current_dir))?;
|
||||||
let configuration = KdlLayoutParser::parse_plugin_user_configuration(&kdl_action)?;
|
let configuration = KdlLayoutParser::parse_plugin_user_configuration(&kdl_action)?;
|
||||||
|
|
@ -914,7 +917,11 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
|
||||||
_allow_exec_host_cmd: false,
|
_allow_exec_host_cmd: false,
|
||||||
configuration,
|
configuration,
|
||||||
};
|
};
|
||||||
Ok(Action::LaunchOrFocusPlugin(run_plugin, should_float))
|
Ok(Action::LaunchOrFocusPlugin(
|
||||||
|
run_plugin,
|
||||||
|
should_float,
|
||||||
|
move_to_focused_tab,
|
||||||
|
))
|
||||||
},
|
},
|
||||||
"PreviousSwapLayout" => Ok(Action::PreviousSwapLayout),
|
"PreviousSwapLayout" => Ok(Action::PreviousSwapLayout),
|
||||||
"NextSwapLayout" => Ok(Action::NextSwapLayout),
|
"NextSwapLayout" => Ok(Action::NextSwapLayout),
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ message LaunchOrFocusPluginPayload {
|
||||||
string plugin_url = 1;
|
string plugin_url = 1;
|
||||||
bool should_float = 2;
|
bool should_float = 2;
|
||||||
optional PluginConfiguration plugin_configuration = 3;
|
optional PluginConfiguration plugin_configuration = 3;
|
||||||
|
bool move_to_focused_tab = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GoToTabNamePayload {
|
message GoToTabNamePayload {
|
||||||
|
|
|
||||||
|
|
@ -399,7 +399,12 @@ impl TryFrom<ProtobufAction> for Action {
|
||||||
configuration,
|
configuration,
|
||||||
};
|
};
|
||||||
let should_float = payload.should_float;
|
let should_float = payload.should_float;
|
||||||
Ok(Action::LaunchOrFocusPlugin(run_plugin, should_float))
|
let move_to_focused_tab = payload.move_to_focused_tab;
|
||||||
|
Ok(Action::LaunchOrFocusPlugin(
|
||||||
|
run_plugin,
|
||||||
|
should_float,
|
||||||
|
move_to_focused_tab,
|
||||||
|
))
|
||||||
},
|
},
|
||||||
_ => Err("Wrong payload for Action::LaunchOrFocusPlugin"),
|
_ => Err("Wrong payload for Action::LaunchOrFocusPlugin"),
|
||||||
}
|
}
|
||||||
|
|
@ -954,7 +959,7 @@ impl TryFrom<Action> for ProtobufAction {
|
||||||
optional_payload: Some(OptionalPayload::MiddleClickPayload(position)),
|
optional_payload: Some(OptionalPayload::MiddleClickPayload(position)),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
Action::LaunchOrFocusPlugin(run_plugin, should_float) => {
|
Action::LaunchOrFocusPlugin(run_plugin, should_float, move_to_focused_tab) => {
|
||||||
let url: Url = Url::from(&run_plugin.location);
|
let url: Url = Url::from(&run_plugin.location);
|
||||||
Ok(ProtobufAction {
|
Ok(ProtobufAction {
|
||||||
name: ProtobufActionName::LaunchOrFocusPlugin as i32,
|
name: ProtobufActionName::LaunchOrFocusPlugin as i32,
|
||||||
|
|
@ -962,6 +967,7 @@ impl TryFrom<Action> for ProtobufAction {
|
||||||
LaunchOrFocusPluginPayload {
|
LaunchOrFocusPluginPayload {
|
||||||
plugin_url: url.into(),
|
plugin_url: url.into(),
|
||||||
should_float,
|
should_float,
|
||||||
|
move_to_focused_tab,
|
||||||
plugin_configuration: Some(run_plugin.configuration.try_into()?),
|
plugin_configuration: Some(run_plugin.configuration.try_into()?),
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
|
|
|
||||||
|
|
@ -2503,10 +2503,12 @@ Config {
|
||||||
configuration: PluginUserConfiguration(
|
configuration: PluginUserConfiguration(
|
||||||
{
|
{
|
||||||
"floating": "true",
|
"floating": "true",
|
||||||
|
"move_to_focused_tab": "true",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
SwitchToMode(
|
SwitchToMode(
|
||||||
Normal,
|
Normal,
|
||||||
|
|
|
||||||
|
|
@ -2503,10 +2503,12 @@ Config {
|
||||||
configuration: PluginUserConfiguration(
|
configuration: PluginUserConfiguration(
|
||||||
{
|
{
|
||||||
"floating": "true",
|
"floating": "true",
|
||||||
|
"move_to_focused_tab": "true",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
SwitchToMode(
|
SwitchToMode(
|
||||||
Normal,
|
Normal,
|
||||||
|
|
|
||||||
|
|
@ -2503,10 +2503,12 @@ Config {
|
||||||
configuration: PluginUserConfiguration(
|
configuration: PluginUserConfiguration(
|
||||||
{
|
{
|
||||||
"floating": "true",
|
"floating": "true",
|
||||||
|
"move_to_focused_tab": "true",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
SwitchToMode(
|
SwitchToMode(
|
||||||
Normal,
|
Normal,
|
||||||
|
|
|
||||||
|
|
@ -2503,10 +2503,12 @@ Config {
|
||||||
configuration: PluginUserConfiguration(
|
configuration: PluginUserConfiguration(
|
||||||
{
|
{
|
||||||
"floating": "true",
|
"floating": "true",
|
||||||
|
"move_to_focused_tab": "true",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
SwitchToMode(
|
SwitchToMode(
|
||||||
Normal,
|
Normal,
|
||||||
|
|
|
||||||
|
|
@ -2503,10 +2503,12 @@ Config {
|
||||||
configuration: PluginUserConfiguration(
|
configuration: PluginUserConfiguration(
|
||||||
{
|
{
|
||||||
"floating": "true",
|
"floating": "true",
|
||||||
|
"move_to_focused_tab": "true",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
SwitchToMode(
|
SwitchToMode(
|
||||||
Normal,
|
Normal,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue