Compare commits
4 Commits
672578b9a9
...
1c4894df9a
Author | SHA1 | Date | |
---|---|---|---|
1c4894df9a | |||
20b214df10 | |||
ca89455d4d | |||
2ff981e28a |
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -5475,6 +5475,16 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "visions-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"gloo-net 0.6.0",
|
||||
"serde 1.0.217",
|
||||
"wasm-bindgen-futures",
|
||||
"yew",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
|
@ -33,4 +33,4 @@ members = [
|
||||
"tree",
|
||||
"visions/server",
|
||||
"gm-dash/server"
|
||||
, "visions/yew-app"]
|
||||
, "visions/yew-app", "visions/ui"]
|
||||
|
24
visions/ui/.gitignore
vendored
24
visions/ui/.gitignore
vendored
@ -1,24 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
11
visions/ui/Cargo.toml
Normal file
11
visions/ui/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "visions-client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
gloo-net = "0.6.0"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
wasm-bindgen-futures = "0.4.50"
|
||||
yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] }
|
||||
|
@ -1,50 +0,0 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||
|
||||
- Configure the top-level `parserOptions` property like this:
|
||||
|
||||
```js
|
||||
export default tseslint.config({
|
||||
languageOptions: {
|
||||
// other options...
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
|
||||
- Optionally add `...tseslint.configs.stylisticTypeChecked`
|
||||
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import react from 'eslint-plugin-react'
|
||||
|
||||
export default tseslint.config({
|
||||
// Set the react version
|
||||
settings: { react: { version: '18.3' } },
|
||||
plugins: {
|
||||
// Add the react plugin
|
||||
react,
|
||||
},
|
||||
rules: {
|
||||
// other rules...
|
||||
// Enable its recommended rules
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs['jsx-runtime'].rules,
|
||||
},
|
||||
})
|
||||
```
|
@ -1,17 +0,0 @@
|
||||
version: "3"
|
||||
|
||||
tasks:
|
||||
fmt:
|
||||
cmds:
|
||||
- npm run fmt
|
||||
|
||||
build:
|
||||
cmds:
|
||||
- npm install
|
||||
- npx tsc --watch
|
||||
|
||||
test:
|
||||
cmds:
|
||||
# - cd ../visions-types && task build
|
||||
- npm install
|
||||
- npm run test
|
36
visions/ui/design.css
Normal file
36
visions/ui/design.css
Normal file
@ -0,0 +1,36 @@
|
||||
:root {
|
||||
--spacing-s: 4px;
|
||||
--spacing-m: 8px;
|
||||
--shadow-shallow: 2px 2px 1px;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: hsl(0, 0%, 95%);
|
||||
font-family: Ariel, sans-serif;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: space-between;
|
||||
border: 1px solid black;
|
||||
box-shadow: var(--shadow-shallow);
|
||||
border-radius: var(--spacing-s);
|
||||
padding: var(--spacing-m);
|
||||
}
|
||||
|
||||
.card > h1 {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.card > * {
|
||||
margin-top: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.login-form {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import tseslint from "typescript-eslint";
|
||||
import eslintConfigPrettier from "eslint-config-prettier";
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ["dist"] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended, eslintConfigPrettier],
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
"react-hooks": reactHooks,
|
||||
"react-refresh": reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
"react-refresh/only-export-components": [
|
||||
"warn",
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
@ -1,11 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head> <meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title> My first web component with typescript </title>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Visions Client Demo</title>
|
||||
<link data-trunk rel="css" href="design.css" />
|
||||
</head>
|
||||
<body>
|
||||
<app-component name="TypeScript"></app-component>
|
||||
<script type="module" src="./dist/app-component.js"></script>
|
||||
</body>
|
||||
<body></body>
|
||||
</html>
|
||||
|
111
visions/ui/package-lock.json
generated
111
visions/ui/package-lock.json
generated
@ -1,111 +0,0 @@
|
||||
{
|
||||
"name": "ui",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ui",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"lit": "^3.2.1",
|
||||
"visions-client": "file:../client",
|
||||
"visions-types": "file:../types"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
},
|
||||
"../client": {
|
||||
"name": "visions-client",
|
||||
"version": "0.0.1",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"visions-types": "file:../types"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.14",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.5.1",
|
||||
"ts-jest": "^29.2.5",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
},
|
||||
"../types": {
|
||||
"name": "visions-types",
|
||||
"version": "0.0.1",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/ssr-dom-shim": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz",
|
||||
"integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ=="
|
||||
},
|
||||
"node_modules/@lit/reactive-element": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
|
||||
"integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr-dom-shim": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
|
||||
},
|
||||
"node_modules/lit": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/lit/-/lit-3.2.1.tgz",
|
||||
"integrity": "sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w==",
|
||||
"dependencies": {
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"lit-element": "^4.1.0",
|
||||
"lit-html": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lit-element": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.1.tgz",
|
||||
"integrity": "sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==",
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr-dom-shim": "^1.2.0",
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"lit-html": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lit-html": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.1.tgz",
|
||||
"integrity": "sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==",
|
||||
"dependencies": {
|
||||
"@types/trusted-types": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/visions-client": {
|
||||
"resolved": "../client",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/visions-types": {
|
||||
"resolved": "../types",
|
||||
"link": true
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "ui",
|
||||
"version": "1.0.0",
|
||||
"description": "This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.",
|
||||
"main": "eslint.config.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"lit": "^3.2.1",
|
||||
"visions-client": "file:../client",
|
||||
"visions-types": "file:../types"
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import {html, css, LitElement} from 'lit';
|
||||
import {customElement, property} from 'lit/decorators.js';
|
||||
|
||||
export class SimpleGreeting extends LitElement {
|
||||
static styles = css`p { color: blue }`;
|
||||
name: String;
|
||||
|
||||
static properties = {
|
||||
name: { type: String }
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.name = 'Somebody';
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<p>Hello, ${this.name}!</p>`;
|
||||
}
|
||||
}
|
||||
|
39
visions/ui/src/client.rs
Normal file
39
visions/ui/src/client.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use gloo_net::http::Request;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
enum AuthResponse {
|
||||
Ok(SessionId),
|
||||
PasswordReset(SessionId),
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SessionId(String);
|
||||
|
||||
enum ClientError {
|
||||
Unauthorized,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UserId(String);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UserInfo {
|
||||
id: UserId,
|
||||
name: String,
|
||||
}
|
||||
|
||||
trait Client {
|
||||
async fn auth(username: String, password: String) -> Result<AuthResponse, ClientError>;
|
||||
async fn list_users(session_id: SessionId) -> Result<Vec<UserInfo>, ClientError>;
|
||||
}
|
||||
|
||||
impl Client for Connection {
|
||||
async fn auth(username: String, password: String) -> Result<AuthResponse, ClientError> {
|
||||
let request = Request::post("http://localhost:8001")
|
||||
.body().unwrap();
|
||||
}
|
||||
|
||||
async fn list_users(session_id: SessionId) -> Result<Vec<UserInfo>, ClientError> {
|
||||
}
|
||||
}
|
78
visions/ui/src/main.rs
Normal file
78
visions/ui/src/main.rs
Normal file
@ -0,0 +1,78 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use yew::prelude::*;
|
||||
|
||||
mod client;
|
||||
pub use client::*;
|
||||
|
||||
struct AuthInfo {
|
||||
session_id: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for AuthInfo {
|
||||
fn default() -> Self {
|
||||
Self { session_id: None }
|
||||
}
|
||||
}
|
||||
|
||||
enum AuthAction {
|
||||
Auth(String),
|
||||
Unauth,
|
||||
}
|
||||
|
||||
impl Reducible for AuthInfo {
|
||||
type Action = AuthAction;
|
||||
|
||||
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
|
||||
match action {
|
||||
AuthAction::Auth(session_id) => Self {
|
||||
session_id: Some(session_id),
|
||||
}
|
||||
.into(),
|
||||
AuthAction::Unauth => Self { session_id: None }.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
struct LoginProps {
|
||||
on_click: Callback<()>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn Login(LoginProps { on_click }: &LoginProps) -> Html {
|
||||
let on_click = {
|
||||
let on_click = on_click.clone();
|
||||
Callback::from(move |_| on_click.emit(()))
|
||||
};
|
||||
html! {
|
||||
<div class="login-form">
|
||||
<div class="card">
|
||||
<h1>{"Welcome to Visions VTT"}</h1>
|
||||
<input type="text" name="username" placeholder="username" />
|
||||
<input type="password" name="password" placeholder="password" />
|
||||
<button onclick={on_click}>{"Login"}</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn App() -> Html {
|
||||
let auth_info = use_reducer(AuthInfo::default);
|
||||
|
||||
let on_login = {
|
||||
let auth_info = auth_info.clone();
|
||||
Callback::from(move |_| auth_info.dispatch(AuthAction::Auth("abcdefg".into())))
|
||||
};
|
||||
|
||||
if auth_info.session_id.is_some() {
|
||||
html! { <p>{ "this is just a thing" }</p> }
|
||||
} else {
|
||||
html! { <Login on_click={on_login.clone()} /> }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
yew::Renderer::<App>::new().render();
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
import { Client } from 'visions-client'
|
||||
|
||||
export type AuthState =
|
||||
| { type: 'unauthed' }
|
||||
| { type: 'authed'; sessionId: string }
|
||||
|
||||
export type State = {
|
||||
auth: AuthState
|
||||
}
|
||||
|
||||
export const initialState = (): State => ({
|
||||
auth: { type: 'unauthed' },
|
||||
})
|
||||
|
||||
export const sessionId = (state: State): string | undefined => {
|
||||
if (state.auth.type === 'authed') {
|
||||
return state.auth.sessionId
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export type Action = { type: 'set-auth'; content: AuthState }
|
||||
|
||||
export const reducer = (state: State, action: Action) => {
|
||||
switch (action.type) {
|
||||
case 'set-auth': {
|
||||
return { ...state, auth: action.content }
|
||||
}
|
||||
default: {
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Controller {
|
||||
client: Client
|
||||
state: State
|
||||
|
||||
constructor(
|
||||
client: Client,
|
||||
state: State,
|
||||
) {
|
||||
this.client = client
|
||||
this.state = state
|
||||
}
|
||||
|
||||
// On any request, there are four options.
|
||||
// The request succeeds. No problem.
|
||||
// The request succeeds, but the user needs to reset their password.
|
||||
// The action fails.
|
||||
// The HTTP request itself fails.
|
||||
async auth(username: string, password: string) {
|
||||
let response = await this.client.auth(username, password)
|
||||
switch (response.status) {
|
||||
case 'ok': {
|
||||
/*
|
||||
this.dispatch({
|
||||
type: 'set-auth',
|
||||
content: {
|
||||
type: 'authed',
|
||||
sessionId: response.content.content,
|
||||
},
|
||||
})
|
||||
*/
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "es6",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user