Initial setup

This commit is contained in:
Condorra 2024-07-21 17:01:56 +10:00
commit 1fbcfefe1e
9 changed files with 1448 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
dist
target
node_modules

1218
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "worldwideportal"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
im = "15.1.0"
wasm-bindgen = "0.2.92"
web-sys = "0.3.69"
yew = { version = "0.21.0", features = ["csr"] }

10
index.html Normal file
View File

@ -0,0 +1,10 @@
<!doctype html>
<html lang="en">
<head>
<link data-trunk rel="css" href="node_modules/@xterm/xterm/css/xterm.css"/>
<script data-trunk src="node_modules/@xterm/xterm/lib/xterm.js"></script>
<script data-trunk src="node_modules/@xterm/addon-fit/lib/addon-fit.js"></script>
<link data-trunk rel="css" href="styles.css"/>
</head>
<body></body>
</html>

30
package-lock.json generated Normal file
View File

@ -0,0 +1,30 @@
{
"name": "yew-test",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "yew-test",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0"
}
},
"node_modules/@xterm/addon-fit": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz",
"integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==",
"peerDependencies": {
"@xterm/xterm": "^5.0.0"
}
},
"node_modules/@xterm/xterm": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A=="
}
}
}

15
package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "yew-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0"
}
}

30
src/main.rs Normal file
View File

@ -0,0 +1,30 @@
use std::rc::Rc;
use yew::prelude::*;
pub mod term_view;
use crate::term_view::*;
#[function_component(App)]
fn app() -> Html {
let frames_handle: UseStateHandle<Rc<RegisteredTermFrames>> =
use_state(|| RegisteredTermFrames::new().into());
let frames = RegisteredTermFrameLens {
get: (*frames_handle).clone(),
set: Callback::from(move |s| frames_handle.set(s)),
};
html! {
<div class="vpane toplevel">
<TermView terminal={TermFrame(0)} frames={frames.clone()}/>
<TermView terminal={TermFrame(1)} frames={frames.clone()}/>
<div class="hpane">
<TermView terminal={TermFrame(2)} frames={frames.clone()}/>
<TermView terminal={TermFrame(3)} {frames}/>
</div>
</div>
}
}
fn main() {
yew::Renderer::<App>::new().render();
}

97
src/term_view.rs Normal file
View File

@ -0,0 +1,97 @@
use std::rc::Rc;
use im::hashmap::*;
use wasm_bindgen::prelude::*;
use web_sys::{Element, Node};
use yew::prelude::*;
#[wasm_bindgen]
extern "C" {
#[derive(PartialEq)]
pub type Terminal;
#[wasm_bindgen(constructor)]
fn new() -> Terminal;
#[wasm_bindgen(method)]
fn open(this: &Terminal, element: &Element);
#[wasm_bindgen(method)]
fn write(this: &Terminal, data: &str);
// Todo: Can we do this with interfaces somehow?
#[wasm_bindgen(method)]
fn loadAddon(this: &Terminal, addon: &FitAddon);
#[derive(PartialEq)]
pub type FitAddon;
}
#[wasm_bindgen(js_namespace=FitAddon)]
extern "C" {
#[wasm_bindgen(constructor)]
fn new() -> FitAddon;
#[wasm_bindgen(method)]
fn fit(this: &FitAddon);
}
#[derive(Eq, Ord, Hash, PartialEq, PartialOrd, Clone)]
pub struct TermFrame(pub u64);
#[derive(Properties, PartialEq)]
pub struct TermFrameData {
pub id: TermFrame,
pub term: Terminal,
pub fit: FitAddon,
pub node: Node,
}
pub type RegisteredTermFrames = HashMap<TermFrame, Rc<TermFrameData>>;
#[derive(Properties, PartialEq, Clone)]
pub struct RegisteredTermFrameLens {
pub get: Rc<RegisteredTermFrames>,
pub set: Callback<Rc<RegisteredTermFrames>, ()>,
}
fn get_or_make_term_frame(
frame: &TermFrame,
frames: &RegisteredTermFrameLens,
) -> Rc<TermFrameData> {
if let Some(tfd) = frames.get.get(frame) {
return tfd.clone();
}
let mut new_frames: RegisteredTermFrames = (*frames.get).clone();
let term = Terminal::new();
let fit = FitAddon::new();
let element = web_sys::window()
.and_then(|w| w.document())
.and_then(|d| d.create_element("div").ok())
.expect("Can create element for term");
element.set_class_name("hterminal");
term.open(&element);
term.loadAddon(&fit);
for i in 0..100 {
term.write(&format!("{} Hello world\r\n", i));
}
let new_data: Rc<TermFrameData> = TermFrameData {
id: frame.clone(),
term,
fit,
node: element.into(),
}
.into();
new_frames.insert(frame.clone(), new_data.clone());
frames.set.emit(new_frames.into());
new_data
}
#[derive(Properties, PartialEq)]
pub struct TermViewProps {
pub terminal: TermFrame,
pub frames: RegisteredTermFrameLens,
}
#[function_component(TermView)]
pub fn term_view(props: &TermViewProps) -> Html {
let term = get_or_make_term_frame(&props.terminal, &props.frames);
term.fit.fit();
html! {
{Html::VRef(term.node.clone())}
}
}

33
styles.css Normal file
View File

@ -0,0 +1,33 @@
html {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
overflow: hidden;
}
body {
border: solid black 10px;
margin: 0px;
background-color: black;
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
}
.vpane {
display: flex;
flex-direction: column;
justify-content: stretch;
}
.hpane {
display: flex;
flex-direction: row;
justify-content: stretch;
}
.toplevel {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
}
.hterminal {
flex: 1 1 0;
min-width: 10px;
min-height: 10px;
border: solid grey 1px;
padding: 2px;
}