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
/src/main/resources/static/
/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
}
}
}
}
}

3322
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,18 +2,24 @@
"name": "hideout",
"version": "1.0.0",
"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": {
"@openapitools/openapi-generator-cli": "^2.6.0",
"@suid/vite-plugin": "^0.1.3",
"typescript": "^5.0.4",
"vite": "^4.2.1",
"vite-plugin-solid": "^2.7.0",
"@suid/vite-plugin": "^0.1.3"
"vite-plugin-solid": "^2.7.0"
},
"scripts": {
"start": "vite",
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
"serve": "vite preview",
"gen-api": "openapi-generator-cli generate"
}
}

View File

@ -20,7 +20,7 @@ paths:
schema:
type: array
items:
$ref: "#/components/schemas/Post"
$ref: "#/components/schemas/PostResponse"
401:
$ref: "#/components/responses/Unauthorized"
403:
@ -37,7 +37,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/Post"
$ref: "#/components/schemas/PostRequest"
responses:
200:
description: 成功
@ -65,7 +65,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/Post"
$ref: "#/components/schemas/PostResponse"
401:
$ref: "#/components/responses/Unauthorized"
403:
@ -90,7 +90,7 @@ paths:
schema:
type: array
items:
$ref: "#/components/schemas/Post"
$ref: "#/components/schemas/PostResponse"
401:
$ref: "#/components/responses/Unauthorized"
403:
@ -114,7 +114,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/Post"
$ref: "#/components/schemas/PostResponse"
401:
$ref: "#/components/responses/Unauthorized"
403:
@ -137,7 +137,7 @@ paths:
schema:
type: array
items:
$ref: "#/components/schemas/User"
$ref: "#/components/schemas/UserResponse"
post:
summary: ユーザーを作成する
@ -181,7 +181,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/User"
$ref: "#/components/schemas/UserResponse"
404:
$ref: "#/components/responses/NotFound"
@ -198,7 +198,7 @@ paths:
schema:
type: array
items:
$ref: "#/components/schemas/User"
$ref: "#/components/schemas/UserResponse"
post:
summary: ユーザーをフォローする
security:
@ -228,7 +228,7 @@ paths:
schema:
type: array
items:
$ref: "#/components/schemas/User"
$ref: "#/components/schemas/UserResponse"
components:
responses:
@ -261,8 +261,22 @@ components:
type: string
schemas:
User:
Visibility:
type: string
enum:
- public
- unlisted
- followers
- direct
UserResponse:
type: object
required:
- id
- name
- domain
- screenName
- description
- createdAt
properties:
id:
type: number
@ -277,14 +291,24 @@ components:
type: string
description:
type: string
nullable: true
url:
type: string
readOnly: true
createdAt:
type: number
readOnly: true
Post:
PostResponse:
type: object
required:
- id
- userId
- text
- createdAt
- visibility
- url
- sensitive
- apId
properties:
id:
type: integer
@ -303,12 +327,7 @@ components:
format: int64
readOnly: true
visibility:
type: string
enum:
- public
- unlisted
- followers
- direct
$ref: "#/components/schemas/Visibility"
url:
type: string
format: uri
@ -328,6 +347,24 @@ components:
format: url
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:
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 = () => {
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const fn = (form: HTMLButtonElement) => {
console.log(form)
const theme = createTheme({
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))
})
}
}>
<input name="username" type="text" placeholder="Username" required
onChange={(e) => setUsername(e.currentTarget.value)}/>
<input name="password" type="password" placeholder="Password" required
onChange={(e) => setPassword(e.currentTarget.value)}/>
<button type="submit">Submit</button>
</form>
return (
<ThemeProvider theme={theme}>
<CssBaseline/>
<Router>
<Routes>
<Route path="/" component={TopPage}/>
</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>
)
}