feat(web): start a new web app

This commit is contained in:
Simon Chan 2023-05-25 23:05:16 +08:00
parent c30db6b694
commit 3e5d180699
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
19 changed files with 2830 additions and 210 deletions

24
apps/web/.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
dist
.solid
.output
.vercel
.netlify
netlify
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
*.launch
.settings/
# Temp
gitignore
# System Files
.DS_Store
Thumbs.db

30
apps/web/README.md Normal file
View file

@ -0,0 +1,30 @@
# SolidStart
Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com);
## Creating a project
```bash
# create a new project in the current directory
npm init solid@latest
# create a new project in my-app
npm init solid@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
Solid apps are built with _adapters_, which optimise your project for deployment to different environments.
By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different adapter, add it to the `devDependencies` in `package.json` and specify in your `vite.config.js`.

35
apps/web/package.json Normal file
View file

@ -0,0 +1,35 @@
{
"name": "@yume-chan/tango-web",
"version": "1.0.0",
"scripts": {
"dev": "solid-start dev",
"build": "solid-start build",
"start": "solid-start start"
},
"type": "module",
"devDependencies": {
"@types/node": "^20.2.1",
"esbuild": "^0.14.54",
"postcss": "^8.4.21",
"solid-start-node": "^0.2.19",
"typescript": "^5.0.3",
"vite": "^4.1.4"
},
"dependencies": {
"@solidjs/meta": "^0.28.2",
"@solidjs/router": "^0.8.2",
"@solid-devtools/overlay": "^0.6.0",
"@yume-chan/adb": "workspace:^0.0.19",
"@yume-chan/adb-credential-web": "workspace:^0.0.19",
"@yume-chan/adb-daemon-webusb": "workspace:^0.0.19",
"@yume-chan/async": "^2.2.0",
"@yume-chan/stream-extra": "workspace:^0.0.19",
"@yume-chan/struct": "workspace:^0.0.19",
"solid-js": "^1.7.2",
"solid-start": "^0.2.26",
"undici": "^5.15.1"
},
"engines": {
"node": ">=16.8"
}
}

BIN
apps/web/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

64
apps/web/public/logo.hlsl Normal file
View file

@ -0,0 +1,64 @@
vec2 project(vec2 a, vec2 b) {
return a * dot(a, b) / dot(a, a);
}
vec4 drawCircle(vec2 coord, vec2 c, float r, float rr, float as, float ae, vec3 color1, vec3 color2) {
vec2 delta = coord - c;
float d = length(delta);
if (d < r - 15.0) {
return vec4(0, 0, 0, 0);
}
float a = atan(delta.y, delta.x) + 3.14;
if (as < ae ? a > as && a < ae : a > as || a < ae) {
if (d < r) {
return vec4(0, 0, 0, 1);
} else if (d < rr) {
float crossLength = sqrt(pow(rr, 2.0) * 2.0);
float p = length(project(vec2(-1,1),delta)-(vec2(-1,1)*crossLength)) / (crossLength * 2.0);
return vec4(mix(color1, color2, p), 1);
} else if (d < rr + 15.0) {
return vec4(0, 0, 0, 1);
}
}
return vec4(0, 0, 0, 0);
}
vec3 parseColor(int color) {
return vec3(float((color >> 16) & 0xff) / 255.0, float((color >> 8) & 0xff) / 255.0, float((color >> 0) & 0xff) / 255.0);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 c1 = vec2(0.6, 0.5) * iResolution.xy;
float r1 = 0.3 * iResolution.y;
float rr1 = 0.2 * iResolution.y;
float as1 = 225.0 / 180.0 * 3.14;
float ae1 = 135.0 / 180.0 * 3.14;
vec2 c2 = vec2(0.45, 0.5) * iResolution.xy;
float r2 = 0.3 * iResolution.y;
float rr2 = 0.2 * iResolution.y;
float as2 = 315.0 / 180.0 * 3.14;
float ae2 = 270.0 / 180.0 * 3.14;
float w = 11.0;
vec3 color11= parseColor(0xD9D9D9);
vec3 color12= parseColor(0x898989);
vec3 color21 = parseColor(0x2D6AF6);
vec3 color22 = parseColor(0xA3BFFF);
vec4 color;
if (fragCoord.y < iResolution.y / 2.0) {
color = drawCircle(fragCoord, c1, rr1, r1, as1, ae1, color11, color12);
if (color.a == 0.0) {
color = drawCircle(fragCoord, c2, rr2, r2, as2, ae2, color21, color22);
}
} else {
color = drawCircle(fragCoord, c2, rr2, r2, as2, ae2, color21, color22);
if (color.a == 0.0) {
color = drawCircle(fragCoord, c1, rr1, r1, as1, ae1, color11, color12);
}
}
fragColor = color;
}

View file

@ -0,0 +1,20 @@
.increment {
font-family: inherit;
font-size: inherit;
padding: 1em 2em;
color: #335d92;
background-color: rgba(68, 107, 158, 0.1);
border-radius: 2em;
border: 2px solid rgba(68, 107, 158, 0);
outline: none;
width: 200px;
font-variant-numeric: tabular-nums;
}
.increment:focus {
border: 2px solid #335d92;
}
.increment:active {
background-color: rgba(68, 107, 158, 0.2);
}

View file

@ -0,0 +1,11 @@
import { createSignal } from "solid-js";
import "./Counter.css";
export default function Counter() {
const [count, setCount] = createSignal(0);
return (
<button class="increment" onClick={() => setCount(count() + 1)}>
Clicks: {count()}
</button>
);
}

View file

@ -0,0 +1,353 @@
import {
AdbDaemonTransport,
AdbPacketData,
AdbPacketInit,
AdbSocket,
} from "@yume-chan/adb";
import AdbWebCredentialStore from "@yume-chan/adb-credential-web";
import { PromiseResolver } from "@yume-chan/async";
import {
Consumable,
ConsumableWritableStream,
PushReadableStream,
PushReadableStreamController,
ReadableWritablePair,
WritableStream,
} from "@yume-chan/stream-extra";
const CredentialStore = new AdbWebCredentialStore();
const transports = new Map<string, AdbDaemonTransport>();
class RetryError extends Error {
public constructor() {
super("Retry");
}
}
class SharedWorkerDaemonConnection
implements ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>
{
#serial: string;
get serial() {
return this.#serial;
}
#port = new PromiseResolver<MessagePort>();
#readable: PushReadableStream<AdbPacketData>;
#readableController!: PushReadableStreamController<AdbPacketData>;
get readable() {
return this.#readable;
}
#writable: WritableStream<Consumable<AdbPacketInit>>;
#writePromise: PromiseResolver<void> | undefined;
get writable() {
return this.#writable;
}
public constructor(serial: string) {
this.#serial = serial;
this.#readable = new PushReadableStream((controller) => {
this.#readableController = controller;
});
this.#writable = new WritableStream({
write: async (chunk) => {
console.log("out begin", chunk);
while (true) {
try {
this.#writePromise = new PromiseResolver();
const port = await this.#port.promise;
console.log("out port", port);
port.postMessage({
type: "data",
payload: chunk.value,
});
await this.#writePromise.promise;
this.#writePromise = undefined;
chunk.consume();
console.log("out finish");
return;
} catch (e) {
if (e instanceof RetryError) {
continue;
}
throw e;
}
}
},
});
}
public attach(port: MessagePort) {
this.#port.resolve(port);
port.onmessage = async (event) => {
const message = event.data as
| { type: "data"; payload: AdbPacketData }
| { type: "ack" }
| { type: "close" };
switch (message.type) {
case "data":
console.log("in", message.payload);
await this.#readableController.enqueue(message.payload);
port.postMessage({ type: "ack" });
break;
case "ack":
this.#writePromise!.resolve();
break;
case "close":
this.#readableController.close();
break;
}
};
}
public detach() {
this.#port = new PromiseResolver();
this.#writePromise?.reject(new RetryError());
}
}
class SharedWorkerSocketOwner {
#port: MessagePort;
#socket: AdbSocket;
#writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>;
#readAbortController = new AbortController();
#pendingAck: PromiseResolver<void> | undefined;
constructor(port: MessagePort, socket: AdbSocket) {
this.#port = port;
this.#socket = socket;
this.#writer = socket.writable.getWriter();
socket.readable
.pipeTo(
new WritableStream({
write: async (chunk) => {
this.#pendingAck = new PromiseResolver();
console.log("socket in write", socket.service, chunk);
port.postMessage({ type: "data", payload: chunk });
await this.#pendingAck.promise;
this.#pendingAck = undefined;
console.log("socket in write done");
},
close: () => {
console.log("socket in close", socket.service);
port.postMessage({ type: "close" });
},
}),
{
signal: this.#readAbortController.signal,
}
)
.catch((e) => {
if (this.#readAbortController.signal.aborted) {
return;
}
throw e;
});
port.onmessage = async (event) => {
const message = event.data as
| {
type: "data";
payload: Uint8Array;
}
| { type: "ack" }
| { type: "close" };
switch (message.type) {
case "data":
await this.write(message.payload);
break;
case "ack":
this.ack();
break;
case "close":
this.close();
break;
}
};
}
public async write(payload: Uint8Array) {
await ConsumableWritableStream.write(this.#writer, payload);
this.#port.postMessage({ type: "ack" });
}
public ack() {
this.#pendingAck!.resolve();
}
public close() {
this.#writer.releaseLock();
this.#socket.close();
this.#port.close();
}
}
class SharedWorkerTransportOwner {
#port: MessagePort;
#transport: AdbDaemonTransport;
constructor(port: MessagePort, transport: AdbDaemonTransport) {
this.#port = port;
this.#transport = transport;
transport.disconnected.then(() => {
port.postMessage({ type: "close" });
});
port.onmessage = async (event) => {
const message = event.data as {
type: "connect";
id: number;
service: string;
};
switch (message.type) {
case "connect":
await this.connect(message.id, message.service);
break;
}
};
}
public async connect(id: number, service: string) {
try {
const socket = await this.#transport.connect(service);
const channel = new MessageChannel();
const server = new SharedWorkerSocketOwner(channel.port2, socket);
this.#port.postMessage(
{
type: "connect",
id: id,
result: true,
port: channel.port1,
},
[channel.port1]
);
} catch {
this.#port.postMessage({
type: "connect",
id: id,
result: false,
});
}
}
}
declare interface SharedWorkerGlobalScope {
onconnect: (e: MessageEvent) => void;
}
const clientToConnections = new Map<
MessagePort,
Set<SharedWorkerDaemonConnection>
>();
const serialToConnection = new Map<string, SharedWorkerDaemonConnection>();
async function connect(port: MessagePort, serial: string) {
const channel = new MessageChannel();
const messageResolver = new PromiseResolver<{
type: "connect";
result: boolean;
}>();
channel.port2.onmessage = async (event) => {
messageResolver.resolve(event.data);
};
port.postMessage({ type: "connect", serial, port: channel.port1 }, [
channel.port1,
]);
const message = await messageResolver.promise;
switch (message.type) {
case "connect":
if (!message.result) {
throw new Error("Failed to connect");
}
if (serialToConnection.has(serial)) {
const connection = serialToConnection.get(serial)!;
console.log("switch", connection, "to", port);
connection.attach(channel.port2);
clientToConnections.get(port)!.add(connection);
} else {
const connection = new SharedWorkerDaemonConnection(serial);
connection.attach(channel.port2);
serialToConnection.set(serial, connection);
clientToConnections.get(port)!.add(connection);
const transport = await AdbDaemonTransport.authenticate({
serial,
connection,
credentialStore: CredentialStore,
});
transports.set(transport.serial, transport);
}
break;
default:
throw new Error("Unknown message type");
}
}
(globalThis as unknown as SharedWorkerGlobalScope).onconnect = (e) => {
const port = e.ports[0]!;
clientToConnections.set(port, new Set());
port.onmessage = async (event) => {
const message = event.data as
| {
type: "query";
id: number;
serial: string;
}
| { type: "disconnect" };
switch (message.type) {
case "query":
if (!transports.has(message.serial)) {
await connect(port, message.serial);
}
const transport = transports.get(message.serial)!;
const channel = new MessageChannel();
port.postMessage(
{
type: "query-success",
id: message.id,
serial: message.serial,
product: transport.banner.product,
model: transport.banner.model,
device: transport.banner.device,
features: transport.banner.features,
maxPayloadSize: transport.maxPayloadSize,
port: channel.port1,
},
[channel.port1]
);
new SharedWorkerTransportOwner(channel.port2, transport);
break;
case "disconnect":
for (const connection of clientToConnections.get(port)!) {
connection.detach();
}
let nextClient: MessagePort;
for (const client of clientToConnections.keys()) {
if (client !== port) {
nextClient = client;
break;
}
}
await Promise.all(
Array.from(clientToConnections.get(port)!, (connection) =>
connect(nextClient, connection.serial)
)
);
clientToConnections.delete(port);
break;
}
};
};

View file

@ -0,0 +1,3 @@
import { mount, StartClient } from "solid-start/entry-client";
mount(() => <StartClient />, document);

View file

@ -0,0 +1,9 @@
import {
createHandler,
renderAsync,
StartServer,
} from "solid-start/entry-server";
export default createHandler(
renderAsync((event) => <StartServer event={event} />)
);

40
apps/web/src/root.css Normal file
View file

@ -0,0 +1,40 @@
body {
font-family: Gordita, Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans",
"Helvetica Neue", sans-serif;
}
a {
margin-right: 1rem;
}
main {
text-align: center;
padding: 1em;
margin: 0 auto;
}
h1 {
color: #335d92;
text-transform: uppercase;
font-size: 4rem;
font-weight: 100;
line-height: 1.1;
margin: 4rem auto;
max-width: 14rem;
}
p {
max-width: 14rem;
margin: 2rem auto;
line-height: 1.35;
}
@media (min-width: 480px) {
h1 {
max-width: none;
}
p {
max-width: none;
}
}

42
apps/web/src/root.tsx Normal file
View file

@ -0,0 +1,42 @@
// @refresh reload
import { Suspense } from "solid-js";
import {
A,
Body,
ErrorBoundary,
FileRoutes,
Head,
Html,
Meta,
Routes,
Scripts,
Title,
} from "solid-start";
import "./root.css";
export default function Root() {
return (
<Html lang="en">
<Head>
<Title>SolidStart - Bare</Title>
<Meta charset="utf-8" />
<Meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
</Head>
<Body>
<Suspense>
<ErrorBoundary>
<A href="/">Index</A>
<A href="/about">About</A>
<Routes>
<FileRoutes />
</Routes>
</ErrorBoundary>
</Suspense>
<Scripts />
</Body>
</Html>
);
}

View file

@ -0,0 +1,19 @@
import { Title } from "solid-start";
import { HttpStatusCode } from "solid-start/server";
export default function NotFound() {
return (
<main>
<Title>Not Found</Title>
<HttpStatusCode code={404} />
<h1>Page Not Found</h1>
<p>
Visit{" "}
<a href="https://start.solidjs.com" target="_blank">
start.solidjs.com
</a>{" "}
to learn how to build SolidStart apps.
</p>
</main>
);
}

View file

@ -0,0 +1,441 @@
import {
Adb,
AdbBanner,
AdbFeature,
AdbIncomingSocketHandler,
AdbPacketData,
AdbPacketInit,
AdbSocket,
AdbTransport,
} from "@yume-chan/adb";
import {
ADB_DEFAULT_DEVICE_FILTER,
AdbDaemonWebUsbDeviceManager,
} from "@yume-chan/adb-daemon-webusb";
import { AsyncOperationManager, PromiseResolver } from "@yume-chan/async";
import {
Consumable,
ConsumableWritableStream,
PushReadableStream,
PushReadableStreamController,
WritableStream,
} from "@yume-chan/stream-extra";
import { createSignal } from "solid-js";
import { Title } from "solid-start";
class SharedWorkerSocket implements AdbSocket {
#port: MessagePort;
#service: string;
get service() {
return this.#service;
}
#readable: PushReadableStream<Uint8Array>;
#readableController!: PushReadableStreamController<Uint8Array>;
get readable() {
return this.#readable;
}
#writable: WritableStream<Consumable<Uint8Array>>;
#pendingWrite: PromiseResolver<void> | undefined;
get writable() {
return this.#writable;
}
public constructor(port: MessagePort, service: string) {
this.#port = port;
this.#service = service;
this.#readable = new PushReadableStream((controller) => {
this.#readableController = controller;
});
this.#writable = new WritableStream({
write: async (chunk) => {
this.#pendingWrite = new PromiseResolver();
port.postMessage({
type: "data",
payload: chunk.value,
});
await this.#pendingWrite.promise;
this.#pendingWrite = undefined;
chunk.consume();
},
});
this.#port.onmessage = async (event) => {
const message = event.data as
| {
type: "data";
payload: Uint8Array;
}
| { type: "ack" }
| { type: "close" };
switch (message.type) {
case "data":
console.log("socket in", this.#service, message.payload);
await this.#readableController.enqueue(message.payload);
port.postMessage({ type: "ack" });
break;
case "ack":
this.#pendingWrite!.resolve();
break;
case "close":
this.#readableController.close();
break;
}
};
}
public close() {
this.#port.postMessage({
type: "close",
});
this.#port.close();
}
}
class SharedWorkerTransport implements AdbTransport {
#serial: string;
get serial() {
return this.#serial;
}
#maxPayloadSize: number;
get maxPayloadSize() {
return this.#maxPayloadSize;
}
#banner: AdbBanner;
get banner() {
return this.#banner;
}
#port: MessagePort;
#operations = new AsyncOperationManager();
#reverseTunnels = new Map<string, AdbIncomingSocketHandler>();
#disconnected = new PromiseResolver<void>();
get disconnected() {
return this.#disconnected.promise;
}
public constructor(
serial: string,
maxPayloadSize: number,
banner: AdbBanner,
port: MessagePort
) {
this.#serial = serial;
this.#maxPayloadSize = maxPayloadSize;
this.#banner = banner;
this.#port = port;
this.#port.onmessage = async (event) => {
const message = event.data;
switch (message.type) {
case "connect":
if (message.result) {
this.#operations.resolve(message.id, message.port);
} else {
this.#operations.reject(
message.id,
new Error("failed to connect")
);
}
break;
case "reverse-tunnel":
{
const handler = this.#reverseTunnels.get(
message.address
);
if (!handler) {
break;
}
const socket = new SharedWorkerSocket(
message.port,
message.address
);
await handler(socket);
}
break;
case "add-reverse-tunnel":
if (message.result) {
this.#operations.resolve(message.id, message.address);
} else {
this.#operations.reject(
message.id,
new Error(message.error)
);
}
break;
case "remove-reverse-tunnel":
case "clear-reverse-tunnels":
if (message.result) {
this.#operations.resolve(message.id, undefined);
} else {
this.#operations.reject(
message.id,
new Error(message.error)
);
}
break;
case "close":
this.#disconnected.resolve();
break;
}
};
}
public async connect(service: string): Promise<AdbSocket> {
const [id, promise] = this.#operations.add<MessagePort>();
this.#port.postMessage({
type: "connect",
id,
service,
});
const port = await promise;
return new SharedWorkerSocket(port, service);
}
public async addReverseTunnel(
handler: AdbIncomingSocketHandler,
address?: string
): Promise<string> {
const [id, promise] = this.#operations.add<string>();
this.#port.postMessage({
type: "add-reverse-tunnel",
id,
address,
});
address = await promise;
this.#reverseTunnels.set(address, handler);
return address;
}
public async removeReverseTunnel(address: string): Promise<void> {
const [id, promise] = this.#operations.add<void>();
this.#port.postMessage({
type: "remove-reverse-tunnel",
id,
address,
});
await promise;
this.#reverseTunnels.delete(address);
}
public async clearReverseTunnels(): Promise<void> {
const [id, promise] = this.#operations.add<void>();
this.#port.postMessage({
type: "clear-reverse-tunnels",
id,
});
await promise;
this.#reverseTunnels.clear();
}
public close() {
this.#port.postMessage({
type: "close",
});
}
}
class SharedWorkerDaemonConnectionOwner {
#port: MessagePort;
#connection: ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>;
#writer: WritableStreamDefaultWriter<Consumable<AdbPacketInit>>;
#pendingWrite: PromiseResolver<void> | undefined;
constructor(
port: MessagePort,
connection: ReadableWritablePair<
AdbPacketData,
Consumable<AdbPacketInit>
>
) {
this.#port = port;
this.#connection = connection;
this.#writer = connection.writable.getWriter();
this.#connection.readable.pipeTo(
new WritableStream<AdbPacketData>({
write: async (chunk) => {
console.log("connection in", chunk);
this.#pendingWrite = new PromiseResolver();
this.#port.postMessage({
type: "data",
payload: chunk,
});
await this.#pendingWrite.promise;
this.#pendingWrite = undefined;
},
close: () => {
this.#port.postMessage({
type: "close",
});
},
})
);
this.#port.onmessage = async (event) => {
const message = event.data as
| { type: "data"; payload: AdbPacketInit }
| { type: "ack" };
switch (message.type) {
case "data":
console.log("connection out", message.payload);
await ConsumableWritableStream.write(
this.#writer,
message.payload
);
this.#port.postMessage({
type: "ack",
});
break;
case "ack":
this.#pendingWrite!.resolve();
break;
}
};
}
}
export default function Home() {
const [adb, setAdb] = createSignal<Adb>();
let operations = new AsyncOperationManager();
let port!: MessagePort;
if (typeof window !== "undefined") {
const worker = new SharedWorker(
new URL("../components/worker.ts", import.meta.url),
{
type: "module",
}
);
port = worker.port;
port.onmessage = async (event) => {
const message = event.data as
| {
type: "query-success";
id: number;
serial: string;
product: string | undefined;
model: string | undefined;
device: string | undefined;
features: AdbFeature[];
maxPayloadSize: number;
port: MessagePort;
}
| { type: "query-error"; id: number; error: string }
| {
type: "connect";
serial: string;
port: MessagePort;
};
switch (message.type) {
case "query-success":
operations.resolve(
message.id,
new SharedWorkerTransport(
message.serial,
message.maxPayloadSize,
new AdbBanner(
message.product,
message.model,
message.device,
message.features
),
message.port
)
);
break;
case "query-error":
operations.reject(message.id, new Error(message.error));
break;
case "connect":
{
const [device] =
await AdbDaemonWebUsbDeviceManager.BROWSER!.getDevices(
[
{
...ADB_DEFAULT_DEVICE_FILTER,
serialNumber: message.serial,
},
]
);
if (!device) {
message.port.postMessage({
type: "connect",
result: false,
});
message.port.close();
return;
}
const connection = await device.connect();
new SharedWorkerDaemonConnectionOwner(
message.port,
connection
);
message.port.postMessage({
type: "connect",
result: true,
});
}
break;
}
};
window.addEventListener("beforeunload", () => {
port.postMessage({
type: "disconnect",
});
});
}
const handleClick = async () => {
const device =
await AdbDaemonWebUsbDeviceManager.BROWSER!.requestDevice();
if (!device) {
return;
}
const [id, promise] = operations.add<SharedWorkerTransport>();
port.postMessage({
type: "query",
id,
serial: device.serial,
});
const transport = await promise;
const adb = new Adb(transport);
setAdb(adb);
setInterval(async () => {
const model = await adb.getProp("ro.product.model");
console.log("model:", model);
}, 1000);
};
const handleDisconnect = async () => {
const _adb = adb();
if (!_adb) {
return;
}
await _adb.close();
setAdb(undefined);
};
return (
<main>
<Title>Tango</Title>
<div>{adb() ? "connected" : "disconnected"}</div>
{adb() ? (
<>
<button onClick={handleDisconnect}>Disconnect</button>
</>
) : (
<button onClick={handleClick}>Connect</button>
)}
</main>
);
}

17
apps/web/tsconfig.json Normal file
View file

@ -0,0 +1,17 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"jsxImportSource": "solid-js",
"jsx": "preserve",
"strict": true,
"types": ["solid-start/env"],
"baseUrl": "./",
"paths": {
"~/*": ["./src/*"]
}
}
}

6
apps/web/vite.config.ts Normal file
View file

@ -0,0 +1,6 @@
import solid from "solid-start/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [solid()],
});

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
{
"pnpmShrinkwrapHash": "c4be1a3333837f03459388b246f781f6e6d5938e",
"pnpmShrinkwrapHash": "a14edaaec87943bdc5bdf6114640f71dcf626d70",
"preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f"
}

View file

@ -502,6 +502,10 @@
{
"packageName": "@yume-chan/adb-cli",
"projectFolder": "apps/cli"
},
{
"packageName": "@yume-chan/tango-web",
"projectFolder": "apps/web"
}
]
}