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]`, | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| }); | ||||
 Nordi98
						Nordi98