use crate::{
api::{CodeEditor as CodeEditorModel, TextModel},
sys::editor::IStandaloneEditorConstructionOptions,
};
use std::{cell::RefCell, rc::Rc};
use web_sys::HtmlElement;
use yew::{html, Callback, Classes, Component, Context, Html, NodeRef, Properties};
#[derive(Clone, Debug, PartialEq, Properties)]
pub struct CodeEditorProps<OPT: std::cmp::PartialEq + Clone + Into<IStandaloneEditorConstructionOptions> = IStandaloneEditorConstructionOptions> {
#[prop_or_default]
pub link: Option<CodeEditorLink>,
#[prop_or_default]
pub options: Option<OPT>,
#[prop_or_default]
pub model: Option<TextModel>,
#[prop_or_default]
pub on_editor_created: Callback<CodeEditorLink>,
#[prop_or_default]
pub classes: Classes,
}
type ModelCell = Rc<RefCell<Option<CodeEditorModel>>>;
#[derive(Debug)]
pub struct CodeEditor {
node_ref: NodeRef,
editor: ModelCell,
}
impl CodeEditor {
fn emit_editor_created(&self, ctx: &Context<Self>) {
let CodeEditorProps {
link,
on_editor_created,
..
} = &ctx.props();
let link = link
.clone()
.unwrap_or_else(|| CodeEditorLink::new_connected(self.editor.clone()));
on_editor_created.emit(link);
}
}
impl Component for CodeEditor {
type Message = ();
type Properties = CodeEditorProps;
fn create(ctx: &Context<Self>) -> Self {
let editor = ModelCell::default();
if let Some(editor_link) = &ctx.props().link {
editor_link.connect(editor.clone());
}
Self {
node_ref: NodeRef::default(),
editor,
}
}
fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
let CodeEditorProps {
link,
options,
model,
on_editor_created: _,
..
} = &ctx.props();
let mut should_render = false;
if link != &old_props.link {
if let Some(link) = &link {
link.connect(self.editor.clone());
}
}
if options != &old_props.options {
should_render = true;
}
if model != &old_props.model {
if let Some(editor) = &mut *self.editor.borrow_mut() {
match model {
Some(model) => editor.set_model(model),
None => {
editor.detach_model();
}
}
}
}
if should_render {
self.editor.replace(None);
}
should_render
}
fn view(&self, ctx: &Context<Self>) -> Html {
let Self {
node_ref, editor, ..
} = self;
let props = ctx.props();
debug_assert!(
editor.borrow().is_none(),
"previous editor must be disposed before re-creating"
);
html! {
<div ref={node_ref.clone()} class={props.classes.clone()} />
}
}
fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
let el = self
.node_ref
.cast::<HtmlElement>()
.expect("failed to resolve editor element");
let props = ctx.props();
let editor = CodeEditorModel::create(&el, props.options.clone());
if let Some(model) = &props.model {
editor.set_model(model)
}
self.editor.replace(Some(editor));
self.emit_editor_created(ctx);
}
}
#[derive(Clone, Debug, Default)]
pub struct CodeEditorLink(RefCell<ModelCell>);
impl CodeEditorLink {
pub fn new() -> Self {
Self(RefCell::default())
}
fn new_connected(model_cell: ModelCell) -> Self {
Self(RefCell::new(model_cell))
}
fn connect(&self, model_cell: ModelCell) {
self.0.replace(model_cell);
}
pub fn with_editor<T>(&self, f: impl FnOnce(&CodeEditorModel) -> T) -> Option<T> {
self.0.borrow().borrow().as_ref().map(f)
}
}
impl PartialEq for CodeEditorLink {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0.borrow(), &other.0.borrow())
}
}