Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e829b6ff5 | |||
| 6cda6fee45 | |||
| 0157a63201 | |||
| 1ee4043c0e | |||
| 2fd088019e | |||
| 2c44abc320 |
@@ -2,9 +2,14 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React + TS</title>
|
<title>NWaifu Tier Maker</title>
|
||||||
|
<script
|
||||||
|
defer
|
||||||
|
src="https://stats.nwaifu.su/script.js"
|
||||||
|
data-website-id="2167decc-21d6-4f30-bdf3-f00da4b74ee9"
|
||||||
|
data-domains="tiermaker.nwaifu.su"
|
||||||
|
></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "nwtierlist",
|
"name": "nwtierlist",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@mui/icons-material": "^6.3.1",
|
"@mui/icons-material": "^6.3.1",
|
||||||
"@mui/material": "^6.3.1",
|
"@mui/material": "^6.3.1",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.3"
|
||||||
|
|||||||
97
src/App.tsx
97
src/App.tsx
@@ -1,5 +1,6 @@
|
|||||||
import { DndContext, DragEndEvent } from '@dnd-kit/core';
|
import { DndContext, DragEndEvent } from '@dnd-kit/core';
|
||||||
import { GitHub } from '@mui/icons-material';
|
import { GitHub } from '@mui/icons-material';
|
||||||
|
import html2canvas from 'html2canvas';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
import './App.scss';
|
import './App.scss';
|
||||||
@@ -15,6 +16,8 @@ const App = () => {
|
|||||||
useShallow(state => [state.images, state.addTierImage, state.tierLevels, state.editTierImage]),
|
useShallow(state => [state.images, state.addTierImage, state.tierLevels, state.editTierImage]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const tierListRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const uploadBtn = useRef<HTMLInputElement>(null);
|
const uploadBtn = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const clickUpload = () => {
|
const clickUpload = () => {
|
||||||
@@ -35,13 +38,32 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDownloadImage = async () => {
|
||||||
|
if (!tierListRef.current) return;
|
||||||
|
const el = tierListRef.current;
|
||||||
|
const canvas = await html2canvas(el);
|
||||||
|
|
||||||
|
const data = canvas.toDataURL('image/png', 1.0);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
|
||||||
|
if (typeof link.download === 'string') {
|
||||||
|
link.href = data;
|
||||||
|
link.download = 'tierlist.png';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
} else {
|
||||||
|
window.open(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='bg-[#2d3436] w-full h-full pb-5'>
|
<div className='bg-[#2d3436] w-full h-full pb-5'>
|
||||||
<div className='bg-[#2d3436] w-full h-12 flex flex-row justify-center items-center text-[#dfe6e9] text-2xl border-b-2'>
|
<div className='bg-[#2d3436] w-full h-12 flex flex-row justify-center items-center text-[#dfe6e9] text-2xl border-b-2'>
|
||||||
NwTierList
|
NwTierList
|
||||||
</div>
|
</div>
|
||||||
<DndContext onDragEnd={handleDragEnd}>
|
<DndContext onDragEnd={handleDragEnd}>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full' ref={tierListRef}>
|
||||||
{tierLevels.map(tier_level => (
|
{tierLevels.map(tier_level => (
|
||||||
<Tier
|
<Tier
|
||||||
color={tier_level.color}
|
color={tier_level.color}
|
||||||
@@ -61,42 +83,51 @@ const App = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div
|
|
||||||
className='border border-[#dfe6e9] w-32 text-center cursor-pointer text-[#dfe6e9] hover:text-[#2d3436] hover:bg-[#dfe6e9]
|
|
||||||
hover:rounded-xl hover:scale-110 active:text-[#2d3436] active:bg-[#dfe6e9] active:rounded-xl active:scale-110 rounded-lg mt-5 ms-auto me-auto
|
|
||||||
transition-all duration-300 ease-in-out'
|
|
||||||
onClick={clickUpload}
|
|
||||||
>
|
|
||||||
<span>Add photo</span>
|
|
||||||
<input
|
|
||||||
type='file'
|
|
||||||
name='imageUpload'
|
|
||||||
accept='.jpeg'
|
|
||||||
ref={uploadBtn}
|
|
||||||
multiple
|
|
||||||
style={{ display: 'none' }}
|
|
||||||
onChange={event => {
|
|
||||||
console.log(event.target.files);
|
|
||||||
if (event.target.files) {
|
|
||||||
const upload_images: tierImage[] = [];
|
|
||||||
for (let i = 0; i < event.target.files.length; i++) {
|
|
||||||
const url = URL.createObjectURL(event.target.files[i]);
|
|
||||||
upload_images.push({
|
|
||||||
url: url,
|
|
||||||
name: event.target.files[i].name.replace('.jpeg', ''),
|
|
||||||
category: '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
handleAdd(upload_images);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<TierModal />
|
<TierModal />
|
||||||
<EditTierModal />
|
<EditTierModal />
|
||||||
</DndContext>
|
</DndContext>
|
||||||
|
<div
|
||||||
|
className='border border-[#dfe6e9] w-32 text-center cursor-pointer text-[#dfe6e9] hover:text-[#2d3436] hover:bg-[#dfe6e9]
|
||||||
|
hover:rounded-xl hover:scale-110 active:text-[#2d3436] active:bg-[#dfe6e9] active:rounded-xl active:scale-110 rounded-lg mt-5 ms-auto me-auto
|
||||||
|
transition-all duration-300 ease-in-out'
|
||||||
|
onClick={clickUpload}
|
||||||
|
>
|
||||||
|
<span>Add photo</span>
|
||||||
|
<input
|
||||||
|
type='file'
|
||||||
|
name='imageUpload'
|
||||||
|
accept='.jpeg'
|
||||||
|
ref={uploadBtn}
|
||||||
|
multiple
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
onChange={event => {
|
||||||
|
console.log(event.target.files);
|
||||||
|
if (event.target.files) {
|
||||||
|
const upload_images: tierImage[] = [];
|
||||||
|
for (let i = 0; i < event.target.files.length; i++) {
|
||||||
|
const url = URL.createObjectURL(event.target.files[i]);
|
||||||
|
upload_images.push({
|
||||||
|
url: url,
|
||||||
|
name: event.target.files[i].name.replace('.jpeg', ''),
|
||||||
|
category: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handleAdd(upload_images);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='border border-[#dfe6e9] w-32 text-center cursor-pointer text-[#dfe6e9] hover:text-[#2d3436] hover:bg-[#dfe6e9]
|
||||||
|
hover:rounded-xl hover:scale-110 active:text-[#2d3436] active:bg-[#dfe6e9] active:rounded-xl active:scale-110 rounded-lg mt-5 ms-auto me-auto
|
||||||
|
transition-all duration-300 ease-in-out'
|
||||||
|
onClick={handleDownloadImage}
|
||||||
|
>
|
||||||
|
<span>Export as PNG</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='w-full text-[#dfe6e9] flex flex-row items-center justify-center md:justify-end mt-5 gap-1 md:pe-5'>
|
<div className='w-full text-[#dfe6e9] flex flex-row items-center justify-center md:justify-between mt-5 gap-1 md:pe-5 md:ps-5'>
|
||||||
|
<span className='text-[#dfe6e9]'>Version 0.0.3</span>
|
||||||
<a href='https://git.nwaifu.su/sergey/NwTierList' target='_blank'>
|
<a href='https://git.nwaifu.su/sergey/NwTierList' target='_blank'>
|
||||||
Source Code <GitHub />
|
Source Code <GitHub />
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
import useStore from '../store';
|
import useStore from '../store';
|
||||||
|
|
||||||
export const EditTierModal = () => {
|
export const EditTierModal = () => {
|
||||||
const [tierLevel, setTierLevel, editTierLevel] = useStore(
|
const [tierLevel, setTierLevel, editTierLevel] = useStore(
|
||||||
useShallow(state => [state.editingTierLevel, state.setEditingTierLevel, state.editTierLevelName]),
|
useShallow(state => [state.editingTierLevel, state.setEditingTierLevel, state.editTierLevelName]),
|
||||||
);
|
);
|
||||||
const [newName, setNewName] = useState('');
|
const [newName, setNewName] = useState('');
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tierLevel) setNewName(tierLevel.name);
|
if (tierLevel) {
|
||||||
|
setNewName(tierLevel.name);
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
} else document.body.style.overflow = 'auto';
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = 'auto';
|
||||||
|
};
|
||||||
}, [tierLevel]);
|
}, [tierLevel]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -20,7 +25,7 @@ export const EditTierModal = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] flex w-2/5 h-1/4 bg-[#2d3436] ps-8 pe-8 pt-8 pb-8 z-50 rounded-3xl flex-row gap-4 justify-center items-center text-white border-white border-2`}
|
className={`absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] flex md:w-2/5 w-5/6 h-1/4 bg-[#2d3436] ps-8 pe-8 pt-8 pb-8 z-50 rounded-3xl flex-row gap-4 justify-center items-center text-white border-white border-2`}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
.tier_name:hover {
|
.tier_name:hover {
|
||||||
button {
|
@media(min-width: 768px) {
|
||||||
opacity: 1;
|
button {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ export const Tier = ({ color, name, textColor, id }: TierProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className='absolute right-[5%] top-[5%] opacity-0 transition duration-250 ease-in-out'
|
className='absolute right-[5%] top-[5%] md:opacity-0 transition duration-250 ease-in-out'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditingTierLevel({ name, color, textColor, id });
|
setEditingTierLevel({ name, color, textColor, id });
|
||||||
}}
|
}}
|
||||||
@@ -58,7 +58,7 @@ export const Tier = ({ color, name, textColor, id }: TierProps) => {
|
|||||||
</button>
|
</button>
|
||||||
<p>{name}</p>
|
<p>{name}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='image-container flex flex-row flex-wrap justify-between w-full h-auto'>
|
<div className='image-container flex flex-row flex-wrap justify-start w-full h-auto gap-1'>
|
||||||
{tierImages
|
{tierImages
|
||||||
.filter(image => image.category === name)
|
.filter(image => image.category === name)
|
||||||
.map((image, index) => (
|
.map((image, index) => (
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
import useStore from '../store';
|
import useStore from '../store';
|
||||||
import { TierModalCategory } from './TierModalCategory';
|
import { TierModalCategory } from './TierModalCategory';
|
||||||
@@ -5,11 +6,19 @@ import { TierModalCategory } from './TierModalCategory';
|
|||||||
export const TierModal = () => {
|
export const TierModal = () => {
|
||||||
const [modalOpen, tierLevels] = useStore(useShallow(state => [state.tierLevelsModalOpen, state.tierLevels]));
|
const [modalOpen, tierLevels] = useStore(useShallow(state => [state.tierLevelsModalOpen, state.tierLevels]));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (modalOpen) document.body.style.overflow = 'hidden';
|
||||||
|
else document.body.style.overflow = 'auto';
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = 'auto';
|
||||||
|
};
|
||||||
|
}, [modalOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`fixed top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] ${
|
className={`fixed top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] ${
|
||||||
modalOpen ? 'flex' : 'hidden'
|
modalOpen ? 'flex' : 'hidden'
|
||||||
} min-w-[300px] md:w-[70vw] md:h-1/4 h-[90vh] bg-[#2d3436] ps-8 pe-8 pt-8 pb-8 z-50 rounded-3xl flex-col md:flex-row gap-4 justify-center items-center text-white border-white border-2`}
|
} min-w-[300px] md:w-[70vw] md:h-1/4 h-[90vh] bg-[#1e2324] ps-8 pe-8 pt-8 pb-8 z-50 rounded-3xl flex-col md:flex-row gap-4 justify-center items-center text-white border-white border-2`}
|
||||||
>
|
>
|
||||||
{tierLevels.map(tier_level => (
|
{tierLevels.map(tier_level => (
|
||||||
<TierModalCategory name={tier_level.name} color={tier_level.color} textColor={tier_level.textColor} />
|
<TierModalCategory name={tier_level.name} color={tier_level.color} textColor={tier_level.textColor} />
|
||||||
|
|||||||
Reference in New Issue
Block a user