inline js data

This commit is contained in:
Robin Appelman 2023-04-29 17:30:56 +02:00
commit 1e2a3e28e7
8 changed files with 111 additions and 69 deletions

View file

@ -1,6 +1,8 @@
mod script; mod script;
mod style; mod style;
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use fnv::FnvHasher; use fnv::FnvHasher;
pub use script::bundle_script; pub use script::bundle_script;
use std::fs::read; use std::fs::read;
@ -43,3 +45,20 @@ pub fn guess_mime(path: &str) -> &'static str {
} }
return "text/plain"; return "text/plain";
} }
fn inline_url(path: &str) -> String {
let content = read(path).unwrap_or_else(|e| {
eprintln!("Failed to write inline file {path}: {e}");
panic!("Failed to inline");
});
let (mime, encode) = guess_embed(path);
if encode {
let encoded = STANDARD.encode(content);
format!("data:{mime};base64,{encoded}")
} else {
let content = String::from_utf8(content).expect("invalid utf8");
let encoded = urlencoding::encode(&content);
format!("data:{mime},{encoded}")
}
}

View file

@ -0,0 +1,39 @@
use crate::inline_url;
use swc_core::ecma::{
ast::*,
visit::{VisitMut, VisitMutWith},
};
pub struct InlineVisitor {}
impl InlineVisitor {
fn visit_require(&mut self, expr: &mut Expr, path: &str) {
if let Some(path) = path.strip_prefix("inline://") {
let data = inline_url(path);
*expr = Expr::Lit(Lit::Str(data.into()));
}
}
}
impl VisitMut for InlineVisitor {
fn visit_mut_expr(&mut self, expr: &mut Expr) {
if let Expr::Call(CallExpr {
args,
callee: Callee::Expr(callee),
..
}) = expr
{
if let Expr::Ident(callee) = callee.as_ref() {
if callee.sym.as_ref() == "require" {
if let Some(arg) = args.first() {
if let Expr::Lit(Lit::Str(arg)) = arg.expr.as_ref() {
let path = arg.value.to_string();
self.visit_require(expr, &path)
}
}
}
}
}
expr.visit_mut_children_with(self);
}
}

View file

@ -1,3 +1,6 @@
mod inline;
use crate::script::inline::InlineVisitor;
use anyhow::Error; use anyhow::Error;
use jsx_dom_expressions::TransformVisitor; use jsx_dom_expressions::TransformVisitor;
use std::collections::HashMap; use std::collections::HashMap;
@ -153,6 +156,7 @@ impl Load for Loader {
let module = module let module = module
.fold_with(&mut strip(top_level_mark)) .fold_with(&mut strip(top_level_mark))
.fold_with(&mut as_folder(InlineVisitor {}))
.fold_with(&mut as_folder(TransformVisitor::new( .fold_with(&mut as_folder(TransformVisitor::new(
jsx_dom_expressions::config::Config { jsx_dom_expressions::config::Config {
module_name: "solid-js/web".to_string(), module_name: "solid-js/web".to_string(),

View file

@ -1,6 +1,4 @@
use crate::guess_embed; use crate::inline_url;
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use lightningcss::bundler::{Bundler, FileProvider}; use lightningcss::bundler::{Bundler, FileProvider};
use lightningcss::stylesheet::{MinifyOptions, ParserOptions, PrinterOptions}; use lightningcss::stylesheet::{MinifyOptions, ParserOptions, PrinterOptions};
use lightningcss::targets::Browsers; use lightningcss::targets::Browsers;
@ -8,7 +6,6 @@ use lightningcss::values::url::Url;
use lightningcss::visit_types; use lightningcss::visit_types;
use lightningcss::visitor::{Visit, VisitTypes, Visitor}; use lightningcss::visitor::{Visit, VisitTypes, Visitor};
use std::convert::Infallible; use std::convert::Infallible;
use std::fs::read;
use std::path::Path; use std::path::Path;
pub fn bundle_style(style: &str) -> Vec<u8> { pub fn bundle_style(style: &str) -> Vec<u8> {
@ -61,20 +58,7 @@ impl<'i> Visitor<'i> for InlineUrlVisitor {
fn visit_url(&mut self, url: &mut Url<'i>) -> Result<(), Self::Error> { fn visit_url(&mut self, url: &mut Url<'i>) -> Result<(), Self::Error> {
if let Some(path) = url.url.strip_prefix("inline://") { if let Some(path) = url.url.strip_prefix("inline://") {
let content = read(path).unwrap_or_else(|e| { url.url = inline_url(path).into();
eprintln!("Failed to write inline file {path}: {e}");
panic!("Failed to inline");
});
let (mime, encode) = guess_embed(path);
if encode {
let encoded = STANDARD.encode(content);
url.url = format!("data:{mime};base64,{encoded}").into();
} else {
let content = String::from_utf8(content).expect("invalid utf8");
let encoded = urlencoding::encode(&content);
url.url = format!("data:{mime},{encoded}").into();
}
} }
Ok(()) Ok(())
} }

View file

@ -36,6 +36,7 @@ ready(async () => {
const parse = async (data: ArrayBuffer, parseProgress: HTMLProgressElement, stored: boolean) => { const parse = async (data: ArrayBuffer, parseProgress: HTMLProgressElement, stored: boolean) => {
const header = parseHeaderFromBuffer(data); const header = parseHeaderFromBuffer(data);
console.log(header);
const parser = new AsyncParser(data, (progress) => parseProgress.value = progress); const parser = new AsyncParser(data, (progress) => parseProgress.value = progress);
await parser.cache(); await parser.cache();

View file

@ -16,30 +16,24 @@ const healthMap = [0, 150, 180, 216];
function getBuildingType(type: BuildingType) { function getBuildingType(type: BuildingType) {
switch (type) { switch (type) {
case BuildingType.TeleporterEntrance: case BuildingType.TeleporterEntrance:
return 'tele_entrance'; return require("inline://images/building_icons/tele_entrance.png");
case BuildingType.TeleporterExit: case BuildingType.TeleporterExit:
return 'tele_exit'; return require("inline://images/building_icons/tele_exit.png");
case BuildingType.Dispenser: case BuildingType.Dispenser:
return 'dispenser'; return require("inline://images/building_icons/dispenser.png");
case BuildingType.Level1Sentry: case BuildingType.Level1Sentry:
return 'sentry_1'; return require("inline://images/building_icons/sentry_1.png");
case BuildingType.Level2Sentry: case BuildingType.Level2Sentry:
return 'sentry_2'; return require("inline://images/building_icons/sentry_2.png");
case BuildingType.Level3Sentry: case BuildingType.Level3Sentry:
return 'sentry_3'; return require("inline://images/building_icons/sentry_3.png");
case BuildingType.MiniSentry: case BuildingType.MiniSentry:
return 'sentry_1'; return require("inline://images/building_icons/sentry_1.png");
default: default:
return 'unknown'; return '';
} }
} }
function getIcon(building: BuildingState) {
const icon = getBuildingType(building.buildingType);
const team = building.team === Team.Red ? 'red' : 'blue';
return `/images/building_icons/${icon}_${team}.png`;
}
export function Building(props: BuildingProp) { export function Building(props: BuildingProp) {
const worldWidth = props.mapBoundary.boundary_max.x - props.mapBoundary.boundary_min.x; const worldWidth = props.mapBoundary.boundary_max.x - props.mapBoundary.boundary_min.x;
const worldHeight = props.mapBoundary.boundary_max.y - props.mapBoundary.boundary_min.y; const worldHeight = props.mapBoundary.boundary_max.y - props.mapBoundary.boundary_min.y;
@ -48,6 +42,7 @@ export function Building(props: BuildingProp) {
const scaledX = () => x() / worldWidth * props.targetSize.width; const scaledX = () => x() / worldWidth * props.targetSize.width;
const scaledY = () => (worldHeight - y()) / worldHeight * props.targetSize.height; const scaledY = () => (worldHeight - y()) / worldHeight * props.targetSize.height;
const maxHealth = () => healthMap[props.building.level]; const maxHealth = () => healthMap[props.building.level];
const teamColor = () => (props.building.team === Team.Red) ? '#a75d50' : '#5b818f';
if (!maxHealth) { if (!maxHealth) {
return null; return null;
} }
@ -57,10 +52,12 @@ export function Building(props: BuildingProp) {
const alpha = () => props.building.health / maxHealth; const alpha = () => props.building.health / maxHealth;
try { try {
const image = getIcon(props.building); const image = () => getBuildingType(props.building.buildingType);
return <g transform={transform()} return <g transform={transform()}
opacity={alpha()}> opacity={alpha()}>
<image href={image} className={"player-icon"} height={32} <circle r={16} stroke-width={1} stroke="white" fill={teamColor()}
opacity={alpha()}/>
<image href={image()} className={"player-icon"} height={32}
width={32} width={32}
transform={`translate(-16 -16)`}/> transform={`translate(-16 -16)`}/>
<Show when={props.building.angle}> <Show when={props.building.angle}>

View file

@ -1,5 +1,4 @@
import {PlayerState, WorldBoundaries, Team} from "../Data/Parser"; import {PlayerState, WorldBoundaries, Team} from "../Data/Parser";
import {createEffect} from "solid-js";
export interface PlayerProp { export interface PlayerProp {
player: PlayerState; player: PlayerState;
@ -24,17 +23,17 @@ const healthMap = {
9: 125,//engineer 9: 125,//engineer
}; };
const classMap = { const imageMap = {
0: "empty", 0: require("inline://images/class_icons/empty.svg"),
1: "scout", 1: require("inline://images/class_icons/scout.svg"),
2: "sniper", 2: require("inline://images/class_icons/sniper.svg"),
3: "soldier", 3: require("inline://images/class_icons/soldier.svg"),
4: "demoman", 4: require("inline://images/class_icons/demoman.svg"),
5: "medic", 5: require("inline://images/class_icons/medic.svg"),
6: "heavy", 6: require("inline://images/class_icons/heavy.svg"),
7: "pyro", 7: require("inline://images/class_icons/pyro.svg"),
8: "spy", 8: require("inline://images/class_icons/spy.svg"),
9: "engineer" 9: require("inline://images/class_icons/engineer.svg"),
}; };
export function Player(props: PlayerProp) { export function Player(props: PlayerProp) {
@ -63,11 +62,10 @@ export function Player(props: PlayerProp) {
} }
function getClassImage(player: PlayerState, imageOpacity: number) { function getClassImage(player: PlayerState, imageOpacity: number) {
if (!classMap[player.playerClass]) { if (!imageMap[player.playerClass]) {
return []; return [];
} }
const image = `/images/class_icons/${classMap[player.playerClass]}.svg`; return <image href={imageMap[player.playerClass]}
return <image href={image}
class={"player-icon " + player.team} class={"player-icon " + player.team}
opacity={imageOpacity} opacity={imageOpacity}
height={32} height={32}

View file

@ -87,34 +87,34 @@
} }
& .class-icon.scout { & .class-icon.scout {
background-image: url('../../images/class_portraits/Icon_scout.jpg'); background-image: url('/images/class_portraits/Icon_scout.jpg');
} }
& .class-icon.soldier { & .class-icon.soldier {
background-image: url('../../images/class_portraits/Icon_soldier.jpg'); background-image: url('/images/class_portraits/Icon_soldier.jpg');
} }
& .class-icon.pyro { & .class-icon.pyro {
background-image: url('../../images/class_portraits/Icon_pyro.jpg'); background-image: url('/images/class_portraits/Icon_pyro.jpg');
} }
& .class-icon.demoman { & .class-icon.demoman {
background-image: url('../../images/class_portraits/Icon_demoman.jpg'); background-image: url('/images/class_portraits/Icon_demoman.jpg');
} }
& .class-icon.engineer { & .class-icon.engineer {
background-image: url('../../images/class_portraits/Icon_engineer.jpg'); background-image: url('/images/class_portraits/Icon_engineer.jpg');
} }
& .class-icon.heavy { & .class-icon.heavy {
background-image: url('../../images/class_portraits/Icon_heavy.jpg'); background-image: url('/images/class_portraits/Icon_heavy.jpg');
} }
& .class-icon.medic { & .class-icon.medic {
background-image: url('../../images/class_portraits/Icon_medic.jpg'); background-image: url('/images/class_portraits/Icon_medic.jpg');
} }
& .class-icon.sniper { & .class-icon.sniper {
background-image: url('../../images/class_portraits/Icon_sniper.jpg'); background-image: url('/images/class_portraits/Icon_sniper.jpg');
} }
& .class-icon.spy{ & .class-icon.spy{
background-image: url('../../images/class_portraits/Icon_spy.jpg'); background-image: url('/images/class_portraits/Icon_spy.jpg');
} }
& .class-icon.uber { & .class-icon.uber {
background-image: url('images/charge_red.svg'); background-image: url('inline://images/charge_red.svg');
} }
& .class-icon, & .steam-avatar { & .class-icon, & .steam-avatar {
@ -148,34 +148,34 @@
} }
& .class-icon.scout { & .class-icon.scout {
background-image: url('../../images/class_portraits/Icon_scout_blue.jpg'); background-image: url('/images/class_portraits/Icon_scout_blue.jpg');
} }
& .class-icon.soldier { & .class-icon.soldier {
background-image: url('../../images/class_portraits/Icon_soldier_blue.jpg'); background-image: url('/images/class_portraits/Icon_soldier_blue.jpg');
} }
& .class-icon.pyro { & .class-icon.pyro {
background-image: url('../../images/class_portraits/Icon_pyro_blue.jpg'); background-image: url('/images/class_portraits/Icon_pyro_blue.jpg');
} }
& .class-icon.demoman { & .class-icon.demoman {
background-image: url('../../images/class_portraits/Icon_demoman_blue.jpg'); background-image: url('/images/class_portraits/Icon_demoman_blue.jpg');
} }
& .class-icon.engineer { & .class-icon.engineer {
background-image: url('../../images/class_portraits/Icon_engineer_blue.jpg'); background-image: url('/images/class_portraits/Icon_engineer_blue.jpg');
} }
& .class-icon.heavy { & .class-icon.heavy {
background-image: url('../../images/class_portraits/Icon_heavy_blue.jpg'); background-image: url('/images/class_portraits/Icon_heavy_blue.jpg');
} }
& .class-icon.medic { & .class-icon.medic {
background-image: url('../../images/class_portraits/Icon_medic_blue.jpg'); background-image: url('/images/class_portraits/Icon_medic_blue.jpg');
} }
& .class-icon.sniper { & .class-icon.sniper {
background-image: url('../../images/class_portraits/Icon_sniper_blue.jpg'); background-image: url('/images/class_portraits/Icon_sniper_blue.jpg');
} }
& .class-icon.spy { & .class-icon.spy {
background-image: url('../../images/class_portraits/Icon_spy_blue.jpg'); background-image: url('/images/class_portraits/Icon_spy_blue.jpg');
} }
& .class-icon.uber { & .class-icon.uber {
background-image: url('images/charge_blue.svg'); background-image: url('inline://images/charge_blue.svg');
} }
} }