Register actions globally before main
This commit is contained in:
parent
80630cd4d9
commit
814e62050c
8 changed files with 33 additions and 169 deletions
|
@ -80,7 +80,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
|
||||||
init_settings(cx);
|
init_settings(cx);
|
||||||
|
|
||||||
let client = Arc::downgrade(client);
|
let client = Arc::downgrade(client);
|
||||||
cx.register_action_type::<SignIn>();
|
|
||||||
cx.on_action({
|
cx.on_action({
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
move |_: &SignIn, cx| {
|
move |_: &SignIn, cx| {
|
||||||
|
@ -93,7 +92,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.register_action_type::<SignOut>();
|
|
||||||
cx.on_action({
|
cx.on_action({
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
move |_: &SignOut, cx| {
|
move |_: &SignOut, cx| {
|
||||||
|
@ -106,7 +104,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.register_action_type::<Reconnect>();
|
|
||||||
cx.on_action({
|
cx.on_action({
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
move |_: &Reconnect, cx| {
|
move |_: &Reconnect, cx| {
|
||||||
|
|
|
@ -406,130 +406,6 @@ pub fn init_settings(cx: &mut AppContext) {
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
init_settings(cx);
|
init_settings(cx);
|
||||||
// cx.register_action_type(Editor::new_file);
|
|
||||||
// cx.register_action_type(Editor::new_file_in_direction);
|
|
||||||
// cx.register_action_type(Editor::cancel);
|
|
||||||
// cx.register_action_type(Editor::newline);
|
|
||||||
// cx.register_action_type(Editor::newline_above);
|
|
||||||
// cx.register_action_type(Editor::newline_below);
|
|
||||||
// cx.register_action_type(Editor::backspace);
|
|
||||||
// cx.register_action_type(Editor::delete);
|
|
||||||
// cx.register_action_type(Editor::tab);
|
|
||||||
// cx.register_action_type(Editor::tab_prev);
|
|
||||||
// cx.register_action_type(Editor::indent);
|
|
||||||
// cx.register_action_type(Editor::outdent);
|
|
||||||
// cx.register_action_type(Editor::delete_line);
|
|
||||||
// cx.register_action_type(Editor::join_lines);
|
|
||||||
// cx.register_action_type(Editor::sort_lines_case_sensitive);
|
|
||||||
// cx.register_action_type(Editor::sort_lines_case_insensitive);
|
|
||||||
// cx.register_action_type(Editor::reverse_lines);
|
|
||||||
// cx.register_action_type(Editor::shuffle_lines);
|
|
||||||
// cx.register_action_type(Editor::convert_to_upper_case);
|
|
||||||
// cx.register_action_type(Editor::convert_to_lower_case);
|
|
||||||
// cx.register_action_type(Editor::convert_to_title_case);
|
|
||||||
// cx.register_action_type(Editor::convert_to_snake_case);
|
|
||||||
// cx.register_action_type(Editor::convert_to_kebab_case);
|
|
||||||
// cx.register_action_type(Editor::convert_to_upper_camel_case);
|
|
||||||
// cx.register_action_type(Editor::convert_to_lower_camel_case);
|
|
||||||
// cx.register_action_type(Editor::delete_to_previous_word_start);
|
|
||||||
// cx.register_action_type(Editor::delete_to_previous_subword_start);
|
|
||||||
// cx.register_action_type(Editor::delete_to_next_word_end);
|
|
||||||
// cx.register_action_type(Editor::delete_to_next_subword_end);
|
|
||||||
// cx.register_action_type(Editor::delete_to_beginning_of_line);
|
|
||||||
// cx.register_action_type(Editor::delete_to_end_of_line);
|
|
||||||
// cx.register_action_type(Editor::cut_to_end_of_line);
|
|
||||||
// cx.register_action_type(Editor::duplicate_line);
|
|
||||||
// cx.register_action_type(Editor::move_line_up);
|
|
||||||
// cx.register_action_type(Editor::move_line_down);
|
|
||||||
// cx.register_action_type(Editor::transpose);
|
|
||||||
// cx.register_action_type(Editor::cut);
|
|
||||||
// cx.register_action_type(Editor::copy);
|
|
||||||
// cx.register_action_type(Editor::paste);
|
|
||||||
// cx.register_action_type(Editor::undo);
|
|
||||||
// cx.register_action_type(Editor::redo);
|
|
||||||
cx.register_action_type::<MoveUp>();
|
|
||||||
// cx.register_action_type(Editor::move_page_up);
|
|
||||||
cx.register_action_type::<MoveDown>();
|
|
||||||
// cx.register_action_type(Editor::move_page_down);
|
|
||||||
// cx.register_action_type(Editor::next_screen);
|
|
||||||
cx.register_action_type::<MoveLeft>();
|
|
||||||
cx.register_action_type::<MoveRight>();
|
|
||||||
// cx.register_action_type(Editor::move_to_previous_word_start);
|
|
||||||
// cx.register_action_type(Editor::move_to_previous_subword_start);
|
|
||||||
// cx.register_action_type(Editor::move_to_next_word_end);
|
|
||||||
// cx.register_action_type(Editor::move_to_next_subword_end);
|
|
||||||
// cx.register_action_type(Editor::move_to_beginning_of_line);
|
|
||||||
// cx.register_action_type(Editor::move_to_end_of_line);
|
|
||||||
// cx.register_action_type(Editor::move_to_start_of_paragraph);
|
|
||||||
// cx.register_action_type(Editor::move_to_end_of_paragraph);
|
|
||||||
// cx.register_action_type(Editor::move_to_beginning);
|
|
||||||
// cx.register_action_type(Editor::move_to_end);
|
|
||||||
// cx.register_action_type(Editor::select_up);
|
|
||||||
// cx.register_action_type(Editor::select_down);
|
|
||||||
// cx.register_action_type(Editor::select_left);
|
|
||||||
// cx.register_action_type(Editor::select_right);
|
|
||||||
// cx.register_action_type(Editor::select_to_previous_word_start);
|
|
||||||
// cx.register_action_type(Editor::select_to_previous_subword_start);
|
|
||||||
// cx.register_action_type(Editor::select_to_next_word_end);
|
|
||||||
// cx.register_action_type(Editor::select_to_next_subword_end);
|
|
||||||
// cx.register_action_type(Editor::select_to_beginning_of_line);
|
|
||||||
// cx.register_action_type(Editor::select_to_end_of_line);
|
|
||||||
// cx.register_action_type(Editor::select_to_start_of_paragraph);
|
|
||||||
// cx.register_action_type(Editor::select_to_end_of_paragraph);
|
|
||||||
// cx.register_action_type(Editor::select_to_beginning);
|
|
||||||
// cx.register_action_type(Editor::select_to_end);
|
|
||||||
// cx.register_action_type(Editor::select_all);
|
|
||||||
// cx.register_action_type(Editor::select_all_matches);
|
|
||||||
// cx.register_action_type(Editor::select_line);
|
|
||||||
// cx.register_action_type(Editor::split_selection_into_lines);
|
|
||||||
// cx.register_action_type(Editor::add_selection_above);
|
|
||||||
// cx.register_action_type(Editor::add_selection_below);
|
|
||||||
// cx.register_action_type(Editor::select_next);
|
|
||||||
// cx.register_action_type(Editor::select_previous);
|
|
||||||
// cx.register_action_type(Editor::toggle_comments);
|
|
||||||
// cx.register_action_type(Editor::select_larger_syntax_node);
|
|
||||||
// cx.register_action_type(Editor::select_smaller_syntax_node);
|
|
||||||
// cx.register_action_type(Editor::move_to_enclosing_bracket);
|
|
||||||
// cx.register_action_type(Editor::undo_selection);
|
|
||||||
// cx.register_action_type(Editor::redo_selection);
|
|
||||||
// cx.register_action_type(Editor::go_to_diagnostic);
|
|
||||||
// cx.register_action_type(Editor::go_to_prev_diagnostic);
|
|
||||||
// cx.register_action_type(Editor::go_to_hunk);
|
|
||||||
// cx.register_action_type(Editor::go_to_prev_hunk);
|
|
||||||
// cx.register_action_type(Editor::go_to_definition);
|
|
||||||
// cx.register_action_type(Editor::go_to_definition_split);
|
|
||||||
// cx.register_action_type(Editor::go_to_type_definition);
|
|
||||||
// cx.register_action_type(Editor::go_to_type_definition_split);
|
|
||||||
// cx.register_action_type(Editor::fold);
|
|
||||||
// cx.register_action_type(Editor::fold_at);
|
|
||||||
// cx.register_action_type(Editor::unfold_lines);
|
|
||||||
// cx.register_action_type(Editor::unfold_at);
|
|
||||||
// cx.register_action_type(Editor::gutter_hover);
|
|
||||||
// cx.register_action_type(Editor::fold_selected_ranges);
|
|
||||||
// cx.register_action_type(Editor::show_completions);
|
|
||||||
// cx.register_action_type(Editor::toggle_code_actions);
|
|
||||||
// cx.register_action_type(Editor::open_excerpts);
|
|
||||||
// cx.register_action_type(Editor::toggle_soft_wrap);
|
|
||||||
// cx.register_action_type(Editor::toggle_inlay_hints);
|
|
||||||
// cx.register_action_type(Editor::reveal_in_finder);
|
|
||||||
// cx.register_action_type(Editor::copy_path);
|
|
||||||
// cx.register_action_type(Editor::copy_relative_path);
|
|
||||||
// cx.register_action_type(Editor::copy_highlight_json);
|
|
||||||
// cx.add_async_action(Editor::format);
|
|
||||||
// cx.register_action_type(Editor::restart_language_server);
|
|
||||||
// cx.register_action_type(Editor::show_character_palette);
|
|
||||||
// cx.add_async_action(Editor::confirm_completion);
|
|
||||||
// cx.add_async_action(Editor::confirm_code_action);
|
|
||||||
// cx.add_async_action(Editor::rename);
|
|
||||||
// cx.add_async_action(Editor::confirm_rename);
|
|
||||||
// cx.add_async_action(Editor::find_all_references);
|
|
||||||
// cx.register_action_type(Editor::next_copilot_suggestion);
|
|
||||||
// cx.register_action_type(Editor::previous_copilot_suggestion);
|
|
||||||
// cx.register_action_type(Editor::copilot_suggest);
|
|
||||||
// cx.register_action_type(Editor::context_menu_first);
|
|
||||||
// cx.register_action_type(Editor::context_menu_prev);
|
|
||||||
// cx.register_action_type(Editor::context_menu_next);
|
|
||||||
// cx.register_action_type(Editor::context_menu_last);
|
|
||||||
|
|
||||||
hover_popover::init(cx);
|
hover_popover::init(cx);
|
||||||
scroll::actions::init(cx);
|
scroll::actions::init(cx);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::SharedString;
|
use crate::SharedString;
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use ctor::ctor;
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::any::{type_name, Any};
|
use std::any::{type_name, Any};
|
||||||
|
|
||||||
|
@ -21,23 +21,41 @@ pub trait Action: std::fmt::Debug + 'static {
|
||||||
|
|
||||||
type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
|
type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
|
||||||
|
|
||||||
#[ctor]
|
lazy_static! {
|
||||||
static ACTION_BUILDERS: Mutex<HashMap<SharedString, ActionBuilder>> = Mutex::default();
|
static ref ACTION_REGISTRY: RwLock<ActionRegistry> = RwLock::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ActionRegistry {
|
||||||
|
builders_by_name: HashMap<SharedString, ActionBuilder>,
|
||||||
|
all_names: Vec<SharedString>, // So we can return a static slice.
|
||||||
|
}
|
||||||
|
|
||||||
/// Register an action type to allow it to be referenced in keymaps.
|
/// Register an action type to allow it to be referenced in keymaps.
|
||||||
pub fn register_action<A: Action>() {
|
pub fn register_action<A: Action>() {
|
||||||
ACTION_BUILDERS.lock().insert(A::qualified_name(), A::build);
|
let name = A::qualified_name();
|
||||||
|
let mut lock = ACTION_REGISTRY.write();
|
||||||
|
lock.builders_by_name.insert(name.clone(), A::build);
|
||||||
|
lock.all_names.push(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
|
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
|
||||||
pub fn build_action(name: &str, params: Option<serde_json::Value>) -> Result<Box<dyn Action>> {
|
pub fn build_action(name: &str, params: Option<serde_json::Value>) -> Result<Box<dyn Action>> {
|
||||||
let lock = &ACTION_BUILDERS.lock();
|
let lock = ACTION_REGISTRY.read();
|
||||||
let build_action = lock
|
let build_action = lock
|
||||||
|
.builders_by_name
|
||||||
.get(name)
|
.get(name)
|
||||||
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
|
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
|
||||||
(build_action)(params)
|
(build_action)(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn all_action_names() -> MappedRwLockReadGuard<'static, [SharedString]> {
|
||||||
|
let lock = ACTION_REGISTRY.read();
|
||||||
|
RwLockReadGuard::map(lock, |registry: &ActionRegistry| {
|
||||||
|
registry.all_names.as_slice()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// actions defines structs that can be used as actions.
|
// actions defines structs that can be used as actions.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! actions {
|
macro_rules! actions {
|
||||||
|
|
|
@ -17,9 +17,9 @@ use crate::{
|
||||||
current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle,
|
current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle,
|
||||||
AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
|
AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
|
||||||
Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId,
|
Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId,
|
||||||
PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SharedString,
|
PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SubscriberSet,
|
||||||
SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem,
|
Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window,
|
||||||
View, Window, WindowContext, WindowHandle, WindowId,
|
WindowContext, WindowHandle, WindowId,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use collections::{HashMap, HashSet, VecDeque};
|
use collections::{HashMap, HashSet, VecDeque};
|
||||||
|
@ -140,7 +140,6 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
|
|
||||||
pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
|
pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
|
||||||
type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
|
type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
|
||||||
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
|
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
|
||||||
|
@ -176,7 +175,6 @@ pub struct AppContext {
|
||||||
pub(crate) keymap: Arc<Mutex<Keymap>>,
|
pub(crate) keymap: Arc<Mutex<Keymap>>,
|
||||||
pub(crate) global_action_listeners:
|
pub(crate) global_action_listeners:
|
||||||
HashMap<TypeId, Vec<Box<dyn Fn(&dyn Action, DispatchPhase, &mut Self)>>>,
|
HashMap<TypeId, Vec<Box<dyn Fn(&dyn Action, DispatchPhase, &mut Self)>>>,
|
||||||
action_builders: HashMap<SharedString, ActionBuilder>,
|
|
||||||
pending_effects: VecDeque<Effect>,
|
pending_effects: VecDeque<Effect>,
|
||||||
pub(crate) pending_notifications: HashSet<EntityId>,
|
pub(crate) pending_notifications: HashSet<EntityId>,
|
||||||
pub(crate) pending_global_notifications: HashSet<TypeId>,
|
pub(crate) pending_global_notifications: HashSet<TypeId>,
|
||||||
|
@ -234,7 +232,6 @@ impl AppContext {
|
||||||
windows: SlotMap::with_key(),
|
windows: SlotMap::with_key(),
|
||||||
keymap: Arc::new(Mutex::new(Keymap::default())),
|
keymap: Arc::new(Mutex::new(Keymap::default())),
|
||||||
global_action_listeners: HashMap::default(),
|
global_action_listeners: HashMap::default(),
|
||||||
action_builders: HashMap::default(),
|
|
||||||
pending_effects: VecDeque::new(),
|
pending_effects: VecDeque::new(),
|
||||||
pending_notifications: HashSet::default(),
|
pending_notifications: HashSet::default(),
|
||||||
pending_global_notifications: HashSet::default(),
|
pending_global_notifications: HashSet::default(),
|
||||||
|
@ -695,10 +692,6 @@ impl AppContext {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = SharedString> + 'a {
|
|
||||||
self.action_builders.keys().cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move the global of the given type to the stack.
|
/// Move the global of the given type to the stack.
|
||||||
pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
|
pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
|
||||||
GlobalLease::new(
|
GlobalLease::new(
|
||||||
|
@ -761,24 +754,6 @@ impl AppContext {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register an action type to allow it to be referenced in keymaps.
|
|
||||||
pub fn register_action_type<A: Action>(&mut self) {
|
|
||||||
self.action_builders.insert(A::qualified_name(), A::build);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Construct an action based on its name and parameters.
|
|
||||||
pub fn build_action(
|
|
||||||
&mut self,
|
|
||||||
name: &str,
|
|
||||||
params: Option<serde_json::Value>,
|
|
||||||
) -> Result<Box<dyn Action>> {
|
|
||||||
let build = self
|
|
||||||
.action_builders
|
|
||||||
.get(name)
|
|
||||||
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
|
|
||||||
(build)(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Event handlers propagate events by default. Call this method to stop dispatching to
|
/// Event handlers propagate events by default. Call this method to stop dispatching to
|
||||||
/// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is
|
/// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is
|
||||||
/// the opposite of [propagate]. It's also possible to cancel a call to [propagate] by
|
/// the opposite of [propagate]. It's also possible to cancel a call to [propagate] by
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
// Input:
|
// Input:
|
||||||
//
|
//
|
||||||
// struct Foo {}
|
// struct FooBar {}
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
//
|
//
|
||||||
// struct Foo {}
|
// struct FooBar {}
|
||||||
//
|
//
|
||||||
// #[allow(non_snake_case)]
|
// #[allow(non_snake_case)]
|
||||||
// #[gpui2::ctor]
|
// #[gpui2::ctor]
|
||||||
// fn register_Foo_builder() {
|
// fn register_foobar_builder() {
|
||||||
// gpui2::register_action_builder::<Foo>()
|
// gpui2::register_action_builder::<Foo>()
|
||||||
// }
|
// }
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
|
|
@ -73,9 +73,9 @@ impl KeymapFile {
|
||||||
"Expected first item in array to be a string."
|
"Expected first item in array to be a string."
|
||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
cx.build_action(&name, Some(data))
|
gpui::build_action(&name, Some(data))
|
||||||
}
|
}
|
||||||
Value::String(name) => cx.build_action(&name, None),
|
Value::String(name) => gpui::build_action(&name, None),
|
||||||
Value::Null => Ok(no_action()),
|
Value::Null => Ok(no_action()),
|
||||||
_ => {
|
_ => {
|
||||||
return Some(Err(anyhow!("Expected two-element array, got {action:?}")))
|
return Some(Err(anyhow!("Expected two-element array, got {action:?}")))
|
||||||
|
|
|
@ -15,8 +15,6 @@ impl FocusStory {
|
||||||
KeyBinding::new("cmd-a", ActionB, Some("child-1")),
|
KeyBinding::new("cmd-a", ActionB, Some("child-1")),
|
||||||
KeyBinding::new("cmd-c", ActionC, None),
|
KeyBinding::new("cmd-c", ActionC, None),
|
||||||
]);
|
]);
|
||||||
cx.register_action_type::<ActionA>();
|
|
||||||
cx.register_action_type::<ActionB>();
|
|
||||||
|
|
||||||
cx.build_view(move |cx| Self {})
|
cx.build_view(move |cx| Self {})
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@ impl LspAdapter for JsonLspAdapter {
|
||||||
&self,
|
&self,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> BoxFuture<'static, serde_json::Value> {
|
) -> BoxFuture<'static, serde_json::Value> {
|
||||||
let action_names = cx.all_action_names().collect::<Vec<_>>();
|
let action_names = gpui::all_action_names();
|
||||||
let staff_mode = cx.is_staff();
|
let staff_mode = cx.is_staff();
|
||||||
let language_names = &self.languages.language_names();
|
let language_names = &self.languages.language_names();
|
||||||
let settings_schema = cx.global::<SettingsStore>().json_schema(
|
let settings_schema = cx.global::<SettingsStore>().json_schema(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue