Minigame Editor Demo 16/10/2024
24
minigame-editor/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
8
minigame-editor/README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# React + Vite
|
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||||
|
|
||||||
|
Currently, two official plugins are available:
|
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||||
38
minigame-editor/eslint.config.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import react from 'eslint-plugin-react'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{ ignores: ['dist'] },
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,jsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
ecmaFeatures: { jsx: true },
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
settings: { react: { version: '18.3' } },
|
||||||
|
plugins: {
|
||||||
|
react,
|
||||||
|
'react-hooks': reactHooks,
|
||||||
|
'react-refresh': reactRefresh,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...js.configs.recommended.rules,
|
||||||
|
...react.configs.recommended.rules,
|
||||||
|
...react.configs['jsx-runtime'].rules,
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
'react/jsx-no-target-blank': 'off',
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
21
minigame-editor/index.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@300;400;500;600;700&display=swap">
|
||||||
|
<title>Vite + React</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
5952
minigame-editor/package-lock.json
generated
Normal file
36
minigame-editor/package.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "vite-project",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@reduxjs/toolkit": "^2.2.8",
|
||||||
|
"@toast-ui/react-image-editor": "^3.15.2",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-color": "^2.19.3",
|
||||||
|
"react-contenteditable": "^3.3.7",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-images-uploading": "^3.1.7",
|
||||||
|
"react-redux": "^9.1.2",
|
||||||
|
"react-tooltip": "^5.28.0",
|
||||||
|
"styled-components": "^6.1.13"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.11.1",
|
||||||
|
"@types/react": "^18.3.10",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||||
|
"eslint": "^9.11.1",
|
||||||
|
"eslint-plugin-react": "^7.37.0",
|
||||||
|
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.12",
|
||||||
|
"globals": "^15.9.0",
|
||||||
|
"vite": "^5.4.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
minigame-editor/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
369
minigame-editor/src/App.css
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
.container {
|
||||||
|
max-width: 410px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 10px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .circle {
|
||||||
|
position: relative;
|
||||||
|
margin: 48px 0 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .circle-bg {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .circle-item {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .circle-item.rw {
|
||||||
|
max-width: 82%;
|
||||||
|
top: 49.2%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .circle-item.ar {
|
||||||
|
top: -25px;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
max-width: 51px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .circle-item.btn {
|
||||||
|
max-width: 132px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .noffy {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .noffy-number {
|
||||||
|
color: #ffb71d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .noffy-remain {
|
||||||
|
display: inline-block;
|
||||||
|
background: url(src/assets/images/hnc-game-2-btn-1.png) no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
width: 223px;
|
||||||
|
height: 38px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 34px;
|
||||||
|
text-align: center;
|
||||||
|
color: #fff;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .noffy-remain .noffy-number {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .noffy-player {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
max-width: 411px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 34px;
|
||||||
|
padding: 0 15px 0 30px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: #4613d3;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px dashed #fff;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .box {
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
padding: 38px 27px 22px 34px;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .result {
|
||||||
|
background-image: url(src/assets/images/hnc-game-2-box-1.png);
|
||||||
|
min-height: 287px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .result-heading {
|
||||||
|
background: linear-gradient(90deg, #ff74ec 26%, #a43dff 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .result-list {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .result-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
min-height: 57px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 21px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
padding: 0 10px;
|
||||||
|
color: #000;
|
||||||
|
background: #fff;
|
||||||
|
clip-path: polygon(50% 0%, 100% 0, 100% 85%, 97% 100%, 0 100%, 0 15%, 3% 0);
|
||||||
|
border-radius: 13px 3px 13px 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .result-no {
|
||||||
|
max-width: max-content;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 39px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 5px;
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(90deg, #ff74ec 26%, #a43dff 100%);
|
||||||
|
clip-path: polygon(50% 0%, 100% 0, 100% 80%, 85% 100%, 0 100%, 0 20%, 15% 0);
|
||||||
|
border-radius: 10px 3px 10px 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward {
|
||||||
|
background-image: url(src/assets/images/hnc-game-2-box-2.png);
|
||||||
|
min-height: 633px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-item {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
text-align: center;
|
||||||
|
color: #002a88;
|
||||||
|
background: #fff;
|
||||||
|
clip-path: polygon(50% 0%, 100% 0, 100% 94%, 94% 100%, 0 100%, 0 6%, 6% 0);
|
||||||
|
border-radius: 13px 3px 13px 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-top {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
justify-content: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 8px 8px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-img {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 111px;
|
||||||
|
height: 111px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-img::after {
|
||||||
|
content: "";
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -25px;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-no {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-name {
|
||||||
|
padding: 6px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-price {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 9px 8px 8px;
|
||||||
|
border-top: 1px dashed #909090;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-bold {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ff0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-item:first-child {
|
||||||
|
grid-column: span 2;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 16px;
|
||||||
|
text-align: left;
|
||||||
|
background: #ffd737;
|
||||||
|
clip-path: polygon(50% 0%, 100% 0, 100% 91%, 96% 100%, 0 100%, 0 9%, 4% 0);
|
||||||
|
border-radius: 17px 3px 17px 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-item:first-child .reward-top {
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 8px 6px 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-item:first-child .reward-no {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-item:first-child .reward-img::after {
|
||||||
|
display: block;
|
||||||
|
background-image: url(src/assets/images/hnc-game-2-icon-top-1.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-item:nth-child(2) .reward-img::after {
|
||||||
|
display: block;
|
||||||
|
background-image: url(src/assets/images/hnc-game-2-icon-top-2.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .reward-item:nth-child(3) .reward-img::after {
|
||||||
|
display: block;
|
||||||
|
background-image: url(src/assets/images/hnc-game-2-icon-top-3.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .policy {
|
||||||
|
padding-top: 16px;
|
||||||
|
background-image: url(src/assets/images/hnc-game-2-box-3.png);
|
||||||
|
min-height: 394px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .popup-heading {
|
||||||
|
background: linear-gradient(90deg, #ff74ec 26%, #a43dff 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .hotline {
|
||||||
|
text-align: center;
|
||||||
|
padding: 8px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .hotline .tel {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 21px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .hotline .tel b {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 27px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .popup {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .popup-bg {
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .popup-main {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
max-width: 408px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .popup-close {
|
||||||
|
position: absolute;
|
||||||
|
top: -11px;
|
||||||
|
right: -11px;
|
||||||
|
width: 44px;
|
||||||
|
height: 49px;
|
||||||
|
background-image: url(src/assets/images/hnc-game-2-btn-close-1.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .popup-box {
|
||||||
|
background-image: url(src/assets/images/hnc-game-2-box-4.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
min-height: 298px;
|
||||||
|
padding: 21px 27px 18px 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .popup-box-2 {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .popup-result {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .popup-reward {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .popup-form {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .form-input {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 14px;
|
||||||
|
border: 1px solid #dcdcdc;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 11px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .form-btn {
|
||||||
|
width: 175px;
|
||||||
|
height: 49px;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 10px;
|
||||||
|
color: #fff;
|
||||||
|
background-image: url(src/assets/images/hnc-game-2-btn-2.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
29
minigame-editor/src/App.jsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import './App.css'
|
||||||
|
|
||||||
|
import Template from './Template'
|
||||||
|
import Editor from './Editor'
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
`
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [target, setTarget] = useState("main");
|
||||||
|
|
||||||
|
const database = useSelector((state) => state.database.value);
|
||||||
|
const props = database[target];
|
||||||
|
|
||||||
|
// console.log(database);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Template setTarget={setTarget} props={database} />
|
||||||
|
<Editor props={props} />
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
BIN
minigame-editor/src/assets/images/hnc-game-2-bg-1.png
Normal file
|
After Width: | Height: | Size: 288 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-box-1.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-box-2.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-box-3.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-box-4.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-btn-1.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-btn-2.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-btn-4.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-btn-close-1.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-cricle-1.png
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-cricle-2.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-cricle-3.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-cricle-4.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-icon-phone-1.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-icon-top-1.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-icon-top-2.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-icon-top-3.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-logo-1.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-success-1.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-title-1.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
minigame-editor/src/assets/images/hnc-game-2-voucher-1.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
1
minigame-editor/src/assets/react.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
19
minigame-editor/src/components/Box.jsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
const StyledBox = styled.div`
|
||||||
|
${props => props.$sty}
|
||||||
|
`
|
||||||
|
|
||||||
|
function Box({ props }) {
|
||||||
|
const cla = props.className;
|
||||||
|
const id = props.id;
|
||||||
|
const style = props.styles;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledBox id={id} className={"ld-item " + cla} $sty={style} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Box
|
||||||
51
minigame-editor/src/components/Editable.jsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { useRef } from 'react'
|
||||||
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
import { updateDatabase } from '../database/database'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import ContentEditable from 'react-contenteditable'
|
||||||
|
|
||||||
|
const StyledContentEditable = styled(ContentEditable)`
|
||||||
|
${props => props.$sty}
|
||||||
|
`
|
||||||
|
|
||||||
|
function Editable({ props }) {
|
||||||
|
const database = useSelector((state) => state.database.value);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const content = useRef(props.content);
|
||||||
|
|
||||||
|
const handleChange = evt => {
|
||||||
|
content.current = evt.target.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
const newParams = { ...database[id], content: content.current };
|
||||||
|
const newDatabase = { ...database, [id]: newParams };
|
||||||
|
dispatch(updateDatabase(newDatabase));
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePaste = (evt) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
const copyText = (evt.originalEvent || evt).clipboardData.getData('text/plain');
|
||||||
|
document.execCommand("insertHTML", false, copyText);
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = props.styles;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledContentEditable
|
||||||
|
html={content.current}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onChange={handleChange}
|
||||||
|
onPaste={handlePaste}
|
||||||
|
tagName={props.tagName}
|
||||||
|
className={'ld-item ' + props.className}
|
||||||
|
id={props.id}
|
||||||
|
$sty={style}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Editable
|
||||||
20
minigame-editor/src/components/Image.jsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
const StyledImage = styled.img`
|
||||||
|
${props => props.$sty}
|
||||||
|
`
|
||||||
|
|
||||||
|
function Image({ props }) {
|
||||||
|
const attr = props.attributes;
|
||||||
|
const cla = props.className;
|
||||||
|
const id = props.id;
|
||||||
|
const style = props.styles;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledImage id={id} className={"ld-item " + cla} src={attr.src} alt={attr.alt} width={attr.width} height={attr.height} $sty={style} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Image
|
||||||
194
minigame-editor/src/database/database.json
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
{
|
||||||
|
"main": {
|
||||||
|
"id": "main",
|
||||||
|
"type": "main",
|
||||||
|
"tagName": "div",
|
||||||
|
"content": "",
|
||||||
|
"className": "main",
|
||||||
|
"attributes": {},
|
||||||
|
"styles": {
|
||||||
|
"background": "url(src/assets/images/hnc-game-2-bg-1.png)",
|
||||||
|
"backgroundRepeat": "no-repeat",
|
||||||
|
"backgroundSize": "cover"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"id": "logo",
|
||||||
|
"type": "image",
|
||||||
|
"tagName": "img",
|
||||||
|
"content": "",
|
||||||
|
"className": "logo",
|
||||||
|
"attributes": {
|
||||||
|
"src": "src/assets/images/hnc-game-2-logo-1.png",
|
||||||
|
"alt": "Hacom",
|
||||||
|
"width": "100",
|
||||||
|
"height": "38"
|
||||||
|
},
|
||||||
|
"styles": {
|
||||||
|
"display": "",
|
||||||
|
"width": "100px",
|
||||||
|
"height": "auto",
|
||||||
|
"padding": "8px 0 12px",
|
||||||
|
"margin": "0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"banner": {
|
||||||
|
"id": "banner",
|
||||||
|
"type": "image",
|
||||||
|
"tagName": "img",
|
||||||
|
"content": "",
|
||||||
|
"className": "banner",
|
||||||
|
"attributes": {
|
||||||
|
"src": "src/assets/images/hnc-game-2-title-1.png",
|
||||||
|
"alt": "Vòng quay may mắn",
|
||||||
|
"width": "289",
|
||||||
|
"height": "106"
|
||||||
|
},
|
||||||
|
"styles": {
|
||||||
|
"width": "289px",
|
||||||
|
"height": "auto",
|
||||||
|
"padding": "0",
|
||||||
|
"margin": "0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"id": "date",
|
||||||
|
"type": "text",
|
||||||
|
"tagName": "div",
|
||||||
|
"content": "01/09/2024 - 17/09/2024",
|
||||||
|
"className": "date",
|
||||||
|
"attributes": {},
|
||||||
|
"styles": {
|
||||||
|
"text-align": "center",
|
||||||
|
"font-size": "18px",
|
||||||
|
"font-weight": "600",
|
||||||
|
"line-height": "27px",
|
||||||
|
"color": "#fff"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"circle_background": {
|
||||||
|
"id": "circle_background",
|
||||||
|
"type": "image",
|
||||||
|
"tagName": "img",
|
||||||
|
"content": "",
|
||||||
|
"className": "circle-bg",
|
||||||
|
"attributes": {
|
||||||
|
"src": "src/assets/images/hnc-game-2-cricle-1.png"
|
||||||
|
},
|
||||||
|
"styles": {}
|
||||||
|
},
|
||||||
|
"circle_arrow": {
|
||||||
|
"id": "circle_arrow",
|
||||||
|
"type": "image",
|
||||||
|
"tagName": "img",
|
||||||
|
"content": "",
|
||||||
|
"className": "circle-item ar",
|
||||||
|
"attributes": {
|
||||||
|
"src": "src/assets/images/hnc-game-2-cricle-3.png"
|
||||||
|
},
|
||||||
|
"styles": {}
|
||||||
|
},
|
||||||
|
"circle_button": {
|
||||||
|
"id": "circle_button",
|
||||||
|
"type": "image",
|
||||||
|
"tagName": "img",
|
||||||
|
"content": "",
|
||||||
|
"className": "circle-bg",
|
||||||
|
"attributes": {
|
||||||
|
"src": "src/assets/images/hnc-game-2-cricle-4.png"
|
||||||
|
},
|
||||||
|
"styles": {}
|
||||||
|
},
|
||||||
|
"reward": {
|
||||||
|
"id": "reward",
|
||||||
|
"type": "reward",
|
||||||
|
"tagName": "div",
|
||||||
|
"content": "",
|
||||||
|
"className": "",
|
||||||
|
"attributes": {
|
||||||
|
"limit": "5"
|
||||||
|
},
|
||||||
|
"styles": {
|
||||||
|
"background": "url(src/assets/images/hnc-game-2-box-2.png)",
|
||||||
|
"background-size": "100% 100%",
|
||||||
|
"background-repeat": "no-repeat",
|
||||||
|
"padding": "38px 27px 22px 34px",
|
||||||
|
"margin": "16px 0",
|
||||||
|
"min-height": "633px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"reward_heading": {
|
||||||
|
"id": "reward_heading",
|
||||||
|
"type": "text",
|
||||||
|
"tagName": "h2",
|
||||||
|
"content": "Danh sách giải thưởng",
|
||||||
|
"className": "heading reward-heading",
|
||||||
|
"attributes": {},
|
||||||
|
"styles": {
|
||||||
|
"font-size": "20px",
|
||||||
|
"line-height": "30px",
|
||||||
|
"text-align": "center",
|
||||||
|
"text-transform": "uppercase",
|
||||||
|
"margin-bottom": "14px",
|
||||||
|
"color": "#73dff8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policy_heading": {
|
||||||
|
"id": "policy_heading",
|
||||||
|
"type": "text",
|
||||||
|
"tagName": "h2",
|
||||||
|
"content": "Thể lệ chương trình",
|
||||||
|
"className": "heading policy-heading",
|
||||||
|
"attributes": {},
|
||||||
|
"styles": {
|
||||||
|
"font-size": "20px",
|
||||||
|
"line-height": "30px",
|
||||||
|
"text-align": "center",
|
||||||
|
"text-transform": "uppercase",
|
||||||
|
"margin-bottom": "14px",
|
||||||
|
"background": "linear-gradient(90deg, #ff74ec 26%, #a43dff 100%)",
|
||||||
|
"-webkit-background-clip": "text",
|
||||||
|
"-webkit-text-fill-color": "transparent"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policy_content": {
|
||||||
|
"id": "policy_content",
|
||||||
|
"type": "text",
|
||||||
|
"tagName": "div",
|
||||||
|
"content": "1. Quý khách cần đăng nhập Thông tin chính xác để quay và nhận thưởng<br><br>2. Phiếu mua hàng chỉ có giá trị tại Hacom trong thời gian chương trình diễn ra và không thể chuyển nhượng<br><br>3. Phiếu mua hàng không quy đổi thành tiền mặt, không hoàn lại, chỉ sử dụng 01 lần<br><br>4. Chương trình không áp dụng đồng thời với ưu đãi XXX<br><br>5. Mọi khiếu nại liên quan đến chương tình sẽ được giải quyết theo quyết định của Hacom. Quyết định của Hacom là quyết định cuối cùng",
|
||||||
|
"className": "policy-content",
|
||||||
|
"attributes": {},
|
||||||
|
"styles": {
|
||||||
|
"font-size": "14px",
|
||||||
|
"line-height": "20px",
|
||||||
|
"text-align": "left",
|
||||||
|
"color": "#000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policy_hotline": {
|
||||||
|
"id": "policy_hotline",
|
||||||
|
"type": "text",
|
||||||
|
"tagName": "b",
|
||||||
|
"content": "1900 1903",
|
||||||
|
"className": "",
|
||||||
|
"attributes": {},
|
||||||
|
"styles": {}
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"id": "footer",
|
||||||
|
"type": "text",
|
||||||
|
"tagName": "div",
|
||||||
|
"content": "© 2024 Công ty Cổ phần đầu tư công nghệ HACOM<br>Trụ sở chính: Số 129+131 Lê Thanh Nghị, Phường Đồng Tâm, Quận Hai Bà Trưng, Thành phố Hà Nội<br>VPGD: Tầng 3 Tòa nhà LILAMA, số 124 Minh Khai, Phường Minh Khai, Quận Hai Bà Trưng, Thành phố Hà Nội<br>GPĐKKD số 0101161194 do Sở KHĐT Tp.Hà Nội cấp ngày 31/8/2001<br>Email: info@hacom.vn. Điện thoại: 1900 1903",
|
||||||
|
"className": "footer",
|
||||||
|
"attributes": {},
|
||||||
|
"styles": {
|
||||||
|
"font-size": "14px",
|
||||||
|
"font-weight": "300",
|
||||||
|
"line-height": "20px",
|
||||||
|
"text-align": "center",
|
||||||
|
"padding": "13px",
|
||||||
|
"color": "#fff",
|
||||||
|
"background": "#2d3075"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
minigame-editor/src/database/database.jsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import initialState from './database.json';
|
||||||
|
|
||||||
|
export const Database = createSlice({
|
||||||
|
name: 'database',
|
||||||
|
initialState: {
|
||||||
|
value: initialState
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
updateDatabase: (state, newState) => {
|
||||||
|
state.value = newState.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Action creators are generated for each case reducer function
|
||||||
|
export const { updateDatabase } = Database.actions
|
||||||
|
|
||||||
|
export default Database.reducer
|
||||||
12
minigame-editor/src/database/global.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"template-1": {
|
||||||
|
"id": "template-1",
|
||||||
|
"name": "template-1",
|
||||||
|
"order": ["header", "circle", "policy", "footer"]
|
||||||
|
},
|
||||||
|
"template-2": {
|
||||||
|
"id": "template-2",
|
||||||
|
"name": "template-2",
|
||||||
|
"order": ["header", "circle", "noffy", "reward", "policy", "footer"]
|
||||||
|
}
|
||||||
|
}
|
||||||
19
minigame-editor/src/database/global.jsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import initialState from './global.json';
|
||||||
|
|
||||||
|
export const Global = createSlice({
|
||||||
|
name: 'global',
|
||||||
|
initialState: {
|
||||||
|
value: initialState
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
updateGlobal: (state, newState) => {
|
||||||
|
state.value = newState.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Action creators are generated for each case reducer function
|
||||||
|
export const { updateGlobal } = Global.actions
|
||||||
|
|
||||||
|
export default Global.reducer
|
||||||
82
minigame-editor/src/database/images.json
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"image-1": {
|
||||||
|
"id": "image-1",
|
||||||
|
"name": "image-1.jpg",
|
||||||
|
"dataURL": "https://picsum.photos/seed/1/600",
|
||||||
|
"size": 102400,
|
||||||
|
"type": "image/jpeg",
|
||||||
|
"time": 1700833415517
|
||||||
|
},
|
||||||
|
"image-2": {
|
||||||
|
"id": "image-2",
|
||||||
|
"name": "image-2.jpg",
|
||||||
|
"dataURL": "https://picsum.photos/seed/2/600",
|
||||||
|
"size": 102400,
|
||||||
|
"type": "image/jpeg",
|
||||||
|
"time": 1700833415517
|
||||||
|
},
|
||||||
|
"image-3": {
|
||||||
|
"id": "image-3",
|
||||||
|
"name": "image-3.jpg",
|
||||||
|
"dataURL": "https://picsum.photos/seed/3/600",
|
||||||
|
"size": 102400,
|
||||||
|
"type": "image/jpeg",
|
||||||
|
"time": 1700833415517
|
||||||
|
},
|
||||||
|
"image-4": {
|
||||||
|
"id": "image-4",
|
||||||
|
"name": "image-4.jpg",
|
||||||
|
"dataURL": "https://picsum.photos/seed/4/600",
|
||||||
|
"size": 102400,
|
||||||
|
"type": "image/jpeg",
|
||||||
|
"time": 1700833415517
|
||||||
|
},
|
||||||
|
"image-5": {
|
||||||
|
"id": "image-5",
|
||||||
|
"name": "image-5.jpg",
|
||||||
|
"dataURL": "https://picsum.photos/seed/5/600",
|
||||||
|
"size": 102400,
|
||||||
|
"type": "image/jpeg",
|
||||||
|
"time": 1700833415517
|
||||||
|
},
|
||||||
|
"image-6": {
|
||||||
|
"id": "image-6",
|
||||||
|
"name": "image-6.jpg",
|
||||||
|
"dataURL": "https://picsum.photos/seed/6/600",
|
||||||
|
"size": 102400,
|
||||||
|
"type": "image/jpeg",
|
||||||
|
"time": 1700833415517
|
||||||
|
},
|
||||||
|
"image-7": {
|
||||||
|
"id": "image-7",
|
||||||
|
"name": "image-7.jpg",
|
||||||
|
"dataURL": "https://picsum.photos/seed/7/600",
|
||||||
|
"size": 102400,
|
||||||
|
"type": "image/jpeg",
|
||||||
|
"time": 1700833415517
|
||||||
|
},
|
||||||
|
"image-8": {
|
||||||
|
"id": "image-8",
|
||||||
|
"name": "image-8.jpg",
|
||||||
|
"dataURL": "https://picsum.photos/seed/8/600",
|
||||||
|
"size": 102400,
|
||||||
|
"type": "image/jpeg",
|
||||||
|
"time": 1700833415517
|
||||||
|
},
|
||||||
|
"image-9": {
|
||||||
|
"id": "image-9",
|
||||||
|
"name": "image-9.jpg",
|
||||||
|
"dataURL": "https://picsum.photos/seed/9/600",
|
||||||
|
"size": 102400,
|
||||||
|
"type": "image/jpeg",
|
||||||
|
"time": 1700833415517
|
||||||
|
},
|
||||||
|
"image-10": {
|
||||||
|
"id": "image-10",
|
||||||
|
"name": "image-10.jpg",
|
||||||
|
"dataURL": "https://picsum.photos/seed/10/600",
|
||||||
|
"size": 102400,
|
||||||
|
"type": "image/jpeg",
|
||||||
|
"time": 1700833415517
|
||||||
|
}
|
||||||
|
}
|
||||||
19
minigame-editor/src/database/images.jsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import initialImages from './images.json';
|
||||||
|
|
||||||
|
export const Images = createSlice({
|
||||||
|
name: 'images',
|
||||||
|
initialState: {
|
||||||
|
value: initialImages
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
updateImages: (state, newState) => {
|
||||||
|
state.value = newState.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Action creators are generated for each case reducer function
|
||||||
|
export const { updateImages } = Images.actions
|
||||||
|
|
||||||
|
export default Images.reducer
|
||||||
56
minigame-editor/src/database/properties.json
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"display": {
|
||||||
|
"id": "display",
|
||||||
|
"name": "Display",
|
||||||
|
"value": "inital",
|
||||||
|
"selects": ["inline", "block", "inline-block", "flex", "inline-flex"],
|
||||||
|
"units": [],
|
||||||
|
"options": ["switch"]
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"id": "width",
|
||||||
|
"name": "Width",
|
||||||
|
"values": "auto",
|
||||||
|
"selects": [
|
||||||
|
"auto",
|
||||||
|
"inherit",
|
||||||
|
"initial",
|
||||||
|
"max-content",
|
||||||
|
"min-content",
|
||||||
|
"fit-content"
|
||||||
|
],
|
||||||
|
"units": ["percent", "px", "em", "rem"],
|
||||||
|
"options": ["slider-range"]
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"id": "height",
|
||||||
|
"name": "Height",
|
||||||
|
"values": "auto",
|
||||||
|
"selects": [
|
||||||
|
"auto",
|
||||||
|
"inherit",
|
||||||
|
"initial",
|
||||||
|
"max-content",
|
||||||
|
"min-content",
|
||||||
|
"fit-content"
|
||||||
|
],
|
||||||
|
"units": ["percent", "px", "em", "rem"],
|
||||||
|
"options": ["slider-range"]
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"id": "color",
|
||||||
|
"name": "Color",
|
||||||
|
"values": "#ff0000",
|
||||||
|
"selects": [],
|
||||||
|
"units": ["hex"],
|
||||||
|
"options": ["color-pick"]
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"id": "background",
|
||||||
|
"name": "Background",
|
||||||
|
"values": "#ff0000",
|
||||||
|
"selects": ["color", "gradient", "image"],
|
||||||
|
"units": ["hex", "url"],
|
||||||
|
"options": ["color-pick", "upload"]
|
||||||
|
}
|
||||||
|
}
|
||||||
10
minigame-editor/src/database/store.jsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
import Database from './database';
|
||||||
|
import Images from './images';
|
||||||
|
|
||||||
|
export default configureStore({
|
||||||
|
reducer: {
|
||||||
|
database: Database,
|
||||||
|
images: Images
|
||||||
|
},
|
||||||
|
})
|
||||||
79
minigame-editor/src/editor/Attribute.jsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { useState, memo } from 'react'
|
||||||
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
import { updateDatabase } from '../database/database'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import ImageGallery from './ImageGallery'
|
||||||
|
|
||||||
|
const Heading = styled.h3`
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Item = styled.div`
|
||||||
|
padding: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Title = styled.h4`
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
function Attribute({ id, props }) {
|
||||||
|
const database = useSelector((state) => state.database.value);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const AttributeInner = memo(function AttributeInner({ props }) {
|
||||||
|
const key = props.key;
|
||||||
|
const type = database[id].type;
|
||||||
|
if (type === "image" && key === "width" || type === "image" && key === "height") return;
|
||||||
|
|
||||||
|
const [value, setValue] = useState(props.value);
|
||||||
|
|
||||||
|
const changeHandle = (evt) => {
|
||||||
|
setValue(evt.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blurHandle = () => {
|
||||||
|
const initalValue = database[id].attributes[key];
|
||||||
|
if (value === initalValue) return;
|
||||||
|
|
||||||
|
const newAttributes = { ...database[id].attributes, [key]: value };
|
||||||
|
const newParams = { ...database[id], attributes: newAttributes };
|
||||||
|
const newDatabase = { ...database, [id]: newParams };
|
||||||
|
dispatch(updateDatabase(newDatabase));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Item>
|
||||||
|
<Title>[ {props.key} ]</Title>
|
||||||
|
<input className='w-100' type='text' value={value} onChange={changeHandle} onBlur={blurHandle} />
|
||||||
|
<img src={props.value} alt="Preview" width={"100"} height={"auto"} />
|
||||||
|
{key === "src" && <ImageGallery id={id} />}
|
||||||
|
</Item>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
const attributeList = Object.keys(props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Heading>Attribute</Heading>
|
||||||
|
{
|
||||||
|
attributeList.map((key, index) => {
|
||||||
|
const attr = {
|
||||||
|
key: key,
|
||||||
|
value: props[key]
|
||||||
|
}
|
||||||
|
return <AttributeInner props={attr} key={index} />
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Attribute
|
||||||
102
minigame-editor/src/editor/ImageGallery/ImageEditor.jsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { React } from "react";
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { updateImages } from '../../database/images';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import ImageEditor from '@toast-ui/react-image-editor';
|
||||||
|
|
||||||
|
import 'tui-image-editor/dist/tui-image-editor.css';
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Block = styled.div`
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Button = styled.button`
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 12px;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 20px;
|
||||||
|
background: red;
|
||||||
|
color: #fff;
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 9;
|
||||||
|
|
||||||
|
&.save {
|
||||||
|
top: unset;
|
||||||
|
bottom: 12px;
|
||||||
|
background: green;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
function ImageEditors({ image, show, setShow }) {
|
||||||
|
if (image === null) return;
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const state = useSelector((state) => state.images.value);
|
||||||
|
|
||||||
|
const imageEditorsSave = () => {
|
||||||
|
const images = state;
|
||||||
|
const canvas = document.querySelector(".lower-canvas");
|
||||||
|
const dataURL = canvas.toDataURL(image.type);
|
||||||
|
|
||||||
|
const newImage = { ...image, 'dataURL': dataURL, 'time': Date.now() };
|
||||||
|
const newState = { ...images, [image.id]: newImage }
|
||||||
|
dispatch(updateImages(newState));
|
||||||
|
setShow(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageEditorsClose = () => {
|
||||||
|
setShow(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{show &&
|
||||||
|
<Container >
|
||||||
|
<Block>
|
||||||
|
<ImageEditor
|
||||||
|
includeUI={{
|
||||||
|
loadImage: {
|
||||||
|
path: image.dataURL,
|
||||||
|
name: 'Edited image',
|
||||||
|
},
|
||||||
|
menu: ['shape', 'filter', 'text', 'crop', 'flip', 'rotate', 'draw', 'icon', 'mask'],
|
||||||
|
uiSize: {
|
||||||
|
width: '1200px',
|
||||||
|
height: '600px',
|
||||||
|
},
|
||||||
|
menuBarPosition: 'right',
|
||||||
|
}}
|
||||||
|
cssMaxHeight={500}
|
||||||
|
cssMaxWidth={700}
|
||||||
|
selectionStyle={{
|
||||||
|
cornerSize: 20,
|
||||||
|
rotatingPointOffset: 70,
|
||||||
|
}}
|
||||||
|
usageStatistics={true}
|
||||||
|
/>
|
||||||
|
<Button type="button" onClick={imageEditorsClose}>X</Button>
|
||||||
|
<Button type="button" className="save" onClick={imageEditorsSave}>✓</Button>
|
||||||
|
</Block>
|
||||||
|
</Container>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImageEditors
|
||||||
231
minigame-editor/src/editor/ImageGallery/ImageUpload.jsx
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
import { React, useState } from "react";
|
||||||
|
import { Tooltip } from 'react-tooltip';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { updateImages } from '../../database/images';
|
||||||
|
import { updateDatabase } from '../../database/database';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import ImageUploading from 'react-images-uploading';
|
||||||
|
import ImageEditors from "./ImageEditor";
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 999;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Box = styled.div`
|
||||||
|
position: relative;
|
||||||
|
max-width: 1000px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Close = styled.button`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: red;
|
||||||
|
color: #fff;
|
||||||
|
border: 0;
|
||||||
|
z-index: 1;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Button = styled.button`
|
||||||
|
color: #fff;
|
||||||
|
pointer-events: ${(props) => props.$enable ? 'unset' : 'none'};
|
||||||
|
`;
|
||||||
|
|
||||||
|
function ImageUpload({ id, setVisible }) {
|
||||||
|
const state = useSelector((state) => state.images.value);
|
||||||
|
const database = useSelector((state) => state.database.value);
|
||||||
|
|
||||||
|
const images = Object.entries(state).map(img => img[1]);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
const [editImage, setEditImage] = useState(null);
|
||||||
|
const [choseImage, setChoseImage] = useState(false);
|
||||||
|
const maxNumber = 20;
|
||||||
|
|
||||||
|
const hideModal = () => {
|
||||||
|
setVisible(false);
|
||||||
|
setChoseImage(false);
|
||||||
|
document.querySelector('body').style.overflow = 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = (imageList, addUpdateIndex) => {
|
||||||
|
const newImages = imageList.map(img => {
|
||||||
|
const id = img.id ? img.id : 'image-' + img.file.lastModified;
|
||||||
|
|
||||||
|
const image = img.id ? img : {
|
||||||
|
'id': id,
|
||||||
|
'name': img.file.name,
|
||||||
|
'dataURL': img.dataURL,
|
||||||
|
'size': img.file.size,
|
||||||
|
'type': img.file.type,
|
||||||
|
'time': img.file.lastModified
|
||||||
|
};
|
||||||
|
return [id, image];
|
||||||
|
})
|
||||||
|
|
||||||
|
const newImageList = Object.fromEntries(newImages);
|
||||||
|
dispatch(updateImages(newImageList));
|
||||||
|
|
||||||
|
const imagesTotal = Object.keys(newImageList).length;
|
||||||
|
if (addUpdateIndex && addUpdateIndex[0] + 1 === imagesTotal) {
|
||||||
|
setTimeout(function () {
|
||||||
|
const target = document.querySelector('.ld-image-upload-item:last-child');
|
||||||
|
target.scrollIntoView({ behavior: "smooth", block: "end", inline: "end" });
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const editImages = (index) => {
|
||||||
|
setEditImage(images[index]);
|
||||||
|
setShow(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSelect = (index) => {
|
||||||
|
const target = document.querySelector(`.ld-image-upload-img[data-index='${index}']`);
|
||||||
|
|
||||||
|
if (target.classList.contains('selected')) {
|
||||||
|
target.classList.remove('selected');
|
||||||
|
setChoseImage(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('.ld-image-upload-img').forEach(img => {
|
||||||
|
img.classList.remove("selected");
|
||||||
|
});
|
||||||
|
|
||||||
|
target.classList.add("selected");
|
||||||
|
setChoseImage(images[index].dataURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
const target = database[id];
|
||||||
|
const targetAttrs = target.attributes;
|
||||||
|
const targetStyles = target.styles;
|
||||||
|
const targetImage = target.type === "image";
|
||||||
|
|
||||||
|
const newParams = targetImage ? { ...targetAttrs, src: choseImage } : { ...targetStyles, background: `url(${choseImage})` };
|
||||||
|
const newData = targetImage ? { ...target, attributes: newParams } : { ...target, styles: newParams };
|
||||||
|
const newDatabase = { ...database, [id]: newData };
|
||||||
|
|
||||||
|
dispatch(updateDatabase(newDatabase));
|
||||||
|
hideModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Container>
|
||||||
|
<Box>
|
||||||
|
<Close onClick={hideModal}>X</Close>
|
||||||
|
|
||||||
|
<ImageUploading multiple value={images} onChange={onChange} maxNumber={maxNumber} dataURLKey="dataURL" >
|
||||||
|
{({
|
||||||
|
imageList,
|
||||||
|
onImageUpload,
|
||||||
|
onImageRemoveAll,
|
||||||
|
onImageUpdate,
|
||||||
|
onImageRemove,
|
||||||
|
isDragging,
|
||||||
|
dragProps,
|
||||||
|
}) => (
|
||||||
|
<div className="ld-image-upload-container">
|
||||||
|
<div>
|
||||||
|
<div className="ld-image-upload-zone" style={isDragging ? { color: 'red' } : undefined} onClick={onImageUpload} {...dragProps}>
|
||||||
|
<svg viewBox="0 0 1024 1024" focusable="false" data-icon="inbox" width="2em" height="2em" fill="currentColor" aria-hidden="true">
|
||||||
|
<path d="M885.2 446.3l-.2-.8-112.2-285.1c-5-16.1-19.9-27.2-36.8-27.2H281.2c-17 0-32.1 11.3-36.9 27.6L139.4 443l-.3.7-.2.8c-1.3 4.9-1.7 9.9-1 14.8-.1 1.6-.2 3.2-.2 4.8V830a60.9 60.9 0 0060.8 60.8h627.2c33.5 0 60.8-27.3 60.9-60.8V464.1c0-1.3 0-2.6-.1-3.7.4-4.9 0-9.6-1.3-14.1zm-295.8-43l-.3 15.7c-.8 44.9-31.8 75.1-77.1 75.1-22.1 0-41.1-7.1-54.8-20.6S436 441.2 435.6 419l-.3-15.7H229.5L309 210h399.2l81.7 193.3H589.4zm-375 76.8h157.3c24.3 57.1 76 90.8 140.4 90.8 33.7 0 65-9.4 90.3-27.2 22.2-15.6 39.5-37.4 50.7-63.6h156.5V814H214.4V480.1z"></path>
|
||||||
|
</svg>
|
||||||
|
Click or Drop here
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="ld-image-upload-list">
|
||||||
|
{!imageList.length && <div className="ld-image-upload-list__empty"><p><b>Thư viện ảnh trống.</b></p> Vui lòng tải thêm ảnh lên để sử dụng.</div>}
|
||||||
|
|
||||||
|
{imageList && imageList.map((image, index) => (
|
||||||
|
<div className="ld-image-upload-item" key={index}>
|
||||||
|
<div className="ld-image-upload-img" data-index={index} onClick={() => onSelect(index)}>
|
||||||
|
<div className="ld-img-block">
|
||||||
|
<img src={image['dataURL']} alt="" width="140" className="ld-img-content" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ld-image-upload-wrapper">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="ld-image-upload-btn ld-image-upload-btn__tool"
|
||||||
|
onClick={() => editImages(index)}
|
||||||
|
data-tooltip-id="ld-images-tooltip"
|
||||||
|
data-tooltip-content="Xem, chỉnh sửa"
|
||||||
|
data-tooltip-place="top"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512">
|
||||||
|
<path d="M441 58.9L453.1 71c9.4 9.4 9.4 24.6 0 33.9L424 134.1 377.9 88 407 58.9c9.4-9.4 24.6-9.4 33.9 0zM209.8 256.2L344 121.9 390.1 168 255.8 302.2c-2.9 2.9-6.5 5-10.4 6.1l-58.5 16.7 16.7-58.5c1.1-3.9 3.2-7.5 6.1-10.4zM373.1 25L175.8 222.2c-8.7 8.7-15 19.4-18.3 31.1l-28.6 100c-2.4 8.4-.1 17.4 6.1 23.6s15.2 8.5 23.6 6.1l100-28.6c11.8-3.4 22.5-9.7 31.1-18.3L487 138.9c28.1-28.1 28.1-73.7 0-101.8L474.9 25C446.8-3.1 401.2-3.1 373.1 25zM88 64C39.4 64 0 103.4 0 152V424c0 48.6 39.4 88 88 88H360c48.6 0 88-39.4 88-88V312c0-13.3-10.7-24-24-24s-24 10.7-24 24V424c0 22.1-17.9 40-40 40H88c-22.1 0-40-17.9-40-40V152c0-22.1 17.9-40 40-40H200c13.3 0 24-10.7 24-24s-10.7-24-24-24H88z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="ld-image-upload-btn ld-image-upload-btn__tool"
|
||||||
|
onClick={() => onImageUpdate(index)}
|
||||||
|
data-tooltip-id="ld-images-tooltip"
|
||||||
|
data-tooltip-content="Thay thế"
|
||||||
|
data-tooltip-place="top"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="14" width="17.5" viewBox="0 0 640 512">
|
||||||
|
<path d="M144 480C64.5 480 0 415.5 0 336c0-62.8 40.2-116.2 96.2-135.9c-.1-2.7-.2-5.4-.2-8.1c0-88.4 71.6-160 160-160c59.3 0 111 32.2 138.7 80.2C409.9 102 428.3 96 448 96c53 0 96 43 96 96c0 12.2-2.3 23.8-6.4 34.6C596 238.4 640 290.1 640 352c0 70.7-57.3 128-128 128H144zm79-217c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l39-39V392c0 13.3 10.7 24 24 24s24-10.7 24-24V257.9l39 39c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-80-80c-9.4-9.4-24.6-9.4-33.9 0l-80 80z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="ld-image-upload-btn ld-image-upload-btn__tool"
|
||||||
|
onClick={() => { if (confirm('Are you sure?')) { onImageRemove(index) } }}
|
||||||
|
data-tooltip-id="ld-images-tooltip"
|
||||||
|
data-tooltip-content="Xóa"
|
||||||
|
data-tooltip-place="top"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="14" width="12.25" viewBox="0 0 448 512">
|
||||||
|
<path d="M170.5 51.6L151.5 80h145l-19-28.4c-1.5-2.2-4-3.6-6.7-3.6H177.1c-2.7 0-5.2 1.3-6.7 3.6zm147-26.6L354.2 80H368h48 8c13.3 0 24 10.7 24 24s-10.7 24-24 24h-8V432c0 44.2-35.8 80-80 80H112c-44.2 0-80-35.8-80-80V128H24c-13.3 0-24-10.7-24-24S10.7 80 24 80h8H80 93.8l36.7-55.1C140.9 9.4 158.4 0 177.1 0h93.7c18.7 0 36.2 9.4 46.6 24.9zM80 128V432c0 17.7 14.3 32 32 32H336c17.7 0 32-14.3 32-32V128H80zm80 64V400c0 8.8-7.2 16-16 16s-16-7.2-16-16V192c0-8.8 7.2-16 16-16s16 7.2 16 16zm80 0V400c0 8.8-7.2 16-16 16s-16-7.2-16-16V192c0-8.8 7.2-16 16-16s16 7.2 16 16zm80 0V400c0 8.8-7.2 16-16 16s-16-7.2-16-16V192c0-8.8 7.2-16 16-16s16 7.2 16 16z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ld-image-upload-wrapper ld-image-upload-wrapper__bottom">
|
||||||
|
<button type="button" className="ld-image-upload-btn remove-all" onClick={() => { if (confirm('Are you sure?')) { onImageRemoveAll() } }}>Remove all images</button>
|
||||||
|
<Button $enable={choseImage} type="button" className="ld-image-upload-btn submit" onClick={() => { if (confirm('Are you sure?')) { onSubmit() } }}>Replace</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ImageUploading>
|
||||||
|
|
||||||
|
<ImageEditors image={editImage} show={show} setShow={setShow} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Tooltip id="ld-images-tooltip" />
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImageUpload
|
||||||
36
minigame-editor/src/editor/ImageGallery/index.jsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { React, useState } from 'react'
|
||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import ImageUpload from './ImageUpload'
|
||||||
|
|
||||||
|
const Button = styled.button`
|
||||||
|
font-weight: 500;
|
||||||
|
background: #fff;
|
||||||
|
color: #f00;
|
||||||
|
padding: 8px 16px;
|
||||||
|
margin: 8px 0 0;
|
||||||
|
`
|
||||||
|
|
||||||
|
function ImageGallery({ id }) {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
const clickHandle = () => {
|
||||||
|
setVisible(true);
|
||||||
|
document.querySelector('body').style.overflow = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button type="button" onClick={clickHandle}>Chọn ảnh</Button>
|
||||||
|
|
||||||
|
{visible && createPortal(
|
||||||
|
<ImageUpload id={id} setVisible={setVisible} />
|
||||||
|
,
|
||||||
|
document.body
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImageGallery
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import { ColorPick, ColorGradientPick } from './Options'
|
||||||
|
import ImageGallery from '../../ImageGallery'
|
||||||
|
|
||||||
|
const Select = styled.select`
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
height: 24px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Image = styled.img`
|
||||||
|
display: block;
|
||||||
|
width: auto;
|
||||||
|
max-height: 200px;
|
||||||
|
margin-top: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const BackgroundInner = ({ props, selected }) => {
|
||||||
|
const { id, value } = props;
|
||||||
|
|
||||||
|
switch (selected) {
|
||||||
|
case 'color':
|
||||||
|
return (
|
||||||
|
<ColorPick props={props} />
|
||||||
|
)
|
||||||
|
case 'gradient':
|
||||||
|
return (
|
||||||
|
<ColorGradientPick props={props} />
|
||||||
|
)
|
||||||
|
case 'image':
|
||||||
|
const image = value.substring(4, value.length - 1);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Image src={image} alt="Preview" width={"50"} height={"50"} />
|
||||||
|
<ImageGallery id={id} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Background({ props }) {
|
||||||
|
const selects = props.settings.selects;
|
||||||
|
const [selected, setSelected] = useState(props.selected);
|
||||||
|
|
||||||
|
const valueChange = (evt) => {
|
||||||
|
setSelected(evt.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Select value={selected} onChange={valueChange}>
|
||||||
|
{
|
||||||
|
selects.map((select, index) => <option value={select} key={index}>{select}</option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
<BackgroundInner props={props} selected={selected} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Background
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { ColorPick } from './Options';
|
||||||
|
|
||||||
|
function Color({ id, props }) {
|
||||||
|
return <ColorPick id={id} props={props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Color
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { SwitchButton, SelectField } from './Options';
|
||||||
|
|
||||||
|
function Display({ props }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SwitchButton props={props} />
|
||||||
|
<SelectField props={props} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Display
|
||||||
207
minigame-editor/src/editor/StyleManager/Properties/Options.jsx
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
import { updateDatabase } from '../../../database/database'
|
||||||
|
import { SketchPicker } from 'react-color';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const ColorContainer = styled.div`
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ColorOverlay = styled.div`
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ColorPicker = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 9;
|
||||||
|
box-shadow: 0 1px 5px rgba(0,0,0,0.3);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ColorButton = styled.button`
|
||||||
|
display: block;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
margin: 10px;
|
||||||
|
border: 1px solid #000000;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: ${props => props.$backgroundColor}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ColorInput = styled.input`
|
||||||
|
height: 36px;
|
||||||
|
width: 36px;
|
||||||
|
font-size: 18px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const BackgroundGradient = styled.div`
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
background: ${props => props.$backgroundGradient};
|
||||||
|
margin: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SwitchButton = ({ props }) => {
|
||||||
|
const database = useSelector((state) => state.database.value);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const { id, value, settings, selected } = props;
|
||||||
|
|
||||||
|
const valueChange = (e) => {
|
||||||
|
console.log(id, e.target.checked);
|
||||||
|
const display = e.target.checked ? "revert-layer" : "none";
|
||||||
|
|
||||||
|
console.log(display);
|
||||||
|
|
||||||
|
// const key = settings.id;
|
||||||
|
// const newStyles = { ...database[id].styles, [key]: display };
|
||||||
|
// const newParams = { ...database[id], styles: newStyles };
|
||||||
|
// const newDatabase = { ...database, [id]: newParams };
|
||||||
|
// dispatch(updateDatabase(newDatabase));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label className="ld-switch">
|
||||||
|
<input className='ld-switch-checkbox' type="checkbox" onChange={valueChange} />
|
||||||
|
<span className="ld-switch-slider"></span>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const SlideRange = ({ props }) => {
|
||||||
|
const { id, value, settings, selected } = props;
|
||||||
|
|
||||||
|
const valueChange = (e) => {
|
||||||
|
console.log(id, e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ld-slide-range">
|
||||||
|
<input type="range" min="0" max="100" defaultValue="50" className="ld-slide-range-line" onInput={valueChange} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const SelectField = ({ props }) => {
|
||||||
|
console.log(props);
|
||||||
|
const { id, value, settings, selected } = props;
|
||||||
|
const selects = settings.selects;
|
||||||
|
|
||||||
|
const handleChange = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<select value={selected} onChange={handleChange}>
|
||||||
|
{
|
||||||
|
selects.map((select, index) => <option value={select} key={index}>{select}</option>)
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ColorPick = ({ props, inital, handle }) => {
|
||||||
|
const database = useSelector((state) => state.database.value);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const { id, value, settings, selected } = props;
|
||||||
|
const initalValue = inital ? inital : selected === "color" ? value : "#f00";
|
||||||
|
const key = settings.id;
|
||||||
|
|
||||||
|
const [color, setColor] = useState(initalValue);
|
||||||
|
const [display, setDisplay] = useState(false);
|
||||||
|
|
||||||
|
const valueChange = (color) => {
|
||||||
|
setColor(color.hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueSave = () => {
|
||||||
|
setDisplay(!display);
|
||||||
|
|
||||||
|
if (handle) {
|
||||||
|
handle(color);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newStyles = { ...database[id].styles, [key]: color };
|
||||||
|
const newParams = { ...database[id], styles: newStyles };
|
||||||
|
const newDatabase = { ...database, [id]: newParams };
|
||||||
|
dispatch(updateDatabase(newDatabase));
|
||||||
|
}
|
||||||
|
|
||||||
|
const showHandle = () => {
|
||||||
|
setDisplay(!display);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ColorContainer>
|
||||||
|
<ColorButton $backgroundColor={color} onClick={showHandle} />
|
||||||
|
|
||||||
|
{display &&
|
||||||
|
<>
|
||||||
|
<ColorOverlay onClick={valueSave} />
|
||||||
|
<ColorPicker>
|
||||||
|
<SketchPicker color={color} onChange={valueChange} />
|
||||||
|
</ColorPicker>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</ColorContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ColorGradientPick = ({ props }) => {
|
||||||
|
const database = useSelector((state) => state.database.value);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
let initalAngel = 0;
|
||||||
|
let initalColor1 = "#000";
|
||||||
|
let initalColor2 = "#fff";
|
||||||
|
|
||||||
|
const { id, value, settings, selected } = props;
|
||||||
|
const key = settings.id;
|
||||||
|
|
||||||
|
if (selected === "gradient") {
|
||||||
|
const gradient = value.substring(16, value.length - 1).split(",");
|
||||||
|
initalAngel = gradient[0].replace("deg", "");
|
||||||
|
initalColor1 = gradient[1].trim();
|
||||||
|
initalColor2 = gradient[2].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const [angle, setAngle] = useState(initalAngel);
|
||||||
|
const [colorStart, setColorStart] = useState(initalColor1);
|
||||||
|
const [colorEnd, setColorEnd] = useState(initalColor2);
|
||||||
|
|
||||||
|
const backgroundGradient = `linear-gradient(${angle}deg, ${colorStart}, ${colorEnd})`
|
||||||
|
|
||||||
|
const clickHandle = () => {
|
||||||
|
const newStyles = { ...database[id].styles, [key]: backgroundGradient };
|
||||||
|
const newParams = { ...database[id], styles: newStyles };
|
||||||
|
const newDatabase = { ...database, [id]: newParams };
|
||||||
|
dispatch(updateDatabase(newDatabase));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ColorContainer>
|
||||||
|
<ColorInput value={angle} onChange={(evt) => setAngle(evt.target.value)} />
|
||||||
|
<ColorPick props={props} inital={initalColor1} handle={setColorStart} />
|
||||||
|
<ColorPick props={props} inital={initalColor2} handle={setColorEnd} />
|
||||||
|
<BackgroundGradient $backgroundGradient={backgroundGradient} onClick={clickHandle} />
|
||||||
|
</ColorContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SwitchButton, SlideRange, SelectField, ColorPick, ColorGradientPick }
|
||||||
12
minigame-editor/src/editor/StyleManager/Properties/Size.jsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { SlideRange, SelectField } from './Options';
|
||||||
|
|
||||||
|
const Size = ({ id, props }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SlideRange id={id} props={props} />
|
||||||
|
<SelectField id={id} props={props} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Size
|
||||||
43
minigame-editor/src/editor/StyleManager/Properties/index.jsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import Size from './Size'
|
||||||
|
import Color from './Color'
|
||||||
|
import Display from './Display'
|
||||||
|
import Background from './Background'
|
||||||
|
|
||||||
|
import properties from '../../../database/properties.json'
|
||||||
|
|
||||||
|
const PropertyInner = ({ props }) => {
|
||||||
|
const type = props.type;
|
||||||
|
const value = props.value;
|
||||||
|
|
||||||
|
const propsNew = {
|
||||||
|
id: props.id,
|
||||||
|
value: value,
|
||||||
|
settings: properties[type],
|
||||||
|
selected: value
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
// case 'display':
|
||||||
|
// return <Display props={propsNew} />
|
||||||
|
// case 'width':
|
||||||
|
// return <Size props={propsNew} />
|
||||||
|
// case 'height':
|
||||||
|
// return <Size props={propsNew} />
|
||||||
|
case 'color':
|
||||||
|
return <Color props={propsNew} />
|
||||||
|
case 'background':
|
||||||
|
const selectedBg = type === "background" ? value.indexOf("url") > -1 ? "image" : value.indexOf("gradient") > -1 ? "gradient" : "color" : "color";
|
||||||
|
const propsBg = { ...propsNew, selected: selectedBg }
|
||||||
|
return <Background props={propsBg} />
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Property({ props }) {
|
||||||
|
return (
|
||||||
|
<PropertyInner props={props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Property
|
||||||
83
minigame-editor/src/editor/StyleManager/index.jsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { useState, memo } from 'react'
|
||||||
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
import { updateDatabase } from '../../database/database'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import Property from './Properties'
|
||||||
|
|
||||||
|
const Heading = styled.h3`
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Item = styled.div`
|
||||||
|
position: relative;
|
||||||
|
padding: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Title = styled.h4`
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
function StyleManager({ id, props }) {
|
||||||
|
const database = useSelector((state) => state.database.value);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const StyleInner = memo(function StyleInner({ props }) {
|
||||||
|
const key = props.key;
|
||||||
|
const [value, setValue] = useState(props.value);
|
||||||
|
|
||||||
|
const changeHandle = (evt) => {
|
||||||
|
setValue(evt.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blurHandle = () => {
|
||||||
|
const initalValue = database[id].styles[key];
|
||||||
|
if (value === initalValue) return;
|
||||||
|
|
||||||
|
const newStyles = { ...database[id].styles, [key]: value };
|
||||||
|
const newParams = { ...database[id], styles: newStyles };
|
||||||
|
const newDatabase = { ...database, [id]: newParams };
|
||||||
|
dispatch(updateDatabase(newDatabase));
|
||||||
|
}
|
||||||
|
|
||||||
|
const propertyProps = {
|
||||||
|
id: id,
|
||||||
|
type: key,
|
||||||
|
value: value
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Item>
|
||||||
|
<Title>[ {key} ]</Title>
|
||||||
|
<input className='w-100' type='text' value={value} onChange={changeHandle} onBlur={blurHandle} />
|
||||||
|
<Property props={propertyProps} />
|
||||||
|
</Item>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
const styleList = Object.keys(props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Heading>Style</Heading>
|
||||||
|
{
|
||||||
|
styleList.map((key, index) => {
|
||||||
|
const attr = {
|
||||||
|
key: key,
|
||||||
|
value: props[key]
|
||||||
|
}
|
||||||
|
return <StyleInner props={attr} key={index} />
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StyleManager
|
||||||
43
minigame-editor/src/editor/index.jsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import Attribute from './Attribute'
|
||||||
|
import StyleManager from './StyleManager'
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
width: 300px;
|
||||||
|
max-height: 100vh;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
background: #333;
|
||||||
|
color : #fff;
|
||||||
|
overflow: auto;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Title = styled.h2`
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
border-bottom: 1px solid #f9f9f9;
|
||||||
|
`
|
||||||
|
|
||||||
|
function Editor({ props }) {
|
||||||
|
const id = props.id;
|
||||||
|
const attributes = props.attributes;
|
||||||
|
const styles = props.styles;
|
||||||
|
const hasAttributes = attributes && Object.keys(attributes).length > 0;
|
||||||
|
const hasStyles = styles && Object.keys(styles).length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Container>
|
||||||
|
<Title>Editor</Title>
|
||||||
|
{hasAttributes && <Attribute id={id} props={attributes} />}
|
||||||
|
{hasStyles && <StyleManager id={id} props={styles} />}
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Editor
|
||||||
734
minigame-editor/src/index.css
Normal file
@@ -0,0 +1,734 @@
|
|||||||
|
:root {
|
||||||
|
--color-primary: #ff74ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
::after,
|
||||||
|
::before {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
-webkit-box-sizing: inherit;
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: "Chakra Petch", sans-serif;
|
||||||
|
color: #000;
|
||||||
|
word-break: break-word;
|
||||||
|
line-height: calc(100% + 6px);
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
input::placeholder,
|
||||||
|
select,
|
||||||
|
textarea,
|
||||||
|
textarea::placeholder {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
max-width: 100%;
|
||||||
|
outline-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
display: inherit;
|
||||||
|
resize: vertical;
|
||||||
|
outline-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: -webkit-box;
|
||||||
|
display: flex;
|
||||||
|
-ms-flex-wrap: wrap;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-right: -8px;
|
||||||
|
margin-left: -8px;
|
||||||
|
}
|
||||||
|
.no-gutters {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
.no-gutters > .col,
|
||||||
|
.no-gutters > [class*="col-"] {
|
||||||
|
padding-right: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.col,
|
||||||
|
.col-1,
|
||||||
|
.col-10,
|
||||||
|
.col-11,
|
||||||
|
.col-12,
|
||||||
|
.col-2,
|
||||||
|
.col-3,
|
||||||
|
.col-4,
|
||||||
|
.col-5,
|
||||||
|
.col-6,
|
||||||
|
.col-7,
|
||||||
|
.col-8,
|
||||||
|
.col-9,
|
||||||
|
.col-auto,
|
||||||
|
.col-sm,
|
||||||
|
.col-sm-1,
|
||||||
|
.col-sm-10,
|
||||||
|
.col-sm-11,
|
||||||
|
.col-sm-12,
|
||||||
|
.col-sm-2,
|
||||||
|
.col-sm-3,
|
||||||
|
.col-sm-4,
|
||||||
|
.col-sm-5,
|
||||||
|
.col-sm-6,
|
||||||
|
.col-sm-7,
|
||||||
|
.col-sm-8,
|
||||||
|
.col-sm-9,
|
||||||
|
.col-sm-auto {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
.col {
|
||||||
|
-ms-flex-preferred-size: 0;
|
||||||
|
flex-basis: 0;
|
||||||
|
-ms-flex-positive: 1;
|
||||||
|
-webkit-box-flex: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.row-cols-1 > * {
|
||||||
|
-ms-flex: 0 0 100%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.row-cols-2 > * {
|
||||||
|
-ms-flex: 0 0 50%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 50%;
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
.row-cols-3 > * {
|
||||||
|
-ms-flex: 0 0 33.333333%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 33.333333%;
|
||||||
|
max-width: 33.333333%;
|
||||||
|
}
|
||||||
|
.row-cols-4 > * {
|
||||||
|
-ms-flex: 0 0 25%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 25%;
|
||||||
|
max-width: 25%;
|
||||||
|
}
|
||||||
|
.row-cols-5 > * {
|
||||||
|
-ms-flex: 0 0 20%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 20%;
|
||||||
|
max-width: 20%;
|
||||||
|
}
|
||||||
|
.row-cols-6 > * {
|
||||||
|
-ms-flex: 0 0 16.666667%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 16.666667%;
|
||||||
|
max-width: 16.666667%;
|
||||||
|
}
|
||||||
|
.col-auto {
|
||||||
|
-ms-flex: 0 0 auto;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.col-1 {
|
||||||
|
-ms-flex: 0 0 8.333333%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 8.333333%;
|
||||||
|
max-width: 8.333333%;
|
||||||
|
}
|
||||||
|
.col-2 {
|
||||||
|
-ms-flex: 0 0 16.666667%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 16.666667%;
|
||||||
|
max-width: 16.666667%;
|
||||||
|
}
|
||||||
|
.col-3 {
|
||||||
|
-ms-flex: 0 0 25%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 25%;
|
||||||
|
max-width: 25%;
|
||||||
|
}
|
||||||
|
.col-4 {
|
||||||
|
-ms-flex: 0 0 33.333333%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 33.333333%;
|
||||||
|
max-width: 33.333333%;
|
||||||
|
}
|
||||||
|
.col-5 {
|
||||||
|
-ms-flex: 0 0 41.666667%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 41.666667%;
|
||||||
|
max-width: 41.666667%;
|
||||||
|
}
|
||||||
|
.col-6 {
|
||||||
|
-ms-flex: 0 0 50%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 50%;
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
.col-7 {
|
||||||
|
-ms-flex: 0 0 58.333333%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 58.333333%;
|
||||||
|
max-width: 58.333333%;
|
||||||
|
}
|
||||||
|
.col-8 {
|
||||||
|
-ms-flex: 0 0 66.666667%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 66.666667%;
|
||||||
|
max-width: 66.666667%;
|
||||||
|
}
|
||||||
|
.col-9 {
|
||||||
|
-ms-flex: 0 0 75%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 75%;
|
||||||
|
max-width: 75%;
|
||||||
|
}
|
||||||
|
.col-10 {
|
||||||
|
-ms-flex: 0 0 83.333333%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 83.333333%;
|
||||||
|
max-width: 83.333333%;
|
||||||
|
}
|
||||||
|
.col-11 {
|
||||||
|
-ms-flex: 0 0 91.666667%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 91.666667%;
|
||||||
|
max-width: 91.666667%;
|
||||||
|
}
|
||||||
|
.col-12 {
|
||||||
|
-ms-flex: 0 0 100%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.offset-1 {
|
||||||
|
margin-left: 8.333333%;
|
||||||
|
}
|
||||||
|
.offset-2 {
|
||||||
|
margin-left: 16.666667%;
|
||||||
|
}
|
||||||
|
.offset-3 {
|
||||||
|
margin-left: 25%;
|
||||||
|
}
|
||||||
|
.offset-4 {
|
||||||
|
margin-left: 33.333333%;
|
||||||
|
}
|
||||||
|
.offset-5 {
|
||||||
|
margin-left: 41.666667%;
|
||||||
|
}
|
||||||
|
.offset-6 {
|
||||||
|
margin-left: 50%;
|
||||||
|
}
|
||||||
|
.offset-7 {
|
||||||
|
margin-left: 58.333333%;
|
||||||
|
}
|
||||||
|
.offset-8 {
|
||||||
|
margin-left: 66.666667%;
|
||||||
|
}
|
||||||
|
.offset-9 {
|
||||||
|
margin-left: 75%;
|
||||||
|
}
|
||||||
|
.offset-10 {
|
||||||
|
margin-left: 83.333333%;
|
||||||
|
}
|
||||||
|
.offset-11 {
|
||||||
|
margin-left: 91.666667%;
|
||||||
|
}
|
||||||
|
@media (max-width: 1600px) {
|
||||||
|
.grid {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
margin-right: -6px;
|
||||||
|
margin-left: -6px;
|
||||||
|
}
|
||||||
|
.col,
|
||||||
|
.col-1,
|
||||||
|
.col-10,
|
||||||
|
.col-11,
|
||||||
|
.col-12,
|
||||||
|
.col-2,
|
||||||
|
.col-3,
|
||||||
|
.col-4,
|
||||||
|
.col-5,
|
||||||
|
.col-6,
|
||||||
|
.col-7,
|
||||||
|
.col-8,
|
||||||
|
.col-9,
|
||||||
|
.col-auto,
|
||||||
|
.col-sm,
|
||||||
|
.col-sm-1,
|
||||||
|
.col-sm-10,
|
||||||
|
.col-sm-11,
|
||||||
|
.col-sm-12,
|
||||||
|
.col-sm-2,
|
||||||
|
.col-sm-3,
|
||||||
|
.col-sm-4,
|
||||||
|
.col-sm-5,
|
||||||
|
.col-sm-6,
|
||||||
|
.col-sm-7,
|
||||||
|
.col-sm-8,
|
||||||
|
.col-sm-9,
|
||||||
|
.col-sm-auto {
|
||||||
|
padding-right: 6px;
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
.no-gutters {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.grid {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
margin-right: -5px;
|
||||||
|
margin-left: -5px;
|
||||||
|
}
|
||||||
|
.col,
|
||||||
|
.col-1,
|
||||||
|
.col-10,
|
||||||
|
.col-11,
|
||||||
|
.col-12,
|
||||||
|
.col-2,
|
||||||
|
.col-3,
|
||||||
|
.col-4,
|
||||||
|
.col-5,
|
||||||
|
.col-6,
|
||||||
|
.col-7,
|
||||||
|
.col-8,
|
||||||
|
.col-9,
|
||||||
|
.col-auto,
|
||||||
|
.col-sm,
|
||||||
|
.col-sm-1,
|
||||||
|
.col-sm-10,
|
||||||
|
.col-sm-11,
|
||||||
|
.col-sm-12,
|
||||||
|
.col-sm-2,
|
||||||
|
.col-sm-3,
|
||||||
|
.col-sm-4,
|
||||||
|
.col-sm-5,
|
||||||
|
.col-sm-6,
|
||||||
|
.col-sm-7,
|
||||||
|
.col-sm-8,
|
||||||
|
.col-sm-9,
|
||||||
|
.col-sm-auto {
|
||||||
|
padding-right: 5px;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
.col-sm-1 {
|
||||||
|
-ms-flex: 0 0 8.333333%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 8.333333%;
|
||||||
|
max-width: 8.333333%;
|
||||||
|
}
|
||||||
|
.col-sm-2 {
|
||||||
|
-ms-flex: 0 0 16.666667%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 16.666667%;
|
||||||
|
max-width: 16.666667%;
|
||||||
|
}
|
||||||
|
.col-sm-3 {
|
||||||
|
-ms-flex: 0 0 25%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 25%;
|
||||||
|
max-width: 25%;
|
||||||
|
}
|
||||||
|
.col-sm-4 {
|
||||||
|
-ms-flex: 0 0 33.333333%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 33.333333%;
|
||||||
|
max-width: 33.333333%;
|
||||||
|
}
|
||||||
|
.col-sm-5 {
|
||||||
|
-ms-flex: 0 0 41.666667%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 41.666667%;
|
||||||
|
max-width: 41.666667%;
|
||||||
|
}
|
||||||
|
.col-sm-6 {
|
||||||
|
-ms-flex: 0 0 50%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 50%;
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
.col-sm-7 {
|
||||||
|
-ms-flex: 0 0 58.333333%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 58.333333%;
|
||||||
|
max-width: 58.333333%;
|
||||||
|
}
|
||||||
|
.col-sm-8 {
|
||||||
|
-ms-flex: 0 0 66.666667%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 66.666667%;
|
||||||
|
max-width: 66.666667%;
|
||||||
|
}
|
||||||
|
.col-sm-9 {
|
||||||
|
-ms-flex: 0 0 75%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 75%;
|
||||||
|
max-width: 75%;
|
||||||
|
}
|
||||||
|
.col-sm-10 {
|
||||||
|
-ms-flex: 0 0 83.333333%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 83.333333%;
|
||||||
|
max-width: 83.333333%;
|
||||||
|
}
|
||||||
|
.col-sm-11 {
|
||||||
|
-ms-flex: 0 0 91.666667%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 91.666667%;
|
||||||
|
max-width: 91.666667%;
|
||||||
|
}
|
||||||
|
.col-sm-12 {
|
||||||
|
-ms-flex: 0 0 100%;
|
||||||
|
-webkit-box-flex: 0;
|
||||||
|
flex: 0 0 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.offset-sm-0 {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
.no-gutters {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulse-icon {
|
||||||
|
position: absolute;
|
||||||
|
display: inline-block;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulse-icon .icon {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #00e745;
|
||||||
|
background: #00e745;
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
left: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulse-icon .elements {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulse-icon .pulse {
|
||||||
|
position: absolute;
|
||||||
|
-webkit-animation: pulse-wave 1s linear infinite both;
|
||||||
|
animation: pulse-wave 1s linear infinite both;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: solid 1px rgb(0, 231, 69, 0.5);
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-wave {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: scale(1.5);
|
||||||
|
transform: scale(1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transform: scale(2);
|
||||||
|
transform: scale(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-display {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-display {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-1 {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-100 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ld-item {
|
||||||
|
outline: 0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
.ld-item:hover {
|
||||||
|
box-shadow: inset 0 0 3px #000;
|
||||||
|
}
|
||||||
|
.tui-image-editor-container .tui-image-editor-main {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.tui-image-editor-container .tui-image-editor-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.tui-image-editor-container .tui-image-editor-help-menu.left {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.tui-image-editor-container [tooltip-content="Delete"],
|
||||||
|
.tui-image-editor-container [tooltip-content="DeleteAll"] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ld-img-block {
|
||||||
|
position: relative;
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
padding-bottom: 100%;
|
||||||
|
}
|
||||||
|
.ld-img-content {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
margin: auto auto;
|
||||||
|
}
|
||||||
|
.ld-img-content.object-cover {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
-o-object-fit: cover;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.ld-block:hover .ld-block-tool,
|
||||||
|
.ld-element:hover .ld-element-tool {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
.ld-element__resize {
|
||||||
|
border: 1px dashed lightgray;
|
||||||
|
}
|
||||||
|
.ld-element__resize:hover {
|
||||||
|
border-color: red;
|
||||||
|
}
|
||||||
|
.ld-element__image {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ld-switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 30px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
.ld-switch-checkbox {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.ld-switch-slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
border-radius: 16px;
|
||||||
|
-webkit-transition: 0.4s;
|
||||||
|
transition: 0.4s;
|
||||||
|
}
|
||||||
|
.ld-switch-slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
left: 4px;
|
||||||
|
bottom: 3px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
-webkit-transition: 0.4s;
|
||||||
|
transition: 0.4s;
|
||||||
|
}
|
||||||
|
.ld-switch-checkbox:checked + .ld-switch-slider {
|
||||||
|
background-color: #2196f3;
|
||||||
|
}
|
||||||
|
.ld-switch-checkbox:focus + .ld-switch-slider {
|
||||||
|
box-shadow: 0 0 1px #2196f3;
|
||||||
|
}
|
||||||
|
.ld-switch-checkbox:checked + .ld-switch-slider:before {
|
||||||
|
-webkit-transform: translateX(12px);
|
||||||
|
-ms-transform: translateX(12px);
|
||||||
|
transform: translateX(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ld-slide-range {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
.ld-slide-range-line {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 10px;
|
||||||
|
background: #d3d3d3;
|
||||||
|
border-radius: 5px;
|
||||||
|
outline: 0;
|
||||||
|
opacity: 0.7;
|
||||||
|
-webkit-transition: 0.2s;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
.ld-slide-range-line:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.ld-slide-range-line::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: #2196f3;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.ld-slide-range-line::-moz-range-thumb {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: #2196f3;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ld-image-upload-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 40px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
.ld-image-upload-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 0 20px;
|
||||||
|
width: 520px;
|
||||||
|
max-height: 419px;
|
||||||
|
min-height: 419px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 20px 20px 0;
|
||||||
|
border: 2px dashed #f0f0f0;
|
||||||
|
}
|
||||||
|
.ld-image-upload-list__empty {
|
||||||
|
grid-column: span 3;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.ld-image-upload-item {
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
.ld-image-upload-img {
|
||||||
|
transition: padding 0.5s;
|
||||||
|
}
|
||||||
|
.ld-image-upload-img.selected {
|
||||||
|
padding: 10px;
|
||||||
|
border: 2px solid red;
|
||||||
|
}
|
||||||
|
.ld-image-upload-btn {
|
||||||
|
border: 0;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.ld-image-upload-btn.remove-all {
|
||||||
|
background: red;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.ld-image-upload-btn.submit {
|
||||||
|
background: green;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.ld-image-upload-btn__tool {
|
||||||
|
padding: 0;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.ld-image-upload-zone {
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
border: 2px dashed #f0f0f0;
|
||||||
|
}
|
||||||
|
.ld-image-upload-zone svg {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto 8px;
|
||||||
|
}
|
||||||
|
.ld-image-upload-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.ld-image-upload-wrapper__bottom {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
15
minigame-editor/src/main.jsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { StrictMode } from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import store from './database/store.jsx';
|
||||||
|
import App from './App.jsx'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')).render(
|
||||||
|
<StrictMode>
|
||||||
|
<Provider store={store}>
|
||||||
|
<App />
|
||||||
|
</Provider>
|
||||||
|
</StrictMode>,
|
||||||
|
)
|
||||||
|
|
||||||
16
minigame-editor/src/template/Circle.jsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
function Circle() {
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<div className="circle">
|
||||||
|
<img id="circle_background" className="ld-item circle-bg" src="src/assets/images/hnc-game-2-cricle-1.png" alt="Cricle 1" width="493" height="493" />
|
||||||
|
<img className="circle-item rw" src="src/assets/images/hnc-game-2-cricle-2.png" alt="Cricle 1" width="406" height="406" />
|
||||||
|
<img id="circle_arrow" className="ld-item circle-item ar" src="src/assets/images/hnc-game-2-cricle-3.png" alt="Cricle 1" width="62" height="133" />
|
||||||
|
<button className="circle-item btn" type="button" onclick="funcTest1();">
|
||||||
|
<img id="circle_button" className="ld-item" src="src/assets/images/hnc-game-2-cricle-4.png" alt="Cricle 1" width="162" height="162" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Circle
|
||||||
11
minigame-editor/src/template/Footer.jsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import Editable from "../components/Editable"
|
||||||
|
|
||||||
|
function Footer({ props }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Editable props={props.footer} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Footer
|
||||||
24
minigame-editor/src/template/Header.jsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import Image from "../components/Image"
|
||||||
|
import Editable from "../components/Editable"
|
||||||
|
|
||||||
|
function Header({ props }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<header className="header">
|
||||||
|
<div className="container">
|
||||||
|
<div>
|
||||||
|
<Image props={props.logo} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Image props={props.banner} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Editable props={props.date} />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Header
|
||||||
28
minigame-editor/src/template/Noffy.jsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
function Noffy() {
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<div className="noffy">
|
||||||
|
<div className="noffy-remain">
|
||||||
|
Bạn có <b className="noffy-number">2</b> lượt quay
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="noffy-player">
|
||||||
|
<p>
|
||||||
|
<span className="noffy-pluse"></span>
|
||||||
|
<span className="pulse-icon">
|
||||||
|
<span className="icon"></span>
|
||||||
|
<span className="elements">
|
||||||
|
<span className="pulse"></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
Đang online: <b className="noffy-number">168</b>
|
||||||
|
</p>
|
||||||
|
<p>Đã chơi: <b className="noffy-number">403</b></p>
|
||||||
|
<p>Đang chơi: <b className="noffy-number">16</b></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Noffy
|
||||||
25
minigame-editor/src/template/Policy.jsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import Editable from "../components/Editable"
|
||||||
|
|
||||||
|
function Policy({ props }) {
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<div className="box policy">
|
||||||
|
<Editable props={props.policy_heading} />
|
||||||
|
|
||||||
|
<Editable props={props.policy_content} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="hotline">
|
||||||
|
<a href="tel:19001903" className="tel">
|
||||||
|
<img src="src/assets/images/hnc-game-2-icon-phone-1.png" alt="Phone" width="26" height="26" />
|
||||||
|
<span>
|
||||||
|
Hotline:
|
||||||
|
<Editable props={props.policy_hotline} />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Policy
|
||||||
41
minigame-editor/src/template/Reward.jsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import Editable from "../components/Editable"
|
||||||
|
|
||||||
|
const StyledReward = styled.div`
|
||||||
|
${props => props.$sty}
|
||||||
|
`
|
||||||
|
|
||||||
|
function Reward({ props }) {
|
||||||
|
const limit = parseInt(props.reward.attributes.limit);
|
||||||
|
const style = props.reward.styles;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<StyledReward id="reward" className="ld-item box reward" $sty={style}>
|
||||||
|
<Editable props={props.reward_heading} />
|
||||||
|
|
||||||
|
<ul className=" list reward-list">
|
||||||
|
{
|
||||||
|
[...Array(limit)].map((item, index) =>
|
||||||
|
<li className="reward-item" key={index}>
|
||||||
|
<div className="reward-top">
|
||||||
|
<div className="reward-img">
|
||||||
|
<img src="src/assets/images/hnc-game-2-voucher-1.png" alt="Voucher" width="111" height="111" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="reward-no">1 giải nhất</p>
|
||||||
|
<p className="reward-name">PC GAMING HACOM HURACAN</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="reward-price">Trị giá <b className="reward-bold">59.999.000 đ</b></p>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</StyledReward>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Reward
|
||||||
116
minigame-editor/src/template/index.jsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import Header from './Header'
|
||||||
|
import Footer from './Footer'
|
||||||
|
import Policy from './Policy'
|
||||||
|
import Reward from './Reward'
|
||||||
|
import Noffy from './Noffy'
|
||||||
|
import Circle from './Circle'
|
||||||
|
|
||||||
|
import global from '../database/global.json'
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding-top: 45px;
|
||||||
|
${props => props.$background}
|
||||||
|
`
|
||||||
|
|
||||||
|
const Popup = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
padding: 50px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 9;
|
||||||
|
background: #f5f5f5;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Button = styled.button`
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: red;
|
||||||
|
color: #fff;
|
||||||
|
margin: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Navbar = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 45px;
|
||||||
|
background: #333;
|
||||||
|
z-index: 9;
|
||||||
|
`
|
||||||
|
|
||||||
|
function Template({ setTarget, props }) {
|
||||||
|
const [template, setTemplate] = useState("");
|
||||||
|
|
||||||
|
const id = props.main.id;
|
||||||
|
const className = props.main.className;
|
||||||
|
const background = `
|
||||||
|
background: ${props.main.styles.background};
|
||||||
|
background-repeat: ${props.main.styles.backgroundRepeat};
|
||||||
|
background-size: ${props.main.styles.backgroundSize};
|
||||||
|
`
|
||||||
|
|
||||||
|
const clickHandle = (evt) => {
|
||||||
|
const target = evt.target;
|
||||||
|
const isItem = target.classList.contains("ld-item");
|
||||||
|
|
||||||
|
if (isItem) {
|
||||||
|
setTarget(target.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{template ?
|
||||||
|
<Container id={id} className={'ld-item ' + className} $background={background} onClick={clickHandle}>
|
||||||
|
<Navbar>
|
||||||
|
<Button onClick={() => setTemplate("")}>Chọn Template</Button>
|
||||||
|
</Navbar>
|
||||||
|
|
||||||
|
{
|
||||||
|
global[template].order.map((type, index) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'header':
|
||||||
|
return <Header props={props} key={index} />
|
||||||
|
case 'circle':
|
||||||
|
return <Circle props={props} key={index} />
|
||||||
|
case 'noffy':
|
||||||
|
return <Noffy props={props} key={index} />
|
||||||
|
case 'reward':
|
||||||
|
return <Reward props={props} key={index} />
|
||||||
|
case 'policy':
|
||||||
|
return <Policy props={props} key={index} />
|
||||||
|
case 'footer':
|
||||||
|
return <Footer props={props} key={index} />
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
:
|
||||||
|
<Container>
|
||||||
|
<Popup>
|
||||||
|
Chọn Template:
|
||||||
|
|
||||||
|
{
|
||||||
|
Object.keys(global).map((id, index) =>
|
||||||
|
<Button key={index} onClick={() => setTemplate(id)}>{global[id].name}</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Popup>
|
||||||
|
</Container>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Template
|
||||||
7
minigame-editor/vite.config.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react-swc'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
})
|
||||||