ed
24
resources/[freizeit]/[gym]/ps-ui/web/.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?
|
||||
48
resources/[freizeit]/[gym]/ps-ui/web/README.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# Svelte + TS + Vite
|
||||
|
||||
This template should help get you started developing with Svelte and TypeScript in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
|
||||
|
||||
## Need an official Svelte framework?
|
||||
|
||||
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
|
||||
|
||||
## Technical considerations
|
||||
|
||||
**Why use this over SvelteKit?**
|
||||
|
||||
- It brings its own routing solution which might not be preferable for some users.
|
||||
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
|
||||
`vite dev` and `vite build` wouldn't work in a SvelteKit environment, for example.
|
||||
|
||||
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
|
||||
|
||||
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
|
||||
|
||||
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
|
||||
|
||||
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
|
||||
|
||||
**Why include `.vscode/extensions.json`?**
|
||||
|
||||
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
|
||||
|
||||
**Why enable `allowJs` in the TS template?**
|
||||
|
||||
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
|
||||
|
||||
**Why is HMR not preserving my local component state?**
|
||||
|
||||
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
|
||||
|
||||
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
|
||||
|
||||
```ts
|
||||
// store.ts
|
||||
// An extremely simple external store
|
||||
import { writable } from 'svelte/store'
|
||||
export default writable(0)
|
||||
```
|
||||
BIN
resources/[freizeit]/[gym]/ps-ui/web/build/Hacker.ttf
Normal file
BIN
resources/[freizeit]/[gym]/ps-ui/web/build/Roboto-Regular.ttf
Normal file
BIN
resources/[freizeit]/[gym]/ps-ui/web/build/fa-brands-400.ttf
Normal file
BIN
resources/[freizeit]/[gym]/ps-ui/web/build/fa-brands-400.woff2
Normal file
BIN
resources/[freizeit]/[gym]/ps-ui/web/build/fa-regular-400.ttf
Normal file
BIN
resources/[freizeit]/[gym]/ps-ui/web/build/fa-regular-400.woff2
Normal file
BIN
resources/[freizeit]/[gym]/ps-ui/web/build/fa-solid-900.ttf
Normal file
BIN
resources/[freizeit]/[gym]/ps-ui/web/build/fa-solid-900.woff2
Normal file
BIN
resources/[freizeit]/[gym]/ps-ui/web/build/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
resources/[freizeit]/[gym]/ps-ui/web/build/images/ps-logo.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
5
resources/[freizeit]/[gym]/ps-ui/web/build/index.css
Normal file
1
resources/[freizeit]/[gym]/ps-ui/web/build/index.html
Normal file
73
resources/[freizeit]/[gym]/ps-ui/web/build/index.js
Normal file
BIN
resources/[freizeit]/[gym]/ps-ui/web/build/ka1.ttf
Normal file
1
resources/[freizeit]/[gym]/ps-ui/web/css/master.css
Normal file
|
|
@ -0,0 +1 @@
|
|||
@import './themes.css';
|
||||
16
resources/[freizeit]/[gym]/ps-ui/web/css/themes.css
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
:root {
|
||||
--color-green: #02f1b5;
|
||||
--color-palegreen: #039672;
|
||||
--color-darkblue: #131313;
|
||||
--color-blue: #0057ae;
|
||||
--color-lightgrey: #dadada;
|
||||
--color-white: #ffffff;
|
||||
--color-black: #000000;
|
||||
--color-darkgrey: #6a6a6a;
|
||||
--color-red: #e10404;
|
||||
|
||||
--cube-bg-darkgreen: #0f2a25;
|
||||
|
||||
font-size: 0.8em;
|
||||
font-family: 'roboto', sans-serif;
|
||||
}
|
||||
21
resources/[freizeit]/[gym]/ps-ui/web/index.html
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="./public/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css"
|
||||
integrity="sha512-SzlrxWUlpfuzQ+pcUCosxcglQRNAq/DZjVsC0lE40xsADsfeQoEypE+enwcOiGjk/bSuGGKHEyjSoQ1zVisanQ=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<title>PS-UI</title>
|
||||
<link rel="stylesheet" href="./css/master.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
3019
resources/[freizeit]/[gym]/ps-ui/web/package-lock.json
generated
Normal file
29
resources/[freizeit]/[gym]/ps-ui/web/package.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "ps-ui",
|
||||
"version": "2.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.1",
|
||||
"@tsconfig/svelte": "^5.0.2",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"postcss": "^8.4.33",
|
||||
"svelte": "^4.2.8",
|
||||
"svelte-check": "^3.6.2",
|
||||
"svelte-preprocess": "^5.1.3",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.11",
|
||||
"html-minifier": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||
"@mojs/core": "^1.7.1"
|
||||
}
|
||||
}
|
||||
1961
resources/[freizeit]/[gym]/ps-ui/web/pnpm-lock.yaml
generated
Normal file
7
resources/[freizeit]/[gym]/ps-ui/web/postcss.config.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import tailwind from 'tailwindcss';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import tailwindConfig from './tailwind.config.cjs';
|
||||
|
||||
export default {
|
||||
plugins: [tailwind(tailwindConfig), autoprefixer],
|
||||
}
|
||||
BIN
resources/[freizeit]/[gym]/ps-ui/web/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
resources/[freizeit]/[gym]/ps-ui/web/public/images/ps-logo.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
224
resources/[freizeit]/[gym]/ps-ui/web/src/App.svelte
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
<script lang="ts">
|
||||
// Svelte
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
// Stores
|
||||
import { isDevMode, showComponent } from './stores/GeneralStores';
|
||||
import { showUi } from './stores/GeneralStores';
|
||||
|
||||
// Enums
|
||||
import { UIComponentsEnum } from './enums/UIComponentsEnum';
|
||||
|
||||
// PS-UI components
|
||||
import GameLauncher from './games/GameLauncher.svelte';
|
||||
import { EventHandler, handleKeyUp } from './../utils/eventHandler';
|
||||
import Image from './components/Image.svelte';
|
||||
import { notificationMock } from './../utils/mockEvent';
|
||||
import Notification from './components/Notification.svelte';
|
||||
import { GamesEnum } from './enums/GamesEnum';
|
||||
import InputComponent from './components/InputComponent.svelte';
|
||||
import StatusBarComponent from './components/StatusBarComponent.svelte';
|
||||
import DrawTextComponent from './components/DrawTextComponent.svelte';
|
||||
import InteractionMenuComponent from './components/InteractionMenuComponent.svelte';
|
||||
import CircleGame from './games/CircleGame.svelte';
|
||||
|
||||
EventHandler();
|
||||
document.onkeyup = handleKeyUp;
|
||||
|
||||
if(isDevMode) {
|
||||
notificationMock();
|
||||
const memoryGameData = {
|
||||
game: GamesEnum.Memory,
|
||||
gameName: 'Memory MiniGame',
|
||||
gameDescription: 'Lorem ipsum is placceholder text commonly used in the arachic, print and publishing industries for previewing layouts and visual mockups.',
|
||||
amountOfAnswers: 9,
|
||||
gameTime: 30, // seconds
|
||||
maxAnswersIncorrect: 0,
|
||||
displayInitialAnswersFor: 5, //seconds
|
||||
gridSize: 10, //5,6,7,8,9,10 - one of these values as number of rows and columns
|
||||
triggerEvent: 'memorygame-callback',
|
||||
};
|
||||
// setupGame({ data: memoryGameData});
|
||||
|
||||
const scramblerGameData = {
|
||||
game: GamesEnum.Scrambler,
|
||||
gameName: 'Scrambler MiniGame',
|
||||
gameDescription: 'Lorem ipsum is placeholder text commonly used in the arachic, print and publishing industries for previewing layouts and visual mockups.',
|
||||
amountOfAnswers: 4, // count of numbers to display
|
||||
gameTime: 80, // seconds
|
||||
sets: 'numeric', // numeric, alphabet, alphanumeric, greek, braille, runes
|
||||
changeBoardAfter: 3 //seconds
|
||||
};
|
||||
// setupGame({ data: scramblerGameData});
|
||||
|
||||
const numberMazeGameData = {
|
||||
game: GamesEnum.NumberMaze,
|
||||
gameName: 'Number Maze MiniGame',
|
||||
gameDescription: 'Lorem ipsum is placeholder text commonly used in the arachic, print and publishing industries for previewing layouts and visual mockups.',
|
||||
gameTime: 30, // seconds
|
||||
maxAnswersIncorrect: 2,
|
||||
triggerEvent: 'maze-callback',
|
||||
};
|
||||
// setupGame({ data: numberMazeGameData});
|
||||
|
||||
const numberPuzzleGameData = {
|
||||
game: GamesEnum.NumberPuzzle,
|
||||
gameName: 'Number Puzzle MiniGame',
|
||||
gameDescription: 'Lorem ipsum is placeholder text commonly used in the arachic, print and publishing industries for previewing layouts and visual mockups.',
|
||||
gameTime: 3, // seconds
|
||||
maxAnswersIncorrect: 2,
|
||||
amountOfAnswers: 5,
|
||||
timeForNumberDisplay: 5, // seconds
|
||||
triggerEvent: 'var-callback',
|
||||
};
|
||||
// setupGame({ data: numberPuzzleGameData});
|
||||
|
||||
//input
|
||||
const inputData = [
|
||||
{
|
||||
id: '1',
|
||||
label: 'Name',
|
||||
icon: 'fa-solid fa-pen',
|
||||
placeholder: 'Insert name',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'Password',
|
||||
icon: 'fa-solid fa-lock',
|
||||
placeholder: 'Enter password',
|
||||
type: 'password',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
label: 'Phone',
|
||||
icon: 'fa-solid fa-phone',
|
||||
placeholder: 'Enter phone number',
|
||||
type: 'phone',
|
||||
},
|
||||
];
|
||||
|
||||
// showInput(inputData);
|
||||
|
||||
const statusBarData = {
|
||||
title: 'Area Dominance',
|
||||
description: 'Write some description here',
|
||||
icon: 'fa-solid fa-circle-info',
|
||||
items: [
|
||||
{
|
||||
key: 'Gang', value: 'Ballas'
|
||||
},
|
||||
{
|
||||
key: 'Dominance', value: '20%'
|
||||
}
|
||||
],
|
||||
};
|
||||
// showStatusBar(statusBarData);
|
||||
|
||||
// // double call for status bar
|
||||
// setTimeout(() => {
|
||||
// showStatusBar(
|
||||
// {
|
||||
// title: 'Area Check',
|
||||
// description: 'Whats up',
|
||||
// icon: 'fa-solid fa-heart',
|
||||
// items: [
|
||||
// {
|
||||
// key: 'Gang', value: 'Ace'
|
||||
// }
|
||||
// ],
|
||||
// }
|
||||
// )
|
||||
// }, 5000);
|
||||
|
||||
const drawTextData = {
|
||||
icon: 'fa-solid fa-circle-info',
|
||||
keys: 'Press [E] to interact',
|
||||
color: 'yellow'
|
||||
};
|
||||
// showDrawTextMenu(drawTextData);
|
||||
|
||||
// // double call for draw text
|
||||
// setTimeout(() => {
|
||||
// showDrawTextMenu(
|
||||
// {
|
||||
// icon: 'fa-solid fa-circle-info',
|
||||
// keys: 'Press [E] to interact and check if the old one exists',
|
||||
// color: ''
|
||||
// }
|
||||
// );
|
||||
// }, 5000);
|
||||
|
||||
const interactionMenuData = [
|
||||
{
|
||||
header: 'Menu item 1',
|
||||
text: 'Some text',
|
||||
icon: 'fa-solid fa-user',
|
||||
color: '#02f1b5',
|
||||
callback: '',
|
||||
subMenu: null,
|
||||
},
|
||||
{
|
||||
header: 'Menu item 2',
|
||||
icon: 'fa-solid fa-user',
|
||||
color: '',
|
||||
callback: '',
|
||||
subMenu: [
|
||||
{
|
||||
header: 'Submenu1',
|
||||
icon: 'fa-solid fa-circle-info',
|
||||
color: '#02f1b5',
|
||||
},
|
||||
{
|
||||
header: 'Submenu2',
|
||||
icon: 'fa-solid fa-circle-info',
|
||||
color: '#02f1b5',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
// setupInteractionMenu(interactionMenuData);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $showUi }
|
||||
<!-- class="min-h-screen min-w-full" -->
|
||||
<main transition:fade={{ duration: 100 }} class="main-bg">
|
||||
{#if $showComponent === UIComponentsEnum.StatusBar}
|
||||
<StatusBarComponent />
|
||||
{/if}
|
||||
|
||||
{#if $showComponent === UIComponentsEnum.DrawText}
|
||||
<DrawTextComponent />
|
||||
{/if}
|
||||
|
||||
{#if $showComponent === UIComponentsEnum.Menu}
|
||||
<InteractionMenuComponent />
|
||||
{/if}
|
||||
|
||||
{#if $showComponent === UIComponentsEnum.Input}
|
||||
<InputComponent />
|
||||
{/if}
|
||||
|
||||
{#if $showComponent === UIComponentsEnum.Game}
|
||||
<GameLauncher />
|
||||
{/if}
|
||||
|
||||
{#if $showComponent === UIComponentsEnum.Image}
|
||||
<Image />
|
||||
{/if}
|
||||
|
||||
{#if $showComponent === UIComponentsEnum.Notification}
|
||||
<Notification />
|
||||
{/if}
|
||||
<CircleGame />
|
||||
</main>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.main-bg {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
BIN
resources/[freizeit]/[gym]/ps-ui/web/src/assets/svelte.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
|
|
@ -0,0 +1,40 @@
|
|||
<script lang="ts">
|
||||
export let color: string = '';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 437.3 512"
|
||||
style="enable-background:new 0 0 437.3 512;"
|
||||
xml:space="preserve"
|
||||
fill={color}
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d="M230.3,455.7c-8.3,0-16.6,0-25,0c-8.5-3.1-17.2-5.7-25.4-9.4c-21.9-9.8-42.1-22.2-58.1-40.4c-10-11.3-16.2-24.2-15.6-39.9
|
||||
c0.5-13.5,0.1-27,0.1-40.5c0-2-0.2-3.2-2.5-4.1c-24.3-9.4-38-29.4-38.1-55.6c-0.1-24.7-0.3-49.4,0.1-74.1c0.2-9.7,0.9-19.6,3.2-29
|
||||
c12.1-47.6,42.8-78.7,87.9-96.4c21.1-8.3,43.3-10.3,65.8-10c33.5,0.4,64.5,8.9,91.8,28.7c35.8,26,56,60.8,56.8,105.6
|
||||
c0.5,25.3,0.2,50.7,0,76c-0.2,25.5-14.4,45.8-37.8,54.6c-2.4,0.9-2.8,2.1-2.8,4.3c0.1,14.3-0.1,28.6,0.1,42.9
|
||||
c0.2,11.1-3.6,20.9-9.8,29.8c-10,14.4-23.5,24.8-38.1,34C266.6,442.5,249.2,450.7,230.3,455.7z M165.8,219.1
|
||||
c-4.7,0.4-9.3,0.5-14,1.1c-12.7,1.5-22,8-26.3,20.1c-4.1,11.4-2.3,22.9,2.4,33.7c3.9,8.9,13.3,13.7,22.6,10.8c9-2.8,17.7-7.2,26-12
|
||||
c8.6-5,15.2-12.4,19.6-21.6c5.8-12,2.3-22.3-9.5-28.3C180,219.5,173,218.9,165.8,219.1z M274.1,218.4c-5.8,1-11.9,1.4-17.5,3.2
|
||||
c-14.4,4.7-19.2,16.8-12.3,30.2c1.7,3.3,3.8,6.5,6.2,9.4c9.1,11.3,21.8,17.4,34.8,22.6c11.9,4.8,24.3-0.9,28.1-13.1
|
||||
c1.9-6.2,2.5-13,2.9-19.5c0.7-12.6-7.2-24.7-19.2-28.7C289.9,220.2,282,219.7,274.1,218.4z M212.6,371.3c-0.5-6.4-2.3-9-6.2-9.2
|
||||
c-3.9-0.2-5.8,2.4-6.9,8.9c-4,0-7.9,0-12.1,0c0.3-4.2-0.8-7.4-4.8-8.6c-4.1-1.2-6.5,1.3-8.5,4.6c-3.7-2-5.4-4.9-5.4-8.9
|
||||
c0-5.8,0-11.7,0-17.5c0-6.9-0.8-8.1-7.2-10.2c-9.7-3.3-19.4-6.5-29.2-9.7c-1.3-0.4-2.7-0.9-4.1-1c-3-0.2-5.1,1.3-6,4
|
||||
c-1,2.7-0.2,5.2,2.2,6.9c1.2,0.9,2.8,1.4,4.2,1.9c8.4,2.8,16.7,5.7,25.1,8.4c2,0.6,2.6,1.6,2.6,3.6c-0.1,4.7,0,9.4,0,14
|
||||
c0,7,2.8,12.6,8.2,16.9c8.1,6.6,17.7,8.5,27.7,8.5c17,0.1,34.1,0.3,51.1-0.1c6.8-0.2,13.7-1.2,20.3-3c9.4-2.7,15.9-8.8,16.8-19.2
|
||||
c0.5-5.4,0.7-10.9,0.6-16.3c0-2.6,0.7-3.9,3.3-4.7c8.7-2.7,17.2-5.7,25.8-8.8c4.8-1.7,6.3-6.8,3.1-10.2c-2.4-2.6-5.3-2.2-8.3-1.2
|
||||
c-10.3,3.5-20.6,7-31,10.4c-3.8,1.2-5.4,3.6-5.3,7.5c0.1,6,0.2,12,0,17.9c-0.1,4.8-1.2,9.3-6.5,11.3c-1.1-3.4-3.1-5.9-6.7-5.4
|
||||
c-5.1,0.6-5.4,4.8-5.7,9c-4.2,0-8.1,0-12.1,0c-0.5-6.3-2.3-8.9-6.2-9.1c-3.8-0.2-5.8,2.4-6.9,9.2
|
||||
C220.6,371.3,216.7,371.3,212.6,371.3z M218,331C218,331,218,331,218,331c3.9,0,7.8,0.2,11.7-0.1c5.6-0.3,8.7-3,9.4-8.6
|
||||
c0.4-3.4,0.3-7-0.1-10.5c-1.6-11.9-4.4-23.5-11.1-33.7c-5.3-8.2-13.3-8.9-18.6-1.1c-9,13.3-12.5,28.4-12.3,44.3
|
||||
c0.1,5.9,3.9,9.2,9.8,9.5C210.4,331.2,214.2,331,218,331z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 570 B |
|
After Width: | Height: | Size: 858 B |
|
|
@ -0,0 +1,229 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import Icon from "./Icon.svelte";
|
||||
import { isDevMode, showComponent, showUi } from "../stores/GeneralStores";
|
||||
import { drawTextStore, hideDrawTextMenu, hideDrawTextStore } from "../stores/DrawTextStore";
|
||||
|
||||
let drawTextDataValue:any = {};
|
||||
drawTextStore.subscribe((value) => drawTextDataValue = value);
|
||||
|
||||
let hideDrawTextValue = false;
|
||||
hideDrawTextStore.subscribe(value => {
|
||||
hideDrawTextValue = value;
|
||||
});
|
||||
|
||||
let marginLeftVal = '2%';
|
||||
let interactionText = [], colorValue = '';
|
||||
|
||||
onMount(() => {
|
||||
getColorValue();
|
||||
handleInteractionText();
|
||||
|
||||
if(isDevMode) {
|
||||
setTimeout(() => {
|
||||
hideDrawTextMenu();
|
||||
}, 10000);
|
||||
}
|
||||
});
|
||||
|
||||
function getColorValue() {
|
||||
switch(drawTextDataValue.color) {
|
||||
case "primary":
|
||||
colorValue ="#0275d8";
|
||||
break;
|
||||
case "error":
|
||||
colorValue = "#d9534f";
|
||||
break;
|
||||
case "success":
|
||||
colorValue = "#5cb85c";
|
||||
break;
|
||||
case "warning":
|
||||
colorValue = "#f0ad4e";
|
||||
break;
|
||||
case "info":
|
||||
colorValue = "#5bc0de";
|
||||
break;
|
||||
case "mint":
|
||||
colorValue = "#a1f8c7";
|
||||
break;
|
||||
default:
|
||||
colorValue = "var(--color-green)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function handleInteractionText() {
|
||||
let matches = drawTextDataValue.keys.match(/\[(.*?)\]/);
|
||||
if (matches) {
|
||||
let submatch = matches[0];
|
||||
|
||||
let splitText = drawTextDataValue.keys.split(submatch);
|
||||
interactionText = [
|
||||
{
|
||||
value: splitText[0],
|
||||
color: 'var(--color-lightgrey)'
|
||||
},
|
||||
{
|
||||
value: submatch,
|
||||
color: colorValue
|
||||
},
|
||||
{
|
||||
value: splitText[1],
|
||||
color: 'var(--color-lightgrey)'
|
||||
}
|
||||
];
|
||||
} else {
|
||||
interactionText = [
|
||||
{
|
||||
value: drawTextDataValue.keys,
|
||||
color: 'var(--color-lightgrey)'
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawText() {
|
||||
const statusWrapperDom = document.getElementById('draw-text-wrapper');
|
||||
if(statusWrapperDom) {
|
||||
statusWrapperDom.style.animation = '2s slide-left';
|
||||
|
||||
let keyFrames = document.createElement('style');
|
||||
|
||||
keyFrames.innerHTML = `
|
||||
@keyframes slide-left {
|
||||
from {
|
||||
margin-left: `+marginLeftVal+`;
|
||||
}
|
||||
to {
|
||||
margin-left: -20%;
|
||||
}
|
||||
}
|
||||
|
||||
.draw-text-wrapper {
|
||||
-moz-animation: 2s slide-left;
|
||||
-webkit-animation: 2s slide-left;
|
||||
animation: 2s slide-left;
|
||||
}
|
||||
`;
|
||||
|
||||
statusWrapperDom.appendChild(keyFrames);
|
||||
|
||||
setTimeout(() => {
|
||||
showUi.set(false);
|
||||
showComponent.set(null);
|
||||
|
||||
drawTextStore.set({
|
||||
// title: '',
|
||||
icon: '',
|
||||
keys: '',
|
||||
color: ''
|
||||
});
|
||||
hideDrawTextStore.set(false);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if(hideDrawTextValue) {
|
||||
closeDrawText();
|
||||
}
|
||||
|
||||
if(drawTextDataValue) {
|
||||
handleInteractionText();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="draw-text-wrapper" class="draw-text-wrapper" style="margin-left:{marginLeftVal};">
|
||||
<div class="draw-text-title-wrapper">
|
||||
<div class="icon">
|
||||
<Icon icon={drawTextDataValue.icon} styleColor={colorValue} />
|
||||
</div>
|
||||
|
||||
<div class="title-info">
|
||||
<p class="title-description">
|
||||
{#each interactionText as text}
|
||||
<p style="color: {text.color};">{text.value}</p>
|
||||
{/each}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.draw-text-wrapper {
|
||||
-moz-animation: 1s slide-right;
|
||||
-webkit-animation: 1s slide-right;
|
||||
animation: 1s slide-right;
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
|
||||
min-width: 10vw;
|
||||
width: max-content;
|
||||
|
||||
min-height: 3vw;
|
||||
height: fit-content;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
background-color: var(--color-darkblue);
|
||||
border-radius: 0.3vw;
|
||||
|
||||
padding: 0.75vw 1.5vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@keyframes slide-right {
|
||||
from {
|
||||
margin-left: -20%;
|
||||
}
|
||||
to {
|
||||
margin-left: 2%;
|
||||
}
|
||||
}
|
||||
@keyframes slide-left {
|
||||
from {
|
||||
margin-left: 2%;
|
||||
}
|
||||
to {
|
||||
margin-left: -100%;
|
||||
}
|
||||
}
|
||||
|
||||
.draw-text-wrapper > .draw-text-title-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.draw-text-wrapper > .draw-text-title-wrapper > .icon {
|
||||
width: 1.5vw;
|
||||
margin: auto 0.75vw auto 0;
|
||||
|
||||
font-size: 1.25vw;
|
||||
}
|
||||
.draw-text-wrapper > .draw-text-title-wrapper > .title-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.draw-text-wrapper > .draw-text-title-wrapper > .title-info > .title-description {
|
||||
font-size: 1vw;
|
||||
font-weight: 300;
|
||||
color: var(--color-lightgrey);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
word-wrap: break-word;
|
||||
flex-wrap: wrap;
|
||||
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
.draw-text-wrapper > .draw-text-title-wrapper > .title-info > .title-description > p {
|
||||
margin-right: 0.25vw;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<script lang="ts">
|
||||
export let icon: string;
|
||||
export let color: string = '';
|
||||
export let classes: string = '';
|
||||
export let styleColor: string = '';
|
||||
</script>
|
||||
|
||||
<i class="{color} {icon} {classes}" style="color: {styleColor};" />
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { imageStore } from './../stores/ImageStore';
|
||||
import { fly } from 'svelte/transition';
|
||||
</script>
|
||||
|
||||
{#if $imageStore.show}
|
||||
<div class="flex items-center justify-center min-h-screen" transition:fly="{{y: +400}}">
|
||||
<div class="flex items-center flex-col ps-bg-darkblue p-10 shadow-md shadow-gray-800 rounded-md">
|
||||
<!-- svelte-ignore a11y-img-redundant-alt -->
|
||||
<img src={$imageStore.url} alt="Image Placeholder" />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
<script lang="ts">
|
||||
import { hideUi, isDevMode } from "../stores/GeneralStores";
|
||||
import { inputStore } from "../stores/InputStores";
|
||||
import Icon from "./Icon.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import fetchNui from "../../utils/fetch";
|
||||
import { handleKeyUp } from "../../utils/eventHandler";
|
||||
|
||||
document.onkeyup = handleKeyUp;
|
||||
let inputData:any = $inputStore;
|
||||
|
||||
onMount(() => {
|
||||
inputData = inputData.map((val) => {
|
||||
val.value = null;
|
||||
return val;
|
||||
});
|
||||
});
|
||||
|
||||
function submit() {
|
||||
let returnData = [];
|
||||
|
||||
let inputs = document.querySelectorAll('input');
|
||||
inputs.forEach((input: HTMLInputElement, index) => {
|
||||
let returnObj = {
|
||||
id: input.id,
|
||||
value: input.value,
|
||||
// compIndex: index
|
||||
};
|
||||
|
||||
returnData.push(returnObj)
|
||||
});
|
||||
|
||||
if(!isDevMode) {
|
||||
fetchNui('input-callback', returnData);
|
||||
}
|
||||
|
||||
closeInputs();
|
||||
}
|
||||
|
||||
export function closeInputs(): void {
|
||||
let inputs = document.querySelectorAll('input');
|
||||
inputs.forEach((input: HTMLInputElement) => {
|
||||
input.value = '';
|
||||
});
|
||||
|
||||
inputData = [];
|
||||
|
||||
if(!isDevMode) {
|
||||
fetchNui('input-close', { ok: true });
|
||||
}
|
||||
|
||||
hideUi();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="input-base-wrapper">
|
||||
<div class="logo">
|
||||
<img src="./images/ps-logo.png" alt="ps-logo" />
|
||||
</div>
|
||||
|
||||
<div class="input-form">
|
||||
{#each inputData as inputValue}
|
||||
<div class="input-wrapper">
|
||||
<div class="input-data-wrapper">
|
||||
<div class="input-icon">
|
||||
<Icon icon={inputValue.icon} color="ps-text-green" classes="text-2xl" />
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<p class="label">
|
||||
{inputValue.label}
|
||||
</p>
|
||||
<input id={inputValue.id} type={inputValue.type} class="value" placeholder={inputValue.placeholder} value={inputValue.value} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="horizontal-line"></div>
|
||||
<!-- <div class="input-message">
|
||||
{inputValue.placeholder}
|
||||
</div> -->
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="button-wrapper">
|
||||
<button class="submit-btn" on:click={submit}>Submit</button>
|
||||
<button class="cancel-btn" on:click={closeInputs}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.input-base-wrapper {
|
||||
/* centering the view */
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
width: 25vw;
|
||||
min-height: 28vw;
|
||||
height: fit-content;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
background-color: var(--color-darkblue);
|
||||
border-radius: 0.3vw;
|
||||
|
||||
padding: 0.5vw 2vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.input-base-wrapper > .input-form {
|
||||
margin-top: 1vw;
|
||||
height: 100%;
|
||||
|
||||
/* border: 0.1px solid red; */
|
||||
}
|
||||
|
||||
.input-base-wrapper > .input-form > .input-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.input-base-wrapper > .input-form > .input-wrapper > .horizontal-line {
|
||||
width: inherit;
|
||||
height: 1px;
|
||||
background-color: var(--color-lightgrey);
|
||||
}
|
||||
.input-base-wrapper > .input-form > .input-wrapper > .input-message {
|
||||
font-size: 0.8vw;
|
||||
opacity: 0.85;
|
||||
color: var(--color-lightgrey);
|
||||
margin-top: 0.2vw;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.input-base-wrapper > .input-form > .input-wrapper > .input-data-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
padding: 0.5vw 0;
|
||||
}
|
||||
.input-base-wrapper > .input-form > .input-wrapper:not(:last-child) {
|
||||
margin-bottom: 1vw;
|
||||
}
|
||||
|
||||
.input-base-wrapper > .input-form > .input-wrapper > .input-data-wrapper > .input-icon {
|
||||
margin-right: 0.75vw;
|
||||
width: 1.5vw;
|
||||
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
.input-base-wrapper > .input-form > .input-wrapper > .input-data-wrapper > .input-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
color: var(--color-lightgrey);
|
||||
}
|
||||
.input-base-wrapper > .input-form > .input-wrapper > .input-data-wrapper > .input-area > .label {
|
||||
font-size: 1vw;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
.input-base-wrapper > .input-form > .input-wrapper > .input-data-wrapper > .input-area > .value {
|
||||
font-size: 0.8vw;
|
||||
font-weight: 300 !important;
|
||||
width: 18vw;
|
||||
background-color: inherit;
|
||||
margin-top: 0.2vw;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
padding-left: 0;
|
||||
outline: none;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: var(--color-green);
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.input-base-wrapper > .button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
|
||||
margin: 1.5vw auto 1.25vw auto;
|
||||
color: var(--color-black);
|
||||
}
|
||||
.input-base-wrapper > .button-wrapper > .submit-btn {
|
||||
background-color: var(--color-green);
|
||||
}
|
||||
.input-base-wrapper > .button-wrapper > .cancel-btn {
|
||||
background-color: var(--color-darkgrey);
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0.3vw;
|
||||
padding: 0.3vw 1vw;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
button:not(:last-child) {
|
||||
margin-right: 0.5vw;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
<script lang="ts">
|
||||
import { closeInteractionMenu, menuStore } from "../stores/MenuStores";
|
||||
import Icon from "./Icon.svelte";
|
||||
import fetchNui from "../../utils/fetch";
|
||||
import { isDevMode } from "../stores/GeneralStores";
|
||||
|
||||
let menuData:Array<any> = $menuStore;
|
||||
let selectedMenuItem = null;
|
||||
let subMenu = null;
|
||||
|
||||
let subMenuTextColorOverride = {
|
||||
id: null, color: 'black'
|
||||
};
|
||||
let menuTextColorOverride = {
|
||||
id: null, color: 'black'
|
||||
};
|
||||
|
||||
function handleMenuSelection(selectedMenu) {
|
||||
selectedMenuItem = selectedMenu;
|
||||
|
||||
if(selectedMenu) {
|
||||
|
||||
if(!selectedMenu.subMenu && !isDevMode) {
|
||||
fetchNui('MenuSelect', {data :selectedMenu});
|
||||
closeInteractionMenu();
|
||||
} else {
|
||||
subMenu = selectedMenuItem.subMenu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubMenuSelection(selectedSubMenu) {
|
||||
const formData = {
|
||||
data:selectedSubMenu
|
||||
};
|
||||
if(!isDevMode) {
|
||||
fetchNui('MenuSelect', formData);
|
||||
closeInteractionMenu();
|
||||
}
|
||||
}
|
||||
|
||||
function handleItemHover(itemId, index, color, action='enter', isSubMenu = false) {
|
||||
const itemDom = document.getElementById(itemId);
|
||||
if(action === 'enter') {
|
||||
if(isSubMenu) {
|
||||
itemDom.style.backgroundColor = color || 'var(--color-green)';
|
||||
subMenuTextColorOverride.id = index;
|
||||
} else {
|
||||
itemDom.style.backgroundColor = color || 'var(--color-green)';
|
||||
menuTextColorOverride.id = index;
|
||||
}
|
||||
} else {
|
||||
itemDom.style.backgroundColor = 'var(--color-darkblue)';
|
||||
subMenuTextColorOverride.id = null;
|
||||
menuTextColorOverride.id = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="menu-base-wrapper">
|
||||
<div class="screen-base">
|
||||
{#if !selectedMenuItem}
|
||||
<div class="header-slot" style="border: 3px solid var(--color-green);">
|
||||
<img src="./images/ps-logo.png" alt="ps-logo" />
|
||||
</div>
|
||||
|
||||
<div class="screen-body">
|
||||
{#each menuData as menu, index}
|
||||
<div id={"menu-"+index} class="each-panel"
|
||||
on:mouseenter={() => handleItemHover("menu-"+index, index, menu.color, 'enter', false)}
|
||||
on:mouseleave={() => handleItemHover("menu-"+index, index, menu.color, 'leave', false)}
|
||||
on:click={() => handleMenuSelection(menu)}>
|
||||
<div class="menu-icon">
|
||||
<Icon icon={menu.icon} styleColor={menuTextColorOverride.id === index ? menuTextColorOverride.color : menu?.color || 'var(--color-green)'} />
|
||||
</div>
|
||||
<div class="menu-details">
|
||||
<p class="header" style="color: {menuTextColorOverride.id === index ? menuTextColorOverride.color : 'var(--color-white)'};">{menu.header}</p>
|
||||
{#if menu.hasOwnProperty('text')}
|
||||
<p class="text" style="color: {menuTextColorOverride.id === index ? menuTextColorOverride.color : 'var(--color-lightgrey)'};">{menu.text}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if menu?.subMenu}
|
||||
<div class="chevron" style="color: {menuTextColorOverride.id === index ? menuTextColorOverride.color : 'var(--color-white)'};">
|
||||
<i class="fa-solid fa-chevron-right"></i>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if selectedMenuItem.hasOwnProperty('subMenu') && selectedMenuItem.subMenu}
|
||||
<div class="submenu-header-slot" on:click={() => handleMenuSelection(null)}>
|
||||
<i class="fa-solid fa-chevron-left left-chevron"></i>
|
||||
<p class="main-menu">Main Menu</p>
|
||||
</div>
|
||||
|
||||
<div class="screen-body">
|
||||
{#each subMenu as menu, index}
|
||||
<div id={"sub-menu-"+index} class="each-panel"
|
||||
on:mouseenter={() => handleItemHover("sub-menu-"+index, index, menu.color, 'enter', true)}
|
||||
on:mouseleave={() => handleItemHover("sub-menu-"+index, index, menu.color, 'leave', true)}
|
||||
on:click={() => handleSubMenuSelection(menu)}>
|
||||
<div id={"menu-icon-"+index} class="menu-icon">
|
||||
<Icon icon={menu.icon} styleColor={subMenuTextColorOverride.id === index ? subMenuTextColorOverride.color : menu.color || 'var(--color-green)'} />
|
||||
</div>
|
||||
<div class="menu-details">
|
||||
<p class="header" style="color: {subMenuTextColorOverride.id === index ? subMenuTextColorOverride.color : 'var(--color-white)'};">{menu.header}</p>
|
||||
{#if menu.hasOwnProperty('text')}
|
||||
<p class="text" style="color: {subMenuTextColorOverride.id === index ? subMenuTextColorOverride.color : 'var(--color-lightgrey)'};">{menu.text}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.menu-base-wrapper {
|
||||
/* centering the view */
|
||||
position: absolute;
|
||||
left: 70%;
|
||||
top: 40%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
width: 20vw;
|
||||
height: 30vw;
|
||||
/* min-height: 4vw;
|
||||
height: fit-content; */
|
||||
|
||||
overflow: hidden;
|
||||
/* border: 0.1px solid red; */
|
||||
}
|
||||
|
||||
.menu-base-wrapper > .screen-base {
|
||||
height: 100%;
|
||||
/* border: 0.1px solid yellow; */
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.menu-base-wrapper > .screen-base > .header-slot {
|
||||
background-color: var(--color-darkblue);
|
||||
border-radius: 0.3vw;
|
||||
|
||||
padding: 0.5vw 2vw;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
min-height: 3vw;
|
||||
height: fit-content;
|
||||
}
|
||||
.menu-base-wrapper > .screen-base > .header-slot > img {
|
||||
height: 4vw;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.menu-base-wrapper > .screen-base > .submenu-header-slot {
|
||||
min-height: 4vw;
|
||||
height: max-content;
|
||||
|
||||
background-color: var(--color-darkblue);
|
||||
border-radius: 0.3vw;
|
||||
color: var(--color-white);
|
||||
|
||||
padding: 0.3vw 1vw;
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.menu-base-wrapper > .screen-base > .submenu-header-slot > .left-chevron {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-right: 0.5vw;
|
||||
|
||||
width: 1.5vw;
|
||||
|
||||
font-size: 1.1vw;
|
||||
}
|
||||
.menu-base-wrapper > .screen-base > .submenu-header-slot > .main-menu {
|
||||
font-weight: 500;
|
||||
font-size: 1.25vw;
|
||||
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.menu-base-wrapper > .screen-base > .screen-body {
|
||||
margin-top: 0.3vw;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* border: 0.1px solid blue; */
|
||||
}
|
||||
|
||||
.menu-base-wrapper > .screen-base > .screen-body > .each-panel {
|
||||
min-height: 4vw;
|
||||
height: max-content;
|
||||
|
||||
background-color: var(--color-darkblue);
|
||||
border-radius: 0.3vw;
|
||||
|
||||
padding: 0.5vw 1vw;
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.menu-base-wrapper > .screen-base > .screen-body > .each-panel:not(:last-child) {
|
||||
margin-bottom: 0.3vw;
|
||||
}
|
||||
|
||||
.menu-base-wrapper > .screen-base > .screen-body > .each-panel > .menu-icon {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-right: 0.5vw;
|
||||
|
||||
width: 1.5vw;
|
||||
|
||||
font-size: 1vw;
|
||||
}
|
||||
|
||||
.menu-base-wrapper > .screen-base > .screen-body > .each-panel > .menu-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.menu-base-wrapper > .screen-base > .screen-body > .each-panel > .menu-details > .header {
|
||||
font-size: 0.8vw;
|
||||
white-space: nowrap;
|
||||
/* color: var(--color-white); */
|
||||
}
|
||||
|
||||
.menu-base-wrapper > .screen-base > .screen-body > .each-panel > .menu-details > .text {
|
||||
font-size: 0.6vw;
|
||||
white-space: nowrap;
|
||||
/* color: var(--color-lightgrey); */
|
||||
}
|
||||
|
||||
.menu-base-wrapper > .screen-base > .screen-body > .each-panel > .chevron {
|
||||
margin-left: 72%;
|
||||
font-size: 1vw;
|
||||
/* color: var(--color-white); */
|
||||
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<script lang="ts">
|
||||
import { notifications } from './../stores/NotificationStore';
|
||||
import { NotificationIcons, NotificationTypes } from './../enums/NotificationTypesEnum';
|
||||
</script>
|
||||
|
||||
<div class="flex justify-end min-h-screen">
|
||||
<div class="flex flex-col mr-4 mt-4 w-[200px]">
|
||||
{#each $notifications as notification}
|
||||
<div
|
||||
class="{notification.type} flex items-center px-4 py-3 mb-1 text-white min-w-full rounded"
|
||||
>
|
||||
<div class="text-2xl mt-1">
|
||||
{#if notification.type === NotificationTypes.Success}
|
||||
<i class={NotificationIcons.Success} />
|
||||
{:else if notification.type === NotificationTypes.Warning}
|
||||
<i class={NotificationIcons.Warning} />
|
||||
{:else if notification.type === NotificationTypes.Error}
|
||||
<i class={NotificationIcons.Error} />
|
||||
{:else if notification.type === NotificationTypes.Info}
|
||||
<i class={NotificationIcons.Info} />
|
||||
{/if}
|
||||
</div>
|
||||
<span class="ml-3">{notification.text}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
<script lang="ts">
|
||||
import { hideStatusBar, hideStatusBarStore, statusBarStore } from "../stores/StatusBarStores";
|
||||
import { onMount } from "svelte";
|
||||
import Icon from "./Icon.svelte";
|
||||
import type { IStatusBarItem } from "src/interfaces/IStatusBar";
|
||||
import { isDevMode, showComponent, showUi } from "../stores/GeneralStores";
|
||||
|
||||
let statusData:any = $statusBarStore;
|
||||
let statusDataItems:[IStatusBarItem] = statusData.items;
|
||||
|
||||
statusBarStore.subscribe((value) => {
|
||||
statusData = value;
|
||||
statusDataItems = statusData.items;
|
||||
});
|
||||
|
||||
let hideStatusBarValue = false;
|
||||
hideStatusBarStore.subscribe(value => {
|
||||
hideStatusBarValue = value;
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
if(isDevMode) {
|
||||
setTimeout(() => {
|
||||
hideStatusBar();
|
||||
}, 10000);
|
||||
}
|
||||
});
|
||||
|
||||
function closeStatusBar() {
|
||||
const statusWrapperDom = document.getElementById('status-bar-wrapper');
|
||||
if(statusWrapperDom) {
|
||||
statusWrapperDom.style.animation = '2s hide-statusbar';
|
||||
|
||||
let keyFrames = document.createElement('style');
|
||||
|
||||
keyFrames.innerHTML = `
|
||||
@keyframes hide-statusbar {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.status-bar-wrapper {
|
||||
-moz-animation: 2s hide-statusbar;
|
||||
-webkit-animation: 2s hide-statusbar;
|
||||
animation: 2s hide-statusbar;
|
||||
}
|
||||
`;
|
||||
|
||||
statusWrapperDom.appendChild(keyFrames);
|
||||
|
||||
setTimeout(() => {
|
||||
showUi.set(false);
|
||||
showComponent.set(null);
|
||||
|
||||
statusBarStore.set({
|
||||
title: '',
|
||||
description: '',
|
||||
items: [],
|
||||
icon: '',
|
||||
});
|
||||
hideStatusBarStore.set(false);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if(hideStatusBarValue) {
|
||||
// if(hideStatusBarValue || !hideStatusBarValue)
|
||||
closeStatusBar();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="status-bar-wrapper" class="status-bar-wrapper">
|
||||
<div class="status-title-wrapper">
|
||||
<div class="icon">
|
||||
<Icon icon={statusData.icon} color="ps-text-green" />
|
||||
</div>
|
||||
|
||||
<div class="title-info">
|
||||
<p class="title">
|
||||
{statusData.title}
|
||||
</p>
|
||||
<p class="title-description">
|
||||
{statusData.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="items-wrapper">
|
||||
{#each statusDataItems as item}
|
||||
<div class="each-item">
|
||||
<p class="label">
|
||||
{item.key}:
|
||||
</p>
|
||||
<p class="value">
|
||||
{item.value}
|
||||
</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.status-bar-wrapper {
|
||||
-moz-animation: 2s display-status;
|
||||
-webkit-animation: 2s display-status;
|
||||
animation: 2s display-status;
|
||||
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 1%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
width: 23vw;
|
||||
min-height: 8vw;
|
||||
height: fit-content;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
background-color: var(--color-darkblue);
|
||||
border-radius: 0.3vw;
|
||||
|
||||
padding: 1vw 2vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@keyframes display-status {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.status-bar-wrapper > .status-title-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.status-bar-wrapper > .status-title-wrapper > .icon {
|
||||
width: 1.5vw;
|
||||
margin-right: 0.5vw;
|
||||
|
||||
font-size: 1.25vw;
|
||||
}
|
||||
.status-bar-wrapper > .status-title-wrapper > .title-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.status-bar-wrapper > .status-title-wrapper > .title-info > .title {
|
||||
font-size: 1.3vw;
|
||||
font-weight: 500;
|
||||
color: var(--color-white);
|
||||
}
|
||||
.status-bar-wrapper > .status-title-wrapper > .title-info > .title-description {
|
||||
font-size: 0.95vw;
|
||||
font-weight: 200;
|
||||
color: var(--color-lightgrey);
|
||||
|
||||
margin-top: -0.2vw;
|
||||
}
|
||||
|
||||
.status-bar-wrapper > .items-wrapper {
|
||||
margin-left: 2vw;
|
||||
margin-top: 0.5vw;
|
||||
}
|
||||
.status-bar-wrapper > .items-wrapper > .each-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
word-wrap: break-word;
|
||||
flex-wrap: wrap;
|
||||
|
||||
font-size: 0.95vw;
|
||||
}
|
||||
.status-bar-wrapper > .items-wrapper > .each-item:not(:last-child) {
|
||||
margin-bottom: 0.3vw;
|
||||
}
|
||||
|
||||
.status-bar-wrapper > .items-wrapper > .each-item > .label {
|
||||
color: var(--color-lightgrey);
|
||||
}
|
||||
.status-bar-wrapper > .items-wrapper > .each-item > .value {
|
||||
color: var(--color-green);
|
||||
margin-left: 0.3vw;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export enum ConnectingGameMessageEnum {
|
||||
Connecting = 'CONNECTING TO INTERFACE',
|
||||
Connected = 'CONNECTED. GET READY.',
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export enum GamesEnum {
|
||||
Scrambler = 'Scramber',
|
||||
NumberMaze = 'NumberMaze',
|
||||
Memory = 'MemoryGame',
|
||||
NumberPuzzle = 'NumberPuzzle',
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
export enum NotificationTypes {
|
||||
Success = 'ps-notification-success',
|
||||
Error = 'ps-notification-error',
|
||||
Warning = 'ps-notification-warning',
|
||||
Info = 'ps-notification-info',
|
||||
}
|
||||
|
||||
export enum NotificationIcons {
|
||||
Success = 'fa-solid fa-circle-check',
|
||||
Error = 'fa-solid fa-circle-exclamation',
|
||||
Warning = 'fa-solid fa-triangle-exclamation',
|
||||
Info = 'fa-solid fa-circle-info',
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
export enum UIComponentsEnum {
|
||||
StatusBar = 'StatusBar',
|
||||
Menu = 'Menu',
|
||||
Input = 'Input',
|
||||
Game = 'Game',
|
||||
MemoryGame = 'MemoryGame',
|
||||
Image = 'ShowImage',
|
||||
DrawText = 'DrawText',
|
||||
Notification = 'Notify',
|
||||
None = 'hideUi',
|
||||
}
|
||||
139
resources/[freizeit]/[gym]/ps-ui/web/src/games/CircleGame.svelte
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
let canvas;
|
||||
let ctx;
|
||||
let W, H, degrees = 0, new_degrees = 0, color = "#38D5AF";
|
||||
let txtcolor = "#ffffff", bgcolor = "#2B312B", bgcolor2 = "#068f6d", bgcolor3 = "#00ff00";
|
||||
let key_to_press, g_start, g_end, animation_loop;
|
||||
let needed = 4, streak = 0, circle_started = false, time = 2;
|
||||
|
||||
function getRandomInt(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||
}
|
||||
|
||||
function StartCircle() {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = bgcolor;
|
||||
ctx.lineWidth = 20;
|
||||
ctx.arc(W / 2, H / 2, Math.min(W, H) / 2 - 10, 0, Math.PI * 2, false);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = correct === true ? bgcolor3 : bgcolor2;
|
||||
ctx.lineWidth = 20;
|
||||
ctx.arc(W / 2, H / 2, Math.min(W, H) / 2 - 10, g_start - 90 * Math.PI / 180, g_end - 90 * Math.PI / 180, false);
|
||||
ctx.stroke();
|
||||
let radians = degrees * Math.PI / 180;
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = 40;
|
||||
ctx.arc(W / 2, H / 2, Math.min(W, H) / 2 - 20, radians - 0.1 - 90 * Math.PI / 180, radians - 90 * Math.PI / 180, false);
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = txtcolor;
|
||||
ctx.font = "100px sans-serif";
|
||||
let text_width = ctx.measureText(key_to_press).width;
|
||||
ctx.fillText(key_to_press, W / 2 - text_width / 2, H / 2 + 35);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
if (typeof animation_loop !== undefined) clearInterval(animation_loop);
|
||||
g_start = getRandomInt(20, 40) / 10;
|
||||
g_end = getRandomInt(5, 10) / 10;
|
||||
g_end = g_start + g_end;
|
||||
degrees = 0;
|
||||
new_degrees = 360;
|
||||
key_to_press = '' + getRandomInt(1, 4);
|
||||
animation_loop = setInterval(animate_to, time);
|
||||
}
|
||||
|
||||
function animate_to() {
|
||||
if (degrees >= new_degrees) {
|
||||
CircleFail();
|
||||
return;
|
||||
}
|
||||
degrees += 2;
|
||||
StartCircle();
|
||||
}
|
||||
|
||||
function correct() {
|
||||
streak += 1;
|
||||
if (streak == needed) {
|
||||
clearInterval(animation_loop);
|
||||
endGame(true);
|
||||
} else {
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
function CircleFail() {
|
||||
clearInterval(animation_loop);
|
||||
endGame(false);
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
document.getElementById('circle').style.display = 'block';
|
||||
document.getElementById('circle').style.pointerEvents = 'auto';
|
||||
circle_started = true;
|
||||
draw();
|
||||
}
|
||||
|
||||
function endGame(status) {
|
||||
document.getElementById('circle').style.display = 'none';
|
||||
circle_started = false;
|
||||
let endResult = status ? true : false;
|
||||
fetch(`https://ps-ui/circle-result`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({endResult})
|
||||
});
|
||||
streak = 0;
|
||||
needed = 4;
|
||||
}
|
||||
|
||||
export function setupCircleGame(data: { circles?: number; time?: number }) {
|
||||
needed = data.circles ?? 4;
|
||||
time = data.time ?? 2;
|
||||
startGame();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
canvas = document.getElementById("circle");
|
||||
ctx = canvas.getContext("2d");
|
||||
W = window.innerWidth * 0.2;
|
||||
H = window.innerHeight * 0.2;
|
||||
canvas.width = W;
|
||||
canvas.height = H;
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data.action == "circle-start") {
|
||||
needed = event.data.circles ?? 4;
|
||||
time = event.data.time ?? 2;
|
||||
startGame();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", function (ev) {
|
||||
let key_pressed = ev.key;
|
||||
let valid_keys = ['1', '2', '3', '4'];
|
||||
if (valid_keys.includes(key_pressed) && circle_started) {
|
||||
if (key_pressed === key_to_press) {
|
||||
let d_start = (180 / Math.PI) * g_start;
|
||||
let d_end = (180 / Math.PI) * g_end;
|
||||
if (degrees < d_start || degrees > d_end) {
|
||||
CircleFail();
|
||||
} else {
|
||||
correct();
|
||||
}
|
||||
} else {
|
||||
CircleFail();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="absolute inset-0 flex items-center justify-center" style="pointer-events: none; z-index: 100;">
|
||||
<canvas id="circle" class="w-auto h-auto" style="display: none;"></canvas>
|
||||
</div>
|
||||
104
resources/[freizeit]/[gym]/ps-ui/web/src/games/GameBase.svelte
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<script lang="ts">
|
||||
import { currentActiveGameDetails, currentGameActive } from "../stores/GameLauncherStore";
|
||||
import Skull from "../assets/svgs/Skull.svelte";
|
||||
import { GamesEnum } from "../enums/GamesEnum";
|
||||
import NewMemoryGame from "./MemoryGame.svelte";
|
||||
import SuccessFailureScreen from "./SuccessFailureScreen.svelte";
|
||||
import NewScrambler from "./Scrambler.svelte";
|
||||
import NewNumberMaze from "./NumberMaze.svelte";
|
||||
import NewNumberPuzzle from "./NumberPuzzle.svelte";
|
||||
|
||||
const skullColor: string = 'var(--color-green)';
|
||||
|
||||
let showResultScreen = false, hackSuccess = false;
|
||||
</script>
|
||||
|
||||
{#if !showResultScreen}
|
||||
<div class="games-container">
|
||||
<div class="game-wrapper ps-bg-darkblue">
|
||||
<div class="skull-logo">
|
||||
<Skull color={skullColor} />
|
||||
</div>
|
||||
|
||||
<div class="game-heading">
|
||||
<p class="ps-font-arcade">
|
||||
{$currentActiveGameDetails?.gameName}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="game-description">
|
||||
<p>{$currentActiveGameDetails?.gameDescription}</p>
|
||||
</div>
|
||||
|
||||
<div class="main-game-body">
|
||||
{#if $currentGameActive === GamesEnum.Memory}
|
||||
<NewMemoryGame on:game-ended={(event) => {showResultScreen = true; hackSuccess = event.detail.hackSuccess; }} />
|
||||
{:else if $currentGameActive === GamesEnum.Scrambler}
|
||||
<NewScrambler on:game-ended={(event) => {showResultScreen = true; hackSuccess = event.detail.hackSuccess; }} />
|
||||
{:else if $currentGameActive === GamesEnum.NumberMaze}
|
||||
<NewNumberMaze on:game-ended={(event) => {showResultScreen = true; hackSuccess = event.detail.hackSuccess; }} />
|
||||
{:else if $currentGameActive === GamesEnum.NumberPuzzle}
|
||||
<NewNumberPuzzle on:game-ended={(event) => {showResultScreen = true; hackSuccess = event.detail.hackSuccess; }} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<SuccessFailureScreen isSuccess={hackSuccess} />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.games-container {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
border-radius: 0.2vw;
|
||||
}
|
||||
|
||||
.games-container > .game-wrapper {
|
||||
width: 33vw;
|
||||
height: 45vw;
|
||||
border-radius: 0.3vw;
|
||||
overflow: hidden;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.games-container > .game-wrapper > .skull-logo {
|
||||
margin: 0 auto;
|
||||
padding-top: 1vw;
|
||||
width: 5vw;
|
||||
}
|
||||
|
||||
.games-container > .game-wrapper > .game-heading {
|
||||
font-size: 1.1vw;
|
||||
letter-spacing: 0.4vw;
|
||||
color: var(--color-lightgrey);
|
||||
|
||||
margin: 0.25vw auto;
|
||||
|
||||
width: 19vw;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.games-container > .game-wrapper > .game-description {
|
||||
font-size: 0.7vw;
|
||||
font-weight: normal;
|
||||
color: var(--color-lightgrey);
|
||||
text-align: center;
|
||||
|
||||
width: 33vw;
|
||||
margin: 1vw auto;
|
||||
}
|
||||
|
||||
.games-container > .game-wrapper > .main-game-body {
|
||||
margin: 0.75vw auto 2vw auto;
|
||||
width: 31vw;
|
||||
/* border: 0.1px solid yellow; */
|
||||
height: 30vw;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<script lang="ts">
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import Skull from '../assets/svgs/Skull.svelte';
|
||||
('./../../utils/mockEvent');
|
||||
import { connectionText, showLoading } from '../stores/GameLauncherStore';
|
||||
import { ConnectingGameMessageEnum } from '../enums/GameConnectionMessages';
|
||||
import GameBase from './GameBase.svelte';
|
||||
|
||||
const skullColor: string = '#02f1b5';
|
||||
|
||||
let loadingBar: HTMLDivElement;
|
||||
let gameLoaded = false;
|
||||
|
||||
/** Asynchronously connects to a game and resolves the Promise when completed.
|
||||
* Uses a loading bar to show progress, incrementing by 1% every 30ms until it reaches 100%.
|
||||
*
|
||||
* @returns Promise that resolves when the loading bar reaches 100%.
|
||||
*/
|
||||
async function connectToGame(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
let width = 0;
|
||||
let interval = setInterval(() => {
|
||||
width++;
|
||||
loadingBar.style.width = `${width}%`;
|
||||
|
||||
if (width === 100) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, 30);
|
||||
});
|
||||
}
|
||||
|
||||
// Call init() on mount
|
||||
onMount(async () => {
|
||||
// Show a loading indicator while connecting to the game server
|
||||
connectionText.set(ConnectingGameMessageEnum.Connecting);
|
||||
showLoading.set(true);
|
||||
|
||||
// Connect to the game server
|
||||
await connectToGame();
|
||||
|
||||
// Hide loading indicator when completed connecting to the game server
|
||||
connectionText.set(ConnectingGameMessageEnum.Connected);
|
||||
setTimeout(() => {
|
||||
showLoading.set(false);
|
||||
gameLoaded = true;
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
onDestroy(async () => {
|
||||
gameLoaded = false
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if $showLoading}
|
||||
<div class="flex min-h-screen justify-center items-center">
|
||||
<div
|
||||
class="flex flex-col h-[400px] w-[700px] ps-bg-darkblue shadow-md shadow-black justify-center items-center"
|
||||
>
|
||||
<span class="w-40"><Skull color={skullColor} /></span>
|
||||
<p class="text-white text-3xl mt-2">{$connectionText}</p>
|
||||
|
||||
<!-- Loading wrapper -->
|
||||
<div class="flex mt-10 ps-border-green border-4 w-[80%] h-10">
|
||||
<!-- Loading bar progress -->
|
||||
<div
|
||||
bind:this={loadingBar}
|
||||
class="ps-bg-green opacity-40 will-change-auto w-0"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !$showLoading && gameLoaded}
|
||||
<GameBase />
|
||||
{/if}
|
||||
269
resources/[freizeit]/[gym]/ps-ui/web/src/games/MemoryGame.svelte
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
<script lang="ts">
|
||||
import { closeGame, currentActiveGameDetails, gameSettings } from "../stores/GameLauncherStore";
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
import fetchNui from "../../utils/fetch";
|
||||
import { isDevMode } from "../stores/GeneralStores";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let gridSizesAcceptable = [
|
||||
{
|
||||
numberOfRowCol: 5,
|
||||
cubeSize: '4.2vw',
|
||||
gap: '1vw'
|
||||
},
|
||||
{
|
||||
numberOfRowCol: 6,
|
||||
cubeSize: '3.7vw',
|
||||
gap: '0.8vw'
|
||||
},
|
||||
{
|
||||
numberOfRowCol: 7,
|
||||
cubeSize: '2.9vw',
|
||||
gap: '1vw'
|
||||
},
|
||||
{
|
||||
numberOfRowCol: 8,
|
||||
cubeSize: '2.6vw',
|
||||
gap: '0.9vw'
|
||||
},
|
||||
{
|
||||
numberOfRowCol: 9,
|
||||
cubeSize: '2.4vw',
|
||||
gap: '0.75vw'
|
||||
},
|
||||
{
|
||||
numberOfRowCol: 10,
|
||||
cubeSize: '2.1vw',
|
||||
gap: '0.75vw'
|
||||
},
|
||||
];
|
||||
|
||||
let gameTimeRemaining = 0;
|
||||
|
||||
let numberOfCorrectCubesToDisplay = $gameSettings.amountOfAnswers; // how many cubes to remember for game - increment number based on difficulty level
|
||||
let gameTime = $gameSettings.gameTime * 100;
|
||||
let numberOfWrongClicksAllowed = $gameSettings.maxAnswersIncorrect;
|
||||
|
||||
let correctIndices = [], displayCorrectIndicesFor = $currentActiveGameDetails.displayInitialAnswersFor * 1000; // time in seconds
|
||||
let counter, gameStarted = false, gameEnded = false;
|
||||
let hackSuccess = false;
|
||||
let numberOfCubes = $currentActiveGameDetails.gridSize * $currentActiveGameDetails.gridSize;
|
||||
let allCubes = [];
|
||||
|
||||
onMount(() => {
|
||||
//generate random indices from number of cubes
|
||||
while(correctIndices.length < numberOfCorrectCubesToDisplay){
|
||||
const r = Math.floor(Math.random() * numberOfCubes);
|
||||
if(correctIndices.indexOf(r) === -1) correctIndices.push(r);
|
||||
}
|
||||
|
||||
//generating an array to maintain each cube data by index
|
||||
for(let i = 0; i < numberOfCubes; i++) {
|
||||
const cubeData = {
|
||||
cubeIndex: i,
|
||||
isCorrectAnswer: correctIndices.includes(i),
|
||||
isClicked: false
|
||||
};
|
||||
allCubes.push(cubeData);
|
||||
allCubes = allCubes;
|
||||
}
|
||||
|
||||
let cubeWidthHeightValue = gridSizesAcceptable.filter((accept) => {
|
||||
return accept.numberOfRowCol === $currentActiveGameDetails.gridSize;
|
||||
})[0];
|
||||
|
||||
setTimeout(() => {
|
||||
//assigning cube width and height
|
||||
allCubes.forEach((cube) => {
|
||||
const gameContainer = document.getElementById('memory-game-container');
|
||||
if(gameContainer) {
|
||||
gameContainer.style.gap = cubeWidthHeightValue.gap;
|
||||
}
|
||||
|
||||
const cubeDom = document.getElementById('each-cube-'+cube.cubeIndex);
|
||||
if(cubeDom) {
|
||||
cubeDom.style.width = cubeWidthHeightValue.cubeSize;
|
||||
cubeDom.style.height = cubeWidthHeightValue.cubeSize;
|
||||
cubeDom.style.border = "2px solid var(--color-green)";
|
||||
}
|
||||
});
|
||||
}, 1500);
|
||||
|
||||
//stop showing the correct cubes and start the guessing game
|
||||
setTimeout(() => {
|
||||
gameStarted = true;
|
||||
counter = setInterval(startTimer, 10);
|
||||
}, displayCorrectIndicesFor + 1500);
|
||||
|
||||
});
|
||||
|
||||
function startTimer() {
|
||||
if (gameTime <= 0)
|
||||
{
|
||||
gameEnded = true;
|
||||
hackSuccess = isSuccessful();
|
||||
clearInterval(counter);
|
||||
return;
|
||||
}
|
||||
gameTime--;
|
||||
gameTimeRemaining = gameTime/100;
|
||||
}
|
||||
|
||||
function isSuccessful() {
|
||||
//all correct cubes clicked and wrong clicks are within threshold then success
|
||||
|
||||
//all correct cubes clicked
|
||||
let allCorrectCubesClicked = false;
|
||||
allCubes.map((item) => {
|
||||
if(item.isCorrectAnswer && item.isClicked) {
|
||||
allCorrectCubesClicked = true;
|
||||
}
|
||||
if(item.isCorrectAnswer && !item.isClicked) {
|
||||
allCorrectCubesClicked = false;
|
||||
}
|
||||
});
|
||||
|
||||
//wrong clicks within threshold
|
||||
const wrongClickedCubes = getWrongClicks();
|
||||
const wrongClicksWithinThreshold = wrongClickedCubes.length < numberOfWrongClicksAllowed;
|
||||
|
||||
return allCorrectCubesClicked && wrongClicksWithinThreshold;
|
||||
}
|
||||
|
||||
function getWrongClicks() {
|
||||
return allCubes.filter((item) => {
|
||||
return item.isClicked && !item.isCorrectAnswer;
|
||||
});
|
||||
}
|
||||
|
||||
function guessAnswer(guessedCube) {
|
||||
if(!gameEnded) {
|
||||
const cubeIndexInArray = allCubes.findIndex((item) => item.cubeIndex === guessedCube.cubeIndex);
|
||||
|
||||
let updatedCube = guessedCube;
|
||||
updatedCube.isClicked = true;
|
||||
|
||||
allCubes[cubeIndexInArray] = updatedCube;
|
||||
|
||||
const wrongClickedCubes = getWrongClicks();
|
||||
|
||||
// if wrong clicks are done, end the game
|
||||
if(wrongClickedCubes.length >= numberOfWrongClicksAllowed) {
|
||||
clearInterval(counter);
|
||||
setTimeout(() => {
|
||||
hackSuccess = false;
|
||||
gameTimeRemaining = 0;
|
||||
gameEnded = true;
|
||||
return;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
hackSuccess = isSuccessful();
|
||||
|
||||
if(hackSuccess) {
|
||||
clearInterval(counter);
|
||||
gameTimeRemaining = 0;
|
||||
gameEnded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if(gameEnded) {
|
||||
if(!isDevMode) {
|
||||
fetchNui('minigame:callback', hackSuccess);
|
||||
dispatch('game-ended', { hackSuccess });
|
||||
}
|
||||
|
||||
dispatch('closeUI', {hackSuccess})
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyEvent(event) {
|
||||
let key_pressed = event.key;
|
||||
let valid_keys = ['Escape'];
|
||||
|
||||
if(gameStarted && valid_keys.includes(key_pressed) && !gameEnded) {
|
||||
switch(key_pressed){
|
||||
case 'Escape':
|
||||
closeGame(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown|preventDefault={handleKeyEvent} />
|
||||
<div class="memory-game-base">
|
||||
<div class="time-left">
|
||||
<i class="fa-solid fa-clock ps-text-lightgrey clock-icon"></i>
|
||||
<p class="{gameTimeRemaining !== 0 ? 'game-timer-var' : 'mr-1'}">{gameTimeRemaining} </p> time remaining
|
||||
</div>
|
||||
|
||||
<div id="memory-game-container" class="memory-game-container" style="gap: 13px;">
|
||||
{#each allCubes as cube}
|
||||
<div
|
||||
id={'each-cube-'+cube.cubeIndex}
|
||||
on:click={() => guessAnswer(cube)}
|
||||
style="width: 0px; height: 0px; border: 0px;"
|
||||
class="each-cube {gameStarted ? 'cursor-pointer' : 'cursor-default'} {!gameStarted ? (cube.isCorrectAnswer ? 'ps-bg-green-cube' : '') : (cube.isClicked && cube.isCorrectAnswer ? 'ps-bg-green-cube' : (cube.isClicked && !cube.isCorrectAnswer ? 'ps-bg-wrong-cube' : ''))}">
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.memory-game-base {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
height: 30vw;
|
||||
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--color-lightgrey);
|
||||
}
|
||||
|
||||
.memory-game-base > .time-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
font-size: 0.85vw;
|
||||
}
|
||||
.memory-game-base > .time-left > .clock-icon {
|
||||
padding-top: 0.17vw;
|
||||
margin-right: 0.3vw;
|
||||
}
|
||||
.memory-game-base > .time-left > .game-timer-var {
|
||||
width: 2.5vw;
|
||||
}
|
||||
|
||||
.memory-game-base > .memory-game-container {
|
||||
/* border: 0.1px solid red; */
|
||||
|
||||
margin-top: 1vw;
|
||||
width: 30vw;
|
||||
height: 29vw;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
/* gap: 1.5vw; */
|
||||
}
|
||||
.memory-game-base > .memory-game-container > .each-cube {
|
||||
/* width: 4.5vw;
|
||||
height: 4.5vw; */
|
||||
|
||||
background-color: var(--cube-bg-darkgreen);
|
||||
border: 2px solid var(--color-green);
|
||||
}
|
||||
|
||||
.ps-bg-green-cube {
|
||||
background-color: var(--color-green) !important;
|
||||
}
|
||||
.ps-bg-wrong-cube {
|
||||
background-color: var(--color-red) !important;
|
||||
}
|
||||
</style>
|
||||
331
resources/[freizeit]/[gym]/ps-ui/web/src/games/NumberMaze.svelte
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
<script lang="ts">
|
||||
import { closeGame, gameSettings } from "../stores/GameLauncherStore";
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
import fetchNui from "../../utils/fetch";
|
||||
import { getRandomArbitrary, isDevMode } from "../stores/GeneralStores";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let gameTimeRemaining = 0;
|
||||
|
||||
let gameTime = $gameSettings.gameTime * 100;
|
||||
let numberOfWrongClicksAllowed = $gameSettings.maxAnswersIncorrect;
|
||||
|
||||
let counter, gameStarted = false, gameEnded = false;
|
||||
let numberOfCubes = 49, allCubes = [];
|
||||
let blinkingIndex, correctRoute = [], goodPositions = [], stopBlinking = false;
|
||||
let lastPos = 0, wrongAnswerCount = 0;
|
||||
let displayCubeNumbers = false;
|
||||
|
||||
onMount(() => {
|
||||
//get starting blinking index
|
||||
blinkingIndex = getRandomArbitrary(1,4); // 4 => highest index, => lowest index for blink
|
||||
|
||||
//generate the correct route
|
||||
correctRoute = generateBestRoute(blinkingIndex);
|
||||
|
||||
goodPositions = Object.keys(correctRoute);
|
||||
|
||||
//generating an array to maintain each cube data by index
|
||||
for(let i = 0; i < numberOfCubes; i++) {
|
||||
const cubeValue = [blinkingIndex, blinkingIndex * 7].includes(i) ? getRandomArbitrary(1,4) : getRandomArbitrary(1,5);
|
||||
|
||||
const cubeData = {
|
||||
cubeIndex: i,
|
||||
cubeValue: goodPositions.includes(i.toLocaleString()) ? correctRoute[i] : cubeValue,
|
||||
classList: ''
|
||||
};
|
||||
allCubes.push(cubeData);
|
||||
allCubes = allCubes;
|
||||
}
|
||||
|
||||
//stop showing the correct cubes and start the guessing game
|
||||
setTimeout(() => {
|
||||
gameStarted = true;
|
||||
counter = setInterval(startTimer, 10);
|
||||
}, 1000);
|
||||
|
||||
});
|
||||
|
||||
function maxVertical(pos) {
|
||||
return Math.floor((48-pos)/7);
|
||||
}
|
||||
|
||||
function maxHorizontal(pos) {
|
||||
let max = (pos+1) % 7;
|
||||
if(max > 0) return 7-max;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
function generateNextPosition(pos) {
|
||||
let maxV = maxVertical(pos);
|
||||
let maxH = maxHorizontal(pos);
|
||||
if(maxV === 0 ){
|
||||
let new_pos = getRandomArbitrary(getRandomArbitrary(1,maxH), maxH);
|
||||
return [new_pos, pos+new_pos];
|
||||
}
|
||||
if(maxH === 0 ){
|
||||
let new_pos = getRandomArbitrary(getRandomArbitrary(1,maxV), maxV);
|
||||
return [new_pos, pos+(new_pos*7)];
|
||||
}
|
||||
if(Math.floor(Math.random() * 1000 + 1) % 2 === 0 ){
|
||||
let new_pos = getRandomArbitrary(getRandomArbitrary(1,maxH), maxH);
|
||||
return [new_pos, pos+new_pos];
|
||||
} else {
|
||||
let new_pos = getRandomArbitrary(getRandomArbitrary(1,maxV), maxV);
|
||||
return [new_pos, pos+(new_pos*7)];
|
||||
}
|
||||
}
|
||||
|
||||
function generateBestRoute(start_pos) {
|
||||
let route = [];
|
||||
if(getRandomArbitrary(1,1000) % 2 === 0 ){
|
||||
start_pos *= 7;
|
||||
}
|
||||
while(start_pos < 48){
|
||||
let new_pos = generateNextPosition(start_pos);
|
||||
route[start_pos] = new_pos[0];
|
||||
start_pos = new_pos[1];
|
||||
}
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
if (gameTime <= 0)
|
||||
{
|
||||
wrongAnswerCount = numberOfWrongClicksAllowed;
|
||||
checkMazeAnswer();
|
||||
return;
|
||||
}
|
||||
gameTime--;
|
||||
gameTimeRemaining = gameTime/100;
|
||||
}
|
||||
|
||||
function updateAllCubesArrayWithClassListOfClickedCube(isGood, clickedCube) {
|
||||
const additionClassString = isGood ? ' ps-bg-green-cube' : ' ps-bg-wrong-cube';
|
||||
const newClassList = clickedCube.classList + additionClassString;
|
||||
clickedCube.classList = newClassList;
|
||||
|
||||
//replace the clicked cube data in allCubes array
|
||||
allCubes[clickedCube.cubeIndex] = clickedCube;
|
||||
allCubes = allCubes;
|
||||
}
|
||||
|
||||
|
||||
function handleCubeClick(clickedCube) {
|
||||
if(!gameEnded && clickedCube.cubeIndex !== 0) {
|
||||
let posClicked = clickedCube.cubeIndex;
|
||||
//game just started and user made first click
|
||||
if(lastPos === 0) {
|
||||
//stop the blinking and hide numbers on cubes
|
||||
stopBlinking = true;
|
||||
if([blinkingIndex, blinkingIndex * 7].includes(posClicked)) {
|
||||
lastPos = posClicked;
|
||||
|
||||
//display good cube click and update allCubes array
|
||||
updateAllCubesArrayWithClassListOfClickedCube(true, clickedCube);
|
||||
} else {
|
||||
wrongAnswerCount++;
|
||||
//display bad cube click and update allCubes array
|
||||
updateAllCubesArrayWithClassListOfClickedCube(false, clickedCube);
|
||||
}
|
||||
} else {
|
||||
let posJumps = allCubes[lastPos].cubeValue;
|
||||
let maxV = maxVertical(lastPos);
|
||||
let maxH = maxHorizontal(lastPos);
|
||||
|
||||
if(posJumps <= maxH && posClicked === lastPos + posJumps) {
|
||||
lastPos = posClicked;
|
||||
//display good cube click and update allCubes array
|
||||
updateAllCubesArrayWithClassListOfClickedCube(true, clickedCube);
|
||||
} else if (posJumps <= maxV && posClicked === lastPos + (posJumps * 7)) {
|
||||
lastPos = posClicked;
|
||||
//display good cube click and update allCubes array
|
||||
updateAllCubesArrayWithClassListOfClickedCube(true, clickedCube);
|
||||
} else {
|
||||
wrongAnswerCount++;
|
||||
//display bad cube click and update allCubes array
|
||||
updateAllCubesArrayWithClassListOfClickedCube(false, clickedCube);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkMazeAnswer();
|
||||
}
|
||||
|
||||
function checkMazeAnswer() {
|
||||
// check if wrong answers exceeded / game ended - both with same condition
|
||||
// if yes, end the game, clear counter and display correct answers and then stop game
|
||||
// else, end the game within few seconds
|
||||
if(wrongAnswerCount === numberOfWrongClicksAllowed) {
|
||||
clearInterval(counter);
|
||||
|
||||
displayCubeNumbers = true;
|
||||
|
||||
allCubes = allCubes.map((cube) => {
|
||||
cube.classList = goodPositions.includes(cube.cubeIndex.toLocaleString()) ? 'ps-bg-green-cube' : '';
|
||||
return cube;
|
||||
});
|
||||
allCubes = allCubes;
|
||||
|
||||
setTimeout(() => {
|
||||
gameEnded = true;
|
||||
if(!isDevMode) {
|
||||
fetchNui('minigame:callback', false);
|
||||
dispatch('game-ended', { hackSuccess: false });
|
||||
}
|
||||
dispatch('closeUI', {hackSuccess: false});
|
||||
}, 3000);
|
||||
|
||||
return;
|
||||
} else if(lastPos === 48){
|
||||
clearInterval(counter);
|
||||
|
||||
displayCubeNumbers = true;
|
||||
|
||||
setTimeout(() => {
|
||||
gameEnded = true;
|
||||
if(!isDevMode) {
|
||||
fetchNui('minigame:callback', true);
|
||||
dispatch('game-ended', { hackSuccess: true });
|
||||
}
|
||||
dispatch('closeUI', {hackSuccess: true});
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyEvent(event) {
|
||||
let key_pressed = event.key;
|
||||
let valid_keys = ['Escape'];
|
||||
|
||||
if(gameStarted && valid_keys.includes(key_pressed) && !gameEnded) {
|
||||
switch(key_pressed){
|
||||
case 'Escape':
|
||||
closeGame(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown|preventDefault={handleKeyEvent} />
|
||||
<div class="maze-game-base">
|
||||
<div class="time-left">
|
||||
<i class="fa-solid fa-clock ps-text-lightgrey clock-icon"></i>
|
||||
<p class="{gameTimeRemaining !== 0 ? 'game-timer-var' : 'mr-1'}">{gameTimeRemaining} </p> time remaining
|
||||
</div>
|
||||
<div id="maze-game-container" class="maze-game-container">
|
||||
{#each allCubes as cube}
|
||||
<div
|
||||
id={'each-cube-'+cube.cubeIndex} on:click={() => handleCubeClick(cube)}
|
||||
class="each-cube {cube.classList}
|
||||
{[0, numberOfCubes - 1].includes(cube.cubeIndex) ? 'start-dest-cube' : ''}
|
||||
{!stopBlinking && [blinkingIndex, blinkingIndex * 7].includes(cube.cubeIndex) ? 'blinking-cube' : ''}
|
||||
"
|
||||
>
|
||||
{#if cube.cubeIndex === 0}
|
||||
<i class="fa-solid fa-ethernet"></i>
|
||||
{:else if cube.cubeIndex === numberOfCubes - 1}
|
||||
<i class="fa-solid fa-network-wired"></i>
|
||||
{:else}
|
||||
{#if !stopBlinking || displayCubeNumbers}
|
||||
<p>{ cube.cubeValue }</p>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.maze-game-base {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
height: 28vw;
|
||||
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--color-lightgrey);
|
||||
}
|
||||
|
||||
.maze-game-base > .time-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
font-size: 0.85vw;
|
||||
}
|
||||
.maze-game-base > .time-left > .clock-icon {
|
||||
padding-top: 0.17vw;
|
||||
margin-right: 0.3vw;
|
||||
}
|
||||
.maze-game-base > .time-left > .game-timer-var {
|
||||
width: 2.5vw;
|
||||
}
|
||||
|
||||
.maze-game-base > .maze-game-container {
|
||||
/* border: 0.1px solid red; */
|
||||
|
||||
margin-top: 0.5vw;
|
||||
width: 30vw;
|
||||
height: 29vw;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.9vw;
|
||||
}
|
||||
.maze-game-base > .maze-game-container > .start-dest-cube {
|
||||
background-color: var(--color-green) !important;
|
||||
color: var(--color-darkblue);
|
||||
|
||||
font-size: 1.65vw;
|
||||
text-align: center;
|
||||
}
|
||||
.maze-game-base > .maze-game-container > .start-dest-cube > i {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.maze-game-base > .maze-game-container > .each-cube {
|
||||
width: 3vw;
|
||||
height: 3vw;
|
||||
|
||||
background-color: var(--cube-bg-darkgreen);
|
||||
border: 2px solid var(--color-green);
|
||||
text-align: center;
|
||||
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.maze-game-base > .maze-game-container > .each-cube > p {
|
||||
font-size: 1.5vw;
|
||||
font-weight: bold;
|
||||
margin-top: 0.2vw
|
||||
}
|
||||
|
||||
.blinking-cube {
|
||||
animation: blink 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%,
|
||||
100% {
|
||||
background-color: var(--color-green);
|
||||
/* background-color: var(--cube-bg-darkgreen); */
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: var(--cube-bg-darkgreen);
|
||||
}
|
||||
}
|
||||
|
||||
.ps-bg-green-cube {
|
||||
background-color: var(--color-green) !important;
|
||||
}
|
||||
.ps-bg-wrong-cube {
|
||||
background-color: var(--color-red) !important;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
<script lang="ts">
|
||||
import { gameSettings, currentActiveGameDetails, closeGame } from "../stores/GameLauncherStore";
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
import fetchNui from "../../utils/fetch";
|
||||
import mojs from '@mojs/core';
|
||||
import { convertVwToPx, getRandomArbitrary, isDevMode } from "../stores/GeneralStores";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let gameTimeRemaining = 0;
|
||||
|
||||
let blocksInput = $gameSettings.amountOfAnswers; // how many cubes to remember for game - increment number based on difficulty level
|
||||
let gameTime = $gameSettings.gameTime * 100;
|
||||
let numberOfWrongClicksAllowed = $gameSettings.maxAnswersIncorrect;
|
||||
let displayNumbersOnCubesFor = $currentActiveGameDetails.timeForNumberDisplay * 100;
|
||||
|
||||
let counter, gameStarted = false, gameEnded = false;
|
||||
let allCubes = [];
|
||||
let order = 0, wrongClicks = 0;
|
||||
let cubeBgColors = ['var(--color-green)', 'var(--color-palegreen)', 'var(--color-blue)'];
|
||||
|
||||
// let topLowerBound = 290, topHigherBound = 660; px
|
||||
// let leftLowerBound = 77, leftHigherBound = 459;
|
||||
let topLowerBound = 18, topHigherBound = 40; //vw
|
||||
let leftLowerBound = 2, leftHigherBound = 28;
|
||||
|
||||
onMount(() => {
|
||||
//generate shuffled cubes indices
|
||||
let cubeIndicesList = [];
|
||||
while(cubeIndicesList.length < blocksInput){
|
||||
const r = Math.floor(Math.random() * blocksInput);
|
||||
if(cubeIndicesList.indexOf(r) === -1) cubeIndicesList.push(r);
|
||||
}
|
||||
|
||||
//generating an array to maintain each cube data by index
|
||||
for(let i = 0; i < cubeIndicesList.length; i++) {
|
||||
//height between 290 and 660 px
|
||||
//horizontal between 77 and 459 px
|
||||
const cubeData = {
|
||||
cubeIndex: cubeIndicesList[i],
|
||||
cubeValue: cubeIndicesList[i],
|
||||
bgColor: cubeBgColors[Math.floor(Math.random() * cubeBgColors.length)],
|
||||
top: getRandomArbitrary(topLowerBound, topHigherBound),
|
||||
left: getRandomArbitrary(leftLowerBound, leftHigherBound)
|
||||
};
|
||||
allCubes.push(cubeData);
|
||||
allCubes = allCubes;
|
||||
}
|
||||
|
||||
//stop showing the correct cubes and start the guessing game
|
||||
setTimeout(() => {
|
||||
gameStarted = true;
|
||||
|
||||
let eachCube = document.querySelectorAll('.each-cube');
|
||||
eachCube.forEach(el => { newPos(el) });
|
||||
|
||||
counter = setInterval(startTimer, 10);
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
function newPos(element) {
|
||||
let top = element.offsetTop;
|
||||
let left = element.offsetLeft;
|
||||
|
||||
let new_top_vw = getRandomArbitrary(topLowerBound,topHigherBound);
|
||||
let new_left_vw = getRandomArbitrary(leftLowerBound,leftHigherBound);
|
||||
|
||||
let new_top = convertVwToPx(new_top_vw);
|
||||
let new_left = convertVwToPx(new_left_vw);
|
||||
|
||||
let diff_top = new_top - top;
|
||||
let diff_left = new_left - left;
|
||||
|
||||
let duration = getRandomArbitrary(10,40)*100;
|
||||
|
||||
new mojs.Html({
|
||||
el: '#'+element.id,
|
||||
x: {
|
||||
0:diff_left,
|
||||
duration: duration,
|
||||
easing: 'linear.none'
|
||||
},
|
||||
y: {
|
||||
0:diff_top,
|
||||
duration: duration,
|
||||
easing: 'linear.none'
|
||||
},
|
||||
duration: duration+50,
|
||||
onComplete() {
|
||||
if(element.offsetTop === 0 && element.offsetLeft === 0) {
|
||||
this.pause();
|
||||
return;
|
||||
}
|
||||
const bgColor = element.style.backgroundColor;
|
||||
element.style = 'background-color: '+bgColor+'; top: '+new_top_vw+'vw; left: '+new_left_vw+'vw; transform: none;';
|
||||
newPos(element);
|
||||
},
|
||||
onUpdate() {
|
||||
if(gameStarted === false) this.pause();
|
||||
}
|
||||
}).play();
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
if (gameTime <= 0)
|
||||
{
|
||||
endGame(false);
|
||||
return;
|
||||
}
|
||||
displayNumbersOnCubesFor--;
|
||||
gameTime--;
|
||||
gameTimeRemaining = gameTime/100;
|
||||
}
|
||||
|
||||
function handleClick(clickedCube) {
|
||||
if(gameStarted && !gameEnded && displayNumbersOnCubesFor <= 0) {
|
||||
//correct click
|
||||
if(order === clickedCube.cubeIndex) {
|
||||
let clickedCubeDom = document.getElementById('each-cube-'+clickedCube.cubeIndex);
|
||||
clickedCubeDom.style.backgroundColor = 'var(--color-darkgrey)';
|
||||
order = order + 1;
|
||||
} else {
|
||||
wrongClicks = wrongClicks + 1;
|
||||
}
|
||||
checkGameStatus();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function checkGameStatus() {
|
||||
if(order === allCubes.length - 1 && wrongClicks < numberOfWrongClicksAllowed) {
|
||||
endGame(true);
|
||||
} else if(order < allCubes.length - 1 && wrongClicks >= numberOfWrongClicksAllowed) {
|
||||
endGame(false);
|
||||
}
|
||||
}
|
||||
|
||||
function endGame(isSuccess) {
|
||||
if(!gameEnded) {
|
||||
gameEnded = true;
|
||||
clearInterval(counter);
|
||||
|
||||
setTimeout(() => {
|
||||
if(!isDevMode) {
|
||||
fetchNui('minigame:callback', isSuccess);
|
||||
dispatch('game-ended', { hackSuccess: isSuccess });
|
||||
}
|
||||
dispatch('minigame:callback', {hackSuccess: isSuccess});
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyEvent(event) {
|
||||
let key_pressed = event.key;
|
||||
let valid_keys = ['Escape'];
|
||||
|
||||
if(gameStarted && valid_keys.includes(key_pressed) && !gameEnded) {
|
||||
switch(key_pressed){
|
||||
case 'Escape':
|
||||
closeGame(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown|preventDefault={handleKeyEvent} />
|
||||
<div class="var-game-base">
|
||||
<div class="time-left">
|
||||
<i class="fa-solid fa-clock ps-text-lightgrey clock-icon"></i>
|
||||
<p class="{gameTimeRemaining !== 0 ? 'game-timer-var' : 'mr-1'}">{gameTimeRemaining} </p> time remaining
|
||||
</div>
|
||||
|
||||
<div id="var-game-container" class="var-game-container">
|
||||
{#each allCubes as cube}
|
||||
<div
|
||||
id={'each-cube-'+cube.cubeIndex}
|
||||
class="each-cube"
|
||||
style="background-color:{cube.bgColor}; top: {cube.top}vw; left: {cube.left}vw;"
|
||||
on:click={() => handleClick(cube)}
|
||||
>
|
||||
{#if displayNumbersOnCubesFor > 0}
|
||||
<p>{cube.cubeValue + 1}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.var-game-base {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
height: 28vw;
|
||||
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--color-lightgrey);
|
||||
}
|
||||
|
||||
.var-game-base > .time-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
font-size: 0.85vw;
|
||||
}
|
||||
.var-game-base > .time-left > .clock-icon {
|
||||
padding-top: 0.17vw;
|
||||
margin-right: 0.3vw;
|
||||
}
|
||||
.var-game-base > .time-left > .game-timer-var {
|
||||
width: 2.5vw;
|
||||
}
|
||||
|
||||
.var-game-base > .var-game-container {
|
||||
border: 2px solid var(--color-green);
|
||||
background-color: var(--cube-bg-darkgreen);
|
||||
|
||||
margin-top: 1vw;
|
||||
width: 30vw;
|
||||
height: 28vw;
|
||||
}
|
||||
.var-game-base > .var-game-container > .each-cube {
|
||||
width: 3vw;
|
||||
height: 3vw;
|
||||
|
||||
border: 2px solid var(--color-lightgrey);
|
||||
position: absolute;
|
||||
|
||||
text-align: center;
|
||||
cursor: default;
|
||||
}
|
||||
.var-game-base > .var-game-container > .each-cube > p {
|
||||
font-size: 1.5vw;
|
||||
font-weight: bold;
|
||||
margin-top: 0.2vw;
|
||||
color: var(--color-black);
|
||||
}
|
||||
</style>
|
||||
321
resources/[freizeit]/[gym]/ps-ui/web/src/games/Scrambler.svelte
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
<script lang="ts">
|
||||
import { closeGame, currentActiveGameDetails, gameSettings } from "../stores/GameLauncherStore";
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
import fetchNui from "../../utils/fetch";
|
||||
import { getRandomArbitrary, isDevMode } from "../stores/GeneralStores";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const randomSetChar = () => {
|
||||
let str='?';
|
||||
switch($currentActiveGameDetails.sets) {
|
||||
case 'numeric':
|
||||
str="0123456789";
|
||||
break;
|
||||
case 'alphabet':
|
||||
str="ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
break;
|
||||
case 'alphanumeric':
|
||||
str="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
break;
|
||||
case 'greek':
|
||||
str="ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ";
|
||||
break;
|
||||
case 'braille':
|
||||
str="⡀⡁⡂⡃⡄⡅⡆⡇⡈⡉⡊⡋⡌⡍⡎⡏⡐⡑⡒⡓⡔⡕⡖⡗⡘⡙⡚⡛⡜⡝⡞⡟⡠⡡⡢⡣⡤⡥⡦⡧⡨⡩⡪⡫⡬⡭⡮⡯⡰⡱⡲⡳⡴⡵⡶⡷⡸⡹⡺⡻⡼⡽⡾⡿"+
|
||||
"⢀⢁⢂⢃⢄⢅⢆⢇⢈⢉⢊⢋⢌⢍⢎⢏⢐⢑⢒⢓⢔⢕⢖⢗⢘⢙⢚⢛⢜⢝⢞⢟⢠⢡⢢⢣⢤⢥⢦⢧⢨⢩⢪⢫⢬⢭⢮⢯⢰⢱⢲⢳⢴⢵⢶⢷⢸⢹⢺⢻⢼⢽⢾⢿"+
|
||||
"⣀⣁⣂⣃⣄⣅⣆⣇⣈⣉⣊⣋⣌⣍⣎⣏⣐⣑⣒⣓⣔⣕⣖⣗⣘⣙⣚⣛⣜⣝⣞⣟⣠⣡⣢⣣⣤⣥⣦⣧⣨⣩⣪⣫⣬⣭⣮⣯⣰⣱⣲⣳⣴⣵⣶⣷⣸⣹⣺⣻⣼⣽⣾⣿";
|
||||
break;
|
||||
case 'runes':
|
||||
str="ᚠᚥᚧᚨᚩᚬᚭᚻᛐᛑᛒᛓᛔᛕᛖᛗᛘᛙᛚᛛᛜᛝᛞᛟᛤ";
|
||||
break;
|
||||
}
|
||||
return str.charAt(getRandomArbitrary(0, str.length));
|
||||
}
|
||||
|
||||
let gameTimeRemaining = 0;
|
||||
|
||||
let amountOfAnswers = $gameSettings.amountOfAnswers; // how many numbers for game
|
||||
let gameTime = $gameSettings.gameTime * 100; // seconds
|
||||
|
||||
let correctIndices = [], correctAnswers = [];
|
||||
let changeBoardAfter = $currentActiveGameDetails.changeBoardAfter * 100; //3 seconds
|
||||
let originalChangeBoardAfter = changeBoardAfter;
|
||||
let counter, gameStarted = false, gameEnded = false;
|
||||
let hackSuccess = false;
|
||||
let numberOfCubes = 80, allCubes = [];
|
||||
let totalNumberOfColumns = 10;
|
||||
|
||||
let cursorIndices = [], cursorStartIndex = 43;
|
||||
|
||||
onMount(() => {
|
||||
//generating an array to maintain each cube data by index
|
||||
for(let i = 0; i < numberOfCubes; i++) {
|
||||
const cubeData = {
|
||||
cubeIndex: i,
|
||||
cubeValue: randomSetChar() + randomSetChar(),
|
||||
};
|
||||
allCubes.push(cubeData);
|
||||
allCubes = allCubes;
|
||||
}
|
||||
|
||||
//generate correct answer values - column number - has to be Number of cols - 4 to have straight 4 answers
|
||||
const columnNumber = Math.floor(Math.random() * 5);
|
||||
//generate correct answer values - row number
|
||||
const rowNumber = Math.floor(Math.random() * 7);
|
||||
|
||||
const startIndex = rowNumber * totalNumberOfColumns + columnNumber;
|
||||
correctAnswers = [];
|
||||
for(let i = 0; i < amountOfAnswers; i++) {
|
||||
correctAnswers.push(allCubes[i + startIndex]);
|
||||
correctIndices.push(i+startIndex);
|
||||
}
|
||||
|
||||
getCursorIndices();
|
||||
|
||||
//start game
|
||||
setTimeout(() => {
|
||||
gameStarted = true;
|
||||
counter = setInterval(startTimer, 10);
|
||||
}, 1000);
|
||||
|
||||
});
|
||||
|
||||
function getCursorIndices() {
|
||||
cursorIndices = [cursorStartIndex];
|
||||
for(let i=1; i<4; i++){
|
||||
if( cursorStartIndex+i >= 80 ){
|
||||
cursorIndices.push( (cursorStartIndex+i) - 80 );
|
||||
}else{
|
||||
cursorIndices.push( cursorStartIndex+i );
|
||||
}
|
||||
}
|
||||
// return group;
|
||||
}
|
||||
|
||||
function endTheGame() {
|
||||
clearInterval(counter);
|
||||
gameEnded = true;
|
||||
|
||||
setTimeout(() => {
|
||||
if(!isDevMode) {
|
||||
fetchNui('minigame:callback', hackSuccess);
|
||||
dispatch('game-ended', { hackSuccess });
|
||||
}
|
||||
dispatch('minigame:callback', hackSuccess);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
if (gameTime <= 0)
|
||||
{
|
||||
hackSuccess = false;
|
||||
endTheGame();
|
||||
return;
|
||||
} else if (changeBoardAfter <= 0) {
|
||||
scrambleBoard();
|
||||
}
|
||||
|
||||
gameTime--;
|
||||
changeBoardAfter--;
|
||||
|
||||
gameTimeRemaining = gameTime/100;
|
||||
}
|
||||
|
||||
function scrambleBoard() {
|
||||
changeBoardAfter = originalChangeBoardAfter;
|
||||
|
||||
//generating an array to maintain each cube data by index
|
||||
let newCubeData = [];
|
||||
for(let i = 0; i < numberOfCubes; i++) {
|
||||
let cubeValue;
|
||||
|
||||
if(i === numberOfCubes - 1) {
|
||||
cubeValue = allCubes[0].cubeValue;
|
||||
} else {
|
||||
cubeValue = allCubes[i+1].cubeValue;
|
||||
}
|
||||
const cubeData = {
|
||||
cubeIndex: i,
|
||||
cubeValue: cubeValue,
|
||||
};
|
||||
newCubeData.push(cubeData);
|
||||
newCubeData = newCubeData;
|
||||
}
|
||||
getCursorIndices();
|
||||
|
||||
allCubes = newCubeData;
|
||||
}
|
||||
|
||||
function checkAnswer() {
|
||||
let selectedValues = cursorIndices.map((currentCursorIndex) => {
|
||||
return allCubes[currentCursorIndex];
|
||||
});
|
||||
|
||||
const selectedValuesData = selectedValues.map((item) => {
|
||||
return item.cubeValue;
|
||||
});
|
||||
|
||||
const correctAnswerValues = correctAnswers.map((item) => {
|
||||
return item.cubeValue;
|
||||
});
|
||||
|
||||
if(JSON.stringify(selectedValuesData) === JSON.stringify(correctAnswerValues)) {
|
||||
hackSuccess = true;
|
||||
} else {
|
||||
hackSuccess = false;
|
||||
}
|
||||
|
||||
endTheGame();
|
||||
}
|
||||
|
||||
function handleKeyEvent(event) {
|
||||
let key_pressed = event.key;
|
||||
let valid_keys = ['a','w','s','d', 'A','W','S','D' ,'ArrowUp','ArrowDown','ArrowRight','ArrowLeft','Enter', 'Escape'];
|
||||
|
||||
if(gameStarted && valid_keys.includes(key_pressed) && !gameEnded) {
|
||||
switch(key_pressed){
|
||||
case 'w':
|
||||
case 'ArrowUp':
|
||||
cursorStartIndex -= 10;
|
||||
if(cursorStartIndex < 0) {
|
||||
cursorStartIndex += 80;
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
case 'ArrowDown':
|
||||
cursorStartIndex += 10;
|
||||
cursorStartIndex %= 80;
|
||||
break;
|
||||
case 'a':
|
||||
case 'ArrowLeft':
|
||||
cursorStartIndex--;
|
||||
if(cursorStartIndex < 0) {
|
||||
cursorStartIndex = 79;
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
case 'ArrowRight':
|
||||
cursorStartIndex++;
|
||||
cursorStartIndex %= 80;
|
||||
break;
|
||||
case 'Enter':
|
||||
clearInterval(counter);
|
||||
checkAnswer();
|
||||
return;
|
||||
case 'Escape':
|
||||
closeGame(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if(cursorStartIndex) {
|
||||
getCursorIndices();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown|preventDefault={handleKeyEvent} />
|
||||
<div class="scrambler-game-base">
|
||||
<div class="game-info-container">
|
||||
<div class="scrambler-find-data">
|
||||
<p>Match the numbers underneath.</p>
|
||||
<div class="original-data-wrapper">
|
||||
{#each correctAnswers as value}
|
||||
<p class="original-digits">{value.cubeValue}</p>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="time-left">
|
||||
<i class="fa-solid fa-clock ps-text-lightgrey clock-icon"></i>
|
||||
<p class="{gameTimeRemaining !== 0 ? 'game-timer-var' : 'mr-1'}">{gameTimeRemaining} </p> time remaining
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="scrambler-game-container" class="scrambler-game-container">
|
||||
{#each allCubes as cube}
|
||||
<div id={'each-cube-'+cube.cubeIndex} class="each-cube">
|
||||
<p class="{!gameEnded && cursorIndices.includes(cube.cubeIndex) ? 'ps-text-red' : ''}">{cube.cubeValue}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.scrambler-game-base {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 28vw;
|
||||
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--color-lightgrey);
|
||||
}
|
||||
|
||||
.scrambler-game-base > .game-info-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.scrambler-game-base > .game-info-container > .scrambler-find-data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.scrambler-game-base > .game-info-container > .scrambler-find-data > .original-data-wrapper {
|
||||
width: max-content;
|
||||
border: 2px solid var(--color-green);
|
||||
border-radius: 0.2vw;
|
||||
background-color: var(--cube-bg-darkgreen);
|
||||
color: var(--color-green);
|
||||
|
||||
margin: 0.5vw auto;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
.scrambler-game-base > .game-info-container > .scrambler-find-data > .original-data-wrapper > .original-digits {
|
||||
font-size: 1vw;
|
||||
padding: 0.3vw 0.5vw;
|
||||
}
|
||||
|
||||
.scrambler-game-base > .game-info-container > .time-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
font-size: 0.85vw;
|
||||
}
|
||||
.scrambler-game-base > .game-info-container > .time-left > .clock-icon {
|
||||
padding-top: 0.17vw;
|
||||
margin-right: 0.3vw;
|
||||
}
|
||||
.scrambler-game-base > .game-info-container > .time-left > .game-timer-var {
|
||||
width: 2.5vw;
|
||||
}
|
||||
|
||||
.scrambler-game-base > .scrambler-game-container {
|
||||
/* border: 0.1px solid red; */
|
||||
|
||||
margin-top: 1vw;
|
||||
width: 30vw;
|
||||
height: 24vw;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5vw;
|
||||
}
|
||||
.scrambler-game-base > .scrambler-game-container > .each-cube {
|
||||
width: 2.5vw;
|
||||
height: 2.5vw;
|
||||
|
||||
font-size: 1.75vw;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import Skull from "../assets/svgs/Skull.svelte";
|
||||
import { closeUi } from "../stores/GameLauncherStore";
|
||||
|
||||
export let isSuccess = false;
|
||||
const skullColor: string = 'var(--color-green)';
|
||||
|
||||
onMount(() => {
|
||||
setTimeout(() => {
|
||||
closeUi();
|
||||
}, 2000);
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="result-container">
|
||||
<div class="result-wrapper ps-bg-darkblue">
|
||||
<div class="skull-logo">
|
||||
<Skull color={skullColor} />
|
||||
</div>
|
||||
{#if isSuccess}
|
||||
<h1 class="text-white text-3xl uppercase">
|
||||
Access granted
|
||||
</h1>
|
||||
{:else}
|
||||
<h1 class="text-white text-3xl uppercase">
|
||||
Access denied
|
||||
</h1>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.result-container {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
border-radius: 0.2vw;
|
||||
}
|
||||
|
||||
.result-container > .result-wrapper {
|
||||
width: 30vw;
|
||||
height: 15vw;
|
||||
border-radius: 0.3vw;
|
||||
overflow: hidden;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.result-container > .result-wrapper > .skull-logo {
|
||||
margin: 0 auto 1vw auto;
|
||||
width: 6vw;
|
||||
}
|
||||
</style>
|
||||
86
resources/[freizeit]/[gym]/ps-ui/web/src/index.css
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: roboto;
|
||||
src: url('./assets/fonts/Roboto/Roboto-Regular.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: hacker;
|
||||
src: url('./assets/fonts/Hacker-Technology-Font/Hacker.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: arcade;
|
||||
src: url('./assets/fonts/karmatic_arcade/ka1.ttf');
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.ps-text-roboto {
|
||||
font-family: 'roboto';
|
||||
}
|
||||
.ps-font-arcade {
|
||||
font-family: 'arcade';
|
||||
}
|
||||
.ps-font-hacker {
|
||||
font-family: 'hacker';
|
||||
}
|
||||
|
||||
.ps-text-green {
|
||||
color: var(--color-green) !important;
|
||||
}
|
||||
.ps-text-lightgrey {
|
||||
color: var(--color-lightgrey);
|
||||
}
|
||||
.ps-text-darkblue {
|
||||
color: var(--color-darkblue);
|
||||
}
|
||||
.ps-text-red {
|
||||
color: var(--color-red) !important;
|
||||
}
|
||||
|
||||
.ps-border-green {
|
||||
border: 1px solid var(--color-green);
|
||||
}
|
||||
.ps-outline-green {
|
||||
outline: 1px solid var(--color-green);
|
||||
}
|
||||
|
||||
.ps-bg-lightgrey {
|
||||
background-color: var(--color-lightgrey);
|
||||
}
|
||||
.ps-bg-darkblue {
|
||||
background-color: var(--color-darkblue);
|
||||
}
|
||||
.ps-bg-green {
|
||||
background-color: var(--color-green);
|
||||
}
|
||||
|
||||
.ps-bg-green-w-opacity {
|
||||
background-color: rgba(2, 241, 181, 0.2);
|
||||
}
|
||||
.ps-notification-success {
|
||||
background-color: #2ebd2e;
|
||||
color: black;
|
||||
border: 1px solid #158515;
|
||||
}
|
||||
.ps-notification-warning {
|
||||
background-color: #fbb433;
|
||||
border: 1px solid #cf8d15;
|
||||
color: black;
|
||||
}
|
||||
.ps-notification-error {
|
||||
background-color: #cd2222;
|
||||
border: 1px solid #8d0e0e;
|
||||
color: white;
|
||||
}
|
||||
.ps-notification-info {
|
||||
background-color: #33addf;
|
||||
border: 1px solid #1886b3;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export interface IDrawText {
|
||||
// title: string;
|
||||
icon: string;
|
||||
keys: string;
|
||||
color: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
export interface IGameSettings {
|
||||
game: string;
|
||||
gameName: string;
|
||||
gameDescription: string;
|
||||
gameTime: number;
|
||||
amountOfAnswers: number;
|
||||
maxAnswersIncorrect: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export interface IImage {
|
||||
action?: string;
|
||||
show: boolean;
|
||||
url: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
export interface IInput {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
placeholder?: string;
|
||||
type: string;
|
||||
}
|
||||
15
resources/[freizeit]/[gym]/ps-ui/web/src/interfaces/IMenu.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
export interface IMenu {
|
||||
header: string;
|
||||
text?: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
callback: string;
|
||||
subMenu: Array<null|ISubMenu>;
|
||||
};
|
||||
|
||||
export interface ISubMenu {
|
||||
header: string;
|
||||
text?: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import type { NotificationTypes } from './../enums/NotificationTypesEnum';
|
||||
|
||||
export interface INotification {
|
||||
text: string;
|
||||
type: NotificationTypes;
|
||||
icon: string;
|
||||
length: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
export interface IStatusBar {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
items: Array<IStatusBarItem>;
|
||||
}
|
||||
|
||||
export interface IStatusBarItem {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
9
resources/[freizeit]/[gym]/ps-ui/web/src/main.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import App from './App.svelte'
|
||||
import './index.css'
|
||||
import '@fortawesome/fontawesome-free/css/all.css'
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById('app')
|
||||
})
|
||||
|
||||
export default app
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import CircleGame from '../games/CircleGame.svelte';
|
||||
|
||||
const circleGameStore = writable<CircleGame | null>(null);
|
||||
|
||||
export function initializeCircleGame() {
|
||||
circleGameStore.update(component => {
|
||||
if (!component) {
|
||||
const newComponent = new CircleGame({
|
||||
target: document.body
|
||||
});
|
||||
return newComponent;
|
||||
}
|
||||
return component;
|
||||
});
|
||||
}
|
||||
|
||||
export function setupCircleGame(data: { circles?: number; time?: number }) {
|
||||
circleGameStore.update(component => {
|
||||
if (component) {
|
||||
component.setupCircleGame(data);
|
||||
}
|
||||
return component;
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { writable, type Writable } from 'svelte/store';
|
||||
import { showComponent, showUi } from './GeneralStores';
|
||||
import { UIComponentsEnum } from '../enums/UIComponentsEnum';
|
||||
import type { IDrawText } from '../interfaces/IDrawText';
|
||||
|
||||
export const drawTextStore: Writable<IDrawText> = writable({
|
||||
// title: '',
|
||||
icon: '',
|
||||
keys: '',
|
||||
color: ''
|
||||
});
|
||||
|
||||
export const hideDrawTextStore: Writable<any> = writable(false);
|
||||
|
||||
export function showDrawTextMenu(data: IDrawText) {
|
||||
showUi.set(true);
|
||||
showComponent.set(UIComponentsEnum.DrawText);
|
||||
|
||||
drawTextStore.set({
|
||||
// title: data.title,
|
||||
icon: data.icon || 'fa-solid fa-circle-info',
|
||||
keys: data.keys,
|
||||
color: data.color || 'var(--color-green)'
|
||||
});
|
||||
}
|
||||
|
||||
export function hideDrawTextMenu() {
|
||||
hideDrawTextStore.set(true);
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
import { UIComponentsEnum } from '../enums/UIComponentsEnum';
|
||||
import { GamesEnum } from '../enums/GamesEnum';
|
||||
import { ConnectingGameMessageEnum } from '../enums/GameConnectionMessages';
|
||||
import { hideUi, isDevMode, showComponent } from './GeneralStores';
|
||||
import type { IGameSettings } from './../interfaces/IGameSettings';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import fetchNui from './../../utils/fetch';
|
||||
|
||||
export const gameSettings: Writable<IGameSettings> = writable({
|
||||
game: '',
|
||||
gameName: '',
|
||||
gameDescription: '',
|
||||
amountOfAnswers: 0,
|
||||
gameTime: 0,
|
||||
maxAnswersIncorrect: 0,
|
||||
});
|
||||
export const currentGameActive: Writable<GamesEnum> | undefined = writable();
|
||||
export const currentActiveGameDetails: Writable<any> | undefined = writable();
|
||||
export const connectionText: Writable<ConnectingGameMessageEnum> = writable();
|
||||
export const showLoading: Writable<boolean> = writable(true);
|
||||
|
||||
export function setupGame(data): void {
|
||||
const settings = data;
|
||||
currentActiveGameDetails.set(settings);
|
||||
|
||||
switch (settings.game) {
|
||||
case GamesEnum.Memory: {
|
||||
showComponent.set(UIComponentsEnum.Game);
|
||||
|
||||
currentGameActive.set(GamesEnum.Memory);
|
||||
connectionText.set(ConnectingGameMessageEnum.Connecting);
|
||||
|
||||
gameSettings.set({
|
||||
game: GamesEnum.Memory,
|
||||
gameName: settings.gameName,
|
||||
gameDescription: settings.gameDescription,
|
||||
gameTime: settings.gameTime || 2,// 1000 = 10 seconds
|
||||
amountOfAnswers: settings.amountOfAnswers || 15,
|
||||
maxAnswersIncorrect: settings.maxAnswersIncorrect || 2
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case GamesEnum.Scrambler: {
|
||||
showComponent.set(UIComponentsEnum.Game);
|
||||
|
||||
currentGameActive.set(GamesEnum.Scrambler);
|
||||
connectionText.set(ConnectingGameMessageEnum.Connecting);
|
||||
|
||||
gameSettings.set({
|
||||
game: GamesEnum.Scrambler,
|
||||
gameName: settings.gameName,
|
||||
gameDescription: settings.gameDescription,
|
||||
gameTime: settings.gameTime || 2,// 1000 = 10 seconds
|
||||
amountOfAnswers: settings.amountOfAnswers || 4,
|
||||
maxAnswersIncorrect: settings.maxAnswersIncorrect || 0,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case GamesEnum.NumberMaze: {
|
||||
showComponent.set(UIComponentsEnum.Game);
|
||||
|
||||
currentGameActive.set(GamesEnum.NumberMaze);
|
||||
connectionText.set(ConnectingGameMessageEnum.Connecting);
|
||||
|
||||
gameSettings.set({
|
||||
game: GamesEnum.NumberMaze,
|
||||
gameName: settings.gameName,
|
||||
gameDescription: settings.gameDescription,
|
||||
gameTime: settings.gameTime || 2,// 1000 = 10 seconds
|
||||
amountOfAnswers: settings.amountOfAnswers || 4,
|
||||
maxAnswersIncorrect: settings.maxAnswersIncorrect || 0,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case GamesEnum.NumberPuzzle: {
|
||||
showComponent.set(UIComponentsEnum.Game);
|
||||
|
||||
currentGameActive.set(GamesEnum.NumberPuzzle);
|
||||
connectionText.set(ConnectingGameMessageEnum.Connecting);
|
||||
|
||||
gameSettings.set({
|
||||
game: GamesEnum.NumberPuzzle,
|
||||
gameName: settings.gameName,
|
||||
gameDescription: settings.gameDescription,
|
||||
gameTime: settings.gameTime || 2,// 1000 = 10 seconds
|
||||
amountOfAnswers: settings.amountOfAnswers || 4,
|
||||
maxAnswersIncorrect: settings.maxAnswersIncorrect || 0,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function closeGame(isSuccess: boolean): void {
|
||||
if(!isDevMode) {
|
||||
fetchNui('minigame:callback', isSuccess);
|
||||
}
|
||||
closeUi();
|
||||
}
|
||||
|
||||
export function closeUi() {
|
||||
hideUi();
|
||||
currentGameActive.set(null);
|
||||
currentActiveGameDetails.set(null);
|
||||
gameSettings.set({
|
||||
game: '',
|
||||
gameName: '',
|
||||
gameDescription: '',
|
||||
amountOfAnswers: 0,
|
||||
gameTime: 0,
|
||||
maxAnswersIncorrect: 0,
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import type { UIComponentsEnum } from './../enums/UIComponentsEnum';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { currentGameActive } from './GameLauncherStore';
|
||||
|
||||
export const showComponent: Writable<UIComponentsEnum | string> = writable();
|
||||
|
||||
export const showUi: Writable<boolean> = writable();
|
||||
export const isDevMode = false;
|
||||
|
||||
export function hideUi(): void {
|
||||
showUi.set(false);
|
||||
showComponent.set(undefined);
|
||||
currentGameActive.set(undefined);
|
||||
}
|
||||
|
||||
export function getRandomArbitrary(min, max) {
|
||||
return Math.floor(Math.random() * (max - min) + min);
|
||||
}
|
||||
|
||||
export function convertVwToPx(vw) {
|
||||
return (document.documentElement.clientWidth * vw) / 100;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import type { IImage } from './../interfaces/IImage';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { showComponent, showUi } from './GeneralStores';
|
||||
import { UIComponentsEnum } from './../enums/UIComponentsEnum';
|
||||
|
||||
export const imageStore: Writable<IImage> = writable({ show: false, url: '' });
|
||||
|
||||
export function showImage(event: any) {
|
||||
showUi.set(true);
|
||||
showComponent.set(UIComponentsEnum.Image);
|
||||
imageStore.set({
|
||||
show: event.show,
|
||||
url: event.url,
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { writable, type Writable } from 'svelte/store';
|
||||
import type { IInput } from './../interfaces/IInput';
|
||||
import { showComponent, showUi } from './GeneralStores';
|
||||
import { UIComponentsEnum } from '../enums/UIComponentsEnum';
|
||||
|
||||
export const inputStore: Writable<Array<IInput>> = writable([
|
||||
{
|
||||
id: '1',
|
||||
label: 'Label',
|
||||
icon: 'fa-solid fa-user',
|
||||
placeholder: 'Insert name',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'Label',
|
||||
icon: 'fa-solid fa-user',
|
||||
placeholder: 'Placeholder',
|
||||
type: 'password',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
label: 'Label',
|
||||
icon: 'fa-solid fa-user',
|
||||
placeholder: 'Placeholder',
|
||||
type: 'phone',
|
||||
},
|
||||
]);
|
||||
|
||||
/** Show the input component */
|
||||
export function showInput(data: Array<IInput>): void {
|
||||
showUi.set(true);
|
||||
showComponent.set(UIComponentsEnum.Input);
|
||||
|
||||
inputStore.set([...data]);
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import type { IMenu } from './../interfaces/IMenu';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { hideUi, showComponent, showUi } from './GeneralStores';
|
||||
import { UIComponentsEnum } from '..//enums/UIComponentsEnum';
|
||||
import fetchNui from '../../utils/fetch';
|
||||
|
||||
export const menuStore: Writable<Array<IMenu>> = writable([]);
|
||||
|
||||
export const menuIdShown: Writable<string> = writable();
|
||||
|
||||
export function setupInteractionMenu(data: Array<IMenu>): void {
|
||||
showUi.set(true);
|
||||
showComponent.set(UIComponentsEnum.Menu);
|
||||
menuStore.set(data.menuData);
|
||||
}
|
||||
|
||||
export function closeInteractionMenu(): void {
|
||||
showUi.set(false);
|
||||
showComponent.set(null);
|
||||
menuStore.set([
|
||||
{
|
||||
header: '',
|
||||
text: '',
|
||||
icon: '',
|
||||
color: '',
|
||||
callback: '',
|
||||
subMenu: null,
|
||||
}
|
||||
]);
|
||||
fetchNui('menuClose');
|
||||
hideUi();
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import type { INotification } from 'src/interfaces/INotification';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { showComponent } from './GeneralStores';
|
||||
import { UIComponentsEnum } from './../enums/UIComponentsEnum';
|
||||
|
||||
export const notifications: Writable<Array<INotification>> = writable([]);
|
||||
|
||||
export function addNotification(newNotification: INotification): void {
|
||||
showComponent.set(UIComponentsEnum.Notification);
|
||||
notifications.update((currentNotifications) => {
|
||||
const updatedNotifications = [...currentNotifications, newNotification];
|
||||
return updatedNotifications;
|
||||
});
|
||||
|
||||
const unsubscribe = notifications.subscribe((data: Array<INotification>) => {
|
||||
data.forEach((notification: INotification) => {
|
||||
setTimeout(() => {
|
||||
removeNotification(notification);
|
||||
}, notification.length);
|
||||
});
|
||||
});
|
||||
unsubscribe();
|
||||
}
|
||||
|
||||
function removeNotification(notification: INotification): void {
|
||||
notifications.update((currentNotifications) => {
|
||||
const updatedNotifications = currentNotifications.filter((n) => n !== notification);
|
||||
return updatedNotifications;
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { writable, type Writable } from 'svelte/store';
|
||||
import type { IStatusBar } from '../interfaces/IStatusBar';
|
||||
import { showComponent, showUi } from './GeneralStores';
|
||||
import { UIComponentsEnum } from '../enums/UIComponentsEnum';
|
||||
|
||||
export const statusBarStore: Writable<IStatusBar> = writable({
|
||||
title: '',
|
||||
description: '',
|
||||
items: [],
|
||||
icon: '',
|
||||
});
|
||||
|
||||
export const hideStatusBarStore: Writable<any> = writable(false);
|
||||
|
||||
export function showStatusBar(data: IStatusBar) {
|
||||
showUi.set(true);
|
||||
showComponent.set(UIComponentsEnum.StatusBar);
|
||||
|
||||
statusBarStore.set({
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
items: data.items,
|
||||
icon: data.icon || 'fa-solid fa-circle-info',
|
||||
});
|
||||
}
|
||||
|
||||
export function hideStatusBar() {
|
||||
hideStatusBarStore.set(true);
|
||||
}
|
||||
2
resources/[freizeit]/[gym]/ps-ui/web/src/vite-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
7
resources/[freizeit]/[gym]/ps-ui/web/svelte.config.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import sveltePreprocess from 'svelte-preprocess'
|
||||
|
||||
export default {
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: sveltePreprocess()
|
||||
}
|
||||
11
resources/[freizeit]/[gym]/ps-ui/web/tailwind.config.cjs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
module.exports = {
|
||||
darkmode: true,
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{svelte,js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
19
resources/[freizeit]/[gym]/ps-ui/web/tsconfig.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
* Note that setting allowJs false does not prevent the use
|
||||
* of JS in `.svelte` files.
|
||||
*/
|
||||
"allowJs": true,
|
||||
"checkJs": true
|
||||
},
|
||||
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
}
|
||||
105
resources/[freizeit]/[gym]/ps-ui/web/utils/eventHandler.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import { hideStatusBar, showStatusBar } from '../src/stores/StatusBarStores';
|
||||
import { hideUi, showComponent, showUi } from '../src/stores/GeneralStores';
|
||||
import { showInput } from './../src/stores/InputStores';
|
||||
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import fetchNui from './fetch';
|
||||
import { UIComponentsEnum } from './../src/enums/UIComponentsEnum';
|
||||
import { currentActiveGameDetails, currentGameActive, gameSettings, setupGame } from '../src/stores/GameLauncherStore';
|
||||
import { showImage } from './../src/stores/ImageStore';
|
||||
import { addNotification } from './../src/stores/NotificationStore';
|
||||
import { hideDrawTextMenu, showDrawTextMenu } from '../src/stores/DrawTextStore';
|
||||
import { closeInteractionMenu, setupInteractionMenu } from '../src/stores/MenuStores';
|
||||
import { initializeCircleGame, setupCircleGame } from '../src/stores/CircleGameStore';
|
||||
|
||||
interface nuiMessage {
|
||||
action: string;
|
||||
data: {[key: string]: any};
|
||||
}
|
||||
|
||||
export function EventHandler() {
|
||||
function mainEvent(event: nuiMessage) {
|
||||
showUi.set(true);
|
||||
switch (event.data.action) {
|
||||
case 'ShowStatusBar':
|
||||
showStatusBar(event.data.data as any);
|
||||
break;
|
||||
case 'UpdateStatusBar':
|
||||
showStatusBar(event.data.data as any);
|
||||
break;
|
||||
case 'HideStatusBar':
|
||||
hideStatusBar();
|
||||
break;
|
||||
case 'ShowMenu':
|
||||
setupInteractionMenu(event.data.data as any)
|
||||
break;
|
||||
case 'HideMenu':
|
||||
closeInteractionMenu();
|
||||
break;
|
||||
case 'ShowInput':
|
||||
showInput(event.data.data as any);
|
||||
break;
|
||||
case 'ShowImage':
|
||||
showImage(event.data.data as any);
|
||||
break;
|
||||
case 'hideUi':
|
||||
hideUi();
|
||||
break;
|
||||
case 'ShowNotification':
|
||||
addNotification(event.data.data as any);
|
||||
break;
|
||||
case 'ShowDrawTextMenu':
|
||||
showDrawTextMenu(event.data.data as any);
|
||||
break;
|
||||
case 'HideDrawTextMenu':
|
||||
hideDrawTextMenu();
|
||||
break;
|
||||
case 'CircleGame':
|
||||
initializeCircleGame()
|
||||
setupCircleGame(event.data.data)
|
||||
break;
|
||||
case 'MemoryGame':
|
||||
case 'Scramber':
|
||||
case 'NumberMaze':
|
||||
case 'GameLauncher':
|
||||
setupGame(event.data.data as any);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => window.addEventListener('message', mainEvent));
|
||||
onDestroy(() => window.removeEventListener('message', mainEvent));
|
||||
}
|
||||
|
||||
export function handleKeyUp(event: KeyboardEvent) {
|
||||
const charCode = event.key;
|
||||
if (charCode == 'Escape') {
|
||||
showComponent.subscribe((component: UIComponentsEnum) => {
|
||||
switch (component) {
|
||||
case UIComponentsEnum.Input:
|
||||
fetchNui('input-close', { ok: true });
|
||||
break;
|
||||
case UIComponentsEnum.Menu:
|
||||
closeInteractionMenu();
|
||||
break;
|
||||
case UIComponentsEnum.Image:
|
||||
fetchNui('minigame:callback', true);
|
||||
break;
|
||||
case UIComponentsEnum.Game:
|
||||
currentGameActive.set(null);
|
||||
currentActiveGameDetails.set(null);
|
||||
gameSettings.set({
|
||||
game: '',
|
||||
gameName: '',
|
||||
gameDescription: '',
|
||||
amountOfAnswers: 0,
|
||||
gameTime: 0,
|
||||
maxAnswersIncorrect: 0,
|
||||
triggerEvent: '',
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
hideUi();
|
||||
}
|
||||
}
|
||||
28
resources/[freizeit]/[gym]/ps-ui/web/utils/fetch.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
export default async function fetchNui(eventName: string, data: unknown = {}) {
|
||||
const options = {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
};
|
||||
|
||||
const getResourceName = () => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
return window.GetParentResourceName();
|
||||
} catch (err) {
|
||||
return 'ps-ui';
|
||||
}
|
||||
};
|
||||
|
||||
const resourceName = getResourceName();
|
||||
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`https://${resourceName}/${eventName}`,
|
||||
options
|
||||
);
|
||||
return await resp.json();
|
||||
} catch (err) {}
|
||||
}
|
||||
45
resources/[freizeit]/[gym]/ps-ui/web/utils/mockEvent.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import type { IImage } from './../src/interfaces/IImage';
|
||||
export default function mockEventCall(data: unknown = {}) {
|
||||
window.dispatchEvent(new MessageEvent('message', { data }));
|
||||
}
|
||||
|
||||
export function newMemoryGameMock() {
|
||||
setTimeout(() => {
|
||||
mockEventCall({
|
||||
action: 'MemoryGame',
|
||||
data: {
|
||||
game: 'MemoryGame',
|
||||
amountOfAnswers: 1,
|
||||
gameTime: 5,
|
||||
maxAnswersIncorrect: 2,
|
||||
triggerEvent: '',
|
||||
},
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
export function showImageMock() {
|
||||
setTimeout(() => {
|
||||
mockEventCall({
|
||||
action: 'ShowImage',
|
||||
data: {
|
||||
show: true,
|
||||
url: 'https://i.ytimg.com/vi/7V15_-32iCU/maxresdefault.jpg',
|
||||
},
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
export function notificationMock() {
|
||||
setTimeout(() => {
|
||||
mockEventCall({
|
||||
action: 'ShowNotification',
|
||||
data: {
|
||||
text: 'New notification',
|
||||
type: 'ps-notification-success',
|
||||
icon: 'fa-solid fa-times',
|
||||
length: 5000,
|
||||
},
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
42
resources/[freizeit]/[gym]/ps-ui/web/vite.config.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
import postcss from './postcss.config.js';
|
||||
import { minify } from 'html-minifier';
|
||||
|
||||
const minifyHtml = () => {
|
||||
return {
|
||||
name: 'html-transform',
|
||||
transformIndexHtml(html) {
|
||||
return minify(html, {
|
||||
collapseWhitespace: true,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
css: {
|
||||
postcss,
|
||||
},
|
||||
plugins: [svelte(), minifyHtml()],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
},
|
||||
base: './', // fivem nui needs to have local dir reference
|
||||
build: {
|
||||
minify: true,
|
||||
emptyOutDir: true,
|
||||
outDir: '../web/build',
|
||||
assetsDir: './',
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// By not having hashes in the name, you don't have to update the manifest, yay!
|
||||
entryFileNames: `[name].js`,
|
||||
chunkFileNames: `[name].js`,
|
||||
assetFileNames: `[name].[ext]`,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||