commit 05
This commit is contained in:
parent
55aa013e1d
commit
58e60e4da4
18 changed files with 286 additions and 207 deletions
|
@ -21,7 +21,6 @@
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"unplugin-icons": "^0.22.0",
|
"unplugin-icons": "^0.22.0",
|
||||||
"vite": "^6.0.3",
|
"vite": "^6.0.3",
|
||||||
"vite-plugin-pages": "^0.32.4",
|
|
||||||
"vite-plugin-windicss": "^1.9.4",
|
"vite-plugin-windicss": "^1.9.4",
|
||||||
"windicss": "^3.5.6"
|
"windicss": "^3.5.6"
|
||||||
},
|
},
|
||||||
|
@ -46,7 +45,6 @@
|
||||||
"pinia": "^2.3.0",
|
"pinia": "^2.3.0",
|
||||||
"superjson": "^2.2.2",
|
"superjson": "^2.2.2",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.5.0",
|
|
||||||
"ws": "^8.18.0",
|
"ws": "^8.18.0",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
}
|
}
|
||||||
|
|
110
pnpm-lock.yaml
generated
110
pnpm-lock.yaml
generated
|
@ -68,9 +68,6 @@ importers:
|
||||||
vue:
|
vue:
|
||||||
specifier: ^3.5.13
|
specifier: ^3.5.13
|
||||||
version: 3.5.13(typescript@5.7.2)
|
version: 3.5.13(typescript@5.7.2)
|
||||||
vue-router:
|
|
||||||
specifier: ^4.5.0
|
|
||||||
version: 4.5.0(vue@3.5.13(typescript@5.7.2))
|
|
||||||
ws:
|
ws:
|
||||||
specifier: ^8.18.0
|
specifier: ^8.18.0
|
||||||
version: 8.18.0(bufferutil@4.0.8)
|
version: 8.18.0(bufferutil@4.0.8)
|
||||||
|
@ -111,9 +108,6 @@ importers:
|
||||||
vite:
|
vite:
|
||||||
specifier: ^6.0.3
|
specifier: ^6.0.3
|
||||||
version: 6.0.3(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(tsx@4.19.2)(yaml@2.6.1)
|
version: 6.0.3(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(tsx@4.19.2)(yaml@2.6.1)
|
||||||
vite-plugin-pages:
|
|
||||||
specifier: ^0.32.4
|
|
||||||
version: 0.32.4(@vue/compiler-sfc@3.5.13)(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(tsx@4.19.2)(yaml@2.6.1))(vue-router@4.5.0(vue@3.5.13(typescript@5.7.2)))
|
|
||||||
vite-plugin-windicss:
|
vite-plugin-windicss:
|
||||||
specifier: ^1.9.4
|
specifier: ^1.9.4
|
||||||
version: 1.9.4(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(tsx@4.19.2)(yaml@2.6.1))
|
version: 1.9.4(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(tsx@4.19.2)(yaml@2.6.1))
|
||||||
|
@ -701,9 +695,6 @@ packages:
|
||||||
'@types/connect@3.4.38':
|
'@types/connect@3.4.38':
|
||||||
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||||
|
|
||||||
'@types/debug@4.1.12':
|
|
||||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
|
||||||
|
|
||||||
'@types/estree@1.0.6':
|
'@types/estree@1.0.6':
|
||||||
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
||||||
|
|
||||||
|
@ -725,9 +716,6 @@ packages:
|
||||||
'@types/mime@1.3.5':
|
'@types/mime@1.3.5':
|
||||||
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
||||||
|
|
||||||
'@types/ms@0.7.34':
|
|
||||||
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
|
|
||||||
|
|
||||||
'@types/node@22.10.2':
|
'@types/node@22.10.2':
|
||||||
resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==}
|
resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==}
|
||||||
|
|
||||||
|
@ -954,10 +942,6 @@ packages:
|
||||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
dequal@2.0.3:
|
|
||||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
destr@2.0.3:
|
destr@2.0.3:
|
||||||
resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==}
|
resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==}
|
||||||
|
|
||||||
|
@ -1014,15 +998,6 @@ packages:
|
||||||
escape-html@1.0.3:
|
escape-html@1.0.3:
|
||||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||||
|
|
||||||
esprima-extract-comments@1.1.0:
|
|
||||||
resolution: {integrity: sha512-sBQUnvJwpeE9QnPrxh7dpI/dp67erYG4WXEAreAMoelPRpMR7NWb4YtwRPn9b+H1uLQKl/qS8WYmyaljTpjIsw==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
|
|
||||||
esprima@4.0.1:
|
|
||||||
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
estree-walker@2.0.2:
|
estree-walker@2.0.2:
|
||||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||||
|
|
||||||
|
@ -1041,10 +1016,6 @@ packages:
|
||||||
resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
|
resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
|
||||||
engines: {node: '>= 0.10.0'}
|
engines: {node: '>= 0.10.0'}
|
||||||
|
|
||||||
extract-comments@1.1.0:
|
|
||||||
resolution: {integrity: sha512-dzbZV2AdSSVW/4E7Ti5hZdHWbA+Z80RJsJhr5uiL10oyjl/gy7/o+HI1HwK4/WSZhlq4SNKU3oUzXlM13Qx02Q==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
fast-glob@3.3.2:
|
fast-glob@3.3.2:
|
||||||
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
|
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
|
||||||
engines: {node: '>=8.6.0'}
|
engines: {node: '>=8.6.0'}
|
||||||
|
@ -1194,11 +1165,6 @@ packages:
|
||||||
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
|
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
json5@2.2.3:
|
|
||||||
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
kolorist@1.8.0:
|
kolorist@1.8.0:
|
||||||
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
|
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
|
||||||
|
|
||||||
|
@ -1323,10 +1289,6 @@ packages:
|
||||||
package-manager-detector@0.2.7:
|
package-manager-detector@0.2.7:
|
||||||
resolution: {integrity: sha512-g4+387DXDKlZzHkP+9FLt8yKj8+/3tOkPv7DVTJGGRm00RkEWgqbFstX1mXJ4M0VDYhUqsTOiISqNOJnhAu3PQ==}
|
resolution: {integrity: sha512-g4+387DXDKlZzHkP+9FLt8yKj8+/3tOkPv7DVTJGGRm00RkEWgqbFstX1mXJ4M0VDYhUqsTOiISqNOJnhAu3PQ==}
|
||||||
|
|
||||||
parse-code-context@1.0.0:
|
|
||||||
resolution: {integrity: sha512-OZQaqKaQnR21iqhlnPfVisFjBWjhnMl5J9MgbP8xC+EwoVqbXrq78lp+9Zb3ahmLzrIX5Us/qbvBnaS3hkH6OA==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
parseurl@1.3.3:
|
parseurl@1.3.3:
|
||||||
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
@ -1565,24 +1527,6 @@ packages:
|
||||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
vite-plugin-pages@0.32.4:
|
|
||||||
resolution: {integrity: sha512-OM8CNb8mAzyYR8ASRC0+2LXVB8ecR/5JHc5RpxbWtF+CmhjhmIELs0iV5y8qvU48soZbk+NsFOYlhoIcjw3+ew==}
|
|
||||||
peerDependencies:
|
|
||||||
'@solidjs/router': '*'
|
|
||||||
'@vue/compiler-sfc': ^2.7.0 || ^3.0.0
|
|
||||||
react-router: '*'
|
|
||||||
vite: ^2.0.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0 || ^6.0.0
|
|
||||||
vue-router: '*'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@solidjs/router':
|
|
||||||
optional: true
|
|
||||||
'@vue/compiler-sfc':
|
|
||||||
optional: true
|
|
||||||
react-router:
|
|
||||||
optional: true
|
|
||||||
vue-router:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
vite-plugin-windicss@1.9.4:
|
vite-plugin-windicss@1.9.4:
|
||||||
resolution: {integrity: sha512-3t1AUVrs2XBXGc2BefRPRvy1CLy8qA/5A1J1Z73Ej1DIx+puXn39MQSWluxZ2FHEz8z9OEIvsoIIPc/s/P3OmQ==}
|
resolution: {integrity: sha512-3t1AUVrs2XBXGc2BefRPRvy1CLy8qA/5A1J1Z73Ej1DIx+puXn39MQSWluxZ2FHEz8z9OEIvsoIIPc/s/P3OmQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1639,11 +1583,6 @@ packages:
|
||||||
'@vue/composition-api':
|
'@vue/composition-api':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
vue-router@4.5.0:
|
|
||||||
resolution: {integrity: sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==}
|
|
||||||
peerDependencies:
|
|
||||||
vue: ^3.2.0
|
|
||||||
|
|
||||||
vue@3.5.13:
|
vue@3.5.13:
|
||||||
resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==}
|
resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -2067,10 +2006,6 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.10.2
|
'@types/node': 22.10.2
|
||||||
|
|
||||||
'@types/debug@4.1.12':
|
|
||||||
dependencies:
|
|
||||||
'@types/ms': 0.7.34
|
|
||||||
|
|
||||||
'@types/estree@1.0.6': {}
|
'@types/estree@1.0.6': {}
|
||||||
|
|
||||||
'@types/express-serve-static-core@5.0.2':
|
'@types/express-serve-static-core@5.0.2':
|
||||||
|
@ -2097,8 +2032,6 @@ snapshots:
|
||||||
|
|
||||||
'@types/mime@1.3.5': {}
|
'@types/mime@1.3.5': {}
|
||||||
|
|
||||||
'@types/ms@0.7.34': {}
|
|
||||||
|
|
||||||
'@types/node@22.10.2':
|
'@types/node@22.10.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.20.0
|
undici-types: 6.20.0
|
||||||
|
@ -2334,8 +2267,6 @@ snapshots:
|
||||||
|
|
||||||
depd@2.0.0: {}
|
depd@2.0.0: {}
|
||||||
|
|
||||||
dequal@2.0.3: {}
|
|
||||||
|
|
||||||
destr@2.0.3: {}
|
destr@2.0.3: {}
|
||||||
|
|
||||||
destroy@1.2.0: {}
|
destroy@1.2.0: {}
|
||||||
|
@ -2420,12 +2351,6 @@ snapshots:
|
||||||
|
|
||||||
escape-html@1.0.3: {}
|
escape-html@1.0.3: {}
|
||||||
|
|
||||||
esprima-extract-comments@1.1.0:
|
|
||||||
dependencies:
|
|
||||||
esprima: 4.0.1
|
|
||||||
|
|
||||||
esprima@4.0.1: {}
|
|
||||||
|
|
||||||
estree-walker@2.0.2: {}
|
estree-walker@2.0.2: {}
|
||||||
|
|
||||||
etag@1.8.1: {}
|
etag@1.8.1: {}
|
||||||
|
@ -2480,11 +2405,6 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
extract-comments@1.1.0:
|
|
||||||
dependencies:
|
|
||||||
esprima-extract-comments: 1.1.0
|
|
||||||
parse-code-context: 1.0.0
|
|
||||||
|
|
||||||
fast-glob@3.3.2:
|
fast-glob@3.3.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nodelib/fs.stat': 2.0.5
|
'@nodelib/fs.stat': 2.0.5
|
||||||
|
@ -2628,8 +2548,6 @@ snapshots:
|
||||||
|
|
||||||
jiti@2.4.2: {}
|
jiti@2.4.2: {}
|
||||||
|
|
||||||
json5@2.2.3: {}
|
|
||||||
|
|
||||||
kolorist@1.8.0: {}
|
kolorist@1.8.0: {}
|
||||||
|
|
||||||
listhen@1.9.0:
|
listhen@1.9.0:
|
||||||
|
@ -2736,8 +2654,6 @@ snapshots:
|
||||||
|
|
||||||
package-manager-detector@0.2.7: {}
|
package-manager-detector@0.2.7: {}
|
||||||
|
|
||||||
parse-code-context@1.0.0: {}
|
|
||||||
|
|
||||||
parseurl@1.3.3: {}
|
parseurl@1.3.3: {}
|
||||||
|
|
||||||
path-key@3.1.1: {}
|
path-key@3.1.1: {}
|
||||||
|
@ -2991,24 +2907,6 @@ snapshots:
|
||||||
|
|
||||||
vary@1.1.2: {}
|
vary@1.1.2: {}
|
||||||
|
|
||||||
vite-plugin-pages@0.32.4(@vue/compiler-sfc@3.5.13)(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(tsx@4.19.2)(yaml@2.6.1))(vue-router@4.5.0(vue@3.5.13(typescript@5.7.2))):
|
|
||||||
dependencies:
|
|
||||||
'@types/debug': 4.1.12
|
|
||||||
debug: 4.4.0
|
|
||||||
dequal: 2.0.3
|
|
||||||
extract-comments: 1.1.0
|
|
||||||
fast-glob: 3.3.2
|
|
||||||
json5: 2.2.3
|
|
||||||
local-pkg: 0.5.1
|
|
||||||
picocolors: 1.1.1
|
|
||||||
vite: 6.0.3(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(tsx@4.19.2)(yaml@2.6.1)
|
|
||||||
yaml: 2.6.1
|
|
||||||
optionalDependencies:
|
|
||||||
'@vue/compiler-sfc': 3.5.13
|
|
||||||
vue-router: 4.5.0(vue@3.5.13(typescript@5.7.2))
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
vite-plugin-windicss@1.9.4(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(tsx@4.19.2)(yaml@2.6.1)):
|
vite-plugin-windicss@1.9.4(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.2)(sass@1.83.0)(tsx@4.19.2)(yaml@2.6.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@windicss/plugin-utils': 1.9.4
|
'@windicss/plugin-utils': 1.9.4
|
||||||
|
@ -3036,11 +2934,6 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.5.13(typescript@5.7.2)
|
vue: 3.5.13(typescript@5.7.2)
|
||||||
|
|
||||||
vue-router@4.5.0(vue@3.5.13(typescript@5.7.2)):
|
|
||||||
dependencies:
|
|
||||||
'@vue/devtools-api': 6.6.4
|
|
||||||
vue: 3.5.13(typescript@5.7.2)
|
|
||||||
|
|
||||||
vue@3.5.13(typescript@5.7.2):
|
vue@3.5.13(typescript@5.7.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/compiler-dom': 3.5.13
|
'@vue/compiler-dom': 3.5.13
|
||||||
|
@ -3063,6 +2956,7 @@ snapshots:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
bufferutil: 4.0.8
|
bufferutil: 4.0.8
|
||||||
|
|
||||||
yaml@2.6.1: {}
|
yaml@2.6.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
zod@3.24.1: {}
|
zod@3.24.1: {}
|
||||||
|
|
BIN
public/objects/schüssel.png
Normal file
BIN
public/objects/schüssel.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
90
src/App.vue
90
src/App.vue
|
@ -6,21 +6,16 @@
|
||||||
<span class="text-center">Verbindung wird hergestellt…</span>
|
<span class="text-center">Verbindung wird hergestellt…</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-full relative flex flex-col">
|
<div class="h-full relative flex flex-col">
|
||||||
<nav class="mx-auto py-4 flex gap-4">
|
<nav class="mx-auto p-4 flex gap-4 overflow-x-auto max-w-full">
|
||||||
<div class="flex items-center rounded-lg bg-dark-400 overflow-hidden">
|
<div class="flex-shrink-0 flex items-center rounded-lg bg-dark-400 overflow-hidden">
|
||||||
<button
|
<button
|
||||||
|
v-for="tab in screens"
|
||||||
|
:key="tab.id"
|
||||||
class="px-4 py-2 bg-gray-700"
|
class="px-4 py-2 bg-gray-700"
|
||||||
:class="activeTab !== 'interactions' && 'bg-opacity-0'"
|
:class="activeScreenId !== tab.id && 'bg-opacity-0'"
|
||||||
@click="activeTab = 'interactions'"
|
@click="activeScreenId = tab.id"
|
||||||
>
|
>
|
||||||
Interagieren
|
{{ tab.label }}
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="px-4 py-2 bg-gray-700"
|
|
||||||
:class="activeTab !== 'queue' && 'bg-opacity-0'"
|
|
||||||
@click="activeTab = 'queue'"
|
|
||||||
>
|
|
||||||
Abstimmen
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button v-if="!isFullscreen" class="px-3 rounded-lg bg-dark-400 flex items-center justify-center" @click="enterFullscreen()">
|
<button v-if="!isFullscreen" class="px-3 rounded-lg bg-dark-400 flex items-center justify-center" @click="enterFullscreen()">
|
||||||
|
@ -28,8 +23,9 @@
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
<main class="flex-grow">
|
<main class="flex-grow">
|
||||||
<InteractionsScreen v-if="activeTab === 'interactions'"/>
|
<transition name="fade" mode="out-in">
|
||||||
<QueueScreen v-else-if="activeTab === 'queue'"/>
|
<component :is="screens.find(t => t.id === activeScreenId)!.component" @switch-screen="(id: string) => (activeScreenId = id)"/>
|
||||||
|
</transition>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,34 +74,76 @@
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slide-down-enter-active,
|
.list-move,
|
||||||
.slide-down-leave-active {
|
.list-enter-active,
|
||||||
position: absolute;
|
.list-leave-active {
|
||||||
transition: 200ms ease;
|
transition: 400ms ease;
|
||||||
transition-property: opacity, transform;
|
transition-property: opacity, transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slide-down-enter-from,
|
.list-enter-from,
|
||||||
.slide-down-leave-to {
|
.list-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(40%);
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-leave-active {
|
||||||
|
position: absolute;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue"
|
import { type Component, computed, ref, watchEffect } from "vue"
|
||||||
import { trpcClient } from "./trpc"
|
import { trpcClient } from "./trpc"
|
||||||
import { useGame } from "./game"
|
import { useGame } from "./game"
|
||||||
import InteractionsScreen from "./screens/InteractionsScreen.vue"
|
import InteractionsScreen from "./screens/InteractionsScreen.vue"
|
||||||
import QueueScreen from "./screens/QueueScreen.vue"
|
import QueueScreen from "./screens/QueueScreen.vue"
|
||||||
import CornersOutIcon from "virtual:icons/ph/corners-out-bold"
|
import CornersOutIcon from "virtual:icons/ph/corners-out-bold"
|
||||||
import { useFullscreen } from "@vueuse/core"
|
import { useBrowserLocation, useFullscreen } from "@vueuse/core"
|
||||||
|
import DirectorScreen from "./screens/DirectorScreen.vue"
|
||||||
|
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const activeTab = ref<"interactions" | "queue">("interactions")
|
const activeScreenId = ref<string>("interactions")
|
||||||
const game = useGame()
|
|
||||||
|
|
||||||
|
const game = useGame()
|
||||||
const { isFullscreen, enter: enterFullscreen } = useFullscreen()
|
const { isFullscreen, enter: enterFullscreen } = useFullscreen()
|
||||||
|
const location = useBrowserLocation()
|
||||||
|
|
||||||
|
const isDirector = computed(() => location.value.hash === "#director")
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (isDirector.value) activeScreenId.value = "director"
|
||||||
|
else activeScreenId.value = "interactions"
|
||||||
|
})
|
||||||
|
|
||||||
|
interface Screen {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
component: Component
|
||||||
|
}
|
||||||
|
|
||||||
|
const screens = computed(() => {
|
||||||
|
const result: Screen[] = [
|
||||||
|
{
|
||||||
|
id: "interactions",
|
||||||
|
label: "Interagieren",
|
||||||
|
component: InteractionsScreen
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "queue",
|
||||||
|
label: "Abstimmen",
|
||||||
|
component: QueueScreen
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if (isDirector.value) result.push({
|
||||||
|
id: "director",
|
||||||
|
label: "Regie",
|
||||||
|
component: DirectorScreen
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
trpcClient.join.subscribe(undefined, {
|
trpcClient.join.subscribe(undefined, {
|
||||||
onStarted: () => {
|
onStarted: () => {
|
||||||
|
@ -116,7 +154,7 @@
|
||||||
console.error("🔴", error)
|
console.error("🔴", error)
|
||||||
},
|
},
|
||||||
onStopped() {
|
onStopped() {
|
||||||
location.reload()
|
window.location.reload()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<HandPointingIcon class="text-4xl mb-6"/>
|
<HandPointingIcon class="text-4xl mb-6"/>
|
||||||
<div class="flex flex-col items-center gap-2">
|
<div class="flex flex-col items-center gap-2">
|
||||||
<ObjectPicture :object-id="item.interaction.objectId"/>
|
<ObjectPicture :object-id="item.interaction.objectId"/>
|
||||||
<div class="text-sm text-gray-300 text-center">
|
<div class="text-sm text-gray-200 text-center">
|
||||||
{{ game.allObjectsById.get(item.interaction.objectId)!.label }}
|
{{ game.allObjectsById.get(item.interaction.objectId)!.label }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,27 +13,42 @@
|
||||||
<template v-else v-for="(objectId, index) in item.interaction.objectIds" :key="objectId">
|
<template v-else v-for="(objectId, index) in item.interaction.objectIds" :key="objectId">
|
||||||
<div class="flex flex-col items-center gap-2">
|
<div class="flex flex-col items-center gap-2">
|
||||||
<ObjectPicture :object-id="objectId"/>
|
<ObjectPicture :object-id="objectId"/>
|
||||||
<div class="text-sm text-gray-300 text-center">
|
<div class="text-sm text-gray-200 text-center">
|
||||||
{{ game.allObjectsById.get(objectId)!.label }}
|
{{ game.allObjectsById.get(objectId)!.label }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PlusIcon v-if="index < item.interaction.objectIds.size - 1" class="text-3xl mb-6"/>
|
<PlusIcon v-if="index < item.interaction.objectIds.size - 1" class="text-3xl mb-6"/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 justify-between items-center bg-gray-800 w-17">
|
<div class="flex flex-col justify-between items-center bg-gray-800 w-17">
|
||||||
<div class="flex flex-col justify-center items-center py-3">
|
<div class="flex flex-col justify-center items-center pt-3 pb-2">
|
||||||
<div class="text-2xl">{{ item.votes }}</div>
|
<div class="text-2xl">{{ item.votes }}</div>
|
||||||
<div class="text-sm text-center">
|
<div class="text-sm text-center">
|
||||||
Vote{{item.votes === 1 ? "" : "s" }}
|
Vote{{item.votes === 1 ? "" : "s" }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="align-end flex-grow py-2 w-full"
|
v-if="mode === 'audience'"
|
||||||
|
class="align-end py-2 w-full"
|
||||||
:class="game.currentInteractionId === item.id ? 'bg-blue-500' : 'bg-gray-700'"
|
:class="game.currentInteractionId === item.id ? 'bg-blue-500' : 'bg-gray-700'"
|
||||||
@click="toggleVote()"
|
@click="toggleVote()"
|
||||||
>
|
>
|
||||||
+1
|
+1
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="mode === 'director' && (item.interaction.type !== 'combine' || findMatchingCombination(game.currentRoom.combinations, item.interaction.objectIds))"
|
||||||
|
class="align-end py-1 w-full bg-green-800"
|
||||||
|
@click="game.activateInteractionQueueItem(item.id)"
|
||||||
|
>
|
||||||
|
<CheckIcon class="relative top-2px"/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="mode === 'director'"
|
||||||
|
class="align-end py-1 w-full bg-red-900"
|
||||||
|
@click="game.removeInteractionQueueItem(item.id)"
|
||||||
|
>
|
||||||
|
<TrashIcon class="relative top-2px"/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -44,13 +59,17 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import PlusIcon from "virtual:icons/ph/plus-bold"
|
import PlusIcon from "virtual:icons/ph/plus-bold"
|
||||||
|
import TrashIcon from "virtual:icons/ph/trash-bold"
|
||||||
|
import CheckIcon from "virtual:icons/ph/check-bold"
|
||||||
import HandPointingIcon from "virtual:icons/ph/hand-pointing-duotone"
|
import HandPointingIcon from "virtual:icons/ph/hand-pointing-duotone"
|
||||||
import type { InteractionQueueItem } from "../shared/script/types"
|
import type { InteractionQueueItem } from "../shared/script/types"
|
||||||
import ObjectPicture from "./ObjectPicture.vue"
|
import ObjectPicture from "./ObjectPicture.vue"
|
||||||
import { useGame } from "../game"
|
import { useGame } from "../game"
|
||||||
|
import { findMatchingCombination } from "../shared/util"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
item: InteractionQueueItem
|
item: InteractionQueueItem
|
||||||
|
mode: "audience" | "director"
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const game = useGame()
|
const game = useGame()
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
:data-object-id="object.id"
|
:data-object-id="object.id"
|
||||||
>
|
>
|
||||||
<ObjectPicture :object-id="object.id"/>
|
<ObjectPicture :object-id="object.id"/>
|
||||||
<div class="text-sm text-gray-300">
|
<div class="text-sm text-gray-200">
|
||||||
{{ object.label }}
|
{{ object.label }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<img :src="`/objects/${objectId}.png`" alt="" class="invert filter object-contain max-w-15"/>
|
<img :src="`/objects/${objectId}.png`" alt="" class="invert filter object-contain max-w-15 pointer-events-none" draggable="false"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style module lang="scss">
|
<style module lang="scss">
|
||||||
|
|
39
src/game.ts
39
src/game.ts
|
@ -7,27 +7,37 @@ import type { GameEvent } from "./shared/gameEvents"
|
||||||
import { getInteractionQueueItemId } from "./shared/util"
|
import { getInteractionQueueItemId } from "./shared/util"
|
||||||
|
|
||||||
export const useGame = defineStore("gameState", () => {
|
export const useGame = defineStore("gameState", () => {
|
||||||
|
const currentRoom = computed(() => script.roomsById.get(currentRoomId.value)!)
|
||||||
const currentRoomId = ref(script.roomsById.values().next().value!.id)
|
const currentRoomId = ref(script.roomsById.values().next().value!.id)
|
||||||
const interactionQueue = reactive(new Map<string, InteractionQueueItem>())
|
|
||||||
const currentInteraction = ref<null | Interaction>(null)
|
const currentInteraction = ref<null | Interaction>(null)
|
||||||
const currentInteractionId = computed(() =>
|
const currentInteractionId = computed(() =>
|
||||||
currentInteraction.value === null ? null : getInteractionQueueItemId(currentInteraction.value))
|
currentInteraction.value === null ? null : getInteractionQueueItemId(currentInteraction.value))
|
||||||
|
|
||||||
const sortedInteractionQueue = computed(() => [...interactionQueue.values()].sort((a, b) => b.votes - a.votes))
|
const interactionQueue = reactive(new Map<string, InteractionQueueItem>())
|
||||||
|
const sortedInteractionQueue = computed(() =>
|
||||||
|
[...interactionQueue.values()].sort((a, b) => b.votes - a.votes))
|
||||||
|
|
||||||
const currentRoom = computed(() => script.roomsById.get(currentRoomId.value)!)
|
const visibleObjectIds = reactive(new Set<string>())
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentRoomId,
|
currentRoomId,
|
||||||
interactionQueue,
|
|
||||||
sortedInteractionQueue,
|
|
||||||
currentRoom,
|
currentRoom,
|
||||||
|
interactionQueue,
|
||||||
|
visibleObjectIds,
|
||||||
|
sortedInteractionQueue,
|
||||||
allObjectsById: computed(() => {
|
allObjectsById: computed(() => {
|
||||||
const map = new Map<string, GameObject>()
|
const map = new Map<string, GameObject>()
|
||||||
currentRoom.value.initialObjects.forEach(o => map.set(o.id, o))
|
currentRoom.value.initialObjects.forEach(o => map.set(o.id, o))
|
||||||
currentRoom.value.hiddenObjects.forEach(o => map.set(o.id, o))
|
currentRoom.value.hiddenObjects.forEach(o => map.set(o.id, o))
|
||||||
return map
|
return map
|
||||||
}),
|
}),
|
||||||
|
visibleObjectsById: computed(() => {
|
||||||
|
const map = new Map<string, GameObject>()
|
||||||
|
currentRoom.value.initialObjects.values().filter(o => visibleObjectIds.has(o.id)).forEach(o => map.set(o.id, o))
|
||||||
|
currentRoom.value.hiddenObjects.values().filter(o => visibleObjectIds.has(o.id)).forEach(o => map.set(o.id, o))
|
||||||
|
return map
|
||||||
|
}),
|
||||||
currentInteraction,
|
currentInteraction,
|
||||||
currentInteractionId,
|
currentInteractionId,
|
||||||
voteForInteraction(interaction: Interaction) {
|
voteForInteraction(interaction: Interaction) {
|
||||||
|
@ -44,16 +54,24 @@ export const useGame = defineStore("gameState", () => {
|
||||||
switchRoom(roomId: string) {
|
switchRoom(roomId: string) {
|
||||||
trpcClient.director.switchRoom.mutate({ roomId })
|
trpcClient.director.switchRoom.mutate({ roomId })
|
||||||
},
|
},
|
||||||
|
activateInteractionQueueItem(id: string) {
|
||||||
|
trpcClient.director.activateInteractionQueueItem.mutate({ id })
|
||||||
|
},
|
||||||
removeInteractionQueueItem(id: string) {
|
removeInteractionQueueItem(id: string) {
|
||||||
trpcClient.director.removeInteractionQueueItem.mutate({ id })
|
trpcClient.director.removeInteractionQueueItem.mutate({ id })
|
||||||
},
|
},
|
||||||
|
setObjectVisibility(id: string, isVisible: boolean) {
|
||||||
|
trpcClient.director.setObjectVisibility.mutate({ id, isVisible })
|
||||||
|
},
|
||||||
handleGameEvent(event: GameEvent) {
|
handleGameEvent(event: GameEvent) {
|
||||||
console.log(event)
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case "room-changed":
|
case "room-changed":
|
||||||
interactionQueue.clear()
|
|
||||||
currentRoomId.value = event.roomId
|
|
||||||
currentInteraction.value = null
|
currentInteraction.value = null
|
||||||
|
currentRoomId.value = event.roomId
|
||||||
|
interactionQueue.clear()
|
||||||
|
|
||||||
|
visibleObjectIds.clear()
|
||||||
|
currentRoom.value.initialObjects.forEach(o => visibleObjectIds.add(o.id))
|
||||||
break
|
break
|
||||||
|
|
||||||
case "interaction-queued":
|
case "interaction-queued":
|
||||||
|
@ -69,6 +87,11 @@ export const useGame = defineStore("gameState", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case "object-visibility-changed":
|
||||||
|
if (event.isVisible) visibleObjectIds.add(event.id)
|
||||||
|
else visibleObjectIds.delete(event.id)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,9 @@ import "@interactjs/actions"
|
||||||
import "@interactjs/auto-start"
|
import "@interactjs/auto-start"
|
||||||
|
|
||||||
import { createApp } from "vue"
|
import { createApp } from "vue"
|
||||||
import { createRouter, createWebHistory } from "vue-router"
|
|
||||||
import App from "./App.vue"
|
import App from "./App.vue"
|
||||||
import routes from "virtual:generated-pages"
|
|
||||||
import { createPinia } from "pinia"
|
import { createPinia } from "pinia"
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
routes,
|
|
||||||
history: createWebHistory()
|
|
||||||
})
|
|
||||||
|
|
||||||
createApp(App)
|
createApp(App)
|
||||||
.use(router)
|
|
||||||
.use(createPinia())
|
.use(createPinia())
|
||||||
.mount("#app")
|
.mount("#app")
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
<template>
|
|
||||||
<
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style module lang="scss">
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
</script>
|
|
70
src/screens/DirectorScreen.vue
Normal file
70
src/screens/DirectorScreen.vue
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex gap-6 p-4">
|
||||||
|
<section class="w-80">
|
||||||
|
<div class="font-bold text-2xl pb-2">Raum</div>
|
||||||
|
<div class="flex flex-col overflow-y-auto bg-dark-900 bg-opacity-50 h-80vh">
|
||||||
|
<div
|
||||||
|
v-for="room in script.roomsById.values()"
|
||||||
|
:key="room.id"
|
||||||
|
class="flex-shrink-0 bg-dark-600 not-last:border-b border-solid border-dark-300 flex items-center"
|
||||||
|
>
|
||||||
|
<div class="px-3 py-2 flex-grow">
|
||||||
|
{{ room.label }}
|
||||||
|
</div>
|
||||||
|
<button v-if="game.currentRoomId === room.id" disabled class="bg-green-900 h-full px-3 text-sm cursor-not-allowed">
|
||||||
|
Aktiv
|
||||||
|
</button>
|
||||||
|
<button v-else class="bg-dark-300 h-full px-3 text-sm" @click="game.switchRoom(room.id)">
|
||||||
|
Aktivieren
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="flex-grow">
|
||||||
|
<div class="font-bold text-2xl pb-2">Versteckte Objekte</div>
|
||||||
|
<div class="grid gap-3 grid-cols-2 flex-grow auto-rows-min p-4 pt-0 relative">
|
||||||
|
<button
|
||||||
|
v-for="object in game.allObjectsById.values().filter(o => !game.visibleObjectIds.has(o.id))"
|
||||||
|
:key="object.id"
|
||||||
|
class="flex flex-col items-center gap-2 bg-dark-600 rounded-lg p-3"
|
||||||
|
@click="game.setObjectVisibility(object.id, true)"
|
||||||
|
>
|
||||||
|
<ObjectPicture :object-id="object.id"/>
|
||||||
|
<span class="text-sm text-gray-200 text-center">
|
||||||
|
{{ object.label }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="w-80">
|
||||||
|
<div class="font-bold text-2xl pb-2">Interaktionen</div>
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<div v-if="game.sortedInteractionQueue.length === 0" class="text-xl text-gray-200">
|
||||||
|
Keine Interaktionen vorhanden.
|
||||||
|
</div>
|
||||||
|
<transition-group v-else tag="div" name="list" class="flex flex-col gap-4 relative h-80vh">
|
||||||
|
<InteractionQueueItemCard
|
||||||
|
v-for="(item, index) in game.sortedInteractionQueue"
|
||||||
|
:key="item.id"
|
||||||
|
:style="{ zIndex: 1000 - index }"
|
||||||
|
:item="item"
|
||||||
|
mode="director"
|
||||||
|
/>
|
||||||
|
</transition-group>
|
||||||
|
</transition>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { script } from "../shared/script"
|
||||||
|
import { useGame } from "../game"
|
||||||
|
import ObjectPicture from "../components/ObjectPicture.vue"
|
||||||
|
import InteractionQueueItemCard from "../components/InteractionQueueItemCard.vue"
|
||||||
|
|
||||||
|
const game = useGame()
|
||||||
|
</script>
|
|
@ -16,9 +16,9 @@
|
||||||
@object-drop="onObjectInteractionDrop"
|
@object-drop="onObjectInteractionDrop"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div ref="objectsContainerElement" class="grid gap-3 grid-cols-2 flex-grow auto-rows-min p-4 pt-0">
|
<transition-group tag="div" name="list" ref="objectsContainerElement" class="grid gap-3 grid-cols-2 flex-grow auto-rows-min p-4 pt-0 relative">
|
||||||
<ObjectCard
|
<ObjectCard
|
||||||
v-for="object in game.currentRoom.initialObjects"
|
v-for="object in game.visibleObjectsById.values()"
|
||||||
:key="object.id"
|
:key="object.id"
|
||||||
:object="object"
|
:object="object"
|
||||||
:is-over-dropzone="allFloatingObjectIds.has(object.id)"
|
:is-over-dropzone="allFloatingObjectIds.has(object.id)"
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
@drag-start="onObjectDragStart"
|
@drag-start="onObjectDragStart"
|
||||||
@drag-end="onObjectDragEnd"
|
@drag-end="onObjectDragEnd"
|
||||||
/>
|
/>
|
||||||
</div>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,40 @@
|
||||||
<template>
|
<template>
|
||||||
<transition-group tag="div" name="list" class="h-full flex flex-col gap-4 p-4 pt-0 relative">
|
<div class="h-full p-4 pt-0">
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<div v-if="game.sortedInteractionQueue.length === 0" class="h-full flex flex-col justify-center items-center gap-4 p-6">
|
||||||
|
<div class="text-xl text-center text-gray-200">
|
||||||
|
Noch keine Interaktionen zum Abstimmen vorhanden.
|
||||||
|
</div>
|
||||||
|
<button class="flex items-center gap-2 px-4 py-2 rounded-lg bg-green-800 text-lg" @click="emit('switch-screen', 'interactions')">
|
||||||
|
<ArrowRightIcon/>
|
||||||
|
<span class="relative top-1px">Interagieren</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<transition-group v-else tag="div" name="list" class="flex flex-col gap-4 relative">
|
||||||
<InteractionQueueItemCard
|
<InteractionQueueItemCard
|
||||||
v-for="(item, index) in game.sortedInteractionQueue"
|
v-for="(item, index) in game.sortedInteractionQueue"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:item="item"
|
|
||||||
class="relative"
|
|
||||||
:style="{ zIndex: 1000 - index }"
|
:style="{ zIndex: 1000 - index }"
|
||||||
|
:item="item"
|
||||||
|
mode="audience"
|
||||||
/>
|
/>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.list-move,
|
|
||||||
.list-enter-active,
|
|
||||||
.list-leave-active {
|
|
||||||
transition: 400ms ease;
|
|
||||||
transition-property: opacity, transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-enter-from,
|
|
||||||
.list-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(-30px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-leave-active {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import InteractionQueueItemCard from "../components/InteractionQueueItemCard.vue"
|
import InteractionQueueItemCard from "../components/InteractionQueueItemCard.vue"
|
||||||
import { useGame } from "../game"
|
import { useGame } from "../game"
|
||||||
|
import ArrowRightIcon from "virtual:icons/ph/arrow-right"
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
"switch-screen": [string]
|
||||||
|
}>()
|
||||||
|
|
||||||
const game = useGame()
|
const game = useGame()
|
||||||
</script>
|
</script>
|
|
@ -2,7 +2,7 @@ import EventEmitter from "eventemitter3"
|
||||||
import type { GameEvent } from "../shared/gameEvents"
|
import type { GameEvent } from "../shared/gameEvents"
|
||||||
import type { Interaction, InteractionQueueItem } from "../shared/script/types"
|
import type { Interaction, InteractionQueueItem } from "../shared/script/types"
|
||||||
import { script } from "../shared/script"
|
import { script } from "../shared/script"
|
||||||
import { getInteractionQueueItemId } from "../shared/util"
|
import { findMatchingCombination, getInteractionQueueItemId } from "../shared/util"
|
||||||
|
|
||||||
interface Events {
|
interface Events {
|
||||||
"game-event": [GameEvent]
|
"game-event": [GameEvent]
|
||||||
|
@ -59,11 +59,42 @@ export class Game extends EventEmitter<Events> {
|
||||||
this.emit("game-event", { type: "room-changed", roomId })
|
this.emit("game-event", { type: "room-changed", roomId })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activateInteractionQueueItem(id: string) {
|
||||||
|
const item = this.interactionQueue.get(id)
|
||||||
|
if (item === undefined) return
|
||||||
|
this.interactionQueue.delete(id)
|
||||||
|
this.emit("game-event", { type: "interaction-votes-changed", id, votes: 0 })
|
||||||
|
switch (item.interaction.type) {
|
||||||
|
case "use":
|
||||||
|
this.emit("game-event", { type: "object-visibility-changed", id: item.interaction.objectId, isVisible: false })
|
||||||
|
break
|
||||||
|
|
||||||
|
case "combine":
|
||||||
|
const matchingCombination = findMatchingCombination(script.roomsById.get(this.currentRoomId)!.combinations, item.interaction.objectIds)
|
||||||
|
|
||||||
|
if (matchingCombination !== undefined) {
|
||||||
|
matchingCombination.inputs.forEach(input => {
|
||||||
|
if (input.isConsumed) this.emit("game-event", { type: "object-visibility-changed", id: input.objectId, isVisible: false })
|
||||||
|
})
|
||||||
|
|
||||||
|
matchingCombination.outputIds.forEach(outputId => {
|
||||||
|
this.emit("game-event", { type: "object-visibility-changed", id: outputId, isVisible: true })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
removeInteractionQueueItem(id: string) {
|
removeInteractionQueueItem(id: string) {
|
||||||
if (!this.interactionQueue.has(id)) return
|
if (!this.interactionQueue.has(id)) return
|
||||||
this.emit("game-event", { type: "interaction-votes-changed", id: id, votes: 0 })
|
this.emit("game-event", { type: "interaction-votes-changed", id, votes: 0 })
|
||||||
this.interactionQueue.delete(id)
|
this.interactionQueue.delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setObjectVisibility(id: string, isVisible: boolean) {
|
||||||
|
this.emit("game-event", { type: "object-visibility-changed", id, isVisible })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const game = new Game()
|
export const game = new Game()
|
|
@ -1,20 +1,38 @@
|
||||||
import { t } from "./base"
|
import { t } from "./base"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
import { game } from "../game"
|
||||||
|
|
||||||
export const directorRouter = t.router({
|
export const directorRouter = t.router({
|
||||||
switchRoom: t.procedure
|
switchRoom: t.procedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
roomId: z.string()
|
roomId: z.string()
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input }) => {
|
||||||
ctx.game.switchRoom(input.roomId)
|
game.switchRoom(input.roomId)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
removeInteractionQueueItem: t.procedure
|
removeInteractionQueueItem: t.procedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
id: z.string()
|
id: z.string()
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input }) => {
|
||||||
ctx.game.removeInteractionQueueItem(input.id)
|
game.removeInteractionQueueItem(input.id)
|
||||||
|
}),
|
||||||
|
|
||||||
|
activateInteractionQueueItem: t.procedure
|
||||||
|
.input(z.object({
|
||||||
|
id: z.string()
|
||||||
|
}))
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
game.activateInteractionQueueItem(input.id)
|
||||||
|
}),
|
||||||
|
|
||||||
|
setObjectVisibility: t.procedure
|
||||||
|
.input(z.object({
|
||||||
|
id: z.string(),
|
||||||
|
isVisible: z.boolean()
|
||||||
|
}))
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
game.setObjectVisibility(input.id, input.isVisible)
|
||||||
}),
|
}),
|
||||||
})
|
})
|
|
@ -4,3 +4,4 @@ export type GameEvent =
|
||||||
| { type: "room-changed"; roomId: string }
|
| { type: "room-changed"; roomId: string }
|
||||||
| { type: "interaction-queued"; item: InteractionQueueItem }
|
| { type: "interaction-queued"; item: InteractionQueueItem }
|
||||||
| { type: "interaction-votes-changed"; id: string; votes: number }
|
| { type: "interaction-votes-changed"; id: string; votes: number }
|
||||||
|
| { type: "object-visibility-changed"; id: string; isVisible: boolean }
|
|
@ -1,4 +1,5 @@
|
||||||
import type { Interaction } from "./script/types"
|
import type { Combination, Interaction } from "./script/types"
|
||||||
|
import { isEqual } from "lodash-es"
|
||||||
|
|
||||||
export const cSet = <T>(...values: T[]) => new Set(values)
|
export const cSet = <T>(...values: T[]) => new Set(values)
|
||||||
export const cMap = <K extends string | number | symbol, V>(object: Record<K, V>) => new Map(Object.entries(object))
|
export const cMap = <K extends string | number | symbol, V>(object: Record<K, V>) => new Map(Object.entries(object))
|
||||||
|
@ -10,3 +11,7 @@ export function getInteractionQueueItemId(interaction: Interaction) {
|
||||||
else throw new Error("Unknown interaction type")
|
else throw new Error("Unknown interaction type")
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findMatchingCombination(combinations: Set<Combination>, objectIds: Set<string>) {
|
||||||
|
return combinations.values().find(c => isEqual(new Set(c.inputs.values().map(i => i.objectId)), objectIds))
|
||||||
|
}
|
|
@ -1,14 +1,12 @@
|
||||||
import { defineConfig } from "vite"
|
import { defineConfig } from "vite"
|
||||||
import vuePlugin from "@vitejs/plugin-vue"
|
import vuePlugin from "@vitejs/plugin-vue"
|
||||||
import iconsPlugin from "unplugin-icons/vite"
|
import iconsPlugin from "unplugin-icons/vite"
|
||||||
import pagesPlugin from "vite-plugin-pages"
|
|
||||||
import windiPlugin from "vite-plugin-windicss"
|
import windiPlugin from "vite-plugin-windicss"
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vuePlugin(),
|
vuePlugin(),
|
||||||
iconsPlugin(),
|
iconsPlugin(),
|
||||||
pagesPlugin(),
|
|
||||||
windiPlugin()
|
windiPlugin()
|
||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
|
|
Loading…
Add table
Reference in a new issue