Move old code out of the way
|
@ -1,16 +0,0 @@
|
|||
[package]
|
||||
name = "datasphere-gtk"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
gdk = "*"
|
||||
gio = { version = "0.14", features = ["v2_62"] }
|
||||
glib = "0.14"
|
||||
gtk-sys = "*"
|
||||
gtk = { version = "0.14", features = ["v3_24"] }
|
||||
gtk-clib = { path = "../../gtk-clib/" }
|
||||
thiserror = "1.0.20"
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
use crate::elementoj::RolulPaĝo;
|
||||
use glib::{Receiver, Sender};
|
||||
use gtk::prelude::*;
|
||||
use gtk_clib::Elemento;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Ago {}
|
||||
|
||||
pub struct Aplikaĵo {
|
||||
sendilo: Sender<Ago>,
|
||||
recivilo: Receiver<Ago>,
|
||||
evoluiga: bool,
|
||||
|
||||
vintro: gtk::ApplicationWindow,
|
||||
}
|
||||
|
||||
impl Aplikaĵo {
|
||||
pub fn kreu(
|
||||
app: >k::Application,
|
||||
sendilo: Sender<Ago>,
|
||||
recivilo: Receiver<Ago>,
|
||||
evoluiga: bool,
|
||||
) -> Self {
|
||||
let vintro = gtk::ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.title("Datasphere Kliento")
|
||||
.build();
|
||||
|
||||
let paĝo = RolulPaĝo::kreu();
|
||||
paĝo.show();
|
||||
|
||||
vintro.add(&paĝo.fenestraĵo());
|
||||
vintro.show();
|
||||
paĝo.show();
|
||||
|
||||
Self {
|
||||
sendilo,
|
||||
recivilo,
|
||||
evoluiga,
|
||||
vintro,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
use gtk::{self, prelude::*};
|
||||
use gtk_clib::{Elemento, TekstEnigo};
|
||||
|
||||
pub struct RolulPaĝo {
|
||||
fenestraĵo: gtk::Box,
|
||||
tekst_enigo: TekstEnigo,
|
||||
}
|
||||
|
||||
impl RolulPaĝo {
|
||||
pub fn kreu() -> Self {
|
||||
let fenestraĵo = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
|
||||
let tekst_enigo = TekstEnigo::kreu::<String>(
|
||||
None,
|
||||
Box::new(|_| String::from("")),
|
||||
Box::new(|v| Ok(Some(v.to_string()))),
|
||||
Box::new(|_| {}),
|
||||
);
|
||||
fenestraĵo.pack_start(&tekst_enigo.fenestraĵo(), false, false, 0);
|
||||
|
||||
Self {
|
||||
fenestraĵo,
|
||||
tekst_enigo,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
self.fenestraĵo.show();
|
||||
}
|
||||
}
|
||||
|
||||
impl Elemento for RolulPaĝo {
|
||||
fn fenestraĵo(&self) -> gtk::Widget {
|
||||
self.fenestraĵo.clone().upcast::<gtk::Widget>()
|
||||
}
|
||||
|
||||
fn montru(&self) {}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// use gtk_clib::TekstEntro;
|
||||
mod aplikajxo;
|
||||
mod elementoj;
|
||||
|
||||
use aplikajxo::Aplikaĵo;
|
||||
use gtk::{self, prelude::*};
|
||||
|
||||
fn main() {
|
||||
let evoluiga = std::env::var("EVO").map_or_else(|_| false, |_| true);
|
||||
let aplikaĵo = gtk::Application::new(
|
||||
Some("com.luminescent-dreams.datasphere"),
|
||||
Default::default(),
|
||||
);
|
||||
aplikaĵo.connect_activate(move |gtk_app| {
|
||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||
|
||||
Aplikaĵo::kreu(gtk_app, tx, rx, evoluiga);
|
||||
});
|
||||
|
||||
aplikaĵo.run();
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
|
@ -1,16 +0,0 @@
|
|||
module.exports = {
|
||||
"stories": [
|
||||
"../src/**/*.stories.mdx",
|
||||
"../src/**/*.stories.@(js|jsx|ts|tsx)"
|
||||
],
|
||||
"addons": [
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-interactions",
|
||||
"@storybook/preset-create-react-app"
|
||||
],
|
||||
"framework": "@storybook/react",
|
||||
"core": {
|
||||
"builder": "@storybook/builder-webpack5"
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
export const parameters = {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.\
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
|
@ -1,74 +0,0 @@
|
|||
{
|
||||
"name": "kampanja-kontrolado",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.0.0-rc.3",
|
||||
"@testing-library/jest-dom": "^5.16.3",
|
||||
"@testing-library/react": "^12.1.4",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/node": "^16.11.26",
|
||||
"@types/react": "^17.0.43",
|
||||
"@types/react-dom": "^17.0.14",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-scripts": "5.0.0",
|
||||
"styled-components": "^5.3.5",
|
||||
"typescript": "^4.6.3",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env BROWSER=none react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"tauri": "tauri",
|
||||
"storybook": "start-storybook -p 6006 -s public",
|
||||
"build-storybook": "build-storybook -s public"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"**/*.stories.*"
|
||||
],
|
||||
"rules": {
|
||||
"import/no-anonymous-default-export": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-actions": "^6.4.20",
|
||||
"@storybook/addon-essentials": "^6.4.20",
|
||||
"@storybook/addon-interactions": "^6.4.20",
|
||||
"@storybook/addon-links": "^6.4.20",
|
||||
"@storybook/builder-webpack5": "^6.4.20",
|
||||
"@storybook/manager-webpack5": "^6.4.20",
|
||||
"@storybook/node-logger": "^6.4.20",
|
||||
"@storybook/preset-create-react-app": "^4.1.0",
|
||||
"@storybook/react": "^6.4.20",
|
||||
"@storybook/testing-library": "0.0.9",
|
||||
"@tauri-apps/cli": "^1.0.0-rc.8",
|
||||
"@types/styled-components": "^5.1.24",
|
||||
"cross-env": "^7.0.3",
|
||||
"webpack": "^5.71.0"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 3.8 KiB |
|
@ -1,43 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 9.4 KiB |
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
|
@ -1,4 +0,0 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
WixTools
|
|
@ -1,28 +0,0 @@
|
|||
[package]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
default-run = "app"
|
||||
edition = "2021"
|
||||
rust-version = "1.57"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.0.0-rc.5", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.0.0-rc.5", features = ["api-all"] }
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||
default = [ "custom-protocol" ]
|
||||
# this feature is used used for production builds where `devPath` points to the filesystem
|
||||
# DO NOT remove this
|
||||
custom-protocol = [ "tauri/custom-protocol" ]
|
|
@ -1,67 +0,0 @@
|
|||
{
|
||||
"package": {
|
||||
"productName": "kampanja-kontrolado",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../build",
|
||||
"devPath": "http://localhost:3000",
|
||||
"beforeDevCommand": "npm run start",
|
||||
"beforeBuildCommand": "npm run build"
|
||||
},
|
||||
"tauri": {
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"identifier": "com.tauri.dev",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"resources": [],
|
||||
"externalBin": [],
|
||||
"copyright": "",
|
||||
"category": "DeveloperTool",
|
||||
"shortDescription": "",
|
||||
"longDescription": "",
|
||||
"deb": {
|
||||
"depends": [],
|
||||
"useBootstrapper": false
|
||||
},
|
||||
"macOS": {
|
||||
"frameworks": [],
|
||||
"useBootstrapper": false,
|
||||
"exceptionDomain": "",
|
||||
"signingIdentity": null,
|
||||
"providerShortName": null,
|
||||
"entitlements": null
|
||||
},
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": ""
|
||||
}
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
},
|
||||
"allowlist": {
|
||||
"all": true
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"title": "Kampanja Kontrolado",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"resizable": true,
|
||||
"fullscreen": false
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
import React, { createContext, useState } from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
import { BrowserRouter, Route, Routes, useParams } from "react-router-dom"
|
||||
import styled from "styled-components"
|
||||
|
||||
import AppProvider from "./appContext"
|
||||
import Menu from "./components/Menu"
|
||||
import PlayerListView from "./views/PlayerListView"
|
||||
|
||||
const Columns = styled.div`
|
||||
display: flex;
|
||||
padding: 1em;
|
||||
`
|
||||
|
||||
const Column = styled.div`
|
||||
width: 50%;
|
||||
`
|
||||
|
||||
const render = () => (<AppProvider>
|
||||
<h1>Numenera Datasphere</h1>
|
||||
<Columns>
|
||||
<Column>
|
||||
<Menu />
|
||||
</Column>
|
||||
<Column>
|
||||
<Routes>
|
||||
<Route path="/" element={<div>root</div>} />
|
||||
<Route path="campaign" element={<div>campaign</div>} />
|
||||
<Route path="players" element={<PlayerListView />}>
|
||||
<Route path=":name" element={<PlayerListView />} />
|
||||
</Route>
|
||||
<Route path="cyphers" element={<div>cyphers</div>} />
|
||||
<Route path="battles" element={<div>battles</div>} />
|
||||
</Routes>
|
||||
</Column>
|
||||
</Columns>
|
||||
</AppProvider>
|
||||
)
|
||||
|
||||
export default render
|
|
@ -1,28 +0,0 @@
|
|||
import React, { ReactNode } from "react";
|
||||
|
||||
import { PlayerCharacter } from "./types"
|
||||
|
||||
export type AppState = {
|
||||
playerCharacters: { [name: string]: PlayerCharacter };
|
||||
}
|
||||
|
||||
export const AppContext = React.createContext<{ state: AppState }>({state: {
|
||||
playerCharacters: {}
|
||||
}})
|
||||
|
||||
const AppProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [state, setState] = React.useState<AppState>({playerCharacters: {
|
||||
"priat": {
|
||||
name: "Priat",
|
||||
concept: "An Intuitive Jack who Explores Yesterday",
|
||||
effort: 1,
|
||||
cypherLimit: 2,
|
||||
might: { value: 12, max: 12, edge: 0 },
|
||||
speed: { value: 14, max: 14, edge: 0 },
|
||||
intellect: { value: 12, max: 12, edge: 1 },
|
||||
}}})
|
||||
|
||||
return (<AppContext.Provider value={{state}}>{children}</AppContext.Provider>)
|
||||
}
|
||||
|
||||
export default AppProvider
|
|
@ -1,14 +0,0 @@
|
|||
import React from "react"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
const MainMenu = () => (<nav>
|
||||
<ul className="menu">
|
||||
<li> <Link to="/campaign">Campaign Settings</Link> </li>
|
||||
<li> <Link to="/players">Player Characters</Link> </li>
|
||||
<li> <Link to="/cyphers">Cyphers and Artifacts</Link> </li>
|
||||
<li> <Link to="/battles">Battles</Link> </li>
|
||||
</ul>
|
||||
</nav>)
|
||||
|
||||
export default MainMenu
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
.menu {
|
||||
list-style-type: none;
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import React from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
import styled from "styled-components"
|
||||
|
||||
interface StatPoolProps {
|
||||
name: string;
|
||||
value: number;
|
||||
max: number;
|
||||
edge: number;
|
||||
}
|
||||
|
||||
const StatPool = styled.div`
|
||||
border: 1px solid black;
|
||||
`
|
||||
|
||||
const render = ({ name, value, max, edge }: StatPoolProps) => (<StatPool>
|
||||
<div>{value} / {max}</div> <div>{edge}</div>
|
||||
</StatPool>)
|
||||
|
||||
export default render
|
|
@ -1,17 +0,0 @@
|
|||
import React, { createContext, useState } from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
import { BrowserRouter, Route, Routes, useParams } from "react-router-dom"
|
||||
import styled from "styled-components"
|
||||
|
||||
import AppPage from "./AppPage"
|
||||
|
||||
const main = () => {
|
||||
ReactDOM.render(
|
||||
<BrowserRouter>
|
||||
<AppPage />
|
||||
</BrowserRouter>
|
||||
, document.getElementById('root')
|
||||
)
|
||||
}
|
||||
|
||||
main()
|
|
@ -1,28 +0,0 @@
|
|||
body {
|
||||
background-color: rgb(200, 190, 180);
|
||||
}
|
||||
|
||||
.columns {
|
||||
display: flex;
|
||||
// border: 1px solid red;
|
||||
margin: auto;
|
||||
padding: 1em;
|
||||
|
||||
> * {
|
||||
margin: 0 1em 0 1em;
|
||||
}
|
||||
|
||||
& > :first-child {
|
||||
margin: 0 1em 0 0;
|
||||
}
|
||||
& > :last-child {
|
||||
margin: 0 0 0 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.c-2 {
|
||||
> div {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
type StatPool = {
|
||||
value: number;
|
||||
max: number;
|
||||
edge: number;
|
||||
}
|
||||
|
||||
export type PlayerCharacter = {
|
||||
name: string;
|
||||
concept: string;
|
||||
effort: number;
|
||||
cypherLimit: number;
|
||||
might: StatPool;
|
||||
speed: StatPool;
|
||||
intellect: StatPool;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import React from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
|
||||
import StatPool from "../components/StatPool"
|
||||
import { PlayerCharacter } from "../types"
|
||||
|
||||
interface PlayerCharacterProps extends PlayerCharacter { }
|
||||
|
||||
const PlayerCharacter = ({ name, concept, might, speed, intellect }: PlayerCharacterProps) => {
|
||||
return (<div>
|
||||
<h2>{name}</h2>
|
||||
<div className="columns c-2">
|
||||
<div>
|
||||
<div>{concept}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<StatPool name="Might" {...might} />
|
||||
<StatPool name="Speed" {...speed} />
|
||||
<StatPool name="Intellect" {...intellect} />
|
||||
</div>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
|
||||
export default PlayerCharacter
|
|
@ -1,24 +0,0 @@
|
|||
import React, { useContext } from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
import { Link, Route, Routes, useParams } from "react-router-dom"
|
||||
|
||||
import { AppContext } from "../appContext"
|
||||
import PlayerCharacter from "./PlayerCharacter"
|
||||
|
||||
const PlayerListView = () => {
|
||||
const params = useParams()
|
||||
const { state } = useContext(AppContext)
|
||||
const character = params.name ? state.playerCharacters[params.name] : null
|
||||
|
||||
return (<div>
|
||||
<nav>
|
||||
<Link to="priat">Priat</Link> |
|
||||
<Link to="dorian">Dorian </Link> |
|
||||
<Link to="ember">Ember</Link> |
|
||||
<Link to="lise">Lise</Link>
|
||||
</nav>
|
||||
{character ? <PlayerCharacter {...character} /> : null }
|
||||
</div>);
|
||||
}
|
||||
|
||||
export default PlayerListView
|
|
@ -1,38 +0,0 @@
|
|||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
|
@ -1,26 +0,0 @@
|
|||
import React from 'react';
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
|
@ -1,22 +0,0 @@
|
|||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
|
||||
import Elektilo from "./Elektilo";
|
||||
|
||||
export default {
|
||||
title: "Elementoj/Elektilo",
|
||||
component: Elektilo,
|
||||
argTypes: {
|
||||
aktuala: {
|
||||
options: [ '', 'Adaptable', 'Articulate', 'Rugged', 'Genteel' ],
|
||||
control: { type: 'radio' },
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof Elektilo>;
|
||||
|
||||
export const ElektilRakonto: ComponentStory<typeof Elektilo> = ({ aktuala }) => <Elektilo elektoj={[
|
||||
"Adaptable",
|
||||
"Articulate",
|
||||
"Rugged",
|
||||
"Genteel",
|
||||
]} aktuala={aktuala} />;
|
|
@ -1,14 +0,0 @@
|
|||
interface Ecoj {
|
||||
elektoj: string[];
|
||||
aktuala?: string;
|
||||
};
|
||||
|
||||
const Elektilo = ({ elektoj, aktuala }: Ecoj) => <select>
|
||||
<option selected={!aktuala}></option>
|
||||
{elektoj.map((elekto) => <option selected={aktuala === elekto} value={elekto}>{elekto}</option>)}
|
||||
</select>;
|
||||
|
||||
|
||||
const bildigu = ( ecoj: Ecoj ) => <Elektilo {...ecoj} />;
|
||||
|
||||
export default bildigu;
|
|
@ -1,20 +0,0 @@
|
|||
import { ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { Ligilo, Subtitolo, Teksto, Titolo } from "./Tipografio";
|
||||
|
||||
export default {
|
||||
title: 'Elementoj/Tipografioj',
|
||||
}
|
||||
|
||||
export const TipogafiaRakontoj = () =>
|
||||
<div>
|
||||
<Titolo>Opal Harris</Titolo>
|
||||
<Subtitolo>An Adaptable Jack who Speaks with a Silver Tongue</Subtitolo>
|
||||
<div>
|
||||
<Teksto>This is the descriptor of a focus or some such</Teksto>
|
||||
</div>
|
||||
<div>
|
||||
<Ligilo destino="localhost">A link</Ligilo>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import { ReactNode } from 'react';
|
||||
import styles from 'styled-components';
|
||||
|
||||
interface TitolaEcoj { children: ReactNode };
|
||||
const TitolaStilo = styles.h1`
|
||||
font-family: Avantgarde, TeX Gyre Adventor, sans-serif;
|
||||
font-size: 24px;
|
||||
`;
|
||||
export const Titolo = ({ children }: TitolaEcoj) => <TitolaStilo>{children}</TitolaStilo>;
|
||||
|
||||
interface SubtitolaEcoj { children: ReactNode };
|
||||
const SubtitolaStilo = styles.h2`
|
||||
font-family: Avantgarde, TeX Gyre Adventor, sans-serif;
|
||||
`;
|
||||
export const Subtitolo = ({ children }: SubtitolaEcoj) => <SubtitolaStilo>{children}</SubtitolaStilo>;
|
||||
|
||||
interface TekstaEcoj { children: ReactNode };
|
||||
const TekstaStilo = styles.span`
|
||||
font-family: Avantgarde, TeX Gyre Adventor, sans-serif;
|
||||
`;
|
||||
export const Teksto = ({ children }: TekstaEcoj) => <TekstaStilo>{children}</TekstaStilo>;
|
||||
|
||||
interface LigilaEcoj { destino: string, children: ReactNode };
|
||||
const LigilaStilo = styles.span`
|
||||
font-family: Avantgarde, TeX Gyre Adventor, sans-serif;
|
||||
`;
|
||||
export const Ligilo = ({ destino, children }: LigilaEcoj) => (
|
||||
<a href={destino}><LigilaStilo>{children}</LigilaStilo></a>
|
||||
);
|
||||
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
Before Width: | Height: | Size: 2.6 KiB |
|
@ -1,99 +0,0 @@
|
|||
import { ComponentMeta } from '@storybook/react';
|
||||
|
||||
import RolulKreilo from "../RolulKreilo/RolulKreilo";
|
||||
import RolulPaĝo from "../Rolulo/Rolulo";
|
||||
import Paĝo from './Paĝo';
|
||||
|
||||
export default {
|
||||
title: 'Paĝoj/Paĝo',
|
||||
component: Paĝo,
|
||||
} as ComponentMeta<typeof Paĝo>;
|
||||
|
||||
const priaĵoj = [
|
||||
"Adaptable",
|
||||
"Articulate",
|
||||
"Genteel",
|
||||
];
|
||||
|
||||
const tipoj = [
|
||||
"Glaive",
|
||||
"Jack",
|
||||
"Nano",
|
||||
];
|
||||
|
||||
const fokusoj = [
|
||||
"Commands a Starship",
|
||||
"Builds Machines",
|
||||
"Guards the Gate",
|
||||
"Is Always Watching",
|
||||
"Speaks with a Silver Tongue",
|
||||
];
|
||||
|
||||
const opal = {
|
||||
nomo: "Opal",
|
||||
nivelo: "Tier 1",
|
||||
frazo: "An Adaptable Jack who Speaks with a Silver Tongue",
|
||||
priaĵo: "Adaptable",
|
||||
tipo: "Jack",
|
||||
fokuso: "Speaks with a Silver Tongue",
|
||||
ecGrupoj: [
|
||||
{
|
||||
eco: "Forto",
|
||||
aktuala: 11,
|
||||
maksimuma: 11,
|
||||
avantaĝo: 0,
|
||||
},
|
||||
{
|
||||
eco: "Rapideco",
|
||||
aktuala: 12,
|
||||
maksimuma: 12,
|
||||
avantaĝo: 0,
|
||||
},
|
||||
{
|
||||
eco: "Intelekto",
|
||||
aktuala: 13,
|
||||
maksimuma: 13,
|
||||
avantaĝo: 1
|
||||
}
|
||||
],
|
||||
kapabloj: [
|
||||
{ nomo: "Versatile" },
|
||||
{ nomo: "Flex Skill" },
|
||||
{ nomo: "Face Morph", kosto: "2+" },
|
||||
{ nomo: "Link Senses", kosto: "2" },
|
||||
],
|
||||
lertecoj: [
|
||||
{ nomo: "Perception", nivelo: "Trained", },
|
||||
{ nomo: "Resilient", nivelo: "Trained", },
|
||||
{ nomo: "Social Interactions", nivelo: "Trained", },
|
||||
{ nomo: "Pleasant social interactions", nivelo: "Specialized", },
|
||||
{ nomo: "Crafting, Understanding Numenera", nivelo: "Inability", },
|
||||
],
|
||||
sekvaResaniĝo: "unu ago",
|
||||
};
|
||||
|
||||
const flankMenuo = [
|
||||
"Opal",
|
||||
"Dorian",
|
||||
"Devan",
|
||||
"Jacine",
|
||||
"Janys",
|
||||
];
|
||||
|
||||
const topMenuo = [
|
||||
"Elsalutu",
|
||||
"Agordoj",
|
||||
];
|
||||
|
||||
export const RolulKreilPaĝRakonto = () => (
|
||||
<Paĝo flankMenuo={flankMenuo} topMenuo={topMenuo}>
|
||||
<RolulKreilo rolulo={opal} priaĵoj={priaĵoj} tipoj={tipoj} fokusoj={fokusoj} />
|
||||
</Paĝo>);
|
||||
|
||||
export const RulolPaĝRakonto = () => (
|
||||
<Paĝo flankMenuo={flankMenuo} topMenuo={topMenuo}>
|
||||
<RolulPaĝo rolulo={opal} />
|
||||
</Paĝo>
|
||||
);
|
||||
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
import { ReactNode } from "react";
|
||||
import styled from "styled-components";
|
||||
import { Teksto } from "../../elementoj/Tipografio/Tipografio";
|
||||
|
||||
interface Ecoj {
|
||||
flankMenuo: string[];
|
||||
topMenuo: string[];
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const FlankPanelo = styled.div`
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
`
|
||||
|
||||
const TopListo = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
`
|
||||
|
||||
const TopPanelo = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
border-bottom: 2px solid #c0a0b7;
|
||||
grid-column: 1 / span 2;
|
||||
padding: 10px;
|
||||
`
|
||||
|
||||
const PrecipaPanelo = styled.div`
|
||||
grid-row-start: 2;
|
||||
grid-column-start: 2;
|
||||
`
|
||||
|
||||
const Ujo = styled.div`
|
||||
height: 100%;
|
||||
background-color: #fcd7b7;
|
||||
color: #552511;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 150px auto;
|
||||
grid-template-rows: 40px auto;
|
||||
font-family: sans-serif;
|
||||
`;
|
||||
|
||||
const bildigu = ({ flankMenuo, topMenuo, children }: Ecoj) => <Ujo>
|
||||
<FlankPanelo>
|
||||
{flankMenuo.map((ejo) => <div><Teksto>{ejo}</Teksto></div>)}
|
||||
</FlankPanelo>
|
||||
<TopPanelo>
|
||||
<TopListo>
|
||||
{topMenuo.map((ejo) => <Teksto>{ejo}</Teksto>)}
|
||||
</TopListo>
|
||||
</TopPanelo>
|
||||
<PrecipaPanelo>
|
||||
{children}
|
||||
</PrecipaPanelo>
|
||||
</Ujo>;
|
||||
|
||||
export default bildigu;
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
import React from 'react';
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
|
||||
import RolulKreilo from "./RolulKreilo";
|
||||
|
||||
export default {
|
||||
title: "Paĝoj/RolulKreilo",
|
||||
component: RolulKreilo,
|
||||
} as ComponentMeta<typeof RolulKreilo>;
|
||||
|
||||
const priaĵoj = [
|
||||
"Adaptable",
|
||||
"Articulate",
|
||||
"Genteel",
|
||||
];
|
||||
|
||||
const tipoj = [
|
||||
"Glaive",
|
||||
"Jack",
|
||||
"Nano",
|
||||
];
|
||||
|
||||
const fokusoj = [
|
||||
"Commands a Starship",
|
||||
"Builds Machines",
|
||||
"Guards the Gate",
|
||||
"Is Always Watching",
|
||||
];
|
||||
|
||||
export const RolulKreilaRakonto = () => <RolulKreilo rolulo={{
|
||||
nomo: "Opal",
|
||||
priaĵo: "Adaptable",
|
||||
tipo: "Jack",
|
||||
fokuso: "Commands a Starship",
|
||||
}} priaĵoj={priaĵoj} tipoj={tipoj} fokusoj={fokusoj} />
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
import styled from "styled-components";
|
||||
import Elektilo from "../../elementoj/Elektilo/Elektilo";
|
||||
|
||||
interface RolulaŜablono {
|
||||
nomo?: string;
|
||||
priaĵo?: string;
|
||||
tipo?: string;
|
||||
fokuso?: string;
|
||||
}
|
||||
|
||||
interface FrazaKreilaEcoj {
|
||||
priaĵo?: string;
|
||||
tipo?: string;
|
||||
fokuso?: string;
|
||||
|
||||
priaĵoj: string[];
|
||||
tipoj: string[];
|
||||
fokusoj: string[];
|
||||
}
|
||||
|
||||
const FrazaKreilo = ( { priaĵo, priaĵoj, tipo, tipoj, fokuso, fokusoj }: FrazaKreilaEcoj) => <div>
|
||||
<Elektilo elektoj={priaĵoj} aktuala={priaĵo} />
|
||||
<Elektilo elektoj={tipoj} aktuala={tipo} />
|
||||
who
|
||||
<Elektilo elektoj={fokusoj} aktuala={fokuso} />
|
||||
</div>;
|
||||
|
||||
interface RolulKreilaEcoj {
|
||||
rolulo: RolulaŜablono;
|
||||
priaĵoj: string[];
|
||||
tipoj: string[];
|
||||
fokusoj: string[];
|
||||
}
|
||||
|
||||
const RolulKreilo = ( { rolulo, priaĵoj, tipoj, fokusoj }: RolulKreilaEcoj ) => <>
|
||||
<input type="text" value={rolulo.nomo}></input>
|
||||
<FrazaKreilo
|
||||
priaĵo={rolulo.priaĵo}
|
||||
tipo={rolulo.tipo}
|
||||
fokuso={rolulo.fokuso}
|
||||
priaĵoj={priaĵoj}
|
||||
tipoj={tipoj}
|
||||
fokusoj={fokusoj}
|
||||
/>
|
||||
</>;
|
||||
|
||||
const Ujo = styled.div`
|
||||
`;
|
||||
|
||||
const bildigu = ( ecoj: RolulKreilaEcoj ) => <Ujo><RolulKreilo {...ecoj} /></Ujo>;
|
||||
|
||||
export default bildigu;
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
import React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
|
||||
import RolulPaĝo from "./Rolulo";
|
||||
|
||||
export default {
|
||||
title: 'Paĝoj/RolulPaĝo',
|
||||
component: RolulPaĝo,
|
||||
} as ComponentMeta<typeof RolulPaĝo>;
|
||||
|
||||
const opal = {
|
||||
nomo: "Opal",
|
||||
nivelo: "(Tier 1)",
|
||||
frazo: "An Adaptable Jack who Speaks with a Silver Tongue",
|
||||
ecGrupoj: [
|
||||
{
|
||||
eco: "Forto",
|
||||
aktuala: 11,
|
||||
maksimuma: 11,
|
||||
avantaĝo: 0,
|
||||
},
|
||||
{
|
||||
eco: "Rapideco",
|
||||
aktuala: 12,
|
||||
maksimuma: 12,
|
||||
avantaĝo: 0,
|
||||
},
|
||||
{
|
||||
eco: "Intelekto",
|
||||
aktuala: 13,
|
||||
maksimuma: 13,
|
||||
avantaĝo: 1
|
||||
}
|
||||
],
|
||||
kapabloj: [
|
||||
{ nomo: "Versatile" },
|
||||
{ nomo: "Flex Skill" },
|
||||
{ nomo: "Face Morph", kosto: "2+" },
|
||||
{ nomo: "Link Senses", kosto: "2" },
|
||||
],
|
||||
lertecoj: [
|
||||
{ nomo: "Perception", nivelo: "Trained", },
|
||||
{ nomo: "Resilient", nivelo: "Trained", },
|
||||
{ nomo: "Social Interactions", nivelo: "Trained", },
|
||||
{ nomo: "Pleasant social interactions", nivelo: "Specialized", },
|
||||
{ nomo: "Crafting, Understanding Numenera", nivelo: "Inability", },
|
||||
],
|
||||
sekvaResaniĝo: "unu ago",
|
||||
};
|
||||
|
||||
|
||||
export const RolulPaĝRakonto = () => <RolulPaĝo rolulo={opal} />
|
|
@ -1,58 +0,0 @@
|
|||
import styled from "styled-components"
|
||||
import { EcGrupo, Lerteco, Kapablo, Rolulo } from "../../tipoj";
|
||||
import { Titolo, Subtitolo, Teksto } from "../../elementoj/Tipografio/Tipografio";
|
||||
|
||||
const EcGrupElemento = ( { eco, aktuala, maksimuma, avantaĝo }: EcGrupo ) => <tr>
|
||||
<th key={eco}> <Teksto>{eco}</Teksto> </th>
|
||||
<td> <Teksto>{aktuala}</Teksto> <button>+</button> <button>-</button> </td>
|
||||
<td> <Teksto>{maksimuma}</Teksto> </td>
|
||||
<td> <Teksto>{avantaĝo}</Teksto> </td>
|
||||
</tr>;
|
||||
|
||||
const KapablaElemento = ( { nomo, kosto }: Kapablo ) => <>
|
||||
<dt key={nomo}> <Teksto>{nomo} {kosto && <span>({kosto})</span>}</Teksto> </dt>
|
||||
</>;
|
||||
|
||||
const LertecaElemento = ( { nomo, nivelo }: Lerteco ) => <tr>
|
||||
<th key={nomo}> <Teksto>{nomo}</Teksto> </th>
|
||||
<td> <Teksto>{nivelo}</Teksto> </td>
|
||||
</tr>;
|
||||
|
||||
interface RolulEcoj {
|
||||
rolulo: Rolulo;
|
||||
}
|
||||
|
||||
const Vico = styled.div`
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
`;
|
||||
|
||||
const RolulPaĝo = ({ rolulo: { nomo, nivelo, frazo, ecGrupoj, kapabloj, lertecoj, sekvaResaniĝo } }: RolulEcoj) =>
|
||||
<>
|
||||
<Titolo>{nomo} ({nivelo})</Titolo>
|
||||
<Subtitolo>{frazo}</Subtitolo>
|
||||
<Vico>
|
||||
<table>
|
||||
{ecGrupoj.map((eco) => <EcGrupElemento {...eco} />)}
|
||||
</table>
|
||||
<div>
|
||||
<div> Sekva Resaniĝo: {sekvaResaniĝo} </div>
|
||||
<div> <button> Resaniĝu </button> </div>
|
||||
</div>
|
||||
</Vico>
|
||||
<dl>
|
||||
{kapabloj.map((kapablo) => <KapablaElemento {...kapablo} />)}
|
||||
</dl>
|
||||
<table>
|
||||
<tbody>
|
||||
{lertecoj.map((lerteco) => <LertecaElemento {...lerteco} />)}
|
||||
</tbody>
|
||||
</table>
|
||||
</>;
|
||||
|
||||
const Ujo = styled.div`
|
||||
`;
|
||||
|
||||
const bildigu = ( ecoj: RolulEcoj ) => <Ujo><RolulPaĝo {...ecoj} /></Ujo>;
|
||||
|
||||
export default bildigu;
|
|
@ -1 +0,0 @@
|
|||
/// <reference types="react-scripts" />
|
|
@ -1,15 +0,0 @@
|
|||
import { ReportHandler } from 'web-vitals';
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
|
@ -1,5 +0,0 @@
|
|||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
|
@ -1,41 +0,0 @@
|
|||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { Button } from './Button';
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'Example/Button',
|
||||
component: Button,
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
} as ComponentMeta<typeof Button>;
|
||||
|
||||
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
|
||||
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;
|
||||
|
||||
export const Primary = Template.bind({});
|
||||
// More on args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
Primary.args = {
|
||||
primary: true,
|
||||
label: 'Button',
|
||||
};
|
||||
|
||||
export const Secondary = Template.bind({});
|
||||
Secondary.args = {
|
||||
label: 'Button',
|
||||
};
|
||||
|
||||
export const Large = Template.bind({});
|
||||
Large.args = {
|
||||
size: 'large',
|
||||
label: 'Button',
|
||||
};
|
||||
|
||||
export const Small = Template.bind({});
|
||||
Small.args = {
|
||||
size: 'small',
|
||||
label: 'Button',
|
||||
};
|
|
@ -1,48 +0,0 @@
|
|||
import React from 'react';
|
||||
import './button.css';
|
||||
|
||||
interface ButtonProps {
|
||||
/**
|
||||
* Is this the principal call to action on the page?
|
||||
*/
|
||||
primary?: boolean;
|
||||
/**
|
||||
* What background color to use
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
/**
|
||||
* How large should the button be?
|
||||
*/
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
/**
|
||||
* Button contents
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Optional click handler
|
||||
*/
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
*/
|
||||
export const Button = ({
|
||||
primary = false,
|
||||
size = 'medium',
|
||||
backgroundColor,
|
||||
label,
|
||||
...props
|
||||
}: ButtonProps) => {
|
||||
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
|
||||
style={{ backgroundColor }}
|
||||
{...props}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
};
|
|
@ -1,25 +0,0 @@
|
|||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { Header } from './Header';
|
||||
|
||||
export default {
|
||||
title: 'Example/Header',
|
||||
component: Header,
|
||||
parameters: {
|
||||
// More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
} as ComponentMeta<typeof Header>;
|
||||
|
||||
const Template: ComponentStory<typeof Header> = (args) => <Header {...args} />;
|
||||
|
||||
export const LoggedIn = Template.bind({});
|
||||
LoggedIn.args = {
|
||||
user: {
|
||||
name: 'Jane Doe',
|
||||
},
|
||||
};
|
||||
|
||||
export const LoggedOut = Template.bind({});
|
||||
LoggedOut.args = {};
|
|
@ -1,56 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Button } from './Button';
|
||||
import './header.css';
|
||||
|
||||
type User = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
interface HeaderProps {
|
||||
user?: User;
|
||||
onLogin: () => void;
|
||||
onLogout: () => void;
|
||||
onCreateAccount: () => void;
|
||||
}
|
||||
|
||||
export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => (
|
||||
<header>
|
||||
<div className="wrapper">
|
||||
<div>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
<path
|
||||
d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z"
|
||||
fill="#555AB9"
|
||||
/>
|
||||
<path
|
||||
d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z"
|
||||
fill="#91BAF8"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<h1>Acme</h1>
|
||||
</div>
|
||||
<div>
|
||||
{user ? (
|
||||
<>
|
||||
<span className="welcome">
|
||||
Welcome, <b>{user.name}</b>!
|
||||
</span>
|
||||
<Button size="small" onClick={onLogout} label="Log out" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button size="small" onClick={onLogin} label="Log in" />
|
||||
<Button primary size="small" onClick={onCreateAccount} label="Sign up" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
|
@ -1,211 +0,0 @@
|
|||
import { Meta } from '@storybook/addon-docs';
|
||||
import Code from './assets/code-brackets.svg';
|
||||
import Colors from './assets/colors.svg';
|
||||
import Comments from './assets/comments.svg';
|
||||
import Direction from './assets/direction.svg';
|
||||
import Flow from './assets/flow.svg';
|
||||
import Plugin from './assets/plugin.svg';
|
||||
import Repo from './assets/repo.svg';
|
||||
import StackAlt from './assets/stackalt.svg';
|
||||
|
||||
<Meta title="Example/Introduction" />
|
||||
|
||||
<style>{`
|
||||
.subheading {
|
||||
--mediumdark: '#999999';
|
||||
font-weight: 900;
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
letter-spacing: 6px;
|
||||
line-height: 24px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 12px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.link-list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: 620px) {
|
||||
.link-list {
|
||||
row-gap: 20px;
|
||||
column-gap: 20px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (-ms-high-contrast:none) {
|
||||
.link-list {
|
||||
display: -ms-grid;
|
||||
-ms-grid-columns: 1fr 1fr;
|
||||
-ms-grid-rows: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.link-item {
|
||||
display: block;
|
||||
padding: 20px 30px 20px 15px;
|
||||
border: 1px solid #00000010;
|
||||
border-radius: 5px;
|
||||
transition: background 150ms ease-out, border 150ms ease-out, transform 150ms ease-out;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.link-item:hover {
|
||||
border-color: #1EA7FD50;
|
||||
transform: translate3d(0, -3px, 0);
|
||||
box-shadow: rgba(0, 0, 0, 0.08) 0 3px 10px 0;
|
||||
}
|
||||
|
||||
.link-item:active {
|
||||
border-color: #1EA7FD;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.link-item strong {
|
||||
font-weight: 700;
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.link-item img {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
margin-right: 15px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.link-item span {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
display: inline-block;
|
||||
border-radius: 1em;
|
||||
font-size: 11px;
|
||||
line-height: 12px;
|
||||
font-weight: 700;
|
||||
background: #E7FDD8;
|
||||
color: #66BF3C;
|
||||
padding: 4px 12px;
|
||||
margin-right: 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.tip-wrapper {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.tip-wrapper code {
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
`}</style>
|
||||
|
||||
# Welcome to Storybook
|
||||
|
||||
Storybook helps you build UI components in isolation from your app's business logic, data, and context.
|
||||
That makes it easy to develop hard-to-reach states. Save these UI states as **stories** to revisit during development, testing, or QA.
|
||||
|
||||
Browse example stories now by navigating to them in the sidebar.
|
||||
View their code in the `src/stories` directory to learn how they work.
|
||||
We recommend building UIs with a [**component-driven**](https://componentdriven.org) process starting with atomic components and ending with pages.
|
||||
|
||||
<div className="subheading">Configure</div>
|
||||
|
||||
<div className="link-list">
|
||||
<a
|
||||
className="link-item"
|
||||
href="https://storybook.js.org/docs/react/addons/addon-types"
|
||||
target="_blank"
|
||||
>
|
||||
<img src={Plugin} alt="plugin" />
|
||||
<span>
|
||||
<strong>Presets for popular tools</strong>
|
||||
Easy setup for TypeScript, SCSS and more.
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
className="link-item"
|
||||
href="https://storybook.js.org/docs/react/configure/webpack"
|
||||
target="_blank"
|
||||
>
|
||||
<img src={StackAlt} alt="Build" />
|
||||
<span>
|
||||
<strong>Build configuration</strong>
|
||||
How to customize webpack and Babel
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
className="link-item"
|
||||
href="https://storybook.js.org/docs/react/configure/styling-and-css"
|
||||
target="_blank"
|
||||
>
|
||||
<img src={Colors} alt="colors" />
|
||||
<span>
|
||||
<strong>Styling</strong>
|
||||
How to load and configure CSS libraries
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
className="link-item"
|
||||
href="https://storybook.js.org/docs/react/get-started/setup#configure-storybook-for-your-stack"
|
||||
target="_blank"
|
||||
>
|
||||
<img src={Flow} alt="flow" />
|
||||
<span>
|
||||
<strong>Data</strong>
|
||||
Providers and mocking for data libraries
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="subheading">Learn</div>
|
||||
|
||||
<div className="link-list">
|
||||
<a className="link-item" href="https://storybook.js.org/docs" target="_blank">
|
||||
<img src={Repo} alt="repo" />
|
||||
<span>
|
||||
<strong>Storybook documentation</strong>
|
||||
Configure, customize, and extend
|
||||
</span>
|
||||
</a>
|
||||
<a className="link-item" href="https://storybook.js.org/tutorials/" target="_blank">
|
||||
<img src={Direction} alt="direction" />
|
||||
<span>
|
||||
<strong>In-depth guides</strong>
|
||||
Best practices from leading teams
|
||||
</span>
|
||||
</a>
|
||||
<a className="link-item" href="https://github.com/storybookjs/storybook" target="_blank">
|
||||
<img src={Code} alt="code" />
|
||||
<span>
|
||||
<strong>GitHub project</strong>
|
||||
View the source and add issues
|
||||
</span>
|
||||
</a>
|
||||
<a className="link-item" href="https://discord.gg/storybook" target="_blank">
|
||||
<img src={Comments} alt="comments" />
|
||||
<span>
|
||||
<strong>Discord chat</strong>
|
||||
Chat with maintainers and the community
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="tip-wrapper">
|
||||
<span className="tip">Tip</span>Edit the Markdown in{' '}
|
||||
<code>src/stories/Introduction.stories.mdx</code>
|
||||
</div>
|
|
@ -1,26 +0,0 @@
|
|||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { within, userEvent } from '@storybook/testing-library';
|
||||
import { Page } from './Page';
|
||||
|
||||
export default {
|
||||
title: 'Example/Page',
|
||||
component: Page,
|
||||
parameters: {
|
||||
// More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
} as ComponentMeta<typeof Page>;
|
||||
|
||||
const Template: ComponentStory<typeof Page> = (args) => <Page {...args} />;
|
||||
|
||||
export const LoggedOut = Template.bind({});
|
||||
|
||||
export const LoggedIn = Template.bind({});
|
||||
|
||||
// More on interaction testing: https://storybook.js.org/docs/react/writing-tests/interaction-testing
|
||||
LoggedIn.play = async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const loginButton = await canvas.getByRole('button', { name: /Log in/i });
|
||||
await userEvent.click(loginButton);
|
||||
};
|
|
@ -1,73 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Header } from './Header';
|
||||
import './page.css';
|
||||
|
||||
type User = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const Page: React.VFC = () => {
|
||||
const [user, setUser] = React.useState<User>();
|
||||
|
||||
return (
|
||||
<article>
|
||||
<Header
|
||||
user={user}
|
||||
onLogin={() => setUser({ name: 'Jane Doe' })}
|
||||
onLogout={() => setUser(undefined)}
|
||||
onCreateAccount={() => setUser({ name: 'Jane Doe' })}
|
||||
/>
|
||||
|
||||
<section>
|
||||
<h2>Pages in Storybook</h2>
|
||||
<p>
|
||||
We recommend building UIs with a{' '}
|
||||
<a href="https://componentdriven.org" target="_blank" rel="noopener noreferrer">
|
||||
<strong>component-driven</strong>
|
||||
</a>{' '}
|
||||
process starting with atomic components and ending with pages.
|
||||
</p>
|
||||
<p>
|
||||
Render pages with mock data. This makes it easy to build and review page states without
|
||||
needing to navigate to them in your app. Here are some handy patterns for managing page
|
||||
data in Storybook:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Use a higher-level connected component. Storybook helps you compose such data from the
|
||||
"args" of child component stories
|
||||
</li>
|
||||
<li>
|
||||
Assemble data in the page component from your services. You can mock these services out
|
||||
using Storybook.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Get a guided tutorial on component-driven development at{' '}
|
||||
<a href="https://storybook.js.org/tutorials/" target="_blank" rel="noopener noreferrer">
|
||||
Storybook tutorials
|
||||
</a>
|
||||
. Read more in the{' '}
|
||||
<a href="https://storybook.js.org/docs" target="_blank" rel="noopener noreferrer">
|
||||
docs
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<div className="tip-wrapper">
|
||||
<span className="tip">Tip</span> Adjust the width of the canvas with the{' '}
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path
|
||||
d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
|
||||
id="a"
|
||||
fill="#999"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
Viewports addon in the toolbar
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48" version="1.1" viewBox="0 0 48 48"><title>illustration/code-brackets</title><g id="illustration/code-brackets" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><path id="Combined-Shape" fill="#87E6E5" d="M11.4139325,12 C11.7605938,12 12,12.5059743 12,13.3779712 L12,17.4951758 L6.43502246,23.3839989 C5.85499251,23.9978337 5.85499251,25.0021663 6.43502246,25.6160011 L12,31.5048242 L12,35.6220288 C12,36.4939606 11.7605228,37 11.4139325,37 C11.2725831,37 11.1134406,36.9158987 10.9453839,36.7379973 L0.435022463,25.6160011 C-0.145007488,25.0021663 -0.145007488,23.9978337 0.435022463,23.3839989 L10.9453839,12.2620027 C11.1134051,12.0841663 11.2725831,12 11.4139325,12 Z M36.5860675,12 C36.7274169,12 36.8865594,12.0841013 37.0546161,12.2620027 L47.5649775,23.3839989 C48.1450075,23.9978337 48.1450075,25.0021663 47.5649775,25.6160011 L37.0546161,36.7379973 C36.8865949,36.9158337 36.7274169,37 36.5860675,37 C36.2394062,37 36,36.4940257 36,35.6220288 L36,31.5048242 L41.5649775,25.6160011 C42.1450075,25.0021663 42.1450075,23.9978337 41.5649775,23.3839989 L36,17.4951758 L36,13.3779712 C36,12.5060394 36.2394772,12 36.5860675,12 Z"/><rect id="Rectangle-7-Copy-5" width="35.57" height="4" x="5.009" y="22.662" fill="#A0DB77" rx="2" transform="translate(22.793959, 24.662305) rotate(-75.000000) translate(-22.793959, -24.662305)"/></g></svg>
|
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 8.3 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48" version="1.1" viewBox="0 0 48 48"><title>illustration/comments</title><g id="illustration/comments" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><path id="Path" fill="#96D07C" d="M2.52730803,17.9196415 C2.44329744,17.9745167 2.36370847,18.000488 2.29303375,18.000488 C2.1197031,18.000488 2,17.8443588 2,17.5752855 L2,4 C2,1.790861 3.790861,3.23296945e-13 6,3.23296945e-13 L33.9995117,3.23296945e-13 C36.2086507,3.23296945e-13 37.9995117,1.790861 37.9995117,4 L37.9995117,9.999512 C37.9995117,12.208651 36.2086507,13.999512 33.9995117,13.999512 L8,13.999512 C7.83499225,13.999512 7.6723181,13.9895206 7.51254954,13.9701099 L2.52730803,17.9196415 Z"/><path id="Path" fill="#73E1E0" d="M7.51066,44.9703679 L2.52730803,47.9186655 C2.44329744,47.9735407 2.36370847,47.999512 2.29303375,47.999512 C2.1197031,47.999512 2,47.8433828 2,47.5743095 L2,35 C2,32.790861 3.790861,31 6,31 L26,31 C28.209139,31 30,32.790861 30,35 L30,41 C30,43.209139 28.209139,45 26,45 L8,45 C7.8343417,45 7.67103544,44.9899297 7.51066,44.9703679 Z"/><path id="Path" fill="#FFD476" d="M46,19.5 L46,33.0747975 C46,33.3438708 45.8802969,33.5 45.7069663,33.5 C45.6362915,33.5 45.5567026,33.4740287 45.472692,33.4191535 L40.4887103,29.4704446 C40.3285371,29.489956 40.1654415,29.5 40,29.5 L18,29.5 C15.790861,29.5 14,27.709139 14,25.5 L14,19.5 C14,17.290861 15.790861,15.5 18,15.5 L42,15.5 C44.209139,15.5 46,17.290861 46,19.5 Z"/></g></svg>
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48" version="1.1" viewBox="0 0 48 48"><title>illustration/direction</title><g id="illustration/direction" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><path id="Combined-Shape" fill="#FFD476" d="M23.4917015,33.6030641 L2.93840258,31.4321033 C2.38917316,31.3740904 1.99096346,30.8818233 2.04897631,30.3325939 C2.0747515,30.0885705 2.18934861,29.8625419 2.37095722,29.6975265 L34.2609105,0.721285325 C34.6696614,0.349881049 35.3021022,0.38015648 35.6735064,0.788907393 C35.9232621,1.06377731 36.0001133,1.45442096 35.8730901,1.80341447 L24.5364357,32.9506164 C24.3793473,33.3822133 23.9484565,33.6513092 23.4917015,33.6030641 L23.4917015,33.6030641 Z"/><path id="Combined-Shape-Copy" fill="#FFC445" d="M24.3163597,33.2881029 C24.0306575,33.0138462 23.9337246,32.5968232 24.069176,32.2246735 L35.091923,1.9399251 C35.2266075,1.56988243 35.5659249,1.31333613 35.9586669,1.28460955 C36.5094802,1.24432106 36.9886628,1.65818318 37.0289513,2.20899647 L40.2437557,46.1609256 C40.2644355,46.4436546 40.1641446,46.7218752 39.9678293,46.9263833 C39.5853672,47.3248067 38.9523344,47.3377458 38.5539111,46.9552837 L24.3163597,33.2881029 L24.3163597,33.2881029 Z"/></g></svg>
|
Before Width: | Height: | Size: 1.3 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48" version="1.1" viewBox="0 0 48 48"><title>illustration/flow</title><g id="illustration/flow" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><path id="Combined-Shape" fill="#79C9FC" fill-rule="nonzero" d="M30,29 C32.7614237,29 35,26.7614237 35,24 C35,14.6111593 27.3888407,7 18,7 C8.61115925,7 1,14.6111593 1,24 C1,33.3888407 8.61115925,41 18,41 C19.3333404,41 20.6447683,40.8466238 21.9154603,40.5471706 C19.5096374,39.3319645 17.5510566,37.8612875 16.0456579,36.1314815 C14.1063138,33.9030427 12.769443,31.0725999 12.0293806,27.6556449 C11.360469,26.565281 11,25.3082308 11,24 C11,20.1340068 14.1340068,17 18,17 C21.8659932,17 25,20.1340068 25,24 C25,26.125 27.7040312,29 30,29 Z"/><path id="Combined-Shape-Copy" fill="#FFC445" fill-rule="nonzero" d="M42,29 C44.7614237,29 47,26.7614237 47,24 C47,14.6111593 39.3888407,7 30,7 C20.6111593,7 13,14.6111593 13,24 C13,33.3888407 20.6111593,41 30,41 C31.3333404,41 32.6447683,40.8466238 33.9154603,40.5471706 C31.5096374,39.3319645 29.4051056,37.9781963 28.0456579,36.1314815 C26.0625,33.4375 23,27.1875 23,24 C23,20.1340068 26.1340068,17 30,17 C33.8659932,17 37,20.1340068 37,24 C37.02301,26.3435241 39.7040312,29 42,29 Z" transform="translate(30.000000, 24.000000) scale(-1, -1) translate(-30.000000, -24.000000)"/></g></svg>
|
Before Width: | Height: | Size: 1.4 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48" version="1.1" viewBox="0 0 48 48"><title>illustration/plugin</title><g id="illustration/plugin" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><path id="Combined-Shape" fill="#79C9FC" d="M26,15.3994248 C26,15.4091303 26,15.4188459 26,15.4285714 L26,21.4694881 C25.8463595,21.4969567 25.6941676,21.51275 25.5873784,21.51275 C25.4974117,21.51275 25.4230979,21.4768034 25.377756,21.4206259 L25.2660784,21.2822603 L25.1317423,21.1657666 C24.2436317,20.3956144 23.100098,19.9633214 21.895551,19.9633214 C19.2039137,19.9633214 17,22.1075558 17,24.7804643 C17,27.4533728 19.2039137,29.5976071 21.895551,29.5976071 C23.1972122,29.5976071 24.3149423,29.2878193 25.1231445,28.3613697 C25.4542273,27.9818463 25.568273,27.9073214 25.5873784,27.9073214 C25.681532,27.9073214 25.8352452,27.9239643 26,27.9524591 L26,32.5714286 C26,32.5811541 26,32.5908697 26,32.6005752 L26,33 C26,35.209139 24.209139,37 22,37 L4,37 C1.790861,37 0,35.209139 0,33 L0,15 C0,12.790861 1.790861,11 4,11 L22,11 C24.209139,11 26,12.790861 26,15 L26,15.3994248 Z"/><path id="Path" fill="#87E6E5" d="M27.9998779,32.5714286 C27.9998779,33.3604068 28.6572726,34 29.4682101,34 L46.5315458,34 C47.3424832,34 47.9998779,33.3604068 47.9998779,32.5714286 L47.9998779,15.4285714 C47.9998779,14.6395932 47.3424832,14 46.5315458,14 L29.4682101,14 C28.6572726,14 27.9998779,14.6395932 27.9998779,15.4285714 L27.9998779,21.8355216 C27.9334367,22.2650514 27.8567585,22.6454496 27.746391,22.8084643 C27.4245309,23.2838571 26.2402709,23.51275 25.5873784,23.51275 C24.8705773,23.51275 24.2322714,23.1857725 23.8214379,22.6767605 C23.3096996,22.2329909 22.6349941,21.9633214 21.895551,21.9633214 C20.2963823,21.9633214 19,23.2245992 19,24.7804643 C19,26.3363293 20.2963823,27.5976071 21.895551,27.5976071 C22.5398535,27.5976071 23.2399343,27.477727 23.6160247,27.0466112 C24.1396029,26.4464286 24.7367044,25.9073214 25.5873784,25.9073214 C26.2402709,25.9073214 27.5912951,26.1766031 27.8226692,26.6116071 C27.8819199,26.7230038 27.9403239,26.921677 27.9998779,27.1556219 L27.9998779,32.5714286 Z"/></g></svg>
|
Before Width: | Height: | Size: 2.1 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48" version="1.1" viewBox="0 0 48 48"><title>illustration/repo</title><g id="illustration/repo" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><path id="Rectangle-62-Copy" fill="#B7F0EF" d="M27.2217723,9.04506931 L41.2217723,6.2682098 C43.3886973,5.83840648 45.4937616,7.2466219 45.9235649,9.41354696 C45.9743993,9.66983721 46,9.93049166 46,10.1917747 L46,32.581381 C46,34.4904961 44.650862,36.1335143 42.7782277,36.5049459 L28.7782277,39.2818054 C26.6113027,39.7116087 24.5062384,38.3033933 24.0764351,36.1364682 C24.0256007,35.880178 24,35.6195235 24,35.3582405 L24,12.9686342 C24,11.0595191 25.349138,9.4165009 27.2217723,9.04506931 Z" opacity=".7"/><path id="Combined-Shape" fill="#87E6E5" d="M6.77822775,6.2682098 L20.7782277,9.04506931 C22.650862,9.4165009 24,11.0595191 24,12.9686342 L24,35.3582405 C24,37.5673795 22.209139,39.3582405 20,39.3582405 C19.738717,39.3582405 19.4780625,39.3326398 19.2217723,39.2818054 L5.22177225,36.5049459 C3.34913798,36.1335143 2,34.4904961 2,32.581381 L2,10.1917747 C2,7.98263571 3.790861,6.19177471 6,6.19177471 C6.26128305,6.19177471 6.5219375,6.21737537 6.77822775,6.2682098 Z"/><path id="Rectangle-63-Copy-2" fill="#61C1FD" d="M22,10 C23.1666667,10.2291667 24.0179036,10.625 24.5537109,11.1875 C25.0895182,11.75 25.5716146,12.875 26,14.5625 C26,29.3020833 26,37.5208333 26,39.21875 C26,40.9166667 26.4241536,42.9583333 27.2724609,45.34375 L24.5537109,41.875 L22.9824219,45.34375 C22.327474,43.1979167 22,41.2291667 22,39.4375 C22,37.6458333 22,27.8333333 22,10 Z"/></g></svg>
|
Before Width: | Height: | Size: 1.6 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48" version="1.1" viewBox="0 0 48 48"><title>illustration/stackalt</title><g id="illustration/stackalt" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><path id="Combined-Shape" fill="#FFAE00" d="M23.8628277,0 L23.8628277,48 L3.32291648,36.2491883 L3.32155653,11.9499781 L23.8628277,0 Z M23.8670509,0 L44.408322,11.9499781 L44.4069621,36.2491883 L23.8670509,48 L23.8670509,0 Z" opacity=".196"/><path id="Rectangle-46-Copy-3" fill="#66BF3C" d="M15.8232279,19.1155258 L24.7368455,21.4714881 C29.6053842,22.7582937 33.4077423,26.5606518 34.694548,31.4291905 L37.0505103,40.3428082 C37.6150232,42.4786032 36.3412474,44.6676353 34.2054524,45.2321482 C33.5569474,45.4035549 32.87575,45.4091235 32.2245294,45.2483418 L23.3459013,43.0562718 C18.2976962,41.809906 14.3561301,37.8683399 13.1097642,32.8201348 L10.9176943,23.9415066 C10.3881737,21.7967682 11.6975664,19.6288529 13.8423049,19.0993322 C14.4935255,18.9385505 15.1747229,18.9441191 15.8232279,19.1155258 Z" opacity=".5" transform="translate(23.999997, 32.166058) rotate(-45.000000) translate(-23.999997, -32.166058)"/><path id="Rectangle-46-Copy-2" fill="#FFAE00" d="M15.8232279,11.2216893 L24.7368455,13.5776516 C29.6053842,14.8644572 33.4077423,18.6668153 34.694548,23.5353541 L37.0505103,32.4489717 C37.6150232,34.5847667 36.3412474,36.7737988 34.2054524,37.3383117 C33.5569474,37.5097184 32.87575,37.515287 32.2245294,37.3545053 L23.3459013,35.1624353 C18.2976962,33.9160695 14.3561301,29.9745034 13.1097642,24.9262983 L10.9176943,16.0476701 C10.3881737,13.9029317 11.6975664,11.7350164 13.8423049,11.2054957 C14.4935255,11.044714 15.1747229,11.0502826 15.8232279,11.2216893 Z" opacity=".5" transform="translate(23.999997, 24.272222) rotate(-45.000000) translate(-23.999997, -24.272222)"/><path id="Rectangle-46-Copy" fill="#FC521F" d="M15.8232279,3.32785281 L24.7368455,5.68381509 C29.6053842,6.97062075 33.4077423,10.7729788 34.694548,15.6415176 L37.0505103,24.5551352 C37.6150232,26.6909302 36.3412474,28.8799623 34.2054524,29.4444752 C33.5569474,29.6158819 32.87575,29.6214505 32.2245294,29.4606688 L23.3459013,27.2685988 C18.2976962,26.022233 14.3561301,22.0806669 13.1097642,17.0324618 L10.9176943,8.15383364 C10.3881737,6.00909519 11.6975664,3.84117987 13.8423049,3.31165925 C14.4935255,3.15087753 15.1747229,3.15644615 15.8232279,3.32785281 Z" opacity=".5" transform="translate(23.999997, 16.378385) rotate(-45.000000) translate(-23.999997, -16.378385)"/></g></svg>
|
Before Width: | Height: | Size: 2.5 KiB |
|
@ -1,30 +0,0 @@
|
|||
.storybook-button {
|
||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-weight: 700;
|
||||
border: 0;
|
||||
border-radius: 3em;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
}
|
||||
.storybook-button--primary {
|
||||
color: white;
|
||||
background-color: #1ea7fd;
|
||||
}
|
||||
.storybook-button--secondary {
|
||||
color: #333;
|
||||
background-color: transparent;
|
||||
box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
|
||||
}
|
||||
.storybook-button--small {
|
||||
font-size: 12px;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
.storybook-button--medium {
|
||||
font-size: 14px;
|
||||
padding: 11px 20px;
|
||||
}
|
||||
.storybook-button--large {
|
||||
font-size: 16px;
|
||||
padding: 12px 24px;
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
.wrapper {
|
||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
svg {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 900;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
margin: 6px 0 6px 10px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
button + button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
section {
|
||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
padding: 48px 20px;
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
section h2 {
|
||||
font-weight: 900;
|
||||
font-size: 32px;
|
||||
line-height: 1;
|
||||
margin: 0 0 4px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
section p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
section a {
|
||||
text-decoration: none;
|
||||
color: #1ea7fd;
|
||||
}
|
||||
|
||||
section ul {
|
||||
padding-left: 30px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
section li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
section .tip {
|
||||
display: inline-block;
|
||||
border-radius: 1em;
|
||||
font-size: 11px;
|
||||
line-height: 12px;
|
||||
font-weight: 700;
|
||||
background: #e7fdd8;
|
||||
color: #66bf3c;
|
||||
padding: 4px 12px;
|
||||
margin-right: 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
section .tip-wrapper {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
section .tip-wrapper svg {
|
||||
display: inline-block;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
margin-right: 4px;
|
||||
vertical-align: top;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
section .tip-wrapper svg path {
|
||||
fill: #1ea7fd;
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
export interface EcGrupo {
|
||||
eco: string;
|
||||
aktuala: number;
|
||||
maksimuma: number;
|
||||
avantaĝo: number;
|
||||
}
|
||||
|
||||
export interface Lerteco {
|
||||
nomo: string;
|
||||
nivelo: string;
|
||||
}
|
||||
|
||||
export interface Kapablo {
|
||||
nomo: string;
|
||||
kosto?: string;
|
||||
}
|
||||
|
||||
export interface Rolulo {
|
||||
nomo: string;
|
||||
nivelo: string;
|
||||
frazo: string;
|
||||
ecGrupoj: EcGrupo[];
|
||||
kapabloj: Kapablo[];
|
||||
lertecoj: Lerteco[];
|
||||
sekvaResaniĝo: string;
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
[package]
|
||||
name = "kampanja-kontrolada-servilo"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1" }
|
||||
rand = { version = "0.8" }
|
||||
rusqlite = { version = "0.26" }
|
||||
sha2 = { version = "0.10" }
|
||||
thiserror = { version = "1" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
uuid = { version = "0.8", features = ["v4"] }
|
||||
warp = { version = "0.3" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = { version = "3" }
|
|
@ -1,5 +0,0 @@
|
|||
|
||||
.PHONY: all
|
||||
|
||||
all:
|
||||
cargo build
|
|
@ -1,461 +0,0 @@
|
|||
use crate::datumbazo::Datumbazo;
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use rusqlite::{
|
||||
params,
|
||||
types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef},
|
||||
OptionalExtension, ToSql,
|
||||
};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::Infallible,
|
||||
str::FromStr,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use uuid::{adapter::Hyphenated, Uuid};
|
||||
use warp::{reject, reject::Reject, Filter, Rejection};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct SesiaĴetono(String);
|
||||
|
||||
impl From<&str> for SesiaĴetono {
|
||||
fn from(s: &str) -> Self {
|
||||
SesiaĴetono(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SesiaĴetono {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(SesiaĴetono(s.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SesiaĴetono> for String {
|
||||
fn from(s: SesiaĴetono) -> Self {
|
||||
s.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct UzantIdentigilo(String);
|
||||
|
||||
impl From<&str> for UzantIdentigilo {
|
||||
fn from(s: &str) -> Self {
|
||||
UzantIdentigilo(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UzantIdentigilo> for String {
|
||||
fn from(s: UzantIdentigilo) -> Self {
|
||||
s.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for UzantIdentigilo {
|
||||
fn column_result(val: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
match val {
|
||||
ValueRef::Text(t) => Ok(UzantIdentigilo::from(
|
||||
String::from_utf8(Vec::from(t)).unwrap().as_ref(),
|
||||
)),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Uzantnomo(String);
|
||||
|
||||
impl From<&str> for Uzantnomo {
|
||||
fn from(s: &str) -> Self {
|
||||
Uzantnomo(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Uzantnomo> for String {
|
||||
fn from(s: &Uzantnomo) -> Self {
|
||||
s.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Uzantnomo> for String {
|
||||
fn from(s: Uzantnomo) -> Self {
|
||||
s.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for Uzantnomo {
|
||||
fn column_result(val: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
match val {
|
||||
ValueRef::Text(t) => Ok(Uzantnomo::from(
|
||||
String::from_utf8(Vec::from(t)).unwrap().as_ref(),
|
||||
)),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Pasvorto(String);
|
||||
|
||||
impl Pasvorto {
|
||||
fn haku(&self, salo: &Salo) -> HakitaPasvorto {
|
||||
let mut haketilo = Sha256::new();
|
||||
haketilo.update(String::from(self).as_bytes());
|
||||
haketilo.update(salo);
|
||||
HakitaPasvorto(Vec::from(haketilo.finalize().as_slice()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Pasvorto {
|
||||
fn from(s: &str) -> Self {
|
||||
Pasvorto(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Pasvorto> for String {
|
||||
fn from(s: &Pasvorto) -> Self {
|
||||
s.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pasvorto> for String {
|
||||
fn from(s: Pasvorto) -> Self {
|
||||
s.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct HakitaPasvorto(Vec<u8>);
|
||||
|
||||
impl ToSql for HakitaPasvorto {
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>, rusqlite::Error> {
|
||||
Ok(ToSqlOutput::Borrowed(ValueRef::Blob(self.0.as_ref())))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for HakitaPasvorto {
|
||||
fn column_result(val: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
match val {
|
||||
ValueRef::Blob(t) => Ok(HakitaPasvorto(Vec::from(t))),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Salo(Vec<u8>);
|
||||
|
||||
impl AsRef<[u8]> for Salo {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for Salo {
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>, rusqlite::Error> {
|
||||
Ok(ToSqlOutput::Borrowed(ValueRef::Blob(self.0.as_ref())))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for Salo {
|
||||
fn column_result(val: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
match val {
|
||||
ValueRef::Blob(t) => Ok(Salo(Vec::from(t))),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AŭtentigoDB: Send {
|
||||
fn kreu_uzanton(&mut self, uzantnomo: Uzantnomo, pasvorto: Pasvorto) -> UzantIdentigilo;
|
||||
|
||||
fn kreu_sesion(&mut self, uzantnomo: &Uzantnomo) -> SesiaĴetono;
|
||||
|
||||
fn ŝanĝu_pasvorton(&mut self, identigilo: &UzantIdentigilo, pasvorto: Pasvorto);
|
||||
|
||||
fn aŭtentigu_per_pasvorto(
|
||||
&self,
|
||||
uzantnomo: &Uzantnomo,
|
||||
pasvorto: &Pasvorto,
|
||||
) -> Option<UzantIdentigilo>;
|
||||
|
||||
fn aŭtentigu_per_sesio(&self, ĵetono: &SesiaĴetono) -> Option<(UzantIdentigilo, Uzantnomo)>;
|
||||
|
||||
fn uzanto_ekzistas(&self, uzantnomo: Uzantnomo) -> bool;
|
||||
}
|
||||
|
||||
pub struct MemAŭtentigo {
|
||||
inversa_uzantoj: HashMap<Uzantnomo, UzantIdentigilo>,
|
||||
pasvortoj: HashMap<UzantIdentigilo, Pasvorto>,
|
||||
sesioj: HashMap<SesiaĴetono, UzantIdentigilo>,
|
||||
uzantoj: HashMap<UzantIdentigilo, Uzantnomo>,
|
||||
}
|
||||
|
||||
impl MemAŭtentigo {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inversa_uzantoj: HashMap::new(),
|
||||
pasvortoj: HashMap::new(),
|
||||
sesioj: HashMap::new(),
|
||||
uzantoj: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AŭtentigoDB for MemAŭtentigo {
|
||||
fn kreu_uzanton(&mut self, uzantnomo: Uzantnomo, pasvorto: Pasvorto) -> UzantIdentigilo {
|
||||
let uzant_id =
|
||||
UzantIdentigilo::from(format!("{}", Hyphenated::from_uuid(Uuid::new_v4())).as_str());
|
||||
self.uzantoj.insert(uzant_id.clone(), uzantnomo.clone());
|
||||
self.pasvortoj.insert(uzant_id.clone(), pasvorto);
|
||||
self.inversa_uzantoj.insert(uzantnomo, uzant_id.clone());
|
||||
uzant_id
|
||||
}
|
||||
|
||||
fn kreu_sesion(&mut self, uzantnomo: &Uzantnomo) -> SesiaĴetono {
|
||||
let identigilo = self.inversa_uzantoj.get(&uzantnomo).cloned().unwrap();
|
||||
let ĵetono =
|
||||
SesiaĴetono::from(format!("{}", Hyphenated::from_uuid(Uuid::new_v4())).as_str());
|
||||
self.sesioj.insert(ĵetono.clone(), identigilo);
|
||||
ĵetono
|
||||
}
|
||||
|
||||
fn ŝanĝu_pasvorton(&mut self, identigilo: &UzantIdentigilo, pasvorto: Pasvorto) {
|
||||
self.pasvortoj.insert(identigilo.clone(), pasvorto);
|
||||
}
|
||||
|
||||
fn aŭtentigu_per_pasvorto(
|
||||
&self,
|
||||
uzantnomo: &Uzantnomo,
|
||||
pasvorto: &Pasvorto,
|
||||
) -> Option<UzantIdentigilo> {
|
||||
self.inversa_uzantoj.get(&uzantnomo).and_then(|id| {
|
||||
self.pasvortoj.get(id).and_then(|kandidato| {
|
||||
if *pasvorto == *kandidato {
|
||||
Some(id.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn aŭtentigu_per_sesio(&self, ĵetono: &SesiaĴetono) -> Option<(UzantIdentigilo, Uzantnomo)> {
|
||||
let identigilo = self.sesioj.get(&ĵetono).cloned()?;
|
||||
let uzantnomo = self.uzantoj.get(&identigilo).cloned()?;
|
||||
Some((identigilo, uzantnomo))
|
||||
}
|
||||
|
||||
fn uzanto_ekzistas(&self, uzantnomo: Uzantnomo) -> bool {
|
||||
self.inversa_uzantoj.get(&uzantnomo).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DBAŭtentigo {
|
||||
pool: Datumbazo,
|
||||
}
|
||||
|
||||
impl DBAŭtentigo {
|
||||
pub fn kreu(pool: Datumbazo) -> Self {
|
||||
DBAŭtentigo { pool }
|
||||
}
|
||||
}
|
||||
|
||||
impl AŭtentigoDB for DBAŭtentigo {
|
||||
fn kreu_uzanton(&mut self, uzantnomo: Uzantnomo, pasvorto: Pasvorto) -> UzantIdentigilo {
|
||||
let mut konekto = self.pool.konektu().unwrap();
|
||||
let tr = konekto.transaction().unwrap();
|
||||
|
||||
let identigilo =
|
||||
UzantIdentigilo::from(format!("{}", Hyphenated::from_uuid(Uuid::new_v4())).as_str());
|
||||
|
||||
let cnt: usize = tr
|
||||
.query_row(
|
||||
"SELECT count(*) from uzantoj WHERE nomo = ?",
|
||||
params![String::from(uzantnomo.clone())],
|
||||
|row| row.get("count(*)"),
|
||||
)
|
||||
.unwrap();
|
||||
if cnt > 0 {
|
||||
panic!("uzanto jam ekzistas");
|
||||
} else {
|
||||
let salo = Salo(
|
||||
thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(10)
|
||||
.collect::<Vec<u8>>(),
|
||||
);
|
||||
let pasvorto = pasvorto.haku(&salo);
|
||||
tr.execute(
|
||||
"INSERT INTO uzantoj VALUES(?, ?, ?, ?)",
|
||||
params![
|
||||
String::from(identigilo.clone()),
|
||||
String::from(uzantnomo),
|
||||
pasvorto,
|
||||
salo
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
tr.commit().unwrap();
|
||||
|
||||
identigilo
|
||||
}
|
||||
|
||||
fn kreu_sesion(&mut self, uzantnomo: &Uzantnomo) -> SesiaĴetono {
|
||||
let ĵetono =
|
||||
SesiaĴetono::from(format!("{}", Hyphenated::from_uuid(Uuid::new_v4())).as_str());
|
||||
let mut konekto = self.pool.konektu().unwrap();
|
||||
let tr = konekto.transaction().unwrap();
|
||||
let uzanta_id: Option<String> = tr
|
||||
.query_row(
|
||||
"SELECT id FROM uzantoj WHERE nomo = ?",
|
||||
[String::from(uzantnomo.clone())],
|
||||
|row| row.get("id"),
|
||||
)
|
||||
.optional()
|
||||
.unwrap();
|
||||
match uzanta_id {
|
||||
None => panic!("uzanto ne ekzistas"),
|
||||
Some(id) => {
|
||||
tr.execute(
|
||||
"INSERT INTO sesioj VALUES(?, ?)",
|
||||
[String::from(ĵetono.clone()), id],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
tr.commit().unwrap();
|
||||
ĵetono
|
||||
}
|
||||
|
||||
fn ŝanĝu_pasvorton(&mut self, identigilo: &UzantIdentigilo, pasvorto: Pasvorto) {}
|
||||
|
||||
fn aŭtentigu_per_pasvorto(
|
||||
&self,
|
||||
uzantnomo: &Uzantnomo,
|
||||
pasvorto: &Pasvorto,
|
||||
) -> Option<UzantIdentigilo> {
|
||||
let konekto = self.pool.konektu().unwrap();
|
||||
let mut demando = konekto
|
||||
.prepare_cached("SELECT * FROM uzantoj WHERE nomo = ?")
|
||||
.unwrap();
|
||||
let rezultoj = demando.query_map(params![String::from(uzantnomo)], |row| {
|
||||
let id = row.get("id")?;
|
||||
let nomo = row.get("nomo")?;
|
||||
let pasvorto = row.get("pasvorto")?;
|
||||
let salo = row.get("pasvortsalo")?;
|
||||
Ok((id, nomo, pasvorto, salo))
|
||||
});
|
||||
|
||||
let rows: Vec<(UzantIdentigilo, Uzantnomo, HakitaPasvorto, Salo)> = match rezultoj {
|
||||
Ok(r) => {
|
||||
let mut rows = Vec::new();
|
||||
for row in r {
|
||||
let (id, nomo, pasvorto, salo) = row.unwrap();
|
||||
rows.push((id, nomo, pasvorto, salo));
|
||||
}
|
||||
rows
|
||||
}
|
||||
Err(_) => panic!("eraro en datumbazo"),
|
||||
};
|
||||
|
||||
match rows.len() {
|
||||
1 => {
|
||||
let (ref identigilo, _, ref hakita_pasvorto, ref salo) = rows[0];
|
||||
if pasvorto.haku(&salo) == *hakita_pasvorto {
|
||||
Some(identigilo.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
0 => None,
|
||||
_ => panic!("pli ol unu kongruo trovis por uzantnomo"),
|
||||
}
|
||||
}
|
||||
|
||||
fn aŭtentigu_per_sesio(&self, ĵetono: &SesiaĴetono) -> Option<(UzantIdentigilo, Uzantnomo)> {
|
||||
let konekto = self.pool.konektu().unwrap();
|
||||
konekto.query_row(
|
||||
"SELECT uzantoj.id, uzantoj.nomo FROM sesioj INNER JOIN uzantoj on sesioj.uzanto == uzantoj.id WHERE sesioj.id = ?",
|
||||
params![String::from(ĵetono.clone())],
|
||||
|row| {
|
||||
let identigilo = row.get("id")
|
||||
.map(|s: String| UzantIdentigilo::from(s.as_str())).unwrap();
|
||||
let nomo = row.get("nomo")
|
||||
.map(|s: String| Uzantnomo::from(s.as_str())).unwrap();
|
||||
Ok((identigilo, nomo))
|
||||
},
|
||||
)
|
||||
.optional()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn uzanto_ekzistas(&self, uzantnomo: Uzantnomo) -> bool {
|
||||
let konekto = self.pool.konektu().unwrap();
|
||||
let cnt: usize = konekto
|
||||
.query_row(
|
||||
"SELECT count(*) from uzantoj WHERE nomo = ?",
|
||||
params![String::from(uzantnomo.clone())],
|
||||
|row| row.get("count(*)"),
|
||||
)
|
||||
.unwrap();
|
||||
cnt > 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AŭtentigoPostulas;
|
||||
|
||||
impl Reject for AŭtentigoPostulas {}
|
||||
|
||||
pub fn kun_aŭtentigo(
|
||||
auth_ctx: Arc<RwLock<impl AŭtentigoDB + Sync>>,
|
||||
) -> impl Filter<Extract = ((UzantIdentigilo, Uzantnomo),), Error = Rejection> + Clone {
|
||||
let auth_ctx = auth_ctx.clone();
|
||||
warp::header("authentication").and_then({
|
||||
let auth_ctx = auth_ctx.clone();
|
||||
move |text: SesiaĴetono| {
|
||||
println!("teksto: {:?}", text);
|
||||
let auth_ctx = auth_ctx.clone();
|
||||
async move {
|
||||
match auth_ctx.read().unwrap().aŭtentigu_per_sesio(&text) {
|
||||
Some(salutiloj) => Ok(salutiloj),
|
||||
None => Err(reject::custom(AŭtentigoPostulas)),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
#[test]
|
||||
fn ĝi_povas_krei_uzanto() {
|
||||
let vojo = NamedTempFile::new().unwrap().into_temp_path();
|
||||
let datumbazo = Datumbazo::kreu(vojo.to_path_buf()).unwrap();
|
||||
let mut aŭtentigo = DBAŭtentigo::kreu(datumbazo);
|
||||
let identigilo =
|
||||
aŭtentigo.kreu_uzanton(Uzantnomo::from("savanni"), Pasvorto::from("abcdefg"));
|
||||
|
||||
let aŭtentita_identigilo = aŭtentigo
|
||||
.aŭtentigu_per_pasvorto(&Uzantnomo::from("savanni"), &Pasvorto::from("abcdefg"));
|
||||
assert_eq!(aŭtentita_identigilo, Some(identigilo));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ĝi_scias_kiel_uzanto_ekzistas() {
|
||||
let vojo = NamedTempFile::new().unwrap().into_temp_path();
|
||||
let datumbazo = Datumbazo::kreu(vojo.to_path_buf()).unwrap();
|
||||
let mut aŭtentigo = DBAŭtentigo::kreu(datumbazo);
|
||||
assert!(!(aŭtentigo.uzanto_ekzistas(Uzantnomo::from("savanni"))));
|
||||
|
||||
let _ = aŭtentigo.kreu_uzanton(Uzantnomo::from("savanni"), Pasvorto::from("abcdefg"));
|
||||
assert!(aŭtentigo.uzanto_ekzistas(Uzantnomo::from("savanni")));
|
||||
}
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
use rusqlite::{params, Connection};
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
pub struct ManagedConnection<'a> {
|
||||
pool: &'a Datumbazo,
|
||||
konekto: Option<Connection>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Datumbazo {
|
||||
dosierindiko: PathBuf,
|
||||
pool: Arc<Mutex<Vec<Connection>>>,
|
||||
}
|
||||
|
||||
impl Datumbazo {
|
||||
pub fn kreu(dosierindiko: PathBuf) -> Result<Datumbazo, anyhow::Error> {
|
||||
let mut konekto = Connection::open(dosierindiko.clone())?;
|
||||
|
||||
let tx = konekto.transaction()?;
|
||||
let versio: i32 = tx.pragma_query_value(None, "user_version", |r| r.get(0))?;
|
||||
println!("versio: {}", versio);
|
||||
if versio == 0 {
|
||||
tx.execute_batch(
|
||||
"CREATE TABLE uzantoj (id string primary key, nomo text, pasvorto text, pasvortsalo text);
|
||||
CREATE TABLE rajtoj (uzanto string, rajto string, foreign key(uzanto) references uzanto(id));
|
||||
CREATE TABLE sesioj (id string primary key not null, uzanto string, foreign key(uzanto) references uzantoj(id));
|
||||
PRAGMA user_version = 1;",
|
||||
)?;
|
||||
}
|
||||
let versio: i32 = tx.pragma_query_value(None, "user_version", |r| r.get(0))?;
|
||||
println!("versio: {}", versio);
|
||||
|
||||
tx.commit()?;
|
||||
|
||||
Ok(Datumbazo {
|
||||
dosierindiko,
|
||||
pool: Arc::new(Mutex::new(vec![konekto])),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn konektu<'a>(&'a self) -> Result<ManagedConnection<'a>, anyhow::Error> {
|
||||
let mut pool = self.pool.lock().unwrap();
|
||||
match pool.pop() {
|
||||
Some(konekto) => Ok(ManagedConnection {
|
||||
pool: &self,
|
||||
konekto: Some(konekto),
|
||||
}),
|
||||
None => {
|
||||
let konekto = Connection::open(self.dosierindiko.clone())?;
|
||||
Ok(ManagedConnection {
|
||||
pool: &self,
|
||||
konekto: Some(konekto),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn revenu(&self, konekto: Connection) {
|
||||
let mut pool = self.pool.lock().unwrap();
|
||||
pool.push(konekto);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ManagedConnection<'_> {
|
||||
type Target = Connection;
|
||||
|
||||
fn deref(&self) -> &Connection {
|
||||
self.konekto.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ManagedConnection<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Connection {
|
||||
self.konekto.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ManagedConnection<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.pool.revenu(self.konekto.take().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
#[test]
|
||||
fn povas_krei_uzanton() {
|
||||
let vojo = NamedTempFile::new().unwrap().into_temp_path();
|
||||
let datumbazo = Datumbazo::kreu(vojo.to_path_buf()).unwrap();
|
||||
let mut konekto = datumbazo.konektu().unwrap();
|
||||
let tr = konekto.transaction().unwrap();
|
||||
tr.execute(
|
||||
"INSERT INTO uzantoj VALUES(?, ?, ?, ?)",
|
||||
params!["abcdfeg", "mia-uzantnomo", "pasvorto", "abcdefg"],
|
||||
)
|
||||
.unwrap();
|
||||
tr.commit().unwrap();
|
||||
|
||||
let konekto = datumbazo.konektu().unwrap();
|
||||
let id: Option<String> = konekto
|
||||
.query_row(
|
||||
"SELECT id FROM uzantoj WHERE nomo = ?",
|
||||
[String::from("mia-uzantnomo")],
|
||||
|row| row.get("id"),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(id, Some(String::from("abcdfeg")));
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
use std::convert::Infallible;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use warp::{Filter, Rejection};
|
||||
|
||||
#[path = "aŭtentigo.rs"]
|
||||
mod aŭtentigo;
|
||||
use aŭtentigo::{kun_aŭtentigo, AŭtentigoDB, AŭtentigoPostulas, Pasvorto, Uzantnomo};
|
||||
|
||||
mod datumbazo;
|
||||
use datumbazo::Datumbazo;
|
||||
|
||||
mod rajtigo;
|
||||
use rajtigo::{DBRajtigo, Rajtigo, Rajto};
|
||||
|
||||
use crate::aŭtentigo::{DBAŭtentigo, UzantIdentigilo};
|
||||
|
||||
async fn traktilo_de_listigu_ludantojn(
|
||||
identigilo: UzantIdentigilo,
|
||||
rajtigo: Arc<RwLock<impl Rajtigo>>,
|
||||
) -> Result<impl warp::Reply, Rejection> {
|
||||
if rajtigo.read().unwrap().havas_rajton(identigilo, |rajtoj| {
|
||||
rajtoj.fold(false, |acc, r| acc || r == Rajto::from("admin"))
|
||||
}) {
|
||||
Ok(warp::reply::json(&vec!["Alice", "Betty", "Charles"]))
|
||||
} else {
|
||||
Err(warp::reject::not_found())
|
||||
}
|
||||
}
|
||||
|
||||
async fn traktilu_erarojn(err: Rejection) -> Result<impl warp::Reply, Infallible> {
|
||||
let kodo;
|
||||
let mesaĝo;
|
||||
if let Some(_) = err.find::<AŭtentigoPostulas>() {
|
||||
kodo = warp::http::StatusCode::UNAUTHORIZED;
|
||||
mesaĝo = "Ensalutu";
|
||||
} else {
|
||||
kodo = warp::http::StatusCode::INTERNAL_SERVER_ERROR;
|
||||
mesaĝo = "Netraktita mesaĝo";
|
||||
}
|
||||
|
||||
Ok(warp::reply::with_status(
|
||||
warp::reply::json(&mesaĝo.to_owned()),
|
||||
kodo,
|
||||
))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
let db = Datumbazo::kreu(PathBuf::from("../servilo.db")).unwrap();
|
||||
|
||||
let auth_ctx = Arc::new(RwLock::new(DBAŭtentigo::kreu(db.clone())));
|
||||
let rajtigo = Arc::new(RwLock::new(DBRajtigo::kreu(db)));
|
||||
|
||||
{
|
||||
let mut auth_ctx = auth_ctx.write().unwrap();
|
||||
let mut rajtigo = rajtigo.write().unwrap();
|
||||
if !auth_ctx.uzanto_ekzistas(Uzantnomo::from("savanni")) {
|
||||
let uzant_identigilo =
|
||||
auth_ctx.kreu_uzanton(Uzantnomo::from("savanni"), Pasvorto::from("abcdefg"));
|
||||
rajtigo.aldonu_rajtojn(
|
||||
uzant_identigilo,
|
||||
&mut [Rajto::from("admin")].iter().cloned(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let ĵetono = auth_ctx
|
||||
.write()
|
||||
.unwrap()
|
||||
.kreu_sesion(&Uzantnomo::from("savanni"));
|
||||
println!("ĵetono: {}", String::from(ĵetono));
|
||||
|
||||
let listigu_ludantojn = {
|
||||
let auth_ctx = auth_ctx.clone();
|
||||
kun_aŭtentigo(auth_ctx)
|
||||
.and(warp::path!("api" / "ludantoj"))
|
||||
.and_then({
|
||||
let rajtigo = rajtigo.clone();
|
||||
move |(identigilo, uzantnomo): (UzantIdentigilo, Uzantnomo)| {
|
||||
println!(
|
||||
"[{}] {}",
|
||||
String::from(identigilo.clone()),
|
||||
String::from(uzantnomo.clone())
|
||||
);
|
||||
let rajtigo = rajtigo.clone();
|
||||
traktilo_de_listigu_ludantojn(identigilo, rajtigo)
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let filter = listigu_ludantojn.recover(traktilu_erarojn);
|
||||
|
||||
let server = warp::serve(filter);
|
||||
server
|
||||
.run(SocketAddr::new(
|
||||
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||
8001,
|
||||
))
|
||||
.await;
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
use crate::{aŭtentigo::UzantIdentigilo, datumbazo::Datumbazo};
|
||||
use rusqlite::{params, OptionalExtension};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Rajto(String);
|
||||
|
||||
impl From<&str> for Rajto {
|
||||
fn from(s: &str) -> Self {
|
||||
Rajto(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rajto> for String {
|
||||
fn from(s: Rajto) -> Self {
|
||||
s.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Rajtigo {
|
||||
fn havas_rajton<'a, F>(&'a self, identigilo: UzantIdentigilo, provo: F) -> bool
|
||||
where
|
||||
F: FnOnce(&mut (dyn Iterator<Item = Rajto>)) -> bool;
|
||||
fn rajtoj(&self, identigilo: &UzantIdentigilo) -> Vec<Rajto>;
|
||||
fn aldonu_rajtojn(
|
||||
&mut self,
|
||||
identigilo: UzantIdentigilo,
|
||||
rajtoj: &mut (dyn Iterator<Item = Rajto>),
|
||||
);
|
||||
fn foriru_rajtoj(
|
||||
&mut self,
|
||||
identigilo: UzantIdentigilo,
|
||||
rajtoj: &mut (dyn Iterator<Item = Rajto>),
|
||||
);
|
||||
fn foriru_uzanto(&mut self, identigilo: UzantIdentigilo);
|
||||
}
|
||||
|
||||
/*
|
||||
pub struct MemRajtigo {
|
||||
rajtoj: HashMap<UzantIdentigilo, HashSet<Rajto>>,
|
||||
}
|
||||
|
||||
impl MemRajtigo {
|
||||
pub fn new() -> Self {
|
||||
MemRajtigo {
|
||||
rajtoj: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rajtigo for MemRajtigo {
|
||||
fn havas_rajton<'a, F>(&'a self, identigilo: UzantIdentigilo, provo: F) -> bool
|
||||
where
|
||||
F: FnOnce(&mut (dyn Iterator<Item = Rajto> + 'a)) -> bool,
|
||||
{
|
||||
match self.rajtoj.get(&identigilo) {
|
||||
Some(rajtoj) => provo(&mut rajtoj.clone().into_iter()),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
fn rajtoj(&self, identigilo: &UzantIdentigilo) -> Vec<Rajto> {
|
||||
self.rajtoj
|
||||
.get(&identigilo)
|
||||
.map(|r| r.into_iter().cloned().collect::<Vec<Rajto>>())
|
||||
.unwrap_or(vec![])
|
||||
}
|
||||
fn aldonu_rajtoj(
|
||||
&mut self,
|
||||
identigilo: UzantIdentigilo,
|
||||
rajtoj: &mut (dyn Iterator<Item = Rajto>),
|
||||
) {
|
||||
let valoro = self.rajtoj.entry(identigilo).or_insert(HashSet::new());
|
||||
|
||||
while let Some(r) = rajtoj.next() {
|
||||
valoro.insert(r);
|
||||
}
|
||||
}
|
||||
fn foriru_rajtoj(
|
||||
&mut self,
|
||||
identigilo: UzantIdentigilo,
|
||||
rajtoj: &mut (dyn Iterator<Item = Rajto>),
|
||||
) {
|
||||
let valoro = self.rajtoj.entry(identigilo).or_insert(HashSet::new());
|
||||
|
||||
while let Some(r) = rajtoj.next() {
|
||||
valoro.remove(&r);
|
||||
}
|
||||
}
|
||||
fn foriru_uzanto(&mut self, identigilo: UzantIdentigilo) {
|
||||
let _ = self.rajtoj.remove(&identigilo);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub struct DBRajtigo {
|
||||
pool: Datumbazo,
|
||||
}
|
||||
|
||||
impl DBRajtigo {
|
||||
pub fn kreu(pool: Datumbazo) -> Self {
|
||||
DBRajtigo { pool }
|
||||
}
|
||||
}
|
||||
|
||||
impl Rajtigo for DBRajtigo {
|
||||
fn havas_rajton<'a, F>(&'a self, identigilo: UzantIdentigilo, provo: F) -> bool
|
||||
where
|
||||
F: FnOnce(&mut (dyn Iterator<Item = Rajto>)) -> bool,
|
||||
{
|
||||
let konekto = self.pool.konektu().unwrap();
|
||||
let mut stmt = konekto
|
||||
.prepare("SELECT rajto FROM rajtoj WHERE uzanto = ?")
|
||||
.unwrap();
|
||||
let mut rajtoj = stmt
|
||||
.query_map([String::from(identigilo)], |row| {
|
||||
row.get("rajto").map(|s: String| Rajto::from(s.as_ref()))
|
||||
})
|
||||
.unwrap()
|
||||
.map(|r| r.unwrap());
|
||||
provo(&mut rajtoj)
|
||||
}
|
||||
|
||||
fn rajtoj(&self, identigilo: &UzantIdentigilo) -> Vec<Rajto> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn aldonu_rajtojn(
|
||||
&mut self,
|
||||
identigilo: UzantIdentigilo,
|
||||
rajtoj: &mut (dyn Iterator<Item = Rajto>),
|
||||
) {
|
||||
let konekto = self.pool.konektu().unwrap();
|
||||
let mut stmt = konekto.prepare("INSERT INTO rajtoj VALUES(?, ?)").unwrap();
|
||||
|
||||
rajtoj.for_each(|r| {
|
||||
stmt.execute(params![String::from(identigilo.clone()), String::from(r)])
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn foriru_rajtoj(
|
||||
&mut self,
|
||||
identigilo: UzantIdentigilo,
|
||||
rajtoj: &mut (dyn Iterator<Item = Rajto>),
|
||||
) {
|
||||
}
|
||||
fn foriru_uzanto(&mut self, identigilo: UzantIdentigilo) {}
|
||||
}
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |