Move scroll animations to Rust/WASM, enable Hasura, and move bookmarks to home sidebar
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 12m7s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 12m7s
Port AutoScroll and Headline scroll logic from Vue/JS to Rust compiled to WASM via wasm-pack. Add multi-stage Docker build for WASM compilation, Vite WASM plugins, and top-level await for WASM init. Enable Hasura service in docker-compose. Move bookmarks from a separate route to an inline sidebar component on the home page. Fix ToggleHeader click propagation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -17,6 +17,9 @@ gitea-runner/nohup.out
|
|||||||
# Rust build artifacts
|
# Rust build artifacts
|
||||||
**/target/
|
**/target/
|
||||||
|
|
||||||
|
# Generated WASM output
|
||||||
|
vue/src/wasm/
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ services:
|
|||||||
- backend
|
- backend
|
||||||
- icecast2
|
- icecast2
|
||||||
- gitea
|
- gitea
|
||||||
|
- hasura
|
||||||
- quartz
|
- quartz
|
||||||
- searxng
|
- searxng
|
||||||
networks:
|
networks:
|
||||||
@@ -112,8 +113,6 @@ services:
|
|||||||
image: hasura/graphql-engine:v2.44.0
|
image: hasura/graphql-engine:v2.44.0
|
||||||
container_name: "${HASURA_HOST}"
|
container_name: "${HASURA_HOST}"
|
||||||
restart: always
|
restart: always
|
||||||
profiles:
|
|
||||||
- disabled
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
|
# Stage 1: Build WASM from Rust
|
||||||
|
FROM rust:slim AS wasm-builder
|
||||||
|
RUN rustup target add wasm32-unknown-unknown \
|
||||||
|
&& cargo install wasm-pack
|
||||||
|
WORKDIR /wasm
|
||||||
|
COPY crates/stp_wasm/ crates/stp_wasm/
|
||||||
|
RUN wasm-pack build crates/stp_wasm --target web --out-dir ../../src/wasm
|
||||||
|
|
||||||
|
# Stage 2: Build Vue frontend
|
||||||
FROM node:22-slim
|
FROM node:22-slim
|
||||||
RUN apt-get update && apt-get install -y make git && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y make git && rm -rf /var/lib/apt/lists/*
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
COPY . .
|
COPY . .
|
||||||
CMD ["sh", "-c", "npm run build -- --outDir /output --emptyOutDir"]
|
COPY --from=wasm-builder /wasm/src/wasm/ src/wasm/
|
||||||
|
CMD ["sh", "-c", "npx vite build --outDir /output --emptyOutDir"]
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ name = "stp_wasm"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
js-sys = "0.3.85"
|
js-sys = "0.3.85"
|
||||||
wasm-bindgen = "0.2.108"
|
wasm-bindgen = "0.2.108"
|
||||||
@@ -10,6 +13,13 @@ web-sys = { version = "0.3.85", features = [
|
|||||||
"console",
|
"console",
|
||||||
"Document",
|
"Document",
|
||||||
"Element",
|
"Element",
|
||||||
|
"HtmlElement",
|
||||||
"Window",
|
"Window",
|
||||||
"Animation",
|
"Animation",
|
||||||
|
"CssStyleDeclaration",
|
||||||
|
"ResizeObserver",
|
||||||
|
"ResizeObserverEntry",
|
||||||
|
"ResizeObserverSize",
|
||||||
|
"EventTarget",
|
||||||
|
"MouseEvent",
|
||||||
] }
|
] }
|
||||||
|
|||||||
259
vue/crates/stp_wasm/src/auto_scroll.rs
Normal file
259
vue/crates/stp_wasm/src/auto_scroll.rs
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use web_sys::HtmlElement;
|
||||||
|
|
||||||
|
const SPEED: f64 = 0.0005; // % per frame
|
||||||
|
const PAUSE: i32 = 2000; // ms at top/bottom
|
||||||
|
|
||||||
|
struct Inner {
|
||||||
|
el: HtmlElement,
|
||||||
|
pos: f64,
|
||||||
|
direction: f64, // 1.0 = down, -1.0 = up
|
||||||
|
hovered: bool,
|
||||||
|
cached_scroll_height: f64,
|
||||||
|
raf_id: Option<i32>,
|
||||||
|
pause_timeout_id: Option<i32>,
|
||||||
|
resize_observer: Option<web_sys::ResizeObserver>,
|
||||||
|
// closures kept alive
|
||||||
|
tick_closure: Option<Closure<dyn FnMut()>>,
|
||||||
|
resize_closure: Option<Closure<dyn FnMut(js_sys::Array)>>,
|
||||||
|
mouseenter_closure: Option<Closure<dyn FnMut()>>,
|
||||||
|
mouseleave_closure: Option<Closure<dyn FnMut()>>,
|
||||||
|
start_after_pause_closure: Option<Closure<dyn FnMut()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct AutoScroller {
|
||||||
|
inner: Rc<RefCell<Inner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Inner {
|
||||||
|
fn measure_scroll_height(&mut self) {
|
||||||
|
self.cached_scroll_height = self.el.scroll_height() as f64;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop_loop(&mut self) {
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
if let Some(id) = self.raf_id.take() {
|
||||||
|
window.cancel_animation_frame(id).ok();
|
||||||
|
}
|
||||||
|
if let Some(id) = self.pause_timeout_id.take() {
|
||||||
|
window.clear_timeout_with_handle(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_loop(inner: &Rc<RefCell<Inner>>) {
|
||||||
|
{
|
||||||
|
let mut s = inner.borrow_mut();
|
||||||
|
s.stop_loop();
|
||||||
|
}
|
||||||
|
schedule_tick(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schedule_tick(inner: &Rc<RefCell<Inner>>) {
|
||||||
|
let inner_clone = Rc::clone(inner);
|
||||||
|
|
||||||
|
// Create a fresh tick closure each frame
|
||||||
|
let closure = Closure::once(move || {
|
||||||
|
tick(&inner_clone);
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut s = inner.borrow_mut();
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let id = window
|
||||||
|
.request_animation_frame(closure.as_ref().unchecked_ref())
|
||||||
|
.unwrap();
|
||||||
|
s.raf_id = Some(id);
|
||||||
|
// Store the closure to keep it alive until the frame fires
|
||||||
|
s.tick_closure = Some(closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick(inner: &Rc<RefCell<Inner>>) {
|
||||||
|
let should_continue;
|
||||||
|
{
|
||||||
|
let mut s = inner.borrow_mut();
|
||||||
|
s.raf_id = None;
|
||||||
|
|
||||||
|
if s.hovered {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.cached_scroll_height == 0.0 {
|
||||||
|
drop(s);
|
||||||
|
schedule_tick(inner);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reached_bottom = s.pos >= 1.0;
|
||||||
|
let reached_top = s.pos <= 0.0;
|
||||||
|
|
||||||
|
if reached_bottom {
|
||||||
|
s.pos = 0.999;
|
||||||
|
s.direction = -1.0;
|
||||||
|
drop(s);
|
||||||
|
schedule_pause(inner);
|
||||||
|
return;
|
||||||
|
} else if reached_top && s.direction == -1.0 {
|
||||||
|
s.pos = 0.001;
|
||||||
|
s.direction = 1.0;
|
||||||
|
drop(s);
|
||||||
|
schedule_pause(inner);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s.pos += s.direction * SPEED;
|
||||||
|
s.el.set_scroll_top((s.pos * s.cached_scroll_height) as i32);
|
||||||
|
should_continue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if should_continue {
|
||||||
|
schedule_tick(inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schedule_pause(inner: &Rc<RefCell<Inner>>) {
|
||||||
|
{
|
||||||
|
let mut s = inner.borrow_mut();
|
||||||
|
s.stop_loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
let inner_clone = Rc::clone(inner);
|
||||||
|
let closure = Closure::once(move || {
|
||||||
|
start_loop(&inner_clone);
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut s = inner.borrow_mut();
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let id = window
|
||||||
|
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||||
|
closure.as_ref().unchecked_ref(),
|
||||||
|
PAUSE,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
s.pause_timeout_id = Some(id);
|
||||||
|
s.start_after_pause_closure = Some(closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl AutoScroller {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(el: HtmlElement) -> AutoScroller {
|
||||||
|
let inner = Rc::new(RefCell::new(Inner {
|
||||||
|
el,
|
||||||
|
pos: 0.0,
|
||||||
|
direction: 1.0,
|
||||||
|
hovered: false,
|
||||||
|
cached_scroll_height: 0.0,
|
||||||
|
raf_id: None,
|
||||||
|
pause_timeout_id: None,
|
||||||
|
resize_observer: None,
|
||||||
|
tick_closure: None,
|
||||||
|
resize_closure: None,
|
||||||
|
mouseenter_closure: None,
|
||||||
|
mouseleave_closure: None,
|
||||||
|
start_after_pause_closure: None,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Set up mouseenter listener
|
||||||
|
{
|
||||||
|
let inner_clone = Rc::clone(&inner);
|
||||||
|
let closure = Closure::wrap(Box::new(move || {
|
||||||
|
let mut s = inner_clone.borrow_mut();
|
||||||
|
s.hovered = true;
|
||||||
|
s.stop_loop();
|
||||||
|
}) as Box<dyn FnMut()>);
|
||||||
|
let s = inner.borrow();
|
||||||
|
s.el
|
||||||
|
.add_event_listener_with_callback("mouseenter", closure.as_ref().unchecked_ref())
|
||||||
|
.unwrap();
|
||||||
|
drop(s);
|
||||||
|
inner.borrow_mut().mouseenter_closure = Some(closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up mouseleave listener
|
||||||
|
{
|
||||||
|
let inner_clone = Rc::clone(&inner);
|
||||||
|
let closure = Closure::wrap(Box::new(move || {
|
||||||
|
{
|
||||||
|
let mut s = inner_clone.borrow_mut();
|
||||||
|
s.hovered = false;
|
||||||
|
if s.cached_scroll_height > 0.0 {
|
||||||
|
s.pos = s.el.scroll_top() as f64 / s.cached_scroll_height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start_loop(&inner_clone);
|
||||||
|
}) as Box<dyn FnMut()>);
|
||||||
|
let s = inner.borrow();
|
||||||
|
s.el
|
||||||
|
.add_event_listener_with_callback("mouseleave", closure.as_ref().unchecked_ref())
|
||||||
|
.unwrap();
|
||||||
|
drop(s);
|
||||||
|
inner.borrow_mut().mouseleave_closure = Some(closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoScroller { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&self) {
|
||||||
|
// Measure initial scroll height
|
||||||
|
self.inner.borrow_mut().measure_scroll_height();
|
||||||
|
|
||||||
|
// Set up resize observer
|
||||||
|
let inner_clone = Rc::clone(&self.inner);
|
||||||
|
let resize_closure = Closure::wrap(Box::new(move |_entries: js_sys::Array| {
|
||||||
|
inner_clone.borrow_mut().measure_scroll_height();
|
||||||
|
}) as Box<dyn FnMut(js_sys::Array)>);
|
||||||
|
|
||||||
|
let observer =
|
||||||
|
web_sys::ResizeObserver::new(resize_closure.as_ref().unchecked_ref()).unwrap();
|
||||||
|
|
||||||
|
// Clone the element ref and drop the borrow before calling observe(),
|
||||||
|
// because observe() can fire the resize callback synchronously,
|
||||||
|
// which would conflict with an active borrow.
|
||||||
|
let el_clone = self.inner.borrow().el.clone();
|
||||||
|
observer.observe(&el_clone);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut s = self.inner.borrow_mut();
|
||||||
|
s.resize_observer = Some(observer);
|
||||||
|
s.resize_closure = Some(resize_closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start with a pause then begin scrolling
|
||||||
|
schedule_pause(&self.inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(&self) {
|
||||||
|
let mut s = self.inner.borrow_mut();
|
||||||
|
s.stop_loop();
|
||||||
|
|
||||||
|
if let Some(observer) = s.resize_observer.take() {
|
||||||
|
observer.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref closure) = s.mouseenter_closure {
|
||||||
|
s.el
|
||||||
|
.remove_event_listener_with_callback(
|
||||||
|
"mouseenter",
|
||||||
|
closure.as_ref().unchecked_ref(),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
if let Some(ref closure) = s.mouseleave_closure {
|
||||||
|
s.el
|
||||||
|
.remove_event_listener_with_callback(
|
||||||
|
"mouseleave",
|
||||||
|
closure.as_ref().unchecked_ref(),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mouseenter_closure = None;
|
||||||
|
s.mouseleave_closure = None;
|
||||||
|
s.resize_closure = None;
|
||||||
|
s.tick_closure = None;
|
||||||
|
s.start_after_pause_closure = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
135
vue/crates/stp_wasm/src/headline.rs
Normal file
135
vue/crates/stp_wasm/src/headline.rs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use web_sys::HtmlElement;
|
||||||
|
|
||||||
|
const SPEED: f64 = 0.5; // pixels per frame
|
||||||
|
|
||||||
|
struct Inner {
|
||||||
|
container: HtmlElement,
|
||||||
|
item1: HtmlElement,
|
||||||
|
offset: f64,
|
||||||
|
cached_width: f64,
|
||||||
|
raf_id: Option<i32>,
|
||||||
|
resize_observer: Option<web_sys::ResizeObserver>,
|
||||||
|
// closures kept alive
|
||||||
|
animate_closure: Option<Closure<dyn FnMut()>>,
|
||||||
|
resize_closure: Option<Closure<dyn FnMut(js_sys::Array)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct HeadlineScroller {
|
||||||
|
inner: Rc<RefCell<Inner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Inner {
|
||||||
|
fn measure_width(&mut self) {
|
||||||
|
let container_width = self.container.offset_width() as f64;
|
||||||
|
let item_width = self.item1.scroll_width() as f64;
|
||||||
|
self.cached_width = container_width.max(item_width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schedule_frame(inner: &Rc<RefCell<Inner>>) {
|
||||||
|
let inner_clone = Rc::clone(inner);
|
||||||
|
|
||||||
|
let closure = Closure::once(move || {
|
||||||
|
animate(&inner_clone);
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut s = inner.borrow_mut();
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let id = window
|
||||||
|
.request_animation_frame(closure.as_ref().unchecked_ref())
|
||||||
|
.unwrap();
|
||||||
|
s.raf_id = Some(id);
|
||||||
|
s.animate_closure = Some(closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn animate(inner: &Rc<RefCell<Inner>>) {
|
||||||
|
{
|
||||||
|
let mut s = inner.borrow_mut();
|
||||||
|
s.raf_id = None;
|
||||||
|
|
||||||
|
if s.cached_width == 0.0 {
|
||||||
|
drop(s);
|
||||||
|
schedule_frame(inner);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s.offset -= SPEED;
|
||||||
|
|
||||||
|
if s.offset <= -s.cached_width {
|
||||||
|
s.offset += s.cached_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
let transform = format!("translateX({}px)", s.offset);
|
||||||
|
s.container.style().set_property("transform", &transform).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
schedule_frame(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl HeadlineScroller {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(container: HtmlElement, item1: HtmlElement) -> HeadlineScroller {
|
||||||
|
let inner = Rc::new(RefCell::new(Inner {
|
||||||
|
container,
|
||||||
|
item1,
|
||||||
|
offset: 0.0,
|
||||||
|
cached_width: 0.0,
|
||||||
|
raf_id: None,
|
||||||
|
resize_observer: None,
|
||||||
|
animate_closure: None,
|
||||||
|
resize_closure: None,
|
||||||
|
}));
|
||||||
|
|
||||||
|
HeadlineScroller { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&self) {
|
||||||
|
// Measure initial width
|
||||||
|
self.inner.borrow_mut().measure_width();
|
||||||
|
|
||||||
|
// Set up resize observer
|
||||||
|
let inner_clone = Rc::clone(&self.inner);
|
||||||
|
let resize_closure = Closure::wrap(Box::new(move |_entries: js_sys::Array| {
|
||||||
|
inner_clone.borrow_mut().measure_width();
|
||||||
|
}) as Box<dyn FnMut(js_sys::Array)>);
|
||||||
|
|
||||||
|
let observer =
|
||||||
|
web_sys::ResizeObserver::new(resize_closure.as_ref().unchecked_ref()).unwrap();
|
||||||
|
|
||||||
|
// Clone the element ref and drop the borrow before calling observe(),
|
||||||
|
// because observe() can fire the resize callback synchronously,
|
||||||
|
// which would conflict with an active borrow.
|
||||||
|
let container_clone = self.inner.borrow().container.clone();
|
||||||
|
observer.observe(&container_clone);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut s = self.inner.borrow_mut();
|
||||||
|
s.resize_observer = Some(observer);
|
||||||
|
s.resize_closure = Some(resize_closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start animation loop
|
||||||
|
schedule_frame(&self.inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(&self) {
|
||||||
|
let mut s = self.inner.borrow_mut();
|
||||||
|
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
if let Some(id) = s.raf_id.take() {
|
||||||
|
window.cancel_animation_frame(id).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(observer) = s.resize_observer.take() {
|
||||||
|
observer.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
s.animate_closure = None;
|
||||||
|
s.resize_closure = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
use wasm_bindgen::prelude::*;
|
mod auto_scroll;
|
||||||
|
mod headline;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
pub use auto_scroll::AutoScroller;
|
||||||
pub struct BadApplePlayer {
|
pub use headline::HeadlineScroller;
|
||||||
is_playing: bool,
|
|
||||||
}
|
|
||||||
|
|||||||
349
vue/package-lock.json
generated
349
vue/package-lock.json
generated
@@ -25,7 +25,9 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"vite": "^7.1.11",
|
"vite": "^7.1.11",
|
||||||
"vite-plugin-vue-devtools": "^8.0.3"
|
"vite-plugin-top-level-await": "^1.6.0",
|
||||||
|
"vite-plugin-vue-devtools": "^8.0.3",
|
||||||
|
"vite-plugin-wasm": "^3.6.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20.19.0 || >=22.12.0"
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
@@ -1024,6 +1026,24 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@rollup/plugin-virtual": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.59.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
|
||||||
@@ -1349,6 +1369,293 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@swc/core": {
|
||||||
|
"version": "1.15.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.24.tgz",
|
||||||
|
"integrity": "sha512-5Hj8aNasue7yusUt8LGCUe/AjM7RMAce8ZoyDyiFwx7Al+GbYKL+yE7g4sJk8vEr1dKIkTRARkNIJENc4CjkBQ==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/counter": "^0.1.3",
|
||||||
|
"@swc/types": "^0.1.26"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/swc"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@swc/core-darwin-arm64": "1.15.24",
|
||||||
|
"@swc/core-darwin-x64": "1.15.24",
|
||||||
|
"@swc/core-linux-arm-gnueabihf": "1.15.24",
|
||||||
|
"@swc/core-linux-arm64-gnu": "1.15.24",
|
||||||
|
"@swc/core-linux-arm64-musl": "1.15.24",
|
||||||
|
"@swc/core-linux-ppc64-gnu": "1.15.24",
|
||||||
|
"@swc/core-linux-s390x-gnu": "1.15.24",
|
||||||
|
"@swc/core-linux-x64-gnu": "1.15.24",
|
||||||
|
"@swc/core-linux-x64-musl": "1.15.24",
|
||||||
|
"@swc/core-win32-arm64-msvc": "1.15.24",
|
||||||
|
"@swc/core-win32-ia32-msvc": "1.15.24",
|
||||||
|
"@swc/core-win32-x64-msvc": "1.15.24"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@swc/helpers": ">=0.5.17"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@swc/helpers": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-darwin-arm64": {
|
||||||
|
"version": "1.15.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.24.tgz",
|
||||||
|
"integrity": "sha512-uM5ZGfFXjtvtJ+fe448PVBEbn/CSxS3UAyLj3O9xOqKIWy3S6hPTXSPbszxkSsGDYKi+YFhzAsR4r/eXLxEQ0g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-darwin-x64": {
|
||||||
|
"version": "1.15.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.24.tgz",
|
||||||
|
"integrity": "sha512-fMIb/Zfn929pw25VMBhV7Ji2Dl+lCWtUPNdYJQYOke+00E5fcQ9ynxtP8+qhUo/HZc+mYQb1gJxwHM9vty+lXg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||||
|
"version": "1.15.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.24.tgz",
|
||||||
|
"integrity": "sha512-vOkjsyjjxnoYx3hMEWcGxQrMgnNrRm6WAegBXrN8foHtDAR+zpdhpGF5a4lj1bNPgXAvmysjui8cM1ov/Clkaw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||||
|
"version": "1.15.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.24.tgz",
|
||||||
|
"integrity": "sha512-h/oNu+upkXJ6Cicnq7YGVj9PkdfarLCdQa8l/FlHYvfv8CEiMaeeTnpLU7gSBH/rGxosM6Qkfa/J9mThGF9CLA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-arm64-musl": {
|
||||||
|
"version": "1.15.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.24.tgz",
|
||||||
|
"integrity": "sha512-ZpF/pRe1guk6sKzQI9D1jAORtjTdNlyeXn9GDz8ophof/w2WhojRblvSDJaGe7rJjcPN8AaOkhwdRUh7q8oYIg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-ppc64-gnu": {
|
||||||
|
"version": "1.15.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.24.tgz",
|
||||||
|
"integrity": "sha512-QZEsZfisHTSJlmyChgDFNmKPb3W6Lhbfo/O76HhIngfEdnQNmukS38/VSe1feho+xkV5A5hETyCbx3sALBZKAQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-s390x-gnu": {
|
||||||
|
"version": "1.15.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.24.tgz",
|
||||||
|
"integrity": "sha512-DLdJKVsJgglqQrJBuoUYNmzm3leI7kUZhLbZGHv42onfKsGf6JDS3+bzCUQfte/XOqDjh/tmmn1DR/CF/tCJFw==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-x64-gnu": {
|
||||||
|
"version": "1.15.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.24.tgz",
|
||||||
|
"integrity": "sha512-IpLYfposPA/XLxYOKpRfeccl1p5dDa3+okZDHHTchBkXEaVCnq5MADPmIWwIYj1tudt7hORsEHccG5no6IUQRw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-x64-musl": {
|
||||||
|
"version": "1.15.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.24.tgz",
|
||||||
|
"integrity": "sha512-JHy3fMSc0t/EPWgo74+OK5TGr51aElnzqfUPaiRf2qJ/BfX5CUCfMiWVBuhI7qmVMBnk1jTRnL/xZnOSHDPLYg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||||
|
"version": "1.15.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.24.tgz",
|
||||||
|
"integrity": "sha512-Txj+qUH1z2bUd1P3JvwByfjKFti3cptlAxhWgmunBUUxy/IW3CXLZ6l6Gk4liANadKkU71nIU1X30Z5vpMT3BA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||||
|
"version": "1.15.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.24.tgz",
|
||||||
|
"integrity": "sha512-15D/nl3XwrhFpMv+MADFOiVwv3FvH9j8c6Rf8EXBT3Q5LoMh8YnDnSgPYqw1JzPnksvsBX6QPXLiPqmcR/Z4qQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-win32-x64-msvc": {
|
||||||
|
"version": "1.15.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.24.tgz",
|
||||||
|
"integrity": "sha512-PR0PlTlPra2JbaDphrOAzm6s0v9rA0F17YzB+XbWD95B4g2cWcZY9LAeTa4xll70VLw9Jr7xBrlohqlQmelMFQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/counter": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@swc/types": {
|
||||||
|
"version": "0.1.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz",
|
||||||
|
"integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/counter": "^0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/wasm": {
|
||||||
|
"version": "1.15.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.15.24.tgz",
|
||||||
|
"integrity": "sha512-vFjzOE8dhJcfeTbM4+HO9Qy58IINV0ysqStAgw81uds+KqCeUDM9huN+SZ5lWZ6U+5nf8VcZoEw5N81xMtAidg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/node": {
|
"node_modules/@tailwindcss/node": {
|
||||||
"version": "4.1.18",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
|
||||||
@@ -3518,6 +3825,20 @@
|
|||||||
"integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==",
|
"integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==",
|
||||||
"license": "(WTFPL OR MIT)"
|
"license": "(WTFPL OR MIT)"
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.3.1",
|
"version": "7.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
||||||
@@ -3661,6 +3982,22 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/vite-plugin-top-level-await": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-bNhUreLamTIkoulCR9aDXbTbhLk6n1YE8NJUTTxl5RYskNRtzOR0ASzSjBVRtNdjIfngDXo11qOsybGLNsrdww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/plugin-virtual": "^3.0.2",
|
||||||
|
"@swc/core": "^1.12.14",
|
||||||
|
"@swc/wasm": "^1.12.14",
|
||||||
|
"uuid": "10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vite": ">=2.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite-plugin-vue-devtools": {
|
"node_modules/vite-plugin-vue-devtools": {
|
||||||
"version": "8.0.5",
|
"version": "8.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-8.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-8.0.5.tgz",
|
||||||
@@ -3736,6 +4073,16 @@
|
|||||||
"vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0"
|
"vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vite-plugin-wasm": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-mL/QPziiIA4RAA6DkaZZzOstdwbW5jO4Vz7Zenj0wieKWBlNvIvX5L5ljum9lcUX0ShNfBgCNLKTjNkRVVqcsw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7 || ^8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue": {
|
"node_modules/vue": {
|
||||||
"version": "3.5.26",
|
"version": "3.5.26",
|
||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz",
|
||||||
|
|||||||
@@ -7,8 +7,9 @@
|
|||||||
"node": "^20.19.0 || >=22.12.0"
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"build:wasm": "wasm-pack build crates/stp_wasm --target web --out-dir ../../src/wasm",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "npm run build:wasm && vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -29,6 +30,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"vite": "^7.1.11",
|
"vite": "^7.1.11",
|
||||||
"vite-plugin-vue-devtools": "^8.0.3"
|
"vite-plugin-top-level-await": "^1.6.0",
|
||||||
|
"vite-plugin-vue-devtools": "^8.0.3",
|
||||||
|
"vite-plugin-wasm": "^3.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +1,22 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, useTemplateRef, onUnmounted } from "vue";
|
import { onMounted, useTemplateRef, onUnmounted } from "vue";
|
||||||
|
import { HeadlineScroller } from "@/wasm/stp_wasm.js";
|
||||||
|
|
||||||
const container = useTemplateRef("container");
|
const container = useTemplateRef("container");
|
||||||
const item1 = useTemplateRef("item1");
|
const item1 = useTemplateRef("item1");
|
||||||
|
|
||||||
let offset = 0;
|
let scroller = null;
|
||||||
let cachedWidth = 0;
|
|
||||||
|
|
||||||
let rafId;
|
|
||||||
|
|
||||||
const speed = 0.5; // pixels per frame
|
|
||||||
|
|
||||||
function measureWidth() {
|
|
||||||
const ctnr = container.value;
|
|
||||||
const it1 = item1.value;
|
|
||||||
if (ctnr && it1) {
|
|
||||||
cachedWidth = Math.max(ctnr.offsetWidth, it1.scrollWidth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function animate() {
|
|
||||||
const ctnr = container.value;
|
|
||||||
if (!ctnr || cachedWidth === 0) {
|
|
||||||
rafId = requestAnimationFrame(animate);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
offset -= speed;
|
|
||||||
|
|
||||||
if (offset <= -cachedWidth) {
|
|
||||||
offset += cachedWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctnr.style.transform = `translateX(${offset}px)`;
|
|
||||||
|
|
||||||
rafId = requestAnimationFrame(animate);
|
|
||||||
}
|
|
||||||
|
|
||||||
let resizeObserver;
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
measureWidth();
|
if (!container.value || !item1.value) return;
|
||||||
rafId = requestAnimationFrame(animate);
|
scroller = new HeadlineScroller(container.value, item1.value);
|
||||||
|
scroller.start();
|
||||||
resizeObserver = new ResizeObserver(measureWidth);
|
|
||||||
resizeObserver.observe(container.value);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
cancelAnimationFrame(rafId);
|
scroller?.destroy();
|
||||||
resizeObserver?.disconnect();
|
scroller?.free();
|
||||||
|
scroller = null;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const handleClick = () => {
|
|||||||
class="pointer-events-none"
|
class="pointer-events-none"
|
||||||
:model-value="props.modelValue"
|
:model-value="props.modelValue"
|
||||||
@update:model-value="updateValue"
|
@update:model-value="updateValue"
|
||||||
|
@click.stop
|
||||||
ref="toggleButtonRef"
|
ref="toggleButtonRef"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,108 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="container" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave" class="overflow-y-auto">
|
<div ref="container" class="overflow-y-auto">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useTemplateRef, onMounted, onBeforeUnmount } from "vue";
|
import { useTemplateRef, onMounted, onBeforeUnmount } from "vue";
|
||||||
|
import { AutoScroller } from "@/wasm/stp_wasm.js";
|
||||||
|
|
||||||
const container = useTemplateRef("container");
|
const container = useTemplateRef("container");
|
||||||
|
|
||||||
const SPEED = 0.0005; // % per frame
|
let scroller = null;
|
||||||
const PAUSE = 2000; // ms at top/bottom
|
|
||||||
|
|
||||||
let pos = 0;
|
|
||||||
let direction = 1; // 1 = down, -1 = up
|
|
||||||
let hovered = false;
|
|
||||||
let rafId = null;
|
|
||||||
let pauseTimeoutId = null;
|
|
||||||
let cachedScrollHeight = 0;
|
|
||||||
|
|
||||||
function measureScrollHeight() {
|
|
||||||
const el = container.value;
|
|
||||||
if (el) cachedScrollHeight = el.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopLoop() {
|
|
||||||
if (rafId !== null) {
|
|
||||||
cancelAnimationFrame(rafId);
|
|
||||||
rafId = null;
|
|
||||||
}
|
|
||||||
if (pauseTimeoutId !== null) {
|
|
||||||
clearTimeout(pauseTimeoutId);
|
|
||||||
pauseTimeoutId = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function startLoop() {
|
|
||||||
stopLoop();
|
|
||||||
rafId = requestAnimationFrame(tick);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMouseEnter() {
|
|
||||||
hovered = true;
|
|
||||||
stopLoop();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMouseLeave() {
|
|
||||||
hovered = false;
|
|
||||||
const el = container.value;
|
|
||||||
if (el && cachedScrollHeight > 0) {
|
|
||||||
pos = el.scrollTop / cachedScrollHeight;
|
|
||||||
}
|
|
||||||
startLoop();
|
|
||||||
}
|
|
||||||
|
|
||||||
function schedulePause(callback) {
|
|
||||||
stopLoop();
|
|
||||||
pauseTimeoutId = setTimeout(callback, PAUSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
function tick() {
|
|
||||||
rafId = null;
|
|
||||||
const el = container.value;
|
|
||||||
if (hovered) return;
|
|
||||||
|
|
||||||
if (!el || cachedScrollHeight === 0) {
|
|
||||||
rafId = requestAnimationFrame(tick);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reachedBottom = pos >= 1;
|
|
||||||
const reachedTop = pos <= 0;
|
|
||||||
|
|
||||||
if (reachedBottom) {
|
|
||||||
pos = 0.999;
|
|
||||||
direction = -1;
|
|
||||||
schedulePause(startLoop);
|
|
||||||
return;
|
|
||||||
} else if (reachedTop && direction === -1) {
|
|
||||||
pos = 0.001;
|
|
||||||
direction = 1;
|
|
||||||
schedulePause(startLoop);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pos += direction * SPEED;
|
|
||||||
|
|
||||||
el.scrollTop = pos * cachedScrollHeight;
|
|
||||||
|
|
||||||
rafId = requestAnimationFrame(tick);
|
|
||||||
}
|
|
||||||
|
|
||||||
let resizeObserver;
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
measureScrollHeight();
|
if (!container.value) return;
|
||||||
schedulePause(startLoop);
|
scroller = new AutoScroller(container.value);
|
||||||
|
scroller.start();
|
||||||
resizeObserver = new ResizeObserver(measureScrollHeight);
|
|
||||||
resizeObserver.observe(container.value);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
stopLoop();
|
scroller?.destroy();
|
||||||
resizeObserver?.disconnect();
|
scroller?.free();
|
||||||
|
scroller = null;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import { createPinia } from "pinia";
|
|||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
import "./assets/styles.css";
|
import "./assets/styles.css";
|
||||||
|
import init from "@/wasm/stp_wasm.js";
|
||||||
|
|
||||||
|
await init();
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
|||||||
@@ -34,11 +34,6 @@ const router = createRouter({
|
|||||||
component: () => import("@/views/admin/Admin.vue"),
|
component: () => import("@/views/admin/Admin.vue"),
|
||||||
meta: { requiresAdmin: true },
|
meta: { requiresAdmin: true },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "bookmarks",
|
|
||||||
name: "bookmarks",
|
|
||||||
component: () => import("@/views/home/bookmarks/Bookmarks.vue"),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "shrines",
|
path: "shrines",
|
||||||
name: "shrine links",
|
name: "shrine links",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import Favorites from "./Favorites.vue";
|
|||||||
import Gym2 from "./Gym2.vue";
|
import Gym2 from "./Gym2.vue";
|
||||||
import Consumption from "./Consumption.vue";
|
import Consumption from "./Consumption.vue";
|
||||||
import Steam from "./Steam.vue";
|
import Steam from "./Steam.vue";
|
||||||
|
import Bookmarks from "./bookmarks/Bookmarks.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -61,6 +62,7 @@ import Steam from "./Steam.vue";
|
|||||||
</div>
|
</div>
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<Steam class="steam-sidebar sidebar-cell" />
|
<Steam class="steam-sidebar sidebar-cell" />
|
||||||
|
<Bookmarks class="bookmarks-sidebar sidebar-cell" />
|
||||||
<Chat
|
<Chat
|
||||||
class="chat-sidebar flex-1 min-h-0 chat-home sidebar-cell"
|
class="chat-sidebar flex-1 min-h-0 chat-home sidebar-cell"
|
||||||
/>
|
/>
|
||||||
@@ -180,7 +182,8 @@ import Steam from "./Steam.vue";
|
|||||||
}
|
}
|
||||||
|
|
||||||
.commits-sidebar,
|
.commits-sidebar,
|
||||||
.steam-sidebar {
|
.steam-sidebar,
|
||||||
|
.bookmarks-sidebar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import LinkTable from "@/components/util/LinkTable.vue";
|
import LinkTable from "@/components/util/LinkTable.vue";
|
||||||
|
import Header from "@/components/text/Header.vue";
|
||||||
import { useHomeDataStore } from "@/stores/homeData";
|
import { useHomeDataStore } from "@/stores/homeData";
|
||||||
|
|
||||||
const homeData = useHomeDataStore();
|
const homeData = useHomeDataStore();
|
||||||
@@ -16,18 +17,30 @@ const groupedBookmarks = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="items-center flex flex-col">
|
<div class="bookmarks-wrapper">
|
||||||
<div
|
<Header class="text-left">Bookmarks</Header>
|
||||||
class="a4page-portrait bdr-1 flex flex-row flex-wrap overflow-x-auto gap-1"
|
<div class="bookmarks-scroll">
|
||||||
>
|
<LinkTable
|
||||||
<div class="w-full h-fit">
|
v-for="group in groupedBookmarks"
|
||||||
<LinkTable
|
:key="group[0]"
|
||||||
class="flex flex-col flex-wrap"
|
:title="group[0]"
|
||||||
v-for="group in groupedBookmarks"
|
:items="group[1]"
|
||||||
:title="group[0]"
|
/>
|
||||||
:items="group[1]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.bookmarks-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmarks-scroll {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { defineConfig } from "vite";
|
|||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import vue from "@vitejs/plugin-vue";
|
import vue from "@vitejs/plugin-vue";
|
||||||
import vueDevTools from "vite-plugin-vue-devtools";
|
import vueDevTools from "vite-plugin-vue-devtools";
|
||||||
|
import wasm from "vite-plugin-wasm";
|
||||||
|
import topLevelAwait from "vite-plugin-top-level-await";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -11,6 +13,8 @@ export default defineConfig({
|
|||||||
vue(),
|
vue(),
|
||||||
...(process.env.NODE_ENV !== "production" ? [vueDevTools()] : []),
|
...(process.env.NODE_ENV !== "production" ? [vueDevTools()] : []),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
|
wasm(),
|
||||||
|
topLevelAwait(),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in New Issue
Block a user