use async_trait::async_trait;
use noosphere_core::data::Header;
use noosphere_into::{
file_to_html_stream, HtmlOutput, ResolvedLink, Resolver, SphereContentTranscluder, Transform,
};
use std::{pin::Pin, rc::Rc, sync::Arc};
use subtext::Slashlink;
use tokio_stream::StreamExt;
use anyhow::{anyhow, Result};
use js_sys::{Function, Promise, Uint8Array};
use noosphere_core::context::{
AsyncFileBody, HasSphereContext, SphereContext, SphereCursor, SphereFile as SphereFileImpl,
};
use tokio::{io::AsyncReadExt, sync::Mutex};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use crate::platform::PlatformStorage;
#[wasm_bindgen]
pub struct SphereFile {
#[wasm_bindgen(skip)]
pub inner: SphereFileImpl<Pin<Box<dyn AsyncFileBody>>>,
#[wasm_bindgen(skip)]
pub cursor: SphereCursor<Arc<Mutex<SphereContext<PlatformStorage>>>, PlatformStorage>,
}
#[wasm_bindgen]
impl SphereFile {
#[wasm_bindgen(js_name = "sphereIdentity")]
pub fn sphere_identity(&self) -> String {
self.inner.sphere_identity.to_string()
}
#[wasm_bindgen(js_name = "sphereVersion")]
pub fn sphere_version(&self) -> String {
self.inner.sphere_version.to_string()
}
#[wasm_bindgen(js_name = "memoVersion")]
pub fn memo_version(&self) -> String {
self.inner.memo_version.to_string()
}
#[wasm_bindgen(js_name = "contentType")]
pub fn content_type(&self) -> Option<String> {
self.inner
.memo
.get_first_header(&Header::ContentType.to_string())
}
#[wasm_bindgen(js_name = "getFirstHeader")]
pub fn get_first_header(&self, name: String) -> Option<String> {
self.inner.memo.get_first_header(&name)
}
#[wasm_bindgen(js_name = "intoText")]
pub async fn into_text(mut self) -> Result<String, String> {
let mut contents = String::new();
self.inner
.contents
.read_to_string(&mut contents)
.await
.map_err(|error| format!("{:?}", error))?;
Ok(contents)
}
#[wasm_bindgen(js_name = "intoHtml")]
pub async fn into_html(self, resolver: Function) -> Result<String, String> {
let transform = JavaScriptTransform::new(resolver, self.cursor.clone());
Ok(
file_to_html_stream(self.inner, HtmlOutput::Fragment, transform)
.collect()
.await,
)
}
#[wasm_bindgen(js_name = "intoBytes")]
pub async fn into_bytes(mut self) -> Result<Uint8Array, String> {
let mut bytes = Vec::new();
self.inner
.contents
.read_to_end(&mut bytes)
.await
.map_err(|error| format!("{:?}", error))?;
let js_array = Uint8Array::new(&bytes.len().into());
js_array.copy_from(&bytes);
Ok(js_array)
}
}
#[derive(Clone)]
pub struct JavaScriptTransform<R: HasSphereContext<PlatformStorage>> {
resolver: JavaScriptResolver,
transcluder: SphereContentTranscluder<R, PlatformStorage>,
}
impl<R> JavaScriptTransform<R>
where
R: HasSphereContext<PlatformStorage>,
{
pub fn new(callback: Function, context: R) -> Self {
JavaScriptTransform {
resolver: JavaScriptResolver::new(callback),
transcluder: SphereContentTranscluder::new(context),
}
}
}
impl<R> Transform for JavaScriptTransform<R>
where
R: HasSphereContext<PlatformStorage>,
{
type Resolver = JavaScriptResolver;
type Transcluder = SphereContentTranscluder<R, PlatformStorage>;
fn resolver(&self) -> &Self::Resolver {
&self.resolver
}
fn transcluder(&self) -> &Self::Transcluder {
&self.transcluder
}
}
#[derive(Clone)]
pub struct JavaScriptResolver {
callback: Rc<Function>,
}
impl JavaScriptResolver {
pub fn new(callback: Function) -> Self {
JavaScriptResolver {
callback: Rc::new(callback),
}
}
}
#[async_trait(?Send)]
impl Resolver for JavaScriptResolver {
async fn resolve(&self, link: &Slashlink) -> Result<ResolvedLink> {
let this = JsValue::null();
let arg1 = JsValue::from(link.to_string());
let result = Promise::resolve(
&self
.callback
.call1(&this, &arg1)
.map_err(|error| anyhow!("{:?}", error))?,
);
let resolver_resolves = JsFuture::from(result);
let resolved_value = resolver_resolves
.await
.map_err(|error| anyhow!("{:?}", error))?;
if let Some(href) = resolved_value.as_string() {
Ok(ResolvedLink::Slashlink {
link: link.clone(),
href,
})
} else {
Err(anyhow!("Expected a string, but got {:?}", resolved_value))
}
}
}