WIP: feat: Create new db app #32
11
db/.editorconfig
Normal file
11
db/.editorconfig
Normal file
@ -0,0 +1,11 @@
|
||||
# https://EditorConfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
3
db/.eslintrc.js
Normal file
3
db/.eslintrc.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ["blitz"],
|
||||
}
|
53
db/.gitignore
vendored
Normal file
53
db/.gitignore
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.pnp.*
|
||||
.npm
|
||||
web_modules/
|
||||
|
||||
# blitz
|
||||
/.blitz/
|
||||
/.next/
|
||||
*.sqlite
|
||||
*.sqlite-journal
|
||||
.now
|
||||
.blitz**
|
||||
blitz-log.log
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
.envrc
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Testing
|
||||
.coverage
|
||||
*.lcov
|
||||
.nyc_output
|
||||
lib-cov
|
||||
|
||||
# Caches
|
||||
*.tsbuildinfo
|
||||
.eslintcache
|
||||
.node_repl_history
|
||||
.yarn-integrity
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
7
db/.npmrc
Normal file
7
db/.npmrc
Normal file
@ -0,0 +1,7 @@
|
||||
save-exact=true
|
||||
legacy-peer-deps=true
|
||||
|
||||
public-hoist-pattern[]=next
|
||||
public-hoist-pattern[]=secure-password
|
||||
public-hoist-pattern[]=*jest*
|
||||
public-hoist-pattern[]=@testing-library/*
|
10
db/.prettierignore
Normal file
10
db/.prettierignore
Normal file
@ -0,0 +1,10 @@
|
||||
.gitkeep
|
||||
.env*
|
||||
*.ico
|
||||
*.lock
|
||||
.next
|
||||
.blitz
|
||||
.yarn
|
||||
.pnp.*
|
||||
node_modules
|
||||
.blitz.config.compiled.js
|
117
db/README.md
Normal file
117
db/README.md
Normal file
@ -0,0 +1,117 @@
|
||||
[![Blitz.js](https://raw.githubusercontent.com/blitz-js/art/master/github-cover-photo.png)](https://blitzjs.com)
|
||||
|
||||
This is a minimal [Blitz.js](https://github.com/blitz-js/blitz) app.
|
||||
|
||||
# **db**
|
||||
|
||||
## Getting Started
|
||||
|
||||
Run your app in the development mode.
|
||||
|
||||
```
|
||||
yarn blitz p generate
|
||||
yarn blitz dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
## Tests
|
||||
|
||||
Runs your tests using Jest.
|
||||
|
||||
```
|
||||
yarn test
|
||||
```
|
||||
|
||||
Blitz comes with a test setup using [Jest](https://jestjs.io/) and [react-testing-library](https://testing-library.com/).
|
||||
|
||||
## Commands
|
||||
|
||||
Blitz comes with a powerful CLI that is designed to make development easy and fast. You can install it with `npm i -g blitz`
|
||||
|
||||
```
|
||||
blitz [COMMAND]
|
||||
|
||||
dev Start a development server
|
||||
build Create a production build
|
||||
start Start a production server
|
||||
export Export your Blitz app as a static application
|
||||
prisma Run prisma commands
|
||||
generate Generate new files for your Blitz project
|
||||
console Run the Blitz console REPL
|
||||
install Install a recipe
|
||||
help Display help for blitz
|
||||
test Run project tests
|
||||
```
|
||||
|
||||
You can read more about it on the [CLI Overview](https://blitzjs.com/docs/cli-overview) documentation.
|
||||
|
||||
## What's included?
|
||||
|
||||
Here is the starting structure of your app.
|
||||
|
||||
```
|
||||
db
|
||||
├── app/
|
||||
│ ├── pages/
|
||||
│ │ ├── _app.tsx
|
||||
│ │ ├── _document.tsx
|
||||
│ │ ├── 404.tsx
|
||||
│ │ ├── index.test.tsx
|
||||
│ │ └── index.tsx
|
||||
├── public/
|
||||
│ ├── favicon.ico
|
||||
│ └── logo.png
|
||||
├── test/
|
||||
│ ├── setup.ts
|
||||
│ └── utils.tsx
|
||||
├── .eslintrc.js
|
||||
├── babel.config.js
|
||||
├── blitz.config.ts
|
||||
├── jest.config.ts
|
||||
├── package.json
|
||||
├── README.md
|
||||
├── tsconfig.json
|
||||
└── types.ts
|
||||
```
|
||||
|
||||
These files are:
|
||||
|
||||
- The `app/` folder is a container for most of your project. This is where you’ll put any pages or API routes.
|
||||
|
||||
- `public/` is a folder where you will put any static assets. If you have images, files, or videos which you want to use in your app, this is where to put them.
|
||||
|
||||
- `test/` is a folder where you can put test utilities and integration tests.
|
||||
|
||||
- `package.json` contains information about your dependencies and devDependencies. If you’re using a tool like `npm` or `yarn`, you won’t have to worry about this much.
|
||||
|
||||
- `tsconfig.json` is our recommended setup for TypeScript.
|
||||
|
||||
- `.babel.config.js`, `.eslintrc.js`, `.env`, etc. ("dotfiles") are configuration files for various bits of JavaScript tooling.
|
||||
|
||||
- `blitz.config.ts` is for advanced custom configuration of Blitz. [Here you can learn how to use it](https://blitzjs.com/docs/blitz-config).
|
||||
|
||||
- `jest.config.js` contains config for Jest tests. You can [customize it if needed](https://jestjs.io/docs/en/configuration).
|
||||
|
||||
You can read more about it in the [File Structure](https://blitzjs.com/docs/file-structure) section of the documentation.
|
||||
|
||||
### Tools included
|
||||
|
||||
Blitz comes with a set of tools that corrects and formats your code, facilitating its future maintenance. You can modify their options and even uninstall them.
|
||||
|
||||
- **ESLint**: It lints your code: searches for bad practices and tell you about it. You can customize it via the `.eslintrc.js`, and you can install (or even write) plugins to have it the way you like it. It already comes with the [`blitz`](https://github.com/blitz-js/blitz/tree/canary/packages/eslint-config) config, but you can remove it safely. [Learn More](https://blitzjs.com/docs/eslint-config).
|
||||
- **Husky**: It adds [githooks](https://git-scm.com/docs/githooks), little pieces of code that get executed when certain Git events are triggerd. For example, `pre-commit` is triggered just before a commit is created. You can see the current hooks inside `.husky/`. If are having problems commiting and pushing, check out ther [troubleshooting](https://typicode.github.io/husky/#/?id=troubleshoot) guide. [Learn More](https://blitzjs.com/docs/husky-config).
|
||||
- **Prettier**: It formats your code to look the same everywhere. You can configure it via the `.prettierrc` file. The `.prettierignore` contains the files that should be ignored by Prettier; useful when you have large files or when you want to keep a custom formatting. [Learn More](https://blitzjs.com/docs/prettier-config).
|
||||
|
||||
## Learn more
|
||||
|
||||
Read the [Blitz.js Documentation](https://blitzjs.com/docs/getting-started) to learn more.
|
||||
|
||||
The Blitz community is warm, safe, diverse, inclusive, and fun! Feel free to reach out to us in any of our communication channels.
|
||||
|
||||
- [Website](https://blitzjs.com)
|
||||
- [Discord](https://blitzjs.com/discord)
|
||||
- [Report an issue](https://github.com/blitz-js/blitz/issues/new/choose)
|
||||
- [Forum discussions](https://github.com/blitz-js/blitz/discussions)
|
||||
- [How to Contribute](https://blitzjs.com/docs/contributing)
|
||||
- [Sponsor or donate](https://github.com/blitz-js/blitz#sponsors-and-donations)
|
13
db/app/api/greetingsQueue.ts
Normal file
13
db/app/api/greetingsQueue.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Queue } from "quirrel/blitz"
|
||||
|
||||
export interface Greetings {
|
||||
to: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export default Queue(
|
||||
"api/greetingsQueue", // the path of this API route
|
||||
async ({ to, message }: Greetings) => {
|
||||
console.log(`Greetings, ${to}! Thy ears shall hear: "${message}"`)
|
||||
}
|
||||
)
|
34
db/app/api/items/refresh.ts
Normal file
34
db/app/api/items/refresh.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { CronJob } from "quirrel/blitz"
|
||||
import { PrismaClient, Item } from '@prisma/client';
|
||||
import { RawItemData } from '../../core/dto/rawData/RawItemData';
|
||||
import { propsToClassKey } from "@mui/styles";
|
||||
|
||||
export default CronJob(
|
||||
"api/items/refresh", // the path of this API route
|
||||
"@daily", // cron schedule (see https://crontab.guru)
|
||||
async () => {
|
||||
const itemsResponse = await fetch('https://dev.sp-tarkov.com/SPT-AKI/Server/raw/branch/development/project/assets/database/templates/items.json');
|
||||
if (itemsResponse.status >= 300) {
|
||||
throw Error(`Could not retrieve items from Gitea: code ${itemsResponse.status}`)
|
||||
}
|
||||
const items: { [id: string]: RawItemData } = await itemsResponse.json();
|
||||
const prisma: PrismaClient = new PrismaClient();
|
||||
|
||||
const promises: any[] = []
|
||||
|
||||
Object.entries(items).forEach(
|
||||
async ([key, value]) => {
|
||||
const {props, ...data} = RawItemData.fromRawData(value).toItemData();
|
||||
promises.push(await prisma.item.upsert({
|
||||
create: {props, ...data},
|
||||
update: data,
|
||||
where: {
|
||||
internalId: data.internalId
|
||||
},
|
||||
}))
|
||||
}
|
||||
);
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
)
|
68
db/app/core/components/DarkModeToggle.tsx
Normal file
68
db/app/core/components/DarkModeToggle.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import {useTheme as nextUseTheme} from "next-themes"
|
||||
import { Box, IconButton, Theme, Typography } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import Brightness4Icon from '@mui/icons-material/Brightness4'
|
||||
import Brightness7Icon from '@mui/icons-material/Brightness7'
|
||||
import { ThemeMode } from '../state/ThemeMode'
|
||||
import { useGlobalState } from '../state/GlobalState'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { makeStyles } from '@mui/styles'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
modeToggleButtonHolder: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexGrow: 1,
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
iconButton: {
|
||||
ml: 1,
|
||||
},
|
||||
}))
|
||||
|
||||
export const DarkModeToggle = () => {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const theme = useTheme()
|
||||
const {setTheme} = nextUseTheme()
|
||||
const classes = useStyles()
|
||||
const [preferedColorScheme, setPreferedColorScheme] = useGlobalState(
|
||||
useCallback(
|
||||
(state) => [state.preferedColorScheme, state.setPreferedColorScheme],
|
||||
[],
|
||||
),
|
||||
)
|
||||
|
||||
useEffect(() => setMounted(true), [])
|
||||
if (!mounted) return null
|
||||
|
||||
|
||||
const toggleColor = () => {
|
||||
const newTheme =
|
||||
preferedColorScheme === ThemeMode.LIGHT_MODE
|
||||
? ThemeMode.DARK_MODE
|
||||
: ThemeMode.LIGHT_MODE
|
||||
setTheme(newTheme);
|
||||
setPreferedColorScheme(newTheme)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Box className={classes.modeToggleButtonHolder} id="modeToggleButtonHolder">
|
||||
<Typography>{theme.palette.mode} toggle</Typography>
|
||||
<IconButton
|
||||
className={classes.iconButton}
|
||||
sx={{ ml: 1 }}
|
||||
onClick={toggleColor}
|
||||
color="inherit"
|
||||
id="modeToggleButton"
|
||||
>
|
||||
{theme.palette.mode === 'dark' ? (
|
||||
<Brightness7Icon />
|
||||
) : (
|
||||
<Brightness4Icon />
|
||||
)}
|
||||
</IconButton>
|
||||
</Box>
|
||||
)
|
||||
}
|
25
db/app/core/components/Footer.tsx
Normal file
25
db/app/core/components/Footer.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import {Box, Theme, Typography} from '@mui/material'
|
||||
import {makeStyles} from '@mui/styles'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
footerHolder: {
|
||||
display: 'flex',
|
||||
flex: '0 1 3vh',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '0 10vw 0 10vw',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary
|
||||
}
|
||||
}))
|
||||
|
||||
export const Footer = () => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<Box className={classes.footerHolder}>
|
||||
<Typography id={"footer"}>SPT-Aki ©2021 Created by Rev and Shirito</Typography>
|
||||
</Box>
|
||||
)
|
||||
}
|
53
db/app/core/components/Form.tsx
Normal file
53
db/app/core/components/Form.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { ReactNode, PropsWithoutRef } from "react"
|
||||
import { Form as FinalForm, FormProps as FinalFormProps } from "react-final-form"
|
||||
import { z } from "zod"
|
||||
import { validateZodSchema } from "blitz"
|
||||
export { FORM_ERROR } from "final-form"
|
||||
|
||||
export interface FormProps<S extends z.ZodType<any, any>>
|
||||
extends Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit"> {
|
||||
/** All your form fields */
|
||||
children?: ReactNode
|
||||
/** Text to display in the submit button */
|
||||
submitText?: string
|
||||
schema?: S
|
||||
onSubmit: FinalFormProps<z.infer<S>>["onSubmit"]
|
||||
initialValues?: FinalFormProps<z.infer<S>>["initialValues"]
|
||||
}
|
||||
|
||||
export function Form<S extends z.ZodType<any, any>>({
|
||||
children,
|
||||
submitText,
|
||||
schema,
|
||||
initialValues,
|
||||
onSubmit,
|
||||
...props
|
||||
}: FormProps<S>) {
|
||||
return (
|
||||
<FinalForm
|
||||
initialValues={initialValues}
|
||||
validate={validateZodSchema(schema)}
|
||||
onSubmit={onSubmit}
|
||||
render={({ handleSubmit, submitting, submitError }) => (
|
||||
<form onSubmit={handleSubmit} className="form" {...props}>
|
||||
{/* Form fields supplied as children are rendered here */}
|
||||
{children}
|
||||
|
||||
{submitError && (
|
||||
<div role="alert" style={{ color: "red" }}>
|
||||
{submitError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{submitText && (
|
||||
<button type="submit" disabled={submitting}>
|
||||
{submitText}
|
||||
</button>
|
||||
)}
|
||||
</form>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Form
|
87
db/app/core/components/Header.tsx
Normal file
87
db/app/core/components/Header.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import { Box, Link, Theme } from '@mui/material'
|
||||
import { makeStyles } from '@mui/styles'
|
||||
import { useCallback } from 'react';
|
||||
import { useGlobalState } from '../state/GlobalState';
|
||||
import { HeaderForm } from './HeaderForm';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
headerContainer: {
|
||||
display: 'flex',
|
||||
flex: '0 1 3vh',
|
||||
flexDirection: 'row',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
alignItems: 'center',
|
||||
padding: '0 10vw 0 10vw',
|
||||
},
|
||||
linksContainer: {
|
||||
display: 'flex',
|
||||
flexGrow: 2,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
},
|
||||
formContainer: {
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
},
|
||||
link: {
|
||||
display: 'flex',
|
||||
padding: '0 1vw 0 1vw',
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
borderBottom: `1px solid transparent`,
|
||||
color: theme.palette.text.primary,
|
||||
'&:hover': {
|
||||
borderBottom: `1px solid ${theme.palette.action.hover}`,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
export const Header = () => {
|
||||
const classes = useStyles()
|
||||
const websiteLink = useGlobalState(useCallback((state) => state.sptarkovWebsiteUrl,[]))
|
||||
const workshopLink = useGlobalState(useCallback((state) => state.sptarkovWorkshopUrl,[]))
|
||||
const documentationLink = useGlobalState(useCallback((state) => state.sptarkovDocumentationUrl,[]))
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box className={classes.headerContainer}>
|
||||
<Box className={classes.linksContainer}>
|
||||
<Link
|
||||
underline="hover"
|
||||
color="inherit"
|
||||
id="website-link"
|
||||
href={websiteLink}
|
||||
className={classes.link}
|
||||
>
|
||||
Website
|
||||
</Link>
|
||||
<Link
|
||||
underline="hover"
|
||||
color="inherit"
|
||||
id="workshop-link"
|
||||
href={workshopLink}
|
||||
className={classes.link}
|
||||
>
|
||||
Workshop
|
||||
</Link>
|
||||
<Link
|
||||
underline="hover"
|
||||
color="inherit"
|
||||
id="documentation-link"
|
||||
href={documentationLink}
|
||||
className={classes.link}
|
||||
>
|
||||
Documentation
|
||||
</Link>
|
||||
</Box>
|
||||
<Box className={classes.formContainer}>
|
||||
<HeaderForm/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
74
db/app/core/components/JsonTheme.tsx
Normal file
74
db/app/core/components/JsonTheme.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
MenuItem,
|
||||
Select,
|
||||
Theme,
|
||||
} from '@mui/material'
|
||||
import { makeStyles } from '@mui/styles'
|
||||
import { ReactJsonViewThemes } from '../state/ReactJsonViewThemes'
|
||||
import { LocalStorageKeys } from '../dto/SaveKeys'
|
||||
import { useGlobalState } from '../state/GlobalState'
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
jsonHolder: {
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
padding: '0 0.5vw 0 0.5vw'
|
||||
},
|
||||
select: {
|
||||
display: 'flex',
|
||||
flexGrow: 1
|
||||
}
|
||||
}))
|
||||
|
||||
export const JsonTheme = () => {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const classes = useStyles()
|
||||
const [preferedJsonViewerTheme, setPreferedJsonViewerTheme] = useGlobalState(
|
||||
useCallback(
|
||||
(state) => [
|
||||
state.preferedJsonViewerTheme,
|
||||
state.setPreferedJsonViewerTheme,
|
||||
],
|
||||
[],
|
||||
),
|
||||
)
|
||||
|
||||
useEffect(() => setMounted(true), [])
|
||||
if (!mounted) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box className={classes.jsonHolder}>
|
||||
<FormControl fullWidth variant="standard">
|
||||
<Select
|
||||
displayEmpty
|
||||
className={classes.select}
|
||||
labelId="react-json-view-theme"
|
||||
value={preferedJsonViewerTheme}
|
||||
label="JSON theme"
|
||||
onChange={(evt) => {
|
||||
setPreferedJsonViewerTheme(evt.target.value)
|
||||
localStorage.setItem(
|
||||
LocalStorageKeys.PREFERED_JSON_THEME,
|
||||
evt.target.value,
|
||||
)
|
||||
}}
|
||||
id="json-selector"
|
||||
>
|
||||
<MenuItem disabled value="">
|
||||
<em>JSON theme</em>
|
||||
</MenuItem>
|
||||
{ReactJsonViewThemes.map((theme, idx) => (
|
||||
<MenuItem key={idx} value={theme}>
|
||||
{theme}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
58
db/app/core/components/LocaleSelect.tsx
Normal file
58
db/app/core/components/LocaleSelect.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import { Select, MenuItem, Theme, Box, FormControl } from '@mui/material'
|
||||
import {makeStyles} from '@mui/styles'
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useGlobalState } from '../state/GlobalState'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
localeHolder: {
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
padding: '0 0.5vw 0 0.5vw'
|
||||
},
|
||||
select: {
|
||||
display: 'flex',
|
||||
flexGrow: 1
|
||||
}
|
||||
}))
|
||||
|
||||
|
||||
|
||||
export const LocaleSelect = () => {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const classes = useStyles()
|
||||
const [preferedLocale, setPreferedLocale] = useGlobalState(useCallback(state => [state.preferedLocale, state.setPreferedLocale],[]))
|
||||
const [localesList, refreshLocalesList] = useGlobalState(useCallback(state => [state.localesList, state.refreshLocalesList],[]))
|
||||
|
||||
useEffect(() => setMounted(true), [])
|
||||
useEffect(()=> {refreshLocalesList();}, [refreshLocalesList])
|
||||
|
||||
if (!mounted) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box className={classes.localeHolder}>
|
||||
<FormControl fullWidth variant="standard">
|
||||
<Select
|
||||
displayEmpty
|
||||
className={classes.select}
|
||||
labelId="prefered-locale"
|
||||
value={localesList.length > 0 ? preferedLocale : ''}
|
||||
onChange={(evt) => {
|
||||
setPreferedLocale(evt.target.value)
|
||||
}}
|
||||
id="locale-selector"
|
||||
>
|
||||
<MenuItem disabled value="">
|
||||
<em>Language</em>
|
||||
</MenuItem>
|
||||
{localesList.map((locale, idx) => (
|
||||
<MenuItem key={idx} value={locale}>
|
||||
{locale}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
111
db/app/core/components/NavigationBreadcrumb.tsx
Normal file
111
db/app/core/components/NavigationBreadcrumb.tsx
Normal file
@ -0,0 +1,111 @@
|
||||
import { useState } from 'react'
|
||||
import { Box, Breadcrumbs, Link, Theme, Typography } from '@mui/material'
|
||||
import { makeStyles } from '@mui/styles'
|
||||
import { useEffect } from 'react'
|
||||
import { useGlobalState } from '../state/GlobalState'
|
||||
import { ItemWithLocale } from '../dto/ItemWithLocale'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
breadcrumbHolder: {
|
||||
display: 'flex',
|
||||
flex: '0 1 3vh',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: '0 10vw 0 10vw',
|
||||
borderBottom: `1px solid ${theme.palette.background.paper}`,
|
||||
},
|
||||
breadcrumb: {
|
||||
display: 'flex',
|
||||
flex: '0 1 3vh',
|
||||
flexDirection: 'row',
|
||||
flexGrow: 1,
|
||||
},
|
||||
link: {
|
||||
color: theme.palette.text.secondary,
|
||||
display: 'flex',
|
||||
padding: '0 1vw 0 1vw',
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
borderBottom: `1px solid transparent`,
|
||||
'&:hover': {
|
||||
color: theme.palette.action.hover,
|
||||
cursor: 'pointer',
|
||||
},
|
||||
},
|
||||
currentItem: {
|
||||
cursor: 'default',
|
||||
borderBottom: `1px solid ${theme.palette.action.hover}`,
|
||||
},
|
||||
}))
|
||||
|
||||
export const NavigationBreadcrumb = () => {
|
||||
const classes = useStyles()
|
||||
const setSelectedItem = useGlobalState((state) => state.setSelectedItem)
|
||||
const itemHierachyState = useGlobalState((state) => state.itemsHierarchy)
|
||||
const [searchInputState, setSearchInput] = useGlobalState((state) => [
|
||||
state.searchInput,
|
||||
state.setSearchInput,
|
||||
])
|
||||
const selectedItem = useGlobalState((state) => state.selectedItem)
|
||||
const [currentHierarchy, setCurrentHierarchy] = useState<ItemWithLocale[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedItem) return;
|
||||
|
||||
const hierarchy: ItemWithLocale[] = [selectedItem]
|
||||
let currItemID: string | undefined = selectedItem?.item?._parent
|
||||
while (currItemID) {
|
||||
const item: ItemWithLocale = itemHierachyState[currItemID]!
|
||||
hierarchy.push(item)
|
||||
currItemID = item?.item?._parent
|
||||
}
|
||||
setCurrentHierarchy(hierarchy.filter(elt => elt !== undefined && elt !== null).reverse())
|
||||
}, [selectedItem, itemHierachyState])
|
||||
|
||||
const formatLink = (item: ItemWithLocale, idx: string) => {
|
||||
if (
|
||||
searchInputState === item.locale.Name ||
|
||||
searchInputState === item.item._id ||
|
||||
searchInputState === item.item._name
|
||||
) {
|
||||
return (
|
||||
<Typography key={item.item._id} variant="body2" className={classes.currentItem}>
|
||||
{item.locale.Name ? item.locale.Name : item.item._name}
|
||||
</Typography>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Link
|
||||
underline="hover"
|
||||
color="inherit"
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
setSearchInput(item.item._id)
|
||||
setSelectedItem(undefined)
|
||||
}}
|
||||
className={classes.link}
|
||||
>
|
||||
<Typography variant="body2">{item.locale.Name ? item.locale.Name : item.item._name}</Typography>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className={classes.breadcrumbHolder}>
|
||||
<Breadcrumbs aria-label="breadcrumb" className={classes.breadcrumb} id='navigation-breadcrumb'>
|
||||
<Link
|
||||
underline="hover"
|
||||
color="inherit"
|
||||
key={'home'}
|
||||
href="/"
|
||||
id='home-breadcrumb'
|
||||
className={classes.link}
|
||||
>
|
||||
<Typography variant="body2">Home</Typography>
|
||||
</Link>
|
||||
{currentHierarchy.map((item, idx) => formatLink(item, idx.toString()))}
|
||||
</Breadcrumbs>
|
||||
</Box>
|
||||
)
|
||||
}
|
207
db/app/core/components/SearchArea.tsx
Normal file
207
db/app/core/components/SearchArea.tsx
Normal file
@ -0,0 +1,207 @@
|
||||
import {ReactElement, SyntheticEvent, useCallback, useEffect, useState} from 'react'
|
||||
import {Autocomplete, Box, CircularProgress, Theme, Typography,} from '@mui/material'
|
||||
import {makeStyles} from '@mui/styles'
|
||||
import TextField from '@mui/material/TextField'
|
||||
import {getItem, getItemHierarchy, searchItem} from '../dataaccess/ItemBackend';
|
||||
import {ItemOption} from '../dto/ItemOption'
|
||||
import {useGlobalState} from '../state/GlobalState'
|
||||
// import {useNavigate, useParams} from "react-router-dom";
|
||||
import {useRouter, dynamic } from "blitz";
|
||||
|
||||
let ReactJson
|
||||
if (process.browser){
|
||||
ReactJson = dynamic(() => import("react-json-view"))
|
||||
}
|
||||
|
||||
interface IItemOption {
|
||||
id: string
|
||||
name: string
|
||||
shortName?: string
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
searchAreaHolder: {
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexDirection: 'column',
|
||||
background: theme.palette.background.paper,
|
||||
padding: '2vh 2vw 2vh 2vw',
|
||||
},
|
||||
jsonHolder: {
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
background: theme.palette.background.paper,
|
||||
maxHeight: '80vh',
|
||||
},
|
||||
autocomplete: {},
|
||||
}))
|
||||
|
||||
export const SearchArea = () => {
|
||||
const classes = useStyles();
|
||||
const router = useRouter()
|
||||
const params = router.query;
|
||||
const preferedLocale = useGlobalState((state) => state.preferedLocale)
|
||||
const preferedJsonViewerTheme = useGlobalState(
|
||||
useCallback((state) => state.preferedJsonViewerTheme, []),
|
||||
)
|
||||
const [searchInputState, setSearchInput] = useGlobalState((state) => [
|
||||
state.searchInput,
|
||||
state.setSearchInput,
|
||||
])
|
||||
const [setHierarchy, initHierarchy] = useGlobalState((state) => [state.setHierarchy, state.initHierarchy])
|
||||
const [selectedItem, setSelectedItem] = useGlobalState((state) => [
|
||||
state.selectedItem,
|
||||
state.setSelectedItem,
|
||||
])
|
||||
const [selectOptions, setSelecteOptions] = useState<ItemOption[]>([])
|
||||
const [isbusy, setIsBusy] = useState<boolean>(false)
|
||||
const searchThreshold = 3
|
||||
|
||||
const handleNameInput = useCallback(async (input: string) => {
|
||||
const searchResults = await searchItem(input, preferedLocale)
|
||||
const options = searchResults?.map((res) => ({
|
||||
id: res.item._id,
|
||||
name: res.locale.Name ? res.locale.Name : res.item._name,
|
||||
shortName: JSON.stringify(res.locale.ShortName)
|
||||
}))
|
||||
setSelecteOptions(options ? options : [])
|
||||
}, [preferedLocale])
|
||||
|
||||
const handleIDInput = useCallback(async (input: string) => {
|
||||
const itemJson = await getItem(input, preferedLocale)
|
||||
if (!itemJson) {
|
||||
setSelectedItem(undefined)
|
||||
setSearchInput('')
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedItem(itemJson)
|
||||
const itemObj = {
|
||||
id: itemJson.item._id,
|
||||
name: itemJson.locale.Name ? itemJson.locale.Name : itemJson.item._name,
|
||||
shortName: itemJson.locale.ShortName
|
||||
}
|
||||
setSelecteOptions([itemObj])
|
||||
setSearchInput(itemObj.name)
|
||||
|
||||
// Update hierachy
|
||||
const itemHierarchy = await getItemHierarchy(
|
||||
itemJson.item,
|
||||
preferedLocale,
|
||||
)
|
||||
setHierarchy(itemHierarchy ? itemHierarchy : {})
|
||||
// eslint-disable-next-line
|
||||
}, []) // Need to only be created on startup
|
||||
|
||||
useEffect(() => initHierarchy(), [initHierarchy])
|
||||
|
||||
useEffect(()=>{
|
||||
if (selectedItem){
|
||||
router.replace(`/search/${selectedItem.item._id}`)
|
||||
}
|
||||
},[selectedItem, router])
|
||||
|
||||
useEffect(() => {
|
||||
if (searchInputState && searchInputState.match(/([a-z0-9]{24})/)) {
|
||||
handleIDInput(searchInputState)
|
||||
}
|
||||
}, [handleIDInput, searchInputState])
|
||||
|
||||
const handleInput = useCallback(async (input: string) => {
|
||||
if (!input || input.length < searchThreshold || isbusy) {
|
||||
setSelectedItem(undefined)
|
||||
setSelecteOptions([])
|
||||
setIsBusy(false)
|
||||
return
|
||||
}
|
||||
setIsBusy(true)
|
||||
|
||||
if (input.match(/([a-z0-9]{24})/)) await handleIDInput(input)
|
||||
else await handleNameInput(input)
|
||||
|
||||
setIsBusy(false)
|
||||
}, [handleIDInput, handleNameInput, isbusy, setSelectedItem])
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchInputState && params.id) {
|
||||
const newId = (params.id as string).trim();
|
||||
console.log(newId);
|
||||
setSearchInput(newId);
|
||||
(async () => await handleInput(newId))();
|
||||
}
|
||||
}, [params, searchInputState, setSearchInput, handleInput, router])
|
||||
|
||||
const formatDisplayItems = () => {
|
||||
// If loading
|
||||
if (isbusy) return <CircularProgress size={100}/>
|
||||
|
||||
// If finished loading
|
||||
console.log(process.browser)
|
||||
if (selectedItem && ReactJson !== undefined) {
|
||||
return (<ReactJson
|
||||
src={selectedItem!}
|
||||
theme={preferedJsonViewerTheme}
|
||||
style={{
|
||||
marginTop: '2vh',
|
||||
width: '100%',
|
||||
overflowY: 'auto',
|
||||
display: 'flex',
|
||||
}}
|
||||
/>)
|
||||
} else return <Typography id='search-no-data'>No data to display</Typography>
|
||||
// return <Typography id='search-no-data'>No data to display</Typography>
|
||||
}
|
||||
|
||||
const findOptionValue = (option: ItemOption, value: ItemOption): boolean => {
|
||||
return option.name?.toLocaleLowerCase() === value.name?.toLocaleLowerCase()
|
||||
|| option.id?.toLocaleLowerCase() === value.id?.toLocaleLowerCase()
|
||||
|| option.shortName?.toLocaleLowerCase() === value.shortName?.toLocaleLowerCase()
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className={classes.searchAreaHolder}>
|
||||
<Autocomplete
|
||||
id='search-autocomplete'
|
||||
options={selectOptions.map((elt) => ({name: elt.name, shortName: elt.shortName, id: elt.id}))}
|
||||
getOptionLabel={(option) => (option.name ? option.name : option.id) as string}
|
||||
isOptionEqualToValue={(option, value) => findOptionValue(option, value)}
|
||||
open={!isbusy && searchInputState.length >= searchThreshold && (searchInputState !== selectedItem?.locale.Name && searchInputState !== selectedItem?.item._name)}
|
||||
className={classes.autocomplete}
|
||||
inputValue={searchInputState ? searchInputState : ''}
|
||||
onInputChange={async (evt: SyntheticEvent, newValue: string) => {
|
||||
if (!evt) return
|
||||
setSelectedItem(undefined)
|
||||
setSearchInput(newValue)
|
||||
await handleInput(newValue.trim())
|
||||
}}
|
||||
value={(() => {
|
||||
const selectedOption = selectOptions.find(elt => elt.id === searchInputState || elt.name === searchInputState);
|
||||
return selectedOption ? selectedOption : null;
|
||||
})()}
|
||||
onChange={async (event: SyntheticEvent, newValue: IItemOption | null) => {
|
||||
if (newValue) {
|
||||
const selectedOption = selectOptions.find(
|
||||
(elt) => elt.name === newValue.name,
|
||||
)
|
||||
if (selectedOption) await handleIDInput(selectedOption.id)
|
||||
}
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} label="Search by name or ID"/>
|
||||
)}
|
||||
renderOption={(props, option ) => (
|
||||
<li {...props} key={option.id}><Typography>{option.name}</Typography></li>
|
||||
)}
|
||||
filterOptions={(options, state) => options.filter(elt => {
|
||||
return (elt.name?.toLocaleLowerCase().includes(state.inputValue.toLocaleLowerCase())
|
||||
|| elt.id?.toLocaleLowerCase().includes(state.inputValue.toLocaleLowerCase())
|
||||
|| elt.shortName?.toLocaleLowerCase().includes(state.inputValue.toLocaleLowerCase()))
|
||||
})}
|
||||
filterSelectedOptions
|
||||
/>
|
||||
<Box className={classes.jsonHolder}>{formatDisplayItems()}</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
14
db/app/core/dataaccess/ItemBackend.ts
Normal file
14
db/app/core/dataaccess/ItemBackend.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export const getItem = (test:any,test2:any):any=>{return null}
|
||||
export const getItemHierarchy = (test:any,test2:any):any =>{return null}
|
||||
export const searchItem = (test:any,test2:any):any[] =>{ return [
|
||||
{
|
||||
item:{
|
||||
_id: 'ABCDE',
|
||||
_name: 'Name',
|
||||
},
|
||||
locale:{
|
||||
name: "test"
|
||||
}
|
||||
|
||||
}
|
||||
]}
|
59
db/app/core/dto/ItemData.ts
Normal file
59
db/app/core/dto/ItemData.ts
Normal file
@ -0,0 +1,59 @@
|
||||
export interface ItemData {
|
||||
_id: string
|
||||
_name: string
|
||||
_parent?: string
|
||||
_type?: string
|
||||
_props: {
|
||||
Name: string
|
||||
ShortName: string
|
||||
Description: string
|
||||
Weight: number
|
||||
BackgroundColor: string
|
||||
Width: number
|
||||
Height: number
|
||||
StackMaxSize: number
|
||||
ItemSound: string
|
||||
Prefab: {
|
||||
path: string
|
||||
rcid: string
|
||||
}
|
||||
UsePrefab: {
|
||||
path: string
|
||||
rcid: string
|
||||
}
|
||||
StackObjectsCount: number
|
||||
NotShownInSlot: boolean
|
||||
ExaminedByDefault: boolean
|
||||
ExamineTime: number
|
||||
IsUndiscardable: boolean
|
||||
IsUnsaleable: boolean
|
||||
IsUnbuyable: boolean
|
||||
IsUngivable: boolean
|
||||
IsLockedafterEquip: boolean
|
||||
QuestItem: boolean
|
||||
LootExperience: number
|
||||
ExamineExperience: number
|
||||
HideEntrails: boolean
|
||||
RepairCost: number
|
||||
RepairSpeed: number
|
||||
ExtraSizeLeft: number
|
||||
ExtraSizeRight: number
|
||||
ExtraSizeUp: number
|
||||
ExtraSizeDown: number
|
||||
ExtraSizeForceAdd: boolean
|
||||
MergesWithChildren: boolean
|
||||
CanSellOnRagfair: boolean
|
||||
CanRequireOnRagfair: boolean
|
||||
ConflictingItems: string[]
|
||||
Unlootable: boolean
|
||||
UnlootableFromSlot: string
|
||||
UnlootableFromSide: string[]
|
||||
AnimationVariantsNumber: number
|
||||
DiscardingBlock: boolean
|
||||
RagFairCommissionModifier: number
|
||||
IsAlwaysAvailableForInsurance: boolean
|
||||
MaxResource: number
|
||||
Resource: number
|
||||
}
|
||||
_proto?: string
|
||||
}
|
29
db/app/core/dto/rawData/RawItemData.ts
Normal file
29
db/app/core/dto/rawData/RawItemData.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { RawItemProps } from './RawItemProps';
|
||||
|
||||
export class RawItemData {
|
||||
_id: string;
|
||||
_name: string;
|
||||
_parent: string;
|
||||
_type: string;
|
||||
_props: RawItemProps;
|
||||
_proto?: string | undefined;
|
||||
|
||||
static fromRawData(data: any): RawItemData {
|
||||
const rawData = new RawItemData();
|
||||
Object.assign(rawData, data)
|
||||
rawData._props = RawItemProps.fromRawData(data['_props'])
|
||||
|
||||
return rawData;
|
||||
}
|
||||
|
||||
toItemData(): Prisma.XOR<Prisma.ItemCreateInput, Prisma.ItemUncheckedCreateInput> {
|
||||
return {
|
||||
internalId: this._id,
|
||||
name: this._name,
|
||||
type: this._type,
|
||||
props: this._props.toItemPropsData(),
|
||||
proto: this._proto
|
||||
}
|
||||
}
|
||||
}
|
27
db/app/core/dto/rawData/RawItemPrefab.ts
Normal file
27
db/app/core/dto/rawData/RawItemPrefab.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Prisma } from "@prisma/client"
|
||||
|
||||
export class RawItemPrefab {
|
||||
id: number
|
||||
path: string
|
||||
rcid: string
|
||||
|
||||
static fromRawData(data: any): RawItemPrefab {
|
||||
const rawData = new RawItemPrefab();
|
||||
Object.assign(rawData, data)
|
||||
return rawData
|
||||
}
|
||||
|
||||
toItemPrefabData(): Prisma.ItemPrefabCreateNestedOneWithoutItemPropInput {
|
||||
return {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
id: this.id
|
||||
},
|
||||
create: {
|
||||
path: this.path,
|
||||
rcid: this.path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
db/app/core/dto/rawData/RawItemProps.ts
Normal file
77
db/app/core/dto/rawData/RawItemProps.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { RawItemPrefab } from './RawItemPrefab';
|
||||
import { RawItemUsePrefab } from './RawItemUsePrefab';
|
||||
|
||||
export class RawItemProps {
|
||||
id: number
|
||||
Name: string
|
||||
ShortName: string
|
||||
Description: string
|
||||
Weight: number
|
||||
BackgroundColor: string
|
||||
Width: number
|
||||
Height: number
|
||||
StackMaxSize: number
|
||||
ItemSound: string
|
||||
Prefab: RawItemPrefab
|
||||
UsePrefab: RawItemUsePrefab
|
||||
StackObjectsCount: number
|
||||
NotShownInSlot: boolean
|
||||
ExaminedByDefault: boolean
|
||||
ExamineTime: number
|
||||
IsUndiscardable: boolean
|
||||
IsUnsaleable: boolean
|
||||
IsUnbuyable: boolean
|
||||
IsUngivable: boolean
|
||||
IsLockedafterEquip: boolean
|
||||
QuestItem: boolean
|
||||
LootExperience: number
|
||||
ExamineExperience: number
|
||||
HideEntrails: boolean
|
||||
RepairCost: number
|
||||
RepairSpeed: number
|
||||
ExtraSizeLeft: number
|
||||
ExtraSizeRight: number
|
||||
ExtraSizeUp: number
|
||||
ExtraSizeDown: number
|
||||
ExtraSizeForceAdd: boolean
|
||||
MergesWithChildren: boolean
|
||||
CanSellOnRagfair: boolean
|
||||
CanRequireOnRagfair: boolean
|
||||
ConflictingItems: string[]
|
||||
Unlootable: boolean
|
||||
UnlootableFromSlot: string
|
||||
UnlootableFromSide: string[]
|
||||
AnimationVariantsNumber: number
|
||||
DiscardingBlock: boolean
|
||||
RagFairCommissionModifier: number
|
||||
IsAlwaysAvailableForInsurance: boolean
|
||||
MaxResource: number
|
||||
Resource: number
|
||||
Backgroundcolor: string
|
||||
IsLockedAfterEquip: boolean
|
||||
|
||||
static fromRawData(data: any): RawItemProps {
|
||||
const rawData = new RawItemProps();
|
||||
Object.assign(rawData, data)
|
||||
rawData.Prefab = RawItemPrefab.fromRawData(data['Prefab'])
|
||||
rawData.UsePrefab = RawItemUsePrefab.fromRawData(data['UsePrefab'])
|
||||
return rawData
|
||||
}
|
||||
|
||||
toItemPropsData(): Prisma.ItemPropCreateNestedOneWithoutItemInput {
|
||||
const {id, ...data} = this;
|
||||
return {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
id
|
||||
},
|
||||
create: {
|
||||
...data,
|
||||
Prefab: this.Prefab.toItemPrefabData(),
|
||||
UsePrefab: this.UsePrefab.toItemUsePrefabData()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
db/app/core/dto/rawData/RawItemUsePrefab.ts
Normal file
26
db/app/core/dto/rawData/RawItemUsePrefab.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
export class RawItemUsePrefab {
|
||||
id: number
|
||||
path: string
|
||||
rcid: string
|
||||
|
||||
static fromRawData(data: any): RawItemUsePrefab {
|
||||
const rawData = new RawItemUsePrefab();
|
||||
Object.assign(rawData, data)
|
||||
return rawData
|
||||
}
|
||||
|
||||
toItemUsePrefabData(): Prisma.ItemUsePrefabCreateNestedOneWithoutItemPropInput {
|
||||
return {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
id: this.id
|
||||
},
|
||||
create: {
|
||||
path: this.path,
|
||||
rcid: this.rcid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
db/app/core/layouts/Layout.tsx
Normal file
35
db/app/core/layouts/Layout.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import {Box} from '@mui/material'
|
||||
import {Footer} from '../components/Footer'
|
||||
import {Header} from '../components/Header'
|
||||
import {makeStyles} from "@mui/styles";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
// import {InteractiveArea} from "./InteractiveArea";
|
||||
// import {PageNotFound} from "./PageNotFound";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
container: {
|
||||
background: 'background.default',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
height: '100vh',
|
||||
maxheight: '100vh',
|
||||
}
|
||||
}))
|
||||
|
||||
interface Props {}
|
||||
|
||||
export const Layout = (props: PropsWithChildren<Props>) => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<>
|
||||
<Box className={classes.container}>
|
||||
<Header/>
|
||||
{props.children}
|
||||
<Footer/>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Layout ;
|
122
db/app/core/state/GlobalState.ts
Normal file
122
db/app/core/state/GlobalState.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import { ThemeKeys } from 'react-json-view'
|
||||
import create from 'zustand'
|
||||
// import { getLocaleList } from '../dataaccess/ItemBackend'
|
||||
import { LocalStorageKeys, SessionStorageKeys } from '../dto/SaveKeys'
|
||||
import { ItemHierarchy } from '../dto/ItemHierarchy'
|
||||
import { ReactJsonViewThemes } from './ReactJsonViewThemes'
|
||||
import { ThemeMode } from './ThemeMode'
|
||||
import { ItemWithLocale } from '../dto/ItemWithLocale';
|
||||
|
||||
const getLocaleList = async () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface GlobalState {
|
||||
sptarkovWebsiteUrl: string
|
||||
sptarkovWorkshopUrl: string
|
||||
sptarkovDocumentationUrl: string
|
||||
|
||||
preferedLocale: string
|
||||
setPreferedLocale: (newLocale: string) => void
|
||||
localesList: string[]
|
||||
refreshLocalesList: () => void
|
||||
|
||||
preferedJsonViewerTheme: ThemeKeys
|
||||
setPreferedJsonViewerTheme: (newJsonTheme: string) => void
|
||||
|
||||
preferedColorScheme: string
|
||||
setPreferedColorScheme: (newColorScheme: ThemeMode) => void
|
||||
|
||||
searchInput: string
|
||||
setSearchInput: (newInput: string) => void
|
||||
|
||||
desiredSearchInput: string
|
||||
setDesiredSearchInput: (newInput: string) => void
|
||||
|
||||
itemsHierarchy: ItemHierarchy
|
||||
initHierarchy: () => void
|
||||
setHierarchy: (newHierarchy: ItemHierarchy) => void
|
||||
|
||||
selectedItem: ItemWithLocale | undefined
|
||||
setSelectedItem: (newSelectedItem: ItemWithLocale | undefined) => void
|
||||
}
|
||||
|
||||
const isClientSide = () => typeof window == 'object' || typeof window !== 'undefined';
|
||||
|
||||
const preferedLocale = isClientSide() ? localStorage?.getItem(LocalStorageKeys.PREFERED_LOCALE) : null
|
||||
const storedPreferedJsonTheme = isClientSide() ? localStorage?.getItem(
|
||||
LocalStorageKeys.PREFERED_JSON_THEME,
|
||||
) : ThemeMode.DARK_MODE;
|
||||
const preferedColorScheme = isClientSide() ? localStorage?.getItem(LocalStorageKeys.PREFERED_COLOR_SCHEME) : null;
|
||||
|
||||
export const useGlobalState = create<GlobalState>((set) => ({
|
||||
sptarkovWebsiteUrl: process.env.REACT_APP_SPTARKOV_HOME ? process.env.REACT_APP_SPTARKOV_HOME : '',
|
||||
sptarkovWorkshopUrl: process.env.REACT_APP_SPTARKOV_WORKSHOP ? process.env.REACT_APP_SPTARKOV_WORKSHOP : '',
|
||||
sptarkovDocumentationUrl: process.env.REACT_APP_SPTARKOV_DOCUMENTATION ? process.env.REACT_APP_SPTARKOV_DOCUMENTATION : '',
|
||||
|
||||
// Locale
|
||||
preferedLocale: preferedLocale ? preferedLocale : 'en',
|
||||
setPreferedLocale: (newLocale: string) => {
|
||||
if (isClientSide())
|
||||
localStorage?.setItem(LocalStorageKeys.PREFERED_LOCALE, newLocale)
|
||||
set((_state) => ({ preferedLocale: newLocale }))
|
||||
},
|
||||
localesList: [],
|
||||
refreshLocalesList: async () => {
|
||||
const locales = isClientSide() ? sessionStorage?.getItem(SessionStorageKeys.LOCALES) : "[en]";
|
||||
const localesList = locales !== null && locales !== undefined && locales !== 'undefined' && locales !== 'null' ? JSON.parse(locales) : await getLocaleList()
|
||||
if (!locales && isClientSide()) sessionStorage?.setItem(SessionStorageKeys.LOCALES, JSON.stringify(localesList ? localesList : null))
|
||||
set((_state) => ({ localesList: localesList ? localesList : [] }))
|
||||
},
|
||||
|
||||
// Json viewer theme
|
||||
preferedJsonViewerTheme:
|
||||
storedPreferedJsonTheme &&
|
||||
ReactJsonViewThemes.includes(storedPreferedJsonTheme)
|
||||
? (storedPreferedJsonTheme as ThemeKeys)
|
||||
: (ReactJsonViewThemes[0] as ThemeKeys),
|
||||
setPreferedJsonViewerTheme: (newJsonTheme: string) => {
|
||||
if (isClientSide()) localStorage?.setItem(LocalStorageKeys.PREFERED_JSON_THEME, newJsonTheme)
|
||||
set((_state) => ({ preferedJsonViewerTheme: newJsonTheme as ThemeKeys }))
|
||||
},
|
||||
|
||||
// Prefered theme
|
||||
preferedColorScheme: preferedColorScheme
|
||||
? preferedColorScheme
|
||||
: ThemeMode.DARK_MODE,
|
||||
setPreferedColorScheme: (newColorScheme: ThemeMode) => {
|
||||
if (isClientSide()) localStorage?.setItem(LocalStorageKeys.PREFERED_COLOR_SCHEME, newColorScheme)
|
||||
set((_state) => ({ preferedColorScheme: newColorScheme }))
|
||||
},
|
||||
|
||||
// SearchInput
|
||||
searchInput: '',
|
||||
setSearchInput: (newInput: string) =>
|
||||
set((_state) => ({ searchInput: newInput })),
|
||||
desiredSearchInput: '',
|
||||
setDesiredSearchInput: (newInput: string) =>
|
||||
set((_state) => ({ desiredSearchInput: newInput })),
|
||||
|
||||
// Hierarchy
|
||||
itemsHierarchy: {},
|
||||
initHierarchy: () => {
|
||||
const itemsHierarchy = isClientSide() ? sessionStorage?.getItem(SessionStorageKeys.ITEMS_HIERARCHY) : "{}";
|
||||
if (itemsHierarchy !== null && itemsHierarchy !== undefined && itemsHierarchy !== 'undefined') {
|
||||
set((_state) => ({ itemsHierarchy: JSON.parse(itemsHierarchy) }))
|
||||
}
|
||||
},
|
||||
setHierarchy: (newHierarchy: ItemHierarchy) => {
|
||||
set((state) => {
|
||||
const newStateHierarchy = Object.assign({}, state.itemsHierarchy, newHierarchy);
|
||||
if (isClientSide()) sessionStorage?.setItem(SessionStorageKeys.ITEMS_HIERARCHY, JSON.stringify(newStateHierarchy ? newStateHierarchy : null))
|
||||
return ({
|
||||
itemsHierarchy: newStateHierarchy,
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
// Selected item
|
||||
selectedItem: undefined,
|
||||
setSelectedItem: (newSelectedItem: ItemWithLocale | undefined) =>
|
||||
set((_state) => ({ selectedItem: newSelectedItem })),
|
||||
}))
|
21
db/app/core/theme/darkTheme.ts
Normal file
21
db/app/core/theme/darkTheme.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import {ThemeMode} from '../state/ThemeMode';
|
||||
import {common, grey, lightBlue, yellow} from '@mui/material/colors';
|
||||
import { createTheme } from "@mui/material/styles";
|
||||
|
||||
export const darkPalette = createTheme({
|
||||
palette:{
|
||||
mode: ThemeMode.DARK_MODE,
|
||||
background: {
|
||||
default: grey[900],
|
||||
paper: '#121212'
|
||||
},
|
||||
text: {
|
||||
primary: common.white,
|
||||
secondary: '#8894a2',
|
||||
disabled: lightBlue[100]
|
||||
},
|
||||
action: {
|
||||
hover: yellow[700]
|
||||
},
|
||||
}
|
||||
});
|
21
db/app/core/theme/lightTheme.ts
Normal file
21
db/app/core/theme/lightTheme.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import {ThemeMode} from '../state/ThemeMode';
|
||||
import {blue, common, grey} from "@mui/material/colors";
|
||||
import {createTheme} from "@mui/material/styles";
|
||||
|
||||
export const lightPalette = createTheme({
|
||||
palette: {
|
||||
mode: ThemeMode.LIGHT_MODE,
|
||||
background: {
|
||||
default: grey[100],
|
||||
paper: grey[300]
|
||||
},
|
||||
text: {
|
||||
primary: common.black,
|
||||
secondary: blue[500],
|
||||
disabled: grey[600]
|
||||
},
|
||||
action: {
|
||||
hover: blue[500],
|
||||
}
|
||||
}
|
||||
});
|
12
db/app/item-prefabs/components/ItemPrefabForm.tsx
Normal file
12
db/app/item-prefabs/components/ItemPrefabForm.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { Form, FormProps } from "app/core/components/Form"
|
||||
import { LabeledTextField } from "app/core/components/LabeledTextField"
|
||||
import { z } from "zod"
|
||||
export { FORM_ERROR } from "app/core/components/Form"
|
||||
|
||||
export function ItemPrefabForm<S extends z.ZodType<any, any>>(props: FormProps<S>) {
|
||||
return (
|
||||
<Form<S> {...props}>
|
||||
<LabeledTextField name="name" label="Name" placeholder="Name" />
|
||||
</Form>
|
||||
)
|
||||
}
|
18
db/app/item-prefabs/mutations/createItemPrefab.ts
Normal file
18
db/app/item-prefabs/mutations/createItemPrefab.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { resolver } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const CreateItemPrefab = z.object({
|
||||
name: z.string(),
|
||||
})
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(CreateItemPrefab),
|
||||
resolver.authorize(),
|
||||
async (input) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const itemPrefab = await db.itemPrefab.create({ data: input })
|
||||
|
||||
return itemPrefab
|
||||
}
|
||||
)
|
18
db/app/item-prefabs/mutations/deleteItemPrefab.ts
Normal file
18
db/app/item-prefabs/mutations/deleteItemPrefab.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { resolver } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const DeleteItemPrefab = z.object({
|
||||
id: z.number(),
|
||||
})
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(DeleteItemPrefab),
|
||||
resolver.authorize(),
|
||||
async ({ id }) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const itemPrefab = await db.itemPrefab.deleteMany({ where: { id } })
|
||||
|
||||
return itemPrefab
|
||||
}
|
||||
)
|
19
db/app/item-prefabs/mutations/updateItemPrefab.ts
Normal file
19
db/app/item-prefabs/mutations/updateItemPrefab.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { resolver } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const UpdateItemPrefab = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
})
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(UpdateItemPrefab),
|
||||
resolver.authorize(),
|
||||
async ({ id, ...data }) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const itemPrefab = await db.itemPrefab.update({ where: { id }, data })
|
||||
|
||||
return itemPrefab
|
||||
}
|
||||
)
|
17
db/app/item-prefabs/queries/getItemPrefab.ts
Normal file
17
db/app/item-prefabs/queries/getItemPrefab.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { resolver, NotFoundError } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const GetItemPrefab = z.object({
|
||||
// This accepts type of undefined, but is required at runtime
|
||||
id: z.number().optional().refine(Boolean, "Required"),
|
||||
})
|
||||
|
||||
export default resolver.pipe(resolver.zod(GetItemPrefab), resolver.authorize(), async ({ id }) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const itemPrefab = await db.itemPrefab.findFirst({ where: { id } })
|
||||
|
||||
if (!itemPrefab) throw new NotFoundError()
|
||||
|
||||
return itemPrefab
|
||||
})
|
30
db/app/item-prefabs/queries/getItemPrefabs.ts
Normal file
30
db/app/item-prefabs/queries/getItemPrefabs.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { paginate, resolver } from "blitz"
|
||||
import db, { Prisma } from "db"
|
||||
|
||||
interface GetItemPrefabsInput
|
||||
extends Pick<Prisma.ItemPrefabFindManyArgs, "where" | "orderBy" | "skip" | "take"> {}
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.authorize(),
|
||||
async ({ where, orderBy, skip = 0, take = 100 }: GetItemPrefabsInput) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const {
|
||||
items: itemPrefabs,
|
||||
hasMore,
|
||||
nextPage,
|
||||
count,
|
||||
} = await paginate({
|
||||
skip,
|
||||
take,
|
||||
count: () => db.itemPrefab.count({ where }),
|
||||
query: (paginateArgs) => db.itemPrefab.findMany({ ...paginateArgs, where, orderBy }),
|
||||
})
|
||||
|
||||
return {
|
||||
itemPrefabs,
|
||||
nextPage,
|
||||
hasMore,
|
||||
count,
|
||||
}
|
||||
}
|
||||
)
|
12
db/app/item-props/components/ItemPropForm.tsx
Normal file
12
db/app/item-props/components/ItemPropForm.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { Form, FormProps } from "app/core/components/Form"
|
||||
import { LabeledTextField } from "app/core/components/LabeledTextField"
|
||||
import { z } from "zod"
|
||||
export { FORM_ERROR } from "app/core/components/Form"
|
||||
|
||||
export function ItemPropForm<S extends z.ZodType<any, any>>(props: FormProps<S>) {
|
||||
return (
|
||||
<Form<S> {...props}>
|
||||
<LabeledTextField name="name" label="Name" placeholder="Name" />
|
||||
</Form>
|
||||
)
|
||||
}
|
14
db/app/item-props/mutations/createItemProp.ts
Normal file
14
db/app/item-props/mutations/createItemProp.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { resolver } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const CreateItemProp = z.object({
|
||||
name: z.string(),
|
||||
})
|
||||
|
||||
export default resolver.pipe(resolver.zod(CreateItemProp), resolver.authorize(), async (input) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const itemProp = await db.itemProp.create({ data: input })
|
||||
|
||||
return itemProp
|
||||
})
|
14
db/app/item-props/mutations/deleteItemProp.ts
Normal file
14
db/app/item-props/mutations/deleteItemProp.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { resolver } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const DeleteItemProp = z.object({
|
||||
id: z.number(),
|
||||
})
|
||||
|
||||
export default resolver.pipe(resolver.zod(DeleteItemProp), resolver.authorize(), async ({ id }) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const itemProp = await db.itemProp.deleteMany({ where: { id } })
|
||||
|
||||
return itemProp
|
||||
})
|
19
db/app/item-props/mutations/updateItemProp.ts
Normal file
19
db/app/item-props/mutations/updateItemProp.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { resolver } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const UpdateItemProp = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
})
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(UpdateItemProp),
|
||||
resolver.authorize(),
|
||||
async ({ id, ...data }) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const itemProp = await db.itemProp.update({ where: { id }, data })
|
||||
|
||||
return itemProp
|
||||
}
|
||||
)
|
17
db/app/item-props/queries/getItemProp.ts
Normal file
17
db/app/item-props/queries/getItemProp.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { resolver, NotFoundError } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const GetItemProp = z.object({
|
||||
// This accepts type of undefined, but is required at runtime
|
||||
id: z.number().optional().refine(Boolean, "Required"),
|
||||
})
|
||||
|
||||
export default resolver.pipe(resolver.zod(GetItemProp), resolver.authorize(), async ({ id }) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const itemProp = await db.itemProp.findFirst({ where: { id } })
|
||||
|
||||
if (!itemProp) throw new NotFoundError()
|
||||
|
||||
return itemProp
|
||||
})
|
30
db/app/item-props/queries/getItemProps.ts
Normal file
30
db/app/item-props/queries/getItemProps.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { paginate, resolver } from "blitz"
|
||||
import db, { Prisma } from "db"
|
||||
|
||||
interface GetItemPropsInput
|
||||
extends Pick<Prisma.ItemPropFindManyArgs, "where" | "orderBy" | "skip" | "take"> {}
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.authorize(),
|
||||
async ({ where, orderBy, skip = 0, take = 100 }: GetItemPropsInput) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const {
|
||||
items: itemProps,
|
||||
hasMore,
|
||||
nextPage,
|
||||
count,
|
||||
} = await paginate({
|
||||
skip,
|
||||
take,
|
||||
count: () => db.itemProp.count({ where }),
|
||||
query: (paginateArgs) => db.itemProp.findMany({ ...paginateArgs, where, orderBy }),
|
||||
})
|
||||
|
||||
return {
|
||||
itemProps,
|
||||
nextPage,
|
||||
hasMore,
|
||||
count,
|
||||
}
|
||||
}
|
||||
)
|
12
db/app/item-use-prefabs/components/ItemUsePrefabForm.tsx
Normal file
12
db/app/item-use-prefabs/components/ItemUsePrefabForm.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { Form, FormProps } from "app/core/components/Form"
|
||||
import { LabeledTextField } from "app/core/components/LabeledTextField"
|
||||
import { z } from "zod"
|
||||
export { FORM_ERROR } from "app/core/components/Form"
|
||||
|
||||
export function ItemUsePrefabForm<S extends z.ZodType<any, any>>(props: FormProps<S>) {
|
||||
return (
|
||||
<Form<S> {...props}>
|
||||
<LabeledTextField name="name" label="Name" placeholder="Name" />
|
||||
</Form>
|
||||
)
|
||||
}
|
18
db/app/item-use-prefabs/mutations/createItemUsePrefab.ts
Normal file
18
db/app/item-use-prefabs/mutations/createItemUsePrefab.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { resolver } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const CreateItemUsePrefab = z.object({
|
||||
name: z.string(),
|
||||
})
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(CreateItemUsePrefab),
|
||||
resolver.authorize(),
|
||||
async (input) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const itemUsePrefab = await db.itemUsePrefab.create({ data: input })
|
||||
|
||||
return itemUsePrefab
|
||||
}
|
||||
)
|
18
db/app/item-use-prefabs/mutations/deleteItemUsePrefab.ts
Normal file
18
db/app/item-use-prefabs/mutations/deleteItemUsePrefab.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { resolver } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const DeleteItemUsePrefab = z.object({
|
||||
id: z.number(),
|
||||
})
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(DeleteItemUsePrefab),
|
||||
resolver.authorize(),
|
||||
async ({ id }) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const itemUsePrefab = await db.itemUsePrefab.deleteMany({ where: { id } })
|
||||
|
||||
return itemUsePrefab
|
||||
}
|
||||
)
|
19
db/app/item-use-prefabs/mutations/updateItemUsePrefab.ts
Normal file
19
db/app/item-use-prefabs/mutations/updateItemUsePrefab.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { resolver } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const UpdateItemUsePrefab = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
})
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(UpdateItemUsePrefab),
|
||||
resolver.authorize(),
|
||||
async ({ id, ...data }) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const itemUsePrefab = await db.itemUsePrefab.update({ where: { id }, data })
|
||||
|
||||
return itemUsePrefab
|
||||
}
|
||||
)
|
21
db/app/item-use-prefabs/queries/getItemUsePrefab.ts
Normal file
21
db/app/item-use-prefabs/queries/getItemUsePrefab.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { resolver, NotFoundError } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const GetItemUsePrefab = z.object({
|
||||
// This accepts type of undefined, but is required at runtime
|
||||
id: z.number().optional().refine(Boolean, "Required"),
|
||||
})
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(GetItemUsePrefab),
|
||||
resolver.authorize(),
|
||||
async ({ id }) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const itemUsePrefab = await db.itemUsePrefab.findFirst({ where: { id } })
|
||||
|
||||
if (!itemUsePrefab) throw new NotFoundError()
|
||||
|
||||
return itemUsePrefab
|
||||
}
|
||||
)
|
30
db/app/item-use-prefabs/queries/getItemUsePrefabs.ts
Normal file
30
db/app/item-use-prefabs/queries/getItemUsePrefabs.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { paginate, resolver } from "blitz"
|
||||
import db, { Prisma } from "db"
|
||||
|
||||
interface GetItemUsePrefabsInput
|
||||
extends Pick<Prisma.ItemUsePrefabFindManyArgs, "where" | "orderBy" | "skip" | "take"> {}
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.authorize(),
|
||||
async ({ where, orderBy, skip = 0, take = 100 }: GetItemUsePrefabsInput) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const {
|
||||
items: itemUsePrefabs,
|
||||
hasMore,
|
||||
nextPage,
|
||||
count,
|
||||
} = await paginate({
|
||||
skip,
|
||||
take,
|
||||
count: () => db.itemUsePrefab.count({ where }),
|
||||
query: (paginateArgs) => db.itemUsePrefab.findMany({ ...paginateArgs, where, orderBy }),
|
||||
})
|
||||
|
||||
return {
|
||||
itemUsePrefabs,
|
||||
nextPage,
|
||||
hasMore,
|
||||
count,
|
||||
}
|
||||
}
|
||||
)
|
12
db/app/items/components/ItemForm.tsx
Normal file
12
db/app/items/components/ItemForm.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { Form, FormProps } from "app/core/components/Form"
|
||||
import { LabeledTextField } from "app/core/components/LabeledTextField"
|
||||
import { z } from "zod"
|
||||
export { FORM_ERROR } from "app/core/components/Form"
|
||||
|
||||
export function ItemForm<S extends z.ZodType<any, any>>(props: FormProps<S>) {
|
||||
return (
|
||||
<Form<S> {...props}>
|
||||
<LabeledTextField name="name" label="Name" placeholder="Name" />
|
||||
</Form>
|
||||
)
|
||||
}
|
16
db/app/items/mutations/createItem.ts
Normal file
16
db/app/items/mutations/createItem.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { resolver } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const CreateItem = z.object({
|
||||
id: z.number().optional().refine(Boolean, "Required"),
|
||||
name: z.string().optional().refine(String, "Required"),
|
||||
type: z.string().optional().refine(String, "Required"),
|
||||
})
|
||||
|
||||
export default resolver.pipe(resolver.zod(CreateItem), resolver.authorize(), async (input) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const item = await db.item.create({ data: input })
|
||||
|
||||
return item
|
||||
})
|
14
db/app/items/mutations/deleteItem.ts
Normal file
14
db/app/items/mutations/deleteItem.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { resolver } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const DeleteItem = z.object({
|
||||
id: z.number(),
|
||||
})
|
||||
|
||||
export default resolver.pipe(resolver.zod(DeleteItem), resolver.authorize(), async ({ id }) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const item = await db.item.deleteMany({ where: { id } })
|
||||
|
||||
return item
|
||||
})
|
19
db/app/items/mutations/updateItem.ts
Normal file
19
db/app/items/mutations/updateItem.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { resolver } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const UpdateItem = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
})
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(UpdateItem),
|
||||
resolver.authorize(),
|
||||
async ({ id, ...data }) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const item = await db.item.update({ where: { id }, data })
|
||||
|
||||
return item
|
||||
}
|
||||
)
|
17
db/app/items/queries/getItem.ts
Normal file
17
db/app/items/queries/getItem.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { resolver, NotFoundError } from "blitz"
|
||||
import db from "db"
|
||||
import { z } from "zod"
|
||||
|
||||
const GetItem = z.object({
|
||||
// This accepts type of undefined, but is required at runtime
|
||||
id: z.number().optional().refine(Boolean, "Required")
|
||||
})
|
||||
|
||||
export default resolver.pipe(resolver.zod(GetItem), resolver.authorize(), async ({ id }) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const item = await db.item.findFirst({ where: { id } })
|
||||
|
||||
if (!item) throw new NotFoundError()
|
||||
|
||||
return item
|
||||
})
|
30
db/app/items/queries/getItems.ts
Normal file
30
db/app/items/queries/getItems.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { paginate, resolver } from "blitz"
|
||||
import db, { Prisma } from "db"
|
||||
|
||||
interface GetItemsInput
|
||||
extends Pick<Prisma.ItemFindManyArgs, "where" | "orderBy" | "skip" | "take"> {}
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.authorize(),
|
||||
async ({ where, orderBy, skip = 0, take = 100 }: GetItemsInput) => {
|
||||
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
|
||||
const {
|
||||
items: items,
|
||||
hasMore,
|
||||
nextPage,
|
||||
count,
|
||||
} = await paginate({
|
||||
skip,
|
||||
take,
|
||||
count: () => db.item.count({ where }),
|
||||
query: (paginateArgs) => db.item.findMany({ ...paginateArgs, where, orderBy }),
|
||||
})
|
||||
|
||||
return {
|
||||
items,
|
||||
nextPage,
|
||||
hasMore,
|
||||
count,
|
||||
}
|
||||
}
|
||||
)
|
8
db/app/mutations/enqueueGreeting.ts
Normal file
8
db/app/mutations/enqueueGreeting.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import greetingsQueue from "app/api/greetingsQueue"
|
||||
|
||||
export default async function enqueueGreeting() {
|
||||
await greetingsQueue.enqueue({
|
||||
to: "Sandy Cheeks",
|
||||
message: "Howdy!",
|
||||
})
|
||||
}
|
49
db/app/pages/404.tsx
Normal file
49
db/app/pages/404.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import {Box, Theme, Typography} from '@mui/material'
|
||||
import {makeStyles} from "@mui/styles";
|
||||
import { Layout } from 'app/core/layouts/Layout';
|
||||
import { BlitzPage } from 'blitz';
|
||||
import React from "react";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
searchContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexGrow: 1,
|
||||
padding: '2vh 2vw 1vh 2vw'
|
||||
},
|
||||
notFoundAreaHolder: {
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexDirection: 'column',
|
||||
background: theme.palette.background.paper,
|
||||
padding: '2vh 2vw 2vh 2vw',
|
||||
},
|
||||
notFoundContainer: {
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexDirection: 'column',
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
paddingTop: "10vh"
|
||||
},
|
||||
}))
|
||||
|
||||
const PageNotFound: BlitzPage = () => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<>
|
||||
<Box className={classes.searchContainer}>
|
||||
<Box className={classes.notFoundAreaHolder}>
|
||||
<Box className={classes.notFoundContainer}>
|
||||
<Typography id={'not-found-message'} variant={"h3"}>This page does not exist !</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
PageNotFound.getLayout = (page) => <Layout>{page}</Layout>
|
||||
PageNotFound.suppressFirstRenderFlicker = true
|
||||
|
||||
export default PageNotFound;
|
63
db/app/pages/_app.tsx
Normal file
63
db/app/pages/_app.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import {
|
||||
AppProps,
|
||||
ErrorBoundary,
|
||||
ErrorComponent,
|
||||
ErrorFallbackProps,
|
||||
useQueryErrorResetBoundary,
|
||||
} from "blitz"
|
||||
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useGlobalState } from "app/core/state/GlobalState";
|
||||
import { LocalStorageKeys } from "app/core/dto/SaveKeys";
|
||||
import { ThemeMode } from "app/core/state/ThemeMode";
|
||||
import { darkPalette } from "app/core/theme/darkTheme";
|
||||
import { lightPalette } from "app/core/theme/lightTheme";
|
||||
import { getTheme } from "app/core/theme/Theme";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
const App = ({ Component, pageProps }: AppProps) => {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const {theme: prefersDarkMode} = useTheme()
|
||||
const [preferedColorScheme ,setPreferedColorScheme] = useGlobalState(state => [state.preferedColorScheme, state.setPreferedColorScheme])
|
||||
const reset = useQueryErrorResetBoundary().reset
|
||||
|
||||
useEffect(() => {
|
||||
const localPreferedTheme = localStorage.getItem(LocalStorageKeys.PREFERED_COLOR_SCHEME);
|
||||
if (localPreferedTheme) {
|
||||
setPreferedColorScheme(localPreferedTheme as ThemeMode)
|
||||
return
|
||||
}
|
||||
|
||||
const preferedTheme = prefersDarkMode ? ThemeMode.DARK_MODE : ThemeMode.LIGHT_MODE;
|
||||
setPreferedColorScheme(preferedTheme)
|
||||
// eslint-disable-next-line
|
||||
}, [prefersDarkMode]) // Need to be only used on prefersDarkMode change
|
||||
const getLayout = Component.getLayout || ((page) => page)
|
||||
|
||||
const palette = useMemo(
|
||||
()=> getTheme(preferedColorScheme === ThemeMode.DARK_MODE ? darkPalette.palette : lightPalette.palette),
|
||||
[preferedColorScheme]);
|
||||
|
||||
useEffect(() => setMounted(true), [])
|
||||
if (!mounted) return null
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={palette}>
|
||||
<CssBaseline />
|
||||
<ErrorBoundary
|
||||
FallbackComponent={RootErrorFallback}
|
||||
onReset={reset}
|
||||
>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
</ErrorBoundary>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function RootErrorFallback({ error }: ErrorFallbackProps) {
|
||||
return <ErrorComponent statusCode={error.statusCode || 400} title={error.message || error.name} />
|
||||
}
|
||||
|
||||
export default App;
|
50
db/app/pages/_document.tsx
Normal file
50
db/app/pages/_document.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import {
|
||||
DocumentContext,
|
||||
Document,
|
||||
Html,
|
||||
DocumentHead,
|
||||
Main,
|
||||
BlitzScript /*DocumentContext*/,
|
||||
} from "blitz";
|
||||
|
||||
import React from "react";
|
||||
import { ServerStyleSheets } from "@mui/styles";
|
||||
|
||||
class MyDocument extends Document {
|
||||
static async getInitialProps(ctx: DocumentContext) {
|
||||
const sheets = new ServerStyleSheets();
|
||||
const originalRenderPage = ctx.renderPage;
|
||||
|
||||
ctx.renderPage = () => originalRenderPage({
|
||||
enhanceApp: App => props => sheets.collect(<App {...props} />)
|
||||
});
|
||||
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
|
||||
return {
|
||||
...initialProps,
|
||||
styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()]
|
||||
};
|
||||
}
|
||||
|
||||
// Only uncomment if you need to customize this behaviour
|
||||
// static async getInitialProps(ctx: DocumentContext) {
|
||||
// const initialProps = await Document.getInitialProps(ctx)
|
||||
// return {...initialProps}
|
||||
// }
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<DocumentHead />
|
||||
<title>Item Finder</title>
|
||||
<body>
|
||||
<Main />
|
||||
<BlitzScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument
|
13
db/app/pages/index.test.tsx
Normal file
13
db/app/pages/index.test.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { render } from "test/utils"
|
||||
|
||||
import Home from "./index"
|
||||
|
||||
test.skip("renders blitz documentation link", () => {
|
||||
// This is an example of how to ensure a specific item is in the document
|
||||
// But it's disabled by default (by test.skip) so the test doesn't fail
|
||||
// when you remove the the default content from the page
|
||||
|
||||
const { getByText } = render(<Home />)
|
||||
const linkElement = getByText(/Documentation/i)
|
||||
expect(linkElement).toBeInTheDocument()
|
||||
})
|
44
db/app/pages/index.tsx
Normal file
44
db/app/pages/index.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import {Box, Theme, Typography} from '@mui/material'
|
||||
import {makeStyles} from "@mui/styles";
|
||||
import React from "react";
|
||||
import { BlitzPage } from 'blitz';
|
||||
import { Layout } from 'app/core/layouts/Layout';
|
||||
import { NavigationBreadcrumb } from 'app/core/components/NavigationBreadcrumb';
|
||||
import { SearchArea } from 'app/core/components/SearchArea';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
container: {
|
||||
background: theme.palette.background.default,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
height: '100vh',
|
||||
maxheight: '100vh',
|
||||
},
|
||||
searchContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexGrow: 1,
|
||||
padding: '2vh 2vw 1vh 2vw'
|
||||
}
|
||||
}));
|
||||
|
||||
const Search: BlitzPage = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box className={classes.container}>
|
||||
<NavigationBreadcrumb/>
|
||||
<Box className={classes.searchContainer}>
|
||||
<SearchArea/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Search.getLayout = (page) => <Layout>{page}</Layout>
|
||||
Search.suppressFirstRenderFlicker = true
|
||||
|
||||
export default Search;
|
4
db/babel.config.js
Normal file
4
db/babel.config.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
presets: ["blitz/babel"],
|
||||
plugins: [],
|
||||
}
|
6
db/blitz-env.d.ts
vendored
Normal file
6
db/blitz-env.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
13
db/blitz.config.ts
Normal file
13
db/blitz.config.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { BlitzConfig } from "blitz"
|
||||
|
||||
const config: BlitzConfig = {
|
||||
/* Uncomment this to customize the webpack config
|
||||
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
|
||||
// Note: we provide webpack above so you should not `require` it
|
||||
// Perform customizations to webpack config
|
||||
// Important: return the modified config
|
||||
return config
|
||||
},
|
||||
*/
|
||||
}
|
||||
module.exports = config
|
7
db/db/index.ts
Normal file
7
db/db/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { enhancePrisma } from "blitz"
|
||||
import { PrismaClient } from "@prisma/client"
|
||||
|
||||
const EnhancedPrisma = enhancePrisma(PrismaClient)
|
||||
|
||||
export * from "@prisma/client"
|
||||
export default new EnhancedPrisma()
|
@ -0,0 +1,41 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Item" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"itemId" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Item_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ItemProp" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"shortName" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"weight" INTEGER NOT NULL,
|
||||
"backgroundcolor" TEXT NOT NULL,
|
||||
"width" INTEGER NOT NULL,
|
||||
"height" INTEGER NOT NULL,
|
||||
"stackMaxSize" INTEGER NOT NULL,
|
||||
"itemSound" TEXT NOT NULL,
|
||||
"stackObjectsCount" INTEGER NOT NULL,
|
||||
"notShownInSlot" BOOLEAN NOT NULL,
|
||||
"examinedByDefault" BOOLEAN NOT NULL,
|
||||
"examineTime" INTEGER NOT NULL,
|
||||
"isUndiscardable" BOOLEAN NOT NULL,
|
||||
"isUnsaleable" BOOLEAN NOT NULL,
|
||||
"isUnbuyable" BOOLEAN NOT NULL,
|
||||
"isUngivable" BOOLEAN NOT NULL,
|
||||
"isLockedAfterEquip" BOOLEAN NOT NULL,
|
||||
|
||||
CONSTRAINT "ItemProp_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Item" ADD CONSTRAINT "Item_itemId_fkey" FOREIGN KEY ("itemId") REFERENCES "Item"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
@ -0,0 +1,10 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "ItemPrefab" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"path" TEXT NOT NULL,
|
||||
"rcid" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "ItemPrefab_pkey" PRIMARY KEY ("id")
|
||||
);
|
@ -0,0 +1,10 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "ItemUsePrefab" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"path" TEXT NOT NULL,
|
||||
"rcid" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "ItemUsePrefab_pkey" PRIMARY KEY ("id")
|
||||
);
|
@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "ItemProp" ADD COLUMN "conflictingItems" TEXT[],
|
||||
ADD COLUMN "unlootableFromSide" TEXT[];
|
@ -0,0 +1,18 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `itemPrefabId` to the `ItemProp` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `itemProp` to the `ItemProp` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `itemUsePrefabId` to the `ItemProp` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "ItemProp" ADD COLUMN "itemPrefabId" INTEGER NOT NULL,
|
||||
ADD COLUMN "itemProp" TEXT NOT NULL,
|
||||
ADD COLUMN "itemUsePrefabId" INTEGER NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ItemProp" ADD CONSTRAINT "ItemProp_itemPrefabId_fkey" FOREIGN KEY ("itemPrefabId") REFERENCES "ItemPrefab"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ItemProp" ADD CONSTRAINT "ItemProp_itemUsePrefabId_fkey" FOREIGN KEY ("itemUsePrefabId") REFERENCES "ItemUsePrefab"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
11
db/db/migrations/20220409024413_add_item_prop/migration.sql
Normal file
11
db/db/migrations/20220409024413_add_item_prop/migration.sql
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `itemPropId` to the `Item` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Item" ADD COLUMN "itemPropId" INTEGER NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Item" ADD CONSTRAINT "Item_itemPropId_fkey" FOREIGN KEY ("itemPropId") REFERENCES "ItemProp"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `internalId` to the `Item` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Item" ADD COLUMN "internalId" TEXT NOT NULL;
|
@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[internalId]` on the table `Item` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Item_internalId_key" ON "Item"("internalId");
|
3
db/db/migrations/migration_lock.toml
Normal file
3
db/db/migrations/migration_lock.toml
Normal file
@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
79
db/db/schema.prisma
Normal file
79
db/db/schema.prisma
Normal file
@ -0,0 +1,79 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
model Item {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
name String
|
||||
type String
|
||||
|
||||
parentItem Item? @relation("ItemToItem", fields: [parent], references: [internalId], onDelete: NoAction, onUpdate: NoAction)
|
||||
Item Item[] @relation("ItemToItem")
|
||||
parent String
|
||||
internalId String @unique
|
||||
|
||||
props ItemProp @relation(fields: [itemPropId], references: [id])
|
||||
itemPropId Int
|
||||
proto String?
|
||||
}
|
||||
|
||||
model ItemProp {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
Name String
|
||||
ShortName String
|
||||
Description String
|
||||
Weight Int
|
||||
Backgroundcolor String
|
||||
Width Int
|
||||
Height Int
|
||||
StackMaxSize Int
|
||||
ItemSound String
|
||||
StackObjectsCount Int
|
||||
NotShownInSlot Boolean
|
||||
ExaminedByDefault Boolean
|
||||
ExamineTime Int
|
||||
IsUndiscardable Boolean
|
||||
IsUnsaleable Boolean
|
||||
IsUnbuyable Boolean
|
||||
IsUngivable Boolean
|
||||
IsLockedAfterEquip Boolean
|
||||
ConflictingItems String[]
|
||||
UnlootableFromSide String[]
|
||||
Prefab ItemPrefab @relation(fields: [ItemPrefabId], references: [id])
|
||||
UsePrefab ItemUsePrefab @relation(fields: [ItemUsePrefabId], references: [id])
|
||||
ItemPrefabId Int
|
||||
ItemUsePrefabId Int
|
||||
Item Item[]
|
||||
}
|
||||
|
||||
model ItemPrefab {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
path String
|
||||
rcid String
|
||||
ItemProp ItemProp[]
|
||||
}
|
||||
|
||||
model ItemUsePrefab {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
path String
|
||||
rcid String
|
||||
ItemProp ItemProp[]
|
||||
}
|
15
db/db/seeds.ts
Normal file
15
db/db/seeds.ts
Normal file
@ -0,0 +1,15 @@
|
||||
// import db from "./index"
|
||||
|
||||
/*
|
||||
* This seed function is executed when you run `blitz db seed`.
|
||||
*
|
||||
* Probably you want to use a library like https://chancejs.com
|
||||
* to easily generate realistic data.
|
||||
*/
|
||||
const seed = async () => {
|
||||
// for (let i = 0; i < 5; i++) {
|
||||
// await db.project.create({ data: { name: "Project " + i } })
|
||||
// }
|
||||
}
|
||||
|
||||
export default seed
|
7
db/jest.config.ts
Normal file
7
db/jest.config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { Config } from "@jest/types"
|
||||
|
||||
const config: Config.InitialOptions = {
|
||||
preset: "blitz",
|
||||
}
|
||||
|
||||
export default config
|
58
db/package.json
Normal file
58
db/package.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "db",
|
||||
"version": "1.0.0",
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
"dev": "concurrently --raw \"blitz dev\" 'quirrel'",
|
||||
"build": "blitz build",
|
||||
"start": "blitz start",
|
||||
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"studio": "blitz prisma studio",
|
||||
"blitz": "blitz"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 100
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,tsx}": [
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"prisma": {
|
||||
"schema": "db/schema.prisma"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "11.x",
|
||||
"@emotion/styled": "11.x",
|
||||
"@mui/icons-material": "5.2.4",
|
||||
"@mui/material": "5.x",
|
||||
"@mui/styles": "5.x",
|
||||
"@prisma/client": "3.12.0",
|
||||
"blitz": "0.44.4",
|
||||
"final-form": "4.20.6",
|
||||
"next-themes": "0.0.15",
|
||||
"quirrel": "1.x",
|
||||
"react": "18.0.0-beta-149b420f6-20211119",
|
||||
"react-dom": "18.0.0-alpha-5ca4b0433-20211020",
|
||||
"react-final-form": "6.5.9",
|
||||
"react-json-view": "1.21.3",
|
||||
"zustand": "3.6.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "17.0.37",
|
||||
"concurrently": "6.x",
|
||||
"eslint": "7.32.0",
|
||||
"lint-staged": "11.3.0-beta.2",
|
||||
"prettier": "2.5.1",
|
||||
"pretty-quick": "3.1.2",
|
||||
"prisma": "3.12.0",
|
||||
"typescript": "~4.5"
|
||||
},
|
||||
"private": true
|
||||
}
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
db/public/logo.png
Normal file
BIN
db/public/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
4
db/test/setup.ts
Normal file
4
db/test/setup.ts
Normal file
@ -0,0 +1,4 @@
|
||||
// This is the jest 'setupFilesAfterEnv' setup file
|
||||
// It's a good place to set globals, add global before/after hooks, etc
|
||||
|
||||
export {} // so TS doesn't complain
|
105
db/test/utils.tsx
Normal file
105
db/test/utils.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
import { RouterContext, BlitzRouter, BlitzProvider } from "blitz"
|
||||
import { render as defaultRender } from "@testing-library/react"
|
||||
import { renderHook as defaultRenderHook } from "@testing-library/react-hooks"
|
||||
|
||||
export * from "@testing-library/react"
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// This file customizes the render() and renderHook() test functions provided
|
||||
// by React testing library. It adds a router context wrapper with a mocked router.
|
||||
//
|
||||
// You should always import `render` and `renderHook` from this file
|
||||
//
|
||||
// This is the place to add any other context providers you need while testing.
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
// --------------------------------------------------
|
||||
// render()
|
||||
// --------------------------------------------------
|
||||
// Override the default test render with our own
|
||||
//
|
||||
// You can override the router mock like this:
|
||||
//
|
||||
// const { baseElement } = render(<MyComponent />, {
|
||||
// router: { pathname: '/my-custom-pathname' },
|
||||
// });
|
||||
// --------------------------------------------------
|
||||
export function render(
|
||||
ui: RenderUI,
|
||||
{ wrapper, router, dehydratedState, ...options }: RenderOptions = {}
|
||||
) {
|
||||
if (!wrapper) {
|
||||
// Add a default context wrapper if one isn't supplied from the test
|
||||
wrapper = ({ children }) => (
|
||||
<BlitzProvider dehydratedState={dehydratedState}>
|
||||
<RouterContext.Provider value={{ ...mockRouter, ...router }}>
|
||||
{children}
|
||||
</RouterContext.Provider>
|
||||
</BlitzProvider>
|
||||
)
|
||||
}
|
||||
return defaultRender(ui, { wrapper, ...options })
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// renderHook()
|
||||
// --------------------------------------------------
|
||||
// Override the default test renderHook with our own
|
||||
//
|
||||
// You can override the router mock like this:
|
||||
//
|
||||
// const result = renderHook(() => myHook(), {
|
||||
// router: { pathname: '/my-custom-pathname' },
|
||||
// });
|
||||
// --------------------------------------------------
|
||||
export function renderHook(
|
||||
hook: RenderHook,
|
||||
{ wrapper, router, dehydratedState, ...options }: RenderHookOptions = {}
|
||||
) {
|
||||
if (!wrapper) {
|
||||
// Add a default context wrapper if one isn't supplied from the test
|
||||
wrapper = ({ children }) => (
|
||||
<BlitzProvider dehydratedState={dehydratedState}>
|
||||
<RouterContext.Provider value={{ ...mockRouter, ...router }}>
|
||||
{children}
|
||||
</RouterContext.Provider>
|
||||
</BlitzProvider>
|
||||
)
|
||||
}
|
||||
return defaultRenderHook(hook, { wrapper, ...options })
|
||||
}
|
||||
|
||||
export const mockRouter: BlitzRouter = {
|
||||
basePath: "",
|
||||
pathname: "/",
|
||||
route: "/",
|
||||
asPath: "/",
|
||||
params: {},
|
||||
query: {},
|
||||
isReady: true,
|
||||
isLocaleDomain: false,
|
||||
isPreview: false,
|
||||
push: jest.fn(),
|
||||
replace: jest.fn(),
|
||||
reload: jest.fn(),
|
||||
back: jest.fn(),
|
||||
prefetch: jest.fn(),
|
||||
beforePopState: jest.fn(),
|
||||
events: {
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
emit: jest.fn(),
|
||||
},
|
||||
isFallback: false,
|
||||
}
|
||||
|
||||
type DefaultParams = Parameters<typeof defaultRender>
|
||||
type RenderUI = DefaultParams[0]
|
||||
type RenderOptions = DefaultParams[1] & { router?: Partial<BlitzRouter>; dehydratedState?: unknown }
|
||||
|
||||
type DefaultHookParams = Parameters<typeof defaultRenderHook>
|
||||
type RenderHook = DefaultHookParams[0]
|
||||
type RenderHookOptions = DefaultHookParams[1] & {
|
||||
router?: Partial<BlitzRouter>
|
||||
dehydratedState?: unknown
|
||||
}
|
24
db/tsconfig.json
Normal file
24
db/tsconfig.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"baseUrl": "./",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": ".tsbuildinfo",
|
||||
},
|
||||
"exclude": ["node_modules", "**/*.e2e.ts", "cypress"],
|
||||
"include": ["blitz-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|
5
db/types.ts
Normal file
5
db/types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { DefaultCtx } from "blitz"
|
||||
|
||||
declare module "blitz" {
|
||||
export interface Ctx extends DefaultCtx {}
|
||||
}
|
11431
db/yarn.lock
Normal file
11431
db/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user