This commit is contained in:
Nordi98 2025-08-06 16:37:06 +02:00
parent 510e3ffcf2
commit f43cf424cf
305 changed files with 34683 additions and 0 deletions

View file

@ -0,0 +1,21 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
settings: { react: { version: '18.2' } },
plugins: ['react-refresh'],
rules: {
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

View file

@ -0,0 +1,23 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View file

@ -0,0 +1,8 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,91 @@
.container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
min-height: 100vh;
background: transparent
}
.dialogue-container {
position: fixed;
bottom: 30px;
width: 50%;
background: transparent;
padding: 15px;
left: 50%;
transform: translate(-50%)
}
.dialogue-text {
background: #242424;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
line-height: 1.6;
font-size: 1.1em;
border: 0.5px dotted rgba(203, 203, 203, 0.5);
box-shadow: 0 4px 16px #0000007e;
}
.dialogue-options {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 6px;
justify-content: space-between;
margin-top: 20px;
width: 100%
}
.dialogue-button {
background: #242424;
border: 0.5px solid rgba(203, 203, 203, 0.5);
color: #fff;
padding: 12px 24px;
border-radius: 4px;
cursor: pointer;
transition: all .2s ease;
font-size: .95em;
flex: 1;
text-align: center;
min-width: calc(33.333% - 8px);
box-shadow: 0 4px 16px #0003
}
.dialogue-button:hover {
background: #3333338c;
transform: translateY(-1px);
box-shadow: 0 4px 16px #0000007e;
border: 1px solid rgba(255, 255, 255, .3);
}
h2 {
color: #fff;
margin: 0;
font-size: 1.4em;
font-weight: 600;
text-shadow: 0 2px 4px rgba(0, 0, 0, .3)
}
p {
color: #fff;
margin: 0;
text-shadow: 0 1px 2px rgba(0, 0, 0, .2)
}
* {
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale
}
.hidden {
display: none
}
* {
overflow-y: hidden;
overflow-x: hidden
}

View file

@ -0,0 +1,50 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MRC Scuba</title>
<script type="module" crossorigin src="./assets/index-B0fYxqh2.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-BEeeZu6c.css">
</head>
<body>
<div id="root"></div>
</body>
<script>
// dont judge me
function isTableJSON(jsonString) {
try {
const parsed = JSON.parse(jsonString);
return parsed;
} catch (e) {
return false;
}
}
const copyToClipboard = (str) => {
const el = document.createElement("textarea");
el.value = str;
document.body.appendChild(el);
el.select();
document.execCommand("copy");
document.body.removeChild(el);
};
window.addEventListener("message", (event) => {
if (event?.data?.type === "copytoclipboard") {
var str = event.data.text;
// str = JSON.parse(str);
if (isTableJSON(str)) {
str = str.replace(/\[/g, '{');
str = str.replace(/\]/g, '}');
// replace : with =
str = str.replace(/:/g, '=');
// I need all indexes with quotes to be replaced with nothing but not affect any string values
str = str.replace(/"([^"]+)"=/g, '$1=');
}
copyToClipboard(str);
}
});
</script>
</html>

View file

@ -0,0 +1,47 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MRC Scuba</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/main.jsx"></script>
</body>
<script>
// dont judge me
function isTableJSON(jsonString) {
try {
const parsed = JSON.parse(jsonString);
return parsed;
} catch (e) {
return false;
}
}
const copyToClipboard = (str) => {
const el = document.createElement("textarea");
el.value = str;
document.body.appendChild(el);
el.select();
document.execCommand("copy");
document.body.removeChild(el);
};
window.addEventListener("message", (event) => {
if (event?.data?.type === "copytoclipboard") {
var str = event.data.text;
// str = JSON.parse(str);
console.log("copying to clipboard", isTableJSON(str));
if (isTableJSON(str)) {
str = str.replace(/\[/g, '{');
str = str.replace(/\]/g, '}');
str = str.replace(/:/g, '=');
str = str.replace(/"([^"]+)"=/g, '$1=');
}
copyToClipboard(str);
}
});
</script>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,31 @@
{
"name": "sonar-ui",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build --watch",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@mantine/core": "^7.17.0",
"@mantine/hooks": "^7.17.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.2",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"vite": "^5.3.1"
}
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,82 @@
.container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
min-height: 100vh;
background: transparent;
}
.dialogue-container {
position: fixed;
bottom: 30px;
width: 50%;
background: transparent;
padding: 25px;
left: 50%;
transform: translateX(-50%);
}
.dialogue-text {
background: rgba(0, 0, 0, 0.85);
padding: 20px;
border-radius: 4px;
margin: 15px 0;
line-height: 1.6;
font-size: 1.1em;
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}
.dialogue-options {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 12px;
justify-content: space-between;
margin-top: 20px;
width: 100%;
}
.dialogue-button {
background: rgba(0, 0, 0, 0.85);
border: 1px solid rgba(255, 255, 255, 0.15);
color: white;
padding: 12px 24px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.95em;
flex: 1;
text-align: center;
min-width: calc(33.333% - 8px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
}
.dialogue-button:hover {
background: rgba(51, 51, 51, 0.55);
transform: translateY(-1px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.3);
}
h2 {
color: #ffffff;
margin: 0;
font-size: 1.4em;
font-weight: 600;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
p {
color: #fff;
margin: 0;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
/* Add this for better text rendering */
* {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View file

@ -0,0 +1,54 @@
import { useState } from 'react'
import { fetchNui } from './fetch'
import './dialogue.css'
function Dialogue() {
const [count, setCount] = useState(0)
const [dialogueOptions, setDialogueOption] = useState([])
const [currentText, setCurrentText] = useState("Welcome traveler! How can I help you today?")
const [speakerName, setSpeakerName] = useState("Friendly NPC")
const handleMessage = (event) => {
var data = event.data;
if (data !== undefined && data.type === "close") {
document.getElementById('root').classList.add("hidden");
}
if (data !== undefined && data.type === "open") {
setDialogueOption(data.options);
setCurrentText(data.text);
setSpeakerName(data.name);
document.getElementById('root').classList.remove("hidden");
}
};
window.addEventListener("message", handleMessage);
return (
<>
<div className="container">
{/* Dialogue System */}
<div className="dialogue-container">
<h2>{speakerName}</h2>
<div className="dialogue-text">
<p>{currentText}</p>
</div>
<div className="dialogue-options">
{dialogueOptions.map((option) => (
<button
key={option.id}
onClick={() => fetchNui("dialogue:SelectOption", { name: speakerName, id: option.id })}
className="dialogue-button"
>
{option.label}
</button>
))}
</div>
</div>
</div>
</>
)
}
export default Dialogue

View file

@ -0,0 +1,26 @@
export const fetchNui = async (cbName, data = {}) => {
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: JSON.stringify(data)
};
try {
const resourceName = window.GetParentResourceName?.() || '';
const resp = await fetch(`https://${resourceName}/${cbName}`, options);
if (!resp.ok) {
throw new Error(`HTTP error! status: ${resp.status}`);
}
return await resp.json();
} catch (error) {
console.error(`Fetch error for ${cbName}:`, error);
if (!window.GetParentResourceName?.()) {
console.error('Resource name is null - are you testing in browser?');
}
return null;
}
};

View file

@ -0,0 +1,9 @@
.hidden {
display: none;
}
* {
overflow-y: hidden;
overflow-x: hidden;
}

View file

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import Dialogue from './dialogue';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Dialogue />
</React.StrictMode>
);

View file

@ -0,0 +1,14 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import Dialogue from './dialogue.jsx'
import './index.css'
const root = document.getElementById('root')
ReactDOM.createRoot(root).render(
<React.StrictMode>
<Dialogue />
</React.StrictMode>,
)
// add hidden to root element
root.classList.add('hidden')

View file

@ -0,0 +1,3 @@
.hidden {
display: none;
}

View file

@ -0,0 +1,32 @@
import { useState } from 'react'
import './test.css'
function Dialogue() {
const [count, setCount] = useState(0)
const handleMessage = (event) => {
var data = event.data;
if (data !== undefined && data.type === "hide") {
document.getElementById('root').classList.add("hidden");
}
if (data !== undefined && data.type === "open") {
document.getElementById('root').classList.remove("hidden");
}
};
window.addEventListener("message", handleMessage);
return (
<>
<div className="container">
<img src= "https://dunb17ur4ymx4.cloudfront.net/wysiwyg/1364588/7b504ad7f496249800123e36ecfb3905677e337d.png"></img>
<br></br>
<button onClick={() => setCount(count + 1)}>Click me</button>
<p>You clicked {count} times</p>
</div>
</>
)
}
export default Dialogue

View file

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})