diff --git a/fxmanifest.lua b/fxmanifest.lua index a3417ea..b0fdcf9 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -7,6 +7,8 @@ version '1.9.0' url 'https://github.com/overextended/oxmysql' author 'overextended' +ui_page 'src/ui/public/index.html' + dependencies { '/server:5104', } @@ -15,4 +17,9 @@ server_scripts { 'dist/server/build.js', } +files { + 'src/ui/public/index.html', + 'src/ui/public/**/*' +} + provide 'mysql-async' diff --git a/src/ui/.gitignore b/src/ui/.gitignore new file mode 100644 index 0000000..da93220 --- /dev/null +++ b/src/ui/.gitignore @@ -0,0 +1,4 @@ +/node_modules/ +/public/build/ + +.DS_Store diff --git a/src/ui/package.json b/src/ui/package.json new file mode 100644 index 0000000..e22d758 --- /dev/null +++ b/src/ui/package.json @@ -0,0 +1,30 @@ +{ + "name": "svelte-app", + "version": "1.0.0", + "private": true, + "scripts": { + "build": "rollup -c", + "dev": "rollup -c -w", + "start": "sirv public --no-clear" + }, + "devDependencies": { + "@rollup/plugin-commonjs": "^17.0.0", + "@rollup/plugin-node-resolve": "^11.0.0", + "@rollup/plugin-typescript": "^8.0.0", + "@tsconfig/svelte": "^2.0.0", + "rollup": "^2.3.4", + "rollup-plugin-css-only": "^3.1.0", + "rollup-plugin-livereload": "^2.0.0", + "rollup-plugin-svelte": "^7.0.0", + "rollup-plugin-terser": "^7.0.0", + "svelte": "^3.0.0", + "svelte-check": "^2.0.0", + "svelte-preprocess": "^4.0.0", + "tslib": "^2.0.0", + "typescript": "^4.0.0" + }, + "dependencies": { + "sirv-cli": "^1.0.0", + "svelte-spa-router": "^3.2.0" + } +} diff --git a/src/ui/public/global.css b/src/ui/public/global.css new file mode 100644 index 0000000..a94d91a --- /dev/null +++ b/src/ui/public/global.css @@ -0,0 +1,11 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter&display=swap'); + +* { + margin: 0; + padding: 0; + text-decoration: none; +} + +body { + height: 100vh; +} diff --git a/src/ui/public/index.html b/src/ui/public/index.html new file mode 100644 index 0000000..bd206a3 --- /dev/null +++ b/src/ui/public/index.html @@ -0,0 +1,16 @@ + + + + + + + Svelte app + + + + + + + + + diff --git a/src/ui/rollup.config.js b/src/ui/rollup.config.js new file mode 100644 index 0000000..c584614 --- /dev/null +++ b/src/ui/rollup.config.js @@ -0,0 +1,87 @@ +import svelte from "rollup-plugin-svelte"; +import commonjs from "@rollup/plugin-commonjs"; +import resolve from "@rollup/plugin-node-resolve"; +import livereload from "rollup-plugin-livereload"; +import { terser } from "rollup-plugin-terser"; +import sveltePreprocess from "svelte-preprocess"; +import typescript from "@rollup/plugin-typescript"; +import css from "rollup-plugin-css-only"; + +const production = !process.env.ROLLUP_WATCH; + +function serve() { + let server; + + function toExit() { + if (server) server.kill(0); + } + + return { + writeBundle() { + if (server) return; + server = require("child_process").spawn( + "npm", + ["run", "start", "--", "--dev"], + { + stdio: ["ignore", "inherit", "inherit"], + shell: true, + } + ); + + process.on("SIGTERM", toExit); + process.on("exit", toExit); + }, + }; +} + +export default { + input: "src/main.ts", + output: { + sourcemap: false, + format: "iife", + name: "app", + file: "public/build/bundle.js", + }, + plugins: [ + svelte({ + preprocess: sveltePreprocess({ sourceMap: !production }), + compilerOptions: { + // enable run-time checks when not in production + dev: !production, + }, + }), + // we'll extract any component CSS out into + // a separate file - better for performance + css({ output: "bundle.css" }), + + // If you have external dependencies installed from + // npm, you'll most likely need these plugins. In + // some cases you'll need additional configuration - + // consult the documentation for details: + // https://github.com/rollup/plugins/tree/master/packages/commonjs + resolve({ + browser: true, + dedupe: ["svelte"], + }), + commonjs(), + typescript({ + sourceMap: !production, + inlineSources: !production, + }), + + // In dev mode, call `npm run start` once + // the bundle has been generated + !production && serve(), + + // Watch the `public` directory and refresh the + // browser on changes when not in production + !production && livereload("public"), + + // If we're building for production (npm run build + // instead of npm run dev), minify + production && terser(), + ], + watch: { + clearScreen: false, + }, +}; diff --git a/src/ui/src/App.svelte b/src/ui/src/App.svelte new file mode 100644 index 0000000..7614e93 --- /dev/null +++ b/src/ui/src/App.svelte @@ -0,0 +1,18 @@ + + +
+ +
+ +
diff --git a/src/ui/src/components/Main.svelte b/src/ui/src/components/Main.svelte new file mode 100644 index 0000000..2bfc574 --- /dev/null +++ b/src/ui/src/components/Main.svelte @@ -0,0 +1,49 @@ + + +
+ +
+
+ +
+
+
+ + diff --git a/src/ui/src/components/menu/NavMenu.svelte b/src/ui/src/components/menu/NavMenu.svelte new file mode 100644 index 0000000..5d04a15 --- /dev/null +++ b/src/ui/src/components/menu/NavMenu.svelte @@ -0,0 +1,61 @@ + + +
+
Home
+ {#each executingResources as resource} + + {/each} +
+ + diff --git a/src/ui/src/components/routes/Home.svelte b/src/ui/src/components/routes/Home.svelte new file mode 100644 index 0000000..974b469 --- /dev/null +++ b/src/ui/src/components/routes/Home.svelte @@ -0,0 +1 @@ +
Some data here
diff --git a/src/ui/src/components/routes/ResourceData.svelte b/src/ui/src/components/routes/ResourceData.svelte new file mode 100644 index 0000000..693bee1 --- /dev/null +++ b/src/ui/src/components/routes/ResourceData.svelte @@ -0,0 +1,38 @@ + + +
+ {resource} + {queryData} +
diff --git a/src/ui/src/global.d.ts b/src/ui/src/global.d.ts new file mode 100644 index 0000000..1a25456 --- /dev/null +++ b/src/ui/src/global.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/ui/src/main.ts b/src/ui/src/main.ts new file mode 100644 index 0000000..83bc6ab --- /dev/null +++ b/src/ui/src/main.ts @@ -0,0 +1,7 @@ +import App from "./App.svelte"; + +const app = new App({ + target: document.body, +}); + +export default app; diff --git a/src/ui/src/providers/VisibilityProvider.svelte b/src/ui/src/providers/VisibilityProvider.svelte new file mode 100644 index 0000000..582c90b --- /dev/null +++ b/src/ui/src/providers/VisibilityProvider.svelte @@ -0,0 +1,35 @@ + + +
+ {#if isVisible} + + {/if} +
diff --git a/src/ui/src/store/stores.ts b/src/ui/src/store/stores.ts new file mode 100644 index 0000000..508d4d9 --- /dev/null +++ b/src/ui/src/store/stores.ts @@ -0,0 +1,4 @@ +import { writable } from "svelte/store"; + +export const visibility = writable(false); +export const currentResource = writable(''); \ No newline at end of file diff --git a/src/ui/src/utils/debugData.ts b/src/ui/src/utils/debugData.ts new file mode 100644 index 0000000..aea5370 --- /dev/null +++ b/src/ui/src/utils/debugData.ts @@ -0,0 +1,30 @@ +import {isEnvBrowser} from "./misc"; + +interface DebugEvent { + action: string; + data: T; +} + +/** + * Emulates dispatching an event using SendNuiMessage in the lua scripts. + * This is used when developing in browser + * + * @param events - The event you want to cover + * @param timer - How long until it should trigger (ms) + */ +export const debugData =

(events: DebugEvent

[], timer = 1000): void => { + if (isEnvBrowser()) { + for (const event of events) { + setTimeout(() => { + window.dispatchEvent( + new MessageEvent("message", { + data: { + action: event.action, + data: event.data, + }, + }) + ); + }, timer); + } + } +}; \ No newline at end of file diff --git a/src/ui/src/utils/fetchNui.ts b/src/ui/src/utils/fetchNui.ts new file mode 100644 index 0000000..95c2794 --- /dev/null +++ b/src/ui/src/utils/fetchNui.ts @@ -0,0 +1,27 @@ +/** +* @param eventName - The endpoint eventname to target +* @param data - Data you wish to send in the NUI Callback +* +* @return returnData - A promise for the data sent back by the NuiCallbacks CB argument +*/ + +export async function fetchNui( + eventName: string, + data: unknown = {} +): Promise { + const options = { + method: "post", + headers: { + "Content-Type": "application/json; charset=UTF-8", + }, + body: JSON.stringify(data), + }; + + const resourceName = (window as any).GetParentResourceName + ? (window as any).GetParentResourceName() + : "nui-frame-app"; + + const resp = await fetch(`https://${resourceName}/${eventName}`, options); + + return await resp.json(); +} diff --git a/src/ui/src/utils/misc.ts b/src/ui/src/utils/misc.ts new file mode 100644 index 0000000..32eae35 --- /dev/null +++ b/src/ui/src/utils/misc.ts @@ -0,0 +1 @@ +export const isEnvBrowser = (): boolean => !(window as any).invokeNative; diff --git a/src/ui/src/utils/useNuiEvent.ts b/src/ui/src/utils/useNuiEvent.ts new file mode 100644 index 0000000..e1d8d2e --- /dev/null +++ b/src/ui/src/utils/useNuiEvent.ts @@ -0,0 +1,31 @@ +import { onMount, onDestroy } from "svelte"; + +interface NuiMessage { + action: string; + data: T; +} + +/** + * A function that manage events listeners for receiving data from the client scripts + * @param action The specific `action` that should be listened for. + * @param handler The callback function that will handle data relayed by this function + * + * @example + * useNuiEvent<{visibility: true, wasVisible: 'something'}>('setVisible', (data) => { + * // whatever logic you want + * }) + * + **/ + +export function useNuiEvent( + action: string, + handler: (data: T) => void +) { + const eventListener = (event: MessageEvent>) => { + const { action: eventAction, data } = event.data; + + eventAction === action && handler(data); + }; + onMount(() => window.addEventListener("message", eventListener)); + onDestroy(() => window.removeEventListener("message", eventListener)); +} diff --git a/src/ui/tsconfig.json b/src/ui/tsconfig.json new file mode 100644 index 0000000..8139637 --- /dev/null +++ b/src/ui/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + + "compilerOptions": { + "strict": true, + "allowJs": true, + "allowSyntheticDefaultImports": true, + "noFallthroughCasesInSwitch": true + }, + + "include": ["src/**/*"], + "exclude": ["node_modules/*", "__sapper__/*", "public/*"] +}