feat: WebUIを追加

This commit is contained in:
usbharu 2023-06-15 00:43:21 +09:00
parent 9c5da79389
commit 6b1b6f8bf2
12 changed files with 3509 additions and 187 deletions

1
.gitignore vendored
View File

@ -37,3 +37,4 @@ out/
*.db *.db
/src/main/resources/static/ /src/main/resources/static/
/node_modules/ /node_modules/
/src/main/web/generated/

22
openapitools.json Normal file
View File

@ -0,0 +1,22 @@
{
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "6.6.0",
"generators": {
"v3.0": {
"generatorName": "typescript-fetch",
"output": "src/main/web/generated",
"glob": "src/main/resources/openapi/api.yaml",
"additionalProperties": {
"modelPropertyNaming": "camelCase",
"supportsES6": true,
"withInterfaces": true,
"typescriptThreePlus": true,
"useSingleRequestParameter": false,
"prependFormOrBodyParameters": true
}
}
}
}
}

3320
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,18 +2,24 @@
"name": "hideout", "name": "hideout",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"solid-js": "^1.7.3" "@solid-primitives/storage": "^1.3.11",
"@solidjs/router": "^0.8.2",
"@suid/icons-material": "^0.6.3",
"@suid/material": "^0.12.3",
"solid-js": "^1.7.6"
}, },
"devDependencies": { "devDependencies": {
"@openapitools/openapi-generator-cli": "^2.6.0",
"@suid/vite-plugin": "^0.1.3",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"vite": "^4.2.1", "vite": "^4.2.1",
"vite-plugin-solid": "^2.7.0", "vite-plugin-solid": "^2.7.0"
"@suid/vite-plugin": "^0.1.3"
}, },
"scripts": { "scripts": {
"start": "vite", "start": "vite",
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"serve": "vite preview" "serve": "vite preview",
"gen-api": "openapi-generator-cli generate"
} }
} }

View File

@ -20,7 +20,7 @@ paths:
schema: schema:
type: array type: array
items: items:
$ref: "#/components/schemas/Post" $ref: "#/components/schemas/PostResponse"
401: 401:
$ref: "#/components/responses/Unauthorized" $ref: "#/components/responses/Unauthorized"
403: 403:
@ -37,7 +37,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Post" $ref: "#/components/schemas/PostRequest"
responses: responses:
200: 200:
description: 成功 description: 成功
@ -65,7 +65,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Post" $ref: "#/components/schemas/PostResponse"
401: 401:
$ref: "#/components/responses/Unauthorized" $ref: "#/components/responses/Unauthorized"
403: 403:
@ -90,7 +90,7 @@ paths:
schema: schema:
type: array type: array
items: items:
$ref: "#/components/schemas/Post" $ref: "#/components/schemas/PostResponse"
401: 401:
$ref: "#/components/responses/Unauthorized" $ref: "#/components/responses/Unauthorized"
403: 403:
@ -114,7 +114,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Post" $ref: "#/components/schemas/PostResponse"
401: 401:
$ref: "#/components/responses/Unauthorized" $ref: "#/components/responses/Unauthorized"
403: 403:
@ -137,7 +137,7 @@ paths:
schema: schema:
type: array type: array
items: items:
$ref: "#/components/schemas/User" $ref: "#/components/schemas/UserResponse"
post: post:
summary: ユーザーを作成する summary: ユーザーを作成する
@ -181,7 +181,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/User" $ref: "#/components/schemas/UserResponse"
404: 404:
$ref: "#/components/responses/NotFound" $ref: "#/components/responses/NotFound"
@ -198,7 +198,7 @@ paths:
schema: schema:
type: array type: array
items: items:
$ref: "#/components/schemas/User" $ref: "#/components/schemas/UserResponse"
post: post:
summary: ユーザーをフォローする summary: ユーザーをフォローする
security: security:
@ -228,7 +228,7 @@ paths:
schema: schema:
type: array type: array
items: items:
$ref: "#/components/schemas/User" $ref: "#/components/schemas/UserResponse"
components: components:
responses: responses:
@ -261,8 +261,22 @@ components:
type: string type: string
schemas: schemas:
User: Visibility:
type: string
enum:
- public
- unlisted
- followers
- direct
UserResponse:
type: object type: object
required:
- id
- name
- domain
- screenName
- description
- createdAt
properties: properties:
id: id:
type: number type: number
@ -277,14 +291,24 @@ components:
type: string type: string
description: description:
type: string type: string
nullable: true
url: url:
type: string type: string
readOnly: true readOnly: true
createdAt: createdAt:
type: number type: number
readOnly: true readOnly: true
Post: PostResponse:
type: object type: object
required:
- id
- userId
- text
- createdAt
- visibility
- url
- sensitive
- apId
properties: properties:
id: id:
type: integer type: integer
@ -303,12 +327,7 @@ components:
format: int64 format: int64
readOnly: true readOnly: true
visibility: visibility:
type: string $ref: "#/components/schemas/Visibility"
enum:
- public
- unlisted
- followers
- direct
url: url:
type: string type: string
format: uri format: uri
@ -328,6 +347,24 @@ components:
format: url format: url
readOnly: true readOnly: true
PostRequest:
type: object
properties:
overview:
type: string
text:
type: string
visibility:
$ref: "#/components/schemas/Visibility"
repostId:
type: integer
format: int64
replyId:
type: integer
format: int64
sensitive:
type: boolean
securitySchemes: securitySchemes:
BearerAuth: BearerAuth:

View File

@ -1,58 +1,24 @@
import {Component, createSignal} from "solid-js"; import {Component} from "solid-js";
import {Route, Router, Routes} from "@solidjs/router";
import {TopPage} from "./pages/TopPage";
import {createTheme, CssBaseline, ThemeProvider, useMediaQuery} from "@suid/material";
export const App: Component = () => { export const App: Component = () => {
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const fn = (form: HTMLButtonElement) => { const theme = createTheme({
console.log(form) palette: {
} mode: prefersDarkMode() ? 'dark' : 'light',
const [username, setUsername] = createSignal("")
const [password, setPassword] = createSignal("")
return (
<form onSubmit={function (e: SubmitEvent) {
e.preventDefault()
fetch("/login", {
method: "POST",
body: JSON.stringify({username: username(), password: password()}),
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.json())
// .then(res => fetch("/auth-check", {
// method: "GET",
// headers: {
// 'Authorization': 'Bearer ' + res.token
// }
// }))
// .then(res => res.json())
.then(res => {
console.log(res.token);
fetch("/refresh-token", {
method: "POST",
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({refreshToken: res.refreshToken}),
}).then(res=> res.json()).then(res => console.log(res.token))
})
} }
})
}> return (
<input name="username" type="text" placeholder="Username" required <ThemeProvider theme={theme}>
onChange={(e) => setUsername(e.currentTarget.value)}/> <CssBaseline/>
<input name="password" type="password" placeholder="Password" required <Router>
onChange={(e) => setPassword(e.currentTarget.value)}/> <Routes>
<button type="submit">Submit</button> <Route path="/" component={TopPage}/>
</form> </Routes>
</Router>
</ThemeProvider>
) )
} }
declare module 'solid-js' {
namespace JSX {
interface Directives {
fn: (form: HTMLFormElement) => void
}
}
}

View File

@ -0,0 +1,8 @@
import {Avatar as SuidAvatar} from "@suid/material";
import {Component, JSXElement} from "solid-js";
export const Avatar: Component<{ src: string }> = (props): JSXElement => {
return (
<SuidAvatar src={props.src}/>
)
}

View File

@ -0,0 +1,64 @@
import {Component, createSignal, Match, Switch} from "solid-js";
import {PostResponse} from "../generated";
import {Box, Card, CardActions, CardContent, CardHeader, IconButton, Menu, MenuItem, Typography} from "@suid/material";
import {Avatar} from "../atoms/Avatar";
import {Favorite, Home, Lock, Mail, MoreVert, Public, Reply, ScreenRotationAlt} from "@suid/icons-material";
export const Post: Component<{ post: PostResponse }> = (props) => {
const [anchorEl, setAnchorEl] = createSignal<null | HTMLElement>(null)
const open = () => Boolean(anchorEl());
const handleClose = () => {
setAnchorEl(null);
}
return (
<Card>
<CardHeader avatar={<Avatar src={""}/>} title={"test user"} subheader={"test@test"}
action={<IconButton onclick={(event) => {
setAnchorEl(event.currentTarget)
}}><MoreVert/><Menu disableScrollLock anchorEl={anchorEl()} open={open()} onClose={handleClose}><MenuItem
onclick={handleClose}>aaa</MenuItem></Menu> </IconButton>}/>
<CardContent>
<Typography>
{props.post.text}
</Typography>
</CardContent>
<CardActions disableSpacing>
<IconButton>
<Reply/>
</IconButton>
<IconButton>
<ScreenRotationAlt/>
</IconButton>
<IconButton>
<Favorite/>
</IconButton>
<Box sx={{marginLeft: "auto"}}>
<Typography>{new Date(props.post.createdAt).toDateString()}</Typography>
</Box>
<Switch fallback={<Public/>}>
<Match when={props.post.visibility == "public"}>
<IconButton>
<Public/>
</IconButton>
</Match>
<Match when={props.post.visibility == "direct"}>
<IconButton>
<Mail/>
</IconButton>
</Match>
<Match when={props.post.visibility == "followers"}>
<IconButton>
<Lock/>
</IconButton>
</Match>
<Match when={props.post.visibility == "unlisted"}>
<IconButton>
<Home/>
</IconButton>
</Match>
</Switch>
</CardActions>
</Card>
)
}

View File

@ -0,0 +1,38 @@
import {Component} from "solid-js";
import {Button, IconButton, Paper, Stack, TextField, Typography} from "@suid/material";
import {Avatar} from "../atoms/Avatar";
import {AddPhotoAlternate, Poll, Public} from "@suid/icons-material";
export const PostForm: Component<{ label: string }> = (props) => {
return (
<Paper>
<Stack>
<Stack direction={"row"} spacing={2} sx={{padding: 2}}>
<Avatar src={""}/>
<TextField label={props.label} multiline rows={4} variant={"standard"} fullWidth/>
</Stack>
<Stack direction={"row"} justifyContent={"space-between"} sx={{padding: 2}}>
<Stack direction={"row"} justifyContent={"flex-start"} alignItems={"center"}>
<IconButton>
<AddPhotoAlternate/>
</IconButton>
<IconButton>
<Poll/>
</IconButton>
<IconButton>
<Public/>
</IconButton>
</Stack>
<Stack direction={"row"} alignItems={"center"} spacing={2}>
<Typography>
aaa
</Typography>
<Button variant={"contained"}>
稿
</Button>
</Stack>
</Stack>
</Stack>
</Paper>
)
}

View File

@ -0,0 +1,56 @@
import {Component} from "solid-js";
import {MainPage} from "../templates/MainPage";
import {PostForm} from "../organisms/PostForm";
import {Stack} from "@suid/material";
import {Post} from "../organisms/Post";
import {PostResponse} from "../generated";
export const TopPage: Component = () => {
return (
<MainPage>
<Stack spacing={1}>
<PostForm label={"投稿する"}/>
<Post post={{
text: "テスト~",
sensitive: false,
apId: "https://example.com",
id: 1234,
createdAt: Date.now(),
url: "https://example.com",
userId: 1234,
visibility: "public"
} as PostResponse}></Post>
<Post post={{
text: "テスト 公開範囲",
sensitive: false,
apId: "https://example.com",
id: 1234,
createdAt: 1234567,
url: "https://example.com",
userId: 1234,
visibility: "direct"
} as PostResponse}></Post>
<Post post={{
text: "テスト~",
sensitive: false,
apId: "https://example.com",
id: 1234,
createdAt: 1234567,
url: "https://example.com",
userId: 1234,
visibility: "unlisted"
} as PostResponse}></Post>
<Post post={{
text: "テスト~",
sensitive: false,
apId: "https://example.com",
id: 1234,
createdAt: 1234567,
url: "https://example.com",
userId: 1234,
visibility: "followers"
} as PostResponse}></Post>
</Stack>
</MainPage>
)
}

View File

@ -0,0 +1,20 @@
import {ParentComponent} from "solid-js";
import {Grid} from "@suid/material";
import {Sidebar} from "./Sidebar";
export const MainPage: ParentComponent = (props) => {
return (
<Grid container spacing={2}>
<Grid item xs={0} md={3}>
<Sidebar/>
</Grid>
<Grid item xs={12} md={5}>
{props.children}
</Grid>
<Grid item xs={0} md={3}>
</Grid>
</Grid>
)
}

View File

@ -0,0 +1,10 @@
import {Component} from "solid-js";
import {Stack} from "@suid/material";
export const Sidebar: Component = () => {
return (
<Stack>
</Stack>
)
}