Skip to content

Commit

Permalink
feat: work towards CHSET list
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiphoseer committed Jan 5, 2025
1 parent 6dedb38 commit 2ff9482
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 75 deletions.
7 changes: 5 additions & 2 deletions crates/sdo-web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,13 @@ <h5 class="offcanvas-title" id="offcanvasLabel">Menu</h5>
<div class="offcanvas-body">
<ul class="navbar-nav justify-content-end flex-grow-1 pe-3">
<li class="nav-item">
<a class="nav-link" id="open" href="#">Open</a>
<a class="nav-link" id="open" href="#/CHSETS/">Font Collection</a>
</li>
<li class="nav-item">
<!-- li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li-->
<li class="nav-item">
<a class="nav-link" href="https://sdo.dseiler.eu">Signum! Document Toolbox</a>
</li>
</ul>
</div>
Expand Down
42 changes: 42 additions & 0 deletions crates/sdo-web/src/glue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use js_sys::{ArrayBuffer, Function, Reflect, Symbol, Uint8Array};
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use web_sys::FileSystemFileHandle;

pub(crate) async fn fs_file_handle_get_file(
file_handle: &FileSystemFileHandle,
) -> Result<web_sys::File, JsValue> {
let file = JsFuture::from(file_handle.get_file())
.await?
.unchecked_into::<web_sys::File>();
Ok(file)
}

pub(crate) async fn js_file_data(file: &web_sys::File) -> Result<Uint8Array, JsValue> {
let buf = js_file_array_buffer(file).await?;
Ok(Uint8Array::new(&buf))
}

pub(crate) async fn js_file_array_buffer(file: &web_sys::File) -> Result<ArrayBuffer, JsValue> {
let array_buffer = JsFuture::from(file.array_buffer())
.await?
.unchecked_into::<ArrayBuffer>();
Ok(array_buffer)
}

pub(crate) fn try_iter_async(val: &JsValue) -> Result<Option<js_sys::AsyncIterator>, JsValue> {
let async_iter_sym = Symbol::async_iterator();
let iter_fn = Reflect::get(val, async_iter_sym.as_ref())?;

let iter_fn: Function = match iter_fn.dyn_into() {
Ok(iter_fn) => iter_fn,
Err(_) => return Ok(None),
};

let it: js_sys::AsyncIterator = match iter_fn.call0(val)?.dyn_into() {
Ok(it) => it,
Err(_) => return Ok(None),
};

Ok(Some(it))
}
64 changes: 37 additions & 27 deletions crates/sdo-web/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#![allow(non_snake_case)] // wasm_bindgen macro

use bstr::BStr;
use glue::js_file_data;
use image::ImageOutputFormat;
use js_sys::{Array, ArrayBuffer, JsString, Uint8Array};
use js_sys::{Array, JsString, Uint8Array};
use log::{info, warn, Level};
use sdo_util::keymap::{KB_DRAW, NP_DRAW};
use signum::{
chsets::{
cache::{ChsetCache, DocumentFontCacheInfo},
cache::{AsyncIterator, ChsetCache, DocumentFontCacheInfo, VfsDirEntry, VFS},
editor::{parse_eset, ESet},
encoding::decode_atari_str,
printer::parse_ps24,
Expand All @@ -22,7 +23,7 @@ use signum::{
raster::{self, render_doc_page, render_editor_text, Page},
util::FourCC,
};
use std::{fmt::Write, io::Cursor};
use std::{ffi::OsStr, fmt::Write, io::Cursor};
use vfs::OriginPrivateFS;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
Expand All @@ -32,6 +33,7 @@ use web_sys::{
HtmlCanvasElement, HtmlElement, HtmlImageElement, HtmlInputElement, ImageBitmap, Url,
};

mod glue;
mod vfs;

/*
Expand Down Expand Up @@ -94,13 +96,6 @@ fn js_four_cc(arr: &Uint8Array) -> Option<FourCC> {
}
}

async fn js_file_data(file: &web_sys::File) -> Result<Uint8Array, JsValue> {
let buf = JsFuture::from(file.array_buffer())
.await?
.unchecked_into::<ArrayBuffer>();
Ok(Uint8Array::new(&buf))
}

pub struct ActiveDocument {
sdoc: SDoc<'static>,
dfci: DocumentFontCacheInfo,
Expand Down Expand Up @@ -472,16 +467,9 @@ impl Handle {
&self.fc,
dfci,
);
//let list_item = self.document.create_element("div")?;
//list_item.class_list().add_1("list-group-item")?;

let blob = self.page_as_blob(&page)?;
Ok(blob)
//let img = Self::blob_image_el(&blob)?;
//img.class_list().add_1("container-fluid")?;
//list_item.append_child(&img)?;

//self.output.append_child(&list_item)?;
} else {
warn!("Missing page {index}");
Err("Missing page in pbuf".into())
Expand Down Expand Up @@ -544,6 +532,24 @@ impl Handle {
}
} else if matches!(fragment, "" | "#" | "#/") {
self.on_change().await?;
} else if matches!(fragment, "#/CHSETS/") {
self.list_chsets().await?;
}
Ok(())
}

async fn list_chsets(&mut self) -> Result<(), JsValue> {
let mut iter = self.fs.read_dir(OriginPrivateFS::chsets_path()).await?;
while let Some(next) = iter.next().await {
let entry = next?;
if self.fs.is_file_entry(&entry) {
let path = entry.path();
let name = path.file_name().map(OsStr::to_string_lossy);
let _name = name.as_deref().unwrap_or("");

let file = self.fs.open_dir_entry(&entry).await?;
let _data = js_file_data(&file).await?;
}
}
Ok(())
}
Expand All @@ -553,25 +559,29 @@ impl Handle {
self.reset()?;
for file in self.input_files()? {
let file = file?;
let arr = JsFuture::from(file.array_buffer())
.await?
.unchecked_into::<ArrayBuffer>();
self.stage(&file.name(), Uint8Array::new(&arr)).await?;
let arr = js_file_data(&file).await?;
self.stage(&file.name(), arr).await?;
}
Ok(())
}

fn card(&self, name: &str, four_cc: FourCC, href: &str) -> Result<Element, JsValue> {
let card = self.document.create_element("a")?;
let kind = decode_atari_str(&four_cc);
card.class_list()
.add_3("list-group-item", "list-group-item-action", kind.as_ref())?;
card.set_attribute("href", href)?;
self.card_body(&card, name, four_cc)?;
Ok(card)
}

async fn stage(&mut self, name: &str, arr: Uint8Array) -> Result<(), JsValue> {
let data = arr.to_vec();
info!("Parsing file '{}'", name);

if let Ok((_, four_cc)) = four_cc(&data) {
let card = self.document.create_element("a")?;
let kind = decode_atari_str(&FourCC::SDOC);
card.class_list()
.add_3("list-group-item", "list-group-item-action", kind.as_ref())?;
card.set_attribute("href", &format!("#/staged/{name}"))?;
self.card_body(&card, name, four_cc)?;
let href = format!("#/staged/{name}");
let card = self.card(name, four_cc, &href)?;
match four_cc {
FourCC::SDOC => {
let doc = self.parse_sdoc(&data)?;
Expand Down
108 changes: 72 additions & 36 deletions crates/sdo-web/src/vfs.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use core::fmt;
use js_sys::{Function, Object, Reflect, Symbol};
use signum::chsets::cache::{AsyncIterator, VFS};
use js_sys::Object;
use signum::chsets::cache::{AsyncIterator, VfsDirEntry, VFS};
use std::{
borrow::Cow,
future::Future,
path::{Path, PathBuf},
};
Expand All @@ -12,6 +13,8 @@ use web_sys::{
FileSystemHandle, FileSystemHandleKind, StorageManager,
};

use crate::glue::{fs_file_handle_get_file, js_file_data, try_iter_async};

/// Browser Origin Private File System
pub struct OriginPrivateFS {
storage: StorageManager,
Expand All @@ -27,16 +30,26 @@ impl OriginPrivateFS {
Ok(root)
}

pub fn chsets_path() -> &'static Path {
Path::new("CHSETS")
}

pub async fn chset_dir(&self) -> Result<FileSystemDirectoryHandle, JsValue> {
let root = self.root_dir()?;
let dir = resolve_dir(root, Path::new("CHSETS"), true).await?;
let dir = resolve_dir(root, Self::chsets_path(), true).await?;
Ok(dir)
}
}

#[derive(Debug)]
pub struct Error(pub JsValue);

impl From<Error> for JsValue {
fn from(value: Error) -> Self {
value.0
}
}

impl From<js_sys::Error> for Error {
fn from(value: js_sys::Error) -> Self {
Self(value.into())
Expand Down Expand Up @@ -80,7 +93,26 @@ impl AsyncIterator for DirIter {
}
}

pub struct DirEntry(pub FileSystemHandle, pub PathBuf);
pub struct Directory {
inner: FileSystemDirectoryHandle,
path: PathBuf,
}

impl Directory {
pub async fn read_dir(&self) -> Result<DirIter, Error> {
let iter =
try_iter_async(&self.inner)?.ok_or_else(|| JsError::new("Not async iterable"))?;
Ok(DirIter(iter, self.path.clone()))
}
}

pub struct DirEntry(FileSystemHandle, PathBuf);

impl VfsDirEntry for DirEntry {
fn path(&self) -> std::borrow::Cow<'_, Path> {
Cow::Borrowed(&self.1)
}
}

impl fmt::Display for DirEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Expand Down Expand Up @@ -148,6 +180,8 @@ impl VFS for OriginPrivateFS {

type DirEntry = DirEntry;

type File = web_sys::File;

fn root(&self) -> impl Future<Output = PathBuf> + 'static {
std::future::ready(PathBuf::from(self.root.as_deref().unwrap().name()))
}
Expand All @@ -160,6 +194,10 @@ impl VFS for OriginPrivateFS {
.unwrap_or(false)
}

fn is_file_entry(&self, entry: &Self::DirEntry) -> bool {
entry.0.kind() == FileSystemHandleKind::File
}

async fn is_dir(&self, path: &Path) -> bool {
let root = self.root.as_ref().expect("Uninitialized OPFS");
resolve_dir(root, path, false)
Expand All @@ -168,47 +206,36 @@ impl VFS for OriginPrivateFS {
.unwrap_or(false)
}

async fn read_dir(&self, path: &Path) -> Result<Self::DirIter, Self::Error> {
let root = self.root.as_ref().expect("Uninitialized OPFS");
let dir = resolve_dir(root, path, false).await?;
let iter =
try_iter_async(dir.as_ref())?.ok_or_else(|| JsError::new("Not async iterable"))?;
Ok(DirIter(iter, path.to_owned()))
fn is_dir_entry(&self, entry: &Self::DirEntry) -> bool {
entry.0.kind() == FileSystemHandleKind::Directory
}

fn dir_entry_path(&self, entry: &Self::DirEntry) -> PathBuf {
entry.1.clone()
async fn read_dir(&self, path: &Path) -> Result<Self::DirIter, Self::Error> {
let dir = self.directory(path).await?;
dir.read_dir().await
}

async fn read(&self, path: &Path) -> Result<Vec<u8>, Self::Error> {
async fn open(&self, path: &Path) -> Result<Self::File, Self::Error> {
let root = self.root_dir()?;
let file_handle = resolve_file(root, path).await?;
let file = JsFuture::from(file_handle.get_file())
.await?
.unchecked_into::<web_sys::File>();
let array_buffer = JsFuture::from(file.array_buffer())
.await?
.unchecked_into::<js_sys::ArrayBuffer>();
let uint8_buf = js_sys::Uint8Array::new(&array_buffer);
Ok(uint8_buf.to_vec())
let file = fs_file_handle_get_file(&file_handle).await?;
Ok(file)
}
}

pub fn try_iter_async(val: &JsValue) -> Result<Option<js_sys::AsyncIterator>, JsValue> {
let async_iter_sym = Symbol::async_iterator();
let iter_fn = Reflect::get(val, async_iter_sym.as_ref())?;

let iter_fn: Function = match iter_fn.dyn_into() {
Ok(iter_fn) => iter_fn,
Err(_) => return Ok(None),
};

let it: js_sys::AsyncIterator = match iter_fn.call0(val)?.dyn_into() {
Ok(it) => it,
Err(_) => return Ok(None),
};
async fn read(&self, path: &Path) -> Result<Vec<u8>, Self::Error> {
let file = self.open(path).await?;
let uint8_buf = js_file_data(&file).await?;
Ok(uint8_buf.to_vec())
}

Ok(Some(it))
async fn open_dir_entry(&self, dir_entry: &Self::DirEntry) -> Result<Self::File, Self::Error> {
let file_handle = dir_entry
.0
.dyn_ref::<FileSystemFileHandle>()
.ok_or_else(|| JsError::new("not a file"))?;
let file = fs_file_handle_get_file(file_handle).await?;
Ok(file)
}
}

impl OriginPrivateFS {
Expand All @@ -223,6 +250,15 @@ impl OriginPrivateFS {
}
}

async fn directory(&self, path: &Path) -> Result<Directory, Error> {
let root = self.root.as_ref().expect("Uninitialized OPFS");
let inner = resolve_dir(root, path, false).await?;
Ok(Directory {
inner,
path: path.to_owned(),
})
}

pub async fn init(&mut self) -> Result<(), JsValue> {
let dir_handle = FileSystemDirectoryHandle::unchecked_from_js(
JsFuture::from(self.storage.get_directory()).await?,
Expand Down
Loading

0 comments on commit 2ff9482

Please sign in to comment.