mirror of
https://github.com/enso-org/enso.git
synced 2024-12-28 03:22:21 +03:00
Expose IDE as a library (https://github.com/enso-org/ide/pull/1080)
Original commit: b16ac40a3d
This commit is contained in:
parent
6a7bee1776
commit
89d990c5b3
@ -72,17 +72,18 @@ function command(docs) {
|
||||
return {docs}
|
||||
}
|
||||
|
||||
function run_project_manager() {
|
||||
const bin_path = paths.get_project_manager_path(paths.dist.bin)
|
||||
console.log(`Starting the language server from "${bin_path}".`)
|
||||
child_process.execFile(bin_path, [], (error, stdout, stderr) => {
|
||||
console.error(stderr)
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
console.log(stdout)
|
||||
})
|
||||
}
|
||||
// FIXME: this does not work if project manager was not downloaded yet.
|
||||
//function run_project_manager() {
|
||||
// const bin_path = paths.get_project_manager_path(paths.dist.bin)
|
||||
// console.log(`Starting the language server from "${bin_path}".`)
|
||||
// child_process.execFile(bin_path, [], (error, stdout, stderr) => {
|
||||
// console.error(stderr)
|
||||
// if (error) {
|
||||
// throw error
|
||||
// }
|
||||
// console.log(stdout)
|
||||
// })
|
||||
//}
|
||||
|
||||
// ================
|
||||
// === Commands ===
|
||||
@ -241,7 +242,8 @@ commands.watch.rust = async function(argv) {
|
||||
build_args.push(`--crate=${argv.crate}`)
|
||||
}
|
||||
|
||||
run_project_manager()
|
||||
// FIXME: See fixme on fn definition.
|
||||
// run_project_manager()
|
||||
build_args = build_args.join(' ')
|
||||
let target =
|
||||
'"' +
|
||||
|
@ -147,9 +147,9 @@ are presented below:
|
||||
utility which will build the project on every change. Open
|
||||
`http://localhost:8080` (the port may vary and will be reported in the
|
||||
terminal if `8080` was already in use) to run the application, or
|
||||
`http://localhost:8080/debug` to open example demo scenes. Please remember
|
||||
to disable the cache in your browser during the development! By default,
|
||||
the script disables heavyweight optimizations to provide interactive
|
||||
`http://localhost:8080/?entry` to open example demo scenes list. Please
|
||||
remember to disable the cache in your browser during the development! By
|
||||
default, the script disables heavyweight optimizations to provide interactive
|
||||
development experience. The scripts are thin wrappers for
|
||||
[wasm-pack](https://github.com/rustwasm/wasm-pack) and accept the same
|
||||
[command line arguments](https://rustwasm.github.io/wasm-pack/book/commands/build.html).
|
||||
@ -158,8 +158,8 @@ are presented below:
|
||||
In order to compile in a production mode (enable all optimizations, strip
|
||||
WASM debug symbols, minimize the output binaries, etc.), run
|
||||
`node ./run build`. To create platform-specific packages and installers use
|
||||
`node ./run dist` instead. The final packages will be located at
|
||||
`app/dist/native`.
|
||||
`node ./run dist` instead. The final executables will be located at
|
||||
`dist/client/$PLATFORM`.
|
||||
|
||||
- **Selective mode**
|
||||
In order to compile only part of the project, and thus drastically shorten
|
||||
@ -173,6 +173,19 @@ are presented below:
|
||||
were defined or re-exported by that crate. In particular, the `ide` crate
|
||||
exposes the `entry_point_ide` function, so you have to compile it to test
|
||||
your code in the Enso IDE.
|
||||
|
||||
|
||||
### Using IDE as a library.
|
||||
In case you want to use the IDE as a library, for example to embed it into
|
||||
another website, you need to first build it using `node ./run {built,dist}` and
|
||||
find the necessary artifacts located at `dist/content`. Especially, the
|
||||
`dist/content/index.js` defines a function `window.enso.main(cfg)` which you
|
||||
can use to run the IDE. Currently, the configuration argument can contain the
|
||||
following options:
|
||||
- `entry` - the entry point, one of predefined scenes. Set it to empty string to
|
||||
see the list of possible entry points.
|
||||
- `project` - the project name to open after loading the IDE.
|
||||
|
||||
|
||||
### Testing, Linting, and Validation
|
||||
After changing the code it's always a good idea to lint and test the code. We
|
||||
|
8
gui/src/config.yaml
Normal file
8
gui/src/config.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
# The name of an object created in the global window scope. This object will contain functions
|
||||
# to control the IDE, like the `main` function, and also the configuration object.
|
||||
windowAppScopeName: "enso"
|
||||
|
||||
# The configuration object nested inside of the `windowAppScopeName` object, containing all
|
||||
# startup configuration options. See usages of this variable to learn more about available
|
||||
# options.
|
||||
windowAppScopeConfigName: "config"
|
@ -10,7 +10,8 @@ let config = {
|
||||
},
|
||||
devDependencies: {
|
||||
"compression-webpack-plugin": "^3.1.0",
|
||||
"copy-webpack-plugin": "^5.1.1"
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"yaml-loader": "^0.6.0",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,9 +69,9 @@
|
||||
}
|
||||
</style>
|
||||
<script type="module" src="/assets/index.js" defer></script>
|
||||
<script type="module" src="/assets/run.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- <div class="titlebar"></div> -->
|
||||
<div id="root"></div>
|
||||
<noscript>
|
||||
This page requires JavaScript to run. Please enable it in your browser.
|
||||
|
@ -5,6 +5,16 @@
|
||||
import * as loader_module from 'enso-studio-common/src/loader'
|
||||
import * as html_utils from 'enso-studio-common/src/html_utils'
|
||||
import * as animation from 'enso-studio-common/src/animation'
|
||||
import * as globalConfig from '../../../../config.yaml'
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === Global API ===
|
||||
// ==================
|
||||
|
||||
let API = {}
|
||||
window[globalConfig.windowAppScopeName] = API
|
||||
|
||||
|
||||
|
||||
@ -109,18 +119,13 @@ function show_debug_screen(wasm,msg) {
|
||||
ul.appendChild(li)
|
||||
a.appendChild(linkText)
|
||||
a.title = name
|
||||
a.href = "javascript:{}"
|
||||
a.onclick = () => {
|
||||
html_utils.remove_node(debug_screen_div)
|
||||
let fn_name = wasm_entry_point_pfx + name
|
||||
let fn = wasm[fn_name]
|
||||
fn()
|
||||
}
|
||||
a.href = "?entry="+name
|
||||
li.appendChild(a)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ====================
|
||||
// === Scam Warning ===
|
||||
// ====================
|
||||
@ -191,7 +196,7 @@ window.logsBuffer = logsBuffer
|
||||
|
||||
let root = document.getElementById('root')
|
||||
|
||||
function prepare_root(cfg) {
|
||||
function style_root() {
|
||||
root.style.backgroundColor = '#f6f3f199'
|
||||
}
|
||||
|
||||
@ -218,43 +223,36 @@ function disableContextMenu() {
|
||||
})
|
||||
}
|
||||
|
||||
function ok(value) {
|
||||
return value !== null && value !== undefined
|
||||
}
|
||||
|
||||
/// Main entry point. Loads WASM, initializes it, chooses the scene to run.
|
||||
async function main() {
|
||||
API.main = async function (inputConfig) {
|
||||
let urlParams = new URLSearchParams(window.location.search);
|
||||
let urlConfig = Object.fromEntries(urlParams.entries())
|
||||
let config = Object.assign({},inputConfig,urlConfig)
|
||||
API[globalConfig.windowAppScopeConfigName] = config
|
||||
|
||||
style_root()
|
||||
printScamWarning()
|
||||
hideLogs()
|
||||
disableContextMenu()
|
||||
let location = window.location.pathname.split('/')
|
||||
location.splice(0,1)
|
||||
let cfg = getUrlParams()
|
||||
prepare_root(cfg)
|
||||
|
||||
let debug_mode = location[0] == "debug"
|
||||
let debug_target = location[1]
|
||||
let no_loader = debug_mode && debug_target
|
||||
let entryTarget = ok(config.entry) ? config.entry : main_entry_point
|
||||
let useLoader = entryTarget === main_entry_point
|
||||
|
||||
await windowShowAnimation()
|
||||
let {wasm,loader} = await download_content({no_loader})
|
||||
let {wasm,loader} = await download_content({no_loader:!useLoader})
|
||||
|
||||
let target = null;
|
||||
if (debug_mode) {
|
||||
loader.destroy()
|
||||
if (debug_target) {
|
||||
target = debug_target
|
||||
}
|
||||
} else {
|
||||
target = main_entry_point
|
||||
}
|
||||
|
||||
if (target) {
|
||||
let fn_name = wasm_entry_point_pfx + target
|
||||
if (entryTarget) {
|
||||
let fn_name = wasm_entry_point_pfx + entryTarget
|
||||
let fn = wasm[fn_name]
|
||||
if (fn) { fn() } else {
|
||||
loader.destroy()
|
||||
show_debug_screen(wasm,"Unknown entry point '" + target + "'. ")
|
||||
show_debug_screen(wasm,"Unknown entry point '" + entryTarget + "'. ")
|
||||
}
|
||||
} else {
|
||||
show_debug_screen(wasm)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
4
gui/src/js/lib/content/src/run.js
Normal file
4
gui/src/js/lib/content/src/run.js
Normal file
@ -0,0 +1,4 @@
|
||||
/// This file is used to simply run the IDE. It can be not invoked if the IDE needs to be used as a
|
||||
/// library.
|
||||
|
||||
window.enso.main()
|
@ -24,6 +24,7 @@ module.exports = {
|
||||
new CompressionPlugin(),
|
||||
new CopyWebpackPlugin([
|
||||
path.resolve(thisPath,'src','index.html'),
|
||||
path.resolve(thisPath,'src','run.js'),
|
||||
path.resolve(wasmPath,'ide.wasm'),
|
||||
]),
|
||||
],
|
||||
@ -42,5 +43,14 @@ module.exports = {
|
||||
hints: false,
|
||||
},
|
||||
mode: 'none',
|
||||
stats: 'minimal'
|
||||
stats: 'minimal',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ya?ml$/,
|
||||
type: 'json',
|
||||
use: 'yaml-loader'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
28
gui/src/js/package-lock.json
generated
28
gui/src/js/package-lock.json
generated
@ -1212,9 +1212,9 @@
|
||||
}
|
||||
},
|
||||
"@octokit/openapi-types": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-2.0.0.tgz",
|
||||
"integrity": "sha512-J4bfM7lf8oZvEAdpS71oTvC1ofKxfEZgU5vKVwzZKi4QPiL82udjpseJwxPid9Pu2FNmyRQOX4iEj6W1iOSnPw==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-2.0.1.tgz",
|
||||
"integrity": "sha512-9AuC04PUnZrjoLiw3uPtwGh9FE4Q3rTqs51oNlQ0rkwgE8ftYsOC+lsrQyvCvWm85smBbSc0FNRKKumvGyb44Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@octokit/plugin-enterprise-rest": {
|
||||
@ -1358,12 +1358,12 @@
|
||||
}
|
||||
},
|
||||
"@octokit/types": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.1.1.tgz",
|
||||
"integrity": "sha512-btm3D6S7VkRrgyYF31etUtVY/eQ1KzrNRqhFt25KSe2mKlXuLXJilglRC6eDA2P6ou94BUnk/Kz5MPEolXgoiw==",
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.1.2.tgz",
|
||||
"integrity": "sha512-LPCpcLbcky7fWfHCTuc7tMiSHFpFlrThJqVdaHgowBTMS0ijlZFfonQC/C1PrZOjD4xRCYgBqH9yttEATGE/nw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@octokit/openapi-types": "^2.0.0",
|
||||
"@octokit/openapi-types": "^2.0.1",
|
||||
"@types/node": ">= 8"
|
||||
}
|
||||
},
|
||||
@ -13038,6 +13038,20 @@
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
||||
},
|
||||
"yaml": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
|
||||
"integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg=="
|
||||
},
|
||||
"yaml-loader": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml-loader/-/yaml-loader-0.6.0.tgz",
|
||||
"integrity": "sha512-1bNiLelumURyj+zvVHOv8Y3dpCri0F2S+DCcmps0pA1zWRLjS+FhZQg4o3aUUDYESh73+pKZNI18bj7stpReow==",
|
||||
"requires": {
|
||||
"loader-utils": "^1.4.0",
|
||||
"yaml": "^1.8.3"
|
||||
}
|
||||
},
|
||||
"yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
|
@ -100,7 +100,7 @@ impl Display for IpWithSocket {
|
||||
}
|
||||
|
||||
/// Project name.
|
||||
#[derive(Debug,Display,Clone,Serialize,Deserialize,PartialEq,Shrinkwrap)]
|
||||
#[derive(Debug,Display,Clone,Serialize,Deserialize,From,PartialEq,Shrinkwrap)]
|
||||
#[shrinkwrap(mutable)]
|
||||
pub struct ProjectName(pub String);
|
||||
|
||||
|
@ -8,26 +8,14 @@ use ensogl::system::web;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
mod connection_arguments {
|
||||
pub const PROJECT_MANAGER : &str = "project_manager";
|
||||
pub const LANGUAGE_SERVER_JSON : &str = "language_server_rpc";
|
||||
pub const LANGUAGE_SERVER_BINARY : &str = "language_server_data";
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Errors ===
|
||||
// ==============
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,Fail)]
|
||||
#[fail(display="Missing program option: {}.",name)]
|
||||
pub struct MissingOption {name:&'static str}
|
||||
#[fail(display="Missing program option: {}.",0)]
|
||||
pub struct MissingOption (&'static str);
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone,Copy,Debug,Fail)]
|
||||
@ -36,9 +24,9 @@ pub struct MutuallyExclusiveOptions;
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === Connection ===
|
||||
// ==================
|
||||
// ======================
|
||||
// === BackendService ===
|
||||
// ======================
|
||||
|
||||
/// A Configuration defining to what backend service should IDE connect.
|
||||
#[allow(missing_docs)]
|
||||
@ -64,36 +52,127 @@ impl Default for BackendService {
|
||||
impl BackendService {
|
||||
/// Read backend configuration from the web arguments. See also [`web::Arguments`]
|
||||
/// documentation.
|
||||
pub fn from_web_arguments(arguments:&web::Arguments) -> FallibleResult<Self> {
|
||||
let pm_endpoint = arguments.get(connection_arguments::PROJECT_MANAGER).cloned();
|
||||
let ls_json_endpoint = arguments.get(connection_arguments::LANGUAGE_SERVER_JSON).cloned();
|
||||
let ls_bin_endpoint = arguments.get(connection_arguments::LANGUAGE_SERVER_BINARY).cloned();
|
||||
if let Some(endpoint) = pm_endpoint {
|
||||
if ls_json_endpoint.is_some() || ls_bin_endpoint.is_some() {
|
||||
pub fn from_web_arguments(config:&ConfigReader) -> FallibleResult<Self> {
|
||||
if let Some(endpoint) = &config.project_manager {
|
||||
if config.language_server_rpc.is_some() || config.language_server_data.is_some() {
|
||||
Err(MutuallyExclusiveOptions.into())
|
||||
} else {
|
||||
let endpoint = endpoint.clone();
|
||||
Ok(Self::ProjectManager {endpoint})
|
||||
}
|
||||
} else {
|
||||
match (ls_json_endpoint,ls_bin_endpoint) {
|
||||
(Some(json_endpoint),Some(binary_endpoint)) =>
|
||||
Ok(Self::LanguageServer {json_endpoint,binary_endpoint}),
|
||||
(None,None) =>
|
||||
Ok(default()),
|
||||
(Some(_),None) =>
|
||||
Err(MissingOption{name:connection_arguments::LANGUAGE_SERVER_BINARY}.into()),
|
||||
(None,Some(_)) =>
|
||||
Err(MissingOption{name:connection_arguments::LANGUAGE_SERVER_JSON }.into())
|
||||
match (&config.language_server_rpc,&config.language_server_data) {
|
||||
(Some(json_endpoint),Some(binary_endpoint)) => {
|
||||
let json_endpoint = json_endpoint.clone();
|
||||
let binary_endpoint = binary_endpoint.clone();
|
||||
Ok(Self::LanguageServer {json_endpoint,binary_endpoint})
|
||||
}
|
||||
(None,None) => Ok(default()),
|
||||
(Some(_),None) => Err(MissingOption(config.names().language_server_data).into()),
|
||||
(None,Some(_)) => Err(MissingOption(config.names().language_server_rpc).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Config ===
|
||||
// ==============
|
||||
|
||||
/// The path at which the config is accessible. This needs to be synchronised with the
|
||||
/// `src/config.yaml` configuration file. In the future, we could write a procedural macro, which
|
||||
/// loads the configuration and splits Rust variables from it during compilation time. This is not
|
||||
/// possible by using macro rules, as there is no way to plug in the output of `include_str!` macro
|
||||
/// to another macro input.
|
||||
const WINDOW_CFG_PATH : &[&str] = &["enso","config"];
|
||||
|
||||
/// Defines a new config structure. The provided fields are converted to optional fields. The config
|
||||
/// constructor queries JavaScript configuration for the keys defined in this structure. For each
|
||||
/// resulting string value, it converts it to the defined type. It also reports warnings for all
|
||||
/// config options that were provided, but were not matched this definition.
|
||||
macro_rules! define_config {
|
||||
($name:ident { $($field:ident : $field_type:ty),* $(,)? }) => {
|
||||
|
||||
/// Reflection mechanism containing string representation of option names.
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Names {
|
||||
$($field : &'static str),*
|
||||
}
|
||||
|
||||
impl Default for Names {
|
||||
fn default() -> Self {
|
||||
$(let $field = stringify!{$field};)*
|
||||
Self {$($field),*}
|
||||
}
|
||||
}
|
||||
|
||||
/// The structure containing application configs.
|
||||
#[derive(Clone,Debug,Default)]
|
||||
pub struct $name {
|
||||
__names__ : Names,
|
||||
$($field : Option<$field_type>),*
|
||||
}
|
||||
|
||||
impl $name {
|
||||
/// Constructor.
|
||||
pub fn new() -> Self {
|
||||
let logger = Logger::new(stringify!{$name});
|
||||
let window = web::window();
|
||||
match web::reflect_get_nested_object(&window,WINDOW_CFG_PATH).ok() {
|
||||
None => {
|
||||
let path = WINDOW_CFG_PATH.join(".");
|
||||
error!(&logger,"The config path '{path}' is invalid.");
|
||||
default()
|
||||
}
|
||||
Some(cfg) => {
|
||||
let __names__ = default();
|
||||
let keys = web::object_keys(&cfg);
|
||||
let mut keys = keys.into_iter().collect::<HashSet<String>>();
|
||||
$(
|
||||
let name = stringify!{$field};
|
||||
let $field = web::reflect_get_nested_string(&cfg,&[name]).ok();
|
||||
let $field = $field.map(|t|t.into());
|
||||
keys.remove(name);
|
||||
)*
|
||||
for key in keys {
|
||||
warning!(&logger,"Unknown config option provided '{key}'.");
|
||||
}
|
||||
Self {__names__,$($field),*}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reflection mechanism to get string representation of option names.
|
||||
pub fn names(&self) -> &Names {
|
||||
&self.__names__
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_config! {
|
||||
ConfigReader {
|
||||
entry : String,
|
||||
project : ProjectName,
|
||||
project_manager : String,
|
||||
language_server_rpc : String,
|
||||
language_server_data : String,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Startup ===
|
||||
// ===============
|
||||
|
||||
/// Configuration data necessary to initialize IDE.
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct Startup {
|
||||
/// The configuration of connection to the backend service.
|
||||
pub backend:BackendService,
|
||||
pub backend : BackendService,
|
||||
/// The project name we want to open on startup.
|
||||
pub project_name : ProjectName
|
||||
}
|
||||
@ -110,12 +189,11 @@ impl Default for Startup {
|
||||
impl Startup {
|
||||
/// Read configuration from the web arguments. See also [`web::Arguments`] documentation.
|
||||
pub fn from_web_arguments() -> FallibleResult<Startup> {
|
||||
let arguments = ensogl::system::web::Arguments::new();
|
||||
let backend = BackendService::from_web_arguments(&arguments)?;
|
||||
let project_name = arguments.get("project").map(ProjectName::new);
|
||||
let project_name = project_name.unwrap_or_else(|| {
|
||||
let config = ConfigReader::new();
|
||||
let backend = BackendService::from_web_arguments(&config)?;
|
||||
let project_name = config.project.unwrap_or_else(||
|
||||
ProjectName::new(constants::DEFAULT_PROJECT_NAME)
|
||||
});
|
||||
);
|
||||
Ok(Startup{backend,project_name})
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ pub use web_sys::WebGl2RenderingContext;
|
||||
pub use web_sys::Window;
|
||||
pub use std::time::Duration;
|
||||
pub use std::time::Instant;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
@ -62,6 +62,13 @@ pub fn Error<S:Into<String>>(message:S) -> Error {
|
||||
|
||||
pub type Result<T> = std::result::Result<T,Error>;
|
||||
|
||||
impl From<JsValue> for Error {
|
||||
fn from(t:JsValue) -> Self {
|
||||
let message = format!("{:?}",t);
|
||||
Self {message}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
@ -595,50 +602,46 @@ pub async fn sleep(duration:Duration) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use async_std::task::sleep;
|
||||
|
||||
/// Stores arguments extracted from `Location`'s search
|
||||
/// (https://developer.mozilla.org/en-US/docs/Web/API/Location/search).
|
||||
/// e.g. extracts arg0 = value0, and arg1 = value1 from http://localhost/?arg0=value0&arg1=value1.
|
||||
#[derive(Debug)]
|
||||
pub struct Arguments {
|
||||
pub hash_map : HashMap<String,String>
|
||||
}
|
||||
|
||||
impl Deref for Arguments {
|
||||
type Target = HashMap<String,String>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.hash_map }
|
||||
}
|
||||
|
||||
impl Arguments {
|
||||
fn args_from_search(search:&str) -> HashMap<String,String> {
|
||||
if search.chars().nth(0) == Some('?') {
|
||||
let search_without_question_mark = &search[1..];
|
||||
search_without_question_mark.split('&').filter_map(|arg| {
|
||||
match arg.split('=').collect_vec().as_slice() {
|
||||
[key,value] => Some(((*key).to_string(),(*value).to_string())),
|
||||
[key] => Some(((*key).to_string(),"".to_string())),
|
||||
_ => None
|
||||
}
|
||||
}).collect()
|
||||
} else {
|
||||
default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new arguments map from location search.
|
||||
pub fn new() -> Self {
|
||||
default()
|
||||
/// Get the nested value of the provided object. This is similar to writing `foo.bar.baz` in
|
||||
/// JavaScript, but in a safe manner, while checking if the value exists on each level.
|
||||
pub fn reflect_get_nested(target:&JsValue, keys:&[&str]) -> Result<JsValue> {
|
||||
let mut tgt = target.clone();
|
||||
for key in keys {
|
||||
let obj = tgt.dyn_into::<js_sys::Object>()?;
|
||||
let key = (*key).into();
|
||||
tgt = js_sys::Reflect::get(&obj,&key)?;
|
||||
}
|
||||
Ok(tgt)
|
||||
}
|
||||
|
||||
impl Default for Arguments {
|
||||
fn default() -> Self {
|
||||
let search = window().location().search().unwrap_or_default();
|
||||
let hash_map = Self::args_from_search(&search);
|
||||
Self{hash_map}
|
||||
}
|
||||
/// Get the nested value of the provided object and cast it to [`Object`]. See docs of
|
||||
/// [`reflect_get_nested`] to learn more.
|
||||
pub fn reflect_get_nested_object(target:&JsValue, keys:&[&str]) -> Result<js_sys::Object> {
|
||||
let tgt = reflect_get_nested(target,keys)?;
|
||||
Ok(tgt.dyn_into()?)
|
||||
}
|
||||
|
||||
/// Get the nested value of the provided object and cast it to [`String`]. See docs of
|
||||
/// [`reflect_get_nested`] to learn more.
|
||||
pub fn reflect_get_nested_string(target:&JsValue, keys:&[&str]) -> Result<String> {
|
||||
let tgt = reflect_get_nested(target,keys)?;
|
||||
let val = tgt.dyn_into::<js_sys::JsString>()?;
|
||||
Ok(val.into())
|
||||
}
|
||||
|
||||
/// Get all the keys of the provided [`Object`].
|
||||
pub fn object_keys(target:&JsValue) -> Vec<String> {
|
||||
target.clone().dyn_into::<js_sys::Object>().ok().map(|obj| {
|
||||
js_sys::Object::keys(&obj).iter().map(|key| {
|
||||
// The unwrap is safe, the `Object::keys` API guarantees it.
|
||||
let js_str = key.dyn_into::<js_sys::JsString>().unwrap();
|
||||
js_str.into()
|
||||
}).collect()
|
||||
}).unwrap_or_default()
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Test ===
|
||||
// ============
|
||||
@ -665,29 +668,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn args() {
|
||||
let search = String::from("?project=HelloWorld&arg0=value0");
|
||||
let args = Arguments::args_from_search(&search);
|
||||
assert_eq!(args.len(), 2);
|
||||
assert_eq!(args.get("project"), Some(&"HelloWorld".to_string()));
|
||||
assert_eq!(args.get("arg0"), Some(&"value0".to_string()));
|
||||
|
||||
let search = String::from("project=HelloWorld&arg0=value0");
|
||||
let args = Arguments::args_from_search(&search);
|
||||
assert_eq!(args.len(), 0);
|
||||
|
||||
let search = String::from("");
|
||||
let args = Arguments::args_from_search(&search);
|
||||
assert_eq!(args.len(), 0);
|
||||
|
||||
let search = String::from("?project&arg0=value0");
|
||||
let args = Arguments::args_from_search(&search);
|
||||
assert_eq!(args.len(), 2);
|
||||
assert_eq!(args.get("project"), Some(&"".to_string()));
|
||||
assert_eq!(args.get("arg0"), Some(&"value0".to_string()));
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod helpers {
|
||||
use std::time::Instant;
|
||||
|
Loading…
Reference in New Issue
Block a user