Skip to content

Commit

Permalink
Export menu (#183)
Browse files Browse the repository at this point in the history
* polish export metadata

* export menu

* enhanced alert system and YAML editing

* run cargo fmt

* upgrade packages and run ts lint

* clean up

* more clean up

* fix build break
  • Loading branch information
Pistonight authored Feb 4, 2024
1 parent 98e9795 commit 46710c8
Show file tree
Hide file tree
Showing 49 changed files with 1,516 additions and 1,106 deletions.
85 changes: 66 additions & 19 deletions compiler-core/src/expo/mod.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,76 @@
//! # Export (expo) phase
//!
//! This phase collects export metadata from the plugins. The export metadata
//! is used to display export options to the user.
//!
//! # Input
//! The input is a [`ExecContext`].
//!
//! # Output
//! The output is a [`ExpoContext`].
use serde_json::Value;

use crate::exec::ExecContext;
use crate::macros::derive_wasm;

/// Output of the export phase
#[derive_wasm]
pub struct ExpoContext<'p> {
#[serde(flatten)]
pub exec_ctx: ExecContext<'p>,
/// The export metadata
pub export_metadata: Vec<ExportMetadata>,
}

/// Data to define a plugin's export capability
#[derive(Debug, Clone)]
#[derive_wasm]
pub struct ExportMetadata {
/// The id of the plugin. This is the same string in the `use` of the plugin
/// The id of the export plugin to run. This is the same string in the `use` of the plugin
pub plugin_id: String,
/// The target of the exporter
pub target: ExportTarget,
/// Name of the export. For example "LiveSplit"
/// Name of the export. For example "LiveSplit". This is shown in the menu
pub name: String,
/// File extension of the export. For example "lss"
pub extension: Option<String>,
/// Long description. This shows as a tooltip
pub description: String,
// todo: icon

/// (Optional) Icon to show next to the name
#[serde(default)]
pub icon: ExportIcon,

/// (Optional) File extension of the export. For example "lss"
pub extension: Option<String>,

/// Extra properties to pass to the exporter when running
///
/// This can be used to distinguish multiple exports from the same exporter.
/// This is not part of the config and cannot be changed by the user
pub properties: Value,

/// (Optional) Example YAML configuration for the exporter to show to the user
pub example_config: Option<String>,
}

pub enum ExportTarget {
/// The exporter only runs for the CompDoc
CompDoc,
/// The exporter only runs for the ExecDoc
ExecDoc,
/// The exporter should run for both phases,
/// and produce the output in the ExecDoc phase
Both,
/// Icon for the export.
///
/// This is only for visual. It does not restrict what the export can/cannot contain.
#[derive(Debug, Clone, Default)]
#[derive_wasm]
pub enum ExportIcon {
Archive,
Binary,
Cat,
Code,
Data,
#[default]
File,
Image,
Text,
Video,
}

/// The exported document type
#[derive(Debug, Clone)]
#[derive_wasm]
pub struct ExpoDoc {
/// The file name
pub file_name: String,
Expand All @@ -34,14 +79,16 @@ pub struct ExpoDoc {
}

impl<'p> ExecContext<'p> {
pub fn prepare_exports(&mut self) -> Vec<ExportMetadata> {
pub fn prepare_exports(mut self) -> ExpoContext<'p> {
let mut result = Vec::new();
for plugin in &mut self.plugin_runtimes {
// TODO #33: error handling
if let Ok(Some(meta)) = plugin.on_prepare_export() {
result.push(meta);
result.extend(meta);
}
}
result
ExpoContext {
exec_ctx: self,
export_metadata: result,
}
}
}
1 change: 1 addition & 0 deletions compiler-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub mod prep;
// public API re-exports
pub use comp::CompDoc;
pub use exec::{ExecContext, ExecDoc};
pub use expo::{ExpoContext, ExpoDoc};
pub use pack::{CompileContext, Compiler};
pub use prep::{ContextBuilder, PreparedContext};

Expand Down
30 changes: 30 additions & 0 deletions compiler-core/src/plugin/builtin/livesplit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! Exporter plugin for LiveSplit split files

use std::borrow::Cow;

use serde_json::json;

use crate::expo::{ExportIcon, ExportMetadata};

use crate::plugin::{PluginResult, PluginRuntime};

pub struct ExportLiveSplitPlugin;

impl PluginRuntime for ExportLiveSplitPlugin {
fn get_id(&self) -> Cow<'static, str> {
Cow::Owned(super::BuiltInPlugin::ExportLiveSplit.id())
}

fn on_prepare_export(&mut self) -> PluginResult<Option<Vec<ExportMetadata>>> {
let metadata = ExportMetadata {
plugin_id: self.get_id().into_owned(),
name: "LiveSplit".to_string(),
description: "Export to a LiveSplit split file".to_string(),
icon: ExportIcon::Data,
extension: Some("lss".to_string()),
properties: json!(null),
example_config: Some(include_str!("./livesplit.yaml").to_string()),
};
Ok(Some(vec![metadata]))
}
}
13 changes: 13 additions & 0 deletions compiler-core/src/plugin/builtin/livesplit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Do you want subsplits?
# --------
# Subsplits are divided by sections in the route document
# Change below to `true` to enable subsplits
subsplits: false

# Do you want icons?
# --------
# Change below to `false` to disable icons
icons: true

# Keep this as-is to use the splits configured in the settings
split-types: null
14 changes: 9 additions & 5 deletions compiler-core/src/plugin/builtin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,19 @@ use super::{PluginResult, PluginRuntime};

mod botw_unstable;
mod link;
mod livesplit;
mod metrics;
mod variables;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum BuiltInPlugin {
Metrics,
BotwAbilityUnstable,
#[serde(rename = "export-livesplit")]
ExportLiveSplit,
Link,
Metrics,
Variables,
BotwAbilityUnstable,
}

impl BuiltInPlugin {
Expand All @@ -30,15 +33,16 @@ impl BuiltInPlugin {
props: &Value,
) -> PluginResult<Box<dyn PluginRuntime>> {
match &self {
BuiltInPlugin::BotwAbilityUnstable => Ok(Box::new(
botw_unstable::BotwAbilityUnstablePlugin::from_props(props),
)),
BuiltInPlugin::ExportLiveSplit => Ok(Box::new(livesplit::ExportLiveSplitPlugin)),
BuiltInPlugin::Link => Ok(Box::new(link::LinkPlugin)),
BuiltInPlugin::Metrics => Ok(Box::new(metrics::MetricsPlugin::from_props(
props,
&ctx.start_time,
))),
BuiltInPlugin::Variables => Ok(Box::new(variables::VariablesPlugin::from_props(props))),
BuiltInPlugin::BotwAbilityUnstable => Ok(Box::new(
botw_unstable::BotwAbilityUnstablePlugin::from_props(props),
)),
}
}

Expand Down
4 changes: 4 additions & 0 deletions compiler-core/src/plugin/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ use crate::lang::BaseError;
pub enum PluginError {
#[error("An exception occured while executing script: {0}")]
ScriptException(String),

#[error("Extra plugin at `{0}` from plugin options is invalid: {1}")]
InvalidAddPlugin(usize, String),

#[error("The plugin `{0}` does not implement the required `{1}` method!")]
NotImplemented(String, String),
}

pub type PluginResult<T> = Result<T, PluginError>;
Expand Down
33 changes: 29 additions & 4 deletions compiler-core/src/plugin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,49 @@ pub trait PluginRuntime {
fn on_before_compile(&mut self, _ctx: &mut CompileContext) -> PluginResult<()> {
Ok(())
}

/// Called after the route is compiled, to transform the route
fn on_after_compile(&mut self, _doc: &mut CompDoc) -> PluginResult<()> {
Ok(())
}

/// Called after the route is turned into ExecDoc
fn on_after_execute(&mut self, _doc: &mut ExecDoc) -> PluginResult<()> {
Ok(())
}

fn on_prepare_export(&mut self) -> PluginResult<Option<ExportMetadata>> {
/// Called at the end of compilation to check what exports are available
fn on_prepare_export(&mut self) -> PluginResult<Option<Vec<ExportMetadata>>> {
Ok(None)
}

fn on_export_comp_doc(&mut self, _doc: &CompDoc) -> PluginResult<Option<ExpoDoc>> {
/// Called only in export workflow, to let the exporter access the CompDoc
///
/// If the exporter needs to access the ExecDoc as well, it should return `None`.
/// Otherwise, the returned export data will be used and the exporter will not be called
/// with the ExecDoc
fn on_export_comp_doc(
&mut self,
_properties: &Value,
_payload: &Value,
_doc: &CompDoc,
) -> PluginResult<Option<ExpoDoc>> {
Ok(None)
}
fn on_export_exec_doc(&mut self, _doc: &ExecDoc) -> PluginResult<Option<ExpoDoc>> {
Ok(None)

/// Called only in export workflow, to let the exporter access the ExecDoc
///
/// The exporter must return the export data or throw an error
fn on_export_exec_doc(
&mut self,
_properties: Value,
_payload: Value,
_doc: &ExecDoc,
) -> PluginResult<ExpoDoc> {
Err(PluginError::NotImplemented(
self.get_display_name().into_owned(),
"on_export_exec_doc".into(),
))
}
}

Expand Down
2 changes: 1 addition & 1 deletion compiler-wasm/build/src/prelude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
import { callWorker } from "low/utils";

// serde_json::Value
export type Value = null | boolean | number | string | Value[] | { [key: string]: Value };
export type Value = unknown;
19 changes: 9 additions & 10 deletions compiler-wasm/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use celerc::{
CompDoc, CompileContext, Compiler, ContextBuilder, ExecContext, PluginOptions, PreparedContext,
};

use crate::interop::OpaqueExecContext;
use crate::interop::OpaqueExpoContext;
use crate::loader::{self, LoadFileOutput, LoaderInWasm};
use crate::plugin;

Expand All @@ -23,15 +23,15 @@ thread_local! {
pub async fn compile_document(
entry_path: Option<String>,
use_cache: bool,
) -> Result<OpaqueExecContext, JsValue> {
) -> Result<OpaqueExpoContext, JsValue> {
let plugin_options = match plugin::get_plugin_options() {
Ok(x) => x,
Err(message) => {
let message = format!("Failed to load user plugin options: {message}");
error!("{message}");
let diagnostic = DocDiagnostic::error(&message, "web-editor");
let exec_ctx = ExecContext::from_diagnostic(diagnostic);
return OpaqueExecContext::try_from(exec_ctx);
return OpaqueExpoContext::try_from(exec_ctx.prepare_exports());
}
};

Expand All @@ -58,8 +58,7 @@ pub async fn compile_document(
Err(e) => {
let comp_doc = CompDoc::from_prep_error(e, start_time);
let exec_context = comp_doc.execute().await;
// TODO #33: exports
return OpaqueExecContext::try_from(exec_context);
return OpaqueExpoContext::try_from(exec_context.prepare_exports());
}
};

Expand Down Expand Up @@ -100,7 +99,7 @@ async fn compile_in_context(
prep_ctx: &PreparedContext<LoaderInWasm>,
start_time: Option<Instant>,
plugin_options: Option<PluginOptions>,
) -> Result<OpaqueExecContext, JsValue> {
) -> Result<OpaqueExpoContext, JsValue> {
let mut comp_ctx = prep_ctx.new_compilation(start_time).await;
match comp_ctx.configure_plugins(plugin_options).await {
Err(e) => compile_with_pack_error(comp_ctx, e).await,
Expand All @@ -114,16 +113,16 @@ async fn compile_in_context(
async fn compile_with_pack_error(
context: CompileContext<'_>,
error: PackError,
) -> Result<OpaqueExecContext, JsValue> {
) -> Result<OpaqueExpoContext, JsValue> {
let comp_doc = CompDoc::from_diagnostic(error, context);
let exec_ctx = comp_doc.execute().await;
OpaqueExecContext::try_from(exec_ctx)
OpaqueExpoContext::try_from(exec_ctx.prepare_exports())
}

async fn compile_with_compiler(compiler: Compiler<'_>) -> Result<OpaqueExecContext, JsValue> {
async fn compile_with_compiler(compiler: Compiler<'_>) -> Result<OpaqueExpoContext, JsValue> {
let comp_doc = compiler.compile().await;
let exec_ctx = comp_doc.execute().await;
OpaqueExecContext::try_from(exec_ctx)
OpaqueExpoContext::try_from(exec_ctx.prepare_exports())
}

/// Create a context builder that corresponds to the root project.yaml
Expand Down
6 changes: 3 additions & 3 deletions compiler-wasm/src/interop/opaque.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use wasm_bindgen::prelude::*;

use celerc::macros::derive_opaque;
use celerc::ExecContext;
use celerc::ExpoContext;

#[derive_opaque(ExecContext)]
pub struct OpaqueExecContext<'p>;
#[derive_opaque(ExpoContext)]
pub struct OpaqueExpoContext<'p>;
4 changes: 2 additions & 2 deletions compiler-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use celerc::prep::EntryPointsSorted;
use celerc::res::{ResPath, Resource};

mod interop;
use interop::OpaqueExecContext;
use interop::OpaqueExpoContext;
mod compile;
mod loader;
use loader::LoaderInWasm;
Expand Down Expand Up @@ -53,7 +53,7 @@ pub async fn get_entry_points() -> Result<EntryPointsSorted, JsValue> {
pub async fn compile_document(
entry_path: Option<String>,
use_cache: bool,
) -> Result<OpaqueExecContext, JsValue> {
) -> Result<OpaqueExpoContext, JsValue> {
compile::compile_document(entry_path, use_cache).await
}

Expand Down
5 changes: 3 additions & 2 deletions docs/src/plugin/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ First follow these steps to get to the user plugin settings:
1. Open the document you want to tweak.
2. Click on <FluentIcon name="Settings20Regular" /> `Settings` on the toolbar.
3. Select the <FluentIcon name="Wrench20Regular" /> `Plugins` category.
4. You should see a text area under `User Plugins`.
4. Under `User Plugins`, click the `Edit Config` button.
5. You should see a dialog with a text area popped up.

The syntax to configure user plugins is:
```yaml
Expand All @@ -40,7 +41,7 @@ Replace:
- `"Document Title"` with the exact title of the document you want to add the plugin to, surrounded by quotes.
There is a little hint above the text area that tells you what the title of the current document is. You can also use the wildcard`"*"` to
add the plugin to all documents.
- `my/plugin/file.js` with the path of the plugin. The plugin should be a file on GitHub,
- `my/extra/plugin1.js` with the path of the plugin. The plugin should be a file on GitHub,
and you can reference it by `<user>/<repo>/path/to/file.js`. See [here](../route/file-structure.md)
for more about the `use` property. However note that you cannot use a path to file on your computer.

Expand Down
Loading

0 comments on commit 46710c8

Please sign in to comment.