commit 7feb10ab1555455eb1e6759897be20da126023a0 Author: DuckieTM Date: Sat Jan 31 09:10:52 2026 +0100 :up: Init V3 diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000..3e5809a --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,11 @@ +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# You can see what browsers were selected by your queries by running: +# npx browserslist + +last 1 Chrome version +last 1 Firefox version +last 1 Edge major versions +last 2 Safari major versions +last 2 iOS major versions diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0792692 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..154341f --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +/dist +/tmp +/out-tsc +/node_modules +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* +/.sass-cache +/connect.lock +/coverage +*.log +.git +.DS_Store +Thumbs.db + +# Nitro +/build +*.zip +.env +public/renderer-config* +public/ui-config* diff --git a/README.md b/README.md new file mode 100644 index 0000000..ebdb05c --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# v2.2.0 - Cool UI Beta !! Use at Own Risk as it is still in Beta !! + +## Prerequisites + +- [Git](https://git-scm.com/) +- [NodeJS](https://nodejs.org/) >= 18 + - If using NodeJS < 18 remove `--openssl-legacy-provider` from the package.json scripts +- [Yarn](https://yarnpkg.com/) `npm i yarn -g` + +## Installation + +- First you should open terminal and navigate to the folder where you want to clone Nitro and Nitro-Renderer +- Clone Nitro (Expl. C:\Github\) + - `git clone https://github.com/duckietm/Nitro-Cool-UI.git` <== For now switch to Dev-RendererV2 + - `git clone https://github.com/duckietm/Nitro-Cool-UI-Renderer.git` + - Install the dependencies for the renderer : cd C:\Github\Nitro-Cool-UI-Renderer + - `yarn install` + - Now we will create a Link for the CoolUI : `yarn link` This will give you a link address `yarn link "@nitrots/nitro-renderer"` + - Install the dependencies for Cool UI : cd C:\Github\Nitro-Cool-UI + - `yarn install` + - `yarn link "@nitrots/nitro-renderer` <== This will link the renderer in the project +- Rename a few files + - Rename `public/renderer-config.json.example` to `public/renderer-config.json` + - Rename `public/ui-config.json.example` to `public/ui-config.json` +- Set your links + - Open `public/renderer-config.json` + - Update `socket.url, asset.url, image.library.url, & hof.furni.url` + - Open `public/ui-config.json` + - Update `camera.url, thumbnails.url, url.prefix, habbopages.url` + - `yarn build` <== the final step to build the DIST folder this is where your browser needs to point / or upload this to your /client if you do the compile on a other machine (preferd) + - You can override any variable by passing it to `NitroConfig` in the index.html + +## Usage + +- To use Nitro you need `.nitro` assets generated, see [nitro-converter](https://git.krews.org/nitro/nitro-converter) for instructions +- See [Morningstar Websockets](https://git.krews.org/nitro/ms-websockets) for instructions on configuring websockets on your server + +### Development + +Run Nitro in development mode when you are editing the files, this way you can see the changes in your browser instantly + +``` +yarn start +``` + +### Production + +To build a production version of Nitro just run the following command + +``` +yarn build:prod +``` + +- A `dist` folder will be generated, these are the files that must be uploaded to your webserver +- Consult your CMS documentation for compatibility with Nitro and how to add the production files diff --git a/css-utils/CSSColorUtils.js b/css-utils/CSSColorUtils.js new file mode 100644 index 0000000..77077fc --- /dev/null +++ b/css-utils/CSSColorUtils.js @@ -0,0 +1,73 @@ +const lightenHexColor = (hex, percent) => +{ +// Remove the hash symbol if present + hex = hex.replace(/^#/, ''); + + // Convert hex to RGB + let r = parseInt(hex.substring(0, 2), 16); + let g = parseInt(hex.substring(2, 4), 16); + let b = parseInt(hex.substring(4, 6), 16); + + // Adjust RGB values + r = Math.round(Math.min(255, r + 255 * percent)); + g = Math.round(Math.min(255, g + 255 * percent)); + b = Math.round(Math.min(255, b + 255 * percent)); + + // Convert RGB back to hex + const result = ((r << 16) | (g << 8) | b).toString(16); + + // Make sure result has 6 digits + return '#' + result.padStart(6, '0'); +} + +const darkenHexColor = (hex, percent) => +{ + // Remove the hash symbol if present + hex = hex.replace(/^#/, ''); + + // Convert hex to RGB + let r = parseInt(hex.substring(0, 2), 16); + let g = parseInt(hex.substring(2, 4), 16); + let b = parseInt(hex.substring(4, 6), 16); + + // Calculate the darkened RGB values + r = Math.round(Math.max(0, r - 255 * percent)); + g = Math.round(Math.max(0, g - 255 * percent)); + b = Math.round(Math.max(0, b - 255 * percent)); + + // Convert RGB back to hex + const result = ((r << 16) | (g << 8) | b).toString(16); + + // Make sure result has 6 digits + return '#' + result.padStart(6, '0'); +}; + + +const generateShades = (colors) => +{ + for (let color in colors) + { + let hex = colors[color] + let extended = {} + const shades = [ 50, 100, 200, 300, 400, 500, 600, 700, 900, 950 ]; + + for (let i = 0; i < shades.length; i++) + { + let shade = shades[i]; + extended[shade] = lightenHexColor(hex, shades[(shades.length - 1 - i) ] / 950); + extended[-shade] = darkenHexColor(hex, shades[(shades.length - 1 - i) ] / 950) + } + + colors[color] = { + DEFAULT: hex, + ...extended + } + } + + return colors; +} + +module.exports = { + generateShades, + lightenHexColor +} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..108a976 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,138 @@ +import typescriptEslintPlugin from "@typescript-eslint/eslint-plugin"; +import typescriptEslintParser from "@typescript-eslint/parser"; +import reactPlugin from "eslint-plugin-react"; +import reactHooksPlugin from "eslint-plugin-react-hooks"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export default [ + { + files: ["**/*.jsx", "**/*.js", "**/*.tsx", "**/*.ts"], + plugins: { + react: reactPlugin, + "react-hooks": reactHooksPlugin, + "@typescript-eslint": typescriptEslintPlugin, + }, + languageOptions: { + parser: typescriptEslintParser, + ecmaVersion: "latest", + parserOptions: { + sourceType: "module", + project: "./tsconfig.json", + tsconfigRootDir: __dirname, + ecmaFeatures: { + jsx: true, + }, + }, + }, + rules: { + ...reactPlugin.configs.recommended.rules, + ...reactHooksPlugin.configs.recommended.rules, + ...typescriptEslintPlugin.configs.recommended.rules, + ...typescriptEslintPlugin.configs[ + "recommended-requiring-type-checking" + ].rules, + 'indent': [ + 'error', + 4, + { + 'SwitchCase': 1 + } + ], + 'no-multi-spaces': [ + 'error' + ], + 'no-trailing-spaces': [ + 'error', + { + 'skipBlankLines': false, + 'ignoreComments': true + } + ], + 'linebreak-style': [ + 'off' + ], + 'quotes': [ + 'error', + 'single' + ], + 'semi': [ + 'error', + 'always' + ], + 'brace-style': [ + 'error', + 'allman' + ], + 'object-curly-spacing': [ + 'error', + 'always' + ], + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/require-await': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/explicit-module-boundary-types': [ + 'off', + { + 'allowedNames': [ + 'getMessageArray' + ] + } + ], + '@typescript-eslint/unbound-method': [ + 'off' + ], + '@typescript-eslint/ban-ts-comment': [ + 'off' + ], + '@typescript-eslint/no-empty-function': [ + 'error', + { + 'allow': [ + 'functions', + 'arrowFunctions', + 'generatorFunctions', + 'methods', + 'generatorMethods', + 'constructors' + ] + } + ], + '@typescript-eslint/no-unused-vars': [ + 'off' + ], + '@typescript-eslint/ban-types': [ + 'error', + { + 'types': + { + 'String': true, + 'Boolean': true, + 'Number': true, + 'Symbol': true, + '{}': false, + 'Object': false, + 'object': false, + 'Function': false + }, + 'extendDefaults': true + } + ], + 'react/react-in-jsx-scope': 'off' + }, + settings: { + react: { + version: "18.3.1", + }, + }, + }, +]; diff --git a/index.html b/index.html new file mode 100644 index 0000000..e3f0ecf --- /dev/null +++ b/index.html @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + Nitro + + + +
+ + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..01d4590 --- /dev/null +++ b/package.json @@ -0,0 +1,49 @@ +{ + "name": "nitro-react", + "version": "2.2", + "homepage": ".", + "private": true, + "scripts": { + "start": "vite --host", + "build": "vite build", + "build:prod": "npx browserslist@latest --update-db && yarn build", + "eslint": "eslint ./src" + }, + "dependencies": { + "@babel/runtime": "^7.26.9", + "@tanstack/react-virtual": "3.2.0", + "@types/react-transition-group": "^4.4.10", + "dompurify": "^3.1.5", + "framer-motion": "^11.2.12", + "react": "^18.3.1", + "react-bootstrap": "^2.10.9", + "react-dom": "^18.3.1", + "react-icons": "^5.2.1", + "react-slider": "^2.0.6", + "react-tiny-popover": "^8.0.4", + "react-youtube": "^7.13.1", + "use-between": "^1.3.5" + }, + "devDependencies": { + "@tailwindcss/forms": "^0.5.7", + "@types/node": "^20.11.30", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@types/react-slider": "^1.3.6", + "@typescript-eslint/eslint-plugin": "^7.13.1", + "@typescript-eslint/parser": "^7.13.1", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.19", + "eslint": "^9.5.0", + "eslint-plugin-react": "^7.34.2", + "eslint-plugin-react-hooks": "^5.1.0-rc-1434af3d22-20240618", + "postcss": "^8.4.38", + "postcss-nested": "^6.0.1", + "sass": "^1.77.4", + "tailwindcss": "^3.4.4", + "typescript": "^5.4.5", + "typescript-eslint": "^7.13.1", + "vite": "^5.2.13", + "vite-tsconfig-paths": "^4.3.2" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..9855208 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,8 @@ +/** @type {import("postcss-load-config").Config} */ + +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +} diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100644 index 0000000..634eb06 Binary files /dev/null and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png new file mode 100644 index 0000000..33cf6c6 Binary files /dev/null and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..349b3dd Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/browserconfig.xml b/public/browserconfig.xml new file mode 100644 index 0000000..b3930d0 --- /dev/null +++ b/public/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000..788a7f1 Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000..ef4a606 Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..34ba1b3 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/mstile-150x150.png b/public/mstile-150x150.png new file mode 100644 index 0000000..3a555aa Binary files /dev/null and b/public/mstile-150x150.png differ diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/public/safari-pinned-tab.svg b/public/safari-pinned-tab.svg new file mode 100644 index 0000000..dc7ced3 --- /dev/null +++ b/public/safari-pinned-tab.svg @@ -0,0 +1,154 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..6264b89 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,20 @@ +{ + "start_url": "/", + "name": "Nitro", + "short_name": "Nitro", + "icons": [ + { + "src": "android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..d3566cc --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,99 @@ +import { GetAssetManager, GetAvatarRenderManager, GetCommunication, GetConfiguration, GetLocalizationManager, GetRoomEngine, GetRoomSessionManager, GetSessionDataManager, GetSoundManager, GetStage, GetTexturePool, GetTicker, HabboWebTools, LegacyExternalInterface, LoadGameUrlEvent, NitroLogger, NitroVersion, PrepareRenderer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { GetUIVersion } from './api'; +import { Base } from './common'; +import { LoadingView } from './components/loading/LoadingView'; +import { MainView } from './components/MainView'; +import { useMessageEvent } from './hooks'; + +NitroVersion.UI_VERSION = GetUIVersion(); + +export const App: FC<{}> = props => +{ + const [ isReady, setIsReady ] = useState(false); + + useMessageEvent(LoadGameUrlEvent, event => + { + const parser = event.getParser(); + + if(!parser) return; + + LegacyExternalInterface.callGame('showGame', parser.url); + }); + + useEffect(() => + { + const prepare = async (width: number, height: number) => + { + try + { + if(!window.NitroConfig) throw new Error('NitroConfig is not defined!'); + + const renderer = await PrepareRenderer({ + width: Math.floor(width), + height: Math.floor(height), + resolution: window.devicePixelRatio, + autoDensity: true, + backgroundAlpha: 0, + preference: 'webgl', + eventMode: 'none', + failIfMajorPerformanceCaveat: false, + roundPixels: true, + useBackBuffer: true // Enable back buffer for blend filters + }); + + await GetConfiguration().init(); + + GetTicker().maxFPS = GetConfiguration().getValue('system.fps.max', 24); + NitroLogger.LOG_DEBUG = GetConfiguration().getValue('system.log.debug', true); + NitroLogger.LOG_WARN = GetConfiguration().getValue('system.log.warn', false); + NitroLogger.LOG_ERROR = GetConfiguration().getValue('system.log.error', false); + NitroLogger.LOG_EVENTS = GetConfiguration().getValue('system.log.events', false); + NitroLogger.LOG_PACKETS = GetConfiguration().getValue('system.log.packets', false); + + const assetUrls = GetConfiguration().getValue('preload.assets.urls').map(url => GetConfiguration().interpolate(url)) ?? []; + + await Promise.all( + [ + GetAssetManager().downloadAssets(assetUrls), + GetLocalizationManager().init(), + GetAvatarRenderManager().init(), + GetSoundManager().init(), + GetSessionDataManager().init(), + GetRoomSessionManager().init() + ] + ); + + await GetRoomEngine().init(); + await GetCommunication().init(); + + if(LegacyExternalInterface.available) LegacyExternalInterface.call('legacyTrack', 'authentication', 'authok', []); + + HabboWebTools.sendHeartBeat(); + + setInterval(() => HabboWebTools.sendHeartBeat(), 10000); + + GetTicker().add(ticker => GetRoomEngine().update(ticker)); + GetTicker().add(ticker => renderer.render(GetStage())); + GetTicker().add(ticker => GetTexturePool().run()); + + setIsReady(true); + } + catch(err) + { + NitroLogger.error(err); + } + }; + + prepare(window.innerWidth, window.innerHeight); + }, []); + + return ( + + { !isReady && + } + { isReady && } + + + ); +}; \ No newline at end of file diff --git a/src/api/GetRendererVersion.ts b/src/api/GetRendererVersion.ts new file mode 100644 index 0000000..bb9e461 --- /dev/null +++ b/src/api/GetRendererVersion.ts @@ -0,0 +1,3 @@ +import { NitroVersion } from '@nitrots/nitro-renderer'; + +export const GetRendererVersion = () => NitroVersion.RENDERER_VERSION; diff --git a/src/api/GetUIVersion.ts b/src/api/GetUIVersion.ts new file mode 100644 index 0000000..bdbe922 --- /dev/null +++ b/src/api/GetUIVersion.ts @@ -0,0 +1 @@ +export const GetUIVersion = () => '2.2.0'; diff --git a/src/api/achievements/AchievementCategory.ts b/src/api/achievements/AchievementCategory.ts new file mode 100644 index 0000000..906d8da --- /dev/null +++ b/src/api/achievements/AchievementCategory.ts @@ -0,0 +1,40 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { AchievementUtilities } from './AchievementUtilities'; +import { IAchievementCategory } from './IAchievementCategory'; + +export class AchievementCategory implements IAchievementCategory +{ + private _code: string; + private _achievements: AchievementData[]; + + constructor(code: string) + { + this._code = code; + this._achievements = []; + } + + public getProgress(): number + { + return AchievementUtilities.getAchievementCategoryProgress(this); + } + + public getMaxProgress(): number + { + return AchievementUtilities.getAchievementCategoryMaxProgress(this); + } + + public get code(): string + { + return this._code; + } + + public get achievements(): AchievementData[] + { + return this._achievements; + } + + public set achievements(achievements: AchievementData[]) + { + this._achievements = achievements; + } +} diff --git a/src/api/achievements/AchievementUtilities.ts b/src/api/achievements/AchievementUtilities.ts new file mode 100644 index 0000000..30f1403 --- /dev/null +++ b/src/api/achievements/AchievementUtilities.ts @@ -0,0 +1,97 @@ +import { AchievementData, GetLocalizationManager } from '@nitrots/nitro-renderer'; +import { GetConfigurationValue } from '../nitro'; +import { IAchievementCategory } from './IAchievementCategory'; + +export class AchievementUtilities +{ + public static getAchievementBadgeCode(achievement: AchievementData): string + { + if(!achievement) return null; + + let badgeId = achievement.badgeId; + + if(!achievement.finalLevel) badgeId = GetLocalizationManager().getPreviousLevelBadgeId(badgeId); + + return badgeId; + } + + public static getAchievementCategoryImageUrl(category: IAchievementCategory, progress: number = null, icon: boolean = false): string + { + const imageUrl = GetConfigurationValue('achievements.images.url'); + + let imageName = icon ? 'achicon_' : 'achcategory_'; + + imageName += category.code; + + if(progress !== null) imageName += `_${ ((progress > 0) ? 'active' : 'inactive') }`; + + return imageUrl.replace('%image%', imageName); + } + + public static getAchievementCategoryMaxProgress(category: IAchievementCategory): number + { + if(!category) return 0; + + let progress = 0; + + for(const achievement of category.achievements) + { + progress += achievement.levelCount; + } + + return progress; + } + + public static getAchievementCategoryProgress(category: IAchievementCategory): number + { + if(!category) return 0; + + let progress = 0; + + for(const achievement of category.achievements) progress += (achievement.finalLevel ? achievement.level : (achievement.level - 1)); + + return progress; + } + + public static getAchievementCategoryTotalUnseen(category: IAchievementCategory): number + { + if(!category) return 0; + + let unseen = 0; + + for(const achievement of category.achievements) ((achievement.unseen > 0) && unseen++); + + return unseen; + } + + public static getAchievementHasStarted(achievement: AchievementData): boolean + { + if(!achievement) return false; + + if(achievement.finalLevel || ((achievement.level - 1) > 0)) return true; + + return false; + } + + public static getAchievementIsIgnored(achievement: AchievementData): boolean + { + if(!achievement) return false; + + const ignored = GetConfigurationValue('achievements.unseen.ignored'); + const value = achievement.badgeId.replace(/[0-9]/g, ''); + const index = ignored.indexOf(value); + + if(index >= 0) return true; + + return false; + } + + public static getAchievementLevel(achievement: AchievementData): number + { + if(!achievement) return 0; + + if(achievement.finalLevel) return achievement.level; + + return (achievement.level - 1); + } +} diff --git a/src/api/achievements/IAchievementCategory.ts b/src/api/achievements/IAchievementCategory.ts new file mode 100644 index 0000000..a049d46 --- /dev/null +++ b/src/api/achievements/IAchievementCategory.ts @@ -0,0 +1,7 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; + +export interface IAchievementCategory +{ + code: string; + achievements: AchievementData[]; +} diff --git a/src/api/achievements/index.ts b/src/api/achievements/index.ts new file mode 100644 index 0000000..a3d44b7 --- /dev/null +++ b/src/api/achievements/index.ts @@ -0,0 +1,3 @@ +export * from './AchievementCategory'; +export * from './AchievementUtilities'; +export * from './IAchievementCategory'; diff --git a/src/api/avatar/AvatarEditorAction.ts b/src/api/avatar/AvatarEditorAction.ts new file mode 100644 index 0000000..064d6df --- /dev/null +++ b/src/api/avatar/AvatarEditorAction.ts @@ -0,0 +1,7 @@ +export class AvatarEditorAction +{ + public static ACTION_SAVE: string = 'AEA_ACTION_SAVE'; + public static ACTION_CLEAR: string = 'AEA_ACTION_CLEAR'; + public static ACTION_RESET: string = 'AEA_ACTION_RESET'; + public static ACTION_RANDOMIZE: string = 'AEA_ACTION_RANDOMIZE'; +} diff --git a/src/api/avatar/AvatarEditorColorSorter.ts b/src/api/avatar/AvatarEditorColorSorter.ts new file mode 100644 index 0000000..7ae5960 --- /dev/null +++ b/src/api/avatar/AvatarEditorColorSorter.ts @@ -0,0 +1,17 @@ +import { IPartColor } from '@nitrots/nitro-renderer'; + +export const AvatarEditorColorSorter = (a: IPartColor, b: IPartColor) => +{ + const clubLevelA = (!a ? -1 : a.clubLevel); + const clubLevelB = (!b ? -1 : b.clubLevel); + + if(clubLevelA < clubLevelB) return -1; + + if(clubLevelA > clubLevelB) return 1; + + if(a.index < b.index) return -1; + + if(a.index > b.index) return 1; + + return 0; +}; diff --git a/src/api/avatar/AvatarEditorPartSorter.ts b/src/api/avatar/AvatarEditorPartSorter.ts new file mode 100644 index 0000000..41e98d8 --- /dev/null +++ b/src/api/avatar/AvatarEditorPartSorter.ts @@ -0,0 +1,35 @@ +import { IFigurePartSet } from '@nitrots/nitro-renderer'; + +export const AvatarEditorPartSorter = (hcFirst: boolean) => +{ + return (a: { partSet: IFigurePartSet, usesColor: boolean, isClear?: boolean }, b: { partSet: IFigurePartSet, usesColor: boolean, isClear?: boolean }) => + { + const clubLevelA = (!a.partSet ? -1 : a.partSet.clubLevel); + const clubLevelB = (!b.partSet ? -1 : b.partSet.clubLevel); + const isSellableA = (!a.partSet ? false : a.partSet.isSellable); + const isSellableB = (!b.partSet ? false : b.partSet.isSellable); + + if(isSellableA && !isSellableB) return 1; + + if(isSellableB && !isSellableA) return -1; + + if(hcFirst) + { + if(clubLevelA > clubLevelB) return -1; + + if(clubLevelA < clubLevelB) return 1; + } + else + { + if(clubLevelA < clubLevelB) return -1; + + if(clubLevelA > clubLevelB) return 1; + } + + if(a.partSet.id < b.partSet.id) return -1; + + if(a.partSet.id > b.partSet.id) return 1; + + return 0; + }; +}; diff --git a/src/api/avatar/AvatarEditorThumbnailsHelper.ts b/src/api/avatar/AvatarEditorThumbnailsHelper.ts new file mode 100644 index 0000000..88a7906 --- /dev/null +++ b/src/api/avatar/AvatarEditorThumbnailsHelper.ts @@ -0,0 +1,196 @@ +import { AvatarFigurePartType, AvatarScaleType, AvatarSetType, GetAssetManager, GetAvatarRenderManager, IFigurePart, IGraphicAsset, IPartColor, NitroAlphaFilter, NitroContainer, NitroRectangle, NitroSprite, TextureUtils } from '@nitrots/nitro-renderer'; +import { IAvatarEditorCategoryPartItem } from './IAvatarEditorCategoryPartItem'; + +export class AvatarEditorThumbnailsHelper +{ + private static THUMBNAIL_CACHE: Map = new Map(); + private static THUMB_DIRECTIONS: number[] = [ 2, 6, 0, 4, 3, 1 ]; + private static ALPHA_FILTER: NitroAlphaFilter = new NitroAlphaFilter({ alpha: 0.2 }); + private static DRAW_ORDER: string[] = [ + AvatarFigurePartType.LEFT_HAND_ITEM, + AvatarFigurePartType.LEFT_HAND, + AvatarFigurePartType.LEFT_SLEEVE, + AvatarFigurePartType.LEFT_COAT_SLEEVE, + AvatarFigurePartType.BODY, + AvatarFigurePartType.SHOES, + AvatarFigurePartType.LEGS, + AvatarFigurePartType.CHEST, + AvatarFigurePartType.CHEST_ACCESSORY, + AvatarFigurePartType.COAT_CHEST, + AvatarFigurePartType.CHEST_PRINT, + AvatarFigurePartType.WAIST_ACCESSORY, + AvatarFigurePartType.RIGHT_HAND, + AvatarFigurePartType.RIGHT_SLEEVE, + AvatarFigurePartType.RIGHT_COAT_SLEEVE, + AvatarFigurePartType.HEAD, + AvatarFigurePartType.FACE, + AvatarFigurePartType.EYES, + AvatarFigurePartType.HAIR, + AvatarFigurePartType.HAIR_BIG, + AvatarFigurePartType.FACE_ACCESSORY, + AvatarFigurePartType.EYE_ACCESSORY, + AvatarFigurePartType.HEAD_ACCESSORY, + AvatarFigurePartType.HEAD_ACCESSORY_EXTRA, + AvatarFigurePartType.RIGHT_HAND_ITEM, + ]; + + private static getThumbnailKey(setType: string, part: IAvatarEditorCategoryPartItem): string + { + return `${ setType }-${ part.partSet.id }`; + } + + public static clearCache(): void + { + this.THUMBNAIL_CACHE.clear(); + } + + public static async build(setType: string, part: IAvatarEditorCategoryPartItem, useColors: boolean, partColors: IPartColor[], isDisabled: boolean = false): Promise + { + if(!setType || !setType.length || !part || !part.partSet || !part.partSet.parts || !part.partSet.parts.length) return null; + + const thumbnailKey = this.getThumbnailKey(setType, part); + const cached = this.THUMBNAIL_CACHE.get(thumbnailKey); + + if(cached) return cached; + + const buildContainer = (part: IAvatarEditorCategoryPartItem, useColors: boolean, partColors: IPartColor[], isDisabled: boolean = false) => + { + const container = new NitroContainer(); + const parts = part.partSet.parts.concat().sort(this.sortByDrawOrder); + + for(const part of parts) + { + if(!part) continue; + + let asset: IGraphicAsset = null; + let direction = 0; + let hasAsset = false; + + while(!hasAsset && (direction < AvatarEditorThumbnailsHelper.THUMB_DIRECTIONS.length)) + { + const assetName = `${ AvatarFigurePartType.SCALE }_${ AvatarFigurePartType.STD }_${ part.type }_${ part.id }_${ AvatarEditorThumbnailsHelper.THUMB_DIRECTIONS[direction] }_${ AvatarFigurePartType.DEFAULT_FRAME }`; + + asset = GetAssetManager().getAsset(assetName); + + if(asset && asset.texture) + { + hasAsset = true; + } + else + { + direction++; + } + } + + if(!hasAsset) + { + console.log(`${ AvatarFigurePartType.SCALE }_${ AvatarFigurePartType.STD }_${ part.type }_${ part.id }`); + continue; + } + + const x = asset.offsetX; + const y = asset.offsetY; + + const sprite = new NitroSprite(asset.texture); + + sprite.position.set(x, y); + + if(useColors && (part.colorLayerIndex > 0) && partColors && partColors.length) + { + const color = partColors[(part.colorLayerIndex - 1)]; + + if(color) sprite.tint = color.rgb; + } + + if(isDisabled) container.filters = [ AvatarEditorThumbnailsHelper.ALPHA_FILTER ]; + + container.addChild(sprite); + } + + return container; + }; + + return new Promise(async (resolve, reject) => + { + const resetFigure = async (figure: string) => + { + const container = buildContainer(part, useColors, partColors, isDisabled); + const imageUrl = await TextureUtils.generateImageUrl(container); + + AvatarEditorThumbnailsHelper.THUMBNAIL_CACHE.set(thumbnailKey, imageUrl); + + resolve(imageUrl); + }; + + const figureContainer = GetAvatarRenderManager().createFigureContainer(`${ setType }-${ part.partSet.id }`); + + if(!GetAvatarRenderManager().isFigureContainerReady(figureContainer)) + { + GetAvatarRenderManager().downloadAvatarFigure(figureContainer, { + resetFigure, + dispose: null, + disposed: false + }); + } + else + { + resetFigure(null); + } + }); + } + + public static async buildForFace(figureString: string, isDisabled: boolean = false): Promise + { + if(!figureString || !figureString.length) return null; + + const thumbnailKey = figureString; + const cached = this.THUMBNAIL_CACHE.get(thumbnailKey); + + if(cached) return cached; + + return new Promise(async (resolve, reject) => + { + const resetFigure = async (figure: string) => + { + const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, null, { resetFigure, dispose: null, disposed: false }); + + if(avatarImage.isPlaceholder()) return; + + const texture = avatarImage.processAsTexture(AvatarSetType.HEAD, false); + const sprite = new NitroSprite(texture); + + if(isDisabled) sprite.filters = [ AvatarEditorThumbnailsHelper.ALPHA_FILTER ]; + + const imageUrl = await TextureUtils.generateImageUrl({ + target: sprite, + frame: new NitroRectangle(0, 0, texture.width, texture.height) + }); + + sprite.destroy(); + avatarImage.dispose(); + + AvatarEditorThumbnailsHelper.THUMBNAIL_CACHE.set(thumbnailKey, imageUrl); + + resolve(imageUrl); + }; + + resetFigure(figureString); + }); + } + + private static sortByDrawOrder(a: IFigurePart, b: IFigurePart): number + { + const indexA = AvatarEditorThumbnailsHelper.DRAW_ORDER.indexOf(a.type); + const indexB = AvatarEditorThumbnailsHelper.DRAW_ORDER.indexOf(b.type); + + if(indexA < indexB) return -1; + + if(indexA > indexB) return 1; + + if(a.index < b.index) return -1; + + if(a.index > b.index) return 1; + + return 0; + } +} diff --git a/src/api/avatar/IAvatarEditorCategory.ts b/src/api/avatar/IAvatarEditorCategory.ts new file mode 100644 index 0000000..a7cfd51 --- /dev/null +++ b/src/api/avatar/IAvatarEditorCategory.ts @@ -0,0 +1,9 @@ +import { IPartColor } from '@nitrots/nitro-renderer'; +import { IAvatarEditorCategoryPartItem } from './IAvatarEditorCategoryPartItem'; + +export interface IAvatarEditorCategory +{ + setType: string; + partItems: IAvatarEditorCategoryPartItem[]; + colorItems: IPartColor[][]; +} diff --git a/src/api/avatar/IAvatarEditorCategoryPartItem.ts b/src/api/avatar/IAvatarEditorCategoryPartItem.ts new file mode 100644 index 0000000..d1cbc0d --- /dev/null +++ b/src/api/avatar/IAvatarEditorCategoryPartItem.ts @@ -0,0 +1,10 @@ +import { IFigurePartSet } from '@nitrots/nitro-renderer'; + +export interface IAvatarEditorCategoryPartItem +{ + id?: number; + partSet?: IFigurePartSet; + usesColor?: boolean; + maxPaletteCount?: number; + isClear?: boolean; +} diff --git a/src/api/avatar/index.ts b/src/api/avatar/index.ts new file mode 100644 index 0000000..415185e --- /dev/null +++ b/src/api/avatar/index.ts @@ -0,0 +1,6 @@ +export * from './AvatarEditorAction'; +export * from './AvatarEditorColorSorter'; +export * from './AvatarEditorPartSorter'; +export * from './AvatarEditorThumbnailsHelper'; +export * from './IAvatarEditorCategory'; +export * from './IAvatarEditorCategoryPartItem'; diff --git a/src/api/camera/CameraEditorTabs.ts b/src/api/camera/CameraEditorTabs.ts new file mode 100644 index 0000000..6e894e7 --- /dev/null +++ b/src/api/camera/CameraEditorTabs.ts @@ -0,0 +1,5 @@ +export class CameraEditorTabs +{ + public static readonly COLORMATRIX: string = 'colormatrix'; + public static readonly COMPOSITE: string = 'composite'; +} diff --git a/src/api/camera/CameraPicture.ts b/src/api/camera/CameraPicture.ts new file mode 100644 index 0000000..020e1ec --- /dev/null +++ b/src/api/camera/CameraPicture.ts @@ -0,0 +1,9 @@ +import { NitroTexture } from '@nitrots/nitro-renderer'; + +export class CameraPicture +{ + constructor( + public texture: NitroTexture, + public imageUrl: string) + {} +} diff --git a/src/api/camera/CameraPictureThumbnail.ts b/src/api/camera/CameraPictureThumbnail.ts new file mode 100644 index 0000000..3e3f782 --- /dev/null +++ b/src/api/camera/CameraPictureThumbnail.ts @@ -0,0 +1,7 @@ +export class CameraPictureThumbnail +{ + constructor( + public effectName: string, + public thumbnailUrl: string) + {} +} diff --git a/src/api/camera/index.ts b/src/api/camera/index.ts new file mode 100644 index 0000000..93c6ccb --- /dev/null +++ b/src/api/camera/index.ts @@ -0,0 +1,3 @@ +export * from './CameraEditorTabs'; +export * from './CameraPicture'; +export * from './CameraPictureThumbnail'; diff --git a/src/api/campaign/CalendarItem.ts b/src/api/campaign/CalendarItem.ts new file mode 100644 index 0000000..d3634b3 --- /dev/null +++ b/src/api/campaign/CalendarItem.ts @@ -0,0 +1,30 @@ +import { ICalendarItem } from './ICalendarItem'; + +export class CalendarItem implements ICalendarItem +{ + private _productName: string; + private _customImage: string; + private _furnitureClassName: string; + + constructor(productName: string, customImage: string, furnitureClassName: string) + { + this._productName = productName; + this._customImage = customImage; + this._furnitureClassName = furnitureClassName; + } + + public get productName(): string + { + return this._productName; + } + + public get customImage(): string + { + return this._customImage; + } + + public get furnitureClassName(): string + { + return this._furnitureClassName; + } +} diff --git a/src/api/campaign/CalendarItemState.ts b/src/api/campaign/CalendarItemState.ts new file mode 100644 index 0000000..1b91ca3 --- /dev/null +++ b/src/api/campaign/CalendarItemState.ts @@ -0,0 +1,7 @@ +export class CalendarItemState +{ + public static readonly STATE_UNLOCKED = 1; + public static readonly STATE_LOCKED_AVAILABLE = 2; + public static readonly STATE_LOCKED_EXPIRED = 3; + public static readonly STATE_LOCKED_FUTURE = 4; +} diff --git a/src/api/campaign/ICalendarItem.ts b/src/api/campaign/ICalendarItem.ts new file mode 100644 index 0000000..87dfbd6 --- /dev/null +++ b/src/api/campaign/ICalendarItem.ts @@ -0,0 +1,6 @@ +export interface ICalendarItem +{ + readonly productName: string; + readonly customImage: string; + readonly furnitureClassName: string; +} diff --git a/src/api/campaign/index.ts b/src/api/campaign/index.ts new file mode 100644 index 0000000..a86e40c --- /dev/null +++ b/src/api/campaign/index.ts @@ -0,0 +1,3 @@ +export * from './CalendarItem'; +export * from './CalendarItemState'; +export * from './ICalendarItem'; diff --git a/src/api/catalog/BuilderFurniPlaceableStatus.ts b/src/api/catalog/BuilderFurniPlaceableStatus.ts new file mode 100644 index 0000000..40eb6f6 --- /dev/null +++ b/src/api/catalog/BuilderFurniPlaceableStatus.ts @@ -0,0 +1,10 @@ +export class BuilderFurniPlaceableStatus +{ + public static OKAY: number = 0; + public static MISSING_OFFER: number = 1; + public static FURNI_LIMIT_REACHED: number = 2; + public static NOT_IN_ROOM: number = 3; + public static NOT_ROOM_OWNER: number = 4; + public static GUILD_ROOM: number = 5; + public static VISITORS_IN_ROOM: number = 6; +} diff --git a/src/api/catalog/CatalogNode.ts b/src/api/catalog/CatalogNode.ts new file mode 100644 index 0000000..5e7c2fc --- /dev/null +++ b/src/api/catalog/CatalogNode.ts @@ -0,0 +1,124 @@ +import { NodeData } from '@nitrots/nitro-renderer'; +import { ICatalogNode } from './ICatalogNode'; + +export class CatalogNode implements ICatalogNode +{ + private _depth: number = 0; + private _localization: string = ''; + private _pageId: number = -1; + private _pageName: string = ''; + private _iconId: number = 0; + private _children: ICatalogNode[]; + private _offerIds: number[]; + private _parent: ICatalogNode; + private _isVisible: boolean; + private _isActive: boolean; + private _isOpen: boolean; + + constructor(node: NodeData, depth: number, parent: ICatalogNode) + { + this._depth = depth; + this._parent = parent; + this._localization = node.localization; + this._pageId = node.pageId; + this._pageName = node.pageName; + this._iconId = node.icon; + this._children = []; + this._offerIds = node.offerIds; + this._isVisible = node.visible; + this._isActive = false; + this._isOpen = false; + } + + public activate(): void + { + this._isActive = true; + } + + public deactivate(): void + { + this._isActive = false; + } + + public open(): void + { + this._isOpen = true; + } + + public close(): void + { + this._isOpen = false; + } + + public addChild(child: ICatalogNode):void + { + if(!child) return; + + this._children.push(child); + } + + public get depth(): number + { + return this._depth; + } + + public get isBranch(): boolean + { + return (this._children.length > 0); + } + + public get isLeaf(): boolean + { + return (this._children.length === 0); + } + + public get localization(): string + { + return this._localization; + } + + public get pageId(): number + { + return this._pageId; + } + + public get pageName(): string + { + return this._pageName; + } + + public get iconId(): number + { + return this._iconId; + } + + public get children(): ICatalogNode[] + { + return this._children; + } + + public get offerIds(): number[] + { + return this._offerIds; + } + + public get parent(): ICatalogNode + { + return this._parent; + } + + public get isVisible(): boolean + { + return this._isVisible; + } + + public get isActive(): boolean + { + return this._isActive; + } + + public get isOpen(): boolean + { + return this._isOpen; + } +} diff --git a/src/api/catalog/CatalogPage.ts b/src/api/catalog/CatalogPage.ts new file mode 100644 index 0000000..1e80609 --- /dev/null +++ b/src/api/catalog/CatalogPage.ts @@ -0,0 +1,59 @@ +import { ICatalogPage } from './ICatalogPage'; +import { IPageLocalization } from './IPageLocalization'; +import { IPurchasableOffer } from './IPurchasableOffer'; + +export class CatalogPage implements ICatalogPage +{ + public static MODE_NORMAL: number = 0; + + private _pageId: number; + private _layoutCode: string; + private _localization: IPageLocalization; + private _offers: IPurchasableOffer[]; + private _acceptSeasonCurrencyAsCredits: boolean; + private _mode: number; + + constructor(pageId: number, layoutCode: string, localization: IPageLocalization, offers: IPurchasableOffer[], acceptSeasonCurrencyAsCredits: boolean, mode: number = -1) + { + this._pageId = pageId; + this._layoutCode = layoutCode; + this._localization = localization; + this._offers = offers; + this._acceptSeasonCurrencyAsCredits = acceptSeasonCurrencyAsCredits; + + for(const offer of offers) (offer.page = this); + + if(mode === -1) this._mode = CatalogPage.MODE_NORMAL; + else this._mode = mode; + } + + public get pageId(): number + { + return this._pageId; + } + + public get layoutCode(): string + { + return this._layoutCode; + } + + public get localization(): IPageLocalization + { + return this._localization; + } + + public get offers(): IPurchasableOffer[] + { + return this._offers; + } + + public get acceptSeasonCurrencyAsCredits(): boolean + { + return this._acceptSeasonCurrencyAsCredits; + } + + public get mode(): number + { + return this._mode; + } +} diff --git a/src/api/catalog/CatalogPageName.ts b/src/api/catalog/CatalogPageName.ts new file mode 100644 index 0000000..ed217d8 --- /dev/null +++ b/src/api/catalog/CatalogPageName.ts @@ -0,0 +1,26 @@ +export class CatalogPageName +{ + public static DUCKET_INFO: string = 'ducket_info'; + public static CREDITS: string = 'credits'; + public static AVATAR_EFFECTS: string = 'avatar_effects'; + public static HC_MEMBERSHIP: string = 'hc_membership'; + public static CLUB_GIFTS: string = 'club_gifts'; + public static LIMITED_SOLD: string = 'limited_sold'; + public static PET_ACCESSORIES: string = 'pet_accessories'; + public static TRAX_SONGS: string = 'trax_songs'; + public static NEW_ADDITIONS: string = 'new_additions'; + public static QUEST_SHELL: string = 'quest_shell'; + public static QUEST_SNOWFLAKES: string = 'quest_snowflakes'; + public static VAL_QUESTS: string = 'val_quests'; + public static GUILD_CUSTOM_FURNI: string = 'guild_custom_furni'; + public static GIFT_SHOP: string = 'gift_shop'; + public static HORSE_STYLES: string = 'horse_styles'; + public static HORSE_SHOE: string = 'horse_shoe'; + public static SET_EASTER: string = 'set_easter'; + public static ECOTRON_TRANSFORM: string = 'ecotron_transform'; + public static LOYALTY_INFO: string = 'loyalty_info'; + public static ROOM_BUNDLES: string = 'room_bundles'; + public static ROOM_BUNDLES_MOBILE: string = 'room_bundles_mobile'; + public static HABBO_CLUB_DESKTOP: string = 'habbo_club_desktop'; + public static MOBILE_SUBSCRIPTIONS: string = 'mobile_subscriptions'; +} diff --git a/src/api/catalog/CatalogPetPalette.ts b/src/api/catalog/CatalogPetPalette.ts new file mode 100644 index 0000000..3b3c134 --- /dev/null +++ b/src/api/catalog/CatalogPetPalette.ts @@ -0,0 +1,10 @@ +import { SellablePetPaletteData } from '@nitrots/nitro-renderer'; + +export class CatalogPetPalette +{ + constructor( + public readonly breed: string, + public readonly palettes: SellablePetPaletteData[] + ) + {} +} diff --git a/src/api/catalog/CatalogPurchaseState.ts b/src/api/catalog/CatalogPurchaseState.ts new file mode 100644 index 0000000..b442f62 --- /dev/null +++ b/src/api/catalog/CatalogPurchaseState.ts @@ -0,0 +1,10 @@ +export class CatalogPurchaseState +{ + public static NONE = 0; + public static CONFIRM = 1; + public static PURCHASE = 2; + public static NO_CREDITS = 3; + public static NO_POINTS = 4; + public static SOLD_OUT = 5; + public static FAILED = 6; +} diff --git a/src/api/catalog/CatalogType.ts b/src/api/catalog/CatalogType.ts new file mode 100644 index 0000000..670ad6f --- /dev/null +++ b/src/api/catalog/CatalogType.ts @@ -0,0 +1,5 @@ +export class CatalogType +{ + public static NORMAL: string = 'NORMAL'; + public static BUILDER: string = 'BUILDERS_CLUB'; +} diff --git a/src/api/catalog/CatalogUtilities.ts b/src/api/catalog/CatalogUtilities.ts new file mode 100644 index 0000000..c2e1d5e --- /dev/null +++ b/src/api/catalog/CatalogUtilities.ts @@ -0,0 +1,124 @@ +import { GetRoomEngine, SellablePetPaletteData } from '@nitrots/nitro-renderer'; +import { ICatalogNode } from './ICatalogNode'; + +export const GetPixelEffectIcon = (id: number) => +{ + return ''; +}; + +export const GetSubscriptionProductIcon = (id: number) => +{ + return ''; +}; + +export const GetOfferNodes = (offerNodes: Map, offerId: number) => +{ + const nodes = offerNodes.get(offerId); + const allowedNodes: ICatalogNode[] = []; + + if(nodes && nodes.length) + { + for(const node of nodes) + { + if(!node.isVisible) continue; + + allowedNodes.push(node); + } + } + + return allowedNodes; +}; + +export const FilterCatalogNode = (search: string, furniLines: string[], node: ICatalogNode, nodes: ICatalogNode[]) => +{ + if(node.isVisible && (node.pageId > 0)) + { + let nodeAdded = false; + + const hayStack = [ node.pageName, node.localization ].join(' ').toLowerCase().replace(/ /gi, ''); + + if(hayStack.indexOf(search) > -1) + { + nodes.push(node); + + nodeAdded = true; + } + + if(!nodeAdded) + { + for(const furniLine of furniLines) + { + if(hayStack.indexOf(furniLine) >= 0) + { + nodes.push(node); + + break; + } + } + } + } + + for(const child of node.children) FilterCatalogNode(search, furniLines, child, nodes); +}; + +export function GetPetIndexFromLocalization(localization: string) +{ + if(!localization.length) return 0; + + let index = (localization.length - 1); + + while(index >= 0) + { + if(isNaN(parseInt(localization.charAt(index)))) break; + + index--; + } + + if(index > 0) return parseInt(localization.substring(index + 1)); + + return -1; +} + +export function GetPetAvailableColors(petIndex: number, palettes: SellablePetPaletteData[]): number[][] +{ + switch(petIndex) + { + case 0: + return [ [ 16743226 ], [ 16750435 ], [ 16764339 ], [ 0xF59500 ], [ 16498012 ], [ 16704690 ], [ 0xEDD400 ], [ 16115545 ], [ 16513201 ], [ 8694111 ], [ 11585939 ], [ 14413767 ], [ 6664599 ], [ 9553845 ], [ 12971486 ], [ 8358322 ], [ 10002885 ], [ 13292268 ], [ 10780600 ], [ 12623573 ], [ 14403561 ], [ 12418717 ], [ 14327229 ], [ 15517403 ], [ 14515069 ], [ 15764368 ], [ 16366271 ], [ 0xABABAB ], [ 0xD4D4D4 ], [ 0xFFFFFF ], [ 14256481 ], [ 14656129 ], [ 15848130 ], [ 14005087 ], [ 14337152 ], [ 15918540 ], [ 15118118 ], [ 15531929 ], [ 9764857 ], [ 11258085 ] ]; + case 1: + return [ [ 16743226 ], [ 16750435 ], [ 16764339 ], [ 0xF59500 ], [ 16498012 ], [ 16704690 ], [ 0xEDD400 ], [ 16115545 ], [ 16513201 ], [ 8694111 ], [ 11585939 ], [ 14413767 ], [ 6664599 ], [ 9553845 ], [ 12971486 ], [ 8358322 ], [ 10002885 ], [ 13292268 ], [ 10780600 ], [ 12623573 ], [ 14403561 ], [ 12418717 ], [ 14327229 ], [ 15517403 ], [ 14515069 ], [ 15764368 ], [ 16366271 ], [ 0xABABAB ], [ 0xD4D4D4 ], [ 0xFFFFFF ], [ 14256481 ], [ 14656129 ], [ 15848130 ], [ 14005087 ], [ 14337152 ], [ 15918540 ], [ 15118118 ], [ 15531929 ], [ 9764857 ], [ 11258085 ] ]; + case 2: + return [ [ 16579283 ], [ 15378351 ], [ 8830016 ], [ 15257125 ], [ 9340985 ], [ 8949607 ], [ 6198292 ], [ 8703620 ], [ 9889626 ], [ 8972045 ], [ 12161285 ], [ 13162269 ], [ 8620113 ], [ 12616503 ], [ 8628101 ], [ 0xD2FF00 ], [ 9764857 ] ]; + case 3: + return [ [ 0xFFFFFF ], [ 0xEEEEEE ], [ 0xDDDDDD ] ]; + case 4: + return [ [ 0xFFFFFF ], [ 16053490 ], [ 15464440 ], [ 16248792 ], [ 15396319 ], [ 15007487 ] ]; + case 5: + return [ [ 0xFFFFFF ], [ 0xEEEEEE ], [ 0xDDDDDD ] ]; + case 6: + return [ [ 0xFFFFFF ], [ 0xEEEEEE ], [ 0xDDDDDD ], [ 16767177 ], [ 16770205 ], [ 16751331 ] ]; + case 7: + return [ [ 0xCCCCCC ], [ 0xAEAEAE ], [ 16751331 ], [ 10149119 ], [ 16763290 ], [ 16743786 ] ]; + default: { + const colors: number[][] = []; + + for(const palette of palettes) + { + const petColorResult = GetRoomEngine().getPetColorResult(petIndex, palette.paletteId); + + if(!petColorResult) continue; + + if(petColorResult.primaryColor === petColorResult.secondaryColor) + { + colors.push([ petColorResult.primaryColor ]); + } + else + { + colors.push([ petColorResult.primaryColor, petColorResult.secondaryColor ]); + } + } + + return colors; + } + } +} diff --git a/src/api/catalog/FurnitureOffer.ts b/src/api/catalog/FurnitureOffer.ts new file mode 100644 index 0000000..367f247 --- /dev/null +++ b/src/api/catalog/FurnitureOffer.ts @@ -0,0 +1,120 @@ +import { GetProductOfferComposer, IFurnitureData } from '@nitrots/nitro-renderer'; +import { GetProductDataForLocalization, SendMessageComposer } from '../nitro'; +import { ICatalogPage } from './ICatalogPage'; +import { IProduct } from './IProduct'; +import { IPurchasableOffer } from './IPurchasableOffer'; +import { Offer } from './Offer'; +import { Product } from './Product'; + +export class FurnitureOffer implements IPurchasableOffer +{ + private _furniData:IFurnitureData; + private _page: ICatalogPage; + private _product: IProduct; + + constructor(furniData: IFurnitureData) + { + this._furniData = furniData; + this._product = (new Product(this._furniData.type, this._furniData.id, this._furniData.customParams, 1, GetProductDataForLocalization(this._furniData.className), this._furniData) as IProduct); + } + + public activate(): void + { + SendMessageComposer(new GetProductOfferComposer((this._furniData.rentOfferId > -1) ? this._furniData.rentOfferId : this._furniData.purchaseOfferId)); + } + + public get offerId(): number + { + return (this.isRentOffer) ? this._furniData.rentOfferId : this._furniData.purchaseOfferId; + } + + public get priceInActivityPoints(): number + { + return 0; + } + + public get activityPointType(): number + { + return 0; + } + + public get priceInCredits(): number + { + return 0; + } + + public get page(): ICatalogPage + { + return this._page; + } + + public set page(page: ICatalogPage) + { + this._page = page; + } + + public get priceType(): string + { + return ''; + } + + public get product(): IProduct + { + return this._product; + } + + public get products(): IProduct[] + { + return [ this._product ]; + } + + public get localizationId(): string + { + return 'roomItem.name.' + this._furniData.id; + } + + public get bundlePurchaseAllowed(): boolean + { + return false; + } + + public get isRentOffer(): boolean + { + return (this._furniData.rentOfferId > -1); + } + + public get giftable(): boolean + { + return false; + } + + public get pricingModel(): string + { + return Offer.PRICING_MODEL_FURNITURE; + } + + public get clubLevel(): number + { + return 0; + } + + public get badgeCode(): string + { + return ''; + } + + public get localizationName(): string + { + return this._furniData.name; + } + + public get localizationDescription(): string + { + return this._furniData.description; + } + + public get isLazy(): boolean + { + return true; + } +} diff --git a/src/api/catalog/GetImageIconUrlForProduct.ts b/src/api/catalog/GetImageIconUrlForProduct.ts new file mode 100644 index 0000000..f0d195d --- /dev/null +++ b/src/api/catalog/GetImageIconUrlForProduct.ts @@ -0,0 +1,19 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; +import { ProductTypeEnum } from './ProductTypeEnum'; + +export const GetImageIconUrlForProduct = (productType: string, productClassId: number, extraData: string = null) => +{ + let imageUrl: string = null; + + switch(productType.toLocaleLowerCase()) + { + case ProductTypeEnum.FLOOR: + imageUrl = GetRoomEngine().getFurnitureFloorIconUrl(productClassId); + break; + case ProductTypeEnum.WALL: + imageUrl = GetRoomEngine().getFurnitureWallIconUrl(productClassId, extraData); + break; + } + + return imageUrl; +}; diff --git a/src/api/catalog/GiftWrappingConfiguration.ts b/src/api/catalog/GiftWrappingConfiguration.ts new file mode 100644 index 0000000..9d29b8c --- /dev/null +++ b/src/api/catalog/GiftWrappingConfiguration.ts @@ -0,0 +1,51 @@ +import { GiftWrappingConfigurationParser } from '@nitrots/nitro-renderer'; + +export class GiftWrappingConfiguration +{ + private _isEnabled: boolean = false; + private _price: number = null; + private _stuffTypes: number[] = null; + private _boxTypes: number[] = null; + private _ribbonTypes: number[] = null; + private _defaultStuffTypes: number[] = null; + + constructor(parser: GiftWrappingConfigurationParser) + { + this._isEnabled = parser.isEnabled; + this._price = parser.price; + this._boxTypes = parser.boxTypes; + this._ribbonTypes = parser.ribbonTypes; + this._stuffTypes = parser.giftWrappers; + this._defaultStuffTypes = parser.giftFurnis; + } + + public get isEnabled(): boolean + { + return this._isEnabled; + } + + public get price(): number + { + return this._price; + } + + public get stuffTypes(): number[] + { + return this._stuffTypes; + } + + public get boxTypes(): number[] + { + return this._boxTypes; + } + + public get ribbonTypes(): number[] + { + return this._ribbonTypes; + } + + public get defaultStuffTypes(): number[] + { + return this._defaultStuffTypes; + } +} diff --git a/src/api/catalog/ICatalogNode.ts b/src/api/catalog/ICatalogNode.ts new file mode 100644 index 0000000..6253a75 --- /dev/null +++ b/src/api/catalog/ICatalogNode.ts @@ -0,0 +1,21 @@ +export interface ICatalogNode +{ + activate(): void; + deactivate(): void; + open(): void; + close(): void; + addChild(node: ICatalogNode): void; + readonly depth: number; + readonly isBranch: boolean; + readonly isLeaf: boolean; + readonly localization: string; + readonly pageId: number; + readonly pageName: string; + readonly iconId: number; + readonly children: ICatalogNode[]; + readonly offerIds: number[]; + readonly parent: ICatalogNode; + readonly isVisible: boolean; + readonly isActive: boolean; + readonly isOpen: boolean; +} diff --git a/src/api/catalog/ICatalogOptions.ts b/src/api/catalog/ICatalogOptions.ts new file mode 100644 index 0000000..2035694 --- /dev/null +++ b/src/api/catalog/ICatalogOptions.ts @@ -0,0 +1,13 @@ +import { ClubGiftInfoParser, ClubOfferData, HabboGroupEntryData, MarketplaceConfigurationMessageParser } from '@nitrots/nitro-renderer'; +import { CatalogPetPalette } from './CatalogPetPalette'; +import { GiftWrappingConfiguration } from './GiftWrappingConfiguration'; + +export interface ICatalogOptions +{ + groups?: HabboGroupEntryData[]; + petPalettes?: CatalogPetPalette[]; + clubOffers?: ClubOfferData[]; + clubGifts?: ClubGiftInfoParser; + giftConfiguration?: GiftWrappingConfiguration; + marketplaceConfiguration?: MarketplaceConfigurationMessageParser; +} diff --git a/src/api/catalog/ICatalogPage.ts b/src/api/catalog/ICatalogPage.ts new file mode 100644 index 0000000..ed11ba0 --- /dev/null +++ b/src/api/catalog/ICatalogPage.ts @@ -0,0 +1,12 @@ +import { IPageLocalization } from './IPageLocalization'; +import { IPurchasableOffer } from './IPurchasableOffer'; + +export interface ICatalogPage +{ + readonly pageId: number; + readonly layoutCode: string; + readonly localization: IPageLocalization; + readonly offers: IPurchasableOffer[]; + readonly acceptSeasonCurrencyAsCredits: boolean; + readonly mode: number; +} diff --git a/src/api/catalog/IMarketplaceSearchOptions.ts b/src/api/catalog/IMarketplaceSearchOptions.ts new file mode 100644 index 0000000..9489ef0 --- /dev/null +++ b/src/api/catalog/IMarketplaceSearchOptions.ts @@ -0,0 +1,7 @@ +export interface IMarketplaceSearchOptions +{ + query: string; + type: number; + minPrice: number; + maxPrice: number; +} diff --git a/src/api/catalog/IPageLocalization.ts b/src/api/catalog/IPageLocalization.ts new file mode 100644 index 0000000..ad652e1 --- /dev/null +++ b/src/api/catalog/IPageLocalization.ts @@ -0,0 +1,5 @@ +export interface IPageLocalization +{ + getText(index: number): string + getImage(index: number): string +} diff --git a/src/api/catalog/IProduct.ts b/src/api/catalog/IProduct.ts new file mode 100644 index 0000000..4a1a392 --- /dev/null +++ b/src/api/catalog/IProduct.ts @@ -0,0 +1,16 @@ +import { IFurnitureData, IProductData } from '@nitrots/nitro-renderer'; +import { IPurchasableOffer } from './IPurchasableOffer'; + +export interface IProduct +{ + getIconUrl(offer?: IPurchasableOffer): string; + productType: string; + productClassId: number; + extraParam: string; + productCount: number; + productData: IProductData; + furnitureData: IFurnitureData; + isUniqueLimitedItem: boolean; + uniqueLimitedItemSeriesSize: number; + uniqueLimitedItemsLeft: number; +} diff --git a/src/api/catalog/IPurchasableOffer.ts b/src/api/catalog/IPurchasableOffer.ts new file mode 100644 index 0000000..b182865 --- /dev/null +++ b/src/api/catalog/IPurchasableOffer.ts @@ -0,0 +1,25 @@ +import { ICatalogPage } from './ICatalogPage'; +import { IProduct } from './IProduct'; + +export interface IPurchasableOffer +{ + activate(): void; + clubLevel: number; + page: ICatalogPage; + offerId: number; + localizationId: string; + priceInCredits: number; + priceInActivityPoints: number; + activityPointType: number; + giftable: boolean; + product: IProduct; + pricingModel: string; + priceType: string; + bundlePurchaseAllowed: boolean; + isRentOffer: boolean; + badgeCode: string; + localizationName: string; + localizationDescription: string; + isLazy: boolean; + products: IProduct[]; +} diff --git a/src/api/catalog/IPurchaseOptions.ts b/src/api/catalog/IPurchaseOptions.ts new file mode 100644 index 0000000..c9fab89 --- /dev/null +++ b/src/api/catalog/IPurchaseOptions.ts @@ -0,0 +1,9 @@ +import { IObjectData } from '@nitrots/nitro-renderer'; + +export interface IPurchaseOptions +{ + quantity?: number; + extraData?: string; + extraParamRequired?: boolean; + previewStuffData?: IObjectData; +} diff --git a/src/api/catalog/MarketplaceOfferData.ts b/src/api/catalog/MarketplaceOfferData.ts new file mode 100644 index 0000000..ba1fa88 --- /dev/null +++ b/src/api/catalog/MarketplaceOfferData.ts @@ -0,0 +1,128 @@ +import { IObjectData } from '@nitrots/nitro-renderer'; + +export class MarketplaceOfferData +{ + public static readonly TYPE_FLOOR: number = 1; + public static readonly TYPE_WALL: number = 2; + + private _offerId: number; + private _furniId: number; + private _furniType: number; + private _extraData: string; + private _stuffData: IObjectData; + private _price: number; + private _averagePrice: number; + private _imageCallback: number; + private _status: number; + private _timeLeftMinutes: number = -1; + private _offerCount: number; + private _image: string; + + constructor(offerId: number, furniId: number, furniType: number, extraData: string, stuffData: IObjectData, price: number, status: number, averagePrice: number, offerCount: number = -1) + { + this._offerId = offerId; + this._furniId = furniId; + this._furniType = furniType; + this._extraData = extraData; + this._stuffData = stuffData; + this._price = price; + this._status = status; + this._averagePrice = averagePrice; + this._offerCount = offerCount; + } + + public get offerId(): number + { + return this._offerId; + } + + public set offerId(offerId: number) + { + this._offerId = offerId; + } + + public get furniId(): number + { + return this._furniId; + } + + public get furniType(): number + { + return this._furniType; + } + + public get extraData(): string + { + return this._extraData; + } + + public get stuffData(): IObjectData + { + return this._stuffData; + } + + public get price(): number + { + return this._price; + } + + public set price(price: number) + { + this._price = price; + } + + public get averagePrice(): number + { + return this._averagePrice; + } + + public get image(): string + { + return this._image; + } + + public set image(image: string) + { + this._image = image; + } + + public get imageCallback(): number + { + return this._imageCallback; + } + + public set imageCallback(callback: number) + { + this._imageCallback = callback; + } + + public get status(): number + { + return this._status; + } + + public get timeLeftMinutes(): number + { + return this._timeLeftMinutes; + } + + public set timeLeftMinutes(minutes: number) + { + this._timeLeftMinutes = minutes; + } + + public get offerCount(): number + { + return this._offerCount; + } + + public set offerCount(count: number) + { + this._offerCount = count; + } + + public get isUniqueLimitedItem(): boolean + { + return (this.stuffData && (this.stuffData.uniqueSeries > 0)); + } +} diff --git a/src/api/catalog/MarketplaceOfferState.ts b/src/api/catalog/MarketplaceOfferState.ts new file mode 100644 index 0000000..6267a5f --- /dev/null +++ b/src/api/catalog/MarketplaceOfferState.ts @@ -0,0 +1,7 @@ +export class MarketPlaceOfferState +{ + public static readonly ONGOING = 1; + public static readonly ONGOING_OWN = 1; + public static readonly SOLD = 2; + public static readonly EXPIRED = 3; +} diff --git a/src/api/catalog/MarketplaceSearchType.ts b/src/api/catalog/MarketplaceSearchType.ts new file mode 100644 index 0000000..ac7a701 --- /dev/null +++ b/src/api/catalog/MarketplaceSearchType.ts @@ -0,0 +1,6 @@ +export class MarketplaceSearchType +{ + public static readonly BY_ACTIVITY = 1; + public static readonly BY_VALUE = 2; + public static readonly ADVANCED = 3; +} diff --git a/src/api/catalog/Offer.ts b/src/api/catalog/Offer.ts new file mode 100644 index 0000000..9182c03 --- /dev/null +++ b/src/api/catalog/Offer.ts @@ -0,0 +1,245 @@ +import { GetFurnitureData, GetProductDataForLocalization, LocalizeText, ProductTypeEnum } from '..'; +import { ICatalogPage } from './ICatalogPage'; +import { IProduct } from './IProduct'; +import { IPurchasableOffer } from './IPurchasableOffer'; +import { Product } from './Product'; + +export class Offer implements IPurchasableOffer +{ + public static PRICING_MODEL_UNKNOWN: string = 'pricing_model_unknown'; + public static PRICING_MODEL_SINGLE: string = 'pricing_model_single'; + public static PRICING_MODEL_MULTI: string = 'pricing_model_multi'; + public static PRICING_MODEL_BUNDLE: string = 'pricing_model_bundle'; + public static PRICING_MODEL_FURNITURE: string = 'pricing_model_furniture'; + public static PRICE_TYPE_NONE: string = 'price_type_none'; + public static PRICE_TYPE_CREDITS: string = 'price_type_credits'; + public static PRICE_TYPE_ACTIVITYPOINTS: string = 'price_type_activitypoints'; + public static PRICE_TYPE_CREDITS_ACTIVITYPOINTS: string = 'price_type_credits_and_activitypoints'; + + private _pricingModel: string; + private _priceType: string; + private _offerId: number; + private _localizationId: string; + private _priceInCredits: number; + private _priceInActivityPoints: number; + private _activityPointType: number; + private _giftable: boolean; + private _isRentOffer: boolean; + private _page: ICatalogPage; + private _clubLevel: number = 0; + private _products: IProduct[]; + private _badgeCode: string; + private _bundlePurchaseAllowed: boolean = false; + + constructor(offerId: number, localizationId: string, isRentOffer: boolean, priceInCredits: number, priceInActivityPoints: number, activityPointType: number, giftable: boolean, clubLevel: number, products: IProduct[], bundlePurchaseAllowed: boolean) + { + this._offerId = offerId; + this._localizationId = localizationId; + this._isRentOffer = isRentOffer; + this._priceInCredits = priceInCredits; + this._priceInActivityPoints = priceInActivityPoints; + this._activityPointType = activityPointType; + this._giftable = giftable; + this._clubLevel = clubLevel; + this._products = products; + this._bundlePurchaseAllowed = bundlePurchaseAllowed; + + this.setPricingModelForProducts(); + this.setPricingType(); + + for(const product of products) + { + if(product.productType === ProductTypeEnum.BADGE) + { + this._badgeCode = product.extraParam; + + break; + } + } + } + + public activate(): void + { + + } + + public get clubLevel(): number + { + return this._clubLevel; + } + + public get page(): ICatalogPage + { + return this._page; + } + + public set page(k: ICatalogPage) + { + this._page = k; + } + + public get offerId(): number + { + return this._offerId; + } + + public get localizationId(): string + { + return this._localizationId; + } + + public get priceInCredits(): number + { + return this._priceInCredits; + } + + public get priceInActivityPoints(): number + { + return this._priceInActivityPoints; + } + + public get activityPointType(): number + { + return this._activityPointType; + } + + public get giftable(): boolean + { + return this._giftable; + } + + public get product(): IProduct + { + if(!this._products || !this._products.length) return null; + + if(this._products.length === 1) return this._products[0]; + + const products = Product.stripAddonProducts(this._products); + + if(products.length) return products[0]; + + return null; + } + + public get pricingModel(): string + { + return this._pricingModel; + } + + public get priceType(): string + { + return this._priceType; + } + + public get bundlePurchaseAllowed(): boolean + { + return this._bundlePurchaseAllowed; + } + + public get isRentOffer(): boolean + { + return this._isRentOffer; + } + + public get badgeCode(): string + { + return this._badgeCode; + } + + public get localizationName(): string + { + const productData = GetProductDataForLocalization(this._localizationId); + + if(productData) return productData.name; + + return LocalizeText(this._localizationId); + } + + public get localizationDescription(): string + { + const productData = GetProductDataForLocalization(this._localizationId); + + if(productData) return productData.description; + + return LocalizeText(this._localizationId); + } + + public get isLazy(): boolean + { + return false; + } + + public get products(): IProduct[] + { + return this._products; + } + + private setPricingModelForProducts(): void + { + const products = Product.stripAddonProducts(this._products); + + if(products.length === 1) + { + if(products[0].productCount === 1) + { + this._pricingModel = Offer.PRICING_MODEL_SINGLE; + } + else + { + this._pricingModel = Offer.PRICING_MODEL_MULTI; + } + } + + else if(products.length > 1) + { + this._pricingModel = Offer.PRICING_MODEL_BUNDLE; + } + + else + { + this._pricingModel = Offer.PRICING_MODEL_UNKNOWN; + } + } + + private setPricingType(): void + { + if((this._priceInCredits > 0) && (this._priceInActivityPoints > 0)) + { + this._priceType = Offer.PRICE_TYPE_CREDITS_ACTIVITYPOINTS; + } + + else if(this._priceInCredits > 0) + { + this._priceType = Offer.PRICE_TYPE_CREDITS; + } + + else if(this._priceInActivityPoints > 0) + { + this._priceType = Offer.PRICE_TYPE_ACTIVITYPOINTS; + } + + else + { + this._priceType = Offer.PRICE_TYPE_NONE; + } + } + + public clone(): IPurchasableOffer + { + const products: IProduct[] = []; + const productData = GetProductDataForLocalization(this.localizationId); + + for(const product of this._products) + { + const furnitureData = GetFurnitureData(product.productClassId, product.productType); + + products.push(new Product(product.productType, product.productClassId, product.extraParam, product.productCount, productData, furnitureData)); + } + + const offer = new Offer(this.offerId, this.localizationId, this.isRentOffer, this.priceInCredits, this.priceInActivityPoints, this.activityPointType, this.giftable, this.clubLevel, products, this.bundlePurchaseAllowed); + + offer.page = this.page; + + return offer; + } +} diff --git a/src/api/catalog/PageLocalization.ts b/src/api/catalog/PageLocalization.ts new file mode 100644 index 0000000..f24ae87 --- /dev/null +++ b/src/api/catalog/PageLocalization.ts @@ -0,0 +1,36 @@ +import { GetConfigurationValue } from '../nitro'; +import { IPageLocalization } from './IPageLocalization'; + +export class PageLocalization implements IPageLocalization +{ + private _images: string[]; + private _texts: string[]; + + constructor(images: string[], texts: string[]) + { + this._images = images; + this._texts = texts; + } + + public getText(index: number): string + { + let message = (this._texts[index] || ''); + + if(message && message.length) message = message.replace(/\r\n|\r|\n/g, '
'); + + return message; + } + + public getImage(index: number): string + { + const imageName = (this._images[index] || ''); + + if(!imageName || !imageName.length) return null; + + let assetUrl = GetConfigurationValue('catalog.asset.image.url'); + + assetUrl = assetUrl.replace('%name%', imageName); + + return assetUrl; + } +} diff --git a/src/api/catalog/PlacedObjectPurchaseData.ts b/src/api/catalog/PlacedObjectPurchaseData.ts new file mode 100644 index 0000000..43d23e3 --- /dev/null +++ b/src/api/catalog/PlacedObjectPurchaseData.ts @@ -0,0 +1,41 @@ +import { IFurnitureData, IProductData } from '@nitrots/nitro-renderer'; +import { IPurchasableOffer } from './IPurchasableOffer'; + +export class PlacedObjectPurchaseData +{ + constructor( + public readonly roomId: number, + public readonly objectId: number, + public readonly category: number, + public readonly wallLocation: string, + public readonly x: number, + public readonly y: number, + public readonly direction: number, + public readonly offer: IPurchasableOffer) + {} + + public get offerId(): number + { + return this.offer.offerId; + } + + public get productClassId(): number + { + return this.offer.product.productClassId; + } + + public get productData(): IProductData + { + return this.offer.product.productData; + } + + public get furniData(): IFurnitureData + { + return this.offer.product.furnitureData; + } + + public get extraParam(): string + { + return this.offer.product.extraParam; + } +} diff --git a/src/api/catalog/Product.ts b/src/api/catalog/Product.ts new file mode 100644 index 0000000..17d9340 --- /dev/null +++ b/src/api/catalog/Product.ts @@ -0,0 +1,143 @@ +import { GetRoomEngine, GetSessionDataManager, IFurnitureData, IObjectData, IProductData } from '@nitrots/nitro-renderer'; +import { GetConfigurationValue } from '../nitro'; +import { GetPixelEffectIcon, GetSubscriptionProductIcon } from './CatalogUtilities'; +import { IProduct } from './IProduct'; +import { IPurchasableOffer } from './IPurchasableOffer'; +import { ProductTypeEnum } from './ProductTypeEnum'; + +export class Product implements IProduct +{ + public static EFFECT_CLASSID_NINJA_DISAPPEAR: number = 108; + + private _productType: string; + private _productClassId: number; + private _extraParam: string; + private _productCount: number; + private _productData: IProductData; + private _furnitureData: IFurnitureData; + private _isUniqueLimitedItem: boolean; + private _uniqueLimitedItemSeriesSize: number; + private _uniqueLimitedItemsLeft: number; + + constructor(productType: string, productClassId: number, extraParam: string, productCount: number, productData: IProductData, furnitureData: IFurnitureData, isUniqueLimitedItem: boolean = false, uniqueLimitedItemSeriesSize: number = 0, uniqueLimitedItemsLeft: number = 0) + { + this._productType = productType.toLowerCase(); + this._productClassId = productClassId; + this._extraParam = extraParam; + this._productCount = productCount; + this._productData = productData; + this._furnitureData = furnitureData; + this._isUniqueLimitedItem = isUniqueLimitedItem; + this._uniqueLimitedItemSeriesSize = uniqueLimitedItemSeriesSize; + this._uniqueLimitedItemsLeft = uniqueLimitedItemsLeft; + } + + public static stripAddonProducts(products: IProduct[]): IProduct[] + { + if(products.length === 1) return products; + + return products.filter(product => ((product.productType !== ProductTypeEnum.BADGE) && (product.productType !== ProductTypeEnum.EFFECT) && (product.productClassId !== Product.EFFECT_CLASSID_NINJA_DISAPPEAR))); + } + + public getIconUrl(offer: IPurchasableOffer = null, stuffData: IObjectData = null): string + { + switch(this._productType) + { + case ProductTypeEnum.FLOOR: + return GetRoomEngine().getFurnitureFloorIconUrl(this.productClassId); + case ProductTypeEnum.WALL: { + if(offer && this._furnitureData) + { + let iconName = ''; + + switch(this._furnitureData.className) + { + case 'floor': + iconName = [ 'th', this._furnitureData.className, offer.product.extraParam ].join('_'); + break; + case 'wallpaper': + iconName = [ 'th', 'wall', offer.product.extraParam ].join('_'); + break; + case 'landscape': + iconName = [ 'th', this._furnitureData.className, (offer.product.extraParam || '').replace('.', '_'), '001' ].join('_'); + break; + } + + if(iconName !== '') + { + const assetUrl = GetConfigurationValue('catalog.asset.url'); + + return `${ assetUrl }/${ iconName }.png`; + } + } + + return GetRoomEngine().getFurnitureWallIconUrl(this.productClassId, this._extraParam); + } + case ProductTypeEnum.EFFECT: + return GetPixelEffectIcon(this.productClassId); + case ProductTypeEnum.HABBO_CLUB: + return GetSubscriptionProductIcon(this.productClassId); + case ProductTypeEnum.BADGE: + return GetSessionDataManager().getBadgeUrl(this._extraParam); + case ProductTypeEnum.ROBOT: + return null; + } + + return null; + } + + public get productType(): string + { + return this._productType; + } + + public get productClassId(): number + { + return this._productClassId; + } + + public get extraParam(): string + { + return this._extraParam; + } + + public set extraParam(extraParam: string) + { + this._extraParam = extraParam; + } + + public get productCount(): number + { + return this._productCount; + } + + public get productData(): IProductData + { + return this._productData; + } + + public get furnitureData(): IFurnitureData + { + return this._furnitureData; + } + + public get isUniqueLimitedItem(): boolean + { + return this._isUniqueLimitedItem; + } + + public get uniqueLimitedItemSeriesSize(): number + { + return this._uniqueLimitedItemSeriesSize; + } + + public get uniqueLimitedItemsLeft(): number + { + return this._uniqueLimitedItemsLeft; + } + + public set uniqueLimitedItemsLeft(uniqueLimitedItemsLeft: number) + { + this._uniqueLimitedItemsLeft = uniqueLimitedItemsLeft; + } +} diff --git a/src/api/catalog/ProductTypeEnum.ts b/src/api/catalog/ProductTypeEnum.ts new file mode 100644 index 0000000..f249081 --- /dev/null +++ b/src/api/catalog/ProductTypeEnum.ts @@ -0,0 +1,11 @@ +export class ProductTypeEnum +{ + public static WALL: string = 'i'; + public static FLOOR: string = 's'; + public static EFFECT: string = 'e'; + public static HABBO_CLUB: string = 'h'; + public static BADGE: string = 'b'; + public static GAME_TOKEN: string = 'GAME_TOKEN'; + public static PET: string = 'p'; + public static ROBOT: string = 'r'; +} diff --git a/src/api/catalog/RequestedPage.ts b/src/api/catalog/RequestedPage.ts new file mode 100644 index 0000000..205cc3e --- /dev/null +++ b/src/api/catalog/RequestedPage.ts @@ -0,0 +1,63 @@ +export class RequestedPage +{ + public static REQUEST_TYPE_NONE: number = 0; + public static REQUEST_TYPE_ID: number = 1; + public static REQUEST_TYPE_OFFER: number = 2; + public static REQUEST_TYPE_NAME: number = 3; + + private _requestType: number; + private _requestById: number; + private _requestedByOfferId: number; + private _requestByName: string; + + constructor() + { + this._requestType = RequestedPage.REQUEST_TYPE_NONE; + } + + public resetRequest():void + { + this._requestType = RequestedPage.REQUEST_TYPE_NONE; + this._requestById = -1; + this._requestedByOfferId = -1; + this._requestByName = null; + } + + public get requestType(): number + { + return this._requestType; + } + + public get requestById(): number + { + return this._requestById; + } + + public set requestById(id: number) + { + this._requestType = RequestedPage.REQUEST_TYPE_ID; + this._requestById = id; + } + + public get requestedByOfferId(): number + { + return this._requestedByOfferId; + } + + public set requestedByOfferId(offerId: number) + { + this._requestType = RequestedPage.REQUEST_TYPE_OFFER; + this._requestedByOfferId = offerId; + } + + public get requestByName(): string + { + return this._requestByName; + } + + public set requestByName(name: string) + { + this._requestType = RequestedPage.REQUEST_TYPE_NAME; + this._requestByName = name; + } +} diff --git a/src/api/catalog/SearchResult.ts b/src/api/catalog/SearchResult.ts new file mode 100644 index 0000000..120aed4 --- /dev/null +++ b/src/api/catalog/SearchResult.ts @@ -0,0 +1,11 @@ +import { ICatalogNode } from './ICatalogNode'; +import { IPurchasableOffer } from './IPurchasableOffer'; + +export class SearchResult +{ + constructor( + public readonly searchValue: string, + public readonly offers: IPurchasableOffer[], + public readonly filteredNodes: ICatalogNode[]) + {} +} diff --git a/src/api/catalog/index.ts b/src/api/catalog/index.ts new file mode 100644 index 0000000..6c5b9e2 --- /dev/null +++ b/src/api/catalog/index.ts @@ -0,0 +1,29 @@ +export * from './BuilderFurniPlaceableStatus'; +export * from './CatalogNode'; +export * from './CatalogPage'; +export * from './CatalogPageName'; +export * from './CatalogPetPalette'; +export * from './CatalogPurchaseState'; +export * from './CatalogType'; +export * from './CatalogUtilities'; +export * from './FurnitureOffer'; +export * from './GetImageIconUrlForProduct'; +export * from './GiftWrappingConfiguration'; +export * from './ICatalogNode'; +export * from './ICatalogOptions'; +export * from './ICatalogPage'; +export * from './IMarketplaceSearchOptions'; +export * from './IPageLocalization'; +export * from './IProduct'; +export * from './IPurchasableOffer'; +export * from './IPurchaseOptions'; +export * from './MarketplaceOfferData'; +export * from './MarketplaceOfferState'; +export * from './MarketplaceSearchType'; +export * from './Offer'; +export * from './PageLocalization'; +export * from './PlacedObjectPurchaseData'; +export * from './Product'; +export * from './ProductTypeEnum'; +export * from './RequestedPage'; +export * from './SearchResult'; diff --git a/src/api/chat-history/ChatEntryType.ts b/src/api/chat-history/ChatEntryType.ts new file mode 100644 index 0000000..045f00c --- /dev/null +++ b/src/api/chat-history/ChatEntryType.ts @@ -0,0 +1,6 @@ +export class ChatEntryType +{ + public static TYPE_CHAT = 1; + public static TYPE_ROOM_INFO = 2; + public static TYPE_IM = 3; +} diff --git a/src/api/chat-history/ChatHistoryCurrentDate.ts b/src/api/chat-history/ChatHistoryCurrentDate.ts new file mode 100644 index 0000000..35d6143 --- /dev/null +++ b/src/api/chat-history/ChatHistoryCurrentDate.ts @@ -0,0 +1,6 @@ +export const ChatHistoryCurrentDate = () => +{ + const currentTime = new Date(); + + return `${ currentTime.getHours().toString().padStart(2, '0') }:${ currentTime.getMinutes().toString().padStart(2, '0') }`; +}; diff --git a/src/api/chat-history/IChatEntry.ts b/src/api/chat-history/IChatEntry.ts new file mode 100644 index 0000000..1bf7a52 --- /dev/null +++ b/src/api/chat-history/IChatEntry.ts @@ -0,0 +1,17 @@ +export interface IChatEntry +{ + id: number; + webId: number; + entityId: number; + name: string; + look?: string; + message?: string; + entityType?: number; + style?: number; + chatType?: number; + imageUrl?: string; + color?: string; + roomId: number; + timestamp: string; + type: number; +} diff --git a/src/api/chat-history/IRoomHistoryEntry.ts b/src/api/chat-history/IRoomHistoryEntry.ts new file mode 100644 index 0000000..4986154 --- /dev/null +++ b/src/api/chat-history/IRoomHistoryEntry.ts @@ -0,0 +1,5 @@ +export interface IRoomHistoryEntry +{ + id: number; + name: string; +} diff --git a/src/api/chat-history/MessengerHistoryCurrentDate.ts b/src/api/chat-history/MessengerHistoryCurrentDate.ts new file mode 100644 index 0000000..b5f7972 --- /dev/null +++ b/src/api/chat-history/MessengerHistoryCurrentDate.ts @@ -0,0 +1,6 @@ +export const MessengerHistoryCurrentDate = (secondsSinceNow: number = 0) => +{ + const currentTime = secondsSinceNow ? new Date(Date.now() - secondsSinceNow * 1000) : new Date(); + + return `${ currentTime.getHours().toString().padStart(2, '0') }:${ currentTime.getMinutes().toString().padStart(2, '0') }`; +}; diff --git a/src/api/chat-history/index.ts b/src/api/chat-history/index.ts new file mode 100644 index 0000000..a989374 --- /dev/null +++ b/src/api/chat-history/index.ts @@ -0,0 +1,5 @@ +export * from './ChatEntryType'; +export * from './ChatHistoryCurrentDate'; +export * from './IChatEntry'; +export * from './IRoomHistoryEntry'; +export * from './MessengerHistoryCurrentDate'; diff --git a/src/api/events/DispatchEvent.ts b/src/api/events/DispatchEvent.ts new file mode 100644 index 0000000..79e2f5c --- /dev/null +++ b/src/api/events/DispatchEvent.ts @@ -0,0 +1,3 @@ +import { IEventDispatcher, NitroEvent } from '@nitrots/nitro-renderer'; + +export const DispatchEvent = (eventDispatcher: IEventDispatcher, event: NitroEvent) => eventDispatcher.dispatchEvent(event); diff --git a/src/api/events/DispatchMainEvent.ts b/src/api/events/DispatchMainEvent.ts new file mode 100644 index 0000000..e316b30 --- /dev/null +++ b/src/api/events/DispatchMainEvent.ts @@ -0,0 +1,4 @@ +import { GetEventDispatcher, NitroEvent } from '@nitrots/nitro-renderer'; +import { DispatchEvent } from './DispatchEvent'; + +export const DispatchMainEvent = (event: NitroEvent) => DispatchEvent(GetEventDispatcher(), event); diff --git a/src/api/events/DispatchUiEvent.ts b/src/api/events/DispatchUiEvent.ts new file mode 100644 index 0000000..5200bb4 --- /dev/null +++ b/src/api/events/DispatchUiEvent.ts @@ -0,0 +1,5 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; +import { DispatchEvent } from './DispatchEvent'; +import { UI_EVENT_DISPATCHER } from './UI_EVENT_DISPATCHER'; + +export const DispatchUiEvent = (event: NitroEvent) => DispatchEvent(UI_EVENT_DISPATCHER, event); diff --git a/src/api/events/UI_EVENT_DISPATCHER.ts b/src/api/events/UI_EVENT_DISPATCHER.ts new file mode 100644 index 0000000..cb57311 --- /dev/null +++ b/src/api/events/UI_EVENT_DISPATCHER.ts @@ -0,0 +1,3 @@ +import { EventDispatcher, IEventDispatcher } from '@nitrots/nitro-renderer'; + +export const UI_EVENT_DISPATCHER: IEventDispatcher = new EventDispatcher(); diff --git a/src/api/events/index.ts b/src/api/events/index.ts new file mode 100644 index 0000000..b7c22ee --- /dev/null +++ b/src/api/events/index.ts @@ -0,0 +1,4 @@ +export * from './DispatchEvent'; +export * from './DispatchMainEvent'; +export * from './DispatchUiEvent'; +export * from './UI_EVENT_DISPATCHER'; diff --git a/src/api/friends/GetGroupChatData.ts b/src/api/friends/GetGroupChatData.ts new file mode 100644 index 0000000..d1a2c7b --- /dev/null +++ b/src/api/friends/GetGroupChatData.ts @@ -0,0 +1,13 @@ +import { IGroupChatData } from './IGroupChatData'; + +export const GetGroupChatData = (extraData: string) => +{ + if(!extraData || !extraData.length) return null; + + const splitData = extraData.split('/'); + const username = splitData[0]; + const figure = splitData[1]; + const userId = parseInt(splitData[2]); + + return ({ username: username, figure: figure, userId: userId } as IGroupChatData); +}; diff --git a/src/api/friends/IGroupChatData.ts b/src/api/friends/IGroupChatData.ts new file mode 100644 index 0000000..24a3f9c --- /dev/null +++ b/src/api/friends/IGroupChatData.ts @@ -0,0 +1,6 @@ +export interface IGroupChatData +{ + username: string; + figure: string; + userId: number; +} diff --git a/src/api/friends/MessengerFriend.ts b/src/api/friends/MessengerFriend.ts new file mode 100644 index 0000000..b5cfc88 --- /dev/null +++ b/src/api/friends/MessengerFriend.ts @@ -0,0 +1,43 @@ +import { FriendParser } from '@nitrots/nitro-renderer'; + +export class MessengerFriend +{ + public static RELATIONSHIP_NONE: number = 0; + public static RELATIONSHIP_HEART: number = 1; + public static RELATIONSHIP_SMILE: number = 2; + public static RELATIONSHIP_BOBBA: number = 3; + + public id: number = -1; + public name: string = null; + public gender: number = 0; + public online: boolean = false; + public followingAllowed: boolean = false; + public figure: string = null; + public categoryId: number = 0; + public motto: string = null; + public realName: string = null; + public lastAccess: string = null; + public persistedMessageUser: boolean = false; + public vipMember: boolean = false; + public pocketHabboUser: boolean = false; + public relationshipStatus: number = -1; + public unread: number = 0; + + public populate(parser: FriendParser): void + { + this.id = parser.id; + this.name = parser.name; + this.gender = parser.gender; + this.online = parser.online; + this.followingAllowed = parser.followingAllowed; + this.figure = parser.figure; + this.categoryId = parser.categoryId; + this.motto = parser.motto; + this.realName = parser.realName; + this.lastAccess = parser.lastAccess; + this.persistedMessageUser = parser.persistedMessageUser; + this.vipMember = parser.vipMember; + this.pocketHabboUser = parser.pocketHabboUser; + this.relationshipStatus = parser.relationshipStatus; + } +} diff --git a/src/api/friends/MessengerGroupType.ts b/src/api/friends/MessengerGroupType.ts new file mode 100644 index 0000000..d46a1b6 --- /dev/null +++ b/src/api/friends/MessengerGroupType.ts @@ -0,0 +1,5 @@ +export class MessengerGroupType +{ + public static readonly GROUP_CHAT = 0; + public static readonly PRIVATE_CHAT = 1; +} diff --git a/src/api/friends/MessengerIconState.ts b/src/api/friends/MessengerIconState.ts new file mode 100644 index 0000000..63f8c13 --- /dev/null +++ b/src/api/friends/MessengerIconState.ts @@ -0,0 +1,6 @@ +export class MessengerIconState +{ + public static HIDDEN: number = 0; + public static SHOW: number = 1; + public static UNREAD: number = 2; +} diff --git a/src/api/friends/MessengerRequest.ts b/src/api/friends/MessengerRequest.ts new file mode 100644 index 0000000..89ceec5 --- /dev/null +++ b/src/api/friends/MessengerRequest.ts @@ -0,0 +1,41 @@ +import { FriendRequestData } from '@nitrots/nitro-renderer'; + +export class MessengerRequest +{ + private _id: number; + private _name: string; + private _requesterUserId: number; + private _figureString: string; + + public populate(data: FriendRequestData): boolean + { + if(!data) return false; + + this._id = data.requestId; + this._name = data.requesterName; + this._figureString = data.figureString; + this._requesterUserId = data.requesterUserId; + + return true; + } + + public get id(): number + { + return this._id; + } + + public get name(): string + { + return this._name; + } + + public get requesterUserId(): number + { + return this._requesterUserId; + } + + public get figureString(): string + { + return this._figureString; + } +} diff --git a/src/api/friends/MessengerSettings.ts b/src/api/friends/MessengerSettings.ts new file mode 100644 index 0000000..e890fcc --- /dev/null +++ b/src/api/friends/MessengerSettings.ts @@ -0,0 +1,11 @@ +import { FriendCategoryData } from '@nitrots/nitro-renderer'; + +export class MessengerSettings +{ + constructor( + public userFriendLimit: number = 0, + public normalFriendLimit: number = 0, + public extendedFriendLimit: number = 0, + public categories: FriendCategoryData[] = []) + {} +} diff --git a/src/api/friends/MessengerThread.ts b/src/api/friends/MessengerThread.ts new file mode 100644 index 0000000..30e931c --- /dev/null +++ b/src/api/friends/MessengerThread.ts @@ -0,0 +1,96 @@ +import { GetGroupChatData } from './GetGroupChatData'; +import { MessengerFriend } from './MessengerFriend'; +import { MessengerGroupType } from './MessengerGroupType'; +import { MessengerThreadChat } from './MessengerThreadChat'; +import { MessengerThreadChatGroup } from './MessengerThreadChatGroup'; + +export class MessengerThread +{ + public static MESSAGE_RECEIVED: string = 'MT_MESSAGE_RECEIVED'; + public static THREAD_ID: number = 0; + + private _threadId: number; + private _participant: MessengerFriend; + private _groups: MessengerThreadChatGroup[]; + private _lastUpdated: Date; + private _unreadCount: number; + + constructor(participant: MessengerFriend) + { + this._threadId = ++MessengerThread.THREAD_ID; + this._participant = participant; + this._groups = []; + this._lastUpdated = new Date(); + this._unreadCount = 0; + } + + public addMessage(senderId: number, message: string, secondsSinceSent: number = 0, extraData: string = null, type: number = 0): MessengerThreadChat + { + const isGroupChat = (senderId < 0 && extraData); + const userId = isGroupChat ? GetGroupChatData(extraData).userId : senderId; + + const group = this.getLastGroup(userId); + + if(!group) return; + + if(isGroupChat) group.type = MessengerGroupType.GROUP_CHAT; + + const chat = new MessengerThreadChat(senderId, message, secondsSinceSent, extraData, type); + + group.addChat(chat); + + this._lastUpdated = new Date(); + + this._unreadCount++; + + return chat; + } + + private getLastGroup(userId: number): MessengerThreadChatGroup + { + let group = this._groups[(this._groups.length - 1)]; + + if(group && (group.userId === userId)) return group; + + group = new MessengerThreadChatGroup(userId); + + this._groups.push(group); + + return group; + } + + public setRead(): void + { + this._unreadCount = 0; + } + + public get threadId(): number + { + return this._threadId; + } + + public get participant(): MessengerFriend + { + return this._participant; + } + + public get groups(): MessengerThreadChatGroup[] + { + return this._groups; + } + + public get lastUpdated(): Date + { + return this._lastUpdated; + } + + public get unreadCount(): number + { + return this._unreadCount; + } + + public get unread(): boolean + { + return (this._unreadCount > 0); + } +} diff --git a/src/api/friends/MessengerThreadChat.ts b/src/api/friends/MessengerThreadChat.ts new file mode 100644 index 0000000..2927fec --- /dev/null +++ b/src/api/friends/MessengerThreadChat.ts @@ -0,0 +1,54 @@ +export class MessengerThreadChat +{ + public static CHAT: number = 0; + public static ROOM_INVITE: number = 1; + public static STATUS_NOTIFICATION: number = 2; + public static SECURITY_NOTIFICATION: number = 3; + + private _type: number; + private _senderId: number; + private _message: string; + private _secondsSinceSent: number; + private _extraData: string; + private _date: Date; + + constructor(senderId: number, message: string, secondsSinceSent: number = 0, extraData: string = null, type: number = 0) + { + this._type = type; + this._senderId = senderId; + this._message = message; + this._secondsSinceSent = secondsSinceSent; + this._extraData = extraData; + this._date = new Date(); + } + + public get type(): number + { + return this._type; + } + + public get senderId(): number + { + return this._senderId; + } + + public get message(): string + { + return this._message; + } + + public get secondsSinceSent(): number + { + return this._secondsSinceSent; + } + + public get extraData(): string + { + return this._extraData; + } + + public get date(): Date + { + return this._date; + } +} diff --git a/src/api/friends/MessengerThreadChatGroup.ts b/src/api/friends/MessengerThreadChatGroup.ts new file mode 100644 index 0000000..1668aed --- /dev/null +++ b/src/api/friends/MessengerThreadChatGroup.ts @@ -0,0 +1,41 @@ +import { MessengerGroupType } from './MessengerGroupType'; +import { MessengerThreadChat } from './MessengerThreadChat'; + +export class MessengerThreadChatGroup +{ + private _userId: number; + private _chats: MessengerThreadChat[]; + private _type: number; + + constructor(userId: number, type = MessengerGroupType.PRIVATE_CHAT) + { + this._userId = userId; + this._chats = []; + this._type = type; + } + + public addChat(message: MessengerThreadChat): void + { + this._chats.push(message); + } + + public get userId(): number + { + return this._userId; + } + + public get chats(): MessengerThreadChat[] + { + return this._chats; + } + + public get type(): number + { + return this._type; + } + + public set type(type: number) + { + this._type = type; + } +} diff --git a/src/api/friends/OpenMessengerChat.ts b/src/api/friends/OpenMessengerChat.ts new file mode 100644 index 0000000..6050f2f --- /dev/null +++ b/src/api/friends/OpenMessengerChat.ts @@ -0,0 +1,7 @@ +import { CreateLinkEvent } from '@nitrots/nitro-renderer'; + +export function OpenMessengerChat(friendId: number = 0): void +{ + if(friendId === 0) CreateLinkEvent('friends-messenger/toggle'); + else CreateLinkEvent(`friends-messenger/${ friendId }`); +} diff --git a/src/api/friends/index.ts b/src/api/friends/index.ts new file mode 100644 index 0000000..ce1ed60 --- /dev/null +++ b/src/api/friends/index.ts @@ -0,0 +1,11 @@ +export * from './GetGroupChatData'; +export * from './IGroupChatData'; +export * from './MessengerFriend'; +export * from './MessengerGroupType'; +export * from './MessengerIconState'; +export * from './MessengerRequest'; +export * from './MessengerSettings'; +export * from './MessengerThread'; +export * from './MessengerThreadChat'; +export * from './MessengerThreadChatGroup'; +export * from './OpenMessengerChat'; diff --git a/src/api/groups/GetGroupInformation.ts b/src/api/groups/GetGroupInformation.ts new file mode 100644 index 0000000..14fe326 --- /dev/null +++ b/src/api/groups/GetGroupInformation.ts @@ -0,0 +1,7 @@ +import { GroupInformationComposer } from '@nitrots/nitro-renderer'; +import { SendMessageComposer } from '../nitro'; + +export function GetGroupInformation(groupId: number): void +{ + SendMessageComposer(new GroupInformationComposer(groupId, true)); +} diff --git a/src/api/groups/GetGroupManager.ts b/src/api/groups/GetGroupManager.ts new file mode 100644 index 0000000..2044a45 --- /dev/null +++ b/src/api/groups/GetGroupManager.ts @@ -0,0 +1,6 @@ +import { CreateLinkEvent } from '@nitrots/nitro-renderer'; + +export function GetGroupManager(groupId: number): void +{ + CreateLinkEvent(`groups/manage/${ groupId }`); +} diff --git a/src/api/groups/GetGroupMembers.ts b/src/api/groups/GetGroupMembers.ts new file mode 100644 index 0000000..9e10b01 --- /dev/null +++ b/src/api/groups/GetGroupMembers.ts @@ -0,0 +1,7 @@ +import { CreateLinkEvent } from '@nitrots/nitro-renderer'; + +export function GetGroupMembers(groupId: number, levelId?: number): void +{ + if(!levelId) CreateLinkEvent(`group-members/${ groupId }`); + else CreateLinkEvent(`group-members/${ groupId }/${ levelId }`); +} diff --git a/src/api/groups/GroupBadgePart.ts b/src/api/groups/GroupBadgePart.ts new file mode 100644 index 0000000..bb6f5e7 --- /dev/null +++ b/src/api/groups/GroupBadgePart.ts @@ -0,0 +1,30 @@ +export class GroupBadgePart +{ + public static BASE: string = 'b'; + public static SYMBOL: string = 's'; + + public type: string; + public key: number; + public color: number; + public position: number; + + constructor(type: string, key?: number, color?: number, position?: number) + { + this.type = type; + this.key = key ? key : 0; + this.color = color ? color : 0; + this.position = position ? position : 4; + } + + public get code(): string + { + if((this.key === 0) && (this.type !== GroupBadgePart.BASE)) return null; + + return GroupBadgePart.getCode(this.type, this.key, this.color, this.position); + } + + public static getCode(type: string, key: number, color: number, position: number): string + { + return type + (key < 10 ? '0' : '') + key + (color < 10 ? '0' : '') + color + position; + } +} diff --git a/src/api/groups/GroupMembershipType.ts b/src/api/groups/GroupMembershipType.ts new file mode 100644 index 0000000..532c836 --- /dev/null +++ b/src/api/groups/GroupMembershipType.ts @@ -0,0 +1,6 @@ +export class GroupMembershipType +{ + public static NOT_MEMBER: number = 0; + public static MEMBER: number = 1; + public static REQUEST_PENDING: number = 2; +} diff --git a/src/api/groups/GroupType.ts b/src/api/groups/GroupType.ts new file mode 100644 index 0000000..744c6c6 --- /dev/null +++ b/src/api/groups/GroupType.ts @@ -0,0 +1,6 @@ +export class GroupType +{ + public static REGULAR: number = 0; + public static EXCLUSIVE: number = 1; + public static PRIVATE: number = 2; +} diff --git a/src/api/groups/IGroupCustomize.ts b/src/api/groups/IGroupCustomize.ts new file mode 100644 index 0000000..44fc4ff --- /dev/null +++ b/src/api/groups/IGroupCustomize.ts @@ -0,0 +1,8 @@ +export interface IGroupCustomize +{ + badgeBases: { id: number, images: string[] }[]; + badgeSymbols: { id: number, images: string[] }[]; + badgePartColors: { id: number, color: string }[]; + groupColorsA: { id: number, color: string }[]; + groupColorsB: { id: number, color: string }[]; +} diff --git a/src/api/groups/IGroupData.ts b/src/api/groups/IGroupData.ts new file mode 100644 index 0000000..bb65b49 --- /dev/null +++ b/src/api/groups/IGroupData.ts @@ -0,0 +1,13 @@ +import { GroupBadgePart } from './GroupBadgePart'; + +export interface IGroupData +{ + groupId: number; + groupName: string; + groupDescription: string; + groupHomeroomId: number; + groupState: number; + groupCanMembersDecorate: boolean; + groupColors: number[]; + groupBadgeParts: GroupBadgePart[]; +} diff --git a/src/api/groups/ToggleFavoriteGroup.ts b/src/api/groups/ToggleFavoriteGroup.ts new file mode 100644 index 0000000..82385d4 --- /dev/null +++ b/src/api/groups/ToggleFavoriteGroup.ts @@ -0,0 +1,7 @@ +import { GroupFavoriteComposer, GroupUnfavoriteComposer, HabboGroupEntryData } from '@nitrots/nitro-renderer'; +import { SendMessageComposer } from '../nitro'; + +export const ToggleFavoriteGroup = (group: HabboGroupEntryData) => +{ + SendMessageComposer(group.favourite ? new GroupUnfavoriteComposer(group.groupId) : new GroupFavoriteComposer(group.groupId)); +}; diff --git a/src/api/groups/TryJoinGroup.ts b/src/api/groups/TryJoinGroup.ts new file mode 100644 index 0000000..63959bf --- /dev/null +++ b/src/api/groups/TryJoinGroup.ts @@ -0,0 +1,4 @@ +import { GroupJoinComposer } from '@nitrots/nitro-renderer'; +import { SendMessageComposer } from '../nitro'; + +export const TryJoinGroup = (groupId: number) => SendMessageComposer(new GroupJoinComposer(groupId)); diff --git a/src/api/groups/index.ts b/src/api/groups/index.ts new file mode 100644 index 0000000..4842948 --- /dev/null +++ b/src/api/groups/index.ts @@ -0,0 +1,10 @@ +export * from './GetGroupInformation'; +export * from './GetGroupManager'; +export * from './GetGroupMembers'; +export * from './GroupBadgePart'; +export * from './GroupMembershipType'; +export * from './GroupType'; +export * from './IGroupCustomize'; +export * from './IGroupData'; +export * from './ToggleFavoriteGroup'; +export * from './TryJoinGroup'; diff --git a/src/api/guide-tool/GuideSessionState.ts b/src/api/guide-tool/GuideSessionState.ts new file mode 100644 index 0000000..c5e24f3 --- /dev/null +++ b/src/api/guide-tool/GuideSessionState.ts @@ -0,0 +1,23 @@ +export class GuideSessionState +{ + public static readonly NONE: string = 'NONE'; + public static readonly ERROR: string = 'ERROR'; + public static readonly REJECTED: string = 'REJECTED'; + public static readonly USER_CREATE: string = 'USER_CREATE'; + public static readonly USER_PENDING: string = 'USER_PENDING'; + public static readonly USER_ONGOING: string = 'USER_ONGOING'; + public static readonly USER_FEEDBACK: string = 'USER_FEEDBACK'; + public static readonly USER_NO_HELPERS: string = 'USER_NO_HELPERS'; + public static readonly USER_SOMETHING_WRONG: string = 'USER_SOMETHING_WRONG'; + public static readonly USER_THANKS: string = 'USER_THANKS'; + public static readonly USER_GUIDE_DISCONNECTED: string = 'USER_GUIDE_DISCONNECTED'; + public static readonly GUIDE_TOOL_MENU: string = 'GUIDE_TOOL_MENU'; + public static readonly GUIDE_ACCEPT: string = 'GUIDE_ACCEPT'; + public static readonly GUIDE_ONGOING: string = 'GUIDE_ONGOING'; + public static readonly GUIDE_CLOSED: string = 'GUIDE_CLOSED'; + public static readonly GUARDIAN_CHAT_REVIEW_ACCEPT: string = 'GUARDIAN_CHAT_REVIEW_ACCEPT'; + public static readonly GUARDIAN_CHAT_REVIEW_WAIT_FOR_VOTERS: string = 'GUARDIAN_CHAT_REVIEW_WAIT_FOR_VOTERS'; + public static readonly GUARDIAN_CHAT_REVIEW_VOTE: string = 'GUARDIAN_CHAT_REVIEW_VOTE'; + public static readonly GUARDIAN_CHAT_REVIEW_WAIT_FOR_RESULTS: string = 'GUARDIAN_CHAT_REVIEW_WAIT_FOR_RESULTS'; + public static readonly GUARDIAN_CHAT_REVIEW_RESULTS: string = 'GUARDIAN_CHAT_REVIEW_RESULTS'; +} diff --git a/src/api/guide-tool/GuideToolMessage.ts b/src/api/guide-tool/GuideToolMessage.ts new file mode 100644 index 0000000..3ed87be --- /dev/null +++ b/src/api/guide-tool/GuideToolMessage.ts @@ -0,0 +1,21 @@ +export class GuideToolMessage +{ + private _message: string; + private _roomId: number; + + constructor(message: string, roomId?: number) + { + this._message = message; + this._roomId = roomId; + } + + public get message(): string + { + return this._message; + } + + public get roomId(): number + { + return this._roomId; + } +} diff --git a/src/api/guide-tool/GuideToolMessageGroup.ts b/src/api/guide-tool/GuideToolMessageGroup.ts new file mode 100644 index 0000000..bf03c9b --- /dev/null +++ b/src/api/guide-tool/GuideToolMessageGroup.ts @@ -0,0 +1,28 @@ +import { GuideToolMessage } from './GuideToolMessage'; + +export class GuideToolMessageGroup +{ + private _userId: number; + private _messages: GuideToolMessage[]; + + constructor(userId: number) + { + this._userId = userId; + this._messages = []; + } + + public addChat(message: GuideToolMessage): void + { + this._messages.push(message); + } + + public get userId(): number + { + return this._userId; + } + + public get messages(): GuideToolMessage[] + { + return this._messages; + } +} diff --git a/src/api/guide-tool/index.ts b/src/api/guide-tool/index.ts new file mode 100644 index 0000000..1400adc --- /dev/null +++ b/src/api/guide-tool/index.ts @@ -0,0 +1,3 @@ +export * from './GuideSessionState'; +export * from './GuideToolMessage'; +export * from './GuideToolMessageGroup'; diff --git a/src/api/hc-center/ClubStatus.ts b/src/api/hc-center/ClubStatus.ts new file mode 100644 index 0000000..e3cba00 --- /dev/null +++ b/src/api/hc-center/ClubStatus.ts @@ -0,0 +1,6 @@ +export class ClubStatus +{ + public static ACTIVE: string = 'active'; + public static NONE: string = 'none'; + public static EXPIRED: string = 'expired'; +} diff --git a/src/api/hc-center/GetClubBadge.ts b/src/api/hc-center/GetClubBadge.ts new file mode 100644 index 0000000..79cf979 --- /dev/null +++ b/src/api/hc-center/GetClubBadge.ts @@ -0,0 +1,11 @@ +const DEFAULT_BADGE: string = 'HC1'; +const BADGES: string[] = [ 'ACH_VipHC1', 'ACH_VipHC2', 'ACH_VipHC3', 'ACH_VipHC4', 'ACH_VipHC5', 'HC1', 'HC2', 'HC3', 'HC4', 'HC5' ]; + +export const GetClubBadge = (badgeCodes: string[]) => +{ + let badgeCode: string = null; + + BADGES.forEach(badge => ((badgeCodes.indexOf(badge) > -1) && (badgeCode = badge))); + + return (badgeCode || DEFAULT_BADGE); +}; diff --git a/src/api/hc-center/index.ts b/src/api/hc-center/index.ts new file mode 100644 index 0000000..cee8f69 --- /dev/null +++ b/src/api/hc-center/index.ts @@ -0,0 +1,2 @@ +export * from './ClubStatus'; +export * from './GetClubBadge'; diff --git a/src/api/help/CallForHelpResult.ts b/src/api/help/CallForHelpResult.ts new file mode 100644 index 0000000..37e7ea1 --- /dev/null +++ b/src/api/help/CallForHelpResult.ts @@ -0,0 +1,5 @@ +export class CallForHelpResult +{ + public static readonly TOO_MANY_PENDING_CALLS_CODE = 1; + public static readonly HAS_ABUSIVE_CALL_CODE = 2; +} diff --git a/src/api/help/GetCloseReasonKey.ts b/src/api/help/GetCloseReasonKey.ts new file mode 100644 index 0000000..8658492 --- /dev/null +++ b/src/api/help/GetCloseReasonKey.ts @@ -0,0 +1,8 @@ +export const GetCloseReasonKey = (code: number) => +{ + if(code === 1) return 'useless'; + + if(code === 2) return 'abusive'; + + return 'resolved'; +}; diff --git a/src/api/help/IHelpReport.ts b/src/api/help/IHelpReport.ts new file mode 100644 index 0000000..8611707 --- /dev/null +++ b/src/api/help/IHelpReport.ts @@ -0,0 +1,19 @@ +import { IChatEntry } from '../chat-history'; + +export interface IHelpReport +{ + reportType: number; + reportedUserId: number; + reportedChats: IChatEntry[]; + cfhCategory: number; + cfhTopic: number; + roomId: number; + roomName: string; + groupId: number; + threadId: number; + messageId: number; + extraData: string; + roomObjectId: number; + message: string; + currentStep: number; +} diff --git a/src/api/help/IReportedUser.ts b/src/api/help/IReportedUser.ts new file mode 100644 index 0000000..90a3887 --- /dev/null +++ b/src/api/help/IReportedUser.ts @@ -0,0 +1,5 @@ +export interface IReportedUser +{ + id: number; + username: string; +} diff --git a/src/api/help/ReportState.ts b/src/api/help/ReportState.ts new file mode 100644 index 0000000..ae3a3bd --- /dev/null +++ b/src/api/help/ReportState.ts @@ -0,0 +1,8 @@ +export class ReportState +{ + public static readonly SELECT_USER = 0; + public static readonly SELECT_CHATS = 1; + public static readonly SELECT_TOPICS = 2; + public static readonly INPUT_REPORT_MESSAGE = 3; + public static readonly REPORT_SUMMARY = 4; +} diff --git a/src/api/help/ReportType.ts b/src/api/help/ReportType.ts new file mode 100644 index 0000000..24eb7ae --- /dev/null +++ b/src/api/help/ReportType.ts @@ -0,0 +1,11 @@ +export class ReportType +{ + public static readonly EMERGENCY = 1; + public static readonly GUIDE = 2; + public static readonly IM = 3; + public static readonly ROOM = 4; + public static readonly BULLY = 6; + public static readonly THREAD = 7; + public static readonly MESSAGE = 8; + public static readonly PHOTO = 9; +} diff --git a/src/api/help/index.ts b/src/api/help/index.ts new file mode 100644 index 0000000..6fa2045 --- /dev/null +++ b/src/api/help/index.ts @@ -0,0 +1,6 @@ +export * from './CallForHelpResult'; +export * from './GetCloseReasonKey'; +export * from './IHelpReport'; +export * from './IReportedUser'; +export * from './ReportState'; +export * from './ReportType'; diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..7089277 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,28 @@ +export * from './GetRendererVersion'; +export * from './GetUIVersion'; +export * from './achievements'; +export * from './avatar'; +export * from './camera'; +export * from './campaign'; +export * from './catalog'; +export * from './chat-history'; +export * from './events'; +export * from './friends'; +export * from './groups'; +export * from './guide-tool'; +export * from './hc-center'; +export * from './help'; +export * from './inventory'; +export * from './mod-tools'; +export * from './navigator'; +export * from './nitro'; +export * from './nitro/room'; +export * from './nitro/session'; +export * from './notification'; +export * from './purse'; +export * from './room'; +export * from './room/events'; +export * from './room/widgets'; +export * from './user'; +export * from './utils'; +export * from './wired'; diff --git a/src/api/inventory/FurniCategory.ts b/src/api/inventory/FurniCategory.ts new file mode 100644 index 0000000..6528947 --- /dev/null +++ b/src/api/inventory/FurniCategory.ts @@ -0,0 +1,26 @@ +export class FurniCategory +{ + public static DEFAULT: number = 1; + public static WALL_PAPER: number = 2; + public static FLOOR: number = 3; + public static LANDSCAPE: number = 4; + public static POST_IT: number = 5; + public static POSTER: number = 6; + public static SOUND_SET: number = 7; + public static TRAX_SONG: number = 8; + public static PRESENT: number = 9; + public static ECOTRON_BOX: number = 10; + public static TROPHY: number = 11; + public static CREDIT_FURNI: number = 12; + public static PET_SHAMPOO: number = 13; + public static PET_CUSTOM_PART: number = 14; + public static PET_CUSTOM_PART_SHAMPOO: number = 15; + public static PET_SADDLE: number = 16; + public static GUILD_FURNI: number = 17; + public static GAME_FURNI: number = 18; + public static MONSTERPLANT_SEED: number = 19; + public static MONSTERPLANT_REVIVAL: number = 20; + public static MONSTERPLANT_REBREED: number = 21; + public static MONSTERPLANT_FERTILIZE: number = 22; + public static FIGURE_PURCHASABLE_SET: number = 23; +} diff --git a/src/api/inventory/FurnitureItem.ts b/src/api/inventory/FurnitureItem.ts new file mode 100644 index 0000000..655d1d3 --- /dev/null +++ b/src/api/inventory/FurnitureItem.ts @@ -0,0 +1,245 @@ +import { GetTickerTime, IFurnitureItemData, IObjectData } from '@nitrots/nitro-renderer'; +import { IFurnitureItem } from './IFurnitureItem'; + +export class FurnitureItem implements IFurnitureItem +{ + private _expirationTimeStamp: number; + private _isWallItem: boolean; + private _songId: number; + private _locked: boolean; + private _id: number; + private _ref: number; + private _category: number; + private _type: number; + private _stuffData: IObjectData; + private _extra: number; + private _recyclable: boolean; + private _tradeable: boolean; + private _groupable: boolean; + private _sellable: boolean; + private _secondsToExpiration: number; + private _hasRentPeriodStarted: boolean; + private _creationDay: number; + private _creationMonth: number; + private _creationYear: number; + private _slotId: string; + private _isRented: boolean; + private _flatId: number; + + constructor(parser: IFurnitureItemData) + { + if(!parser) return; + + this._locked = false; + this._id = parser.itemId; + this._type = parser.spriteId; + this._ref = parser.ref; + this._category = parser.category; + this._groupable = ((parser.isGroupable) && (!(parser.rentable))); + this._tradeable = parser.tradable; + this._recyclable = parser.isRecycleable; + this._sellable = parser.sellable; + this._stuffData = parser.stuffData; + this._extra = parser.extra; + this._secondsToExpiration = parser.secondsToExpiration; + this._expirationTimeStamp = parser.expirationTimeStamp; + this._hasRentPeriodStarted = parser.hasRentPeriodStarted; + this._creationDay = parser.creationDay; + this._creationMonth = parser.creationMonth; + this._creationYear = parser.creationYear; + this._slotId = parser.slotId; + this._songId = parser.songId; + this._flatId = parser.flatId; + this._isRented = parser.rentable; + this._isWallItem = parser.isWallItem; + } + + public get rentable(): boolean + { + return this._isRented; + } + + public get id(): number + { + return this._id; + } + + public get ref(): number + { + return this._ref; + } + + public get category(): number + { + return this._category; + } + + public get type(): number + { + return this._type; + } + + public get stuffData(): IObjectData + { + return this._stuffData; + } + + public set stuffData(k: IObjectData) + { + this._stuffData = k; + } + + public get extra(): number + { + return this._extra; + } + + public get recyclable(): boolean + { + return this._recyclable; + } + + public get isTradable(): boolean + { + return this._tradeable; + } + + public get isGroupable(): boolean + { + return this._groupable; + } + + public get sellable(): boolean + { + return this._sellable; + } + + public get secondsToExpiration(): number + { + if(this._secondsToExpiration === -1) return -1; + + let time = -1; + + if(this._hasRentPeriodStarted) + { + time = (this._secondsToExpiration - ((GetTickerTime() - this._expirationTimeStamp) / 1000)); + + if(time < 0) time = 0; + } + else + { + time = this._secondsToExpiration; + } + + return time; + } + + public get creationDay(): number + { + return this._creationDay; + } + + public get creationMonth(): number + { + return this._creationMonth; + } + + public get creationYear(): number + { + return this._creationYear; + } + + public get slotId(): string + { + return this._slotId; + } + + public get songId(): number + { + return this._songId; + } + + public get locked(): boolean + { + return this._locked; + } + + public set locked(k: boolean) + { + this._locked = k; + } + + public get flatId(): number + { + return this._flatId; + } + + public get isWallItem(): boolean + { + return this._isWallItem; + } + + public get hasRentPeriodStarted(): boolean + { + return this._hasRentPeriodStarted; + } + + public get expirationTimeStamp(): number + { + return this._expirationTimeStamp; + } + + public update(parser: IFurnitureItemData): void + { + this._type = parser.spriteId; + this._ref = parser.ref; + this._category = parser.category; + this._groupable = (parser.isGroupable && !parser.rentable); + this._tradeable = parser.tradable; + this._recyclable = parser.isRecycleable; + this._sellable = parser.sellable; + this._stuffData = parser.stuffData; + this._extra = parser.extra; + this._secondsToExpiration = parser.secondsToExpiration; + this._expirationTimeStamp = parser.expirationTimeStamp; + this._hasRentPeriodStarted = parser.hasRentPeriodStarted; + this._creationDay = parser.creationDay; + this._creationMonth = parser.creationMonth; + this._creationYear = parser.creationYear; + this._slotId = parser.slotId; + this._songId = parser.songId; + this._flatId = parser.flatId; + this._isRented = parser.rentable; + this._isWallItem = parser.isWallItem; + } + + public clone(): FurnitureItem + { + const item = new FurnitureItem(null); + + item._expirationTimeStamp = this._expirationTimeStamp; + item._isWallItem = this._isWallItem; + item._songId = this._songId; + item._locked = this._locked; + item._id = this._id; + item._ref = this._ref; + item._category = this._category; + item._type = this._type; + item._stuffData = this._stuffData; + item._extra = this._extra; + item._recyclable = this._recyclable; + item._tradeable = this._tradeable; + item._groupable = this._groupable; + item._sellable = this._sellable; + item._secondsToExpiration = this._secondsToExpiration; + item._hasRentPeriodStarted = this._hasRentPeriodStarted; + item._creationDay = this._creationDay; + item._creationMonth = this._creationMonth; + item._creationYear = this._creationYear; + item._slotId = this._slotId; + item._isRented = this._isRented; + item._flatId = this._flatId; + + return item; + } +} diff --git a/src/api/inventory/FurnitureUtilities.ts b/src/api/inventory/FurnitureUtilities.ts new file mode 100644 index 0000000..93b9765 --- /dev/null +++ b/src/api/inventory/FurnitureUtilities.ts @@ -0,0 +1,171 @@ +import { FurnitureListItemParser, GetRoomEngine, IObjectData } from '@nitrots/nitro-renderer'; +import { FurniCategory } from './FurniCategory'; +import { FurnitureItem } from './FurnitureItem'; +import { GroupItem } from './GroupItem'; + +export const createGroupItem = (type: number, category: number, stuffData: IObjectData, extra: number = NaN) => new GroupItem(type, category, GetRoomEngine(), stuffData, extra); + +const addSingleFurnitureItem = (set: GroupItem[], item: FurnitureItem, unseen: boolean) => +{ + const groupItems: GroupItem[] = []; + + for(const groupItem of set) + { + if(groupItem.type === item.type) groupItems.push(groupItem); + } + + for(const groupItem of groupItems) + { + if(groupItem.getItemById(item.id)) return groupItem; + } + + const groupItem = createGroupItem(item.type, item.category, item.stuffData, item.extra); + + groupItem.push(item); + + if(unseen) + { + groupItem.hasUnseenItems = true; + + set.unshift(groupItem); + } + else + { + set.push(groupItem); + } + + return groupItem; +}; + +const addGroupableFurnitureItem = (set: GroupItem[], item: FurnitureItem, unseen: boolean) => +{ + let existingGroup: GroupItem = null; + + for(const groupItem of set) + { + if((groupItem.type === item.type) && (groupItem.isWallItem === item.isWallItem) && groupItem.isGroupable) + { + if(item.category === FurniCategory.POSTER) + { + if(groupItem.stuffData.getLegacyString() === item.stuffData.getLegacyString()) + { + existingGroup = groupItem; + + break; + } + } + + else if(item.category === FurniCategory.GUILD_FURNI) + { + if(item.stuffData.compare(groupItem.stuffData)) + { + existingGroup = groupItem; + + break; + } + } + + else + { + existingGroup = groupItem; + + break; + } + } + } + + if(existingGroup) + { + existingGroup.push(item); + + if(unseen) + { + existingGroup.hasUnseenItems = true; + + const index = set.indexOf(existingGroup); + + if(index >= 0) set.splice(index, 1); + + set.unshift(existingGroup); + } + + return existingGroup; + } + + existingGroup = createGroupItem(item.type, item.category, item.stuffData, item.extra); + + existingGroup.push(item); + + if(unseen) + { + existingGroup.hasUnseenItems = true; + + set.unshift(existingGroup); + } + else + { + set.push(existingGroup); + } + + return existingGroup; +}; + +export const addFurnitureItem = (set: GroupItem[], item: FurnitureItem, unseen: boolean) => +{ + if(!item.isGroupable) + { + addSingleFurnitureItem(set, item, unseen); + } + else + { + addGroupableFurnitureItem(set, item, unseen); + } +}; + +export const mergeFurniFragments = (fragment: Map, totalFragments: number, fragmentNumber: number, fragments: Map[]) => +{ + if(totalFragments === 1) return fragment; + + fragments[fragmentNumber] = fragment; + + for(const frag of fragments) + { + if(!frag) return null; + } + + const merged: Map = new Map(); + + for(const frag of fragments) + { + for(const [ key, value ] of frag) merged.set(key, value); + + frag.clear(); + } + + fragments = null; + + return merged; +}; + +export const getAllItemIds = (groupItems: GroupItem[]) => +{ + const itemIds: number[] = []; + + for(const groupItem of groupItems) + { + let totalCount = groupItem.getTotalCount(); + + if(groupItem.category === FurniCategory.POST_IT) totalCount = 1; + + let i = 0; + + while(i < totalCount) + { + itemIds.push(groupItem.getItemByIndex(i).id); + + i++; + } + } + + return itemIds; +}; diff --git a/src/api/inventory/GroupItem.ts b/src/api/inventory/GroupItem.ts new file mode 100644 index 0000000..8569321 --- /dev/null +++ b/src/api/inventory/GroupItem.ts @@ -0,0 +1,461 @@ +import { IObjectData, IRoomEngine } from '@nitrots/nitro-renderer'; +import { LocalizeText } from '../utils'; +import { FurniCategory } from './FurniCategory'; +import { FurnitureItem } from './FurnitureItem'; +import { IFurnitureItem } from './IFurnitureItem'; + +export class GroupItem +{ + private _type: number; + private _category: number; + private _roomEngine: IRoomEngine; + private _stuffData: IObjectData; + private _extra: number; + private _isWallItem: boolean; + private _iconUrl: string; + private _name: string; + private _description: string; + private _locked: boolean; + private _selected: boolean; + private _hasUnseenItems: boolean; + private _items: FurnitureItem[]; + + constructor(type: number = -1, category: number = -1, roomEngine: IRoomEngine = null, stuffData: IObjectData = null, extra: number = -1) + { + this._type = type; + this._category = category; + this._roomEngine = roomEngine; + this._stuffData = stuffData; + this._extra = extra; + this._isWallItem = false; + this._iconUrl = null; + this._name = null; + this._description = null; + this._locked = false; + this._selected = false; + this._hasUnseenItems = false; + this._items = []; + } + + public clone(): GroupItem + { + const groupItem = new GroupItem(); + + groupItem._type = this._type; + groupItem._category = this._category; + groupItem._roomEngine = this._roomEngine; + groupItem._stuffData = this._stuffData; + groupItem._extra = this._extra; + groupItem._isWallItem = this._isWallItem; + groupItem._iconUrl = this._iconUrl; + groupItem._name = this._name; + groupItem._description = this._description; + groupItem._locked = this._locked; + groupItem._selected = this._selected; + groupItem._hasUnseenItems = this._hasUnseenItems; + groupItem._items = this._items; + + return groupItem; + } + + public prepareGroup(): void + { + this.setIcon(); + this.setName(); + this.setDescription(); + } + + public dispose(): void + { + + } + + public getItemByIndex(index: number): FurnitureItem + { + return this._items[index]; + } + + public getItemById(id: number): FurnitureItem + { + for(const item of this._items) + { + if(item.id !== id) continue; + + return item; + } + + return null; + } + + public getTradeItems(count: number): IFurnitureItem[] + { + const items: IFurnitureItem[] = []; + + const furnitureItem = this.getLastItem(); + + if(!furnitureItem) return items; + + let found = 0; + let i = 0; + + while(i < this._items.length) + { + if(found >= count) break; + + const item = this.getItemByIndex(i); + + if(!item.locked && item.isTradable && (item.type === furnitureItem.type)) + { + items.push(item); + + found++; + } + + i++; + } + + return items; + } + + public push(item: FurnitureItem): void + { + const items = [ ...this._items ]; + + let index = 0; + + while(index < items.length) + { + let existingItem = items[index]; + + if(existingItem.id === item.id) + { + existingItem = existingItem.clone(); + + existingItem.locked = false; + + items.splice(index, 1); + + items.push(existingItem); + + this._items = items; + + return; + } + + index++; + } + + items.push(item); + + this._items = items; + + if(this._items.length === 1) this.prepareGroup(); + } + + public pop(): FurnitureItem + { + const items = [ ...this._items ]; + + let item: FurnitureItem = null; + + if(items.length > 0) + { + const index = (items.length - 1); + + item = items[index]; + + items.splice(index, 1); + } + + this._items = items; + + return item; + } + + public remove(k: number): FurnitureItem + { + const items = [ ...this._items ]; + + let index = 0; + + while(index < items.length) + { + let existingItem = items[index]; + + if(existingItem.id === k) + { + items.splice(index, 1); + + this._items = items; + + return existingItem; + } + + index++; + } + + return null; + } + + public getTotalCount(): number + { + if(this._category === FurniCategory.POST_IT) + { + let count = 0; + let index = 0; + + while(index < this._items.length) + { + const item = this.getItemByIndex(index); + + count = (count + parseInt(item.stuffData.getLegacyString())); + + index++; + } + + return count; + } + + return this._items.length; + } + + public getUnlockedCount(): number + { + if(this.category === FurniCategory.POST_IT) return this.getTotalCount(); + + let count = 0; + let index = 0; + + while(index < this._items.length) + { + const item = this.getItemByIndex(index); + + if(!item.locked) count++; + + index++; + } + + return count; + } + + public getLastItem(): FurnitureItem + { + if(!this._items.length) return null; + + const item = this.getItemByIndex((this._items.length - 1)); + + return item; + } + + public unlockAllItems(): void + { + const items = [ ...this._items ]; + + let index = 0; + + while(index < items.length) + { + const item = items[index]; + + if(item.locked) + { + const newItem = item.clone(); + + newItem.locked = false; + + items[index] = newItem; + } + + index++; + } + + this._items = items; + } + + public lockItemIds(itemIds: number[]): boolean + { + const items = [ ...this._items ]; + + let index = 0; + let updated = false; + + while(index < items.length) + { + const item = items[index]; + const locked = (itemIds.indexOf(item.ref) >= 0); + + if(item.locked !== locked) + { + updated = true; + + const newItem = item.clone(); + + newItem.locked = locked; + + items[index] = newItem; + } + + index++; + } + + this._items = items; + + return updated; + } + + private setName(): void + { + const k = this.getLastItem(); + + if(!k) + { + this._name = ''; + + return; + } + + let key = ''; + + switch(this._category) + { + case FurniCategory.POSTER: + key = (('poster_' + k.stuffData.getLegacyString()) + '_name'); + break; + case FurniCategory.TRAX_SONG: + this._name = 'SONG_NAME'; + return; + default: + if(this.isWallItem) + { + key = ('wallItem.name.' + k.type); + } + else + { + key = ('roomItem.name.' + k.type); + } + } + + this._name = LocalizeText(key); + } + + private setDescription(): void + { + this._description = ''; + } + + private setIcon(): void + { + if(this._iconUrl) return; + + let url = null; + + if(this.isWallItem) + { + url = this._roomEngine.getFurnitureWallIconUrl(this._type, this._stuffData.getLegacyString()); + } + else + { + url = this._roomEngine.getFurnitureFloorIconUrl(this._type); + } + + if(!url) return; + + this._iconUrl = url; + } + + public get type(): number + { + return this._type; + } + + public get category(): number + { + return this._category; + } + + public get stuffData(): IObjectData + { + return this._stuffData; + } + + public get extra(): number + { + return this._extra; + } + + public get iconUrl(): string + { + return this._iconUrl; + } + + public get name(): string + { + return this._name; + } + + public get description(): string + { + return this._description; + } + + public get hasUnseenItems(): boolean + { + return this._hasUnseenItems; + } + + public set hasUnseenItems(flag: boolean) + { + this._hasUnseenItems = flag; + } + + public get locked(): boolean + { + return this._locked; + } + + public set locked(flag: boolean) + { + this._locked = flag; + } + + public get selected(): boolean + { + return this._selected; + } + + public set selected(flag: boolean) + { + this._selected = flag; + } + + public get isWallItem(): boolean + { + const item = this.getItemByIndex(0); + + return (item ? item.isWallItem : false); + } + + public get isGroupable(): boolean + { + const item = this.getItemByIndex(0); + + return (item ? item.isGroupable : false); + } + + public get isSellable(): boolean + { + const item = this.getItemByIndex(0); + + return (item ? item.sellable : false); + } + + public get items(): FurnitureItem[] + { + return this._items; + } + + public set items(items: FurnitureItem[]) + { + this._items = items; + } +} diff --git a/src/api/inventory/IBotItem.ts b/src/api/inventory/IBotItem.ts new file mode 100644 index 0000000..0a370ba --- /dev/null +++ b/src/api/inventory/IBotItem.ts @@ -0,0 +1,6 @@ +import { BotData } from '@nitrots/nitro-renderer'; + +export interface IBotItem +{ + botData: BotData; +} diff --git a/src/api/inventory/IFurnitureItem.ts b/src/api/inventory/IFurnitureItem.ts new file mode 100644 index 0000000..435597d --- /dev/null +++ b/src/api/inventory/IFurnitureItem.ts @@ -0,0 +1,17 @@ +import { IObjectData } from '@nitrots/nitro-renderer'; + +export interface IFurnitureItem +{ + id: number; + ref: number; + type: number; + stuffData: IObjectData; + extra: number; + category: number; + recyclable: boolean; + isTradable: boolean; + isGroupable: boolean; + sellable: boolean; + locked: boolean; + isWallItem: boolean; +} diff --git a/src/api/inventory/IPetItem.ts b/src/api/inventory/IPetItem.ts new file mode 100644 index 0000000..910d5df --- /dev/null +++ b/src/api/inventory/IPetItem.ts @@ -0,0 +1,6 @@ +import { PetData } from '@nitrots/nitro-renderer'; + +export interface IPetItem +{ + petData: PetData; +} diff --git a/src/api/inventory/IUnseenItemTracker.ts b/src/api/inventory/IUnseenItemTracker.ts new file mode 100644 index 0000000..8a70a16 --- /dev/null +++ b/src/api/inventory/IUnseenItemTracker.ts @@ -0,0 +1,12 @@ +export interface IUnseenItemTracker +{ + dispose(): void; + resetCategory(category: number): boolean; + resetItems(category: number, itemIds: number[]): boolean; + isUnseen(category: number, itemId: number): boolean; + removeUnseen(category: number, itemId: number): boolean; + getIds(category: number): number[]; + getCount(category: number): number; + getFullCount(): number; + addItems(category: number, itemIds: number[]): void; +} diff --git a/src/api/inventory/InventoryUtilities.ts b/src/api/inventory/InventoryUtilities.ts new file mode 100644 index 0000000..ac28cbd --- /dev/null +++ b/src/api/inventory/InventoryUtilities.ts @@ -0,0 +1,117 @@ +import { CreateLinkEvent, FurniturePlacePaintComposer, GetRoomEngine, GetRoomSessionManager, RoomObjectCategory, RoomObjectPlacementSource, RoomObjectType } from '@nitrots/nitro-renderer'; +import { SendMessageComposer } from '../nitro'; +import { FurniCategory } from './FurniCategory'; +import { GroupItem } from './GroupItem'; +import { IBotItem } from './IBotItem'; +import { IPetItem } from './IPetItem'; + +let objectMoverRequested = false; +let itemIdInPlacing = -1; + +export const isObjectMoverRequested = () => objectMoverRequested; + +export const setObjectMoverRequested = (flag: boolean) => objectMoverRequested = flag; + +export const getPlacingItemId = () => itemIdInPlacing; + +export const setPlacingItemId = (id: number) => (itemIdInPlacing = id); + +export const cancelRoomObjectPlacement = () => +{ + if(getPlacingItemId() === -1) return; + + GetRoomEngine().cancelRoomObjectPlacement(); + + setPlacingItemId(-1); + setObjectMoverRequested(false); +}; + +export const attemptPetPlacement = (petItem: IPetItem, flag: boolean = false) => +{ + const petData = petItem.petData; + + if(!petData) return false; + + const session = GetRoomSessionManager().getSession(1); + + if(!session) return false; + + if(!session.isRoomOwner && !session.allowPets) return false; + + CreateLinkEvent('inventory/hide'); + + if(GetRoomEngine().processRoomObjectPlacement(RoomObjectPlacementSource.INVENTORY, -(petData.id), RoomObjectCategory.UNIT, RoomObjectType.PET, petData.figureData.figuredata)) + { + setPlacingItemId(petData.id); + setObjectMoverRequested(true); + } + + return true; +}; + +export const attemptItemPlacement = (groupItem: GroupItem, flag: boolean = false) => +{ + if(!groupItem || !groupItem.getUnlockedCount()) return false; + + const item = groupItem.getLastItem(); + + if(!item) return false; + + if((item.category === FurniCategory.FLOOR) || (item.category === FurniCategory.WALL_PAPER) || (item.category === FurniCategory.LANDSCAPE)) + { + if(flag) return false; + + SendMessageComposer(new FurniturePlacePaintComposer(item.id)); + + return false; + } + else + { + CreateLinkEvent('inventory/hide'); + + let category = 0; + let isMoving = false; + + if(item.isWallItem) category = RoomObjectCategory.WALL; + else category = RoomObjectCategory.FLOOR; + + if((item.category === FurniCategory.POSTER)) // or external image from furnidata + { + isMoving = GetRoomEngine().processRoomObjectPlacement(RoomObjectPlacementSource.INVENTORY, item.id, category, item.type, item.stuffData.getLegacyString()); + } + else + { + isMoving = GetRoomEngine().processRoomObjectPlacement(RoomObjectPlacementSource.INVENTORY, item.id, category, item.type, item.extra.toString(), item.stuffData); + } + + if(isMoving) + { + setPlacingItemId(item.ref); + setObjectMoverRequested(true); + } + } + + return true; +}; + + +export const attemptBotPlacement = (botItem: IBotItem, flag: boolean = false) => +{ + const botData = botItem.botData; + + if(!botData) return false; + + const session = GetRoomSessionManager().getSession(1); + + if(!session || !session.isRoomOwner) return false; + + CreateLinkEvent('inventory/hide'); + + if(GetRoomEngine().processRoomObjectPlacement(RoomObjectPlacementSource.INVENTORY, -(botData.id), RoomObjectCategory.UNIT, RoomObjectType.RENTABLE_BOT, botData.figure)) + { + setPlacingItemId(botData.id); + setObjectMoverRequested(true); + } + + return true; +}; diff --git a/src/api/inventory/PetUtilities.ts b/src/api/inventory/PetUtilities.ts new file mode 100644 index 0000000..c53ada2 --- /dev/null +++ b/src/api/inventory/PetUtilities.ts @@ -0,0 +1,103 @@ +import { CreateLinkEvent, PetData } from '@nitrots/nitro-renderer'; +import { IPetItem } from './IPetItem'; +import { cancelRoomObjectPlacement, getPlacingItemId } from './InventoryUtilities'; +import { UnseenItemCategory } from './UnseenItemCategory'; + +export const getAllPetIds = (petItems: IPetItem[]) => petItems.map(item => item.petData.id); + +export const addSinglePetItem = (petData: PetData, set: IPetItem[], unseen: boolean = true) => +{ + const petItem = { petData }; + + if(unseen) + { + //petItem.isUnseen = true; + + set.unshift(petItem); + } + else + { + set.push(petItem); + } + + return petItem; +}; + +export const removePetItemById = (id: number, set: IPetItem[]) => +{ + let index = 0; + + while(index < set.length) + { + const petItem = set[index]; + + if(petItem && (petItem.petData.id === id)) + { + if(getPlacingItemId() === petItem.petData.id) + { + cancelRoomObjectPlacement(); + + CreateLinkEvent('inventory/open'); + } + + set.splice(index, 1); + + return petItem; + } + + index++; + } + + return null; +}; + +export const processPetFragment = (set: IPetItem[], fragment: Map, isUnseen: (category: number, itemId: number) => boolean) => +{ + const existingIds = getAllPetIds(set); + const addedIds: number[] = []; + const removedIds: number[] = []; + + for(const key of fragment.keys()) (existingIds.indexOf(key) === -1) && addedIds.push(key); + + for(const itemId of existingIds) (!fragment.get(itemId)) && removedIds.push(itemId); + + const emptyExistingSet = (existingIds.length === 0); + + for(const id of removedIds) removePetItemById(id, set); + + for(const id of addedIds) + { + const parser = fragment.get(id); + + if(!parser) continue; + + addSinglePetItem(parser, set, isUnseen(UnseenItemCategory.PET, parser.id)); + } + + return set; +}; + +export const mergePetFragments = (fragment: Map, totalFragments: number, fragmentNumber: number, fragments: Map[]) => +{ + if(totalFragments === 1) return fragment; + + fragments[fragmentNumber] = fragment; + + for(const frag of fragments) + { + if(!frag) return null; + } + + const merged: Map = new Map(); + + for(const frag of fragments) + { + for(const [ key, value ] of frag) merged.set(key, value); + + frag.clear(); + } + + fragments = null; + + return merged; +}; diff --git a/src/api/inventory/TradeState.ts b/src/api/inventory/TradeState.ts new file mode 100644 index 0000000..3df418b --- /dev/null +++ b/src/api/inventory/TradeState.ts @@ -0,0 +1,10 @@ +export class TradeState +{ + public static TRADING_STATE_READY: number = 0; + public static TRADING_STATE_RUNNING: number = 1; + public static TRADING_STATE_COUNTDOWN: number = 2; + public static TRADING_STATE_CONFIRMING: number = 3; + public static TRADING_STATE_CONFIRMED: number = 4; + public static TRADING_STATE_COMPLETED: number = 5; + public static TRADING_STATE_CANCELLED: number = 6; +} diff --git a/src/api/inventory/TradeUserData.ts b/src/api/inventory/TradeUserData.ts new file mode 100644 index 0000000..ba3d66b --- /dev/null +++ b/src/api/inventory/TradeUserData.ts @@ -0,0 +1,15 @@ +import { AdvancedMap } from '@nitrots/nitro-renderer'; +import { GroupItem } from './GroupItem'; + +export class TradeUserData +{ + constructor( + public userId: number = -1, + public userName: string = '', + public userItems: AdvancedMap = new AdvancedMap(), + public itemCount: number = 0, + public creditsCount: number = 0, + public accepts: boolean = false, + public canTrade: boolean = false) + {} +} diff --git a/src/api/inventory/TradingNotificationType.ts b/src/api/inventory/TradingNotificationType.ts new file mode 100644 index 0000000..4aed490 --- /dev/null +++ b/src/api/inventory/TradingNotificationType.ts @@ -0,0 +1,12 @@ +export class TradingNotificationType +{ + public static ALERT_SCAM: number = 0; + public static HOTEL_TRADING_DISABLED = 1; + public static YOU_NOT_ALLOWED: number = 2; + public static THEY_NOT_ALLOWED: number = 4; + public static ROOM_DISABLED: number = 6; + public static YOU_OPEN: number = 7; + public static THEY_OPEN: number = 8; + public static ERROR_WHILE_COMMIT: number = 9; + public static THEY_CANCELLED: number = 10; +} diff --git a/src/api/inventory/TradingUtilities.ts b/src/api/inventory/TradingUtilities.ts new file mode 100644 index 0000000..8cdfb96 --- /dev/null +++ b/src/api/inventory/TradingUtilities.ts @@ -0,0 +1,70 @@ +import { AdvancedMap, GetSessionDataManager, IObjectData, ItemDataStructure, StringDataType } from '@nitrots/nitro-renderer'; +import { FurniCategory } from './FurniCategory'; +import { FurnitureItem } from './FurnitureItem'; +import { createGroupItem } from './FurnitureUtilities'; +import { GroupItem } from './GroupItem'; + +const isExternalImage = (spriteId: number) => GetSessionDataManager().getWallItemData(spriteId)?.isExternalImage || false; + +export const parseTradeItems = (items: ItemDataStructure[]) => +{ + const existingItems = new AdvancedMap(); + const totalItems = items.length; + + if(totalItems) + { + for(const item of items) + { + const spriteId = item.spriteId; + const category = item.category; + + let name = (item.furniType + spriteId); + + if(!item.isGroupable || isExternalImage(spriteId)) + { + name = ('itemid' + item.itemId); + } + + if(item.category === FurniCategory.POSTER) + { + name = (item.itemId + 'poster' + item.stuffData.getLegacyString()); + } + + else if(item.category === FurniCategory.GUILD_FURNI) + { + name = ''; + } + + let groupItem = ((item.isGroupable && !isExternalImage(item.spriteId)) ? existingItems.getValue(name) : null); + + if(!groupItem) + { + groupItem = createGroupItem(spriteId, category, item.stuffData); + + existingItems.add(name, groupItem); + } + + groupItem.push(new FurnitureItem(item)); + } + } + + return existingItems; +}; + +export const getGuildFurniType = (spriteId: number, stuffData: IObjectData) => +{ + let type = spriteId.toString(); + + if(!(stuffData instanceof StringDataType)) return type; + + let i = 1; + + while(i < 5) + { + type = (type + (',' + stuffData.getValue(i))); + + i++; + } + + return type; +}; diff --git a/src/api/inventory/UnseenItemCategory.ts b/src/api/inventory/UnseenItemCategory.ts new file mode 100644 index 0000000..cbd7e9b --- /dev/null +++ b/src/api/inventory/UnseenItemCategory.ts @@ -0,0 +1,9 @@ +export class UnseenItemCategory +{ + public static FURNI: number = 1; + public static RENTABLE: number = 2; + public static PET: number = 3; + public static BADGE: number = 4; + public static BOT: number = 5; + public static GAMES: number = 6; +} diff --git a/src/api/inventory/index.ts b/src/api/inventory/index.ts new file mode 100644 index 0000000..6a245d7 --- /dev/null +++ b/src/api/inventory/index.ts @@ -0,0 +1,15 @@ +export * from './FurniCategory'; +export * from './FurnitureItem'; +export * from './FurnitureUtilities'; +export * from './GroupItem'; +export * from './IBotItem'; +export * from './IFurnitureItem'; +export * from './IPetItem'; +export * from './IUnseenItemTracker'; +export * from './InventoryUtilities'; +export * from './PetUtilities'; +export * from './TradeState'; +export * from './TradeUserData'; +export * from './TradingNotificationType'; +export * from './TradingUtilities'; +export * from './UnseenItemCategory'; diff --git a/src/api/mod-tools/GetIssueCategoryName.ts b/src/api/mod-tools/GetIssueCategoryName.ts new file mode 100644 index 0000000..ce2b902 --- /dev/null +++ b/src/api/mod-tools/GetIssueCategoryName.ts @@ -0,0 +1,35 @@ +export const GetIssueCategoryName = (categoryId: number) => +{ + switch(categoryId) + { + case 1: + case 2: + return 'Normal'; + case 3: + return 'Automatic'; + case 4: + return 'Automatic IM'; + case 5: + return 'Guide System'; + case 6: + return 'IM'; + case 7: + return 'Room'; + case 8: + return 'Panic'; + case 9: + return 'Guardian'; + case 10: + return 'Automatic Helper'; + case 11: + return 'Discussion'; + case 12: + return 'Selfie'; + case 14: + return 'Photo'; + case 15: + return 'Ambassador'; + } + + return 'Unknown'; +}; diff --git a/src/api/mod-tools/ISelectedUser.ts b/src/api/mod-tools/ISelectedUser.ts new file mode 100644 index 0000000..4f6e76b --- /dev/null +++ b/src/api/mod-tools/ISelectedUser.ts @@ -0,0 +1,5 @@ +export interface ISelectedUser +{ + userId: number; + username: string; +} diff --git a/src/api/mod-tools/IUserInfo.ts b/src/api/mod-tools/IUserInfo.ts new file mode 100644 index 0000000..8d49aa7 --- /dev/null +++ b/src/api/mod-tools/IUserInfo.ts @@ -0,0 +1,6 @@ +export interface IUserInfo +{ + nameKey: string; + nameKeyFallback: string; + value: string; +} diff --git a/src/api/mod-tools/ModActionDefinition.ts b/src/api/mod-tools/ModActionDefinition.ts new file mode 100644 index 0000000..b28aa9c --- /dev/null +++ b/src/api/mod-tools/ModActionDefinition.ts @@ -0,0 +1,49 @@ +export class ModActionDefinition +{ + public static ALERT: number = 1; + public static MUTE: number = 2; + public static BAN: number = 3; + public static KICK: number = 4; + public static TRADE_LOCK: number = 5; + public static MESSAGE: number = 6; + + private readonly _actionId: number; + private readonly _name: string; + private readonly _actionType: number; + private readonly _sanctionTypeId: number; + private readonly _actionLengthHours: number; + + constructor(actionId: number, actionName: string, actionType: number, sanctionTypeId: number, actionLengthHours:number) + { + this._actionId = actionId; + this._name = actionName; + this._actionType = actionType; + this._sanctionTypeId = sanctionTypeId; + this._actionLengthHours = actionLengthHours; + } + + public get actionId(): number + { + return this._actionId; + } + + public get name(): string + { + return this._name; + } + + public get actionType(): number + { + return this._actionType; + } + + public get sanctionTypeId(): number + { + return this._sanctionTypeId; + } + + public get actionLengthHours(): number + { + return this._actionLengthHours; + } +} diff --git a/src/api/mod-tools/index.ts b/src/api/mod-tools/index.ts new file mode 100644 index 0000000..004bbaa --- /dev/null +++ b/src/api/mod-tools/index.ts @@ -0,0 +1,4 @@ +export * from './GetIssueCategoryName'; +export * from './ISelectedUser'; +export * from './IUserInfo'; +export * from './ModActionDefinition'; diff --git a/src/api/navigator/DoorStateType.ts b/src/api/navigator/DoorStateType.ts new file mode 100644 index 0000000..1f8a8ef --- /dev/null +++ b/src/api/navigator/DoorStateType.ts @@ -0,0 +1,12 @@ +export class DoorStateType +{ + public static NONE: number = 0; + public static START_DOORBELL: number = 1; + public static START_PASSWORD: number = 2; + public static STATE_PENDING_SERVER: number = 3; + public static UPDATE_STATE: number = 4; + public static STATE_WAITING: number = 5; + public static STATE_NO_ANSWER: number = 6; + public static STATE_WRONG_PASSWORD: number = 7; + public static STATE_ACCEPTED: number = 8; +} diff --git a/src/api/navigator/INavigatorData.ts b/src/api/navigator/INavigatorData.ts new file mode 100644 index 0000000..e50b6fe --- /dev/null +++ b/src/api/navigator/INavigatorData.ts @@ -0,0 +1,17 @@ +import { RoomDataParser } from '@nitrots/nitro-renderer'; + +export interface INavigatorData +{ + homeRoomId: number; + settingsReceived: boolean; + enteredGuestRoom: RoomDataParser; + currentRoomOwner: boolean; + currentRoomId: number; + currentRoomIsStaffPick: boolean; + createdFlatId: number; + avatarId: number; + roomPicker: boolean; + eventMod: boolean; + currentRoomRating: number; + canRate: boolean; +} diff --git a/src/api/navigator/INavigatorSearchFilter.ts b/src/api/navigator/INavigatorSearchFilter.ts new file mode 100644 index 0000000..179d5d5 --- /dev/null +++ b/src/api/navigator/INavigatorSearchFilter.ts @@ -0,0 +1,5 @@ +export interface INavigatorSearchFilter +{ + name: string; + query: string; +} diff --git a/src/api/navigator/IRoomChatSettings.ts b/src/api/navigator/IRoomChatSettings.ts new file mode 100644 index 0000000..aee426c --- /dev/null +++ b/src/api/navigator/IRoomChatSettings.ts @@ -0,0 +1,8 @@ +export interface IRoomChatSettings +{ + mode: number; + weight: number; + speed: number; + distance: number; + protection: number; +} diff --git a/src/api/navigator/IRoomData.ts b/src/api/navigator/IRoomData.ts new file mode 100644 index 0000000..9146314 --- /dev/null +++ b/src/api/navigator/IRoomData.ts @@ -0,0 +1,23 @@ +import { IRoomChatSettings } from './IRoomChatSettings'; +import { IRoomModerationSettings } from './IRoomModerationSettings'; + +export interface IRoomData +{ + roomId: number; + roomName: string; + roomDescription: string; + categoryId: number; + userCount: number; + tags: string[]; + tradeState: number; + allowWalkthrough: boolean; + lockState: number; + password: string; + allowPets: boolean; + allowPetsEat: boolean; + hideWalls: boolean; + wallThickness: number; + floorThickness: number; + chatSettings: IRoomChatSettings; + moderationSettings: IRoomModerationSettings; +} diff --git a/src/api/navigator/IRoomModel.ts b/src/api/navigator/IRoomModel.ts new file mode 100644 index 0000000..73dfe27 --- /dev/null +++ b/src/api/navigator/IRoomModel.ts @@ -0,0 +1,6 @@ +export interface IRoomModel +{ + clubLevel: number; + tileSize: number; + name: string; +} diff --git a/src/api/navigator/IRoomModerationSettings.ts b/src/api/navigator/IRoomModerationSettings.ts new file mode 100644 index 0000000..266fe47 --- /dev/null +++ b/src/api/navigator/IRoomModerationSettings.ts @@ -0,0 +1,6 @@ +export interface IRoomModerationSettings +{ + allowMute: number; + allowKick: number; + allowBan: number; +} diff --git a/src/api/navigator/NavigatorSearchResultViewDisplayMode.ts b/src/api/navigator/NavigatorSearchResultViewDisplayMode.ts new file mode 100644 index 0000000..b532d1a --- /dev/null +++ b/src/api/navigator/NavigatorSearchResultViewDisplayMode.ts @@ -0,0 +1,6 @@ +export class NavigatorSearchResultViewDisplayMode +{ + public static readonly LIST: number = 0; + public static readonly THUMBNAILS: number = 1; + public static readonly FORCED_THUMBNAILS: number = 2; +} diff --git a/src/api/navigator/RoomInfoData.ts b/src/api/navigator/RoomInfoData.ts new file mode 100644 index 0000000..fc0a93b --- /dev/null +++ b/src/api/navigator/RoomInfoData.ts @@ -0,0 +1,60 @@ +import { RoomDataParser } from '@nitrots/nitro-renderer'; + +export class RoomInfoData +{ + private _enteredGuestRoom: RoomDataParser = null; + private _createdRoomId: number = 0; + private _currentRoomId: number = 0; + private _currentRoomOwner: boolean = false; + private _canRate: boolean = false; + + public get enteredGuestRoom(): RoomDataParser + { + return this._enteredGuestRoom; + } + + public set enteredGuestRoom(data: RoomDataParser) + { + this._enteredGuestRoom = data; + } + + public get createdRoomId(): number + { + return this._createdRoomId; + } + + public set createdRoomId(id: number) + { + this._createdRoomId = id; + } + + public get currentRoomId(): number + { + return this._currentRoomId; + } + + public set currentRoomId(id: number) + { + this._currentRoomId = id; + } + + public get currentRoomOwner(): boolean + { + return this._currentRoomOwner; + } + + public set currentRoomOwner(flag: boolean) + { + this._currentRoomOwner = flag; + } + + public get canRate(): boolean + { + return this._canRate; + } + + public set canRate(flag: boolean) + { + this._canRate = flag; + } +} diff --git a/src/api/navigator/RoomSettingsUtils.ts b/src/api/navigator/RoomSettingsUtils.ts new file mode 100644 index 0000000..36f636f --- /dev/null +++ b/src/api/navigator/RoomSettingsUtils.ts @@ -0,0 +1,10 @@ +const BuildMaxVisitorsList = () => +{ + const list: number[] = []; + + for(let i = 10; i <= 100; i = i + 10) list.push(i); + + return list; +}; + +export const GetMaxVisitorsList = BuildMaxVisitorsList(); diff --git a/src/api/navigator/SearchFilterOptions.ts b/src/api/navigator/SearchFilterOptions.ts new file mode 100644 index 0000000..aaf1290 --- /dev/null +++ b/src/api/navigator/SearchFilterOptions.ts @@ -0,0 +1,24 @@ +import { INavigatorSearchFilter } from './INavigatorSearchFilter'; + +export const SearchFilterOptions: INavigatorSearchFilter[] = [ + { + name: 'anything', + query: null + }, + { + name: 'room.name', + query: 'roomname' + }, + { + name: 'owner', + query: 'owner' + }, + { + name: 'tag', + query: 'tag' + }, + { + name: 'group', + query: 'group' + } +]; diff --git a/src/api/navigator/TryVisitRoom.ts b/src/api/navigator/TryVisitRoom.ts new file mode 100644 index 0000000..81138d6 --- /dev/null +++ b/src/api/navigator/TryVisitRoom.ts @@ -0,0 +1,7 @@ +import { GetGuestRoomMessageComposer } from '@nitrots/nitro-renderer'; +import { SendMessageComposer } from '../nitro'; + +export function TryVisitRoom(roomId: number): void +{ + SendMessageComposer(new GetGuestRoomMessageComposer(roomId, false, true)); +} diff --git a/src/api/navigator/index.ts b/src/api/navigator/index.ts new file mode 100644 index 0000000..bceb33e --- /dev/null +++ b/src/api/navigator/index.ts @@ -0,0 +1,12 @@ +export * from './DoorStateType'; +export * from './INavigatorData'; +export * from './INavigatorSearchFilter'; +export * from './IRoomChatSettings'; +export * from './IRoomData'; +export * from './IRoomModel'; +export * from './IRoomModerationSettings'; +export * from './NavigatorSearchResultViewDisplayMode'; +export * from './RoomInfoData'; +export * from './RoomSettingsUtils'; +export * from './SearchFilterOptions'; +export * from './TryVisitRoom'; diff --git a/src/api/nitro/GetConfigurationValue.ts b/src/api/nitro/GetConfigurationValue.ts new file mode 100644 index 0000000..755ca1d --- /dev/null +++ b/src/api/nitro/GetConfigurationValue.ts @@ -0,0 +1,6 @@ +import { GetConfiguration } from '@nitrots/nitro-renderer'; + +export function GetConfigurationValue(key: string, value: T = null): T +{ + return GetConfiguration().getValue(key, value); +} diff --git a/src/api/nitro/OpenUrl.ts b/src/api/nitro/OpenUrl.ts new file mode 100644 index 0000000..44992e8 --- /dev/null +++ b/src/api/nitro/OpenUrl.ts @@ -0,0 +1,15 @@ +import { CreateLinkEvent, HabboWebTools } from '@nitrots/nitro-renderer'; + +export const OpenUrl = (url: string) => +{ + if(!url || !url.length) return; + + if(url.startsWith('http')) + { + HabboWebTools.openWebPage(url); + } + else + { + CreateLinkEvent(url); + } +}; diff --git a/src/api/nitro/SendMessageComposer.ts b/src/api/nitro/SendMessageComposer.ts new file mode 100644 index 0000000..4229c28 --- /dev/null +++ b/src/api/nitro/SendMessageComposer.ts @@ -0,0 +1,3 @@ +import { GetCommunication, IMessageComposer } from '@nitrots/nitro-renderer'; + +export const SendMessageComposer = (event: IMessageComposer) => GetCommunication().connection.send(event); diff --git a/src/api/nitro/index.ts b/src/api/nitro/index.ts new file mode 100644 index 0000000..11b9d02 --- /dev/null +++ b/src/api/nitro/index.ts @@ -0,0 +1,5 @@ +export * from './GetConfigurationValue'; +export * from './OpenUrl'; +export * from './SendMessageComposer'; +export * from './room'; +export * from './session'; diff --git a/src/api/nitro/room/DispatchMouseEvent.ts b/src/api/nitro/room/DispatchMouseEvent.ts new file mode 100644 index 0000000..ccbe0bc --- /dev/null +++ b/src/api/nitro/room/DispatchMouseEvent.ts @@ -0,0 +1,54 @@ +import { GetRoomEngine, MouseEventType } from '@nitrots/nitro-renderer'; + +let didMouseMove = false; +let lastClick = 0; +let clickCount = 0; + +export const DispatchMouseEvent = (event: MouseEvent, canvasId: number = 1) => +{ + const x = event.clientX; + const y = event.clientY; + + let eventType = event.type; + + if(eventType === MouseEventType.MOUSE_CLICK) + { + if(lastClick) + { + clickCount = 1; + + if(lastClick >= Date.now() - 300) clickCount++; + } + + lastClick = Date.now(); + + if(clickCount === 2) + { + if(!didMouseMove) eventType = MouseEventType.DOUBLE_CLICK; + + clickCount = 0; + lastClick = null; + } + } + + switch(eventType) + { + case MouseEventType.MOUSE_CLICK: + break; + case MouseEventType.DOUBLE_CLICK: + break; + case MouseEventType.MOUSE_MOVE: + didMouseMove = true; + break; + case MouseEventType.MOUSE_DOWN: + didMouseMove = false; + break; + case MouseEventType.MOUSE_UP: + break; + case MouseEventType.RIGHT_CLICK: + break; + default: return; + } + + GetRoomEngine().dispatchMouseEvent(canvasId, x, y, eventType, event.altKey, (event.ctrlKey || event.metaKey), event.shiftKey, false); +}; diff --git a/src/api/nitro/room/DispatchTouchEvent.ts b/src/api/nitro/room/DispatchTouchEvent.ts new file mode 100644 index 0000000..7a90997 --- /dev/null +++ b/src/api/nitro/room/DispatchTouchEvent.ts @@ -0,0 +1,81 @@ +import { GetRoomEngine, MouseEventType, TouchEventType } from '@nitrots/nitro-renderer'; + +let didMouseMove = false; +let lastClick = 0; +let clickCount = 0; + +export const DispatchTouchEvent = (event: TouchEvent, canvasId: number = 1, longTouch: boolean = false, altKey: boolean = false, ctrlKey: boolean = false, shiftKey: boolean = false) => +{ + let x = 0; + let y = 0; + + if(event.touches[0]) + { + x = event.touches[0].clientX; + y = event.touches[0].clientY; + } + + else if(event.changedTouches[0]) + { + x = event.changedTouches[0].clientX; + y = event.changedTouches[0].clientY; + } + + let eventType = event.type; + + if(longTouch) eventType = TouchEventType.TOUCH_LONG; + + if(eventType === MouseEventType.MOUSE_CLICK || eventType === TouchEventType.TOUCH_END) + { + eventType = MouseEventType.MOUSE_CLICK; + + if(lastClick) + { + clickCount = 1; + + if(lastClick >= (Date.now() - 300)) clickCount++; + } + + lastClick = Date.now(); + + if(clickCount === 2) + { + if(!didMouseMove) eventType = MouseEventType.DOUBLE_CLICK; + + clickCount = 0; + lastClick = null; + } + } + + switch(eventType) + { + case MouseEventType.MOUSE_CLICK: + break; + case MouseEventType.DOUBLE_CLICK: + break; + case TouchEventType.TOUCH_START: + eventType = MouseEventType.MOUSE_DOWN; + + didMouseMove = false; + break; + case TouchEventType.TOUCH_MOVE: + eventType = MouseEventType.MOUSE_MOVE; + + didMouseMove = true; + break; + case TouchEventType.TOUCH_END: + eventType = MouseEventType.MOUSE_UP; + break; + case TouchEventType.TOUCH_LONG: + eventType = MouseEventType.MOUSE_DOWN_LONG; + break; + default: return; + } + + if(eventType === TouchEventType.TOUCH_START) + { + GetRoomEngine().dispatchMouseEvent(canvasId, x, y, eventType, altKey, ctrlKey, shiftKey, false); + } + + GetRoomEngine().dispatchMouseEvent(canvasId, x, y, eventType, altKey, ctrlKey, shiftKey, false); +}; diff --git a/src/api/nitro/room/GetOwnRoomObject.ts b/src/api/nitro/room/GetOwnRoomObject.ts new file mode 100644 index 0000000..aae0b77 --- /dev/null +++ b/src/api/nitro/room/GetOwnRoomObject.ts @@ -0,0 +1,31 @@ +import { GetRoomEngine, GetSessionDataManager, IRoomObjectController, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { GetRoomSession } from '../session'; + +export function GetOwnRoomObject(): IRoomObjectController +{ + const userId = GetSessionDataManager().userId; + const roomId = GetRoomEngine().activeRoomId; + const category = RoomObjectCategory.UNIT; + const totalObjects = GetRoomEngine().getTotalObjectsForManager(roomId, category); + + let i = 0; + + while(i < totalObjects) + { + const roomObject = GetRoomEngine().getRoomObjectByIndex(roomId, i, category); + + if(roomObject) + { + const userData = GetRoomSession().userDataManager.getUserDataByIndex(roomObject.id); + + if(userData) + { + if(userData.webID === userId) return roomObject; + } + } + + i++; + } + + return null; +} diff --git a/src/api/nitro/room/GetRoomObjectBounds.ts b/src/api/nitro/room/GetRoomObjectBounds.ts new file mode 100644 index 0000000..dca0338 --- /dev/null +++ b/src/api/nitro/room/GetRoomObjectBounds.ts @@ -0,0 +1,13 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; + +export const GetRoomObjectBounds = (roomId: number, objectId: number, category: number, canvasId = 1) => +{ + const rectangle = GetRoomEngine().getRoomObjectBoundingRectangle(roomId, objectId, category, canvasId); + + if(!rectangle) return null; + + rectangle.x = Math.round(rectangle.x); + rectangle.y = Math.round(rectangle.y); + + return rectangle; +}; diff --git a/src/api/nitro/room/GetRoomObjectScreenLocation.ts b/src/api/nitro/room/GetRoomObjectScreenLocation.ts new file mode 100644 index 0000000..4152609 --- /dev/null +++ b/src/api/nitro/room/GetRoomObjectScreenLocation.ts @@ -0,0 +1,13 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; + +export const GetRoomObjectScreenLocation = (roomId: number, objectId: number, category: number, canvasId = 1) => +{ + const point = GetRoomEngine().getRoomObjectScreenLocation(roomId, objectId, category, canvasId); + + if(!point) return null; + + point.x = Math.round(point.x); + point.y = Math.round(point.y); + + return point; +}; diff --git a/src/api/nitro/room/InitializeRoomInstanceRenderingCanvas.ts b/src/api/nitro/room/InitializeRoomInstanceRenderingCanvas.ts new file mode 100644 index 0000000..1289b5e --- /dev/null +++ b/src/api/nitro/room/InitializeRoomInstanceRenderingCanvas.ts @@ -0,0 +1,9 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; + +export const InitializeRoomInstanceRenderingCanvas = (width: number, height: number, canvasId: number = 1) => +{ + const roomEngine = GetRoomEngine(); + const roomId = roomEngine.activeRoomId; + + roomEngine.initializeRoomInstanceRenderingCanvas(roomId, canvasId, width, height); +}; diff --git a/src/api/nitro/room/IsFurnitureSelectionDisabled.ts b/src/api/nitro/room/IsFurnitureSelectionDisabled.ts new file mode 100644 index 0000000..e86f9a3 --- /dev/null +++ b/src/api/nitro/room/IsFurnitureSelectionDisabled.ts @@ -0,0 +1,22 @@ +import { GetRoomEngine, GetSessionDataManager, RoomEngineObjectEvent, RoomObjectVariable } from '@nitrots/nitro-renderer'; + +export function IsFurnitureSelectionDisabled(event: RoomEngineObjectEvent): boolean +{ + let result = false; + + const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category); + + if(roomObject) + { + const selectionDisabled = (roomObject.model.getValue(RoomObjectVariable.FURNITURE_SELECTION_DISABLED) === 1); + + if(selectionDisabled) + { + result = true; + + if(GetSessionDataManager().isModerator) result = false; + } + } + + return result; +} diff --git a/src/api/nitro/room/ProcessRoomObjectOperation.ts b/src/api/nitro/room/ProcessRoomObjectOperation.ts new file mode 100644 index 0000000..5a1c997 --- /dev/null +++ b/src/api/nitro/room/ProcessRoomObjectOperation.ts @@ -0,0 +1,6 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; + +export function ProcessRoomObjectOperation(objectId: number, category: number, operation: string): void +{ + GetRoomEngine().processRoomObjectOperation(objectId, category, operation); +} diff --git a/src/api/nitro/room/SetActiveRoomId.ts b/src/api/nitro/room/SetActiveRoomId.ts new file mode 100644 index 0000000..9446537 --- /dev/null +++ b/src/api/nitro/room/SetActiveRoomId.ts @@ -0,0 +1,6 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; + +export function SetActiveRoomId(roomId: number): void +{ + GetRoomEngine().setActiveRoomId(roomId); +} diff --git a/src/api/nitro/room/index.ts b/src/api/nitro/room/index.ts new file mode 100644 index 0000000..2af9c28 --- /dev/null +++ b/src/api/nitro/room/index.ts @@ -0,0 +1,9 @@ +export * from './DispatchMouseEvent'; +export * from './DispatchTouchEvent'; +export * from './GetOwnRoomObject'; +export * from './GetRoomObjectBounds'; +export * from './GetRoomObjectScreenLocation'; +export * from './InitializeRoomInstanceRenderingCanvas'; +export * from './IsFurnitureSelectionDisabled'; +export * from './ProcessRoomObjectOperation'; +export * from './SetActiveRoomId'; diff --git a/src/api/nitro/session/CanManipulateFurniture.ts b/src/api/nitro/session/CanManipulateFurniture.ts new file mode 100644 index 0000000..ba89efd --- /dev/null +++ b/src/api/nitro/session/CanManipulateFurniture.ts @@ -0,0 +1,9 @@ +import { GetRoomEngine, GetSessionDataManager, IRoomSession, RoomControllerLevel } from '@nitrots/nitro-renderer'; +import { IsOwnerOfFurniture } from './IsOwnerOfFurniture'; + +export function CanManipulateFurniture(roomSession: IRoomSession, objectId: number, category: number): boolean +{ + if(!roomSession) return false; + + return (roomSession.isRoomOwner || (roomSession.controllerLevel >= RoomControllerLevel.GUEST) || GetSessionDataManager().isModerator || IsOwnerOfFurniture(GetRoomEngine().getRoomObject(roomSession.roomId, objectId, category))); +} diff --git a/src/api/nitro/session/CreateRoomSession.ts b/src/api/nitro/session/CreateRoomSession.ts new file mode 100644 index 0000000..3f12bb4 --- /dev/null +++ b/src/api/nitro/session/CreateRoomSession.ts @@ -0,0 +1,6 @@ +import { GetRoomSessionManager } from '@nitrots/nitro-renderer'; + +export function CreateRoomSession(roomId: number, password: string = null): void +{ + GetRoomSessionManager().createSession(roomId, password); +} diff --git a/src/api/nitro/session/GetCanStandUp.ts b/src/api/nitro/session/GetCanStandUp.ts new file mode 100644 index 0000000..841ada9 --- /dev/null +++ b/src/api/nitro/session/GetCanStandUp.ts @@ -0,0 +1,13 @@ +import { AvatarAction, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { GetOwnRoomObject } from '../room'; + +export function GetCanStandUp(): string +{ + const roomObject = GetOwnRoomObject(); + + if(!roomObject) return AvatarAction.POSTURE_STAND; + + const model = roomObject.model; + + return model.getValue(RoomObjectVariable.FIGURE_CAN_STAND_UP); +} diff --git a/src/api/nitro/session/GetCanUseExpression.ts b/src/api/nitro/session/GetCanUseExpression.ts new file mode 100644 index 0000000..da27f6a --- /dev/null +++ b/src/api/nitro/session/GetCanUseExpression.ts @@ -0,0 +1,14 @@ +import { RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { GetOwnRoomObject } from '../room'; + +export function GetCanUseExpression(): boolean +{ + const roomObject = GetOwnRoomObject(); + + if(!roomObject) return false; + + const model = roomObject.model; + const effectId = model.getValue(RoomObjectVariable.FIGURE_EFFECT); + + return !((effectId === 29) || (effectId === 30) || (effectId === 185)); +} diff --git a/src/api/nitro/session/GetClubMemberLevel.ts b/src/api/nitro/session/GetClubMemberLevel.ts new file mode 100644 index 0000000..d3cdc37 --- /dev/null +++ b/src/api/nitro/session/GetClubMemberLevel.ts @@ -0,0 +1,9 @@ +import { GetSessionDataManager, HabboClubLevelEnum } from '@nitrots/nitro-renderer'; +import { GetConfigurationValue } from '../GetConfigurationValue'; + +export function GetClubMemberLevel(): number +{ + if(GetConfigurationValue('hc.disabled', false)) return HabboClubLevelEnum.VIP; + + return GetSessionDataManager().clubLevel; +} diff --git a/src/api/nitro/session/GetFurnitureData.ts b/src/api/nitro/session/GetFurnitureData.ts new file mode 100644 index 0000000..b7646df --- /dev/null +++ b/src/api/nitro/session/GetFurnitureData.ts @@ -0,0 +1,19 @@ +import { GetSessionDataManager, IFurnitureData } from '@nitrots/nitro-renderer'; +import { ProductTypeEnum } from '../../catalog'; + +export function GetFurnitureData(furniClassId: number, productType: string): IFurnitureData +{ + let furniData: IFurnitureData = null; + + switch(productType.toLowerCase()) + { + case ProductTypeEnum.FLOOR: + furniData = GetSessionDataManager().getFloorItemData(furniClassId); + break; + case ProductTypeEnum.WALL: + furniData = GetSessionDataManager().getWallItemData(furniClassId); + break; + } + + return furniData; +} diff --git a/src/api/nitro/session/GetFurnitureDataForProductOffer.ts b/src/api/nitro/session/GetFurnitureDataForProductOffer.ts new file mode 100644 index 0000000..b037765 --- /dev/null +++ b/src/api/nitro/session/GetFurnitureDataForProductOffer.ts @@ -0,0 +1,20 @@ +import { CatalogPageMessageProductData, FurnitureType, GetSessionDataManager, IFurnitureData } from '@nitrots/nitro-renderer'; + +export function GetFurnitureDataForProductOffer(offer: CatalogPageMessageProductData): IFurnitureData +{ + if (!offer) return null; + + let furniData: IFurnitureData = null; + + switch ((offer.productType) as FurnitureType) + { + case FurnitureType.FLOOR: + furniData = GetSessionDataManager().getFloorItemData(offer.furniClassId); + break; + case FurnitureType.WALL: + furniData = GetSessionDataManager().getWallItemData(offer.furniClassId); + break; + } + + return furniData; +} diff --git a/src/api/nitro/session/GetFurnitureDataForRoomObject.ts b/src/api/nitro/session/GetFurnitureDataForRoomObject.ts new file mode 100644 index 0000000..fb76b9e --- /dev/null +++ b/src/api/nitro/session/GetFurnitureDataForRoomObject.ts @@ -0,0 +1,20 @@ +import { GetRoomEngine, GetSessionDataManager, IFurnitureData, RoomObjectCategory, RoomObjectVariable } from '@nitrots/nitro-renderer'; + +export function GetFurnitureDataForRoomObject(roomId: number, objectId: number, category: number): IFurnitureData +{ + const roomObject = GetRoomEngine().getRoomObject(roomId, objectId, category); + + if(!roomObject) return; + + const typeId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID); + + switch(category) + { + case RoomObjectCategory.FLOOR: + return GetSessionDataManager().getFloorItemData(typeId); + case RoomObjectCategory.WALL: + return GetSessionDataManager().getWallItemData(typeId); + } + + return null; +} diff --git a/src/api/nitro/session/GetOwnPosture.ts b/src/api/nitro/session/GetOwnPosture.ts new file mode 100644 index 0000000..ed2a698 --- /dev/null +++ b/src/api/nitro/session/GetOwnPosture.ts @@ -0,0 +1,13 @@ +import { AvatarAction, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { GetOwnRoomObject } from '../room'; + +export function GetOwnPosture(): string +{ + const roomObject = GetOwnRoomObject(); + + if(!roomObject) return AvatarAction.POSTURE_STAND; + + const model = roomObject.model; + + return model.getValue(RoomObjectVariable.FIGURE_POSTURE); +} diff --git a/src/api/nitro/session/GetProductDataForLocalization.ts b/src/api/nitro/session/GetProductDataForLocalization.ts new file mode 100644 index 0000000..ac89803 --- /dev/null +++ b/src/api/nitro/session/GetProductDataForLocalization.ts @@ -0,0 +1,8 @@ +import { GetSessionDataManager, IProductData } from '@nitrots/nitro-renderer'; + +export function GetProductDataForLocalization(localizationId: string): IProductData +{ + if(!localizationId) return null; + + return GetSessionDataManager().getProductData(localizationId); +} diff --git a/src/api/nitro/session/GetRoomSession.ts b/src/api/nitro/session/GetRoomSession.ts new file mode 100644 index 0000000..da2af41 --- /dev/null +++ b/src/api/nitro/session/GetRoomSession.ts @@ -0,0 +1,3 @@ +import { GetRoomSessionManager } from '@nitrots/nitro-renderer'; + +export const GetRoomSession = () => GetRoomSessionManager().getSession(-1); diff --git a/src/api/nitro/session/GoToDesktop.ts b/src/api/nitro/session/GoToDesktop.ts new file mode 100644 index 0000000..34f2031 --- /dev/null +++ b/src/api/nitro/session/GoToDesktop.ts @@ -0,0 +1,7 @@ +import { DesktopViewComposer } from '@nitrots/nitro-renderer'; +import { SendMessageComposer } from '../SendMessageComposer'; + +export function GoToDesktop(): void +{ + SendMessageComposer(new DesktopViewComposer()); +} diff --git a/src/api/nitro/session/HasHabboClub.ts b/src/api/nitro/session/HasHabboClub.ts new file mode 100644 index 0000000..9cee03f --- /dev/null +++ b/src/api/nitro/session/HasHabboClub.ts @@ -0,0 +1,6 @@ +import { GetSessionDataManager, HabboClubLevelEnum } from '@nitrots/nitro-renderer'; + +export function HasHabboClub(): boolean +{ + return (GetSessionDataManager().clubLevel >= HabboClubLevelEnum.CLUB); +} diff --git a/src/api/nitro/session/HasHabboVip.ts b/src/api/nitro/session/HasHabboVip.ts new file mode 100644 index 0000000..f5a3e21 --- /dev/null +++ b/src/api/nitro/session/HasHabboVip.ts @@ -0,0 +1,6 @@ +import { GetSessionDataManager, HabboClubLevelEnum } from '@nitrots/nitro-renderer'; + +export function HasHabboVip(): boolean +{ + return (GetSessionDataManager().clubLevel >= HabboClubLevelEnum.VIP); +} diff --git a/src/api/nitro/session/IsOwnerOfFloorFurniture.ts b/src/api/nitro/session/IsOwnerOfFloorFurniture.ts new file mode 100644 index 0000000..5675db9 --- /dev/null +++ b/src/api/nitro/session/IsOwnerOfFloorFurniture.ts @@ -0,0 +1,14 @@ +import { GetRoomEngine, GetSessionDataManager, RoomObjectCategory, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { GetRoomSession } from './GetRoomSession'; + +export function IsOwnerOfFloorFurniture(id: number): boolean +{ + const roomObject = GetRoomEngine().getRoomObject(GetRoomSession().roomId, id, RoomObjectCategory.FLOOR); + + if(!roomObject || !roomObject.model) return false; + + const userId = GetSessionDataManager().userId; + const objectOwnerId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_OWNER_ID); + + return (userId === objectOwnerId); +} diff --git a/src/api/nitro/session/IsOwnerOfFurniture.ts b/src/api/nitro/session/IsOwnerOfFurniture.ts new file mode 100644 index 0000000..49ce166 --- /dev/null +++ b/src/api/nitro/session/IsOwnerOfFurniture.ts @@ -0,0 +1,11 @@ +import { GetSessionDataManager, IRoomObject, RoomObjectVariable } from '@nitrots/nitro-renderer'; + +export function IsOwnerOfFurniture(roomObject: IRoomObject): boolean +{ + if(!roomObject || !roomObject.model) return false; + + const userId = GetSessionDataManager().userId; + const objectOwnerId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_OWNER_ID); + + return (userId === objectOwnerId); +} diff --git a/src/api/nitro/session/IsRidingHorse.ts b/src/api/nitro/session/IsRidingHorse.ts new file mode 100644 index 0000000..9c70b5d --- /dev/null +++ b/src/api/nitro/session/IsRidingHorse.ts @@ -0,0 +1,14 @@ +import { RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { GetOwnRoomObject } from '../room'; + +export function IsRidingHorse(): boolean +{ + const roomObject = GetOwnRoomObject(); + + if(!roomObject) return false; + + const model = roomObject.model; + const effectId = model.getValue(RoomObjectVariable.FIGURE_EFFECT); + + return (effectId === 77); +} diff --git a/src/api/nitro/session/StartRoomSession.ts b/src/api/nitro/session/StartRoomSession.ts new file mode 100644 index 0000000..c203a77 --- /dev/null +++ b/src/api/nitro/session/StartRoomSession.ts @@ -0,0 +1,6 @@ +import { GetRoomSessionManager, IRoomSession } from '@nitrots/nitro-renderer'; + +export function StartRoomSession(session: IRoomSession): void +{ + GetRoomSessionManager().startSession(session); +} diff --git a/src/api/nitro/session/VisitDesktop.ts b/src/api/nitro/session/VisitDesktop.ts new file mode 100644 index 0000000..2309f01 --- /dev/null +++ b/src/api/nitro/session/VisitDesktop.ts @@ -0,0 +1,11 @@ +import { GetRoomSessionManager } from '@nitrots/nitro-renderer'; +import { GetRoomSession } from './GetRoomSession'; +import { GoToDesktop } from './GoToDesktop'; + +export const VisitDesktop = () => +{ + if(!GetRoomSession()) return; + + GoToDesktop(); + GetRoomSessionManager().removeSession(-1); +}; diff --git a/src/api/nitro/session/index.ts b/src/api/nitro/session/index.ts new file mode 100644 index 0000000..4c0491d --- /dev/null +++ b/src/api/nitro/session/index.ts @@ -0,0 +1,19 @@ +export * from './CanManipulateFurniture'; +export * from './CreateRoomSession'; +export * from './GetCanStandUp'; +export * from './GetCanUseExpression'; +export * from './GetClubMemberLevel'; +export * from './GetFurnitureData'; +export * from './GetFurnitureDataForProductOffer'; +export * from './GetFurnitureDataForRoomObject'; +export * from './GetOwnPosture'; +export * from './GetProductDataForLocalization'; +export * from './GetRoomSession'; +export * from './GoToDesktop'; +export * from './HasHabboClub'; +export * from './HasHabboVip'; +export * from './IsOwnerOfFloorFurniture'; +export * from './IsOwnerOfFurniture'; +export * from './IsRidingHorse'; +export * from './StartRoomSession'; +export * from './VisitDesktop'; diff --git a/src/api/notification/NotificationAlertItem.ts b/src/api/notification/NotificationAlertItem.ts new file mode 100644 index 0000000..2d7702c --- /dev/null +++ b/src/api/notification/NotificationAlertItem.ts @@ -0,0 +1,67 @@ +import { NotificationAlertType } from './NotificationAlertType'; + +export class NotificationAlertItem +{ + private static ITEM_ID: number = -1; + + private _id: number; + private _messages: string[]; + private _alertType: string; + private _clickUrl: string; + private _clickUrlText: string; + private _title: string; + private _imageUrl: string; + + constructor(messages: string[], alertType: string = NotificationAlertType.DEFAULT, clickUrl: string = null, clickUrlText: string = null, title: string = null, imageUrl: string = null) + { + NotificationAlertItem.ITEM_ID += 1; + + this._id = NotificationAlertItem.ITEM_ID; + this._messages = messages; + this._alertType = alertType; + this._clickUrl = clickUrl; + this._clickUrlText = clickUrlText; + this._title = title; + this._imageUrl = imageUrl; + } + + public get id(): number + { + return this._id; + } + + public get messages(): string[] + { + return this._messages; + } + + public set alertType(alertType: string) + { + this._alertType = alertType; + } + + public get alertType(): string + { + return this._alertType; + } + + public get clickUrl(): string + { + return this._clickUrl; + } + + public get clickUrlText(): string + { + return this._clickUrlText; + } + + public get title(): string + { + return this._title; + } + + public get imageUrl(): string + { + return this._imageUrl; + } +} diff --git a/src/api/notification/NotificationAlertType.ts b/src/api/notification/NotificationAlertType.ts new file mode 100644 index 0000000..ad804e8 --- /dev/null +++ b/src/api/notification/NotificationAlertType.ts @@ -0,0 +1,10 @@ +export class NotificationAlertType +{ + public static DEFAULT: string = 'default'; + public static MOTD: string = 'motd'; + public static MODERATION: string = 'moderation'; + public static EVENT: string = 'event'; + public static NITRO: string = 'nitro'; + public static SEARCH: string = 'search'; + public static ALERT: string = 'alert'; +} diff --git a/src/api/notification/NotificationBubbleItem.ts b/src/api/notification/NotificationBubbleItem.ts new file mode 100644 index 0000000..fe90dab --- /dev/null +++ b/src/api/notification/NotificationBubbleItem.ts @@ -0,0 +1,48 @@ +import { NotificationBubbleType } from './NotificationBubbleType'; + +export class NotificationBubbleItem +{ + private static ITEM_ID: number = -1; + + private _id: number; + private _message: string; + private _notificationType: string; + private _iconUrl: string; + private _linkUrl: string; + + constructor(message: string, notificationType: string = NotificationBubbleType.INFO, iconUrl: string = null, linkUrl: string = null) + { + NotificationBubbleItem.ITEM_ID += 1; + + this._id = NotificationBubbleItem.ITEM_ID; + this._message = message; + this._notificationType = notificationType; + this._iconUrl = iconUrl; + this._linkUrl = linkUrl; + } + + public get id(): number + { + return this._id; + } + + public get message(): string + { + return this._message; + } + + public get notificationType(): string + { + return this._notificationType; + } + + public get iconUrl(): string + { + return this._iconUrl; + } + + public get linkUrl(): string + { + return this._linkUrl; + } +} diff --git a/src/api/notification/NotificationBubbleType.ts b/src/api/notification/NotificationBubbleType.ts new file mode 100644 index 0000000..858573b --- /dev/null +++ b/src/api/notification/NotificationBubbleType.ts @@ -0,0 +1,19 @@ +export class NotificationBubbleType +{ + public static FRIENDOFFLINE: string = 'friendoffline'; + public static FRIENDONLINE: string = 'friendonline'; + public static THIRDPARTYFRIENDOFFLINE: string = 'thirdpartyfriendoffline'; + public static THIRDPARTYFRIENDONLINE: string = 'thirdpartyfriendonline'; + public static ACHIEVEMENT: string = 'achievement'; + public static BADGE_RECEIVED: string = 'badge_received'; + public static INFO: string = 'info'; + public static RECYCLEROK: string = 'recyclerok'; + public static RESPECT: string = 'respect'; + public static CLUB: string = 'club'; + public static SOUNDMACHINE: string = 'soundmachine'; + public static PETLEVEL: string = 'petlevel'; + public static CLUBGIFT: string = 'clubgift'; + public static BUYFURNI: string = 'buyfurni'; + public static VIP: string = 'vip'; + public static ROOMMESSAGESPOSTED: string = 'roommessagesposted'; +} diff --git a/src/api/notification/NotificationConfirmItem.ts b/src/api/notification/NotificationConfirmItem.ts new file mode 100644 index 0000000..0455662 --- /dev/null +++ b/src/api/notification/NotificationConfirmItem.ts @@ -0,0 +1,67 @@ +export class NotificationConfirmItem +{ + private static ITEM_ID: number = -1; + + private _id: number; + private _confirmType: string; + private _message: string; + private _onConfirm: Function; + private _onCancel: Function; + private _confirmText: string; + private _cancelText: string; + private _title: string; + + constructor(confirmType: string, message: string, onConfirm: Function, onCancel: Function, confirmText: string, cancelText: string, title: string) + { + NotificationConfirmItem.ITEM_ID += 1; + + this._id = NotificationConfirmItem.ITEM_ID; + this._confirmType = confirmType; + this._message = message; + this._onConfirm = onConfirm; + this._onCancel = onCancel; + this._confirmText = confirmText; + this._cancelText = cancelText; + this._title = title; + } + + public get id(): number + { + return this._id; + } + + public get confirmType(): string + { + return this._confirmType; + } + + public get message(): string + { + return this._message; + } + + public get onConfirm(): Function + { + return this._onConfirm; + } + + public get onCancel(): Function + { + return this._onCancel; + } + + public get confirmText(): string + { + return this._confirmText; + } + + public get cancelText(): string + { + return this._cancelText; + } + + public get title(): string + { + return this._title; + } +} diff --git a/src/api/notification/NotificationConfirmType.ts b/src/api/notification/NotificationConfirmType.ts new file mode 100644 index 0000000..533ca05 --- /dev/null +++ b/src/api/notification/NotificationConfirmType.ts @@ -0,0 +1,4 @@ +export class NotificationConfirmType +{ + public static DEFAULT: string = 'default'; +} diff --git a/src/api/notification/index.ts b/src/api/notification/index.ts new file mode 100644 index 0000000..23476d3 --- /dev/null +++ b/src/api/notification/index.ts @@ -0,0 +1,6 @@ +export * from './NotificationAlertItem'; +export * from './NotificationAlertType'; +export * from './NotificationBubbleItem'; +export * from './NotificationBubbleType'; +export * from './NotificationConfirmItem'; +export * from './NotificationConfirmType'; diff --git a/src/api/purse/IPurse.ts b/src/api/purse/IPurse.ts new file mode 100644 index 0000000..9fffb18 --- /dev/null +++ b/src/api/purse/IPurse.ts @@ -0,0 +1,15 @@ +export interface IPurse +{ + credits: number; + activityPoints: Map; + clubDays: number; + clubPeriods: number; + hasClubLeft: boolean; + isVip: boolean; + pastClubDays: number; + pastVipDays: number; + isExpiring: boolean; + minutesUntilExpiration: number; + minutesSinceLastModified: number; + clubLevel: number; +} diff --git a/src/api/purse/Purse.ts b/src/api/purse/Purse.ts new file mode 100644 index 0000000..6970e59 --- /dev/null +++ b/src/api/purse/Purse.ts @@ -0,0 +1,165 @@ +import { GetTickerTime, HabboClubLevelEnum } from '@nitrots/nitro-renderer'; +import { IPurse } from './IPurse'; + +export class Purse implements IPurse +{ + private _credits: number = 0; + private _activityPoints: Map = new Map(); + private _clubDays: number = 0; + private _clubPeriods: number = 0; + private _isVIP: boolean = false; + private _pastClubDays: number = 0; + private _pastVipDays: number = 0; + private _isExpiring: boolean = false; + private _minutesUntilExpiration: number = 0; + private _minutesSinceLastModified: number = 0; + private _lastUpdated: number = 0; + + public static from(purse: Purse): Purse + { + const newPurse = new Purse(); + + newPurse._credits = purse._credits; + newPurse._activityPoints = purse._activityPoints; + newPurse._clubDays = purse._clubDays; + newPurse._clubPeriods = purse._clubPeriods; + newPurse._isVIP = purse._isVIP; + newPurse._pastClubDays = purse._pastClubDays; + newPurse._pastVipDays = purse._pastVipDays; + newPurse._isExpiring = purse._isExpiring; + newPurse._minutesUntilExpiration = purse._minutesUntilExpiration; + newPurse._minutesSinceLastModified = purse._minutesSinceLastModified; + newPurse._lastUpdated = purse._lastUpdated; + + return newPurse; + } + + public get credits(): number + { + return this._credits; + } + + public set credits(credits: number) + { + this._lastUpdated = GetTickerTime(); + this._credits = credits; + } + + public get activityPoints(): Map + { + return this._activityPoints; + } + + public set activityPoints(k: Map) + { + this._lastUpdated = GetTickerTime(); + this._activityPoints = k; + } + + public get clubDays(): number + { + return this._clubDays; + } + + public set clubDays(k: number) + { + this._lastUpdated = GetTickerTime(); + this._clubDays = k; + } + + public get clubPeriods(): number + { + return this._clubPeriods; + } + + public set clubPeriods(k: number) + { + this._lastUpdated = GetTickerTime(); + this._clubPeriods = k; + } + + public get hasClubLeft(): boolean + { + return (this._clubDays > 0) || (this._clubPeriods > 0); + } + + public get isVip(): boolean + { + return this._isVIP; + } + + public set isVip(k: boolean) + { + this._isVIP = k; + } + + public get pastClubDays(): number + { + return this._pastClubDays; + } + + public set pastClubDays(k: number) + { + this._lastUpdated = GetTickerTime(); + this._pastClubDays = k; + } + + public get pastVipDays(): number + { + return this._pastVipDays; + } + + public set pastVipDays(k: number) + { + this._lastUpdated = GetTickerTime(); + this._pastVipDays = k; + } + + public get isExpiring(): boolean + { + return this._isExpiring; + } + + public set isExpiring(k: boolean) + { + this._isExpiring = k; + } + + public get minutesUntilExpiration(): number + { + var k: number = ((GetTickerTime() - this._lastUpdated) / (1000 * 60)); + var _local_2: number = (this._minutesUntilExpiration - k); + return (_local_2 > 0) ? _local_2 : 0; + } + + public set minutesUntilExpiration(k: number) + { + this._lastUpdated = GetTickerTime(); + this._minutesUntilExpiration = k; + } + + public get minutesSinceLastModified(): number + { + return this._minutesSinceLastModified; + } + + public set minutesSinceLastModified(k: number) + { + this._lastUpdated = GetTickerTime(); + this._minutesSinceLastModified = k; + } + + public get lastUpdated(): number + { + return this._lastUpdated; + } + + public get clubLevel(): number + { + if(((this.clubDays === 0) && (this.clubPeriods === 0))) return HabboClubLevelEnum.NO_CLUB; + + if(this.isVip) return HabboClubLevelEnum.VIP; + + return HabboClubLevelEnum.CLUB; + } +} diff --git a/src/api/purse/index.ts b/src/api/purse/index.ts new file mode 100644 index 0000000..ed34480 --- /dev/null +++ b/src/api/purse/index.ts @@ -0,0 +1,2 @@ +export * from './IPurse'; +export * from './Purse'; diff --git a/src/api/room/events/RoomWidgetPollUpdateEvent.ts b/src/api/room/events/RoomWidgetPollUpdateEvent.ts new file mode 100644 index 0000000..edfb8fd --- /dev/null +++ b/src/api/room/events/RoomWidgetPollUpdateEvent.ts @@ -0,0 +1,110 @@ +import { IPollQuestion } from '@nitrots/nitro-renderer'; +import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; + +export class RoomWidgetPollUpdateEvent extends RoomWidgetUpdateEvent +{ + public static readonly OFFER = 'RWPUW_OFFER'; + public static readonly ERROR = 'RWPUW_ERROR'; + public static readonly CONTENT = 'RWPUW_CONTENT'; + + private _id = -1; + private _summary: string; + private _headline: string; + private _numQuestions = 0; + private _startMessage = ''; + private _endMessage = ''; + private _questionArray: IPollQuestion[] = null; + private _pollType = ''; + private _npsPoll = false; + + constructor(type: string, id: number) + { + super(type); + this._id = id; + } + + public get id(): number + { + return this._id; + } + + public get summary(): string + { + return this._summary; + } + + public set summary(k: string) + { + this._summary = k; + } + + public get headline(): string + { + return this._headline; + } + + public set headline(k: string) + { + this._headline = k; + } + + public get numQuestions(): number + { + return this._numQuestions; + } + + public set numQuestions(k: number) + { + this._numQuestions = k; + } + + public get startMessage(): string + { + return this._startMessage; + } + + public set startMessage(k: string) + { + this._startMessage = k; + } + + public get endMessage(): string + { + return this._endMessage; + } + + public set endMessage(k: string) + { + this._endMessage = k; + } + + public get questionArray(): IPollQuestion[] + { + return this._questionArray; + } + + public set questionArray(k: IPollQuestion[]) + { + this._questionArray = k; + } + + public get pollType(): string + { + return this._pollType; + } + + public set pollType(k: string) + { + this._pollType = k; + } + + public get npsPoll(): boolean + { + return this._npsPoll; + } + + public set npsPoll(k: boolean) + { + this._npsPoll = k; + } +} diff --git a/src/api/room/events/RoomWidgetUpdateBackgroundColorPreviewEvent.ts b/src/api/room/events/RoomWidgetUpdateBackgroundColorPreviewEvent.ts new file mode 100644 index 0000000..30135a3 --- /dev/null +++ b/src/api/room/events/RoomWidgetUpdateBackgroundColorPreviewEvent.ts @@ -0,0 +1,35 @@ +import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; + +export class RoomWidgetUpdateBackgroundColorPreviewEvent extends RoomWidgetUpdateEvent +{ + public static PREVIEW = 'RWUBCPE_PREVIEW'; + public static CLEAR_PREVIEW = 'RWUBCPE_CLEAR_PREVIEW'; + + private _hue: number; + private _saturation: number; + private _lightness: number; + + constructor(type: string, hue: number = 0, saturation: number = 0, lightness: number = 0) + { + super(type); + + this._hue = hue; + this._saturation = saturation; + this._lightness = lightness; + } + + public get hue(): number + { + return this._hue; + } + + public get saturation(): number + { + return this._saturation; + } + + public get lightness(): number + { + return this._lightness; + } +} diff --git a/src/api/room/events/RoomWidgetUpdateChatInputContentEvent.ts b/src/api/room/events/RoomWidgetUpdateChatInputContentEvent.ts new file mode 100644 index 0000000..9352372 --- /dev/null +++ b/src/api/room/events/RoomWidgetUpdateChatInputContentEvent.ts @@ -0,0 +1,29 @@ +import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; + +export class RoomWidgetUpdateChatInputContentEvent extends RoomWidgetUpdateEvent +{ + public static CHAT_INPUT_CONTENT: string = 'RWUCICE_CHAT_INPUT_CONTENT'; + public static WHISPER: string = 'whisper'; + public static SHOUT: string = 'shout'; + + private _chatMode: string = ''; + private _userName: string = ''; + + constructor(chatMode: string, userName: string) + { + super(RoomWidgetUpdateChatInputContentEvent.CHAT_INPUT_CONTENT); + + this._chatMode = chatMode; + this._userName = userName; + } + + public get chatMode(): string + { + return this._chatMode; + } + + public get userName(): string + { + return this._userName; + } +} diff --git a/src/api/room/events/RoomWidgetUpdateEvent.ts b/src/api/room/events/RoomWidgetUpdateEvent.ts new file mode 100644 index 0000000..0ac8ff8 --- /dev/null +++ b/src/api/room/events/RoomWidgetUpdateEvent.ts @@ -0,0 +1,4 @@ +import { NitroEvent } from '@nitrots/nitro-renderer'; + +export class RoomWidgetUpdateEvent extends NitroEvent +{} diff --git a/src/api/room/events/RoomWidgetUpdateRentableBotChatEvent.ts b/src/api/room/events/RoomWidgetUpdateRentableBotChatEvent.ts new file mode 100644 index 0000000..6191e1b --- /dev/null +++ b/src/api/room/events/RoomWidgetUpdateRentableBotChatEvent.ts @@ -0,0 +1,62 @@ +import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; + +export class RoomWidgetUpdateRentableBotChatEvent extends RoomWidgetUpdateEvent +{ + public static UPDATE_CHAT: string = 'RWURBCE_UPDATE_CHAT'; + + private _objectId: number; + private _category: number; + private _botId: number; + private _chat: string; + private _automaticChat: boolean; + private _chatDelay: number; + private _mixSentences: boolean; + + constructor(objectId: number, category: number, botId: number, chat: string, automaticChat: boolean, chatDelay: number, mixSentences: boolean) + { + super(RoomWidgetUpdateRentableBotChatEvent.UPDATE_CHAT); + + this._objectId = objectId; + this._category = category; + this._botId = botId; + this._chat = chat; + this._automaticChat = automaticChat; + this._chatDelay = chatDelay; + this._mixSentences = mixSentences; + } + + public get objectId(): number + { + return this._objectId; + } + + public get category(): number + { + return this._category; + } + + public get botId(): number + { + return this._botId; + } + + public get chat(): string + { + return this._chat; + } + + public get automaticChat(): boolean + { + return this._automaticChat; + } + + public get chatDelay(): number + { + return this._chatDelay; + } + + public get mixSentences(): boolean + { + return this._mixSentences; + } +} diff --git a/src/api/room/events/RoomWidgetUpdateRoomObjectEvent.ts b/src/api/room/events/RoomWidgetUpdateRoomObjectEvent.ts new file mode 100644 index 0000000..0660276 --- /dev/null +++ b/src/api/room/events/RoomWidgetUpdateRoomObjectEvent.ts @@ -0,0 +1,43 @@ +import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent'; + +export class RoomWidgetUpdateRoomObjectEvent extends RoomWidgetUpdateEvent +{ + public static OBJECT_SELECTED: string = 'RWUROE_OBJECT_SELECTED'; + public static OBJECT_DESELECTED: string = 'RWUROE_OBJECT_DESELECTED'; + public static USER_REMOVED: string = 'RWUROE_USER_REMOVED'; + public static FURNI_REMOVED: string = 'RWUROE_FURNI_REMOVED'; + public static FURNI_ADDED: string = 'RWUROE_FURNI_ADDED'; + public static USER_ADDED: string = 'RWUROE_USER_ADDED'; + public static OBJECT_ROLL_OVER: string = 'RWUROE_OBJECT_ROLL_OVER'; + public static OBJECT_ROLL_OUT: string = 'RWUROE_OBJECT_ROLL_OUT'; + public static OBJECT_REQUEST_MANIPULATION: string = 'RWUROE_OBJECT_REQUEST_MANIPULATION'; + public static OBJECT_DOUBLE_CLICKED: string = 'RWUROE_OBJECT_DOUBLE_CLICKED'; + + private _id: number; + private _category: number; + private _roomId: number; + + constructor(type: string, id: number, category: number, roomId: number) + { + super(type); + + this._id = id; + this._category = category; + this._roomId = roomId; + } + + public get id(): number + { + return this._id; + } + + public get category(): number + { + return this._category; + } + + public get roomId(): number + { + return this._roomId; + } +} diff --git a/src/api/room/events/index.ts b/src/api/room/events/index.ts new file mode 100644 index 0000000..e5ed0d8 --- /dev/null +++ b/src/api/room/events/index.ts @@ -0,0 +1,6 @@ +export * from './RoomWidgetPollUpdateEvent'; +export * from './RoomWidgetUpdateBackgroundColorPreviewEvent'; +export * from './RoomWidgetUpdateChatInputContentEvent'; +export * from './RoomWidgetUpdateEvent'; +export * from './RoomWidgetUpdateRentableBotChatEvent'; +export * from './RoomWidgetUpdateRoomObjectEvent'; diff --git a/src/api/room/index.ts b/src/api/room/index.ts new file mode 100644 index 0000000..56aea79 --- /dev/null +++ b/src/api/room/index.ts @@ -0,0 +1,2 @@ +export * from './events'; +export * from './widgets'; diff --git a/src/api/room/widgets/AvatarInfoFurni.ts b/src/api/room/widgets/AvatarInfoFurni.ts new file mode 100644 index 0000000..47743e9 --- /dev/null +++ b/src/api/room/widgets/AvatarInfoFurni.ts @@ -0,0 +1,37 @@ +import { IObjectData } from '@nitrots/nitro-renderer'; +import { IAvatarInfo } from './IAvatarInfo'; + +export class AvatarInfoFurni implements IAvatarInfo +{ + public static FURNI: string = 'IFI_FURNI'; + + public id: number = 0; + public category: number = 0; + public name: string = ''; + public description: string = ''; + public isWallItem: boolean = false; + public isStickie: boolean = false; + public isRoomOwner: boolean = false; + public roomControllerLevel: number = 0; + public isAnyRoomController: boolean = false; + public expiration: number = -1; + public purchaseCatalogPageId: number = -1; + public purchaseOfferId: number = -1; + public extraParam: string = ''; + public isOwner: boolean = false; + public stuffData: IObjectData = null; + public groupId: number = 0; + public ownerId: number = 0; + public ownerName: string = ''; + public usagePolicy: number = 0; + public rentCatalogPageId: number = -1; + public rentOfferId: number = -1; + public purchaseCouldBeUsedForBuyout: boolean = false; + public rentCouldBeUsedForBuyout: boolean = false; + public availableForBuildersClub: boolean = false; + public tileSizeX: number = 1; + public tileSizeY: number = 1; + + constructor(public readonly type: string) + {} +} diff --git a/src/api/room/widgets/AvatarInfoName.ts b/src/api/room/widgets/AvatarInfoName.ts new file mode 100644 index 0000000..66a6a7e --- /dev/null +++ b/src/api/room/widgets/AvatarInfoName.ts @@ -0,0 +1,11 @@ +export class AvatarInfoName +{ + constructor( + public readonly roomIndex: number, + public readonly category: number, + public readonly id: number, + public readonly name: string, + public readonly userType: number, + public readonly isFriend: boolean = false) + {} +} diff --git a/src/api/room/widgets/AvatarInfoPet.ts b/src/api/room/widgets/AvatarInfoPet.ts new file mode 100644 index 0000000..0c0435a --- /dev/null +++ b/src/api/room/widgets/AvatarInfoPet.ts @@ -0,0 +1,46 @@ +import { IAvatarInfo } from './IAvatarInfo'; + +export class AvatarInfoPet implements IAvatarInfo +{ + public static PET_INFO: string = 'IPI_PET_INFO'; + + public level: number = 0; + public maximumLevel: number = 0; + public experience: number = 0; + public levelExperienceGoal: number = 0; + public energy: number = 0; + public maximumEnergy: number = 0; + public happyness: number = 0; + public maximumHappyness: number = 0; + public respectsPetLeft: number = 0; + public respect: number = 0; + public age: number = 0; + public name: string = ''; + public id: number = -1; + public image: HTMLImageElement = null; + public petType: number = 0; + public petBreed: number = 0; + public petFigure: string = ''; + public posture: string = 'std'; + public isOwner: boolean = false; + public ownerId: number = -1; + public ownerName: string = ''; + public canRemovePet: boolean = false; + public roomIndex: number = 0; + public unknownRarityLevel: number = 0; + public saddle: boolean = false; + public rider: boolean = false; + public breedable: boolean = false; + public skillTresholds: number[] = []; + public publiclyRideable: number = 0; + public fullyGrown: boolean = false; + public dead: boolean = false; + public rarityLevel: number = 0; + public maximumTimeToLive: number = 0; + public remainingTimeToLive: number = 0; + public remainingGrowTime: number = 0; + public publiclyBreedable: boolean = false; + + constructor(public readonly type: string) + {} +} diff --git a/src/api/room/widgets/AvatarInfoRentableBot.ts b/src/api/room/widgets/AvatarInfoRentableBot.ts new file mode 100644 index 0000000..77fb10c --- /dev/null +++ b/src/api/room/widgets/AvatarInfoRentableBot.ts @@ -0,0 +1,23 @@ +import { IAvatarInfo } from './IAvatarInfo'; + +export class AvatarInfoRentableBot implements IAvatarInfo +{ + public static RENTABLE_BOT: string = 'IRBI_RENTABLE_BOT'; + + public name: string = ''; + public motto: string = ''; + public webID: number = 0; + public figure: string = ''; + public badges: string[] = []; + public carryItem: number = 0; + public roomIndex: number = 0; + public amIOwner: boolean = false; + public amIAnyRoomController: boolean = false; + public roomControllerLevel: number = 0; + public ownerId: number = -1; + public ownerName: string = ''; + public botSkills: number[] = []; + + constructor(public readonly type: string) + {} +} diff --git a/src/api/room/widgets/AvatarInfoUser.ts b/src/api/room/widgets/AvatarInfoUser.ts new file mode 100644 index 0000000..fa3fc1a --- /dev/null +++ b/src/api/room/widgets/AvatarInfoUser.ts @@ -0,0 +1,52 @@ +import { IAvatarInfo } from './IAvatarInfo'; + +export class AvatarInfoUser implements IAvatarInfo +{ + public static OWN_USER: string = 'IUI_OWN_USER'; + public static PEER: string = 'IUI_PEER'; + public static BOT: string = 'IUI_BOT'; + public static TRADE_REASON_OK: number = 0; + public static TRADE_REASON_SHUTDOWN: number = 2; + public static TRADE_REASON_NO_TRADING: number = 3; + public static DEFAULT_BOT_BADGE_ID: string = 'BOT'; + + public name: string = ''; + public motto: string = ''; + public achievementScore: number = 0; + public backgroundId: number = 0; + public standId: number = 0; + public overlayId: number = 0; + public webID: number = 0; + public xp: number = 0; + public userType: number = -1; + public figure: string = ''; + public badges: string[] = []; + public groupId: number = 0; + public groupName: string = ''; + public groupBadgeId: string = ''; + public carryItem: number = 0; + public roomIndex: number = 0; + public isSpectatorMode: boolean = false; + public allowNameChange: boolean = false; + public amIOwner: boolean = false; + public amIAnyRoomController: boolean = false; + public roomControllerLevel: number = 0; + public canBeKicked: boolean = false; + public canBeBanned: boolean = false; + public canBeMuted: boolean = false; + public respectLeft: number = 0; + public isIgnored: boolean = false; + public isGuildRoom: boolean = false; + public canTrade: boolean = false; + public canTradeReason: number = 0; + public targetRoomControllerLevel: number = 0; + public isAmbassador: boolean = false; + + constructor(public readonly type: string) + {} + + public get isOwnUser(): boolean + { + return (this.type === AvatarInfoUser.OWN_USER); + } +} diff --git a/src/api/room/widgets/AvatarInfoUtilities.ts b/src/api/room/widgets/AvatarInfoUtilities.ts new file mode 100644 index 0000000..b8154ee --- /dev/null +++ b/src/api/room/widgets/AvatarInfoUtilities.ts @@ -0,0 +1,442 @@ +import { GetRoomEngine, GetSessionDataManager, GetTickerTime, IFurnitureData, IRoomModerationSettings, IRoomPetData, IRoomUserData, ObjectDataFactory, PetFigureData, PetType, RoomControllerLevel, RoomModerationSettings, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomTradingLevelEnum, RoomWidgetEnumItemExtradataParameter } from '@nitrots/nitro-renderer'; +import { GetRoomSession, IsOwnerOfFurniture } from '../../nitro'; +import { LocalizeText } from '../../utils'; +import { AvatarInfoFurni } from './AvatarInfoFurni'; +import { AvatarInfoName } from './AvatarInfoName'; +import { AvatarInfoPet } from './AvatarInfoPet'; +import { AvatarInfoRentableBot } from './AvatarInfoRentableBot'; +import { AvatarInfoUser } from './AvatarInfoUser'; + +export class AvatarInfoUtilities +{ + public static getObjectName(objectId: number, category: number): AvatarInfoName + { + const roomSession = GetRoomSession(); + + let id = -1; + let name: string = null; + let userType = 0; + + switch(category) + { + case RoomObjectCategory.FLOOR: + case RoomObjectCategory.WALL: { + const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, objectId, category); + + if(!roomObject) break; + + if(roomObject.type.indexOf('poster') === 0) + { + name = LocalizeText('${poster_' + parseInt(roomObject.type.replace('poster', '')) + '_name}'); + } + else + { + let furniData: IFurnitureData = null; + + const typeId = roomObject.model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID); + + if(category === RoomObjectCategory.FLOOR) + { + furniData = GetSessionDataManager().getFloorItemData(typeId); + } + + else if(category === RoomObjectCategory.WALL) + { + furniData = GetSessionDataManager().getWallItemData(typeId); + } + + if(!furniData) break; + + id = furniData.id; + name = furniData.name; + } + break; + } + case RoomObjectCategory.UNIT: { + const userData = roomSession.userDataManager.getUserDataByIndex(objectId); + + if(!userData) break; + + id = userData.webID; + name = userData.name; + userType = userData.type; + break; + } + } + + if(!name || !name.length) return null; + + return new AvatarInfoName(objectId, category, id, name, userType); + } + + public static getFurniInfo(objectId: number, category: number): AvatarInfoFurni + { + const roomSession = GetRoomSession(); + const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, objectId, category); + + if(!roomObject) return null; + + const furniInfo = new AvatarInfoFurni(AvatarInfoFurni.FURNI); + + furniInfo.id = objectId; + furniInfo.category = category; + + const model = roomObject.model; + + if(model.getValue(RoomWidgetEnumItemExtradataParameter.INFOSTAND_EXTRA_PARAM)) furniInfo.extraParam = model.getValue(RoomWidgetEnumItemExtradataParameter.INFOSTAND_EXTRA_PARAM); + + const objectData = ObjectDataFactory.getData(model.getValue(RoomObjectVariable.FURNITURE_DATA_FORMAT)); + + objectData.initializeFromRoomObjectModel(model); + + furniInfo.stuffData = objectData; + + const objectType = roomObject.type; + + if(objectType.indexOf('poster') === 0) + { + const posterId = parseInt(objectType.replace('poster', '')); + + furniInfo.name = LocalizeText(('${poster_' + posterId) + '_name}'); + furniInfo.description = LocalizeText(('${poster_' + posterId) + '_desc}'); + } + else + { + const typeId = model.getValue(RoomObjectVariable.FURNITURE_TYPE_ID); + + let furnitureData: IFurnitureData = null; + + if(category === RoomObjectCategory.FLOOR) + { + furnitureData = GetSessionDataManager().getFloorItemData(typeId); + } + + else if(category === RoomObjectCategory.WALL) + { + furnitureData = GetSessionDataManager().getWallItemData(typeId); + } + + if(furnitureData) + { + furniInfo.name = furnitureData.name; + furniInfo.description = furnitureData.description; + furniInfo.purchaseOfferId = furnitureData.purchaseOfferId; + furniInfo.purchaseCouldBeUsedForBuyout = furnitureData.purchaseCouldBeUsedForBuyout; + furniInfo.rentOfferId = furnitureData.rentOfferId; + furniInfo.rentCouldBeUsedForBuyout = furnitureData.rentCouldBeUsedForBuyout; + furniInfo.availableForBuildersClub = furnitureData.availableForBuildersClub; + furniInfo.tileSizeX = furnitureData.tileSizeX; + furniInfo.tileSizeY = furnitureData.tileSizeY; + } + } + + if(objectType.indexOf('post_it') > -1) furniInfo.isStickie = true; + + const expiryTime = model.getValue(RoomObjectVariable.FURNITURE_EXPIRY_TIME); + const expiryTimestamp = model.getValue(RoomObjectVariable.FURNITURE_EXPIRTY_TIMESTAMP); + + furniInfo.expiration = ((expiryTime < 0) ? expiryTime : Math.max(0, (expiryTime - ((GetTickerTime() - expiryTimestamp) / 1000)))); + + /* let roomObjectImage = GetRoomEngine().getRoomObjectImage(roomSession.roomId, objectId, category, new Vector3d(180), 64, null); + + if(!roomObjectImage.data || (roomObjectImage.data.width > 140) || (roomObjectImage.data.height > 200)) + { + roomObjectImage = GetRoomEngine().getRoomObjectImage(roomSession.roomId, objectId, category, new Vector3d(180), 1, null); + } + + furniInfo.image = roomObjectImage.getImage(); */ + furniInfo.isWallItem = (category === RoomObjectCategory.WALL); + furniInfo.isRoomOwner = roomSession.isRoomOwner; + furniInfo.roomControllerLevel = roomSession.controllerLevel; + furniInfo.isAnyRoomController = GetSessionDataManager().isModerator; + furniInfo.ownerId = model.getValue(RoomObjectVariable.FURNITURE_OWNER_ID); + furniInfo.ownerName = model.getValue(RoomObjectVariable.FURNITURE_OWNER_NAME); + furniInfo.usagePolicy = model.getValue(RoomObjectVariable.FURNITURE_USAGE_POLICY); + + const guildId = model.getValue(RoomObjectVariable.FURNITURE_GUILD_CUSTOMIZED_GUILD_ID); + + if(guildId !== 0) furniInfo.groupId = guildId; + + if(IsOwnerOfFurniture(roomObject)) furniInfo.isOwner = true; + + return furniInfo; + } + + public static getUserInfo(category: number, userData: IRoomUserData): AvatarInfoUser + { + const roomSession = GetRoomSession(); + + const userInfo = new AvatarInfoUser((userData.webID === GetSessionDataManager().userId) ? AvatarInfoUser.OWN_USER : AvatarInfoUser.PEER); + + userInfo.isSpectatorMode = roomSession.isSpectator; + userInfo.name = userData.name; + userInfo.motto = userData.custom; + userInfo.backgroundId = userData.background; + userInfo.standId = userData.stand; + userInfo.overlayId = userData.overlay; + userInfo.achievementScore = userData.activityPoints; + userInfo.webID = userData.webID; + userInfo.roomIndex = userData.roomIndex; + userInfo.userType = RoomObjectType.USER; + + const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, userData.roomIndex, category); + + if(roomObject) userInfo.carryItem = (roomObject.model.getValue(RoomObjectVariable.FIGURE_CARRY_OBJECT) || 0); + + if(userInfo.type === AvatarInfoUser.OWN_USER) userInfo.allowNameChange = GetSessionDataManager().canChangeName; + + userInfo.amIOwner = roomSession.isRoomOwner; + userInfo.isGuildRoom = roomSession.isGuildRoom; + userInfo.roomControllerLevel = roomSession.controllerLevel; + userInfo.amIAnyRoomController = GetSessionDataManager().isModerator; + userInfo.isAmbassador = GetSessionDataManager().isAmbassador; + + if(userInfo.type === AvatarInfoUser.PEER) + { + if(roomObject) + { + userInfo.targetRoomControllerLevel = roomObject.model.getValue(RoomObjectVariable.FIGURE_FLAT_CONTROL); + userInfo.canBeMuted = this.canBeMuted(userInfo); + userInfo.canBeKicked = this.canBeKicked(userInfo); + userInfo.canBeBanned = this.canBeBanned(userInfo); + } + + userInfo.isIgnored = GetSessionDataManager().isUserIgnored(userData.name); + userInfo.respectLeft = GetSessionDataManager().respectsLeft; + + const isShuttingDown = GetSessionDataManager().isSystemShutdown; + const tradeMode = roomSession.tradeMode; + + if(isShuttingDown) + { + userInfo.canTrade = false; + } + else + { + switch(tradeMode) + { + case RoomTradingLevelEnum.ROOM_CONTROLLER_REQUIRED: { + const roomController = ((userInfo.roomControllerLevel !== RoomControllerLevel.NONE) && (userInfo.roomControllerLevel !== RoomControllerLevel.GUILD_MEMBER)); + const targetController = ((userInfo.targetRoomControllerLevel !== RoomControllerLevel.NONE) && (userInfo.targetRoomControllerLevel !== RoomControllerLevel.GUILD_MEMBER)); + + userInfo.canTrade = (roomController || targetController); + break; + } + case RoomTradingLevelEnum.FREE_TRADING: + userInfo.canTrade = true; + break; + default: + userInfo.canTrade = false; + break; + } + } + + userInfo.canTradeReason = AvatarInfoUser.TRADE_REASON_OK; + + if(isShuttingDown) userInfo.canTradeReason = AvatarInfoUser.TRADE_REASON_SHUTDOWN; + + if(tradeMode !== RoomTradingLevelEnum.FREE_TRADING) userInfo.canTradeReason = AvatarInfoUser.TRADE_REASON_NO_TRADING; + + // const _local_12 = GetSessionDataManager().userId; + // _local_13 = GetSessionDataManager().getUserTags(_local_12); + // this._Str_16287(_local_12, _local_13); + } + + userInfo.groupId = userData.groupId; + userInfo.groupBadgeId = GetSessionDataManager().getGroupBadge(userInfo.groupId); + userInfo.groupName = userData.groupName; + userInfo.badges = roomSession.userDataManager.getUserBadges(userData.webID); + userInfo.figure = userData.figure; + //var _local_8:Array = GetSessionDataManager().getUserTags(userData.webID); + //this._Str_16287(userData.webId, _local_8); + //this._container.habboGroupsManager.updateVisibleExtendedProfile(userData.webID); + //this._container.connection.send(new GetRelationshipStatusInfoMessageComposer(userData.webId)); + + return userInfo; + } + + public static getBotInfo(category: number, userData: IRoomUserData): AvatarInfoUser + { + const roomSession = GetRoomSession(); + const userInfo = new AvatarInfoUser(AvatarInfoUser.BOT); + + userInfo.name = userData.name; + userInfo.motto = userData.custom; + userInfo.webID = userData.webID; + userInfo.roomIndex = userData.roomIndex; + userInfo.userType = userData.type; + + const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, userData.roomIndex, category); + + if(roomObject) userInfo.carryItem = (roomObject.model.getValue(RoomObjectVariable.FIGURE_CARRY_OBJECT) || 0); + + userInfo.amIOwner = roomSession.isRoomOwner; + userInfo.isGuildRoom = roomSession.isGuildRoom; + userInfo.roomControllerLevel = roomSession.controllerLevel; + userInfo.amIAnyRoomController = GetSessionDataManager().isModerator; + userInfo.isAmbassador = GetSessionDataManager().isAmbassador; + userInfo.badges = [ AvatarInfoUser.DEFAULT_BOT_BADGE_ID ]; + userInfo.figure = userData.figure; + + return userInfo; + } + + public static getRentableBotInfo(category: number, userData: IRoomUserData): AvatarInfoRentableBot + { + const roomSession = GetRoomSession(); + const botInfo = new AvatarInfoRentableBot(AvatarInfoRentableBot.RENTABLE_BOT); + + botInfo.name = userData.name; + botInfo.motto = userData.custom; + botInfo.webID = userData.webID; + botInfo.roomIndex = userData.roomIndex; + botInfo.ownerId = userData.ownerId; + botInfo.ownerName = userData.ownerName; + botInfo.botSkills = userData.botSkills; + + const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, userData.roomIndex, category); + + if(roomObject) botInfo.carryItem = (roomObject.model.getValue(RoomObjectVariable.FIGURE_CARRY_OBJECT) || 0); + + botInfo.amIOwner = roomSession.isRoomOwner; + botInfo.roomControllerLevel = roomSession.controllerLevel; + botInfo.amIAnyRoomController = GetSessionDataManager().isModerator; + botInfo.badges = [ AvatarInfoUser.DEFAULT_BOT_BADGE_ID ]; + botInfo.figure = userData.figure; + + return botInfo; + } + + public static getPetInfo(petData: IRoomPetData): AvatarInfoPet + { + const roomSession = GetRoomSession(); + const userData = roomSession.userDataManager.getPetData(petData.id); + + if(!userData) return; + + const figure = new PetFigureData(userData.figure); + + let posture: string = null; + + if(figure.typeId === PetType.MONSTERPLANT) + { + if(petData.level >= petData.adultLevel) posture = 'std'; + else posture = ('grw' + petData.level); + } + + const isOwner = (petData.ownerId === GetSessionDataManager().userId); + const petInfo = new AvatarInfoPet(AvatarInfoPet.PET_INFO); + + petInfo.name = userData.name; + petInfo.id = petData.id; + petInfo.ownerId = petData.ownerId; + petInfo.ownerName = petData.ownerName; + petInfo.rarityLevel = petData.rarityLevel; + petInfo.petType = figure.typeId; + petInfo.petBreed = figure.paletteId; + petInfo.petFigure = userData.figure; + petInfo.posture = posture; + petInfo.isOwner = isOwner; + petInfo.roomIndex = userData.roomIndex; + petInfo.level = petData.level; + petInfo.maximumLevel = petData.maximumLevel; + petInfo.experience = petData.experience; + petInfo.levelExperienceGoal = petData.levelExperienceGoal; + petInfo.energy = petData.energy; + petInfo.maximumEnergy = petData.maximumEnergy; + petInfo.happyness = petData.happyness; + petInfo.maximumHappyness = petData.maximumHappyness; + petInfo.respect = petData.respect; + petInfo.respectsPetLeft = GetSessionDataManager().respectsPetLeft; + petInfo.age = petData.age; + petInfo.saddle = petData.saddle; + petInfo.rider = petData.rider; + petInfo.breedable = petData.breedable; + petInfo.fullyGrown = petData.fullyGrown; + petInfo.dead = petData.dead; + petInfo.rarityLevel = petData.rarityLevel; + petInfo.skillTresholds = petData.skillTresholds; + petInfo.canRemovePet = false; + petInfo.publiclyRideable = petData.publiclyRideable; + petInfo.maximumTimeToLive = petData.maximumTimeToLive; + petInfo.remainingTimeToLive = petData.remainingTimeToLive; + petInfo.remainingGrowTime = petData.remainingGrowTime; + petInfo.publiclyBreedable = petData.publiclyBreedable; + + if(isOwner || roomSession.isRoomOwner || GetSessionDataManager().isModerator || (roomSession.controllerLevel >= RoomControllerLevel.GUEST)) petInfo.canRemovePet = true; + + return petInfo; + } + + private static checkGuildSetting(userInfo: AvatarInfoUser): boolean + { + if(userInfo.isGuildRoom) return (userInfo.roomControllerLevel >= RoomControllerLevel.GUILD_ADMIN); + + return (userInfo.roomControllerLevel >= RoomControllerLevel.GUEST); + } + + private static isValidSetting(userInfo: AvatarInfoUser, checkSetting: (userInfo: AvatarInfoUser, moderation: IRoomModerationSettings) => boolean): boolean + { + const roomSession = GetRoomSession(); + + if(!roomSession.isPrivateRoom) return false; + + const moderation = roomSession.moderationSettings; + + let flag = false; + + if(moderation) flag = checkSetting(userInfo, moderation); + + return (flag && (userInfo.targetRoomControllerLevel < RoomControllerLevel.ROOM_OWNER)); + } + + private static canBeMuted(userInfo: AvatarInfoUser): boolean + { + const checkSetting = (userInfo: AvatarInfoUser, moderation: IRoomModerationSettings) => + { + switch(moderation.allowMute) + { + case RoomModerationSettings.MODERATION_LEVEL_USER_WITH_RIGHTS: + return this.checkGuildSetting(userInfo); + default: + return (userInfo.roomControllerLevel >= RoomControllerLevel.ROOM_OWNER); + } + }; + + return this.isValidSetting(userInfo, checkSetting); + } + + private static canBeKicked(userInfo: AvatarInfoUser): boolean + { + const checkSetting = (userInfo: AvatarInfoUser, moderation: IRoomModerationSettings) => + { + switch(moderation.allowKick) + { + case RoomModerationSettings.MODERATION_LEVEL_ALL: + return true; + case RoomModerationSettings.MODERATION_LEVEL_USER_WITH_RIGHTS: + return this.checkGuildSetting(userInfo); + default: + return (userInfo.roomControllerLevel >= RoomControllerLevel.ROOM_OWNER); + } + }; + + return this.isValidSetting(userInfo, checkSetting); + } + + private static canBeBanned(userInfo: AvatarInfoUser): boolean + { + const checkSetting = (userInfo: AvatarInfoUser, moderation: IRoomModerationSettings) => + { + switch(moderation.allowBan) + { + case RoomModerationSettings.MODERATION_LEVEL_USER_WITH_RIGHTS: + return this.checkGuildSetting(userInfo); + default: + return (userInfo.roomControllerLevel >= RoomControllerLevel.ROOM_OWNER); + } + }; + + return this.isValidSetting(userInfo, checkSetting); + } +} diff --git a/src/api/room/widgets/BotSkillsEnum.ts b/src/api/room/widgets/BotSkillsEnum.ts new file mode 100644 index 0000000..b879cdc --- /dev/null +++ b/src/api/room/widgets/BotSkillsEnum.ts @@ -0,0 +1,18 @@ +export class BotSkillsEnum +{ + public static GENERIC_SKILL: number = 0; + public static DRESS_UP: number = 1; + public static SETUP_CHAT: number = 2; + public static RANDOM_WALK: number = 3; + public static DANCE: number = 4; + public static CHANGE_BOT_NAME: number = 5; + public static SERVE_BEVERAGE: number = 6; + public static INCLIENT_LINK: number = 7; + public static NUX_PROCEED: number = 8; + public static CHANGE_BOT_MOTTO: number = 9; + public static NUX_TAKE_TOUR: number = 10; + public static NO_PICK_UP: number = 12; + public static NAVIGATOR_SEARCH: number = 14; + public static DONATE_TO_USER: number = 24; + public static DONATE_TO_ALL: number = 25; +} diff --git a/src/api/room/widgets/ChatBubbleMessage.ts b/src/api/room/widgets/ChatBubbleMessage.ts new file mode 100644 index 0000000..3e31e38 --- /dev/null +++ b/src/api/room/widgets/ChatBubbleMessage.ts @@ -0,0 +1,54 @@ +export class ChatBubbleMessage +{ + public static BUBBLE_COUNTER: number = 0; + + public id: number = -1; + public width: number = 0; + public height: number = 0; + public elementRef: HTMLDivElement = null; + public skipMovement: boolean = false; + + private _top: number = 0; + private _left: number = 0; + + constructor( + public senderId: number = -1, + public senderCategory: number = -1, + public roomId: number = -1, + public text: string = '', + public formattedText: string = '', + public username: string = '', + public location: { x: number, y: number } = null, + public type: number = 0, + public styleId: number = 0, + public imageUrl: string = null, + public color: string = null + ) + { + this.id = ++ChatBubbleMessage.BUBBLE_COUNTER; + } + + public get top(): number + { + return this._top; + } + + public set top(value: number) + { + this._top = value; + + if(this.elementRef) this.elementRef.style.top = (this._top + 'px'); + } + + public get left(): number + { + return this._left; + } + + public set left(value: number) + { + this._left = value; + + if(this.elementRef) this.elementRef.style.left = (this._left + 'px'); + } +} diff --git a/src/api/room/widgets/ChatBubbleUtilities.ts b/src/api/room/widgets/ChatBubbleUtilities.ts new file mode 100644 index 0000000..fff0a14 --- /dev/null +++ b/src/api/room/widgets/ChatBubbleUtilities.ts @@ -0,0 +1,69 @@ +import { AvatarFigurePartType, AvatarScaleType, AvatarSetType, GetAvatarRenderManager, GetRoomEngine, PetFigureData, TextureUtils, Vector3d } from '@nitrots/nitro-renderer'; + +export class ChatBubbleUtilities +{ + public static AVATAR_COLOR_CACHE: Map = new Map(); + public static AVATAR_IMAGE_CACHE: Map = new Map(); + public static PET_IMAGE_CACHE: Map = new Map(); + + private static placeHolderImageUrl: string = ''; + + public static async setFigureImage(figure: string): Promise + { + const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, null, { + resetFigure: figure => this.setFigureImage(figure), + dispose: () => + {}, + disposed: false + }); + + if(!avatarImage) return null; + + const isPlaceholder = avatarImage.isPlaceholder(); + + if(isPlaceholder && this.placeHolderImageUrl?.length) return this.placeHolderImageUrl; + + figure = avatarImage.getFigure().getFigureString(); + + const imageUrl = avatarImage.processAsImageUrl(AvatarSetType.HEAD); + const color = avatarImage.getPartColor(AvatarFigurePartType.CHEST); + + if(isPlaceholder) this.placeHolderImageUrl = imageUrl; + + this.AVATAR_COLOR_CACHE.set(figure, ((color && color.rgb) || 16777215)); + this.AVATAR_IMAGE_CACHE.set(figure, imageUrl); + + avatarImage.dispose(); + + return imageUrl; + } + + public static async getUserImage(figure: string): Promise + { + let existing = this.AVATAR_IMAGE_CACHE.get(figure); + + if(!existing) existing = await this.setFigureImage(figure); + + return existing; + } + + public static async getPetImage(figure: string, direction: number, _arg_3: boolean, scale: number = 64, posture: string = null) + { + let existing = this.PET_IMAGE_CACHE.get((figure + posture)); + + if(existing) return existing; + + const figureData = new PetFigureData(figure); + const typeId = figureData.typeId; + const image = GetRoomEngine().getRoomObjectPetImage(typeId, figureData.paletteId, figureData.color, new Vector3d((direction * 45)), scale, null, false, 0, figureData.customParts, posture); + + if(image) + { + existing = await TextureUtils.generateImageUrl(image.data); + + this.PET_IMAGE_CACHE.set((figure + posture), existing); + } + + return existing; + } +} diff --git a/src/api/room/widgets/ChatMessageTypeEnum.ts b/src/api/room/widgets/ChatMessageTypeEnum.ts new file mode 100644 index 0000000..1a5296b --- /dev/null +++ b/src/api/room/widgets/ChatMessageTypeEnum.ts @@ -0,0 +1,6 @@ +export class ChatMessageTypeEnum +{ + public static CHAT_DEFAULT: number = 0; + public static CHAT_WHISPER: number = 1; + public static CHAT_SHOUT: number = 2; +} diff --git a/src/api/room/widgets/DimmerFurnitureWidgetPresetItem.ts b/src/api/room/widgets/DimmerFurnitureWidgetPresetItem.ts new file mode 100644 index 0000000..009e530 --- /dev/null +++ b/src/api/room/widgets/DimmerFurnitureWidgetPresetItem.ts @@ -0,0 +1,9 @@ +export class DimmerFurnitureWidgetPresetItem +{ + constructor( + public id: number = 0, + public type: number = 0, + public color: number = 0, + public light: number = 0) + {} +} diff --git a/src/api/room/widgets/DoChatsOverlap.ts b/src/api/room/widgets/DoChatsOverlap.ts new file mode 100644 index 0000000..74f0d7f --- /dev/null +++ b/src/api/room/widgets/DoChatsOverlap.ts @@ -0,0 +1,6 @@ +import { ChatBubbleMessage } from './ChatBubbleMessage'; + +export const DoChatsOverlap = (a: ChatBubbleMessage, b: ChatBubbleMessage, additionalBTop: number, padding: number = 0) => +{ + return !((((a.left + padding) + a.width) < (b.left + padding)) || ((a.left + padding) > ((b.left + padding) + b.width)) || ((a.top + a.height) < (b.top + additionalBTop)) || (a.top > ((b.top + additionalBTop) + b.height))); +}; diff --git a/src/api/room/widgets/FurnitureDimmerUtilities.ts b/src/api/room/widgets/FurnitureDimmerUtilities.ts new file mode 100644 index 0000000..f55fc87 --- /dev/null +++ b/src/api/room/widgets/FurnitureDimmerUtilities.ts @@ -0,0 +1,30 @@ +import { GetRoomEngine } from '@nitrots/nitro-renderer'; +import { GetRoomSession } from '../../nitro'; + +export class FurnitureDimmerUtilities +{ + public static AVAILABLE_COLORS: number[] = [ 7665141, 21495, 15161822, 15353138, 15923281, 8581961, 0 ]; + public static HTML_COLORS: string[] = [ '#74F5F5', '#0053F7', '#E759DE', '#EA4532', '#F2F851', '#82F349', '#000000' ]; + public static MIN_BRIGHTNESS: number = 76; + public static MAX_BRIGHTNESS: number = 255; + + public static savePreset(presetNumber: number, effectTypeId: number, color: number, brightness: number, apply: boolean): void + { + GetRoomSession().updateMoodlightData(presetNumber, effectTypeId, color, brightness, apply); + } + + public static changeState(): void + { + GetRoomSession().toggleMoodlightState(); + } + + public static previewDimmer(color: number, brightness: number, bgOnly: boolean): void + { + GetRoomEngine().updateObjectRoomColor(GetRoomSession().roomId, color, brightness, bgOnly); + } + + public static scaleBrightness(value: number): number + { + return ~~((((value - this.MIN_BRIGHTNESS) * (100 - 0)) / (this.MAX_BRIGHTNESS - this.MIN_BRIGHTNESS)) + 0); + } +} diff --git a/src/api/room/widgets/GetDiskColor.ts b/src/api/room/widgets/GetDiskColor.ts new file mode 100644 index 0000000..97cd24d --- /dev/null +++ b/src/api/room/widgets/GetDiskColor.ts @@ -0,0 +1,37 @@ +const DISK_COLOR_RED_MIN: number = 130; +const DISK_COLOR_RED_RANGE: number = 100; +const DISK_COLOR_GREEN_MIN: number = 130; +const DISK_COLOR_GREEN_RANGE: number = 100; +const DISK_COLOR_BLUE_MIN: number = 130; +const DISK_COLOR_BLUE_RANGE: number = 100; + +export const GetDiskColor = (name: string) => +{ + let r: number = 0; + let g: number = 0; + let b: number = 0; + let index: number = 0; + + while(index < name.length) + { + switch((index % 3)) + { + case 0: + r = (r + ( name.charCodeAt(index) * 37) ); + break; + case 1: + g = (g + ( name.charCodeAt(index) * 37) ); + break; + case 2: + b = (b + ( name.charCodeAt(index) * 37) ); + break; + } + index++; + } + + r = ((r % DISK_COLOR_RED_RANGE) + DISK_COLOR_RED_MIN); + g = ((g % DISK_COLOR_GREEN_RANGE) + DISK_COLOR_GREEN_MIN); + b = ((b % DISK_COLOR_BLUE_RANGE) + DISK_COLOR_BLUE_MIN); + + return `rgb(${ r },${ g },${ b })`; +}; diff --git a/src/api/room/widgets/IAvatarInfo.ts b/src/api/room/widgets/IAvatarInfo.ts new file mode 100644 index 0000000..23fb47b --- /dev/null +++ b/src/api/room/widgets/IAvatarInfo.ts @@ -0,0 +1,4 @@ +export interface IAvatarInfo +{ + type: string; +} diff --git a/src/api/room/widgets/ICraftingIngredient.ts b/src/api/room/widgets/ICraftingIngredient.ts new file mode 100644 index 0000000..cb2b031 --- /dev/null +++ b/src/api/room/widgets/ICraftingIngredient.ts @@ -0,0 +1,6 @@ +export interface ICraftingIngredient +{ + name: string; + iconUrl: string; + count: number; +} diff --git a/src/api/room/widgets/ICraftingRecipe.ts b/src/api/room/widgets/ICraftingRecipe.ts new file mode 100644 index 0000000..dd99291 --- /dev/null +++ b/src/api/room/widgets/ICraftingRecipe.ts @@ -0,0 +1,6 @@ +export interface ICraftingRecipe +{ + name: string; + localizedName: string; + iconUrl: string; +} diff --git a/src/api/room/widgets/IPhotoData.ts b/src/api/room/widgets/IPhotoData.ts new file mode 100644 index 0000000..9a7b846 --- /dev/null +++ b/src/api/room/widgets/IPhotoData.ts @@ -0,0 +1,42 @@ +export interface IPhotoData +{ + /** + * creator username + */ + n?: string; + + /** + * creator user id + */ + s?: number; + + /** + * photo unique id + */ + u?: number; + + /** + * creation timestamp + */ + t?: number; + + /** + * photo caption + */ + m?: string; + + /** + * photo image url + */ + w?: string; + + /** + * owner id + */ + oi?: number; + + /** + * owner name + */ + o?: string; +} \ No newline at end of file diff --git a/src/api/room/widgets/MannequinUtilities.ts b/src/api/room/widgets/MannequinUtilities.ts new file mode 100644 index 0000000..74d45f9 --- /dev/null +++ b/src/api/room/widgets/MannequinUtilities.ts @@ -0,0 +1,38 @@ +import { AvatarFigurePartType, GetAvatarRenderManager, IAvatarFigureContainer } from '@nitrots/nitro-renderer'; + +export class MannequinUtilities +{ + public static MANNEQUIN_FIGURE = [ 'hd', 99999, [ 99998 ] ]; + public static MANNEQUIN_CLOTHING_PART_TYPES = [ + AvatarFigurePartType.CHEST_ACCESSORY, + AvatarFigurePartType.COAT_CHEST, + AvatarFigurePartType.CHEST, + AvatarFigurePartType.LEGS, + AvatarFigurePartType.SHOES, + AvatarFigurePartType.WAIST_ACCESSORY + ]; + + public static getMergedMannequinFigureContainer(figure: string, targetFigure: string): IAvatarFigureContainer + { + const figureContainer = GetAvatarRenderManager().createFigureContainer(figure); + const targetFigureContainer = GetAvatarRenderManager().createFigureContainer(targetFigure); + + for(const part of this.MANNEQUIN_CLOTHING_PART_TYPES) figureContainer.removePart(part); + + for(const part of targetFigureContainer.getPartTypeIds()) figureContainer.updatePart(part, targetFigureContainer.getPartSetId(part), targetFigureContainer.getPartColorIds(part)); + + return figureContainer; + } + + public static transformAsMannequinFigure(figureContainer: IAvatarFigureContainer): void + { + for(const part of figureContainer.getPartTypeIds()) + { + if(this.MANNEQUIN_CLOTHING_PART_TYPES.indexOf(part) >= 0) continue; + + figureContainer.removePart(part); + } + + figureContainer.updatePart((this.MANNEQUIN_FIGURE[0] as string), (this.MANNEQUIN_FIGURE[1] as number), (this.MANNEQUIN_FIGURE[2] as number[])); + }; +} diff --git a/src/api/room/widgets/PetSupplementEnum.ts b/src/api/room/widgets/PetSupplementEnum.ts new file mode 100644 index 0000000..eb23687 --- /dev/null +++ b/src/api/room/widgets/PetSupplementEnum.ts @@ -0,0 +1,5 @@ +export class PetSupplementEnum +{ + public static WATER: number = 0; + public static LIGHT: number = 1; +} diff --git a/src/api/room/widgets/PostureTypeEnum.ts b/src/api/room/widgets/PostureTypeEnum.ts new file mode 100644 index 0000000..21352d7 --- /dev/null +++ b/src/api/room/widgets/PostureTypeEnum.ts @@ -0,0 +1,5 @@ +export class PostureTypeEnum +{ + public static POSTURE_STAND: number = 0; + public static POSTURE_SIT: number = 1; +} diff --git a/src/api/room/widgets/RoomDimmerPreset.ts b/src/api/room/widgets/RoomDimmerPreset.ts new file mode 100644 index 0000000..86600d5 --- /dev/null +++ b/src/api/room/widgets/RoomDimmerPreset.ts @@ -0,0 +1,35 @@ +export class RoomDimmerPreset +{ + private _id: number; + private _type: number; + private _color: number; + private _brightness: number; + + constructor(id: number, type: number, color: number, brightness: number) + { + this._id = id; + this._type = type; + this._color = color; + this._brightness = brightness; + } + + public get id(): number + { + return this._id; + } + + public get type(): number + { + return this._type; + } + + public get color(): number + { + return this._color; + } + + public get brightness(): number + { + return this._brightness; + } +} diff --git a/src/api/room/widgets/RoomObjectItem.ts b/src/api/room/widgets/RoomObjectItem.ts new file mode 100644 index 0000000..f4fb2d6 --- /dev/null +++ b/src/api/room/widgets/RoomObjectItem.ts @@ -0,0 +1,28 @@ +export class RoomObjectItem +{ + private _id: number; + private _category: number; + private _name: string; + + constructor(id: number, category: number, name: string) + { + this._id = id; + this._category = category; + this._name = name; + } + + public get id(): number + { + return this._id; + } + + public get category(): number + { + return this._category; + } + + public get name(): string + { + return this._name; + } +} diff --git a/src/api/room/widgets/UseProductItem.ts b/src/api/room/widgets/UseProductItem.ts new file mode 100644 index 0000000..d3e2088 --- /dev/null +++ b/src/api/room/widgets/UseProductItem.ts @@ -0,0 +1,12 @@ +export class UseProductItem +{ + constructor( + public readonly id: number, + public readonly category: number, + public readonly name: string, + public readonly requestRoomObjectId: number, + public readonly targetRoomObjectId: number, + public readonly requestInventoryStripId: number, + public readonly replace: boolean) + {} +} diff --git a/src/api/room/widgets/VoteValue.ts b/src/api/room/widgets/VoteValue.ts new file mode 100644 index 0000000..ecf4336 --- /dev/null +++ b/src/api/room/widgets/VoteValue.ts @@ -0,0 +1,8 @@ +export const VALUE_KEY_DISLIKE = '0'; +export const VALUE_KEY_LIKE = '1'; + +export interface VoteValue +{ + value: string; + secondsLeft: number; +} diff --git a/src/api/room/widgets/YoutubeVideoPlaybackStateEnum.ts b/src/api/room/widgets/YoutubeVideoPlaybackStateEnum.ts new file mode 100644 index 0000000..2d17841 --- /dev/null +++ b/src/api/room/widgets/YoutubeVideoPlaybackStateEnum.ts @@ -0,0 +1,9 @@ +export class YoutubeVideoPlaybackStateEnum +{ + public static readonly UNSTARTED = -1; + public static readonly ENDED = 0; + public static readonly PLAYING = 1; + public static readonly PAUSED = 2; + public static readonly BUFFERING = 3; + public static readonly CUED = 5; +} diff --git a/src/api/room/widgets/index.ts b/src/api/room/widgets/index.ts new file mode 100644 index 0000000..6c50c83 --- /dev/null +++ b/src/api/room/widgets/index.ts @@ -0,0 +1,26 @@ +export * from './AvatarInfoFurni'; +export * from './AvatarInfoName'; +export * from './AvatarInfoPet'; +export * from './AvatarInfoRentableBot'; +export * from './AvatarInfoUser'; +export * from './AvatarInfoUtilities'; +export * from './BotSkillsEnum'; +export * from './ChatBubbleMessage'; +export * from './ChatBubbleUtilities'; +export * from './ChatMessageTypeEnum'; +export * from './DimmerFurnitureWidgetPresetItem'; +export * from './DoChatsOverlap'; +export * from './FurnitureDimmerUtilities'; +export * from './GetDiskColor'; +export * from './IAvatarInfo'; +export * from './ICraftingIngredient'; +export * from './ICraftingRecipe'; +export * from './IPhotoData'; +export * from './MannequinUtilities'; +export * from './PetSupplementEnum'; +export * from './PostureTypeEnum'; +export * from './RoomDimmerPreset'; +export * from './RoomObjectItem'; +export * from './UseProductItem'; +export * from './VoteValue'; +export * from './YoutubeVideoPlaybackStateEnum'; diff --git a/src/api/user/GetUserProfile.ts b/src/api/user/GetUserProfile.ts new file mode 100644 index 0000000..13c67aa --- /dev/null +++ b/src/api/user/GetUserProfile.ts @@ -0,0 +1,7 @@ +import { UserProfileComposer } from '@nitrots/nitro-renderer'; +import { SendMessageComposer } from '../nitro'; + +export function GetUserProfile(userId: number): void +{ + SendMessageComposer(new UserProfileComposer(userId)); +} diff --git a/src/api/user/index.ts b/src/api/user/index.ts new file mode 100644 index 0000000..1c609ea --- /dev/null +++ b/src/api/user/index.ts @@ -0,0 +1 @@ +export * from './GetUserProfile'; diff --git a/src/api/utils/CloneObject.ts b/src/api/utils/CloneObject.ts new file mode 100644 index 0000000..b306fac --- /dev/null +++ b/src/api/utils/CloneObject.ts @@ -0,0 +1,14 @@ +export const CloneObject = (object: T): T => +{ + if((object == null) || ('object' != typeof object)) return object; + + // @ts-ignore + const copy = new object.constructor(); + + for(const attr in object) + { + if(object.hasOwnProperty(attr)) copy[attr] = object[attr]; + } + + return copy; +}; diff --git a/src/api/utils/ColorUtils.ts b/src/api/utils/ColorUtils.ts new file mode 100644 index 0000000..ff3a0bf --- /dev/null +++ b/src/api/utils/ColorUtils.ts @@ -0,0 +1,65 @@ +export class ColorUtils +{ + public static makeColorHex(color: string): string + { + return ('#' + color); + } + + public static makeColorNumberHex(color: number): string + { + let val = color.toString(16); + return ( '#' + val.padStart(6, '0')); + } + + public static convertFromHex(color: string): number + { + return parseInt(color.replace('#', ''), 16); + } + + public static uintHexColor(color: number): string + { + const realColor = color >>>0; + + return ColorUtils.makeColorHex(realColor.toString(16).substring(2)); + } + + /** + * Converts an integer format into an array of 8-bit values + * @param {number} value value in integer format + * @returns {Array} 8-bit values + */ + public static int_to_8BitVals(value: number): [number, number, number, number] + { + const val1 = ((value >> 24) & 0xFF); + const val2 = ((value >> 16) & 0xFF); + const val3 = ((value >> 8) & 0xFF); + const val4 = (value & 0xFF); + + return [ val1, val2, val3, val4 ]; + } + + /** + * Combines 4 8-bit values into a 32-bit integer. Values are combined in + * in the order of the parameters + * @param val1 + * @param val2 + * @param val3 + * @param val4 + * @returns 32-bit integer of combined values + */ + public static eight_bitVals_to_int(val1: number, val2: number, val3: number, val4: number): number + { + return (((val1) << 24) + ((val2) << 16) + ((val3) << 8) + (val4| 0)); + } + + public static int2rgb(color: number): string + { + color >>>= 0; + const b = color & 0xFF; + const g = (color & 0xFF00) >>> 8; + const r = (color & 0xFF0000) >>> 16; + const a = ((color & 0xFF000000) >>> 24) / 255; + + return 'rgba(' + [ r, g, b, 1 ].join(',') + ')'; + } +} diff --git a/src/api/utils/ConvertSeconds.ts b/src/api/utils/ConvertSeconds.ts new file mode 100644 index 0000000..351dda8 --- /dev/null +++ b/src/api/utils/ConvertSeconds.ts @@ -0,0 +1,9 @@ +export const ConvertSeconds = (seconds: number) => +{ + let numDays = Math.floor(seconds / 86400); + let numHours = Math.floor((seconds % 86400) / 3600); + let numMinutes = Math.floor(((seconds % 86400) % 3600) / 60); + let numSeconds = ((seconds % 86400) % 3600) % 60; + + return numDays.toString().padStart(2, '0') + ':' + numHours.toString().padStart(2, '0') + ':' + numMinutes.toString().padStart(2, '0') + ':' + numSeconds.toString().padStart(2, '0'); +}; diff --git a/src/api/utils/FixedSizeStack.ts b/src/api/utils/FixedSizeStack.ts new file mode 100644 index 0000000..af8e09a --- /dev/null +++ b/src/api/utils/FixedSizeStack.ts @@ -0,0 +1,65 @@ +export class FixedSizeStack +{ + private _data: number[]; + private _maxSize: number; + private _index: number; + + constructor(k: number) + { + this._data = []; + this._maxSize = k; + this._index = 0; + } + + public reset(): void + { + this._data = []; + this._index = 0; + } + + public addValue(k: number): void + { + if(this._data.length < this._maxSize) + { + this._data.push(k); + } + else + { + this._data[this._index] = k; + } + + this._index = ((this._index + 1) % this._maxSize); + } + + public getMax(): number + { + let k = Number.MIN_VALUE; + + let _local_2 = 0; + + while(_local_2 < this._maxSize) + { + if(this._data[_local_2] > k) k = this._data[_local_2]; + + _local_2++; + } + + return k; + } + + public getMin(): number + { + let k = Number.MAX_VALUE; + + let _local_2 = 0; + + while(_local_2 < this._maxSize) + { + if(this._data[_local_2] < k) k = this._data[_local_2]; + + _local_2++; + } + + return k; + } +} diff --git a/src/api/utils/FriendlyTime.ts b/src/api/utils/FriendlyTime.ts new file mode 100644 index 0000000..7acb39c --- /dev/null +++ b/src/api/utils/FriendlyTime.ts @@ -0,0 +1,47 @@ +import { LocalizeText } from './LocalizeText'; + +export class FriendlyTime +{ + private static MINUTE: number = 60; + private static HOUR: number = (60 * FriendlyTime.MINUTE); + private static DAY: number = (24 * FriendlyTime.HOUR); + private static WEEK: number = (7 * FriendlyTime.DAY); + private static MONTH: number = (30 * FriendlyTime.DAY); + private static YEAR: number = (365 * FriendlyTime.DAY); + + + public static format(seconds: number, key: string = '', threshold: number = 3): string + { + if(seconds > (threshold * FriendlyTime.YEAR)) return FriendlyTime.getLocalization(('friendlytime.years' + key), Math.round((seconds / FriendlyTime.YEAR))); + + if(seconds > (threshold * FriendlyTime.MONTH)) return FriendlyTime.getLocalization(('friendlytime.months' + key), Math.round((seconds / FriendlyTime.MONTH))); + + if(seconds > (threshold * FriendlyTime.DAY)) return FriendlyTime.getLocalization(('friendlytime.days' + key), Math.round((seconds / FriendlyTime.DAY))); + + if(seconds > (threshold * FriendlyTime.HOUR)) return FriendlyTime.getLocalization(('friendlytime.hours' + key), Math.round((seconds / FriendlyTime.HOUR))); + + if(seconds > (threshold * FriendlyTime.MINUTE)) return FriendlyTime.getLocalization(('friendlytime.minutes' + key), Math.round((seconds / FriendlyTime.MINUTE))); + + return FriendlyTime.getLocalization(('friendlytime.seconds' + key), Math.round(seconds)); + } + + public static shortFormat(seconds: number, key: string = '', threshold: number = 3): string + { + if(seconds > (threshold * FriendlyTime.YEAR)) return FriendlyTime.getLocalization(('friendlytime.years.short' + key), Math.round((seconds / FriendlyTime.YEAR))); + + if(seconds > (threshold * FriendlyTime.MONTH)) return FriendlyTime.getLocalization(('friendlytime.months.short' + key), Math.round((seconds / FriendlyTime.MONTH))); + + if(seconds > (threshold * FriendlyTime.DAY)) return FriendlyTime.getLocalization(('friendlytime.days.short' + key), Math.round((seconds / FriendlyTime.DAY))); + + if(seconds > (threshold * FriendlyTime.HOUR)) return FriendlyTime.getLocalization(('friendlytime.hours.short' + key), Math.round((seconds / FriendlyTime.HOUR))); + + if(seconds > (threshold * FriendlyTime.MINUTE)) return FriendlyTime.getLocalization(('friendlytime.minutes.short' + key), Math.round((seconds / FriendlyTime.MINUTE))); + + return FriendlyTime.getLocalization(('friendlytime.seconds.short' + key), Math.round(seconds)); + } + + public static getLocalization(key: string, amount: number): string + { + return LocalizeText(key, [ 'amount' ], [ amount.toString() ]); + } +} diff --git a/src/api/utils/GetLocalStorage.ts b/src/api/utils/GetLocalStorage.ts new file mode 100644 index 0000000..a4270cf --- /dev/null +++ b/src/api/utils/GetLocalStorage.ts @@ -0,0 +1,11 @@ +export const GetLocalStorage = (key: string) => +{ + try + { + JSON.parse(window.localStorage.getItem(key)) as T ?? null; + } + catch (e) + { + return null; + } +}; diff --git a/src/api/utils/LocalStorageKeys.ts b/src/api/utils/LocalStorageKeys.ts new file mode 100644 index 0000000..6c92279 --- /dev/null +++ b/src/api/utils/LocalStorageKeys.ts @@ -0,0 +1,5 @@ +export class LocalStorageKeys +{ + public static CATALOG_PLACE_MULTIPLE_OBJECTS: string = 'catalogPlaceMultipleObjects'; + public static CATALOG_SKIP_PURCHASE_CONFIRMATION: string = 'catalogSkipPurchaseConfirmation'; +} diff --git a/src/api/utils/LocalizeBadgeDescription.ts b/src/api/utils/LocalizeBadgeDescription.ts new file mode 100644 index 0000000..11f178e --- /dev/null +++ b/src/api/utils/LocalizeBadgeDescription.ts @@ -0,0 +1,10 @@ +import { GetLocalizationManager } from '@nitrots/nitro-renderer'; + +export const LocalizeBadgeDescription = (key: string) => +{ + let badgeDesc = GetLocalizationManager().getBadgeDesc(key); + + if(!badgeDesc || !badgeDesc.length) badgeDesc = `badge_desc_${ key }`; + + return badgeDesc; +}; diff --git a/src/api/utils/LocalizeBageName.ts b/src/api/utils/LocalizeBageName.ts new file mode 100644 index 0000000..47645cb --- /dev/null +++ b/src/api/utils/LocalizeBageName.ts @@ -0,0 +1,10 @@ +import { GetLocalizationManager } from '@nitrots/nitro-renderer'; + +export const LocalizeBadgeName = (key: string) => +{ + let badgeName = GetLocalizationManager().getBadgeName(key); + + if(!badgeName || !badgeName.length) badgeName = `badge_name_${ key }`; + + return badgeName; +}; diff --git a/src/api/utils/LocalizeFormattedNumber.ts b/src/api/utils/LocalizeFormattedNumber.ts new file mode 100644 index 0000000..fab30d4 --- /dev/null +++ b/src/api/utils/LocalizeFormattedNumber.ts @@ -0,0 +1,6 @@ +export function LocalizeFormattedNumber(number: number): string +{ + if(!number || isNaN(number)) return '0'; + + return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' '); +}; diff --git a/src/api/utils/LocalizeShortNumber.ts b/src/api/utils/LocalizeShortNumber.ts new file mode 100644 index 0000000..30975ec --- /dev/null +++ b/src/api/utils/LocalizeShortNumber.ts @@ -0,0 +1,36 @@ +export function LocalizeShortNumber(number: number): string +{ + if(!number || isNaN(number)) return '0'; + + let abs = Math.abs(number); + + const rounder = Math.pow(10, 1); + const isNegative = (number < 0); + + let key = ''; + + const powers = [ + { key: 'Q', value: Math.pow(10, 15) }, + { key: 'T', value: Math.pow(10, 12) }, + { key: 'B', value: Math.pow(10, 9) }, + { key: 'M', value: Math.pow(10, 6) }, + { key: 'K', value: 1000 } + ]; + + for(const power of powers) + { + let reduced = abs / power.value; + + reduced = Math.round(reduced * rounder) / rounder; + + if(reduced >= 1) + { + abs = reduced; + key = power.key; + + break; + } + } + + return ((isNegative ? '-' : '') + abs + key); +} diff --git a/src/api/utils/LocalizeText.ts b/src/api/utils/LocalizeText.ts new file mode 100644 index 0000000..68d0273 --- /dev/null +++ b/src/api/utils/LocalizeText.ts @@ -0,0 +1,6 @@ +import { GetLocalizationManager } from '@nitrots/nitro-renderer'; + +export function LocalizeText(key: string, parameters: string[] = null, replacements: string[] = null): string +{ + return GetLocalizationManager().getValueWithParameters(key, parameters, replacements); +} diff --git a/src/api/utils/PlaySound.ts b/src/api/utils/PlaySound.ts new file mode 100644 index 0000000..b0f903c --- /dev/null +++ b/src/api/utils/PlaySound.ts @@ -0,0 +1,24 @@ +import { MouseEventType, NitroSoundEvent } from '@nitrots/nitro-renderer'; +import { DispatchMainEvent } from '../events'; + +let canPlaySound = false; + +export const PlaySound = (sampleCode: string) => +{ + if(!canPlaySound) return; + + DispatchMainEvent(new NitroSoundEvent(NitroSoundEvent.PLAY_SOUND, sampleCode)); +}; + +const eventTypes = [ MouseEventType.MOUSE_CLICK ]; + +const startListening = () => +{ + const stopListening = () => eventTypes.forEach(type => window.removeEventListener(type, onEvent)); + + const onEvent = (event: Event) => ((canPlaySound = true) && stopListening()); + + eventTypes.forEach(type => window.addEventListener(type, onEvent)); +}; + +startListening(); diff --git a/src/api/utils/ProductImageUtility.ts b/src/api/utils/ProductImageUtility.ts new file mode 100644 index 0000000..5443513 --- /dev/null +++ b/src/api/utils/ProductImageUtility.ts @@ -0,0 +1,58 @@ +import { FurnitureType, GetRoomEngine } from '@nitrots/nitro-renderer'; +import { FurniCategory } from '../inventory'; + +export class ProductImageUtility +{ + public static getProductImageUrl(productType: FurnitureType, furniClassId: number, extraParam: string): string + { + let imageUrl: string = null; + + switch(productType) + { + case FurnitureType.FLOOR: + imageUrl = GetRoomEngine().getFurnitureFloorIconUrl(furniClassId); + break; + case FurnitureType.WALL: + const productCategory = this.getProductCategory(CatalogPageMessageProductData.I, furniClassId); + + if(productCategory === 1) + { + imageUrl = GetRoomEngine().getFurnitureWallIconUrl(furniClassId, extraParam); + } + else + { + switch(productCategory) + { + case FurniCategory.WALL_PAPER: + break; + case FurniCategory.LANDSCAPE: + break; + case FurniCategory.FLOOR: + break; + } + } + break; + case FurnitureType.EFFECT: + // fx_icon_furniClassId_png + break; + } + + return imageUrl; + } + + public static getProductCategory(productType: FurnitureType, furniClassId: number): number + { + if(productType === FurnitureType.FLOOR) return 1; + + if(productType === FurnitureType.WALL) + { + if(furniClassId === 3001) return FurniCategory.WALL_PAPER; + + if(furniClassId === 3002) return FurniCategory.FLOOR; + + if(furniClassId === 4057) return FurniCategory.LANDSCAPE; + } + + return 1; + } +} diff --git a/src/api/utils/Randomizer.ts b/src/api/utils/Randomizer.ts new file mode 100644 index 0000000..1f67a12 --- /dev/null +++ b/src/api/utils/Randomizer.ts @@ -0,0 +1,28 @@ +export class Randomizer +{ + public static getRandomNumber(count: number): number + { + return Math.floor(Math.random() * count); + } + + public static getRandomElement(elements: T[]): T + { + return elements[this.getRandomNumber(elements.length)]; + } + + public static getRandomElements(elements: T[], count: number): T[] + { + const result: T[] = new Array(count); + let len = elements.length; + const taken = new Array(len); + + while(count--) + { + var x = this.getRandomNumber(len); + result[count] = elements[x in taken ? taken[x] : x]; + taken[x] = --len in taken ? taken[len] : len; + } + + return result; + } +} diff --git a/src/api/utils/RoomChatFormatter.ts b/src/api/utils/RoomChatFormatter.ts new file mode 100644 index 0000000..f87840b --- /dev/null +++ b/src/api/utils/RoomChatFormatter.ts @@ -0,0 +1,75 @@ +const allowedColours: Map = new Map(); + +allowedColours.set('r', 'red'); +allowedColours.set('b', 'blue'); +allowedColours.set('g', 'green'); +allowedColours.set('y', 'yellow'); +allowedColours.set('w', 'white'); +allowedColours.set('o', 'orange'); +allowedColours.set('c', 'cyan'); +allowedColours.set('br', 'brown'); +allowedColours.set('pr', 'purple'); +allowedColours.set('pk', 'pink'); + +allowedColours.set('red', 'red'); +allowedColours.set('blue', 'blue'); +allowedColours.set('green', 'green'); +allowedColours.set('yellow', 'yellow'); +allowedColours.set('white', 'white'); +allowedColours.set('orange', 'orange'); +allowedColours.set('cyan', 'cyan'); +allowedColours.set('brown', 'brown'); +allowedColours.set('purple', 'purple'); +allowedColours.set('pink', 'pink'); + +const encodeHTML = (str: string) => +{ + return str.replace(/([\u00A0-\u9999<>&])(.|$)/g, (full, char, next) => + { + if(char !== '&' || next !== '#') + { + if(/[\u00A0-\u9999<>&]/.test(next)) next = '&#' + next.charCodeAt(0) + ';'; + + return '&#' + char.charCodeAt(0) + ';' + next; + } + + return full; + }); +}; + +export const RoomChatFormatter = (content: string) => +{ + let result = ''; + + content = encodeHTML(content); + //content = (joypixels.shortnameToUnicode(content) as string) + + if(content.startsWith('@') && content.indexOf('@', 1) > -1) + { + let match = null; + + while((match = /@[a-zA-Z]+@/g.exec(content)) !== null) + { + const colorTag = match[0].toString(); + const colorName = colorTag.substr(1, colorTag.length - 2); + const text = content.replace(colorTag, ''); + + if(!allowedColours.has(colorName)) + { + result = text; + } + else + { + const color = allowedColours.get(colorName); + result = '' + text + ''; + } + break; + } + } + else + { + result = content; + } + + return result; +}; diff --git a/src/api/utils/SetLocalStorage.ts b/src/api/utils/SetLocalStorage.ts new file mode 100644 index 0000000..02aa8f3 --- /dev/null +++ b/src/api/utils/SetLocalStorage.ts @@ -0,0 +1 @@ +export const SetLocalStorage = (key: string, value: T) => window.localStorage.setItem(key, JSON.stringify(value)); diff --git a/src/api/utils/SoundNames.ts b/src/api/utils/SoundNames.ts new file mode 100644 index 0000000..4459651 --- /dev/null +++ b/src/api/utils/SoundNames.ts @@ -0,0 +1,9 @@ +export class SoundNames +{ + public static CAMERA_SHUTTER = 'camera_shutter'; + public static CREDITS = 'credits'; + public static DUCKETS = 'duckets'; + public static MESSENGER_NEW_THREAD = 'messenger_new_thread'; + public static MESSENGER_MESSAGE_RECEIVED = 'messenger_message_received'; + public static MODTOOLS_NEW_TICKET = 'modtools_new_ticket'; +} diff --git a/src/api/utils/WindowSaveOptions.ts b/src/api/utils/WindowSaveOptions.ts new file mode 100644 index 0000000..9aa8456 --- /dev/null +++ b/src/api/utils/WindowSaveOptions.ts @@ -0,0 +1,5 @@ +export interface WindowSaveOptions +{ + offset: { x: number, y: number }; + size: { width: number, height: number }; +} diff --git a/src/api/utils/index.ts b/src/api/utils/index.ts new file mode 100644 index 0000000..1824add --- /dev/null +++ b/src/api/utils/index.ts @@ -0,0 +1,19 @@ +export * from './CloneObject'; +export * from './ColorUtils'; +export * from './ConvertSeconds'; +export * from './FixedSizeStack'; +export * from './FriendlyTime'; +export * from './GetLocalStorage'; +export * from './LocalStorageKeys'; +export * from './LocalizeBadgeDescription'; +export * from './LocalizeBageName'; +export * from './LocalizeFormattedNumber'; +export * from './LocalizeShortNumber'; +export * from './LocalizeText'; +export * from './PlaySound'; +export * from './ProductImageUtility'; +export * from './Randomizer'; +export * from './RoomChatFormatter'; +export * from './SetLocalStorage'; +export * from './SoundNames'; +export * from './WindowSaveOptions'; diff --git a/src/api/wired/GetWiredTimeLocale.ts b/src/api/wired/GetWiredTimeLocale.ts new file mode 100644 index 0000000..39f3516 --- /dev/null +++ b/src/api/wired/GetWiredTimeLocale.ts @@ -0,0 +1,8 @@ +export const GetWiredTimeLocale = (value: number) => +{ + const time = Math.floor((value / 2)); + + if(!(value % 2)) return time.toString(); + + return (time + 0.5).toString(); +}; diff --git a/src/api/wired/WiredActionLayoutCode.ts b/src/api/wired/WiredActionLayoutCode.ts new file mode 100644 index 0000000..5282dc5 --- /dev/null +++ b/src/api/wired/WiredActionLayoutCode.ts @@ -0,0 +1,29 @@ +export class WiredActionLayoutCode +{ + public static TOGGLE_FURNI_STATE: number = 0; + public static RESET: number = 1; + public static SET_FURNI_STATE: number = 3; + public static MOVE_FURNI: number = 4; + public static GIVE_SCORE: number = 6; + public static CHAT: number = 7; + public static TELEPORT: number = 8; + public static JOIN_TEAM: number = 9; + public static LEAVE_TEAM: number = 10; + public static CHASE: number = 11; + public static FLEE: number = 12; + public static MOVE_AND_ROTATE_FURNI: number = 13; + public static GIVE_SCORE_TO_PREDEFINED_TEAM: number = 14; + public static TOGGLE_TO_RANDOM_STATE: number = 15; + public static MOVE_FURNI_TO: number = 16; + public static GIVE_REWARD: number = 17; + public static CALL_ANOTHER_STACK: number = 18; + public static KICK_FROM_ROOM: number = 19; + public static MUTE_USER: number = 20; + public static BOT_TELEPORT: number = 21; + public static BOT_MOVE: number = 22; + public static BOT_TALK: number = 23; + public static BOT_GIVE_HAND_ITEM: number = 24; + public static BOT_FOLLOW_AVATAR: number = 25; + public static BOT_CHANGE_FIGURE: number = 26; + public static BOT_TALK_DIRECT_TO_AVTR: number = 27; +} diff --git a/src/api/wired/WiredConditionLayoutCode.ts b/src/api/wired/WiredConditionLayoutCode.ts new file mode 100644 index 0000000..58cae5d --- /dev/null +++ b/src/api/wired/WiredConditionLayoutCode.ts @@ -0,0 +1,29 @@ +export class WiredConditionlayout +{ + public static STATES_MATCH: number = 0; + public static FURNIS_HAVE_AVATARS: number = 1; + public static ACTOR_IS_ON_FURNI: number = 2; + public static TIME_ELAPSED_MORE: number = 3; + public static TIME_ELAPSED_LESS: number = 4; + public static USER_COUNT_IN: number = 5; + public static ACTOR_IS_IN_TEAM: number = 6; + public static HAS_STACKED_FURNIS: number = 7; + public static STUFF_TYPE_MATCHES: number = 8; + public static STUFFS_IN_FORMATION: number = 9; + public static ACTOR_IS_GROUP_MEMBER: number = 10; + public static ACTOR_IS_WEARING_BADGE: number = 11; + public static ACTOR_IS_WEARING_EFFECT: number = 12; + public static NOT_STATES_MATCH: number = 13; + public static FURNI_NOT_HAVE_HABBO: number = 14; + public static NOT_ACTOR_ON_FURNI: number = 15; + public static NOT_USER_COUNT_IN: number = 16; + public static NOT_ACTOR_IN_TEAM: number = 17; + public static NOT_HAS_STACKED_FURNIS: number = 18; + public static NOT_FURNI_IS_OF_TYPE: number = 19; + public static NOT_STUFFS_IN_FORMATION: number = 20; + public static NOT_ACTOR_IN_GROUP: number = 21; + public static NOT_ACTOR_WEARS_BADGE: number = 22; + public static NOT_ACTOR_WEARING_EFFECT: number = 23; + public static DATE_RANGE_ACTIVE: number = 24; + public static ACTOR_HAS_HANDITEM: number = 25; +} diff --git a/src/api/wired/WiredDateToString.ts b/src/api/wired/WiredDateToString.ts new file mode 100644 index 0000000..825adc8 --- /dev/null +++ b/src/api/wired/WiredDateToString.ts @@ -0,0 +1 @@ +export const WiredDateToString = (date: Date) => `${ date.getFullYear() }/${ ('0' + (date.getMonth() + 1)).slice(-2) }/${ ('0' + date.getDate()).slice(-2) } ${ ('0' + date.getHours()).slice(-2) }:${ ('0' + date.getMinutes()).slice(-2) }`; diff --git a/src/api/wired/WiredFurniType.ts b/src/api/wired/WiredFurniType.ts new file mode 100644 index 0000000..447e970 --- /dev/null +++ b/src/api/wired/WiredFurniType.ts @@ -0,0 +1,7 @@ +export class WiredFurniType +{ + public static STUFF_SELECTION_OPTION_NONE: number = 0; + public static STUFF_SELECTION_OPTION_BY_ID: number = 1; + public static STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE: number = 2; + public static STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT: number = 3; +} diff --git a/src/api/wired/WiredSelectionVisualizer.ts b/src/api/wired/WiredSelectionVisualizer.ts new file mode 100644 index 0000000..18edbf7 --- /dev/null +++ b/src/api/wired/WiredSelectionVisualizer.ts @@ -0,0 +1,85 @@ +import { GetRoomEngine, IRoomObject, IRoomObjectSpriteVisualization, RoomObjectCategory, WiredFilter } from '@nitrots/nitro-renderer'; + +export class WiredSelectionVisualizer +{ + private static _selectionShader: WiredFilter = new WiredFilter({ + lineColor: [ 1, 1, 1 ], + color: [ 0.6, 0.6, 0.6 ] + }); + + public static show(furniId: number): void + { + WiredSelectionVisualizer.applySelectionShader(WiredSelectionVisualizer.getRoomObject(furniId)); + } + + public static hide(furniId: number): void + { + WiredSelectionVisualizer.clearSelectionShader(WiredSelectionVisualizer.getRoomObject(furniId)); + } + + public static clearSelectionShaderFromFurni(furniIds: number[]): void + { + for(const furniId of furniIds) + { + WiredSelectionVisualizer.clearSelectionShader(WiredSelectionVisualizer.getRoomObject(furniId)); + } + } + + public static applySelectionShaderToFurni(furniIds: number[]): void + { + for(const furniId of furniIds) + { + WiredSelectionVisualizer.applySelectionShader(WiredSelectionVisualizer.getRoomObject(furniId)); + } + } + + private static getRoomObject(objectId: number): IRoomObject + { + const roomEngine = GetRoomEngine(); + + return roomEngine.getRoomObject(roomEngine.activeRoomId, objectId, RoomObjectCategory.FLOOR); + } + + private static applySelectionShader(roomObject: IRoomObject): void + { + if(!roomObject) return; + + const visualization = (roomObject.visualization as IRoomObjectSpriteVisualization); + + if(!visualization) return; + + for(const sprite of visualization.sprites) + { + if(sprite.blendMode === 'add') continue; + + if(!sprite.filters) sprite.filters = []; + + sprite.filters.push(WiredSelectionVisualizer._selectionShader); + + sprite.increaseUpdateCounter(); + } + } + + private static clearSelectionShader(roomObject: IRoomObject): void + { + if(!roomObject) return; + + const visualization = (roomObject.visualization as IRoomObjectSpriteVisualization); + + if(!visualization) return; + + for(const sprite of visualization.sprites) + { + if(!sprite.filters) continue; + + const index = sprite.filters.indexOf(WiredSelectionVisualizer._selectionShader); + + if(index >= 0) + { + sprite.filters.splice(index, 1); + + sprite.increaseUpdateCounter(); + } + } + } +} diff --git a/src/api/wired/WiredStringDelimeter.ts b/src/api/wired/WiredStringDelimeter.ts new file mode 100644 index 0000000..bc4cf2e --- /dev/null +++ b/src/api/wired/WiredStringDelimeter.ts @@ -0,0 +1 @@ +export const WIRED_STRING_DELIMETER: string = '\t'; diff --git a/src/api/wired/WiredTriggerLayoutCode.ts b/src/api/wired/WiredTriggerLayoutCode.ts new file mode 100644 index 0000000..fd758df --- /dev/null +++ b/src/api/wired/WiredTriggerLayoutCode.ts @@ -0,0 +1,17 @@ +export class WiredTriggerLayout +{ + public static AVATAR_SAYS_SOMETHING: number = 0; + public static AVATAR_WALKS_ON_FURNI: number = 1; + public static AVATAR_WALKS_OFF_FURNI: number = 2; + public static EXECUTE_ONCE: number = 3; + public static TOGGLE_FURNI: number = 4; + public static EXECUTE_PERIODICALLY: number = 6; + public static AVATAR_ENTERS_ROOM: number = 7; + public static GAME_STARTS: number = 8; + public static GAME_ENDS: number = 9; + public static SCORE_ACHIEVED: number = 10; + public static COLLISION: number = 11; + public static EXECUTE_PERIODICALLY_LONG: number = 12; + public static BOT_REACHED_STUFF: number = 13; + public static BOT_REACHED_AVATAR: number = 14; +} diff --git a/src/api/wired/index.ts b/src/api/wired/index.ts new file mode 100644 index 0000000..6590adf --- /dev/null +++ b/src/api/wired/index.ts @@ -0,0 +1,8 @@ +export * from './GetWiredTimeLocale'; +export * from './WiredActionLayoutCode'; +export * from './WiredConditionLayoutCode'; +export * from './WiredDateToString'; +export * from './WiredFurniType'; +export * from './WiredSelectionVisualizer'; +export * from './WiredStringDelimeter'; +export * from './WiredTriggerLayoutCode'; diff --git a/src/assets/images/achievements/back-arrow.png b/src/assets/images/achievements/back-arrow.png new file mode 100644 index 0000000..e795c0e Binary files /dev/null and b/src/assets/images/achievements/back-arrow.png differ diff --git a/src/assets/images/avatareditor/arrow-left-icon.png b/src/assets/images/avatareditor/arrow-left-icon.png new file mode 100644 index 0000000..f94a7df Binary files /dev/null and b/src/assets/images/avatareditor/arrow-left-icon.png differ diff --git a/src/assets/images/avatareditor/arrow-right-icon.png b/src/assets/images/avatareditor/arrow-right-icon.png new file mode 100644 index 0000000..1d2217f Binary files /dev/null and b/src/assets/images/avatareditor/arrow-right-icon.png differ diff --git a/src/assets/images/avatareditor/avatar-editor-spritesheet.png b/src/assets/images/avatareditor/avatar-editor-spritesheet.png new file mode 100644 index 0000000..0c91ca0 Binary files /dev/null and b/src/assets/images/avatareditor/avatar-editor-spritesheet.png differ diff --git a/src/assets/images/avatareditor/ca-icon.png b/src/assets/images/avatareditor/ca-icon.png new file mode 100644 index 0000000..c9803b9 Binary files /dev/null and b/src/assets/images/avatareditor/ca-icon.png differ diff --git a/src/assets/images/avatareditor/ca-selected-icon.png b/src/assets/images/avatareditor/ca-selected-icon.png new file mode 100644 index 0000000..b118c3e Binary files /dev/null and b/src/assets/images/avatareditor/ca-selected-icon.png differ diff --git a/src/assets/images/avatareditor/cc-icon.png b/src/assets/images/avatareditor/cc-icon.png new file mode 100644 index 0000000..4a8844e Binary files /dev/null and b/src/assets/images/avatareditor/cc-icon.png differ diff --git a/src/assets/images/avatareditor/cc-selected-icon.png b/src/assets/images/avatareditor/cc-selected-icon.png new file mode 100644 index 0000000..3493751 Binary files /dev/null and b/src/assets/images/avatareditor/cc-selected-icon.png differ diff --git a/src/assets/images/avatareditor/ch-icon.png b/src/assets/images/avatareditor/ch-icon.png new file mode 100644 index 0000000..ef7da11 Binary files /dev/null and b/src/assets/images/avatareditor/ch-icon.png differ diff --git a/src/assets/images/avatareditor/ch-selected-icon.png b/src/assets/images/avatareditor/ch-selected-icon.png new file mode 100644 index 0000000..c5e9f34 Binary files /dev/null and b/src/assets/images/avatareditor/ch-selected-icon.png differ diff --git a/src/assets/images/avatareditor/clear-icon.png b/src/assets/images/avatareditor/clear-icon.png new file mode 100644 index 0000000..e0d50ab Binary files /dev/null and b/src/assets/images/avatareditor/clear-icon.png differ diff --git a/src/assets/images/avatareditor/cp-icon.png b/src/assets/images/avatareditor/cp-icon.png new file mode 100644 index 0000000..5e460f1 Binary files /dev/null and b/src/assets/images/avatareditor/cp-icon.png differ diff --git a/src/assets/images/avatareditor/cp-selected-icon.png b/src/assets/images/avatareditor/cp-selected-icon.png new file mode 100644 index 0000000..a067085 Binary files /dev/null and b/src/assets/images/avatareditor/cp-selected-icon.png differ diff --git a/src/assets/images/avatareditor/ea-icon.png b/src/assets/images/avatareditor/ea-icon.png new file mode 100644 index 0000000..c227ae1 Binary files /dev/null and b/src/assets/images/avatareditor/ea-icon.png differ diff --git a/src/assets/images/avatareditor/ea-selected-icon.png b/src/assets/images/avatareditor/ea-selected-icon.png new file mode 100644 index 0000000..e7678c4 Binary files /dev/null and b/src/assets/images/avatareditor/ea-selected-icon.png differ diff --git a/src/assets/images/avatareditor/fa-icon.png b/src/assets/images/avatareditor/fa-icon.png new file mode 100644 index 0000000..9b72ff5 Binary files /dev/null and b/src/assets/images/avatareditor/fa-icon.png differ diff --git a/src/assets/images/avatareditor/fa-selected-icon.png b/src/assets/images/avatareditor/fa-selected-icon.png new file mode 100644 index 0000000..a1d26b6 Binary files /dev/null and b/src/assets/images/avatareditor/fa-selected-icon.png differ diff --git a/src/assets/images/avatareditor/female-icon.png b/src/assets/images/avatareditor/female-icon.png new file mode 100644 index 0000000..8e6e820 Binary files /dev/null and b/src/assets/images/avatareditor/female-icon.png differ diff --git a/src/assets/images/avatareditor/female-selected-icon.png b/src/assets/images/avatareditor/female-selected-icon.png new file mode 100644 index 0000000..50ffde0 Binary files /dev/null and b/src/assets/images/avatareditor/female-selected-icon.png differ diff --git a/src/assets/images/avatareditor/ha-icon.png b/src/assets/images/avatareditor/ha-icon.png new file mode 100644 index 0000000..f0a8191 Binary files /dev/null and b/src/assets/images/avatareditor/ha-icon.png differ diff --git a/src/assets/images/avatareditor/ha-selected-icon.png b/src/assets/images/avatareditor/ha-selected-icon.png new file mode 100644 index 0000000..4c81ece Binary files /dev/null and b/src/assets/images/avatareditor/ha-selected-icon.png differ diff --git a/src/assets/images/avatareditor/he-icon.png b/src/assets/images/avatareditor/he-icon.png new file mode 100644 index 0000000..7cf6dc4 Binary files /dev/null and b/src/assets/images/avatareditor/he-icon.png differ diff --git a/src/assets/images/avatareditor/he-selected-icon.png b/src/assets/images/avatareditor/he-selected-icon.png new file mode 100644 index 0000000..3263355 Binary files /dev/null and b/src/assets/images/avatareditor/he-selected-icon.png differ diff --git a/src/assets/images/avatareditor/hr-icon.png b/src/assets/images/avatareditor/hr-icon.png new file mode 100644 index 0000000..de29990 Binary files /dev/null and b/src/assets/images/avatareditor/hr-icon.png differ diff --git a/src/assets/images/avatareditor/hr-selected-icon.png b/src/assets/images/avatareditor/hr-selected-icon.png new file mode 100644 index 0000000..c694b82 Binary files /dev/null and b/src/assets/images/avatareditor/hr-selected-icon.png differ diff --git a/src/assets/images/avatareditor/lg-icon.png b/src/assets/images/avatareditor/lg-icon.png new file mode 100644 index 0000000..0bdd750 Binary files /dev/null and b/src/assets/images/avatareditor/lg-icon.png differ diff --git a/src/assets/images/avatareditor/lg-selected-icon.png b/src/assets/images/avatareditor/lg-selected-icon.png new file mode 100644 index 0000000..7a2853b Binary files /dev/null and b/src/assets/images/avatareditor/lg-selected-icon.png differ diff --git a/src/assets/images/avatareditor/loading-icon.png b/src/assets/images/avatareditor/loading-icon.png new file mode 100644 index 0000000..50d132b Binary files /dev/null and b/src/assets/images/avatareditor/loading-icon.png differ diff --git a/src/assets/images/avatareditor/male-icon.png b/src/assets/images/avatareditor/male-icon.png new file mode 100644 index 0000000..95a1b35 Binary files /dev/null and b/src/assets/images/avatareditor/male-icon.png differ diff --git a/src/assets/images/avatareditor/male-selected-icon.png b/src/assets/images/avatareditor/male-selected-icon.png new file mode 100644 index 0000000..85debbb Binary files /dev/null and b/src/assets/images/avatareditor/male-selected-icon.png differ diff --git a/src/assets/images/avatareditor/sellable-icon.png b/src/assets/images/avatareditor/sellable-icon.png new file mode 100644 index 0000000..4485b51 Binary files /dev/null and b/src/assets/images/avatareditor/sellable-icon.png differ diff --git a/src/assets/images/avatareditor/sh-icon.png b/src/assets/images/avatareditor/sh-icon.png new file mode 100644 index 0000000..915c7c1 Binary files /dev/null and b/src/assets/images/avatareditor/sh-icon.png differ diff --git a/src/assets/images/avatareditor/sh-selected-icon.png b/src/assets/images/avatareditor/sh-selected-icon.png new file mode 100644 index 0000000..12c6deb Binary files /dev/null and b/src/assets/images/avatareditor/sh-selected-icon.png differ diff --git a/src/assets/images/avatareditor/spotlight-icon.png b/src/assets/images/avatareditor/spotlight-icon.png new file mode 100644 index 0000000..8755373 Binary files /dev/null and b/src/assets/images/avatareditor/spotlight-icon.png differ diff --git a/src/assets/images/avatareditor/wa-icon.png b/src/assets/images/avatareditor/wa-icon.png new file mode 100644 index 0000000..8a73b7a Binary files /dev/null and b/src/assets/images/avatareditor/wa-icon.png differ diff --git a/src/assets/images/avatareditor/wa-selected-icon.png b/src/assets/images/avatareditor/wa-selected-icon.png new file mode 100644 index 0000000..5348be3 Binary files /dev/null and b/src/assets/images/avatareditor/wa-selected-icon.png differ diff --git a/src/assets/images/backgrounds/background/bg_0.png b/src/assets/images/backgrounds/background/bg_0.png new file mode 100644 index 0000000..dc80b28 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_0.png differ diff --git a/src/assets/images/backgrounds/background/bg_1.gif b/src/assets/images/backgrounds/background/bg_1.gif new file mode 100644 index 0000000..5a07f4f Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_1.gif differ diff --git a/src/assets/images/backgrounds/background/bg_1.png b/src/assets/images/backgrounds/background/bg_1.png new file mode 100644 index 0000000..5b2fc5a Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_1.png differ diff --git a/src/assets/images/backgrounds/background/bg_10.png b/src/assets/images/backgrounds/background/bg_10.png new file mode 100644 index 0000000..80764d4 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_10.png differ diff --git a/src/assets/images/backgrounds/background/bg_100.gif b/src/assets/images/backgrounds/background/bg_100.gif new file mode 100644 index 0000000..8ad3ab7 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_100.gif differ diff --git a/src/assets/images/backgrounds/background/bg_101.png b/src/assets/images/backgrounds/background/bg_101.png new file mode 100644 index 0000000..22d8307 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_101.png differ diff --git a/src/assets/images/backgrounds/background/bg_102.gif b/src/assets/images/backgrounds/background/bg_102.gif new file mode 100644 index 0000000..e5ee68a Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_102.gif differ diff --git a/src/assets/images/backgrounds/background/bg_103.gif b/src/assets/images/backgrounds/background/bg_103.gif new file mode 100644 index 0000000..d0645c1 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_103.gif differ diff --git a/src/assets/images/backgrounds/background/bg_104.gif b/src/assets/images/backgrounds/background/bg_104.gif new file mode 100644 index 0000000..8198d2f Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_104.gif differ diff --git a/src/assets/images/backgrounds/background/bg_105.gif b/src/assets/images/backgrounds/background/bg_105.gif new file mode 100644 index 0000000..508d38d Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_105.gif differ diff --git a/src/assets/images/backgrounds/background/bg_106.gif b/src/assets/images/backgrounds/background/bg_106.gif new file mode 100644 index 0000000..1a2f8ab Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_106.gif differ diff --git a/src/assets/images/backgrounds/background/bg_107.gif b/src/assets/images/backgrounds/background/bg_107.gif new file mode 100644 index 0000000..2f24a27 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_107.gif differ diff --git a/src/assets/images/backgrounds/background/bg_108.gif b/src/assets/images/backgrounds/background/bg_108.gif new file mode 100644 index 0000000..b996f34 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_108.gif differ diff --git a/src/assets/images/backgrounds/background/bg_109.gif b/src/assets/images/backgrounds/background/bg_109.gif new file mode 100644 index 0000000..23db9e6 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_109.gif differ diff --git a/src/assets/images/backgrounds/background/bg_11.png b/src/assets/images/backgrounds/background/bg_11.png new file mode 100644 index 0000000..ebcc2a7 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_11.png differ diff --git a/src/assets/images/backgrounds/background/bg_110.gif b/src/assets/images/backgrounds/background/bg_110.gif new file mode 100644 index 0000000..fed52bb Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_110.gif differ diff --git a/src/assets/images/backgrounds/background/bg_111.gif b/src/assets/images/backgrounds/background/bg_111.gif new file mode 100644 index 0000000..fa94d51 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_111.gif differ diff --git a/src/assets/images/backgrounds/background/bg_112.gif b/src/assets/images/backgrounds/background/bg_112.gif new file mode 100644 index 0000000..974a1ff Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_112.gif differ diff --git a/src/assets/images/backgrounds/background/bg_113.gif b/src/assets/images/backgrounds/background/bg_113.gif new file mode 100644 index 0000000..f63d029 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_113.gif differ diff --git a/src/assets/images/backgrounds/background/bg_114.gif b/src/assets/images/backgrounds/background/bg_114.gif new file mode 100644 index 0000000..024c9b4 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_114.gif differ diff --git a/src/assets/images/backgrounds/background/bg_115.gif b/src/assets/images/backgrounds/background/bg_115.gif new file mode 100644 index 0000000..6f0cb29 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_115.gif differ diff --git a/src/assets/images/backgrounds/background/bg_116.gif b/src/assets/images/backgrounds/background/bg_116.gif new file mode 100644 index 0000000..02b464e Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_116.gif differ diff --git a/src/assets/images/backgrounds/background/bg_117.gif b/src/assets/images/backgrounds/background/bg_117.gif new file mode 100644 index 0000000..2c6a4a6 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_117.gif differ diff --git a/src/assets/images/backgrounds/background/bg_118.gif b/src/assets/images/backgrounds/background/bg_118.gif new file mode 100644 index 0000000..5b7d61f Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_118.gif differ diff --git a/src/assets/images/backgrounds/background/bg_119.gif b/src/assets/images/backgrounds/background/bg_119.gif new file mode 100644 index 0000000..52c4a2c Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_119.gif differ diff --git a/src/assets/images/backgrounds/background/bg_12.png b/src/assets/images/backgrounds/background/bg_12.png new file mode 100644 index 0000000..6876efd Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_12.png differ diff --git a/src/assets/images/backgrounds/background/bg_120.gif b/src/assets/images/backgrounds/background/bg_120.gif new file mode 100644 index 0000000..4d2a314 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_120.gif differ diff --git a/src/assets/images/backgrounds/background/bg_121.gif b/src/assets/images/backgrounds/background/bg_121.gif new file mode 100644 index 0000000..f5bc596 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_121.gif differ diff --git a/src/assets/images/backgrounds/background/bg_122.gif b/src/assets/images/backgrounds/background/bg_122.gif new file mode 100644 index 0000000..a625ecc Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_122.gif differ diff --git a/src/assets/images/backgrounds/background/bg_123.gif b/src/assets/images/backgrounds/background/bg_123.gif new file mode 100644 index 0000000..adda1dc Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_123.gif differ diff --git a/src/assets/images/backgrounds/background/bg_124.gif b/src/assets/images/backgrounds/background/bg_124.gif new file mode 100644 index 0000000..551af32 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_124.gif differ diff --git a/src/assets/images/backgrounds/background/bg_125.gif b/src/assets/images/backgrounds/background/bg_125.gif new file mode 100644 index 0000000..629010b Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_125.gif differ diff --git a/src/assets/images/backgrounds/background/bg_126.gif b/src/assets/images/backgrounds/background/bg_126.gif new file mode 100644 index 0000000..c799c79 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_126.gif differ diff --git a/src/assets/images/backgrounds/background/bg_127.gif b/src/assets/images/backgrounds/background/bg_127.gif new file mode 100644 index 0000000..885ce78 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_127.gif differ diff --git a/src/assets/images/backgrounds/background/bg_128.gif b/src/assets/images/backgrounds/background/bg_128.gif new file mode 100644 index 0000000..7603a4a Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_128.gif differ diff --git a/src/assets/images/backgrounds/background/bg_129.gif b/src/assets/images/backgrounds/background/bg_129.gif new file mode 100644 index 0000000..cd7c75c Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_129.gif differ diff --git a/src/assets/images/backgrounds/background/bg_13.png b/src/assets/images/backgrounds/background/bg_13.png new file mode 100644 index 0000000..d57057e Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_13.png differ diff --git a/src/assets/images/backgrounds/background/bg_130.gif b/src/assets/images/backgrounds/background/bg_130.gif new file mode 100644 index 0000000..54e01ce Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_130.gif differ diff --git a/src/assets/images/backgrounds/background/bg_131.gif b/src/assets/images/backgrounds/background/bg_131.gif new file mode 100644 index 0000000..19102c8 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_131.gif differ diff --git a/src/assets/images/backgrounds/background/bg_132.gif b/src/assets/images/backgrounds/background/bg_132.gif new file mode 100644 index 0000000..492c05d Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_132.gif differ diff --git a/src/assets/images/backgrounds/background/bg_133.gif b/src/assets/images/backgrounds/background/bg_133.gif new file mode 100644 index 0000000..5a03310 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_133.gif differ diff --git a/src/assets/images/backgrounds/background/bg_134.gif b/src/assets/images/backgrounds/background/bg_134.gif new file mode 100644 index 0000000..13a0711 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_134.gif differ diff --git a/src/assets/images/backgrounds/background/bg_135.gif b/src/assets/images/backgrounds/background/bg_135.gif new file mode 100644 index 0000000..2f24a27 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_135.gif differ diff --git a/src/assets/images/backgrounds/background/bg_136.gif b/src/assets/images/backgrounds/background/bg_136.gif new file mode 100644 index 0000000..3d4d8f7 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_136.gif differ diff --git a/src/assets/images/backgrounds/background/bg_137.gif b/src/assets/images/backgrounds/background/bg_137.gif new file mode 100644 index 0000000..80cbbeb Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_137.gif differ diff --git a/src/assets/images/backgrounds/background/bg_138.gif b/src/assets/images/backgrounds/background/bg_138.gif new file mode 100644 index 0000000..0c7b67f Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_138.gif differ diff --git a/src/assets/images/backgrounds/background/bg_139.gif b/src/assets/images/backgrounds/background/bg_139.gif new file mode 100644 index 0000000..6c76b2f Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_139.gif differ diff --git a/src/assets/images/backgrounds/background/bg_14.png b/src/assets/images/backgrounds/background/bg_14.png new file mode 100644 index 0000000..5e8debc Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_14.png differ diff --git a/src/assets/images/backgrounds/background/bg_140.gif b/src/assets/images/backgrounds/background/bg_140.gif new file mode 100644 index 0000000..bce3709 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_140.gif differ diff --git a/src/assets/images/backgrounds/background/bg_141.gif b/src/assets/images/backgrounds/background/bg_141.gif new file mode 100644 index 0000000..05a7ad9 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_141.gif differ diff --git a/src/assets/images/backgrounds/background/bg_142.gif b/src/assets/images/backgrounds/background/bg_142.gif new file mode 100644 index 0000000..aeef99c Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_142.gif differ diff --git a/src/assets/images/backgrounds/background/bg_143.gif b/src/assets/images/backgrounds/background/bg_143.gif new file mode 100644 index 0000000..5c1e597 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_143.gif differ diff --git a/src/assets/images/backgrounds/background/bg_144.gif b/src/assets/images/backgrounds/background/bg_144.gif new file mode 100644 index 0000000..a563283 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_144.gif differ diff --git a/src/assets/images/backgrounds/background/bg_145.gif b/src/assets/images/backgrounds/background/bg_145.gif new file mode 100644 index 0000000..2b6e9c1 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_145.gif differ diff --git a/src/assets/images/backgrounds/background/bg_146.gif b/src/assets/images/backgrounds/background/bg_146.gif new file mode 100644 index 0000000..ccc0264 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_146.gif differ diff --git a/src/assets/images/backgrounds/background/bg_147.gif b/src/assets/images/backgrounds/background/bg_147.gif new file mode 100644 index 0000000..9ea76f7 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_147.gif differ diff --git a/src/assets/images/backgrounds/background/bg_148.gif b/src/assets/images/backgrounds/background/bg_148.gif new file mode 100644 index 0000000..2974315 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_148.gif differ diff --git a/src/assets/images/backgrounds/background/bg_149.gif b/src/assets/images/backgrounds/background/bg_149.gif new file mode 100644 index 0000000..7520899 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_149.gif differ diff --git a/src/assets/images/backgrounds/background/bg_15.png b/src/assets/images/backgrounds/background/bg_15.png new file mode 100644 index 0000000..a9ec769 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_15.png differ diff --git a/src/assets/images/backgrounds/background/bg_150.gif b/src/assets/images/backgrounds/background/bg_150.gif new file mode 100644 index 0000000..f0f50fa Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_150.gif differ diff --git a/src/assets/images/backgrounds/background/bg_151.gif b/src/assets/images/backgrounds/background/bg_151.gif new file mode 100644 index 0000000..77c150f Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_151.gif differ diff --git a/src/assets/images/backgrounds/background/bg_152.gif b/src/assets/images/backgrounds/background/bg_152.gif new file mode 100644 index 0000000..1ded113 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_152.gif differ diff --git a/src/assets/images/backgrounds/background/bg_153.gif b/src/assets/images/backgrounds/background/bg_153.gif new file mode 100644 index 0000000..19b9ecb Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_153.gif differ diff --git a/src/assets/images/backgrounds/background/bg_154.gif b/src/assets/images/backgrounds/background/bg_154.gif new file mode 100644 index 0000000..38338a0 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_154.gif differ diff --git a/src/assets/images/backgrounds/background/bg_155.gif b/src/assets/images/backgrounds/background/bg_155.gif new file mode 100644 index 0000000..8770d24 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_155.gif differ diff --git a/src/assets/images/backgrounds/background/bg_156.gif b/src/assets/images/backgrounds/background/bg_156.gif new file mode 100644 index 0000000..614d7b2 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_156.gif differ diff --git a/src/assets/images/backgrounds/background/bg_157.gif b/src/assets/images/backgrounds/background/bg_157.gif new file mode 100644 index 0000000..707a559 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_157.gif differ diff --git a/src/assets/images/backgrounds/background/bg_158.gif b/src/assets/images/backgrounds/background/bg_158.gif new file mode 100644 index 0000000..ea576ca Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_158.gif differ diff --git a/src/assets/images/backgrounds/background/bg_159.gif b/src/assets/images/backgrounds/background/bg_159.gif new file mode 100644 index 0000000..d7b28fb Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_159.gif differ diff --git a/src/assets/images/backgrounds/background/bg_16.png b/src/assets/images/backgrounds/background/bg_16.png new file mode 100644 index 0000000..0afb1ed Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_16.png differ diff --git a/src/assets/images/backgrounds/background/bg_160.gif b/src/assets/images/backgrounds/background/bg_160.gif new file mode 100644 index 0000000..d9824e8 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_160.gif differ diff --git a/src/assets/images/backgrounds/background/bg_161.gif b/src/assets/images/backgrounds/background/bg_161.gif new file mode 100644 index 0000000..91b9518 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_161.gif differ diff --git a/src/assets/images/backgrounds/background/bg_162.gif b/src/assets/images/backgrounds/background/bg_162.gif new file mode 100644 index 0000000..6029e0e Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_162.gif differ diff --git a/src/assets/images/backgrounds/background/bg_163.gif b/src/assets/images/backgrounds/background/bg_163.gif new file mode 100644 index 0000000..8c59781 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_163.gif differ diff --git a/src/assets/images/backgrounds/background/bg_164.gif b/src/assets/images/backgrounds/background/bg_164.gif new file mode 100644 index 0000000..b6804ab Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_164.gif differ diff --git a/src/assets/images/backgrounds/background/bg_165.gif b/src/assets/images/backgrounds/background/bg_165.gif new file mode 100644 index 0000000..d01da99 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_165.gif differ diff --git a/src/assets/images/backgrounds/background/bg_166.gif b/src/assets/images/backgrounds/background/bg_166.gif new file mode 100644 index 0000000..ce7f415 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_166.gif differ diff --git a/src/assets/images/backgrounds/background/bg_167.gif b/src/assets/images/backgrounds/background/bg_167.gif new file mode 100644 index 0000000..8068696 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_167.gif differ diff --git a/src/assets/images/backgrounds/background/bg_168.gif b/src/assets/images/backgrounds/background/bg_168.gif new file mode 100644 index 0000000..5de9226 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_168.gif differ diff --git a/src/assets/images/backgrounds/background/bg_169.gif b/src/assets/images/backgrounds/background/bg_169.gif new file mode 100644 index 0000000..f344b74 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_169.gif differ diff --git a/src/assets/images/backgrounds/background/bg_17.png b/src/assets/images/backgrounds/background/bg_17.png new file mode 100644 index 0000000..3d593e4 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_17.png differ diff --git a/src/assets/images/backgrounds/background/bg_170.png b/src/assets/images/backgrounds/background/bg_170.png new file mode 100644 index 0000000..1880697 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_170.png differ diff --git a/src/assets/images/backgrounds/background/bg_171.png b/src/assets/images/backgrounds/background/bg_171.png new file mode 100644 index 0000000..f1c1767 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_171.png differ diff --git a/src/assets/images/backgrounds/background/bg_172.png b/src/assets/images/backgrounds/background/bg_172.png new file mode 100644 index 0000000..6de0de0 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_172.png differ diff --git a/src/assets/images/backgrounds/background/bg_173.png b/src/assets/images/backgrounds/background/bg_173.png new file mode 100644 index 0000000..ee36cc7 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_173.png differ diff --git a/src/assets/images/backgrounds/background/bg_174.png b/src/assets/images/backgrounds/background/bg_174.png new file mode 100644 index 0000000..920270b Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_174.png differ diff --git a/src/assets/images/backgrounds/background/bg_175.png b/src/assets/images/backgrounds/background/bg_175.png new file mode 100644 index 0000000..5f2305c Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_175.png differ diff --git a/src/assets/images/backgrounds/background/bg_176.png b/src/assets/images/backgrounds/background/bg_176.png new file mode 100644 index 0000000..f66effc Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_176.png differ diff --git a/src/assets/images/backgrounds/background/bg_177.gif b/src/assets/images/backgrounds/background/bg_177.gif new file mode 100644 index 0000000..7801fe0 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_177.gif differ diff --git a/src/assets/images/backgrounds/background/bg_178.png b/src/assets/images/backgrounds/background/bg_178.png new file mode 100644 index 0000000..287d1ed Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_178.png differ diff --git a/src/assets/images/backgrounds/background/bg_179.png b/src/assets/images/backgrounds/background/bg_179.png new file mode 100644 index 0000000..39266b3 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_179.png differ diff --git a/src/assets/images/backgrounds/background/bg_18.png b/src/assets/images/backgrounds/background/bg_18.png new file mode 100644 index 0000000..81bab28 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_18.png differ diff --git a/src/assets/images/backgrounds/background/bg_180.png b/src/assets/images/backgrounds/background/bg_180.png new file mode 100644 index 0000000..4e1bb7a Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_180.png differ diff --git a/src/assets/images/backgrounds/background/bg_181.png b/src/assets/images/backgrounds/background/bg_181.png new file mode 100644 index 0000000..62c6dd2 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_181.png differ diff --git a/src/assets/images/backgrounds/background/bg_182.png b/src/assets/images/backgrounds/background/bg_182.png new file mode 100644 index 0000000..81613a1 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_182.png differ diff --git a/src/assets/images/backgrounds/background/bg_183.png b/src/assets/images/backgrounds/background/bg_183.png new file mode 100644 index 0000000..1d839eb Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_183.png differ diff --git a/src/assets/images/backgrounds/background/bg_184.png b/src/assets/images/backgrounds/background/bg_184.png new file mode 100644 index 0000000..dd53887 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_184.png differ diff --git a/src/assets/images/backgrounds/background/bg_185.png b/src/assets/images/backgrounds/background/bg_185.png new file mode 100644 index 0000000..669819e Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_185.png differ diff --git a/src/assets/images/backgrounds/background/bg_186.png b/src/assets/images/backgrounds/background/bg_186.png new file mode 100644 index 0000000..82709ad Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_186.png differ diff --git a/src/assets/images/backgrounds/background/bg_187.gif b/src/assets/images/backgrounds/background/bg_187.gif new file mode 100644 index 0000000..f782ce7 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_187.gif differ diff --git a/src/assets/images/backgrounds/background/bg_19.png b/src/assets/images/backgrounds/background/bg_19.png new file mode 100644 index 0000000..10bb78c Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_19.png differ diff --git a/src/assets/images/backgrounds/background/bg_2.png b/src/assets/images/backgrounds/background/bg_2.png new file mode 100644 index 0000000..5b2fc5a Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_2.png differ diff --git a/src/assets/images/backgrounds/background/bg_20.png b/src/assets/images/backgrounds/background/bg_20.png new file mode 100644 index 0000000..a60ef76 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_20.png differ diff --git a/src/assets/images/backgrounds/background/bg_21.png b/src/assets/images/backgrounds/background/bg_21.png new file mode 100644 index 0000000..8029ae0 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_21.png differ diff --git a/src/assets/images/backgrounds/background/bg_22.png b/src/assets/images/backgrounds/background/bg_22.png new file mode 100644 index 0000000..07a1915 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_22.png differ diff --git a/src/assets/images/backgrounds/background/bg_23.png b/src/assets/images/backgrounds/background/bg_23.png new file mode 100644 index 0000000..86dbad3 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_23.png differ diff --git a/src/assets/images/backgrounds/background/bg_24.png b/src/assets/images/backgrounds/background/bg_24.png new file mode 100644 index 0000000..79278d1 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_24.png differ diff --git a/src/assets/images/backgrounds/background/bg_25.png b/src/assets/images/backgrounds/background/bg_25.png new file mode 100644 index 0000000..d8025a6 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_25.png differ diff --git a/src/assets/images/backgrounds/background/bg_26.png b/src/assets/images/backgrounds/background/bg_26.png new file mode 100644 index 0000000..60e0c37 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_26.png differ diff --git a/src/assets/images/backgrounds/background/bg_27.png b/src/assets/images/backgrounds/background/bg_27.png new file mode 100644 index 0000000..8e942a3 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_27.png differ diff --git a/src/assets/images/backgrounds/background/bg_28.png b/src/assets/images/backgrounds/background/bg_28.png new file mode 100644 index 0000000..689c2b9 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_28.png differ diff --git a/src/assets/images/backgrounds/background/bg_29.png b/src/assets/images/backgrounds/background/bg_29.png new file mode 100644 index 0000000..3260464 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_29.png differ diff --git a/src/assets/images/backgrounds/background/bg_3.png b/src/assets/images/backgrounds/background/bg_3.png new file mode 100644 index 0000000..41034f7 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_3.png differ diff --git a/src/assets/images/backgrounds/background/bg_30.png b/src/assets/images/backgrounds/background/bg_30.png new file mode 100644 index 0000000..3db687a Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_30.png differ diff --git a/src/assets/images/backgrounds/background/bg_31.png b/src/assets/images/backgrounds/background/bg_31.png new file mode 100644 index 0000000..348b000 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_31.png differ diff --git a/src/assets/images/backgrounds/background/bg_32.png b/src/assets/images/backgrounds/background/bg_32.png new file mode 100644 index 0000000..7b11ec3 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_32.png differ diff --git a/src/assets/images/backgrounds/background/bg_33.png b/src/assets/images/backgrounds/background/bg_33.png new file mode 100644 index 0000000..a5ba792 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_33.png differ diff --git a/src/assets/images/backgrounds/background/bg_34.png b/src/assets/images/backgrounds/background/bg_34.png new file mode 100644 index 0000000..c022119 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_34.png differ diff --git a/src/assets/images/backgrounds/background/bg_35.png b/src/assets/images/backgrounds/background/bg_35.png new file mode 100644 index 0000000..bacd72f Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_35.png differ diff --git a/src/assets/images/backgrounds/background/bg_36.gif b/src/assets/images/backgrounds/background/bg_36.gif new file mode 100644 index 0000000..b30f769 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_36.gif differ diff --git a/src/assets/images/backgrounds/background/bg_36.png b/src/assets/images/backgrounds/background/bg_36.png new file mode 100644 index 0000000..572e661 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_36.png differ diff --git a/src/assets/images/backgrounds/background/bg_37.png b/src/assets/images/backgrounds/background/bg_37.png new file mode 100644 index 0000000..572e661 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_37.png differ diff --git a/src/assets/images/backgrounds/background/bg_38.png b/src/assets/images/backgrounds/background/bg_38.png new file mode 100644 index 0000000..e47c7ad Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_38.png differ diff --git a/src/assets/images/backgrounds/background/bg_39.png b/src/assets/images/backgrounds/background/bg_39.png new file mode 100644 index 0000000..a4f1999 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_39.png differ diff --git a/src/assets/images/backgrounds/background/bg_4.png b/src/assets/images/backgrounds/background/bg_4.png new file mode 100644 index 0000000..b150551 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_4.png differ diff --git a/src/assets/images/backgrounds/background/bg_40.png b/src/assets/images/backgrounds/background/bg_40.png new file mode 100644 index 0000000..1af2c2e Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_40.png differ diff --git a/src/assets/images/backgrounds/background/bg_41.png b/src/assets/images/backgrounds/background/bg_41.png new file mode 100644 index 0000000..4201d9e Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_41.png differ diff --git a/src/assets/images/backgrounds/background/bg_42.png b/src/assets/images/backgrounds/background/bg_42.png new file mode 100644 index 0000000..61057a3 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_42.png differ diff --git a/src/assets/images/backgrounds/background/bg_43.png b/src/assets/images/backgrounds/background/bg_43.png new file mode 100644 index 0000000..5178123 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_43.png differ diff --git a/src/assets/images/backgrounds/background/bg_44.png b/src/assets/images/backgrounds/background/bg_44.png new file mode 100644 index 0000000..4f282a3 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_44.png differ diff --git a/src/assets/images/backgrounds/background/bg_45.png b/src/assets/images/backgrounds/background/bg_45.png new file mode 100644 index 0000000..0cfec58 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_45.png differ diff --git a/src/assets/images/backgrounds/background/bg_46.png b/src/assets/images/backgrounds/background/bg_46.png new file mode 100644 index 0000000..0b37070 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_46.png differ diff --git a/src/assets/images/backgrounds/background/bg_47.png b/src/assets/images/backgrounds/background/bg_47.png new file mode 100644 index 0000000..c8192c6 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_47.png differ diff --git a/src/assets/images/backgrounds/background/bg_48.png b/src/assets/images/backgrounds/background/bg_48.png new file mode 100644 index 0000000..167ff55 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_48.png differ diff --git a/src/assets/images/backgrounds/background/bg_49.png b/src/assets/images/backgrounds/background/bg_49.png new file mode 100644 index 0000000..ec8e60f Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_49.png differ diff --git a/src/assets/images/backgrounds/background/bg_5.png b/src/assets/images/backgrounds/background/bg_5.png new file mode 100644 index 0000000..f3a86fe Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_5.png differ diff --git a/src/assets/images/backgrounds/background/bg_50.gif b/src/assets/images/backgrounds/background/bg_50.gif new file mode 100644 index 0000000..f1e97a4 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_50.gif differ diff --git a/src/assets/images/backgrounds/background/bg_50.png b/src/assets/images/backgrounds/background/bg_50.png new file mode 100644 index 0000000..275494e Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_50.png differ diff --git a/src/assets/images/backgrounds/background/bg_51.gif b/src/assets/images/backgrounds/background/bg_51.gif new file mode 100644 index 0000000..f1e97a4 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_51.gif differ diff --git a/src/assets/images/backgrounds/background/bg_52.gif b/src/assets/images/backgrounds/background/bg_52.gif new file mode 100644 index 0000000..0f0c071 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_52.gif differ diff --git a/src/assets/images/backgrounds/background/bg_52.png b/src/assets/images/backgrounds/background/bg_52.png new file mode 100644 index 0000000..f0fadde Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_52.png differ diff --git a/src/assets/images/backgrounds/background/bg_53.gif b/src/assets/images/backgrounds/background/bg_53.gif new file mode 100644 index 0000000..fe88155 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_53.gif differ diff --git a/src/assets/images/backgrounds/background/bg_53.png b/src/assets/images/backgrounds/background/bg_53.png new file mode 100644 index 0000000..629010b Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_53.png differ diff --git a/src/assets/images/backgrounds/background/bg_54.gif b/src/assets/images/backgrounds/background/bg_54.gif new file mode 100644 index 0000000..2dab5fe Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_54.gif differ diff --git a/src/assets/images/backgrounds/background/bg_55.gif b/src/assets/images/backgrounds/background/bg_55.gif new file mode 100644 index 0000000..a7688a1 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_55.gif differ diff --git a/src/assets/images/backgrounds/background/bg_56.gif b/src/assets/images/backgrounds/background/bg_56.gif new file mode 100644 index 0000000..c0fc06d Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_56.gif differ diff --git a/src/assets/images/backgrounds/background/bg_57.gif b/src/assets/images/backgrounds/background/bg_57.gif new file mode 100644 index 0000000..2db068f Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_57.gif differ diff --git a/src/assets/images/backgrounds/background/bg_58.gif b/src/assets/images/backgrounds/background/bg_58.gif new file mode 100644 index 0000000..d06f671 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_58.gif differ diff --git a/src/assets/images/backgrounds/background/bg_59.gif b/src/assets/images/backgrounds/background/bg_59.gif new file mode 100644 index 0000000..a103717 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_59.gif differ diff --git a/src/assets/images/backgrounds/background/bg_6.png b/src/assets/images/backgrounds/background/bg_6.png new file mode 100644 index 0000000..79ecaab Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_6.png differ diff --git a/src/assets/images/backgrounds/background/bg_60.gif b/src/assets/images/backgrounds/background/bg_60.gif new file mode 100644 index 0000000..9c7ad38 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_60.gif differ diff --git a/src/assets/images/backgrounds/background/bg_61.gif b/src/assets/images/backgrounds/background/bg_61.gif new file mode 100644 index 0000000..aa0d872 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_61.gif differ diff --git a/src/assets/images/backgrounds/background/bg_62.gif b/src/assets/images/backgrounds/background/bg_62.gif new file mode 100644 index 0000000..b02708e Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_62.gif differ diff --git a/src/assets/images/backgrounds/background/bg_63.gif b/src/assets/images/backgrounds/background/bg_63.gif new file mode 100644 index 0000000..ced6c2b Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_63.gif differ diff --git a/src/assets/images/backgrounds/background/bg_64.gif b/src/assets/images/backgrounds/background/bg_64.gif new file mode 100644 index 0000000..c051492 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_64.gif differ diff --git a/src/assets/images/backgrounds/background/bg_65.gif b/src/assets/images/backgrounds/background/bg_65.gif new file mode 100644 index 0000000..b48ed88 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_65.gif differ diff --git a/src/assets/images/backgrounds/background/bg_66.gif b/src/assets/images/backgrounds/background/bg_66.gif new file mode 100644 index 0000000..45860c1 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_66.gif differ diff --git a/src/assets/images/backgrounds/background/bg_67.gif b/src/assets/images/backgrounds/background/bg_67.gif new file mode 100644 index 0000000..613512a Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_67.gif differ diff --git a/src/assets/images/backgrounds/background/bg_68.gif b/src/assets/images/backgrounds/background/bg_68.gif new file mode 100644 index 0000000..221547a Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_68.gif differ diff --git a/src/assets/images/backgrounds/background/bg_69.gif b/src/assets/images/backgrounds/background/bg_69.gif new file mode 100644 index 0000000..e23d282 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_69.gif differ diff --git a/src/assets/images/backgrounds/background/bg_7.png b/src/assets/images/backgrounds/background/bg_7.png new file mode 100644 index 0000000..7e61d10 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_7.png differ diff --git a/src/assets/images/backgrounds/background/bg_70.gif b/src/assets/images/backgrounds/background/bg_70.gif new file mode 100644 index 0000000..8f626fa Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_70.gif differ diff --git a/src/assets/images/backgrounds/background/bg_71.gif b/src/assets/images/backgrounds/background/bg_71.gif new file mode 100644 index 0000000..083417c Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_71.gif differ diff --git a/src/assets/images/backgrounds/background/bg_72.gif b/src/assets/images/backgrounds/background/bg_72.gif new file mode 100644 index 0000000..af10f9b Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_72.gif differ diff --git a/src/assets/images/backgrounds/background/bg_73.gif b/src/assets/images/backgrounds/background/bg_73.gif new file mode 100644 index 0000000..9293170 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_73.gif differ diff --git a/src/assets/images/backgrounds/background/bg_74.gif b/src/assets/images/backgrounds/background/bg_74.gif new file mode 100644 index 0000000..a3c49ad Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_74.gif differ diff --git a/src/assets/images/backgrounds/background/bg_75.gif b/src/assets/images/backgrounds/background/bg_75.gif new file mode 100644 index 0000000..82709ad Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_75.gif differ diff --git a/src/assets/images/backgrounds/background/bg_76.gif b/src/assets/images/backgrounds/background/bg_76.gif new file mode 100644 index 0000000..bd9b9cd Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_76.gif differ diff --git a/src/assets/images/backgrounds/background/bg_77.gif b/src/assets/images/backgrounds/background/bg_77.gif new file mode 100644 index 0000000..14b2dc0 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_77.gif differ diff --git a/src/assets/images/backgrounds/background/bg_78.gif b/src/assets/images/backgrounds/background/bg_78.gif new file mode 100644 index 0000000..66288b3 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_78.gif differ diff --git a/src/assets/images/backgrounds/background/bg_79.gif b/src/assets/images/backgrounds/background/bg_79.gif new file mode 100644 index 0000000..82709ad Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_79.gif differ diff --git a/src/assets/images/backgrounds/background/bg_8.png b/src/assets/images/backgrounds/background/bg_8.png new file mode 100644 index 0000000..89a49bb Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_8.png differ diff --git a/src/assets/images/backgrounds/background/bg_80.gif b/src/assets/images/backgrounds/background/bg_80.gif new file mode 100644 index 0000000..c1d6fa3 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_80.gif differ diff --git a/src/assets/images/backgrounds/background/bg_81.gif b/src/assets/images/backgrounds/background/bg_81.gif new file mode 100644 index 0000000..899e77c Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_81.gif differ diff --git a/src/assets/images/backgrounds/background/bg_82.gif b/src/assets/images/backgrounds/background/bg_82.gif new file mode 100644 index 0000000..8330696 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_82.gif differ diff --git a/src/assets/images/backgrounds/background/bg_83.gif b/src/assets/images/backgrounds/background/bg_83.gif new file mode 100644 index 0000000..2cf2355 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_83.gif differ diff --git a/src/assets/images/backgrounds/background/bg_84.gif b/src/assets/images/backgrounds/background/bg_84.gif new file mode 100644 index 0000000..27282b7 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_84.gif differ diff --git a/src/assets/images/backgrounds/background/bg_85.gif b/src/assets/images/backgrounds/background/bg_85.gif new file mode 100644 index 0000000..7862b23 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_85.gif differ diff --git a/src/assets/images/backgrounds/background/bg_86.png b/src/assets/images/backgrounds/background/bg_86.png new file mode 100644 index 0000000..16ce06d Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_86.png differ diff --git a/src/assets/images/backgrounds/background/bg_87.gif b/src/assets/images/backgrounds/background/bg_87.gif new file mode 100644 index 0000000..3833cae Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_87.gif differ diff --git a/src/assets/images/backgrounds/background/bg_88.gif b/src/assets/images/backgrounds/background/bg_88.gif new file mode 100644 index 0000000..ac1004b Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_88.gif differ diff --git a/src/assets/images/backgrounds/background/bg_89.gif b/src/assets/images/backgrounds/background/bg_89.gif new file mode 100644 index 0000000..071b4ac Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_89.gif differ diff --git a/src/assets/images/backgrounds/background/bg_9.png b/src/assets/images/backgrounds/background/bg_9.png new file mode 100644 index 0000000..f48f4a4 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_9.png differ diff --git a/src/assets/images/backgrounds/background/bg_90.gif b/src/assets/images/backgrounds/background/bg_90.gif new file mode 100644 index 0000000..5babf74 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_90.gif differ diff --git a/src/assets/images/backgrounds/background/bg_91.gif b/src/assets/images/backgrounds/background/bg_91.gif new file mode 100644 index 0000000..cae21c5 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_91.gif differ diff --git a/src/assets/images/backgrounds/background/bg_92.gif b/src/assets/images/backgrounds/background/bg_92.gif new file mode 100644 index 0000000..1ca656e Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_92.gif differ diff --git a/src/assets/images/backgrounds/background/bg_93.gif b/src/assets/images/backgrounds/background/bg_93.gif new file mode 100644 index 0000000..ee78487 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_93.gif differ diff --git a/src/assets/images/backgrounds/background/bg_94.gif b/src/assets/images/backgrounds/background/bg_94.gif new file mode 100644 index 0000000..f2ebe51 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_94.gif differ diff --git a/src/assets/images/backgrounds/background/bg_95.gif b/src/assets/images/backgrounds/background/bg_95.gif new file mode 100644 index 0000000..568187f Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_95.gif differ diff --git a/src/assets/images/backgrounds/background/bg_96.gif b/src/assets/images/backgrounds/background/bg_96.gif new file mode 100644 index 0000000..8dfd887 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_96.gif differ diff --git a/src/assets/images/backgrounds/background/bg_97.gif b/src/assets/images/backgrounds/background/bg_97.gif new file mode 100644 index 0000000..62b8504 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_97.gif differ diff --git a/src/assets/images/backgrounds/background/bg_98.gif b/src/assets/images/backgrounds/background/bg_98.gif new file mode 100644 index 0000000..7f5ab86 Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_98.gif differ diff --git a/src/assets/images/backgrounds/background/bg_99.gif b/src/assets/images/backgrounds/background/bg_99.gif new file mode 100644 index 0000000..327a73b Binary files /dev/null and b/src/assets/images/backgrounds/background/bg_99.gif differ diff --git a/src/assets/images/backgrounds/new/bg_0.png b/src/assets/images/backgrounds/new/bg_0.png new file mode 100644 index 0000000..dc80b28 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_0.png differ diff --git a/src/assets/images/backgrounds/new/bg_1.gif b/src/assets/images/backgrounds/new/bg_1.gif new file mode 100644 index 0000000..5a07f4f Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_1.gif differ diff --git a/src/assets/images/backgrounds/new/bg_10.png b/src/assets/images/backgrounds/new/bg_10.png new file mode 100644 index 0000000..80764d4 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_10.png differ diff --git a/src/assets/images/backgrounds/new/bg_100.gif b/src/assets/images/backgrounds/new/bg_100.gif new file mode 100644 index 0000000..8ad3ab7 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_100.gif differ diff --git a/src/assets/images/backgrounds/new/bg_102.gif b/src/assets/images/backgrounds/new/bg_102.gif new file mode 100644 index 0000000..e5ee68a Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_102.gif differ diff --git a/src/assets/images/backgrounds/new/bg_103.gif b/src/assets/images/backgrounds/new/bg_103.gif new file mode 100644 index 0000000..d0645c1 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_103.gif differ diff --git a/src/assets/images/backgrounds/new/bg_104.gif b/src/assets/images/backgrounds/new/bg_104.gif new file mode 100644 index 0000000..8198d2f Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_104.gif differ diff --git a/src/assets/images/backgrounds/new/bg_105.gif b/src/assets/images/backgrounds/new/bg_105.gif new file mode 100644 index 0000000..508d38d Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_105.gif differ diff --git a/src/assets/images/backgrounds/new/bg_106.gif b/src/assets/images/backgrounds/new/bg_106.gif new file mode 100644 index 0000000..1a2f8ab Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_106.gif differ diff --git a/src/assets/images/backgrounds/new/bg_107.gif b/src/assets/images/backgrounds/new/bg_107.gif new file mode 100644 index 0000000..2f24a27 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_107.gif differ diff --git a/src/assets/images/backgrounds/new/bg_108.gif b/src/assets/images/backgrounds/new/bg_108.gif new file mode 100644 index 0000000..b996f34 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_108.gif differ diff --git a/src/assets/images/backgrounds/new/bg_109.gif b/src/assets/images/backgrounds/new/bg_109.gif new file mode 100644 index 0000000..23db9e6 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_109.gif differ diff --git a/src/assets/images/backgrounds/new/bg_11.png b/src/assets/images/backgrounds/new/bg_11.png new file mode 100644 index 0000000..ebcc2a7 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_11.png differ diff --git a/src/assets/images/backgrounds/new/bg_110.gif b/src/assets/images/backgrounds/new/bg_110.gif new file mode 100644 index 0000000..fed52bb Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_110.gif differ diff --git a/src/assets/images/backgrounds/new/bg_111.gif b/src/assets/images/backgrounds/new/bg_111.gif new file mode 100644 index 0000000..fa94d51 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_111.gif differ diff --git a/src/assets/images/backgrounds/new/bg_112.gif b/src/assets/images/backgrounds/new/bg_112.gif new file mode 100644 index 0000000..974a1ff Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_112.gif differ diff --git a/src/assets/images/backgrounds/new/bg_113.gif b/src/assets/images/backgrounds/new/bg_113.gif new file mode 100644 index 0000000..f63d029 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_113.gif differ diff --git a/src/assets/images/backgrounds/new/bg_114.gif b/src/assets/images/backgrounds/new/bg_114.gif new file mode 100644 index 0000000..024c9b4 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_114.gif differ diff --git a/src/assets/images/backgrounds/new/bg_115.gif b/src/assets/images/backgrounds/new/bg_115.gif new file mode 100644 index 0000000..6f0cb29 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_115.gif differ diff --git a/src/assets/images/backgrounds/new/bg_116.gif b/src/assets/images/backgrounds/new/bg_116.gif new file mode 100644 index 0000000..02b464e Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_116.gif differ diff --git a/src/assets/images/backgrounds/new/bg_117.gif b/src/assets/images/backgrounds/new/bg_117.gif new file mode 100644 index 0000000..2c6a4a6 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_117.gif differ diff --git a/src/assets/images/backgrounds/new/bg_118.gif b/src/assets/images/backgrounds/new/bg_118.gif new file mode 100644 index 0000000..5b7d61f Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_118.gif differ diff --git a/src/assets/images/backgrounds/new/bg_119.gif b/src/assets/images/backgrounds/new/bg_119.gif new file mode 100644 index 0000000..52c4a2c Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_119.gif differ diff --git a/src/assets/images/backgrounds/new/bg_12.png b/src/assets/images/backgrounds/new/bg_12.png new file mode 100644 index 0000000..6876efd Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_12.png differ diff --git a/src/assets/images/backgrounds/new/bg_120.gif b/src/assets/images/backgrounds/new/bg_120.gif new file mode 100644 index 0000000..4d2a314 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_120.gif differ diff --git a/src/assets/images/backgrounds/new/bg_121.gif b/src/assets/images/backgrounds/new/bg_121.gif new file mode 100644 index 0000000..f5bc596 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_121.gif differ diff --git a/src/assets/images/backgrounds/new/bg_122.gif b/src/assets/images/backgrounds/new/bg_122.gif new file mode 100644 index 0000000..a625ecc Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_122.gif differ diff --git a/src/assets/images/backgrounds/new/bg_123.gif b/src/assets/images/backgrounds/new/bg_123.gif new file mode 100644 index 0000000..adda1dc Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_123.gif differ diff --git a/src/assets/images/backgrounds/new/bg_124.gif b/src/assets/images/backgrounds/new/bg_124.gif new file mode 100644 index 0000000..551af32 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_124.gif differ diff --git a/src/assets/images/backgrounds/new/bg_125.gif b/src/assets/images/backgrounds/new/bg_125.gif new file mode 100644 index 0000000..629010b Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_125.gif differ diff --git a/src/assets/images/backgrounds/new/bg_126.gif b/src/assets/images/backgrounds/new/bg_126.gif new file mode 100644 index 0000000..c799c79 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_126.gif differ diff --git a/src/assets/images/backgrounds/new/bg_127.gif b/src/assets/images/backgrounds/new/bg_127.gif new file mode 100644 index 0000000..885ce78 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_127.gif differ diff --git a/src/assets/images/backgrounds/new/bg_128.gif b/src/assets/images/backgrounds/new/bg_128.gif new file mode 100644 index 0000000..7603a4a Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_128.gif differ diff --git a/src/assets/images/backgrounds/new/bg_129.gif b/src/assets/images/backgrounds/new/bg_129.gif new file mode 100644 index 0000000..cd7c75c Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_129.gif differ diff --git a/src/assets/images/backgrounds/new/bg_13.png b/src/assets/images/backgrounds/new/bg_13.png new file mode 100644 index 0000000..d57057e Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_13.png differ diff --git a/src/assets/images/backgrounds/new/bg_130.gif b/src/assets/images/backgrounds/new/bg_130.gif new file mode 100644 index 0000000..54e01ce Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_130.gif differ diff --git a/src/assets/images/backgrounds/new/bg_131.gif b/src/assets/images/backgrounds/new/bg_131.gif new file mode 100644 index 0000000..19102c8 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_131.gif differ diff --git a/src/assets/images/backgrounds/new/bg_132.gif b/src/assets/images/backgrounds/new/bg_132.gif new file mode 100644 index 0000000..492c05d Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_132.gif differ diff --git a/src/assets/images/backgrounds/new/bg_133.gif b/src/assets/images/backgrounds/new/bg_133.gif new file mode 100644 index 0000000..5a03310 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_133.gif differ diff --git a/src/assets/images/backgrounds/new/bg_134.gif b/src/assets/images/backgrounds/new/bg_134.gif new file mode 100644 index 0000000..13a0711 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_134.gif differ diff --git a/src/assets/images/backgrounds/new/bg_135.gif b/src/assets/images/backgrounds/new/bg_135.gif new file mode 100644 index 0000000..2f24a27 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_135.gif differ diff --git a/src/assets/images/backgrounds/new/bg_136.gif b/src/assets/images/backgrounds/new/bg_136.gif new file mode 100644 index 0000000..3d4d8f7 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_136.gif differ diff --git a/src/assets/images/backgrounds/new/bg_137.gif b/src/assets/images/backgrounds/new/bg_137.gif new file mode 100644 index 0000000..80cbbeb Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_137.gif differ diff --git a/src/assets/images/backgrounds/new/bg_138.gif b/src/assets/images/backgrounds/new/bg_138.gif new file mode 100644 index 0000000..0c7b67f Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_138.gif differ diff --git a/src/assets/images/backgrounds/new/bg_139.gif b/src/assets/images/backgrounds/new/bg_139.gif new file mode 100644 index 0000000..6c76b2f Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_139.gif differ diff --git a/src/assets/images/backgrounds/new/bg_14.png b/src/assets/images/backgrounds/new/bg_14.png new file mode 100644 index 0000000..5e8debc Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_14.png differ diff --git a/src/assets/images/backgrounds/new/bg_140.gif b/src/assets/images/backgrounds/new/bg_140.gif new file mode 100644 index 0000000..bce3709 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_140.gif differ diff --git a/src/assets/images/backgrounds/new/bg_141.gif b/src/assets/images/backgrounds/new/bg_141.gif new file mode 100644 index 0000000..05a7ad9 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_141.gif differ diff --git a/src/assets/images/backgrounds/new/bg_142.gif b/src/assets/images/backgrounds/new/bg_142.gif new file mode 100644 index 0000000..aeef99c Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_142.gif differ diff --git a/src/assets/images/backgrounds/new/bg_143.gif b/src/assets/images/backgrounds/new/bg_143.gif new file mode 100644 index 0000000..5c1e597 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_143.gif differ diff --git a/src/assets/images/backgrounds/new/bg_144.gif b/src/assets/images/backgrounds/new/bg_144.gif new file mode 100644 index 0000000..a563283 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_144.gif differ diff --git a/src/assets/images/backgrounds/new/bg_145.gif b/src/assets/images/backgrounds/new/bg_145.gif new file mode 100644 index 0000000..2b6e9c1 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_145.gif differ diff --git a/src/assets/images/backgrounds/new/bg_146.gif b/src/assets/images/backgrounds/new/bg_146.gif new file mode 100644 index 0000000..ccc0264 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_146.gif differ diff --git a/src/assets/images/backgrounds/new/bg_147.gif b/src/assets/images/backgrounds/new/bg_147.gif new file mode 100644 index 0000000..9ea76f7 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_147.gif differ diff --git a/src/assets/images/backgrounds/new/bg_148.gif b/src/assets/images/backgrounds/new/bg_148.gif new file mode 100644 index 0000000..2974315 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_148.gif differ diff --git a/src/assets/images/backgrounds/new/bg_149.gif b/src/assets/images/backgrounds/new/bg_149.gif new file mode 100644 index 0000000..7520899 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_149.gif differ diff --git a/src/assets/images/backgrounds/new/bg_15.png b/src/assets/images/backgrounds/new/bg_15.png new file mode 100644 index 0000000..a9ec769 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_15.png differ diff --git a/src/assets/images/backgrounds/new/bg_150.gif b/src/assets/images/backgrounds/new/bg_150.gif new file mode 100644 index 0000000..f0f50fa Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_150.gif differ diff --git a/src/assets/images/backgrounds/new/bg_151.gif b/src/assets/images/backgrounds/new/bg_151.gif new file mode 100644 index 0000000..77c150f Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_151.gif differ diff --git a/src/assets/images/backgrounds/new/bg_152.gif b/src/assets/images/backgrounds/new/bg_152.gif new file mode 100644 index 0000000..1ded113 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_152.gif differ diff --git a/src/assets/images/backgrounds/new/bg_153.gif b/src/assets/images/backgrounds/new/bg_153.gif new file mode 100644 index 0000000..19b9ecb Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_153.gif differ diff --git a/src/assets/images/backgrounds/new/bg_154.gif b/src/assets/images/backgrounds/new/bg_154.gif new file mode 100644 index 0000000..38338a0 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_154.gif differ diff --git a/src/assets/images/backgrounds/new/bg_155.gif b/src/assets/images/backgrounds/new/bg_155.gif new file mode 100644 index 0000000..8770d24 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_155.gif differ diff --git a/src/assets/images/backgrounds/new/bg_156.gif b/src/assets/images/backgrounds/new/bg_156.gif new file mode 100644 index 0000000..614d7b2 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_156.gif differ diff --git a/src/assets/images/backgrounds/new/bg_157.gif b/src/assets/images/backgrounds/new/bg_157.gif new file mode 100644 index 0000000..707a559 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_157.gif differ diff --git a/src/assets/images/backgrounds/new/bg_158.gif b/src/assets/images/backgrounds/new/bg_158.gif new file mode 100644 index 0000000..ea576ca Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_158.gif differ diff --git a/src/assets/images/backgrounds/new/bg_159.gif b/src/assets/images/backgrounds/new/bg_159.gif new file mode 100644 index 0000000..d7b28fb Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_159.gif differ diff --git a/src/assets/images/backgrounds/new/bg_16.png b/src/assets/images/backgrounds/new/bg_16.png new file mode 100644 index 0000000..0afb1ed Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_16.png differ diff --git a/src/assets/images/backgrounds/new/bg_160.gif b/src/assets/images/backgrounds/new/bg_160.gif new file mode 100644 index 0000000..d9824e8 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_160.gif differ diff --git a/src/assets/images/backgrounds/new/bg_161.gif b/src/assets/images/backgrounds/new/bg_161.gif new file mode 100644 index 0000000..91b9518 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_161.gif differ diff --git a/src/assets/images/backgrounds/new/bg_162.gif b/src/assets/images/backgrounds/new/bg_162.gif new file mode 100644 index 0000000..6029e0e Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_162.gif differ diff --git a/src/assets/images/backgrounds/new/bg_163.gif b/src/assets/images/backgrounds/new/bg_163.gif new file mode 100644 index 0000000..8c59781 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_163.gif differ diff --git a/src/assets/images/backgrounds/new/bg_164.gif b/src/assets/images/backgrounds/new/bg_164.gif new file mode 100644 index 0000000..b6804ab Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_164.gif differ diff --git a/src/assets/images/backgrounds/new/bg_165.gif b/src/assets/images/backgrounds/new/bg_165.gif new file mode 100644 index 0000000..d01da99 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_165.gif differ diff --git a/src/assets/images/backgrounds/new/bg_166.gif b/src/assets/images/backgrounds/new/bg_166.gif new file mode 100644 index 0000000..ce7f415 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_166.gif differ diff --git a/src/assets/images/backgrounds/new/bg_167.gif b/src/assets/images/backgrounds/new/bg_167.gif new file mode 100644 index 0000000..8068696 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_167.gif differ diff --git a/src/assets/images/backgrounds/new/bg_168.gif b/src/assets/images/backgrounds/new/bg_168.gif new file mode 100644 index 0000000..5de9226 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_168.gif differ diff --git a/src/assets/images/backgrounds/new/bg_169.gif b/src/assets/images/backgrounds/new/bg_169.gif new file mode 100644 index 0000000..f344b74 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_169.gif differ diff --git a/src/assets/images/backgrounds/new/bg_17.png b/src/assets/images/backgrounds/new/bg_17.png new file mode 100644 index 0000000..3d593e4 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_17.png differ diff --git a/src/assets/images/backgrounds/new/bg_170.png b/src/assets/images/backgrounds/new/bg_170.png new file mode 100644 index 0000000..1880697 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_170.png differ diff --git a/src/assets/images/backgrounds/new/bg_171.png b/src/assets/images/backgrounds/new/bg_171.png new file mode 100644 index 0000000..f1c1767 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_171.png differ diff --git a/src/assets/images/backgrounds/new/bg_172.png b/src/assets/images/backgrounds/new/bg_172.png new file mode 100644 index 0000000..6de0de0 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_172.png differ diff --git a/src/assets/images/backgrounds/new/bg_173.png b/src/assets/images/backgrounds/new/bg_173.png new file mode 100644 index 0000000..ee36cc7 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_173.png differ diff --git a/src/assets/images/backgrounds/new/bg_174.png b/src/assets/images/backgrounds/new/bg_174.png new file mode 100644 index 0000000..920270b Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_174.png differ diff --git a/src/assets/images/backgrounds/new/bg_175.png b/src/assets/images/backgrounds/new/bg_175.png new file mode 100644 index 0000000..5f2305c Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_175.png differ diff --git a/src/assets/images/backgrounds/new/bg_176.png b/src/assets/images/backgrounds/new/bg_176.png new file mode 100644 index 0000000..f66effc Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_176.png differ diff --git a/src/assets/images/backgrounds/new/bg_178.png b/src/assets/images/backgrounds/new/bg_178.png new file mode 100644 index 0000000..287d1ed Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_178.png differ diff --git a/src/assets/images/backgrounds/new/bg_179.png b/src/assets/images/backgrounds/new/bg_179.png new file mode 100644 index 0000000..39266b3 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_179.png differ diff --git a/src/assets/images/backgrounds/new/bg_18.png b/src/assets/images/backgrounds/new/bg_18.png new file mode 100644 index 0000000..81bab28 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_18.png differ diff --git a/src/assets/images/backgrounds/new/bg_180.png b/src/assets/images/backgrounds/new/bg_180.png new file mode 100644 index 0000000..4e1bb7a Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_180.png differ diff --git a/src/assets/images/backgrounds/new/bg_181.png b/src/assets/images/backgrounds/new/bg_181.png new file mode 100644 index 0000000..62c6dd2 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_181.png differ diff --git a/src/assets/images/backgrounds/new/bg_182.png b/src/assets/images/backgrounds/new/bg_182.png new file mode 100644 index 0000000..81613a1 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_182.png differ diff --git a/src/assets/images/backgrounds/new/bg_183.png b/src/assets/images/backgrounds/new/bg_183.png new file mode 100644 index 0000000..1d839eb Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_183.png differ diff --git a/src/assets/images/backgrounds/new/bg_184.png b/src/assets/images/backgrounds/new/bg_184.png new file mode 100644 index 0000000..dd53887 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_184.png differ diff --git a/src/assets/images/backgrounds/new/bg_185.png b/src/assets/images/backgrounds/new/bg_185.png new file mode 100644 index 0000000..669819e Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_185.png differ diff --git a/src/assets/images/backgrounds/new/bg_186.png b/src/assets/images/backgrounds/new/bg_186.png new file mode 100644 index 0000000..82709ad Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_186.png differ diff --git a/src/assets/images/backgrounds/new/bg_187.gif b/src/assets/images/backgrounds/new/bg_187.gif new file mode 100644 index 0000000..f782ce7 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_187.gif differ diff --git a/src/assets/images/backgrounds/new/bg_188.gif b/src/assets/images/backgrounds/new/bg_188.gif new file mode 100644 index 0000000..8d5e2a2 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_188.gif differ diff --git a/src/assets/images/backgrounds/new/bg_19.png b/src/assets/images/backgrounds/new/bg_19.png new file mode 100644 index 0000000..10bb78c Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_19.png differ diff --git a/src/assets/images/backgrounds/new/bg_2.png b/src/assets/images/backgrounds/new/bg_2.png new file mode 100644 index 0000000..5b2fc5a Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_2.png differ diff --git a/src/assets/images/backgrounds/new/bg_20.png b/src/assets/images/backgrounds/new/bg_20.png new file mode 100644 index 0000000..a60ef76 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_20.png differ diff --git a/src/assets/images/backgrounds/new/bg_21.png b/src/assets/images/backgrounds/new/bg_21.png new file mode 100644 index 0000000..8029ae0 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_21.png differ diff --git a/src/assets/images/backgrounds/new/bg_22.png b/src/assets/images/backgrounds/new/bg_22.png new file mode 100644 index 0000000..07a1915 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_22.png differ diff --git a/src/assets/images/backgrounds/new/bg_23.png b/src/assets/images/backgrounds/new/bg_23.png new file mode 100644 index 0000000..86dbad3 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_23.png differ diff --git a/src/assets/images/backgrounds/new/bg_24.png b/src/assets/images/backgrounds/new/bg_24.png new file mode 100644 index 0000000..79278d1 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_24.png differ diff --git a/src/assets/images/backgrounds/new/bg_25.png b/src/assets/images/backgrounds/new/bg_25.png new file mode 100644 index 0000000..d8025a6 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_25.png differ diff --git a/src/assets/images/backgrounds/new/bg_26.png b/src/assets/images/backgrounds/new/bg_26.png new file mode 100644 index 0000000..60e0c37 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_26.png differ diff --git a/src/assets/images/backgrounds/new/bg_27.png b/src/assets/images/backgrounds/new/bg_27.png new file mode 100644 index 0000000..8e942a3 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_27.png differ diff --git a/src/assets/images/backgrounds/new/bg_28.png b/src/assets/images/backgrounds/new/bg_28.png new file mode 100644 index 0000000..689c2b9 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_28.png differ diff --git a/src/assets/images/backgrounds/new/bg_29.png b/src/assets/images/backgrounds/new/bg_29.png new file mode 100644 index 0000000..3260464 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_29.png differ diff --git a/src/assets/images/backgrounds/new/bg_3.png b/src/assets/images/backgrounds/new/bg_3.png new file mode 100644 index 0000000..41034f7 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_3.png differ diff --git a/src/assets/images/backgrounds/new/bg_30.png b/src/assets/images/backgrounds/new/bg_30.png new file mode 100644 index 0000000..3db687a Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_30.png differ diff --git a/src/assets/images/backgrounds/new/bg_31.png b/src/assets/images/backgrounds/new/bg_31.png new file mode 100644 index 0000000..348b000 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_31.png differ diff --git a/src/assets/images/backgrounds/new/bg_32.png b/src/assets/images/backgrounds/new/bg_32.png new file mode 100644 index 0000000..7b11ec3 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_32.png differ diff --git a/src/assets/images/backgrounds/new/bg_33.png b/src/assets/images/backgrounds/new/bg_33.png new file mode 100644 index 0000000..a5ba792 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_33.png differ diff --git a/src/assets/images/backgrounds/new/bg_34.png b/src/assets/images/backgrounds/new/bg_34.png new file mode 100644 index 0000000..c022119 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_34.png differ diff --git a/src/assets/images/backgrounds/new/bg_35.png b/src/assets/images/backgrounds/new/bg_35.png new file mode 100644 index 0000000..bacd72f Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_35.png differ diff --git a/src/assets/images/backgrounds/new/bg_36.gif b/src/assets/images/backgrounds/new/bg_36.gif new file mode 100644 index 0000000..b30f769 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_36.gif differ diff --git a/src/assets/images/backgrounds/new/bg_37.png b/src/assets/images/backgrounds/new/bg_37.png new file mode 100644 index 0000000..572e661 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_37.png differ diff --git a/src/assets/images/backgrounds/new/bg_38.png b/src/assets/images/backgrounds/new/bg_38.png new file mode 100644 index 0000000..e47c7ad Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_38.png differ diff --git a/src/assets/images/backgrounds/new/bg_39.png b/src/assets/images/backgrounds/new/bg_39.png new file mode 100644 index 0000000..a4f1999 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_39.png differ diff --git a/src/assets/images/backgrounds/new/bg_4.png b/src/assets/images/backgrounds/new/bg_4.png new file mode 100644 index 0000000..b150551 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_4.png differ diff --git a/src/assets/images/backgrounds/new/bg_40.png b/src/assets/images/backgrounds/new/bg_40.png new file mode 100644 index 0000000..1af2c2e Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_40.png differ diff --git a/src/assets/images/backgrounds/new/bg_41.png b/src/assets/images/backgrounds/new/bg_41.png new file mode 100644 index 0000000..4201d9e Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_41.png differ diff --git a/src/assets/images/backgrounds/new/bg_42.png b/src/assets/images/backgrounds/new/bg_42.png new file mode 100644 index 0000000..61057a3 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_42.png differ diff --git a/src/assets/images/backgrounds/new/bg_43.png b/src/assets/images/backgrounds/new/bg_43.png new file mode 100644 index 0000000..5178123 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_43.png differ diff --git a/src/assets/images/backgrounds/new/bg_44.png b/src/assets/images/backgrounds/new/bg_44.png new file mode 100644 index 0000000..4f282a3 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_44.png differ diff --git a/src/assets/images/backgrounds/new/bg_45.png b/src/assets/images/backgrounds/new/bg_45.png new file mode 100644 index 0000000..0cfec58 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_45.png differ diff --git a/src/assets/images/backgrounds/new/bg_46.png b/src/assets/images/backgrounds/new/bg_46.png new file mode 100644 index 0000000..0b37070 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_46.png differ diff --git a/src/assets/images/backgrounds/new/bg_47.png b/src/assets/images/backgrounds/new/bg_47.png new file mode 100644 index 0000000..c8192c6 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_47.png differ diff --git a/src/assets/images/backgrounds/new/bg_48.png b/src/assets/images/backgrounds/new/bg_48.png new file mode 100644 index 0000000..167ff55 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_48.png differ diff --git a/src/assets/images/backgrounds/new/bg_49.png b/src/assets/images/backgrounds/new/bg_49.png new file mode 100644 index 0000000..ec8e60f Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_49.png differ diff --git a/src/assets/images/backgrounds/new/bg_5.png b/src/assets/images/backgrounds/new/bg_5.png new file mode 100644 index 0000000..f3a86fe Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_5.png differ diff --git a/src/assets/images/backgrounds/new/bg_50.png b/src/assets/images/backgrounds/new/bg_50.png new file mode 100644 index 0000000..275494e Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_50.png differ diff --git a/src/assets/images/backgrounds/new/bg_51.gif b/src/assets/images/backgrounds/new/bg_51.gif new file mode 100644 index 0000000..f1e97a4 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_51.gif differ diff --git a/src/assets/images/backgrounds/new/bg_52.gif b/src/assets/images/backgrounds/new/bg_52.gif new file mode 100644 index 0000000..0f0c071 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_52.gif differ diff --git a/src/assets/images/backgrounds/new/bg_53.gif b/src/assets/images/backgrounds/new/bg_53.gif new file mode 100644 index 0000000..fe88155 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_53.gif differ diff --git a/src/assets/images/backgrounds/new/bg_54.gif b/src/assets/images/backgrounds/new/bg_54.gif new file mode 100644 index 0000000..2dab5fe Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_54.gif differ diff --git a/src/assets/images/backgrounds/new/bg_55.gif b/src/assets/images/backgrounds/new/bg_55.gif new file mode 100644 index 0000000..a7688a1 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_55.gif differ diff --git a/src/assets/images/backgrounds/new/bg_56.gif b/src/assets/images/backgrounds/new/bg_56.gif new file mode 100644 index 0000000..c0fc06d Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_56.gif differ diff --git a/src/assets/images/backgrounds/new/bg_57.gif b/src/assets/images/backgrounds/new/bg_57.gif new file mode 100644 index 0000000..2db068f Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_57.gif differ diff --git a/src/assets/images/backgrounds/new/bg_58.gif b/src/assets/images/backgrounds/new/bg_58.gif new file mode 100644 index 0000000..d06f671 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_58.gif differ diff --git a/src/assets/images/backgrounds/new/bg_59.gif b/src/assets/images/backgrounds/new/bg_59.gif new file mode 100644 index 0000000..a103717 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_59.gif differ diff --git a/src/assets/images/backgrounds/new/bg_6.png b/src/assets/images/backgrounds/new/bg_6.png new file mode 100644 index 0000000..79ecaab Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_6.png differ diff --git a/src/assets/images/backgrounds/new/bg_60.gif b/src/assets/images/backgrounds/new/bg_60.gif new file mode 100644 index 0000000..9c7ad38 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_60.gif differ diff --git a/src/assets/images/backgrounds/new/bg_61.gif b/src/assets/images/backgrounds/new/bg_61.gif new file mode 100644 index 0000000..aa0d872 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_61.gif differ diff --git a/src/assets/images/backgrounds/new/bg_62.gif b/src/assets/images/backgrounds/new/bg_62.gif new file mode 100644 index 0000000..b02708e Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_62.gif differ diff --git a/src/assets/images/backgrounds/new/bg_63.gif b/src/assets/images/backgrounds/new/bg_63.gif new file mode 100644 index 0000000..ced6c2b Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_63.gif differ diff --git a/src/assets/images/backgrounds/new/bg_64.gif b/src/assets/images/backgrounds/new/bg_64.gif new file mode 100644 index 0000000..c051492 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_64.gif differ diff --git a/src/assets/images/backgrounds/new/bg_65.gif b/src/assets/images/backgrounds/new/bg_65.gif new file mode 100644 index 0000000..b48ed88 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_65.gif differ diff --git a/src/assets/images/backgrounds/new/bg_66.gif b/src/assets/images/backgrounds/new/bg_66.gif new file mode 100644 index 0000000..45860c1 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_66.gif differ diff --git a/src/assets/images/backgrounds/new/bg_67.gif b/src/assets/images/backgrounds/new/bg_67.gif new file mode 100644 index 0000000..613512a Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_67.gif differ diff --git a/src/assets/images/backgrounds/new/bg_68.gif b/src/assets/images/backgrounds/new/bg_68.gif new file mode 100644 index 0000000..221547a Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_68.gif differ diff --git a/src/assets/images/backgrounds/new/bg_69.gif b/src/assets/images/backgrounds/new/bg_69.gif new file mode 100644 index 0000000..e23d282 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_69.gif differ diff --git a/src/assets/images/backgrounds/new/bg_7.png b/src/assets/images/backgrounds/new/bg_7.png new file mode 100644 index 0000000..7e61d10 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_7.png differ diff --git a/src/assets/images/backgrounds/new/bg_70.gif b/src/assets/images/backgrounds/new/bg_70.gif new file mode 100644 index 0000000..8f626fa Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_70.gif differ diff --git a/src/assets/images/backgrounds/new/bg_71.gif b/src/assets/images/backgrounds/new/bg_71.gif new file mode 100644 index 0000000..083417c Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_71.gif differ diff --git a/src/assets/images/backgrounds/new/bg_72.gif b/src/assets/images/backgrounds/new/bg_72.gif new file mode 100644 index 0000000..af10f9b Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_72.gif differ diff --git a/src/assets/images/backgrounds/new/bg_73.gif b/src/assets/images/backgrounds/new/bg_73.gif new file mode 100644 index 0000000..9293170 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_73.gif differ diff --git a/src/assets/images/backgrounds/new/bg_74.gif b/src/assets/images/backgrounds/new/bg_74.gif new file mode 100644 index 0000000..a3c49ad Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_74.gif differ diff --git a/src/assets/images/backgrounds/new/bg_75.gif b/src/assets/images/backgrounds/new/bg_75.gif new file mode 100644 index 0000000..82709ad Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_75.gif differ diff --git a/src/assets/images/backgrounds/new/bg_76.gif b/src/assets/images/backgrounds/new/bg_76.gif new file mode 100644 index 0000000..bd9b9cd Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_76.gif differ diff --git a/src/assets/images/backgrounds/new/bg_77.gif b/src/assets/images/backgrounds/new/bg_77.gif new file mode 100644 index 0000000..14b2dc0 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_77.gif differ diff --git a/src/assets/images/backgrounds/new/bg_78.gif b/src/assets/images/backgrounds/new/bg_78.gif new file mode 100644 index 0000000..66288b3 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_78.gif differ diff --git a/src/assets/images/backgrounds/new/bg_79.gif b/src/assets/images/backgrounds/new/bg_79.gif new file mode 100644 index 0000000..82709ad Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_79.gif differ diff --git a/src/assets/images/backgrounds/new/bg_8.png b/src/assets/images/backgrounds/new/bg_8.png new file mode 100644 index 0000000..89a49bb Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_8.png differ diff --git a/src/assets/images/backgrounds/new/bg_80.gif b/src/assets/images/backgrounds/new/bg_80.gif new file mode 100644 index 0000000..c1d6fa3 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_80.gif differ diff --git a/src/assets/images/backgrounds/new/bg_81.gif b/src/assets/images/backgrounds/new/bg_81.gif new file mode 100644 index 0000000..899e77c Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_81.gif differ diff --git a/src/assets/images/backgrounds/new/bg_82.gif b/src/assets/images/backgrounds/new/bg_82.gif new file mode 100644 index 0000000..8330696 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_82.gif differ diff --git a/src/assets/images/backgrounds/new/bg_83.gif b/src/assets/images/backgrounds/new/bg_83.gif new file mode 100644 index 0000000..2cf2355 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_83.gif differ diff --git a/src/assets/images/backgrounds/new/bg_84.gif b/src/assets/images/backgrounds/new/bg_84.gif new file mode 100644 index 0000000..27282b7 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_84.gif differ diff --git a/src/assets/images/backgrounds/new/bg_85.gif b/src/assets/images/backgrounds/new/bg_85.gif new file mode 100644 index 0000000..7862b23 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_85.gif differ diff --git a/src/assets/images/backgrounds/new/bg_86.gif b/src/assets/images/backgrounds/new/bg_86.gif new file mode 100644 index 0000000..f9f0d0c Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_86.gif differ diff --git a/src/assets/images/backgrounds/new/bg_87.gif b/src/assets/images/backgrounds/new/bg_87.gif new file mode 100644 index 0000000..3833cae Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_87.gif differ diff --git a/src/assets/images/backgrounds/new/bg_88.gif b/src/assets/images/backgrounds/new/bg_88.gif new file mode 100644 index 0000000..ac1004b Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_88.gif differ diff --git a/src/assets/images/backgrounds/new/bg_89.gif b/src/assets/images/backgrounds/new/bg_89.gif new file mode 100644 index 0000000..071b4ac Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_89.gif differ diff --git a/src/assets/images/backgrounds/new/bg_9.png b/src/assets/images/backgrounds/new/bg_9.png new file mode 100644 index 0000000..f48f4a4 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_9.png differ diff --git a/src/assets/images/backgrounds/new/bg_90.gif b/src/assets/images/backgrounds/new/bg_90.gif new file mode 100644 index 0000000..5babf74 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_90.gif differ diff --git a/src/assets/images/backgrounds/new/bg_91.gif b/src/assets/images/backgrounds/new/bg_91.gif new file mode 100644 index 0000000..cae21c5 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_91.gif differ diff --git a/src/assets/images/backgrounds/new/bg_92.gif b/src/assets/images/backgrounds/new/bg_92.gif new file mode 100644 index 0000000..1ca656e Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_92.gif differ diff --git a/src/assets/images/backgrounds/new/bg_93.gif b/src/assets/images/backgrounds/new/bg_93.gif new file mode 100644 index 0000000..ee78487 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_93.gif differ diff --git a/src/assets/images/backgrounds/new/bg_94.gif b/src/assets/images/backgrounds/new/bg_94.gif new file mode 100644 index 0000000..f2ebe51 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_94.gif differ diff --git a/src/assets/images/backgrounds/new/bg_95.gif b/src/assets/images/backgrounds/new/bg_95.gif new file mode 100644 index 0000000..568187f Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_95.gif differ diff --git a/src/assets/images/backgrounds/new/bg_96.gif b/src/assets/images/backgrounds/new/bg_96.gif new file mode 100644 index 0000000..8dfd887 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_96.gif differ diff --git a/src/assets/images/backgrounds/new/bg_97.gif b/src/assets/images/backgrounds/new/bg_97.gif new file mode 100644 index 0000000..62b8504 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_97.gif differ diff --git a/src/assets/images/backgrounds/new/bg_98.gif b/src/assets/images/backgrounds/new/bg_98.gif new file mode 100644 index 0000000..7f5ab86 Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_98.gif differ diff --git a/src/assets/images/backgrounds/new/bg_99.gif b/src/assets/images/backgrounds/new/bg_99.gif new file mode 100644 index 0000000..327a73b Binary files /dev/null and b/src/assets/images/backgrounds/new/bg_99.gif differ diff --git a/src/assets/images/backgrounds/overlay/overlay_0.png b/src/assets/images/backgrounds/overlay/overlay_0.png new file mode 100644 index 0000000..afd389a Binary files /dev/null and b/src/assets/images/backgrounds/overlay/overlay_0.png differ diff --git a/src/assets/images/backgrounds/overlay/overlay_1.png b/src/assets/images/backgrounds/overlay/overlay_1.png new file mode 100644 index 0000000..22cadb7 Binary files /dev/null and b/src/assets/images/backgrounds/overlay/overlay_1.png differ diff --git a/src/assets/images/backgrounds/overlay/overlay_2.png b/src/assets/images/backgrounds/overlay/overlay_2.png new file mode 100644 index 0000000..16aee82 Binary files /dev/null and b/src/assets/images/backgrounds/overlay/overlay_2.png differ diff --git a/src/assets/images/backgrounds/overlay/overlay_3.png b/src/assets/images/backgrounds/overlay/overlay_3.png new file mode 100644 index 0000000..ca52992 Binary files /dev/null and b/src/assets/images/backgrounds/overlay/overlay_3.png differ diff --git a/src/assets/images/backgrounds/overlay/overlay_4.png b/src/assets/images/backgrounds/overlay/overlay_4.png new file mode 100644 index 0000000..b61dc84 Binary files /dev/null and b/src/assets/images/backgrounds/overlay/overlay_4.png differ diff --git a/src/assets/images/backgrounds/overlay/overlay_5.gif b/src/assets/images/backgrounds/overlay/overlay_5.gif new file mode 100644 index 0000000..841a7b6 Binary files /dev/null and b/src/assets/images/backgrounds/overlay/overlay_5.gif differ diff --git a/src/assets/images/backgrounds/overlay/overlay_6.png b/src/assets/images/backgrounds/overlay/overlay_6.png new file mode 100644 index 0000000..a1f95e7 Binary files /dev/null and b/src/assets/images/backgrounds/overlay/overlay_6.png differ diff --git a/src/assets/images/backgrounds/overlay/overlay_7.png b/src/assets/images/backgrounds/overlay/overlay_7.png new file mode 100644 index 0000000..987719e Binary files /dev/null and b/src/assets/images/backgrounds/overlay/overlay_7.png differ diff --git a/src/assets/images/backgrounds/overlay/overlay_8.png b/src/assets/images/backgrounds/overlay/overlay_8.png new file mode 100644 index 0000000..3c0f341 Binary files /dev/null and b/src/assets/images/backgrounds/overlay/overlay_8.png differ diff --git a/src/assets/images/backgrounds/overlay_0.png b/src/assets/images/backgrounds/overlay_0.png new file mode 100644 index 0000000..afd389a Binary files /dev/null and b/src/assets/images/backgrounds/overlay_0.png differ diff --git a/src/assets/images/backgrounds/overlay_1.png b/src/assets/images/backgrounds/overlay_1.png new file mode 100644 index 0000000..22cadb7 Binary files /dev/null and b/src/assets/images/backgrounds/overlay_1.png differ diff --git a/src/assets/images/backgrounds/overlay_2.png b/src/assets/images/backgrounds/overlay_2.png new file mode 100644 index 0000000..16aee82 Binary files /dev/null and b/src/assets/images/backgrounds/overlay_2.png differ diff --git a/src/assets/images/backgrounds/overlay_3.png b/src/assets/images/backgrounds/overlay_3.png new file mode 100644 index 0000000..ca52992 Binary files /dev/null and b/src/assets/images/backgrounds/overlay_3.png differ diff --git a/src/assets/images/backgrounds/overlay_4.png b/src/assets/images/backgrounds/overlay_4.png new file mode 100644 index 0000000..b61dc84 Binary files /dev/null and b/src/assets/images/backgrounds/overlay_4.png differ diff --git a/src/assets/images/backgrounds/overlay_ring.gif b/src/assets/images/backgrounds/overlay_ring.gif new file mode 100644 index 0000000..841a7b6 Binary files /dev/null and b/src/assets/images/backgrounds/overlay_ring.gif differ diff --git a/src/assets/images/backgrounds/stand_0.png b/src/assets/images/backgrounds/stand_0.png new file mode 100644 index 0000000..afd389a Binary files /dev/null and b/src/assets/images/backgrounds/stand_0.png differ diff --git a/src/assets/images/backgrounds/stand_1.png b/src/assets/images/backgrounds/stand_1.png new file mode 100644 index 0000000..27ebe0f Binary files /dev/null and b/src/assets/images/backgrounds/stand_1.png differ diff --git a/src/assets/images/backgrounds/stand_10.png b/src/assets/images/backgrounds/stand_10.png new file mode 100644 index 0000000..59c2820 Binary files /dev/null and b/src/assets/images/backgrounds/stand_10.png differ diff --git a/src/assets/images/backgrounds/stand_11.png b/src/assets/images/backgrounds/stand_11.png new file mode 100644 index 0000000..6974b38 Binary files /dev/null and b/src/assets/images/backgrounds/stand_11.png differ diff --git a/src/assets/images/backgrounds/stand_12.png b/src/assets/images/backgrounds/stand_12.png new file mode 100644 index 0000000..da4cccb Binary files /dev/null and b/src/assets/images/backgrounds/stand_12.png differ diff --git a/src/assets/images/backgrounds/stand_13.png b/src/assets/images/backgrounds/stand_13.png new file mode 100644 index 0000000..ab4e8be Binary files /dev/null and b/src/assets/images/backgrounds/stand_13.png differ diff --git a/src/assets/images/backgrounds/stand_14.png b/src/assets/images/backgrounds/stand_14.png new file mode 100644 index 0000000..1a60cce Binary files /dev/null and b/src/assets/images/backgrounds/stand_14.png differ diff --git a/src/assets/images/backgrounds/stand_15.png b/src/assets/images/backgrounds/stand_15.png new file mode 100644 index 0000000..211802b Binary files /dev/null and b/src/assets/images/backgrounds/stand_15.png differ diff --git a/src/assets/images/backgrounds/stand_16.png b/src/assets/images/backgrounds/stand_16.png new file mode 100644 index 0000000..b1e945d Binary files /dev/null and b/src/assets/images/backgrounds/stand_16.png differ diff --git a/src/assets/images/backgrounds/stand_17.png b/src/assets/images/backgrounds/stand_17.png new file mode 100644 index 0000000..1190763 Binary files /dev/null and b/src/assets/images/backgrounds/stand_17.png differ diff --git a/src/assets/images/backgrounds/stand_18.png b/src/assets/images/backgrounds/stand_18.png new file mode 100644 index 0000000..fd5aae5 Binary files /dev/null and b/src/assets/images/backgrounds/stand_18.png differ diff --git a/src/assets/images/backgrounds/stand_19.png b/src/assets/images/backgrounds/stand_19.png new file mode 100644 index 0000000..c522804 Binary files /dev/null and b/src/assets/images/backgrounds/stand_19.png differ diff --git a/src/assets/images/backgrounds/stand_2.png b/src/assets/images/backgrounds/stand_2.png new file mode 100644 index 0000000..9f789bd Binary files /dev/null and b/src/assets/images/backgrounds/stand_2.png differ diff --git a/src/assets/images/backgrounds/stand_20.png b/src/assets/images/backgrounds/stand_20.png new file mode 100644 index 0000000..695122d Binary files /dev/null and b/src/assets/images/backgrounds/stand_20.png differ diff --git a/src/assets/images/backgrounds/stand_21.gif b/src/assets/images/backgrounds/stand_21.gif new file mode 100644 index 0000000..7449b39 Binary files /dev/null and b/src/assets/images/backgrounds/stand_21.gif differ diff --git a/src/assets/images/backgrounds/stand_3.png b/src/assets/images/backgrounds/stand_3.png new file mode 100644 index 0000000..800748e Binary files /dev/null and b/src/assets/images/backgrounds/stand_3.png differ diff --git a/src/assets/images/backgrounds/stand_4.png b/src/assets/images/backgrounds/stand_4.png new file mode 100644 index 0000000..d7042da Binary files /dev/null and b/src/assets/images/backgrounds/stand_4.png differ diff --git a/src/assets/images/backgrounds/stand_5.png b/src/assets/images/backgrounds/stand_5.png new file mode 100644 index 0000000..0f28a29 Binary files /dev/null and b/src/assets/images/backgrounds/stand_5.png differ diff --git a/src/assets/images/backgrounds/stand_6.png b/src/assets/images/backgrounds/stand_6.png new file mode 100644 index 0000000..f20be87 Binary files /dev/null and b/src/assets/images/backgrounds/stand_6.png differ diff --git a/src/assets/images/backgrounds/stand_7.png b/src/assets/images/backgrounds/stand_7.png new file mode 100644 index 0000000..9a72c8f Binary files /dev/null and b/src/assets/images/backgrounds/stand_7.png differ diff --git a/src/assets/images/backgrounds/stand_8.png b/src/assets/images/backgrounds/stand_8.png new file mode 100644 index 0000000..b0f8021 Binary files /dev/null and b/src/assets/images/backgrounds/stand_8.png differ diff --git a/src/assets/images/backgrounds/stand_9.png b/src/assets/images/backgrounds/stand_9.png new file mode 100644 index 0000000..bf42a94 Binary files /dev/null and b/src/assets/images/backgrounds/stand_9.png differ diff --git a/src/assets/images/campaign/available.png b/src/assets/images/campaign/available.png new file mode 100644 index 0000000..1cc8fa6 Binary files /dev/null and b/src/assets/images/campaign/available.png differ diff --git a/src/assets/images/campaign/campaign_day_generic_bg.png b/src/assets/images/campaign/campaign_day_generic_bg.png new file mode 100644 index 0000000..25b3c62 Binary files /dev/null and b/src/assets/images/campaign/campaign_day_generic_bg.png differ diff --git a/src/assets/images/campaign/campaign_opened.png b/src/assets/images/campaign/campaign_opened.png new file mode 100644 index 0000000..9ec1bb3 Binary files /dev/null and b/src/assets/images/campaign/campaign_opened.png differ diff --git a/src/assets/images/campaign/campaign_spritesheet.png b/src/assets/images/campaign/campaign_spritesheet.png new file mode 100644 index 0000000..eeac9d4 Binary files /dev/null and b/src/assets/images/campaign/campaign_spritesheet.png differ diff --git a/src/assets/images/campaign/locked.png b/src/assets/images/campaign/locked.png new file mode 100644 index 0000000..3805e50 Binary files /dev/null and b/src/assets/images/campaign/locked.png differ diff --git a/src/assets/images/campaign/locked_bg.png b/src/assets/images/campaign/locked_bg.png new file mode 100644 index 0000000..93927dd Binary files /dev/null and b/src/assets/images/campaign/locked_bg.png differ diff --git a/src/assets/images/campaign/next.png b/src/assets/images/campaign/next.png new file mode 100644 index 0000000..88a2883 Binary files /dev/null and b/src/assets/images/campaign/next.png differ diff --git a/src/assets/images/campaign/prev.png b/src/assets/images/campaign/prev.png new file mode 100644 index 0000000..914a659 Binary files /dev/null and b/src/assets/images/campaign/prev.png differ diff --git a/src/assets/images/campaign/unavailable.png b/src/assets/images/campaign/unavailable.png new file mode 100644 index 0000000..dc134c3 Binary files /dev/null and b/src/assets/images/campaign/unavailable.png differ diff --git a/src/assets/images/campaign/unlocked_bg.png b/src/assets/images/campaign/unlocked_bg.png new file mode 100644 index 0000000..31c35ba Binary files /dev/null and b/src/assets/images/campaign/unlocked_bg.png differ diff --git a/src/assets/images/catalog/diamond_info_illustration.gif b/src/assets/images/catalog/diamond_info_illustration.gif new file mode 100644 index 0000000..d082ef4 Binary files /dev/null and b/src/assets/images/catalog/diamond_info_illustration.gif differ diff --git a/src/assets/images/catalog/hc_banner_big.png b/src/assets/images/catalog/hc_banner_big.png new file mode 100644 index 0000000..4b2c4ec Binary files /dev/null and b/src/assets/images/catalog/hc_banner_big.png differ diff --git a/src/assets/images/catalog/hc_big.png b/src/assets/images/catalog/hc_big.png new file mode 100644 index 0000000..5f8c0a2 Binary files /dev/null and b/src/assets/images/catalog/hc_big.png differ diff --git a/src/assets/images/catalog/hc_small.png b/src/assets/images/catalog/hc_small.png new file mode 100644 index 0000000..9983330 Binary files /dev/null and b/src/assets/images/catalog/hc_small.png differ diff --git a/src/assets/images/catalog/paint-icon.png b/src/assets/images/catalog/paint-icon.png new file mode 100644 index 0000000..f2bf7ea Binary files /dev/null and b/src/assets/images/catalog/paint-icon.png differ diff --git a/src/assets/images/catalog/target-price.png b/src/assets/images/catalog/target-price.png new file mode 100644 index 0000000..8639afd Binary files /dev/null and b/src/assets/images/catalog/target-price.png differ diff --git a/src/assets/images/catalog/vip.png b/src/assets/images/catalog/vip.png new file mode 100644 index 0000000..9a3aad6 Binary files /dev/null and b/src/assets/images/catalog/vip.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_0.png b/src/assets/images/chat/chatbubbles/bubble_0.png new file mode 100644 index 0000000..da68536 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_0.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_0_1_33_34_pointer.png b/src/assets/images/chat/chatbubbles/bubble_0_1_33_34_pointer.png new file mode 100644 index 0000000..e7a4740 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_0_1_33_34_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_0_transparent.png b/src/assets/images/chat/chatbubbles/bubble_0_transparent.png new file mode 100644 index 0000000..596d2c7 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_0_transparent.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_1.png b/src/assets/images/chat/chatbubbles/bubble_1.png new file mode 100644 index 0000000..492dbde Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_1.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_10.png b/src/assets/images/chat/chatbubbles/bubble_10.png new file mode 100644 index 0000000..cbf25a8 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_10.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_10_pointer.png b/src/assets/images/chat/chatbubbles/bubble_10_pointer.png new file mode 100644 index 0000000..f7bd858 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_10_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_11.png b/src/assets/images/chat/chatbubbles/bubble_11.png new file mode 100644 index 0000000..a8026d6 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_11.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_11_pointer.png b/src/assets/images/chat/chatbubbles/bubble_11_pointer.png new file mode 100644 index 0000000..d6c4482 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_11_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_12.png b/src/assets/images/chat/chatbubbles/bubble_12.png new file mode 100644 index 0000000..fa73413 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_12.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_12_pointer.png b/src/assets/images/chat/chatbubbles/bubble_12_pointer.png new file mode 100644 index 0000000..309a550 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_12_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_13.png b/src/assets/images/chat/chatbubbles/bubble_13.png new file mode 100644 index 0000000..a9f2c41 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_13.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_13_pointer.png b/src/assets/images/chat/chatbubbles/bubble_13_pointer.png new file mode 100644 index 0000000..65be4f2 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_13_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_14.png b/src/assets/images/chat/chatbubbles/bubble_14.png new file mode 100644 index 0000000..54232f2 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_14.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_14_pointer.png b/src/assets/images/chat/chatbubbles/bubble_14_pointer.png new file mode 100644 index 0000000..4975897 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_14_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_15.png b/src/assets/images/chat/chatbubbles/bubble_15.png new file mode 100644 index 0000000..6a7ad13 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_15.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_15_pointer.png b/src/assets/images/chat/chatbubbles/bubble_15_pointer.png new file mode 100644 index 0000000..b4a69e1 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_15_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_16.png b/src/assets/images/chat/chatbubbles/bubble_16.png new file mode 100644 index 0000000..ae0a23e Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_16.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_16_pointer.png b/src/assets/images/chat/chatbubbles/bubble_16_pointer.png new file mode 100644 index 0000000..abb9625 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_16_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_17.png b/src/assets/images/chat/chatbubbles/bubble_17.png new file mode 100644 index 0000000..5193025 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_17.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_17_pointer.png b/src/assets/images/chat/chatbubbles/bubble_17_pointer.png new file mode 100644 index 0000000..b73b25b Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_17_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_18.png b/src/assets/images/chat/chatbubbles/bubble_18.png new file mode 100644 index 0000000..ad07d05 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_18.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_18_pointer.png b/src/assets/images/chat/chatbubbles/bubble_18_pointer.png new file mode 100644 index 0000000..f364525 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_18_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_19.png b/src/assets/images/chat/chatbubbles/bubble_19.png new file mode 100644 index 0000000..c0dfb41 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_19.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_19_20_pointer.png b/src/assets/images/chat/chatbubbles/bubble_19_20_pointer.png new file mode 100644 index 0000000..990c21f Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_19_20_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_2.png b/src/assets/images/chat/chatbubbles/bubble_2.png new file mode 100644 index 0000000..dc33507 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_2.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_20.png b/src/assets/images/chat/chatbubbles/bubble_20.png new file mode 100644 index 0000000..127d0f9 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_20.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_21.png b/src/assets/images/chat/chatbubbles/bubble_21.png new file mode 100644 index 0000000..933daf5 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_21.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_21_pointer.png b/src/assets/images/chat/chatbubbles/bubble_21_pointer.png new file mode 100644 index 0000000..fe1f312 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_21_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_22.png b/src/assets/images/chat/chatbubbles/bubble_22.png new file mode 100644 index 0000000..a77a733 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_22.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_22_pointer.png b/src/assets/images/chat/chatbubbles/bubble_22_pointer.png new file mode 100644 index 0000000..855ceff Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_22_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_23.png b/src/assets/images/chat/chatbubbles/bubble_23.png new file mode 100644 index 0000000..d2a8fb4 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_23.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_23_37_pointer.png b/src/assets/images/chat/chatbubbles/bubble_23_37_pointer.png new file mode 100644 index 0000000..786c849 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_23_37_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_24.png b/src/assets/images/chat/chatbubbles/bubble_24.png new file mode 100644 index 0000000..73ee650 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_24.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_24_pointer.png b/src/assets/images/chat/chatbubbles/bubble_24_pointer.png new file mode 100644 index 0000000..4653eef Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_24_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_25.png b/src/assets/images/chat/chatbubbles/bubble_25.png new file mode 100644 index 0000000..60dcaad Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_25.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_25_pointer.png b/src/assets/images/chat/chatbubbles/bubble_25_pointer.png new file mode 100644 index 0000000..7567395 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_25_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_26.png b/src/assets/images/chat/chatbubbles/bubble_26.png new file mode 100644 index 0000000..0b43dec Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_26.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_26_pointer.png b/src/assets/images/chat/chatbubbles/bubble_26_pointer.png new file mode 100644 index 0000000..d97093f Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_26_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_27.png b/src/assets/images/chat/chatbubbles/bubble_27.png new file mode 100644 index 0000000..57de9a9 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_27.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_27_pointer.png b/src/assets/images/chat/chatbubbles/bubble_27_pointer.png new file mode 100644 index 0000000..d0c0cee Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_27_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_28.png b/src/assets/images/chat/chatbubbles/bubble_28.png new file mode 100644 index 0000000..3337b79 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_28.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_28_pointer.png b/src/assets/images/chat/chatbubbles/bubble_28_pointer.png new file mode 100644 index 0000000..850b99e Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_28_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_29.png b/src/assets/images/chat/chatbubbles/bubble_29.png new file mode 100644 index 0000000..9eb5aec Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_29.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_29_pointer.png b/src/assets/images/chat/chatbubbles/bubble_29_pointer.png new file mode 100644 index 0000000..1462b37 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_29_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_2_31_pointer.png b/src/assets/images/chat/chatbubbles/bubble_2_31_pointer.png new file mode 100644 index 0000000..ad9db87 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_2_31_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_3.png b/src/assets/images/chat/chatbubbles/bubble_3.png new file mode 100644 index 0000000..6298809 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_3.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_30.png b/src/assets/images/chat/chatbubbles/bubble_30.png new file mode 100644 index 0000000..581fc70 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_30.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_30_pointer.png b/src/assets/images/chat/chatbubbles/bubble_30_pointer.png new file mode 100644 index 0000000..8660de9 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_30_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_32.png b/src/assets/images/chat/chatbubbles/bubble_32.png new file mode 100644 index 0000000..598d8c8 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_32.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_32_pointer.png b/src/assets/images/chat/chatbubbles/bubble_32_pointer.png new file mode 100644 index 0000000..a68ddfb Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_32_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_33_34.png b/src/assets/images/chat/chatbubbles/bubble_33_34.png new file mode 100644 index 0000000..d871e1a Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_33_34.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_33_extra.png b/src/assets/images/chat/chatbubbles/bubble_33_extra.png new file mode 100644 index 0000000..5b398ba Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_33_extra.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_34_extra.png b/src/assets/images/chat/chatbubbles/bubble_34_extra.png new file mode 100644 index 0000000..9a67674 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_34_extra.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_35.png b/src/assets/images/chat/chatbubbles/bubble_35.png new file mode 100644 index 0000000..e4e7ea6 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_35.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_35_pointer.png b/src/assets/images/chat/chatbubbles/bubble_35_pointer.png new file mode 100644 index 0000000..a8e8c32 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_35_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_36.png b/src/assets/images/chat/chatbubbles/bubble_36.png new file mode 100644 index 0000000..a96e5e0 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_36.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_36_extra.png b/src/assets/images/chat/chatbubbles/bubble_36_extra.png new file mode 100644 index 0000000..8e72fe4 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_36_extra.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_36_pointer.png b/src/assets/images/chat/chatbubbles/bubble_36_pointer.png new file mode 100644 index 0000000..caa9e3c Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_36_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_37.png b/src/assets/images/chat/chatbubbles/bubble_37.png new file mode 100644 index 0000000..43e609e Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_37.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_38.png b/src/assets/images/chat/chatbubbles/bubble_38.png new file mode 100644 index 0000000..326cdf4 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_38.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_38_extra.png b/src/assets/images/chat/chatbubbles/bubble_38_extra.png new file mode 100644 index 0000000..73cfcaf Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_38_extra.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_38_pointer.png b/src/assets/images/chat/chatbubbles/bubble_38_pointer.png new file mode 100644 index 0000000..402e543 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_38_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_3_pointer.png b/src/assets/images/chat/chatbubbles/bubble_3_pointer.png new file mode 100644 index 0000000..55df368 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_3_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_4.png b/src/assets/images/chat/chatbubbles/bubble_4.png new file mode 100644 index 0000000..c5f5706 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_4.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_4_pointer.png b/src/assets/images/chat/chatbubbles/bubble_4_pointer.png new file mode 100644 index 0000000..beb6919 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_4_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_5.png b/src/assets/images/chat/chatbubbles/bubble_5.png new file mode 100644 index 0000000..fa33a77 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_5.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_5_pointer.png b/src/assets/images/chat/chatbubbles/bubble_5_pointer.png new file mode 100644 index 0000000..ce8131a Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_5_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_6.png b/src/assets/images/chat/chatbubbles/bubble_6.png new file mode 100644 index 0000000..ad6a0f2 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_6.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_6_pointer.png b/src/assets/images/chat/chatbubbles/bubble_6_pointer.png new file mode 100644 index 0000000..8c343f5 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_6_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_7.png b/src/assets/images/chat/chatbubbles/bubble_7.png new file mode 100644 index 0000000..8ced1de Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_7.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_7_pointer.png b/src/assets/images/chat/chatbubbles/bubble_7_pointer.png new file mode 100644 index 0000000..6c05652 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_7_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_8.png b/src/assets/images/chat/chatbubbles/bubble_8.png new file mode 100644 index 0000000..2d37069 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_8.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_8_pointer.png b/src/assets/images/chat/chatbubbles/bubble_8_pointer.png new file mode 100644 index 0000000..f786e7e Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_8_pointer.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_9.png b/src/assets/images/chat/chatbubbles/bubble_9.png new file mode 100644 index 0000000..23d5721 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_9.png differ diff --git a/src/assets/images/chat/chatbubbles/bubble_9_pointer.png b/src/assets/images/chat/chatbubbles/bubble_9_pointer.png new file mode 100644 index 0000000..9bc5919 Binary files /dev/null and b/src/assets/images/chat/chatbubbles/bubble_9_pointer.png differ diff --git a/src/assets/images/chat/chathistory_background.png b/src/assets/images/chat/chathistory_background.png new file mode 100644 index 0000000..301d951 Binary files /dev/null and b/src/assets/images/chat/chathistory_background.png differ diff --git a/src/assets/images/chat/styles-icon.png b/src/assets/images/chat/styles-icon.png new file mode 100644 index 0000000..74cd9ec Binary files /dev/null and b/src/assets/images/chat/styles-icon.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-0.png b/src/assets/images/floorplaneditor/door-direction-0.png new file mode 100644 index 0000000..8c272a0 Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-0.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-1.png b/src/assets/images/floorplaneditor/door-direction-1.png new file mode 100644 index 0000000..52e488f Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-1.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-2.png b/src/assets/images/floorplaneditor/door-direction-2.png new file mode 100644 index 0000000..da1a1cb Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-2.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-3.png b/src/assets/images/floorplaneditor/door-direction-3.png new file mode 100644 index 0000000..15712a9 Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-3.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-4.png b/src/assets/images/floorplaneditor/door-direction-4.png new file mode 100644 index 0000000..eb1b4b5 Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-4.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-5.png b/src/assets/images/floorplaneditor/door-direction-5.png new file mode 100644 index 0000000..46e6f4d Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-5.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-6.png b/src/assets/images/floorplaneditor/door-direction-6.png new file mode 100644 index 0000000..fda613a Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-6.png differ diff --git a/src/assets/images/floorplaneditor/door-direction-7.png b/src/assets/images/floorplaneditor/door-direction-7.png new file mode 100644 index 0000000..96fa8e4 Binary files /dev/null and b/src/assets/images/floorplaneditor/door-direction-7.png differ diff --git a/src/assets/images/floorplaneditor/icon-active-squaresselect.png b/src/assets/images/floorplaneditor/icon-active-squaresselect.png new file mode 100644 index 0000000..ea9d9f7 Binary files /dev/null and b/src/assets/images/floorplaneditor/icon-active-squaresselect.png differ diff --git a/src/assets/images/floorplaneditor/icon-deselect.png b/src/assets/images/floorplaneditor/icon-deselect.png new file mode 100644 index 0000000..6fa4f74 Binary files /dev/null and b/src/assets/images/floorplaneditor/icon-deselect.png differ diff --git a/src/assets/images/floorplaneditor/icon-door.png b/src/assets/images/floorplaneditor/icon-door.png new file mode 100644 index 0000000..1b56bb2 Binary files /dev/null and b/src/assets/images/floorplaneditor/icon-door.png differ diff --git a/src/assets/images/floorplaneditor/icon-select.png b/src/assets/images/floorplaneditor/icon-select.png new file mode 100644 index 0000000..dc81c0e Binary files /dev/null and b/src/assets/images/floorplaneditor/icon-select.png differ diff --git a/src/assets/images/floorplaneditor/icon-squaresselect.png b/src/assets/images/floorplaneditor/icon-squaresselect.png new file mode 100644 index 0000000..351098a Binary files /dev/null and b/src/assets/images/floorplaneditor/icon-squaresselect.png differ diff --git a/src/assets/images/floorplaneditor/icon-tile-down.png b/src/assets/images/floorplaneditor/icon-tile-down.png new file mode 100644 index 0000000..352c48d Binary files /dev/null and b/src/assets/images/floorplaneditor/icon-tile-down.png differ diff --git a/src/assets/images/floorplaneditor/icon-tile-set.png b/src/assets/images/floorplaneditor/icon-tile-set.png new file mode 100644 index 0000000..eac6153 Binary files /dev/null and b/src/assets/images/floorplaneditor/icon-tile-set.png differ diff --git a/src/assets/images/floorplaneditor/icon-tile-unset.png b/src/assets/images/floorplaneditor/icon-tile-unset.png new file mode 100644 index 0000000..3f5e218 Binary files /dev/null and b/src/assets/images/floorplaneditor/icon-tile-unset.png differ diff --git a/src/assets/images/floorplaneditor/icon-tile-up.png b/src/assets/images/floorplaneditor/icon-tile-up.png new file mode 100644 index 0000000..27153e0 Binary files /dev/null and b/src/assets/images/floorplaneditor/icon-tile-up.png differ diff --git a/src/assets/images/floorplaneditor/preview_tile.png b/src/assets/images/floorplaneditor/preview_tile.png new file mode 100644 index 0000000..607f450 Binary files /dev/null and b/src/assets/images/floorplaneditor/preview_tile.png differ diff --git a/src/assets/images/floorplaneditor/selected_height_icon.png b/src/assets/images/floorplaneditor/selected_height_icon.png new file mode 100644 index 0000000..f763fde Binary files /dev/null and b/src/assets/images/floorplaneditor/selected_height_icon.png differ diff --git a/src/assets/images/friends/friends-spritesheet.png b/src/assets/images/friends/friends-spritesheet.png new file mode 100644 index 0000000..aa72325 Binary files /dev/null and b/src/assets/images/friends/friends-spritesheet.png differ diff --git a/src/assets/images/friends/icon-accept.png b/src/assets/images/friends/icon-accept.png new file mode 100644 index 0000000..da56941 Binary files /dev/null and b/src/assets/images/friends/icon-accept.png differ diff --git a/src/assets/images/friends/icon-add.png b/src/assets/images/friends/icon-add.png new file mode 100644 index 0000000..d570c8f Binary files /dev/null and b/src/assets/images/friends/icon-add.png differ diff --git a/src/assets/images/friends/icon-bobba.png b/src/assets/images/friends/icon-bobba.png new file mode 100644 index 0000000..eaea895 Binary files /dev/null and b/src/assets/images/friends/icon-bobba.png differ diff --git a/src/assets/images/friends/icon-chat.png b/src/assets/images/friends/icon-chat.png new file mode 100644 index 0000000..631b7bc Binary files /dev/null and b/src/assets/images/friends/icon-chat.png differ diff --git a/src/assets/images/friends/icon-deny.png b/src/assets/images/friends/icon-deny.png new file mode 100644 index 0000000..c74e5c0 Binary files /dev/null and b/src/assets/images/friends/icon-deny.png differ diff --git a/src/assets/images/friends/icon-follow.png b/src/assets/images/friends/icon-follow.png new file mode 100644 index 0000000..9e04516 Binary files /dev/null and b/src/assets/images/friends/icon-follow.png differ diff --git a/src/assets/images/friends/icon-friendbar-chat.png b/src/assets/images/friends/icon-friendbar-chat.png new file mode 100644 index 0000000..e0c1645 Binary files /dev/null and b/src/assets/images/friends/icon-friendbar-chat.png differ diff --git a/src/assets/images/friends/icon-friendbar-visit.png b/src/assets/images/friends/icon-friendbar-visit.png new file mode 100644 index 0000000..fac57d9 Binary files /dev/null and b/src/assets/images/friends/icon-friendbar-visit.png differ diff --git a/src/assets/images/friends/icon-heart.png b/src/assets/images/friends/icon-heart.png new file mode 100644 index 0000000..5dd1015 Binary files /dev/null and b/src/assets/images/friends/icon-heart.png differ diff --git a/src/assets/images/friends/icon-new-message.png b/src/assets/images/friends/icon-new-message.png new file mode 100644 index 0000000..46d23f5 Binary files /dev/null and b/src/assets/images/friends/icon-new-message.png differ diff --git a/src/assets/images/friends/icon-none.png b/src/assets/images/friends/icon-none.png new file mode 100644 index 0000000..ececd9e Binary files /dev/null and b/src/assets/images/friends/icon-none.png differ diff --git a/src/assets/images/friends/icon-profile-sm-hover.png b/src/assets/images/friends/icon-profile-sm-hover.png new file mode 100644 index 0000000..f8f42f2 Binary files /dev/null and b/src/assets/images/friends/icon-profile-sm-hover.png differ diff --git a/src/assets/images/friends/icon-profile-sm.png b/src/assets/images/friends/icon-profile-sm.png new file mode 100644 index 0000000..60107e1 Binary files /dev/null and b/src/assets/images/friends/icon-profile-sm.png differ diff --git a/src/assets/images/friends/icon-profile.png b/src/assets/images/friends/icon-profile.png new file mode 100644 index 0000000..2c8ec5c Binary files /dev/null and b/src/assets/images/friends/icon-profile.png differ diff --git a/src/assets/images/friends/icon-smile.png b/src/assets/images/friends/icon-smile.png new file mode 100644 index 0000000..62fcae6 Binary files /dev/null and b/src/assets/images/friends/icon-smile.png differ diff --git a/src/assets/images/friends/icon-warning.png b/src/assets/images/friends/icon-warning.png new file mode 100644 index 0000000..3a3ffcf Binary files /dev/null and b/src/assets/images/friends/icon-warning.png differ diff --git a/src/assets/images/friends/messenger_notification_icon.png b/src/assets/images/friends/messenger_notification_icon.png new file mode 100644 index 0000000..73f7e5e Binary files /dev/null and b/src/assets/images/friends/messenger_notification_icon.png differ diff --git a/src/assets/images/gamecenter/selectedIcon.png b/src/assets/images/gamecenter/selectedIcon.png new file mode 100644 index 0000000..718e48b Binary files /dev/null and b/src/assets/images/gamecenter/selectedIcon.png differ diff --git a/src/assets/images/gift/gift_tag.png b/src/assets/images/gift/gift_tag.png new file mode 100644 index 0000000..3c24813 Binary files /dev/null and b/src/assets/images/gift/gift_tag.png differ diff --git a/src/assets/images/gift/incognito.png b/src/assets/images/gift/incognito.png new file mode 100644 index 0000000..304c30c Binary files /dev/null and b/src/assets/images/gift/incognito.png differ diff --git a/src/assets/images/groups/creator_images.png b/src/assets/images/groups/creator_images.png new file mode 100644 index 0000000..b39b0d7 Binary files /dev/null and b/src/assets/images/groups/creator_images.png differ diff --git a/src/assets/images/groups/creator_tabs.png b/src/assets/images/groups/creator_tabs.png new file mode 100644 index 0000000..e95b9f0 Binary files /dev/null and b/src/assets/images/groups/creator_tabs.png differ diff --git a/src/assets/images/groups/icons/group_decorate_icon.png b/src/assets/images/groups/icons/group_decorate_icon.png new file mode 100644 index 0000000..3a395fb Binary files /dev/null and b/src/assets/images/groups/icons/group_decorate_icon.png differ diff --git a/src/assets/images/groups/icons/group_favorite.png b/src/assets/images/groups/icons/group_favorite.png new file mode 100644 index 0000000..4cd734d Binary files /dev/null and b/src/assets/images/groups/icons/group_favorite.png differ diff --git a/src/assets/images/groups/icons/group_icon_admin.png b/src/assets/images/groups/icons/group_icon_admin.png new file mode 100644 index 0000000..bad07e4 Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_admin.png differ diff --git a/src/assets/images/groups/icons/group_icon_big_admin.png b/src/assets/images/groups/icons/group_icon_big_admin.png new file mode 100644 index 0000000..2d3e287 Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_big_admin.png differ diff --git a/src/assets/images/groups/icons/group_icon_big_member.png b/src/assets/images/groups/icons/group_icon_big_member.png new file mode 100644 index 0000000..3435937 Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_big_member.png differ diff --git a/src/assets/images/groups/icons/group_icon_big_owner.png b/src/assets/images/groups/icons/group_icon_big_owner.png new file mode 100644 index 0000000..52b8361 Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_big_owner.png differ diff --git a/src/assets/images/groups/icons/group_icon_not_admin.png b/src/assets/images/groups/icons/group_icon_not_admin.png new file mode 100644 index 0000000..7c8b9ee Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_not_admin.png differ diff --git a/src/assets/images/groups/icons/group_icon_small_owner.png b/src/assets/images/groups/icons/group_icon_small_owner.png new file mode 100644 index 0000000..7230ecc Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_small_owner.png differ diff --git a/src/assets/images/groups/icons/group_notfavorite.png b/src/assets/images/groups/icons/group_notfavorite.png new file mode 100644 index 0000000..835d8eb Binary files /dev/null and b/src/assets/images/groups/icons/group_notfavorite.png differ diff --git a/src/assets/images/groups/icons/grouptype_icon_0.png b/src/assets/images/groups/icons/grouptype_icon_0.png new file mode 100644 index 0000000..9dc4191 Binary files /dev/null and b/src/assets/images/groups/icons/grouptype_icon_0.png differ diff --git a/src/assets/images/groups/icons/grouptype_icon_1.png b/src/assets/images/groups/icons/grouptype_icon_1.png new file mode 100644 index 0000000..ed37213 Binary files /dev/null and b/src/assets/images/groups/icons/grouptype_icon_1.png differ diff --git a/src/assets/images/groups/icons/grouptype_icon_2.png b/src/assets/images/groups/icons/grouptype_icon_2.png new file mode 100644 index 0000000..6a5c057 Binary files /dev/null and b/src/assets/images/groups/icons/grouptype_icon_2.png differ diff --git a/src/assets/images/groups/no-group-1.png b/src/assets/images/groups/no-group-1.png new file mode 100644 index 0000000..caf0182 Binary files /dev/null and b/src/assets/images/groups/no-group-1.png differ diff --git a/src/assets/images/groups/no-group-2.png b/src/assets/images/groups/no-group-2.png new file mode 100644 index 0000000..797c946 Binary files /dev/null and b/src/assets/images/groups/no-group-2.png differ diff --git a/src/assets/images/groups/no-group-3.png b/src/assets/images/groups/no-group-3.png new file mode 100644 index 0000000..e5d6a7b Binary files /dev/null and b/src/assets/images/groups/no-group-3.png differ diff --git a/src/assets/images/groups/no-group-spritesheet.png b/src/assets/images/groups/no-group-spritesheet.png new file mode 100644 index 0000000..3eed4b9 Binary files /dev/null and b/src/assets/images/groups/no-group-spritesheet.png differ diff --git a/src/assets/images/guide-tool/guide_tool_duty_switch.png b/src/assets/images/guide-tool/guide_tool_duty_switch.png new file mode 100644 index 0000000..f7de6be Binary files /dev/null and b/src/assets/images/guide-tool/guide_tool_duty_switch.png differ diff --git a/src/assets/images/guide-tool/guide_tool_info_icon.png b/src/assets/images/guide-tool/guide_tool_info_icon.png new file mode 100644 index 0000000..32c4a35 Binary files /dev/null and b/src/assets/images/guide-tool/guide_tool_info_icon.png differ diff --git a/src/assets/images/hc-center/benefits.png b/src/assets/images/hc-center/benefits.png new file mode 100644 index 0000000..508ecc8 Binary files /dev/null and b/src/assets/images/hc-center/benefits.png differ diff --git a/src/assets/images/hc-center/clock.png b/src/assets/images/hc-center/clock.png new file mode 100644 index 0000000..4c37e95 Binary files /dev/null and b/src/assets/images/hc-center/clock.png differ diff --git a/src/assets/images/hc-center/hc_logo.gif b/src/assets/images/hc-center/hc_logo.gif new file mode 100644 index 0000000..8834034 Binary files /dev/null and b/src/assets/images/hc-center/hc_logo.gif differ diff --git a/src/assets/images/hc-center/payday.png b/src/assets/images/hc-center/payday.png new file mode 100644 index 0000000..c9bd80b Binary files /dev/null and b/src/assets/images/hc-center/payday.png differ diff --git a/src/assets/images/help/help_index.png b/src/assets/images/help/help_index.png new file mode 100644 index 0000000..2844f41 Binary files /dev/null and b/src/assets/images/help/help_index.png differ diff --git a/src/assets/images/hotelview/arrow_down.png b/src/assets/images/hotelview/arrow_down.png new file mode 100644 index 0000000..70d0b9c Binary files /dev/null and b/src/assets/images/hotelview/arrow_down.png differ diff --git a/src/assets/images/hotelview/hotelview.png b/src/assets/images/hotelview/hotelview.png new file mode 100644 index 0000000..62609c2 Binary files /dev/null and b/src/assets/images/hotelview/hotelview.png differ diff --git a/src/assets/images/hotelview/infobus.gif b/src/assets/images/hotelview/infobus.gif new file mode 100644 index 0000000..f15e9a4 Binary files /dev/null and b/src/assets/images/hotelview/infobus.gif differ diff --git a/src/assets/images/hotelview/lobby.png b/src/assets/images/hotelview/lobby.png new file mode 100644 index 0000000..f5f3e86 Binary files /dev/null and b/src/assets/images/hotelview/lobby.png differ diff --git a/src/assets/images/hotelview/peacefulpark.gif b/src/assets/images/hotelview/peacefulpark.gif new file mode 100644 index 0000000..9bde74a Binary files /dev/null and b/src/assets/images/hotelview/peacefulpark.gif differ diff --git a/src/assets/images/hotelview/picnic.gif b/src/assets/images/hotelview/picnic.gif new file mode 100644 index 0000000..a59d214 Binary files /dev/null and b/src/assets/images/hotelview/picnic.gif differ diff --git a/src/assets/images/hotelview/pool.gif b/src/assets/images/hotelview/pool.gif new file mode 100644 index 0000000..18aea23 Binary files /dev/null and b/src/assets/images/hotelview/pool.gif differ diff --git a/src/assets/images/hotelview/rooftop.gif b/src/assets/images/hotelview/rooftop.gif new file mode 100644 index 0000000..5d53599 Binary files /dev/null and b/src/assets/images/hotelview/rooftop.gif differ diff --git a/src/assets/images/hotelview/rooftop_pool.gif b/src/assets/images/hotelview/rooftop_pool.gif new file mode 100644 index 0000000..d74569c Binary files /dev/null and b/src/assets/images/hotelview/rooftop_pool.gif differ diff --git a/src/assets/images/icons/arrows.png b/src/assets/images/icons/arrows.png new file mode 100644 index 0000000..47a833c Binary files /dev/null and b/src/assets/images/icons/arrows.png differ diff --git a/src/assets/images/icons/camera-colormatrix.png b/src/assets/images/icons/camera-colormatrix.png new file mode 100644 index 0000000..894396e Binary files /dev/null and b/src/assets/images/icons/camera-colormatrix.png differ diff --git a/src/assets/images/icons/camera-composite.png b/src/assets/images/icons/camera-composite.png new file mode 100644 index 0000000..681fb18 Binary files /dev/null and b/src/assets/images/icons/camera-composite.png differ diff --git a/src/assets/images/icons/camera-small.png b/src/assets/images/icons/camera-small.png new file mode 100644 index 0000000..1c0563e Binary files /dev/null and b/src/assets/images/icons/camera-small.png differ diff --git a/src/assets/images/icons/chat-history.png b/src/assets/images/icons/chat-history.png new file mode 100644 index 0000000..da51f37 Binary files /dev/null and b/src/assets/images/icons/chat-history.png differ diff --git a/src/assets/images/icons/close.png b/src/assets/images/icons/close.png new file mode 100644 index 0000000..a723b3e Binary files /dev/null and b/src/assets/images/icons/close.png differ diff --git a/src/assets/images/icons/cog.png b/src/assets/images/icons/cog.png new file mode 100644 index 0000000..b7101d7 Binary files /dev/null and b/src/assets/images/icons/cog.png differ diff --git a/src/assets/images/icons/disablebubble.png b/src/assets/images/icons/disablebubble.png new file mode 100644 index 0000000..bbf1390 Binary files /dev/null and b/src/assets/images/icons/disablebubble.png differ diff --git a/src/assets/images/icons/enablebubble.png b/src/assets/images/icons/enablebubble.png new file mode 100644 index 0000000..217109c Binary files /dev/null and b/src/assets/images/icons/enablebubble.png differ diff --git a/src/assets/images/icons/help.png b/src/assets/images/icons/help.png new file mode 100644 index 0000000..50d8aa8 Binary files /dev/null and b/src/assets/images/icons/help.png differ diff --git a/src/assets/images/icons/house-small.png b/src/assets/images/icons/house-small.png new file mode 100644 index 0000000..e106a45 Binary files /dev/null and b/src/assets/images/icons/house-small.png differ diff --git a/src/assets/images/icons/icon_cog.png b/src/assets/images/icons/icon_cog.png new file mode 100644 index 0000000..7175afb Binary files /dev/null and b/src/assets/images/icons/icon_cog.png differ diff --git a/src/assets/images/icons/like-room.png b/src/assets/images/icons/like-room.png new file mode 100644 index 0000000..1c13cd8 Binary files /dev/null and b/src/assets/images/icons/like-room.png differ diff --git a/src/assets/images/icons/loading-icon.png b/src/assets/images/icons/loading-icon.png new file mode 100644 index 0000000..e3d64d0 Binary files /dev/null and b/src/assets/images/icons/loading-icon.png differ diff --git a/src/assets/images/icons/room-history-back-disabled.png b/src/assets/images/icons/room-history-back-disabled.png new file mode 100644 index 0000000..78a4475 Binary files /dev/null and b/src/assets/images/icons/room-history-back-disabled.png differ diff --git a/src/assets/images/icons/room-history-back-enabled.png b/src/assets/images/icons/room-history-back-enabled.png new file mode 100644 index 0000000..bed6a42 Binary files /dev/null and b/src/assets/images/icons/room-history-back-enabled.png differ diff --git a/src/assets/images/icons/room-history-disabled.png b/src/assets/images/icons/room-history-disabled.png new file mode 100644 index 0000000..fcd8119 Binary files /dev/null and b/src/assets/images/icons/room-history-disabled.png differ diff --git a/src/assets/images/icons/room-history-enabled.png b/src/assets/images/icons/room-history-enabled.png new file mode 100644 index 0000000..287227b Binary files /dev/null and b/src/assets/images/icons/room-history-enabled.png differ diff --git a/src/assets/images/icons/room-history-next-disabled.png b/src/assets/images/icons/room-history-next-disabled.png new file mode 100644 index 0000000..3f82d0e Binary files /dev/null and b/src/assets/images/icons/room-history-next-disabled.png differ diff --git a/src/assets/images/icons/room-history-next-enabled.png b/src/assets/images/icons/room-history-next-enabled.png new file mode 100644 index 0000000..3de01c0 Binary files /dev/null and b/src/assets/images/icons/room-history-next-enabled.png differ diff --git a/src/assets/images/icons/room-link.png b/src/assets/images/icons/room-link.png new file mode 100644 index 0000000..efbebf4 Binary files /dev/null and b/src/assets/images/icons/room-link.png differ diff --git a/src/assets/images/icons/sign-exclamation.png b/src/assets/images/icons/sign-exclamation.png new file mode 100644 index 0000000..7db61fb Binary files /dev/null and b/src/assets/images/icons/sign-exclamation.png differ diff --git a/src/assets/images/icons/sign-heart.png b/src/assets/images/icons/sign-heart.png new file mode 100644 index 0000000..8ac5685 Binary files /dev/null and b/src/assets/images/icons/sign-heart.png differ diff --git a/src/assets/images/icons/sign-red.png b/src/assets/images/icons/sign-red.png new file mode 100644 index 0000000..ac0915e Binary files /dev/null and b/src/assets/images/icons/sign-red.png differ diff --git a/src/assets/images/icons/sign-skull.png b/src/assets/images/icons/sign-skull.png new file mode 100644 index 0000000..6221d9a Binary files /dev/null and b/src/assets/images/icons/sign-skull.png differ diff --git a/src/assets/images/icons/sign-smile.png b/src/assets/images/icons/sign-smile.png new file mode 100644 index 0000000..1a1721c Binary files /dev/null and b/src/assets/images/icons/sign-smile.png differ diff --git a/src/assets/images/icons/sign-soccer.png b/src/assets/images/icons/sign-soccer.png new file mode 100644 index 0000000..334f1fa Binary files /dev/null and b/src/assets/images/icons/sign-soccer.png differ diff --git a/src/assets/images/icons/sign-yellow.png b/src/assets/images/icons/sign-yellow.png new file mode 100644 index 0000000..272358f Binary files /dev/null and b/src/assets/images/icons/sign-yellow.png differ diff --git a/src/assets/images/icons/small-room.png b/src/assets/images/icons/small-room.png new file mode 100644 index 0000000..c8bbbcc Binary files /dev/null and b/src/assets/images/icons/small-room.png differ diff --git a/src/assets/images/icons/tickets.png b/src/assets/images/icons/tickets.png new file mode 100644 index 0000000..81f4883 Binary files /dev/null and b/src/assets/images/icons/tickets.png differ diff --git a/src/assets/images/icons/user.png b/src/assets/images/icons/user.png new file mode 100644 index 0000000..c4155a2 Binary files /dev/null and b/src/assets/images/icons/user.png differ diff --git a/src/assets/images/icons/zoom-less.png b/src/assets/images/icons/zoom-less.png new file mode 100644 index 0000000..36423da Binary files /dev/null and b/src/assets/images/icons/zoom-less.png differ diff --git a/src/assets/images/icons/zoom-more.png b/src/assets/images/icons/zoom-more.png new file mode 100644 index 0000000..c14a9e8 Binary files /dev/null and b/src/assets/images/icons/zoom-more.png differ diff --git a/src/assets/images/infostand/bot_background.png b/src/assets/images/infostand/bot_background.png new file mode 100644 index 0000000..cd460bb Binary files /dev/null and b/src/assets/images/infostand/bot_background.png differ diff --git a/src/assets/images/infostand/countown-timer.png b/src/assets/images/infostand/countown-timer.png new file mode 100644 index 0000000..ebfe629 Binary files /dev/null and b/src/assets/images/infostand/countown-timer.png differ diff --git a/src/assets/images/infostand/disk-creator.png b/src/assets/images/infostand/disk-creator.png new file mode 100644 index 0000000..c4e95c9 Binary files /dev/null and b/src/assets/images/infostand/disk-creator.png differ diff --git a/src/assets/images/infostand/disk-icon.png b/src/assets/images/infostand/disk-icon.png new file mode 100644 index 0000000..9ee4ed8 Binary files /dev/null and b/src/assets/images/infostand/disk-icon.png differ diff --git a/src/assets/images/infostand/icon_edit.gif b/src/assets/images/infostand/icon_edit.gif new file mode 100644 index 0000000..aabdb0a Binary files /dev/null and b/src/assets/images/infostand/icon_edit.gif differ diff --git a/src/assets/images/infostand/pencil-icon.png b/src/assets/images/infostand/pencil-icon.png new file mode 100644 index 0000000..27de0d6 Binary files /dev/null and b/src/assets/images/infostand/pencil-icon.png differ diff --git a/src/assets/images/infostand/rarity-level.png b/src/assets/images/infostand/rarity-level.png new file mode 100644 index 0000000..eb1278e Binary files /dev/null and b/src/assets/images/infostand/rarity-level.png differ diff --git a/src/assets/images/inventory/empty.png b/src/assets/images/inventory/empty.png new file mode 100644 index 0000000..d975b41 Binary files /dev/null and b/src/assets/images/inventory/empty.png differ diff --git a/src/assets/images/inventory/rarity-level.png b/src/assets/images/inventory/rarity-level.png new file mode 100644 index 0000000..218eccb Binary files /dev/null and b/src/assets/images/inventory/rarity-level.png differ diff --git a/src/assets/images/inventory/trading/locked-icon.png b/src/assets/images/inventory/trading/locked-icon.png new file mode 100644 index 0000000..4f54e2d Binary files /dev/null and b/src/assets/images/inventory/trading/locked-icon.png differ diff --git a/src/assets/images/inventory/trading/unlocked-icon.png b/src/assets/images/inventory/trading/unlocked-icon.png new file mode 100644 index 0000000..d6362c4 Binary files /dev/null and b/src/assets/images/inventory/trading/unlocked-icon.png differ diff --git a/src/assets/images/loading/loading.gif b/src/assets/images/loading/loading.gif new file mode 100644 index 0000000..1303b75 Binary files /dev/null and b/src/assets/images/loading/loading.gif differ diff --git a/src/assets/images/loading/progress_habbos.gif b/src/assets/images/loading/progress_habbos.gif new file mode 100644 index 0000000..2224994 Binary files /dev/null and b/src/assets/images/loading/progress_habbos.gif differ diff --git a/src/assets/images/modtool/chatlog.gif b/src/assets/images/modtool/chatlog.gif new file mode 100644 index 0000000..a64ca0b Binary files /dev/null and b/src/assets/images/modtool/chatlog.gif differ diff --git a/src/assets/images/modtool/key.gif b/src/assets/images/modtool/key.gif new file mode 100644 index 0000000..578ee65 Binary files /dev/null and b/src/assets/images/modtool/key.gif differ diff --git a/src/assets/images/modtool/m_icon.png b/src/assets/images/modtool/m_icon.png new file mode 100644 index 0000000..1116b4d Binary files /dev/null and b/src/assets/images/modtool/m_icon.png differ diff --git a/src/assets/images/modtool/reports.png b/src/assets/images/modtool/reports.png new file mode 100644 index 0000000..4731fed Binary files /dev/null and b/src/assets/images/modtool/reports.png differ diff --git a/src/assets/images/modtool/room.gif b/src/assets/images/modtool/room.gif new file mode 100644 index 0000000..94e77dd Binary files /dev/null and b/src/assets/images/modtool/room.gif differ diff --git a/src/assets/images/modtool/room.png b/src/assets/images/modtool/room.png new file mode 100644 index 0000000..2ce5efa Binary files /dev/null and b/src/assets/images/modtool/room.png differ diff --git a/src/assets/images/modtool/user.gif b/src/assets/images/modtool/user.gif new file mode 100644 index 0000000..ab9a590 Binary files /dev/null and b/src/assets/images/modtool/user.gif differ diff --git a/src/assets/images/modtool/wrench.gif b/src/assets/images/modtool/wrench.gif new file mode 100644 index 0000000..530c78a Binary files /dev/null and b/src/assets/images/modtool/wrench.gif differ diff --git a/src/assets/images/mysterybox/chain_mysterybox_box_overlay.png b/src/assets/images/mysterybox/chain_mysterybox_box_overlay.png new file mode 100644 index 0000000..5914aa5 Binary files /dev/null and b/src/assets/images/mysterybox/chain_mysterybox_box_overlay.png differ diff --git a/src/assets/images/mysterybox/key_overlay.png b/src/assets/images/mysterybox/key_overlay.png new file mode 100644 index 0000000..8f8c2a5 Binary files /dev/null and b/src/assets/images/mysterybox/key_overlay.png differ diff --git a/src/assets/images/mysterybox/mystery_box.png b/src/assets/images/mysterybox/mystery_box.png new file mode 100644 index 0000000..e85f966 Binary files /dev/null and b/src/assets/images/mysterybox/mystery_box.png differ diff --git a/src/assets/images/mysterybox/mystery_box_key.png b/src/assets/images/mysterybox/mystery_box_key.png new file mode 100644 index 0000000..79b43de Binary files /dev/null and b/src/assets/images/mysterybox/mystery_box_key.png differ diff --git a/src/assets/images/mysterytrophy/frank_mystery_trophy.png b/src/assets/images/mysterytrophy/frank_mystery_trophy.png new file mode 100644 index 0000000..67bfeba Binary files /dev/null and b/src/assets/images/mysterytrophy/frank_mystery_trophy.png differ diff --git a/src/assets/images/navigator/icons/info.png b/src/assets/images/navigator/icons/info.png new file mode 100644 index 0000000..b32d14d Binary files /dev/null and b/src/assets/images/navigator/icons/info.png differ diff --git a/src/assets/images/navigator/icons/room_group.png b/src/assets/images/navigator/icons/room_group.png new file mode 100644 index 0000000..b059ba4 Binary files /dev/null and b/src/assets/images/navigator/icons/room_group.png differ diff --git a/src/assets/images/navigator/icons/room_invisible.png b/src/assets/images/navigator/icons/room_invisible.png new file mode 100644 index 0000000..976fe8b Binary files /dev/null and b/src/assets/images/navigator/icons/room_invisible.png differ diff --git a/src/assets/images/navigator/icons/room_locked.png b/src/assets/images/navigator/icons/room_locked.png new file mode 100644 index 0000000..f46843c Binary files /dev/null and b/src/assets/images/navigator/icons/room_locked.png differ diff --git a/src/assets/images/navigator/icons/room_password.png b/src/assets/images/navigator/icons/room_password.png new file mode 100644 index 0000000..9fa392f Binary files /dev/null and b/src/assets/images/navigator/icons/room_password.png differ diff --git a/src/assets/images/navigator/models/model_0.png b/src/assets/images/navigator/models/model_0.png new file mode 100644 index 0000000..8b7b1d3 Binary files /dev/null and b/src/assets/images/navigator/models/model_0.png differ diff --git a/src/assets/images/navigator/models/model_1.png b/src/assets/images/navigator/models/model_1.png new file mode 100644 index 0000000..36f325a Binary files /dev/null and b/src/assets/images/navigator/models/model_1.png differ diff --git a/src/assets/images/navigator/models/model_2.png b/src/assets/images/navigator/models/model_2.png new file mode 100644 index 0000000..921d6f1 Binary files /dev/null and b/src/assets/images/navigator/models/model_2.png differ diff --git a/src/assets/images/navigator/models/model_3.png b/src/assets/images/navigator/models/model_3.png new file mode 100644 index 0000000..0444324 Binary files /dev/null and b/src/assets/images/navigator/models/model_3.png differ diff --git a/src/assets/images/navigator/models/model_4.png b/src/assets/images/navigator/models/model_4.png new file mode 100644 index 0000000..e371475 Binary files /dev/null and b/src/assets/images/navigator/models/model_4.png differ diff --git a/src/assets/images/navigator/models/model_5.png b/src/assets/images/navigator/models/model_5.png new file mode 100644 index 0000000..4036e1d Binary files /dev/null and b/src/assets/images/navigator/models/model_5.png differ diff --git a/src/assets/images/navigator/models/model_6.png b/src/assets/images/navigator/models/model_6.png new file mode 100644 index 0000000..dd14b3b Binary files /dev/null and b/src/assets/images/navigator/models/model_6.png differ diff --git a/src/assets/images/navigator/models/model_7.png b/src/assets/images/navigator/models/model_7.png new file mode 100644 index 0000000..031751e Binary files /dev/null and b/src/assets/images/navigator/models/model_7.png differ diff --git a/src/assets/images/navigator/models/model_8.png b/src/assets/images/navigator/models/model_8.png new file mode 100644 index 0000000..7e38e1f Binary files /dev/null and b/src/assets/images/navigator/models/model_8.png differ diff --git a/src/assets/images/navigator/models/model_9.png b/src/assets/images/navigator/models/model_9.png new file mode 100644 index 0000000..0f36c7c Binary files /dev/null and b/src/assets/images/navigator/models/model_9.png differ diff --git a/src/assets/images/navigator/models/model_a.png b/src/assets/images/navigator/models/model_a.png new file mode 100644 index 0000000..cc4a072 Binary files /dev/null and b/src/assets/images/navigator/models/model_a.png differ diff --git a/src/assets/images/navigator/models/model_b.png b/src/assets/images/navigator/models/model_b.png new file mode 100644 index 0000000..49b780a Binary files /dev/null and b/src/assets/images/navigator/models/model_b.png differ diff --git a/src/assets/images/navigator/models/model_c.png b/src/assets/images/navigator/models/model_c.png new file mode 100644 index 0000000..2ce5efa Binary files /dev/null and b/src/assets/images/navigator/models/model_c.png differ diff --git a/src/assets/images/navigator/models/model_d.png b/src/assets/images/navigator/models/model_d.png new file mode 100644 index 0000000..de061c8 Binary files /dev/null and b/src/assets/images/navigator/models/model_d.png differ diff --git a/src/assets/images/navigator/models/model_e.png b/src/assets/images/navigator/models/model_e.png new file mode 100644 index 0000000..039b927 Binary files /dev/null and b/src/assets/images/navigator/models/model_e.png differ diff --git a/src/assets/images/navigator/models/model_f.png b/src/assets/images/navigator/models/model_f.png new file mode 100644 index 0000000..4b4fadb Binary files /dev/null and b/src/assets/images/navigator/models/model_f.png differ diff --git a/src/assets/images/navigator/models/model_g.png b/src/assets/images/navigator/models/model_g.png new file mode 100644 index 0000000..26d0372 Binary files /dev/null and b/src/assets/images/navigator/models/model_g.png differ diff --git a/src/assets/images/navigator/models/model_h.png b/src/assets/images/navigator/models/model_h.png new file mode 100644 index 0000000..d8c4be7 Binary files /dev/null and b/src/assets/images/navigator/models/model_h.png differ diff --git a/src/assets/images/navigator/models/model_i.png b/src/assets/images/navigator/models/model_i.png new file mode 100644 index 0000000..f5e3d55 Binary files /dev/null and b/src/assets/images/navigator/models/model_i.png differ diff --git a/src/assets/images/navigator/models/model_j.png b/src/assets/images/navigator/models/model_j.png new file mode 100644 index 0000000..8be8f67 Binary files /dev/null and b/src/assets/images/navigator/models/model_j.png differ diff --git a/src/assets/images/navigator/models/model_k.png b/src/assets/images/navigator/models/model_k.png new file mode 100644 index 0000000..96fcc8b Binary files /dev/null and b/src/assets/images/navigator/models/model_k.png differ diff --git a/src/assets/images/navigator/models/model_l.png b/src/assets/images/navigator/models/model_l.png new file mode 100644 index 0000000..f479323 Binary files /dev/null and b/src/assets/images/navigator/models/model_l.png differ diff --git a/src/assets/images/navigator/models/model_m.png b/src/assets/images/navigator/models/model_m.png new file mode 100644 index 0000000..d1d8dd7 Binary files /dev/null and b/src/assets/images/navigator/models/model_m.png differ diff --git a/src/assets/images/navigator/models/model_n.png b/src/assets/images/navigator/models/model_n.png new file mode 100644 index 0000000..6e023a1 Binary files /dev/null and b/src/assets/images/navigator/models/model_n.png differ diff --git a/src/assets/images/navigator/models/model_o.png b/src/assets/images/navigator/models/model_o.png new file mode 100644 index 0000000..4587064 Binary files /dev/null and b/src/assets/images/navigator/models/model_o.png differ diff --git a/src/assets/images/navigator/models/model_p.png b/src/assets/images/navigator/models/model_p.png new file mode 100644 index 0000000..356601e Binary files /dev/null and b/src/assets/images/navigator/models/model_p.png differ diff --git a/src/assets/images/navigator/models/model_q.png b/src/assets/images/navigator/models/model_q.png new file mode 100644 index 0000000..9208a14 Binary files /dev/null and b/src/assets/images/navigator/models/model_q.png differ diff --git a/src/assets/images/navigator/models/model_r.png b/src/assets/images/navigator/models/model_r.png new file mode 100644 index 0000000..a93d80d Binary files /dev/null and b/src/assets/images/navigator/models/model_r.png differ diff --git a/src/assets/images/navigator/models/model_snowwar1.png b/src/assets/images/navigator/models/model_snowwar1.png new file mode 100644 index 0000000..41bab59 Binary files /dev/null and b/src/assets/images/navigator/models/model_snowwar1.png differ diff --git a/src/assets/images/navigator/models/model_snowwar2.png b/src/assets/images/navigator/models/model_snowwar2.png new file mode 100644 index 0000000..41bab59 Binary files /dev/null and b/src/assets/images/navigator/models/model_snowwar2.png differ diff --git a/src/assets/images/navigator/models/model_t.png b/src/assets/images/navigator/models/model_t.png new file mode 100644 index 0000000..920255d Binary files /dev/null and b/src/assets/images/navigator/models/model_t.png differ diff --git a/src/assets/images/navigator/models/model_u.png b/src/assets/images/navigator/models/model_u.png new file mode 100644 index 0000000..96da101 Binary files /dev/null and b/src/assets/images/navigator/models/model_u.png differ diff --git a/src/assets/images/navigator/models/model_v.png b/src/assets/images/navigator/models/model_v.png new file mode 100644 index 0000000..6d85c22 Binary files /dev/null and b/src/assets/images/navigator/models/model_v.png differ diff --git a/src/assets/images/navigator/models/model_w.png b/src/assets/images/navigator/models/model_w.png new file mode 100644 index 0000000..7bc8024 Binary files /dev/null and b/src/assets/images/navigator/models/model_w.png differ diff --git a/src/assets/images/navigator/models/model_x.png b/src/assets/images/navigator/models/model_x.png new file mode 100644 index 0000000..ce04037 Binary files /dev/null and b/src/assets/images/navigator/models/model_x.png differ diff --git a/src/assets/images/navigator/models/model_y.png b/src/assets/images/navigator/models/model_y.png new file mode 100644 index 0000000..430344c Binary files /dev/null and b/src/assets/images/navigator/models/model_y.png differ diff --git a/src/assets/images/navigator/models/model_z.png b/src/assets/images/navigator/models/model_z.png new file mode 100644 index 0000000..0809c91 Binary files /dev/null and b/src/assets/images/navigator/models/model_z.png differ diff --git a/src/assets/images/navigator/thumbnail_placeholder.png b/src/assets/images/navigator/thumbnail_placeholder.png new file mode 100644 index 0000000..be26f84 Binary files /dev/null and b/src/assets/images/navigator/thumbnail_placeholder.png differ diff --git a/src/assets/images/nitro/nitro-dark.svg b/src/assets/images/nitro/nitro-dark.svg new file mode 100644 index 0000000..20cc533 --- /dev/null +++ b/src/assets/images/nitro/nitro-dark.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/images/nitro/nitro-light.svg b/src/assets/images/nitro/nitro-light.svg new file mode 100644 index 0000000..5706684 --- /dev/null +++ b/src/assets/images/nitro/nitro-light.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/images/nitro/nitro-n-dark.svg b/src/assets/images/nitro/nitro-n-dark.svg new file mode 100644 index 0000000..f8d0ebd --- /dev/null +++ b/src/assets/images/nitro/nitro-n-dark.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/src/assets/images/nitro/nitro-n-light.svg b/src/assets/images/nitro/nitro-n-light.svg new file mode 100644 index 0000000..4dd94fc --- /dev/null +++ b/src/assets/images/nitro/nitro-n-light.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/src/assets/images/notifications/coolui.png b/src/assets/images/notifications/coolui.png new file mode 100644 index 0000000..b78ce8b Binary files /dev/null and b/src/assets/images/notifications/coolui.png differ diff --git a/src/assets/images/notifications/frank.gif b/src/assets/images/notifications/frank.gif new file mode 100644 index 0000000..211634f Binary files /dev/null and b/src/assets/images/notifications/frank.gif differ diff --git a/src/assets/images/pets/pet-package/gnome.png b/src/assets/images/pets/pet-package/gnome.png new file mode 100644 index 0000000..2c38828 Binary files /dev/null and b/src/assets/images/pets/pet-package/gnome.png differ diff --git a/src/assets/images/pets/pet-package/leprechaun_box.png b/src/assets/images/pets/pet-package/leprechaun_box.png new file mode 100644 index 0000000..1603eb8 Binary files /dev/null and b/src/assets/images/pets/pet-package/leprechaun_box.png differ diff --git a/src/assets/images/pets/pet-package/petbox_epic.png b/src/assets/images/pets/pet-package/petbox_epic.png new file mode 100644 index 0000000..e09ad77 Binary files /dev/null and b/src/assets/images/pets/pet-package/petbox_epic.png differ diff --git a/src/assets/images/pets/pet-package/pterosaur_egg.png b/src/assets/images/pets/pet-package/pterosaur_egg.png new file mode 100644 index 0000000..43ee141 Binary files /dev/null and b/src/assets/images/pets/pet-package/pterosaur_egg.png differ diff --git a/src/assets/images/pets/pet-package/val11_present.png b/src/assets/images/pets/pet-package/val11_present.png new file mode 100644 index 0000000..3d371b5 Binary files /dev/null and b/src/assets/images/pets/pet-package/val11_present.png differ diff --git a/src/assets/images/pets/pet-package/velociraptor_egg.png b/src/assets/images/pets/pet-package/velociraptor_egg.png new file mode 100644 index 0000000..242f0df Binary files /dev/null and b/src/assets/images/pets/pet-package/velociraptor_egg.png differ diff --git a/src/assets/images/prize/prize_background.png b/src/assets/images/prize/prize_background.png new file mode 100644 index 0000000..ec9c030 Binary files /dev/null and b/src/assets/images/prize/prize_background.png differ diff --git a/src/assets/images/profile/icons/offline.png b/src/assets/images/profile/icons/offline.png new file mode 100644 index 0000000..677aadc Binary files /dev/null and b/src/assets/images/profile/icons/offline.png differ diff --git a/src/assets/images/profile/icons/online.gif b/src/assets/images/profile/icons/online.gif new file mode 100644 index 0000000..3a79838 Binary files /dev/null and b/src/assets/images/profile/icons/online.gif differ diff --git a/src/assets/images/profile/icons/tick.png b/src/assets/images/profile/icons/tick.png new file mode 100644 index 0000000..ec8c52f Binary files /dev/null and b/src/assets/images/profile/icons/tick.png differ diff --git a/src/assets/images/room-spectator/room_spectator_bottom_left.png b/src/assets/images/room-spectator/room_spectator_bottom_left.png new file mode 100644 index 0000000..01688cb Binary files /dev/null and b/src/assets/images/room-spectator/room_spectator_bottom_left.png differ diff --git a/src/assets/images/room-spectator/room_spectator_bottom_right.png b/src/assets/images/room-spectator/room_spectator_bottom_right.png new file mode 100644 index 0000000..59c8ef2 Binary files /dev/null and b/src/assets/images/room-spectator/room_spectator_bottom_right.png differ diff --git a/src/assets/images/room-spectator/room_spectator_middle_bottom.png b/src/assets/images/room-spectator/room_spectator_middle_bottom.png new file mode 100644 index 0000000..ba6fdec Binary files /dev/null and b/src/assets/images/room-spectator/room_spectator_middle_bottom.png differ diff --git a/src/assets/images/room-spectator/room_spectator_middle_left.png b/src/assets/images/room-spectator/room_spectator_middle_left.png new file mode 100644 index 0000000..6d9aaa7 Binary files /dev/null and b/src/assets/images/room-spectator/room_spectator_middle_left.png differ diff --git a/src/assets/images/room-spectator/room_spectator_middle_right.png b/src/assets/images/room-spectator/room_spectator_middle_right.png new file mode 100644 index 0000000..9d963b3 Binary files /dev/null and b/src/assets/images/room-spectator/room_spectator_middle_right.png differ diff --git a/src/assets/images/room-spectator/room_spectator_middle_top.png b/src/assets/images/room-spectator/room_spectator_middle_top.png new file mode 100644 index 0000000..f6559ce Binary files /dev/null and b/src/assets/images/room-spectator/room_spectator_middle_top.png differ diff --git a/src/assets/images/room-spectator/room_spectator_top_left.png b/src/assets/images/room-spectator/room_spectator_top_left.png new file mode 100644 index 0000000..5e62a3c Binary files /dev/null and b/src/assets/images/room-spectator/room_spectator_top_left.png differ diff --git a/src/assets/images/room-spectator/room_spectator_top_right.png b/src/assets/images/room-spectator/room_spectator_top_right.png new file mode 100644 index 0000000..825f3fb Binary files /dev/null and b/src/assets/images/room-spectator/room_spectator_top_right.png differ diff --git a/src/assets/images/room-widgets/avatar-info/preview-background.png b/src/assets/images/room-widgets/avatar-info/preview-background.png new file mode 100644 index 0000000..dea4f08 Binary files /dev/null and b/src/assets/images/room-widgets/avatar-info/preview-background.png differ diff --git a/src/assets/images/room-widgets/camera-widget/btn.png b/src/assets/images/room-widgets/camera-widget/btn.png new file mode 100644 index 0000000..76b086b Binary files /dev/null and b/src/assets/images/room-widgets/camera-widget/btn.png differ diff --git a/src/assets/images/room-widgets/camera-widget/btn_down.png b/src/assets/images/room-widgets/camera-widget/btn_down.png new file mode 100644 index 0000000..76f25da Binary files /dev/null and b/src/assets/images/room-widgets/camera-widget/btn_down.png differ diff --git a/src/assets/images/room-widgets/camera-widget/btn_hi.png b/src/assets/images/room-widgets/camera-widget/btn_hi.png new file mode 100644 index 0000000..5f04fc0 Binary files /dev/null and b/src/assets/images/room-widgets/camera-widget/btn_hi.png differ diff --git a/src/assets/images/room-widgets/camera-widget/cam_bg.png b/src/assets/images/room-widgets/camera-widget/cam_bg.png new file mode 100644 index 0000000..d6cf994 Binary files /dev/null and b/src/assets/images/room-widgets/camera-widget/cam_bg.png differ diff --git a/src/assets/images/room-widgets/camera-widget/camera-spritesheet.png b/src/assets/images/room-widgets/camera-widget/camera-spritesheet.png new file mode 100644 index 0000000..4ea82e3 Binary files /dev/null and b/src/assets/images/room-widgets/camera-widget/camera-spritesheet.png differ diff --git a/src/assets/images/room-widgets/camera-widget/viewfinder.png b/src/assets/images/room-widgets/camera-widget/viewfinder.png new file mode 100644 index 0000000..ab6a9b2 Binary files /dev/null and b/src/assets/images/room-widgets/camera-widget/viewfinder.png differ diff --git a/src/assets/images/room-widgets/dimmer-widget/dimmer_banner.png b/src/assets/images/room-widgets/dimmer-widget/dimmer_banner.png new file mode 100644 index 0000000..fdc6e9f Binary files /dev/null and b/src/assets/images/room-widgets/dimmer-widget/dimmer_banner.png differ diff --git a/src/assets/images/room-widgets/engraving-lock-widget/engraving-lock-spritesheet.png b/src/assets/images/room-widgets/engraving-lock-widget/engraving-lock-spritesheet.png new file mode 100644 index 0000000..472dc85 Binary files /dev/null and b/src/assets/images/room-widgets/engraving-lock-widget/engraving-lock-spritesheet.png differ diff --git a/src/assets/images/room-widgets/exchange-credit/exchange-credit-image.png b/src/assets/images/room-widgets/exchange-credit/exchange-credit-image.png new file mode 100644 index 0000000..eef5da6 Binary files /dev/null and b/src/assets/images/room-widgets/exchange-credit/exchange-credit-image.png differ diff --git a/src/assets/images/room-widgets/furni-context-menu/monsterplant-preview.png b/src/assets/images/room-widgets/furni-context-menu/monsterplant-preview.png new file mode 100644 index 0000000..8d3d771 Binary files /dev/null and b/src/assets/images/room-widgets/furni-context-menu/monsterplant-preview.png differ diff --git a/src/assets/images/room-widgets/mannequin-widget/mannequin-spritesheet.png b/src/assets/images/room-widgets/mannequin-widget/mannequin-spritesheet.png new file mode 100644 index 0000000..45e11f3 Binary files /dev/null and b/src/assets/images/room-widgets/mannequin-widget/mannequin-spritesheet.png differ diff --git a/src/assets/images/room-widgets/playlist-editor/disk_2.png b/src/assets/images/room-widgets/playlist-editor/disk_2.png new file mode 100644 index 0000000..3033020 Binary files /dev/null and b/src/assets/images/room-widgets/playlist-editor/disk_2.png differ diff --git a/src/assets/images/room-widgets/playlist-editor/disk_image.png b/src/assets/images/room-widgets/playlist-editor/disk_image.png new file mode 100644 index 0000000..7a8ab45 Binary files /dev/null and b/src/assets/images/room-widgets/playlist-editor/disk_image.png differ diff --git a/src/assets/images/room-widgets/playlist-editor/move.png b/src/assets/images/room-widgets/playlist-editor/move.png new file mode 100644 index 0000000..9d1635d Binary files /dev/null and b/src/assets/images/room-widgets/playlist-editor/move.png differ diff --git a/src/assets/images/room-widgets/playlist-editor/pause-btn.png b/src/assets/images/room-widgets/playlist-editor/pause-btn.png new file mode 100644 index 0000000..900f99b Binary files /dev/null and b/src/assets/images/room-widgets/playlist-editor/pause-btn.png differ diff --git a/src/assets/images/room-widgets/playlist-editor/pause.png b/src/assets/images/room-widgets/playlist-editor/pause.png new file mode 100644 index 0000000..ec5fef4 Binary files /dev/null and b/src/assets/images/room-widgets/playlist-editor/pause.png differ diff --git a/src/assets/images/room-widgets/playlist-editor/playing.png b/src/assets/images/room-widgets/playlist-editor/playing.png new file mode 100644 index 0000000..0e3449d Binary files /dev/null and b/src/assets/images/room-widgets/playlist-editor/playing.png differ diff --git a/src/assets/images/room-widgets/playlist-editor/preview.png b/src/assets/images/room-widgets/playlist-editor/preview.png new file mode 100644 index 0000000..160f0be Binary files /dev/null and b/src/assets/images/room-widgets/playlist-editor/preview.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-blue.png b/src/assets/images/room-widgets/stickie-widget/stickie-blue.png new file mode 100644 index 0000000..9a14182 Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-blue.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-christmas.png b/src/assets/images/room-widgets/stickie-widget/stickie-christmas.png new file mode 100644 index 0000000..82b4732 Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-christmas.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-close.png b/src/assets/images/room-widgets/stickie-widget/stickie-close.png new file mode 100644 index 0000000..9621c56 Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-close.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-dreams.png b/src/assets/images/room-widgets/stickie-widget/stickie-dreams.png new file mode 100644 index 0000000..1723bdc Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-dreams.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-green.png b/src/assets/images/room-widgets/stickie-widget/stickie-green.png new file mode 100644 index 0000000..5e73c74 Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-green.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-heart.png b/src/assets/images/room-widgets/stickie-widget/stickie-heart.png new file mode 100644 index 0000000..4552385 Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-heart.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-juninas.png b/src/assets/images/room-widgets/stickie-widget/stickie-juninas.png new file mode 100644 index 0000000..faaea9d Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-juninas.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-pink.png b/src/assets/images/room-widgets/stickie-widget/stickie-pink.png new file mode 100644 index 0000000..7565899 Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-pink.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-shakesp.png b/src/assets/images/room-widgets/stickie-widget/stickie-shakesp.png new file mode 100644 index 0000000..d5011c7 Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-shakesp.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-spritesheet.png b/src/assets/images/room-widgets/stickie-widget/stickie-spritesheet.png new file mode 100644 index 0000000..0249571 Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-spritesheet.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-trash.png b/src/assets/images/room-widgets/stickie-widget/stickie-trash.png new file mode 100644 index 0000000..96dff8f Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-trash.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-yellow.png b/src/assets/images/room-widgets/stickie-widget/stickie-yellow.png new file mode 100644 index 0000000..759d3f9 Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-yellow.png differ diff --git a/src/assets/images/room-widgets/thumbnail-widget/thumbnail-camera-spritesheet.png b/src/assets/images/room-widgets/thumbnail-widget/thumbnail-camera-spritesheet.png new file mode 100644 index 0000000..63a9397 Binary files /dev/null and b/src/assets/images/room-widgets/thumbnail-widget/thumbnail-camera-spritesheet.png differ diff --git a/src/assets/images/room-widgets/trophy-widget/trophy-spritesheet.png b/src/assets/images/room-widgets/trophy-widget/trophy-spritesheet.png new file mode 100644 index 0000000..f9184cb Binary files /dev/null and b/src/assets/images/room-widgets/trophy-widget/trophy-spritesheet.png differ diff --git a/src/assets/images/room-widgets/wordquiz-widget/thumbs-down-small.png b/src/assets/images/room-widgets/wordquiz-widget/thumbs-down-small.png new file mode 100644 index 0000000..78e51cf Binary files /dev/null and b/src/assets/images/room-widgets/wordquiz-widget/thumbs-down-small.png differ diff --git a/src/assets/images/room-widgets/wordquiz-widget/thumbs-down.png b/src/assets/images/room-widgets/wordquiz-widget/thumbs-down.png new file mode 100644 index 0000000..fd320c5 Binary files /dev/null and b/src/assets/images/room-widgets/wordquiz-widget/thumbs-down.png differ diff --git a/src/assets/images/room-widgets/wordquiz-widget/thumbs-up-small.png b/src/assets/images/room-widgets/wordquiz-widget/thumbs-up-small.png new file mode 100644 index 0000000..b93111f Binary files /dev/null and b/src/assets/images/room-widgets/wordquiz-widget/thumbs-up-small.png differ diff --git a/src/assets/images/room-widgets/wordquiz-widget/thumbs-up.png b/src/assets/images/room-widgets/wordquiz-widget/thumbs-up.png new file mode 100644 index 0000000..dd65098 Binary files /dev/null and b/src/assets/images/room-widgets/wordquiz-widget/thumbs-up.png differ diff --git a/src/assets/images/room-widgets/youtube-widget/next.png b/src/assets/images/room-widgets/youtube-widget/next.png new file mode 100644 index 0000000..a02e164 Binary files /dev/null and b/src/assets/images/room-widgets/youtube-widget/next.png differ diff --git a/src/assets/images/room-widgets/youtube-widget/prev.png b/src/assets/images/room-widgets/youtube-widget/prev.png new file mode 100644 index 0000000..d48b658 Binary files /dev/null and b/src/assets/images/room-widgets/youtube-widget/prev.png differ diff --git a/src/assets/images/stackhelper/slider-background.png b/src/assets/images/stackhelper/slider-background.png new file mode 100644 index 0000000..20ab191 Binary files /dev/null and b/src/assets/images/stackhelper/slider-background.png differ diff --git a/src/assets/images/stackhelper/slider-pointer.png b/src/assets/images/stackhelper/slider-pointer.png new file mode 100644 index 0000000..8787456 Binary files /dev/null and b/src/assets/images/stackhelper/slider-pointer.png differ diff --git a/src/assets/images/toolbar/arrow.png b/src/assets/images/toolbar/arrow.png new file mode 100644 index 0000000..bf04ea0 Binary files /dev/null and b/src/assets/images/toolbar/arrow.png differ diff --git a/src/assets/images/toolbar/friend-search.png b/src/assets/images/toolbar/friend-search.png new file mode 100644 index 0000000..7156c4f Binary files /dev/null and b/src/assets/images/toolbar/friend-search.png differ diff --git a/src/assets/images/toolbar/icons/buildersclub.png b/src/assets/images/toolbar/icons/buildersclub.png new file mode 100644 index 0000000..bbf6d68 Binary files /dev/null and b/src/assets/images/toolbar/icons/buildersclub.png differ diff --git a/src/assets/images/toolbar/icons/camera.png b/src/assets/images/toolbar/icons/camera.png new file mode 100644 index 0000000..da5d835 Binary files /dev/null and b/src/assets/images/toolbar/icons/camera.png differ diff --git a/src/assets/images/toolbar/icons/catalog.png b/src/assets/images/toolbar/icons/catalog.png new file mode 100644 index 0000000..f680921 Binary files /dev/null and b/src/assets/images/toolbar/icons/catalog.png differ diff --git a/src/assets/images/toolbar/icons/friend_all.png b/src/assets/images/toolbar/icons/friend_all.png new file mode 100644 index 0000000..b2ca0d7 Binary files /dev/null and b/src/assets/images/toolbar/icons/friend_all.png differ diff --git a/src/assets/images/toolbar/icons/friend_head.png b/src/assets/images/toolbar/icons/friend_head.png new file mode 100644 index 0000000..6380c90 Binary files /dev/null and b/src/assets/images/toolbar/icons/friend_head.png differ diff --git a/src/assets/images/toolbar/icons/friend_search.png b/src/assets/images/toolbar/icons/friend_search.png new file mode 100644 index 0000000..ebe1c65 Binary files /dev/null and b/src/assets/images/toolbar/icons/friend_search.png differ diff --git a/src/assets/images/toolbar/icons/game.png b/src/assets/images/toolbar/icons/game.png new file mode 100644 index 0000000..59ef8aa Binary files /dev/null and b/src/assets/images/toolbar/icons/game.png differ diff --git a/src/assets/images/toolbar/icons/habbo.png b/src/assets/images/toolbar/icons/habbo.png new file mode 100644 index 0000000..78cd0a4 Binary files /dev/null and b/src/assets/images/toolbar/icons/habbo.png differ diff --git a/src/assets/images/toolbar/icons/house.png b/src/assets/images/toolbar/icons/house.png new file mode 100644 index 0000000..f2c8746 Binary files /dev/null and b/src/assets/images/toolbar/icons/house.png differ diff --git a/src/assets/images/toolbar/icons/inventory.png b/src/assets/images/toolbar/icons/inventory.png new file mode 100644 index 0000000..d848586 Binary files /dev/null and b/src/assets/images/toolbar/icons/inventory.png differ diff --git a/src/assets/images/toolbar/icons/joinroom.png b/src/assets/images/toolbar/icons/joinroom.png new file mode 100644 index 0000000..894ee78 Binary files /dev/null and b/src/assets/images/toolbar/icons/joinroom.png differ diff --git a/src/assets/images/toolbar/icons/me-menu/achievements.png b/src/assets/images/toolbar/icons/me-menu/achievements.png new file mode 100644 index 0000000..575464d Binary files /dev/null and b/src/assets/images/toolbar/icons/me-menu/achievements.png differ diff --git a/src/assets/images/toolbar/icons/me-menu/clothing.png b/src/assets/images/toolbar/icons/me-menu/clothing.png new file mode 100644 index 0000000..bfacabd Binary files /dev/null and b/src/assets/images/toolbar/icons/me-menu/clothing.png differ diff --git a/src/assets/images/toolbar/icons/me-menu/cog.png b/src/assets/images/toolbar/icons/me-menu/cog.png new file mode 100644 index 0000000..6180409 Binary files /dev/null and b/src/assets/images/toolbar/icons/me-menu/cog.png differ diff --git a/src/assets/images/toolbar/icons/me-menu/forums.png b/src/assets/images/toolbar/icons/me-menu/forums.png new file mode 100644 index 0000000..e22426e Binary files /dev/null and b/src/assets/images/toolbar/icons/me-menu/forums.png differ diff --git a/src/assets/images/toolbar/icons/me-menu/helper-tool.png b/src/assets/images/toolbar/icons/me-menu/helper-tool.png new file mode 100644 index 0000000..e324611 Binary files /dev/null and b/src/assets/images/toolbar/icons/me-menu/helper-tool.png differ diff --git a/src/assets/images/toolbar/icons/me-menu/my-rooms.png b/src/assets/images/toolbar/icons/me-menu/my-rooms.png new file mode 100644 index 0000000..8d4dcad Binary files /dev/null and b/src/assets/images/toolbar/icons/me-menu/my-rooms.png differ diff --git a/src/assets/images/toolbar/icons/me-menu/profile.png b/src/assets/images/toolbar/icons/me-menu/profile.png new file mode 100644 index 0000000..04964bf Binary files /dev/null and b/src/assets/images/toolbar/icons/me-menu/profile.png differ diff --git a/src/assets/images/toolbar/icons/me-menu/rooms.png b/src/assets/images/toolbar/icons/me-menu/rooms.png new file mode 100644 index 0000000..00261ce Binary files /dev/null and b/src/assets/images/toolbar/icons/me-menu/rooms.png differ diff --git a/src/assets/images/toolbar/icons/me-menu/talents.png b/src/assets/images/toolbar/icons/me-menu/talents.png new file mode 100644 index 0000000..2f91dfe Binary files /dev/null and b/src/assets/images/toolbar/icons/me-menu/talents.png differ diff --git a/src/assets/images/toolbar/icons/message.png b/src/assets/images/toolbar/icons/message.png new file mode 100644 index 0000000..c12d5bb Binary files /dev/null and b/src/assets/images/toolbar/icons/message.png differ diff --git a/src/assets/images/toolbar/icons/message_unsee.gif b/src/assets/images/toolbar/icons/message_unsee.gif new file mode 100644 index 0000000..eddfe1c Binary files /dev/null and b/src/assets/images/toolbar/icons/message_unsee.gif differ diff --git a/src/assets/images/toolbar/icons/modtools.png b/src/assets/images/toolbar/icons/modtools.png new file mode 100644 index 0000000..24c362f Binary files /dev/null and b/src/assets/images/toolbar/icons/modtools.png differ diff --git a/src/assets/images/toolbar/icons/rooms.png b/src/assets/images/toolbar/icons/rooms.png new file mode 100644 index 0000000..00261ce Binary files /dev/null and b/src/assets/images/toolbar/icons/rooms.png differ diff --git a/src/assets/images/toolbar/icons/sendmessage.png b/src/assets/images/toolbar/icons/sendmessage.png new file mode 100644 index 0000000..9f64b17 Binary files /dev/null and b/src/assets/images/toolbar/icons/sendmessage.png differ diff --git a/src/assets/images/ui/loading_icon.png b/src/assets/images/ui/loading_icon.png new file mode 100644 index 0000000..15f66be Binary files /dev/null and b/src/assets/images/ui/loading_icon.png differ diff --git a/src/assets/images/ui/ubuntu-close-buttons.png b/src/assets/images/ui/ubuntu-close-buttons.png new file mode 100644 index 0000000..d6a79a6 Binary files /dev/null and b/src/assets/images/ui/ubuntu-close-buttons.png differ diff --git a/src/assets/images/unique/catalog-info-amount-bg.png b/src/assets/images/unique/catalog-info-amount-bg.png new file mode 100644 index 0000000..4a56c9b Binary files /dev/null and b/src/assets/images/unique/catalog-info-amount-bg.png differ diff --git a/src/assets/images/unique/catalog-info-sold-out.png b/src/assets/images/unique/catalog-info-sold-out.png new file mode 100644 index 0000000..79626e1 Binary files /dev/null and b/src/assets/images/unique/catalog-info-sold-out.png differ diff --git a/src/assets/images/unique/grid-bg-glass.png b/src/assets/images/unique/grid-bg-glass.png new file mode 100644 index 0000000..5b64c48 Binary files /dev/null and b/src/assets/images/unique/grid-bg-glass.png differ diff --git a/src/assets/images/unique/grid-bg-sold-out.png b/src/assets/images/unique/grid-bg-sold-out.png new file mode 100644 index 0000000..94f6662 Binary files /dev/null and b/src/assets/images/unique/grid-bg-sold-out.png differ diff --git a/src/assets/images/unique/grid-bg.png b/src/assets/images/unique/grid-bg.png new file mode 100644 index 0000000..d7737ba Binary files /dev/null and b/src/assets/images/unique/grid-bg.png differ diff --git a/src/assets/images/unique/grid-count-bg.png b/src/assets/images/unique/grid-count-bg.png new file mode 100644 index 0000000..68e13bd Binary files /dev/null and b/src/assets/images/unique/grid-count-bg.png differ diff --git a/src/assets/images/unique/inventory-info-amount-bg.png b/src/assets/images/unique/inventory-info-amount-bg.png new file mode 100644 index 0000000..af4e31e Binary files /dev/null and b/src/assets/images/unique/inventory-info-amount-bg.png differ diff --git a/src/assets/images/unique/numbers.png b/src/assets/images/unique/numbers.png new file mode 100644 index 0000000..e1ece79 Binary files /dev/null and b/src/assets/images/unique/numbers.png differ diff --git a/src/assets/images/wired/card-action-corners.png b/src/assets/images/wired/card-action-corners.png new file mode 100644 index 0000000..faec234 Binary files /dev/null and b/src/assets/images/wired/card-action-corners.png differ diff --git a/src/assets/images/wired/icon_action.png b/src/assets/images/wired/icon_action.png new file mode 100644 index 0000000..78e90e6 Binary files /dev/null and b/src/assets/images/wired/icon_action.png differ diff --git a/src/assets/images/wired/icon_condition.png b/src/assets/images/wired/icon_condition.png new file mode 100644 index 0000000..26925a6 Binary files /dev/null and b/src/assets/images/wired/icon_condition.png differ diff --git a/src/assets/images/wired/icon_trigger.png b/src/assets/images/wired/icon_trigger.png new file mode 100644 index 0000000..f48d13c Binary files /dev/null and b/src/assets/images/wired/icon_trigger.png differ diff --git a/src/assets/images/wired/icon_wired_around.png b/src/assets/images/wired/icon_wired_around.png new file mode 100644 index 0000000..0b4b5a1 Binary files /dev/null and b/src/assets/images/wired/icon_wired_around.png differ diff --git a/src/assets/images/wired/icon_wired_left_right.png b/src/assets/images/wired/icon_wired_left_right.png new file mode 100644 index 0000000..862d6d8 Binary files /dev/null and b/src/assets/images/wired/icon_wired_left_right.png differ diff --git a/src/assets/images/wired/icon_wired_north_east.png b/src/assets/images/wired/icon_wired_north_east.png new file mode 100644 index 0000000..3710854 Binary files /dev/null and b/src/assets/images/wired/icon_wired_north_east.png differ diff --git a/src/assets/images/wired/icon_wired_north_west.png b/src/assets/images/wired/icon_wired_north_west.png new file mode 100644 index 0000000..09eeefc Binary files /dev/null and b/src/assets/images/wired/icon_wired_north_west.png differ diff --git a/src/assets/images/wired/icon_wired_rotate_clockwise.png b/src/assets/images/wired/icon_wired_rotate_clockwise.png new file mode 100644 index 0000000..2827e3d Binary files /dev/null and b/src/assets/images/wired/icon_wired_rotate_clockwise.png differ diff --git a/src/assets/images/wired/icon_wired_rotate_counter_clockwise.png b/src/assets/images/wired/icon_wired_rotate_counter_clockwise.png new file mode 100644 index 0000000..7e281ba Binary files /dev/null and b/src/assets/images/wired/icon_wired_rotate_counter_clockwise.png differ diff --git a/src/assets/images/wired/icon_wired_south_east.png b/src/assets/images/wired/icon_wired_south_east.png new file mode 100644 index 0000000..4217c4b Binary files /dev/null and b/src/assets/images/wired/icon_wired_south_east.png differ diff --git a/src/assets/images/wired/icon_wired_south_west.png b/src/assets/images/wired/icon_wired_south_west.png new file mode 100644 index 0000000..07ab1f9 Binary files /dev/null and b/src/assets/images/wired/icon_wired_south_west.png differ diff --git a/src/assets/images/wired/icon_wired_up_down.png b/src/assets/images/wired/icon_wired_up_down.png new file mode 100644 index 0000000..c2d243b Binary files /dev/null and b/src/assets/images/wired/icon_wired_up_down.png differ diff --git a/src/assets/webfonts/Ubuntu-C.ttf b/src/assets/webfonts/Ubuntu-C.ttf new file mode 100644 index 0000000..8e2c4bc Binary files /dev/null and b/src/assets/webfonts/Ubuntu-C.ttf differ diff --git a/src/assets/webfonts/Ubuntu-b.ttf b/src/assets/webfonts/Ubuntu-b.ttf new file mode 100644 index 0000000..9073aa2 Binary files /dev/null and b/src/assets/webfonts/Ubuntu-b.ttf differ diff --git a/src/assets/webfonts/Ubuntu-i.ttf b/src/assets/webfonts/Ubuntu-i.ttf new file mode 100644 index 0000000..1be5141 Binary files /dev/null and b/src/assets/webfonts/Ubuntu-i.ttf differ diff --git a/src/assets/webfonts/Ubuntu-ib.ttf b/src/assets/webfonts/Ubuntu-ib.ttf new file mode 100644 index 0000000..13ecca8 Binary files /dev/null and b/src/assets/webfonts/Ubuntu-ib.ttf differ diff --git a/src/assets/webfonts/Ubuntu-m.ttf b/src/assets/webfonts/Ubuntu-m.ttf new file mode 100644 index 0000000..8de0928 Binary files /dev/null and b/src/assets/webfonts/Ubuntu-m.ttf differ diff --git a/src/assets/webfonts/Ubuntu.ttf b/src/assets/webfonts/Ubuntu.ttf new file mode 100644 index 0000000..975da10 Binary files /dev/null and b/src/assets/webfonts/Ubuntu.ttf differ diff --git a/src/common/AutoGrid.tsx b/src/common/AutoGrid.tsx new file mode 100644 index 0000000..167ee92 --- /dev/null +++ b/src/common/AutoGrid.tsx @@ -0,0 +1,28 @@ +import { CSSProperties, FC, useMemo } from 'react'; +import { Grid, GridProps } from './Grid'; + +export interface AutoGridProps extends GridProps +{ + columnMinWidth?: number; + columnMinHeight?: number; +} + +export const AutoGrid: FC = props => +{ + const { columnMinWidth = 40, columnMinHeight = 40, columnCount = 0, fullHeight = false, maxContent = true, overflow = 'auto', style = {}, ...rest } = props; + + const getStyle = useMemo(() => + { + let newStyle: CSSProperties = {}; + + newStyle['--nitro-grid-column-min-height'] = (columnMinHeight + 'px'); + + if(columnCount > 1) newStyle.gridTemplateColumns = `repeat(auto-fill, minmax(${ columnMinWidth }px, 1fr))`; + + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; + + return newStyle; + }, [ columnMinWidth, columnMinHeight, columnCount, style ]); + + return ; +}; diff --git a/src/common/Base.tsx b/src/common/Base.tsx new file mode 100644 index 0000000..a2747c6 --- /dev/null +++ b/src/common/Base.tsx @@ -0,0 +1,84 @@ +import { CSSProperties, DetailedHTMLProps, FC, HTMLAttributes, MutableRefObject, ReactNode, useMemo } from 'react'; +import { ColorVariantType, DisplayType, FloatType, OverflowType, PositionType } from './types'; + +export interface BaseProps extends DetailedHTMLProps, T> +{ + innerRef?: MutableRefObject; + display?: DisplayType; + fit?: boolean; + fitV?: boolean; + grow?: boolean; + shrink?: boolean; + fullWidth?: boolean; + fullHeight?: boolean; + overflow?: OverflowType; + position?: PositionType; + float?: FloatType; + pointer?: boolean; + visible?: boolean; + textColor?: ColorVariantType; + classNames?: string[]; + children?: ReactNode; +} + +export const Base: FC> = props => +{ + const { ref = null, innerRef = null, display = null, fit = false, fitV = false, grow = false, shrink = false, fullWidth = false, fullHeight = false, overflow = null, position = null, float = null, pointer = false, visible = null, textColor = null, classNames = [], className = '', style = {}, children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = []; + + if(display && display.length) newClassNames.push(display); + + if(fit || fullWidth) newClassNames.push('w-full'); + + if(fit || fullHeight) newClassNames.push('h-full'); + + if(fitV) newClassNames.push('vw-full', 'vh-full'); + + if(grow) newClassNames.push('!flex-grow'); + + if(shrink) newClassNames.push('!flex-shrink-0'); + + if(overflow) newClassNames.push('overflow-' + overflow); + + if(position) newClassNames.push(position); + + if(float) newClassNames.push('float-' + float); + + if(pointer) newClassNames.push('cursor-pointer'); + + if(visible !== null) newClassNames.push(visible ? 'visible' : 'invisible'); + + if(textColor) newClassNames.push('text-' + textColor); + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ display, fit, fitV, grow, shrink, fullWidth, fullHeight, overflow, position, float, pointer, visible, textColor, classNames ]); + + const getClassName = useMemo(() => + { + let newClassName = getClassNames.join(' '); + + if(className.length) newClassName += (' ' + className); + + return newClassName.trim(); + }, [ getClassNames, className ]); + + const getStyle = useMemo(() => + { + let newStyle: CSSProperties = {}; + + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; + + return newStyle; + }, [ style ]); + + return ( +
+ { children } +
+ ); +}; diff --git a/src/common/Button.tsx b/src/common/Button.tsx new file mode 100644 index 0000000..5caca6c --- /dev/null +++ b/src/common/Button.tsx @@ -0,0 +1,71 @@ +import { FC, useMemo } from 'react'; +import { Flex, FlexProps } from './Flex'; +import { ButtonSizeType, ColorVariantType } from './types'; + +export interface ButtonProps extends FlexProps +{ + variant?: ColorVariantType; + size?: ButtonSizeType; + active?: boolean; + disabled?: boolean; +} + +export const Button: FC = props => +{ + const { variant = 'primary', size = 'sm', active = false, disabled = false, classNames = [], ...rest } = props; + + const getClassNames = useMemo(() => + { + + // fucked up method i know (i dont have a clue what im doing because im a ninja) + + const newClassNames: string[] = [ 'pointer-events-auto inline-block font-normal leading-normal text-[#fff] text-center no-underline align-middle cursor-pointer select-none border-[1px] border-[solid] border-[transparent] px-[.75rem] py-[.375rem] text-[.9rem] rounded-[.25rem] [transition:color_.15s_ease-in-out,_background-color_.15s_ease-in-out,_border-color_.15s_ease-in-out,_box-shadow_.15s_ease-in-out]' ]; + + if(variant) + { + + if(variant == 'primary') + newClassNames.push('text-white bg-[#1e7295] border-[#1e7295] [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-white hover:bg-[#1a617f] hover:border-[#185b77]'); + + if(variant == 'success') + newClassNames.push('text-white bg-[#00800b] border-[#00800b] [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-white hover:bg-[#006d09] hover:border-[#006609]'); + + if(variant == 'danger') + newClassNames.push('text-white bg-[#a81a12] border-[#a81a12] [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-white hover:bg-[#8f160f] hover:border-[#86150e]'); + + if(variant == 'warning') + newClassNames.push('text-white bg-[#ffc107] border-[#ffc107] [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-[#000] hover:bg-[#ffca2c] hover:border-[#ffc720]'); + + if(variant == 'black') + newClassNames.push('text-white bg-[#000] border-[#000] [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-white hover:bg-[#000] hover:border-[#000]'); + + if(variant == 'secondary') + newClassNames.push('text-white bg-[#185d79] border-[#185d79] [box-shadow:inset_0_2px_#ffffff26,_inset_0_-2px_#0000001a,_0_1px_#0000001a] hover:text-white hover:bg-[#144f67] hover:border-[#134a61]'); + + if(variant == 'dark') + newClassNames.push('text-white bg-dark [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-white hover:bg-[#18181bfb] hover:border-[#161619fb]'); + + if(variant == 'gray') + newClassNames.push('text-white bg-[#1e7295] border-[#1e7295] [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-white hover:bg-[#1a617f] hover:border-[#185b77]'); + + } + + if(size) + { + if(size == 'sm') + { + newClassNames.push('!px-[.5rem] !py-[.25rem] !text-[.7875rem] !rounded-[.2rem] !min-h-[28px]'); + } + } + + if(active) newClassNames.push('active'); + + if(disabled) newClassNames.push('pointer-events-none opacity-[.65] [box-shadow:none]'); + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ variant, size, active, disabled, classNames ]); + + return ; +}; diff --git a/src/common/ButtonGroup.tsx b/src/common/ButtonGroup.tsx new file mode 100644 index 0000000..033bb1f --- /dev/null +++ b/src/common/ButtonGroup.tsx @@ -0,0 +1,22 @@ +import { FC, useMemo } from 'react'; +import { Base, BaseProps } from './Base'; + +export interface ButtonGroupProps extends BaseProps +{ +} + +export const ButtonGroup: FC = props => +{ + const { classNames = [], ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'btn-group' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ; +} diff --git a/src/common/Column.tsx b/src/common/Column.tsx new file mode 100644 index 0000000..13cca1e --- /dev/null +++ b/src/common/Column.tsx @@ -0,0 +1,46 @@ +import { FC, useMemo } from 'react'; +import { Flex, FlexProps } from './Flex'; +import { useGridContext } from './GridContext'; +import { ColumnSizesType } from './types'; + +export interface ColumnProps extends FlexProps +{ + size?: ColumnSizesType; + offset?: ColumnSizesType; + column?: boolean; +} + +export const Column: FC = props => +{ + const { size = 0, offset = 0, column = true, gap = 2, classNames = [], ...rest } = props; + const { isCssGrid = false } = useGridContext(); + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = []; + + if(size) + { + let colClassName = `col-span-${ size }`; + + if(isCssGrid) colClassName = `${ colClassName }`; + + newClassNames.push(colClassName); + } + + if(offset) + { + let colClassName = `offset-${ offset }`; + + if(isCssGrid) colClassName = `g-start-${ offset }`; + + newClassNames.push(colClassName); + } + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ size, offset, isCssGrid, classNames ]); + + return ; +}; diff --git a/src/common/Flex.tsx b/src/common/Flex.tsx new file mode 100644 index 0000000..6d332ac --- /dev/null +++ b/src/common/Flex.tsx @@ -0,0 +1,50 @@ +import { FC, useMemo } from 'react'; +import { Base, BaseProps } from './Base'; +import { AlignItemType, AlignSelfType, JustifyContentType, SpacingType } from './types'; + +export interface FlexProps extends BaseProps +{ + column?: boolean; + reverse?: boolean; + gap?: SpacingType; + center?: boolean; + alignSelf?: AlignSelfType; + alignItems?: AlignItemType; + justifyContent?: JustifyContentType; +} + +export const Flex: FC = props => +{ + const { display = 'flex', column = undefined, reverse = false, gap = null, center = false, alignSelf = null, alignItems = null, justifyContent = null, classNames = [], ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = []; + + if(column) + { + if(reverse) newClassNames.push('flex-col-span-reverse'); + else newClassNames.push('flex-col'); + } + else + { + if(reverse) newClassNames.push('flex-row-reverse'); + } + + if(gap) newClassNames.push('gap-' + gap); + + if(alignSelf) newClassNames.push('self-' + alignSelf); + + if(alignItems) newClassNames.push('items-' + alignItems); + + if(justifyContent) newClassNames.push('justify-' + justifyContent); + + if(!alignItems && !justifyContent && center) newClassNames.push('items-center', 'justify-center'); + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ column, reverse, gap, center, alignSelf, alignItems, justifyContent, classNames ]); + + return ; +}; diff --git a/src/common/FormGroup.tsx b/src/common/FormGroup.tsx new file mode 100644 index 0000000..2d73f24 --- /dev/null +++ b/src/common/FormGroup.tsx @@ -0,0 +1,22 @@ +import { FC, useMemo } from 'react'; +import { Flex, FlexProps } from './Flex'; + +export interface FormGroupProps extends FlexProps +{ +} + +export const FormGroup: FC = props => +{ + const { classNames = [], ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'form-group' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ; +}; diff --git a/src/common/Grid.tsx b/src/common/Grid.tsx new file mode 100644 index 0000000..441db8a --- /dev/null +++ b/src/common/Grid.tsx @@ -0,0 +1,64 @@ +import { CSSProperties, FC, useMemo } from 'react'; +import { Base, BaseProps } from './Base'; +import { GridContextProvider } from './GridContext'; +import { AlignItemType, AlignSelfType, JustifyContentType, SpacingType } from './types'; + +export interface GridProps extends BaseProps +{ + inline?: boolean; + gap?: SpacingType; + maxContent?: boolean; + columnCount?: number; + center?: boolean; + alignSelf?: AlignSelfType; + alignItems?: AlignItemType; + justifyContent?: JustifyContentType; +} + +export const Grid: FC = props => +{ + const { inline = false, gap = 2, maxContent = false, columnCount = 0, center = false, alignSelf = null, alignItems = null, justifyContent = null, fullHeight = true, classNames = [], style = {}, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = []; + + + if(inline) newClassNames.push('inline-grid'); + else newClassNames.push('grid grid-rows-[repeat(var(--bs-rows,_1),_1fr)] grid-cols-[repeat(var(--bs-columns,_12),_1fr)]'); + + if(gap) newClassNames.push('gap-' + gap); + else if(gap === 0) newClassNames.push('gap-0'); + + if(maxContent) newClassNames.push('[flex-basis:max-content]'); + + if(alignSelf) newClassNames.push('self-' + alignSelf); + + if(alignItems) newClassNames.push('items-' + alignItems); + + if(justifyContent) newClassNames.push('justify-' + justifyContent); + + if(!alignItems && !justifyContent && center) newClassNames.push('items-center', 'justify-center'); + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ inline, gap, maxContent, alignSelf, alignItems, justifyContent, center, classNames ]); + + const getStyle = useMemo(() => + { + let newStyle: CSSProperties = {}; + + if(columnCount) newStyle['--bs-columns'] = columnCount.toString(); + + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; + + return newStyle; + }, [ columnCount, style ]); + + return ( + + + + ); +}; diff --git a/src/common/GridContext.tsx b/src/common/GridContext.tsx new file mode 100644 index 0000000..082d4be --- /dev/null +++ b/src/common/GridContext.tsx @@ -0,0 +1,17 @@ +import { createContext, FC, ProviderProps, useContext } from 'react'; + +export interface IGridContext +{ + isCssGrid: boolean; +} + +const GridContext = createContext({ + isCssGrid: false +}); + +export const GridContextProvider: FC> = props => +{ + return { props.children }; +}; + +export const useGridContext = () => useContext(GridContext); diff --git a/src/common/HorizontalRule.tsx b/src/common/HorizontalRule.tsx new file mode 100644 index 0000000..54164ae --- /dev/null +++ b/src/common/HorizontalRule.tsx @@ -0,0 +1,38 @@ +import { CSSProperties, FC, useMemo } from 'react'; +import { Base, BaseProps } from './Base'; +import { ColorVariantType } from './types'; + +export interface HorizontalRuleProps extends BaseProps +{ + variant?: ColorVariantType; + height?: number; +} + +export const HorizontalRule: FC = props => +{ + const { variant = 'black', height = 1, classNames = [], style = {}, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = []; + + if(variant) newClassNames.push('bg-' + variant); + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ variant, classNames ]); + + const getStyle = useMemo(() => + { + let newStyle: CSSProperties = { display: 'list-item' }; + + if(height > 0) newStyle.height = height; + + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; + + return newStyle; + }, [ height, style ]); + + return ; +}; diff --git a/src/common/InfiniteScroll.tsx b/src/common/InfiniteScroll.tsx new file mode 100644 index 0000000..51966f8 --- /dev/null +++ b/src/common/InfiniteScroll.tsx @@ -0,0 +1,55 @@ +import { useVirtualizer } from '@tanstack/react-virtual'; +import { FC, ReactElement, useRef, useState } from 'react'; +import { Base } from './Base'; + +interface InfiniteScrollProps +{ + rows: T[]; + overscan?: number; + scrollToBottom?: boolean; + rowRender: (row: T) => ReactElement; +} + +export const InfiniteScroll: FC = props => +{ + const { rows = [], overscan = 5, scrollToBottom = false, rowRender = null } = props; + const [ scrollIndex, setScrollIndex ] = useState(rows.length - 1); + const parentRef = useRef(null); + + const virtualizer = useVirtualizer({ + count: rows.length, + overscan, + getScrollElement: () => parentRef.current, + estimateSize: () => 45, + }); + const items = virtualizer.getVirtualItems(); + + return ( + +
+
+ { items.map((virtualRow) => ( +
+ { rowRender(rows[virtualRow.index]) } +
+ )) } +
+
+ + ); +}; diff --git a/src/common/Popover.tsx b/src/common/Popover.tsx new file mode 100644 index 0000000..5a7c5c8 --- /dev/null +++ b/src/common/Popover.tsx @@ -0,0 +1,54 @@ +import { FC, PropsWithChildren, useEffect, useRef, useState } from 'react'; + +export const ReactPopover: FC> = props => +{ + const { content = null, trigger = null, children = null } = props; + const [ show, setShow ] = useState(false); + const wrapperRef = useRef(null); + + const handleMouseOver = () => (trigger === 'hover') && setShow(true); + + const handleMouseLeft = () => (trigger === 'hover') && setShow(false); + + useEffect(() => + { + if(!show) return; + + const handleClickOutside = (event: MouseEvent) => + { + if(wrapperRef.current && !wrapperRef.current.contains(event.target)) setShow(false); + }; + + document.addEventListener('mousedown', handleClickOutside); + + return () => + { + // Unbind the event listener on clean up + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [ show, wrapperRef ]); + + return ( +
+
setShow(!show) } + > + { children } +
+ +
+ ); +}; diff --git a/src/common/Slider.tsx b/src/common/Slider.tsx new file mode 100644 index 0000000..50cba28 --- /dev/null +++ b/src/common/Slider.tsx @@ -0,0 +1,21 @@ +import { FC } from 'react'; +import ReactSlider, { ReactSliderProps } from 'react-slider'; +import { Button } from './Button'; +import { Flex } from './Flex'; +import { FaAngleLeft, FaAngleRight } from 'react-icons/fa'; + +export interface SliderProps extends ReactSliderProps +{ + disabledButton?: boolean; +} + +export const Slider: FC = props => +{ + const { disabledButton, max, min, value, onChange, ...rest } = props; + + return + { !disabledButton && } + + { !disabledButton && } + ; +} diff --git a/src/common/Text.tsx b/src/common/Text.tsx new file mode 100644 index 0000000..da5235d --- /dev/null +++ b/src/common/Text.tsx @@ -0,0 +1,79 @@ +import { FC, useMemo } from 'react'; +import { Base, BaseProps } from './Base'; +import { ColorVariantType, FontSizeType, FontWeightType, TextAlignType } from './types'; + +export interface TextProps extends BaseProps { + variant?: ColorVariantType; + fontWeight?: FontWeightType; + fontSize?: FontSizeType; + fontSizeCustom?: number; + align?: TextAlignType; + bold?: boolean; + underline?: boolean; + italics?: boolean; + truncate?: boolean; + center?: boolean; + textEnd?: boolean; + small?: boolean; + wrap?: boolean; + noWrap?: boolean; + textBreak?: boolean; +} + +export const Text: FC = props => { + const { + variant = 'black', + fontWeight = null, + fontSize = 0, + fontSizeCustom, + align = null, + bold = false, + underline = false, + italics = false, + truncate = false, + center = false, + textEnd = false, + small = false, + wrap = false, + noWrap = false, + textBreak = false, + ...rest + } = props; + + const getClassNames = useMemo(() => { + const newClassNames: string[] = ['inline']; + + if (variant) { + if (variant === 'primary') newClassNames.push('text-[#1e7295]'); + if (variant == 'secondary') newClassNames.push('text-[#185d79]'); + if (variant === 'black') newClassNames.push('text-[#000000]'); + if (variant == 'dark') newClassNames.push('text-[#18181b]'); + if (variant === 'gray') newClassNames.push('text-[#6b7280]'); + if (variant === 'white') newClassNames.push('text-[#ffffff]'); + if (variant == 'success') newClassNames.push('text-[#00800b]'); + if (variant == 'danger') newClassNames.push('text-[#a81a12]'); + if (variant == 'warning') newClassNames.push('text-[#ffc107]'); + } + + if (bold) newClassNames.push('font-bold'); + if (fontWeight) newClassNames.push('font-' + fontWeight); + if (fontSize) newClassNames.push('fs-' + fontSize); + if (fontSizeCustom) newClassNames.push('fs-custom'); + if (align) newClassNames.push('text-' + align); + if (underline) newClassNames.push('underline'); + if (italics) newClassNames.push('italic'); + if (truncate) newClassNames.push('text-truncate'); + if (center) newClassNames.push('text-center'); + if (textEnd) newClassNames.push('text-end'); + if (small) newClassNames.push('text-sm'); + if (wrap) newClassNames.push('text-wrap'); + if (noWrap) newClassNames.push('text-nowrap'); + if (textBreak) newClassNames.push('text-break'); + + return newClassNames; + }, [variant, fontWeight, fontSize, fontSizeCustom, align, bold, underline, italics, truncate, center, textEnd, small, wrap, noWrap, textBreak]); + + const style = fontSizeCustom ? { '--font-size': `${fontSizeCustom}px` } as React.CSSProperties : undefined; + + return ; +}; \ No newline at end of file diff --git a/src/common/card/NitroCardContentView.tsx b/src/common/card/NitroCardContentView.tsx new file mode 100644 index 0000000..a93703b --- /dev/null +++ b/src/common/card/NitroCardContentView.tsx @@ -0,0 +1,19 @@ +import { FC, useMemo } from 'react'; +import { Column, ColumnProps } from '..'; + +export const NitroCardContentView: FC = props => +{ + const { overflow = 'auto', classNames = [], ...rest } = props; + + const getClassNames = useMemo(() => + { + // Theme Changer + const newClassNames: string[] = [ 'container-fluid', 'h-full p-[8px] overflow-auto', 'bg-light' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ; +}; diff --git a/src/common/card/NitroCardContext.tsx b/src/common/card/NitroCardContext.tsx new file mode 100644 index 0000000..c296b2a --- /dev/null +++ b/src/common/card/NitroCardContext.tsx @@ -0,0 +1,17 @@ +import { createContext, FC, ProviderProps, useContext } from 'react'; + +interface INitroCardContext +{ + theme: string; +} + +const NitroCardContext = createContext({ + theme: null +}); + +export const NitroCardContextProvider: FC> = props => +{ + return { props.children }; +}; + +export const useNitroCardContext = () => useContext(NitroCardContext); diff --git a/src/common/card/NitroCardHeaderView.tsx b/src/common/card/NitroCardHeaderView.tsx new file mode 100644 index 0000000..2cfd638 --- /dev/null +++ b/src/common/card/NitroCardHeaderView.tsx @@ -0,0 +1,41 @@ +import { FC, MouseEvent } from 'react'; +import { FaFlag } from 'react-icons/fa'; +import { Base, Column, ColumnProps, Flex } from '..'; + +interface NitroCardHeaderViewProps extends ColumnProps +{ + headerText: string; + isGalleryPhoto?: boolean; + noCloseButton?: boolean; + onReportPhoto?: (event: MouseEvent) => void; + onCloseClick: (event: MouseEvent) => void; +} + +export const NitroCardHeaderView: FC = props => +{ + const { headerText = null, isGalleryPhoto = false, noCloseButton = false, onReportPhoto = null, onCloseClick = null, justifyContent = 'center', alignItems = 'center', classNames = [], children = null, ...rest } = props; + + + + const onMouseDown = (event: MouseEvent) => + { + event.stopPropagation(); + event.nativeEvent.stopImmediatePropagation(); + }; + + return ( + + + { headerText } + { isGalleryPhoto && + + + + } +
+
+ +
+
+ ); +}; diff --git a/src/common/card/NitroCardView.tsx b/src/common/card/NitroCardView.tsx new file mode 100644 index 0000000..9018cac --- /dev/null +++ b/src/common/card/NitroCardView.tsx @@ -0,0 +1,35 @@ +import { FC, useMemo, useRef } from 'react'; +import { Column, ColumnProps } from '..'; +import { DraggableWindow, DraggableWindowPosition, DraggableWindowProps } from '../draggable-window'; +import { NitroCardContextProvider } from './NitroCardContext'; + +export interface NitroCardViewProps extends DraggableWindowProps, ColumnProps +{ + theme?: string; +} + +export const NitroCardView: FC = props => +{ + const { theme = 'primary', uniqueKey = null, handleSelector = '.drag-handler', windowPosition = DraggableWindowPosition.CENTER, disableDrag = false, overflow = 'hidden', position = 'relative', gap = 0, classNames = [], ...rest } = props; + const elementRef = useRef(); + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'resize', 'rounded', 'shadow', ]; + + // Card Theme Changer + newClassNames.push('border-[1px] border-[#283F5D]'); + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( + + + + + + ); +}; diff --git a/src/common/card/accordion/NitroCardAccordionContext.tsx b/src/common/card/accordion/NitroCardAccordionContext.tsx new file mode 100644 index 0000000..5e65c30 --- /dev/null +++ b/src/common/card/accordion/NitroCardAccordionContext.tsx @@ -0,0 +1,21 @@ +import { createContext, Dispatch, FC, ProviderProps, SetStateAction, useContext } from 'react'; + +export interface INitroCardAccordionContext +{ + closers: Function[]; + setClosers: Dispatch>; + closeAll: () => void; +} + +const NitroCardAccordionContext = createContext({ + closers: null, + setClosers: null, + closeAll: null +}); + +export const NitroCardAccordionContextProvider: FC> = props => +{ + return ; +}; + +export const useNitroCardAccordionContext = () => useContext(NitroCardAccordionContext); diff --git a/src/common/card/accordion/NitroCardAccordionItemView.tsx b/src/common/card/accordion/NitroCardAccordionItemView.tsx new file mode 100644 index 0000000..238aab4 --- /dev/null +++ b/src/common/card/accordion/NitroCardAccordionItemView.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react'; +import { Flex, FlexProps } from '../..'; + +export interface NitroCardAccordionItemViewProps extends FlexProps +{ + +} + +export const NitroCardAccordionItemView: FC = props => +{ + const { alignItems = 'center', gap = 1, children = null, ...rest } = props; + + return ( + + { children } + + ); +}; diff --git a/src/common/card/accordion/NitroCardAccordionSetView.tsx b/src/common/card/accordion/NitroCardAccordionSetView.tsx new file mode 100644 index 0000000..1a059d4 --- /dev/null +++ b/src/common/card/accordion/NitroCardAccordionSetView.tsx @@ -0,0 +1,84 @@ +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { FaCaretDown, FaCaretUp } from 'react-icons/fa'; +import { Column, ColumnProps, Flex, Text } from '../..'; +import { useNitroCardAccordionContext } from './NitroCardAccordionContext'; + +export interface NitroCardAccordionSetViewProps extends ColumnProps +{ + headerText: string; + isExpanded?: boolean; +} + +export const NitroCardAccordionSetView: FC = props => +{ + const { headerText = '', isExpanded = false, gap = 0, classNames = [], children = null, ...rest } = props; + const [ isOpen, setIsOpen ] = useState(false); + const { setClosers = null, closeAll = null } = useNitroCardAccordionContext(); + + const onClick = () => + { + closeAll(); + + setIsOpen(prevValue => !prevValue); + }; + + const onClose = useCallback(() => setIsOpen(false), []); + + const getClassNames = useMemo(() => + { + const newClassNames = [ 'nitro-card-accordion-set' ]; + + if(isOpen) newClassNames.push('active'); + + if(classNames && classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ isOpen, classNames ]); + + useEffect(() => + { + setIsOpen(isExpanded); + }, [ isExpanded ]); + + useEffect(() => + { + const closeFunction = onClose; + + setClosers(prevValue => + { + const newClosers = [ ...prevValue ]; + + newClosers.push(closeFunction); + + return newClosers; + }); + + return () => + { + setClosers(prevValue => + { + const newClosers = [ ...prevValue ]; + + const index = newClosers.indexOf(closeFunction); + + if(index >= 0) newClosers.splice(index, 1); + + return newClosers; + }); + }; + }, [ onClose, setClosers ]); + + return ( + + + { headerText } + { isOpen && } + { !isOpen && } + + { isOpen && + + { children } + } + + ); +}; diff --git a/src/common/card/accordion/NitroCardAccordionView.tsx b/src/common/card/accordion/NitroCardAccordionView.tsx new file mode 100644 index 0000000..0874450 --- /dev/null +++ b/src/common/card/accordion/NitroCardAccordionView.tsx @@ -0,0 +1,25 @@ +import { FC, useCallback, useState } from 'react'; +import { Column, ColumnProps } from '../..'; +import { NitroCardAccordionContextProvider } from './NitroCardAccordionContext'; + +interface NitroCardAccordionViewProps extends ColumnProps +{ + +} + +export const NitroCardAccordionView: FC = props => +{ + const { ...rest } = props; + const [ closers, setClosers ] = useState([]); + + const closeAll = useCallback(() => + { + for(const closer of closers) closer(); + }, [ closers ]); + + return ( + + + + ); +}; diff --git a/src/common/card/accordion/index.ts b/src/common/card/accordion/index.ts new file mode 100644 index 0000000..d585b33 --- /dev/null +++ b/src/common/card/accordion/index.ts @@ -0,0 +1,4 @@ +export * from './NitroCardAccordionContext'; +export * from './NitroCardAccordionItemView'; +export * from './NitroCardAccordionSetView'; +export * from './NitroCardAccordionView'; diff --git a/src/common/card/index.ts b/src/common/card/index.ts new file mode 100644 index 0000000..3ce0d60 --- /dev/null +++ b/src/common/card/index.ts @@ -0,0 +1,6 @@ +export * from './NitroCardContentView'; +export * from './NitroCardContext'; +export * from './NitroCardHeaderView'; +export * from './NitroCardView'; +export * from './accordion'; +export * from './tabs'; diff --git a/src/common/card/tabs/NitroCardTabsItemView.tsx b/src/common/card/tabs/NitroCardTabsItemView.tsx new file mode 100644 index 0000000..2f0b014 --- /dev/null +++ b/src/common/card/tabs/NitroCardTabsItemView.tsx @@ -0,0 +1,36 @@ +import { FC, useMemo } from 'react'; +import { Flex, FlexProps } from '../../Flex'; +import { LayoutItemCountView } from '../../layout'; + +interface NitroCardTabsItemViewProps extends FlexProps +{ + isActive?: boolean; + count?: number; +} + +export const NitroCardTabsItemView: FC = props => +{ + const { isActive = false, count = 0, overflow = 'hidden', position = 'relative', pointer = true, classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'overflow-hidden relative cursor-pointer rounded-t-md flex bg-card-tab-item px-3 py-1 z-[1] border-card-border border-t border-l border-r before:absolute before:w-[93%] before:h-[3px] before:rounded-md before:top-[1.5px] before:left-0 before:right-0 before:m-auto before:z-[1] before:bg-[#C2C9D1]', + isActive && 'bg-card-tab-item-active -mb-[1px] before:bg-white' ]; + + //if (isActive) newClassNames.push('bg-[#dfdfdf] border-b-[1px_solid_black]'); + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ isActive, classNames ]); + + return ( + + + { children } + + { (count > 0) && + } + + ); +}; diff --git a/src/common/card/tabs/NitroCardTabsView.tsx b/src/common/card/tabs/NitroCardTabsView.tsx new file mode 100644 index 0000000..8e5d118 --- /dev/null +++ b/src/common/card/tabs/NitroCardTabsView.tsx @@ -0,0 +1,22 @@ +import { FC, useMemo } from 'react'; +import { Flex, FlexProps } from '../..'; + +export const NitroCardTabsView: FC = props => +{ + const { justifyContent = 'center', gap = 1, classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'justify-center gap-0.5 flex bg-card-tabs min-h-card-tabs max-h-card-tabs pt-1 border-b border-card-border px-2 -mt-[1px]' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( + + { children } + + ); +}; diff --git a/src/common/card/tabs/index.ts b/src/common/card/tabs/index.ts new file mode 100644 index 0000000..517db11 --- /dev/null +++ b/src/common/card/tabs/index.ts @@ -0,0 +1,2 @@ +export * from './NitroCardTabsItemView'; +export * from './NitroCardTabsView'; diff --git a/src/common/draggable-window/DraggableWindow.tsx b/src/common/draggable-window/DraggableWindow.tsx new file mode 100644 index 0000000..b71a353 --- /dev/null +++ b/src/common/draggable-window/DraggableWindow.tsx @@ -0,0 +1,245 @@ +import { MouseEventType, TouchEventType } from '@nitrots/nitro-renderer'; +import { CSSProperties, FC, Key, MouseEvent as ReactMouseEvent, ReactNode, TouchEvent as ReactTouchEvent, useCallback, useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { GetLocalStorage, SetLocalStorage, WindowSaveOptions } from '../../api'; +import { DraggableWindowPosition } from './DraggableWindowPosition'; + +const CURRENT_WINDOWS: HTMLElement[] = []; +const POS_MEMORY: Map = new Map(); +const BOUNDS_THRESHOLD_TOP: number = 0; +const BOUNDS_THRESHOLD_LEFT: number = 0; + +export interface DraggableWindowProps { + uniqueKey?: Key; + handleSelector?: string; + windowPosition?: string; + disableDrag?: boolean; + dragStyle?: CSSProperties; + offsetLeft?: number; + offsetTop?: number; + children?: ReactNode; +} + +export const DraggableWindow: FC = props => { + const { uniqueKey = null, handleSelector = '.drag-handler', windowPosition = DraggableWindowPosition.CENTER, disableDrag = false, dragStyle = {}, children = null, offsetLeft = 0, offsetTop = 0 } = props; + const [delta, setDelta] = useState<{ x: number, y: number }>({ x: 0, y: 0 }); + const [offset, setOffset] = useState<{ x: number, y: number }>({ x: 0, y: 0 }); + const [start, setStart] = useState<{ x: number, y: number }>({ x: 0, y: 0 }); + const [isDragging, setIsDragging] = useState(false); + const [isPositioned, setIsPositioned] = useState(false); // New state to control visibility + const [dragHandler, setDragHandler] = useState(null); + const elementRef = useRef(); + + const bringToTop = useCallback(() => { + let zIndex = 400; + for (const existingWindow of CURRENT_WINDOWS) { + zIndex += 1; + existingWindow.style.zIndex = zIndex.toString(); + } + }, []); + + const moveCurrentWindow = useCallback(() => { + const index = CURRENT_WINDOWS.indexOf(elementRef.current); + if (index === -1) { + CURRENT_WINDOWS.push(elementRef.current); + } else if (index === (CURRENT_WINDOWS.length - 1)) return; + else if (index >= 0) { + CURRENT_WINDOWS.splice(index, 1); + CURRENT_WINDOWS.push(elementRef.current); + } + bringToTop(); + }, [bringToTop]); + + const onMouseDown = useCallback((event: ReactMouseEvent) => { + moveCurrentWindow(); + }, [moveCurrentWindow]); + + const onTouchStart = useCallback((event: ReactTouchEvent) => { + moveCurrentWindow(); + }, [moveCurrentWindow]); + + const startDragging = useCallback((startX: number, startY: number) => { + setStart({ x: startX, y: startY }); + setIsDragging(true); + }, []); + + const onDragMouseDown = useCallback((event: MouseEvent) => { + startDragging(event.clientX, event.clientY); + }, [startDragging]); + + const onTouchDown = useCallback((event: TouchEvent) => { + const touch = event.touches[0]; + startDragging(touch.clientX, touch.clientY); + }, [startDragging]); + + const clampPosition = useCallback((newX: number, newY: number) => { + if (!elementRef.current) return { x: newX, y: newY }; + + const windowWidth = elementRef.current.offsetWidth; + const windowHeight = elementRef.current.offsetHeight; + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + const clampedX = Math.max(BOUNDS_THRESHOLD_LEFT, Math.min(newX, viewportWidth - windowWidth)); + const clampedY = Math.max(BOUNDS_THRESHOLD_TOP, Math.min(newY, viewportHeight - windowHeight)); + + return { x: clampedX, y: clampedY }; + }, []); + + const onDragMouseMove = useCallback((event: MouseEvent) => { + if (!elementRef.current || !isDragging) return; + + const newDeltaX = event.clientX - start.x; + const newDeltaY = event.clientY - start.y; + const newOffsetX = offset.x + newDeltaX; + const newOffsetY = offset.y + newDeltaY; + + const clampedPos = clampPosition(newOffsetX, newOffsetY); + setDelta({ x: clampedPos.x - offset.x, y: clampedPos.y - offset.y }); + }, [start, offset, clampPosition, isDragging]); + + const onDragTouchMove = useCallback((event: TouchEvent) => { + if (!elementRef.current || !isDragging) return; + + const touch = event.touches[0]; + const newDeltaX = touch.clientX - start.x; + const newDeltaY = touch.clientY - start.y; + const newOffsetX = offset.x + newDeltaX; + const newOffsetY = offset.y + newDeltaY; + + const clampedPos = clampPosition(newOffsetX, newOffsetY); + setDelta({ x: clampedPos.x - offset.x, y: clampedPos.y - offset.y }); + }, [start, offset, clampPosition, isDragging]); + + const completeDrag = useCallback(() => { + if (!elementRef.current || !dragHandler || !isDragging) return; + + const finalOffsetX = offset.x + delta.x; + const finalOffsetY = offset.y + delta.y; + const clampedPos = clampPosition(finalOffsetX, finalOffsetY); + + setDelta({ x: 0, y: 0 }); + setOffset({ x: clampedPos.x, y: clampedPos.y }); + setIsDragging(false); + + if (uniqueKey !== null) { + const newStorage = { ...GetLocalStorage(`nitro.windows.${uniqueKey}`) } as WindowSaveOptions; + newStorage.offset = { x: clampedPos.x, y: clampedPos.y }; + SetLocalStorage(`nitro.windows.${uniqueKey}`, newStorage); + } + }, [dragHandler, delta, offset, uniqueKey, clampPosition, isDragging]); + + const onDragMouseUp = useCallback((event: MouseEvent) => { + completeDrag(); + }, [completeDrag]); + + const onDragTouchUp = useCallback((event: TouchEvent) => { + completeDrag(); + }, [completeDrag]); + + useEffect(() => { + const element = elementRef.current as HTMLElement; + if (!element) return; + + CURRENT_WINDOWS.push(element); + bringToTop(); + + if (!disableDrag) { + const handle = element.querySelector(handleSelector); + if (handle) setDragHandler(handle as HTMLElement); + } + + const windowWidth = element.offsetWidth || 340; + const windowHeight = element.offsetHeight || 462; + let offsetX = 0; + let offsetY = 0; + + switch (windowPosition) { + case DraggableWindowPosition.TOP_CENTER: + offsetY = 50 + offsetTop; + offsetX = (window.innerWidth - windowWidth) / 2 + offsetLeft; + break; + case DraggableWindowPosition.CENTER: + offsetY = (window.innerHeight - windowHeight) / 2 + offsetTop; + offsetX = (window.innerWidth - windowWidth) / 2 + offsetLeft; + break; + case DraggableWindowPosition.TOP_LEFT: + offsetY = 50 + offsetTop; + offsetX = 50 + offsetLeft; + break; + } + + const clampedPos = clampPosition(offsetX, offsetY); + element.style.left = '0px'; + element.style.top = '0px'; + setOffset({ x: clampedPos.x, y: clampedPos.y }); + setDelta({ x: 0, y: 0 }); + setIsPositioned(true); // Mark as positioned after setting initial offset + + return () => { + const index = CURRENT_WINDOWS.indexOf(element); + if (index >= 0) CURRENT_WINDOWS.splice(index, 1); + }; + }, [handleSelector, windowPosition, uniqueKey, disableDrag, offsetLeft, offsetTop, bringToTop]); + + useEffect(() => { + const element = elementRef.current as HTMLElement; + if (!element || !isPositioned) return; + + element.style.transform = `translate(${offset.x + delta.x}px, ${offset.y + delta.y}px)`; + element.style.visibility = 'visible'; + }, [offset, delta, isPositioned]); + + useEffect(() => { + if (!dragHandler) return; + + dragHandler.addEventListener(MouseEventType.MOUSE_DOWN, onDragMouseDown); + dragHandler.addEventListener(TouchEventType.TOUCH_START, onTouchDown); + + return () => { + dragHandler.removeEventListener(MouseEventType.MOUSE_DOWN, onDragMouseDown); + dragHandler.removeEventListener(TouchEventType.TOUCH_START, onTouchDown); + }; + }, [dragHandler, onDragMouseDown, onTouchDown]); + + useEffect(() => { + if (!isDragging) return; + + document.addEventListener(MouseEventType.MOUSE_UP, onDragMouseUp); + document.addEventListener(TouchEventType.TOUCH_END, onDragTouchUp); + document.addEventListener(MouseEventType.MOUSE_MOVE, onDragMouseMove); + document.addEventListener(TouchEventType.TOUCH_MOVE, onDragTouchMove); + + return () => { + document.removeEventListener(MouseEventType.MOUSE_UP, onDragMouseUp); + document.removeEventListener(TouchEventType.TOUCH_END, onDragTouchUp); + document.removeEventListener(MouseEventType.MOUSE_MOVE, onDragMouseMove); + document.removeEventListener(TouchEventType.TOUCH_MOVE, onDragTouchMove); + }; + }, [isDragging, onDragMouseUp, onDragMouseMove, onDragTouchUp, onDragTouchMove]); + + useEffect(() => { + if (!uniqueKey) return; + + const localStorage = GetLocalStorage(`nitro.windows.${uniqueKey}`); + if (!localStorage || !localStorage.offset) return; + + const clampedPos = clampPosition(localStorage.offset.x, localStorage.offset.y); + setDelta({ x: 0, y: 0 }); + setOffset({ x: clampedPos.x, y: clampedPos.y }); + setIsPositioned(true); // Ensure positioned when loading from storage + }, [uniqueKey, clampPosition]); + + return createPortal( +
+ {children} +
, + document.getElementById('draggable-windows-container') + ); +}; \ No newline at end of file diff --git a/src/common/draggable-window/DraggableWindowPosition.ts b/src/common/draggable-window/DraggableWindowPosition.ts new file mode 100644 index 0000000..476a37e --- /dev/null +++ b/src/common/draggable-window/DraggableWindowPosition.ts @@ -0,0 +1,7 @@ +export class DraggableWindowPosition +{ + public static CENTER: string = 'DWP_CENTER'; + public static TOP_CENTER: string = 'DWP_TOP_CENTER'; + public static TOP_LEFT: string = 'DWP_TOP_LEFT'; + public static NOTHING: string = 'DWP_NOTHING'; +} diff --git a/src/common/draggable-window/index.ts b/src/common/draggable-window/index.ts new file mode 100644 index 0000000..7672f52 --- /dev/null +++ b/src/common/draggable-window/index.ts @@ -0,0 +1,2 @@ +export * from './DraggableWindow'; +export * from './DraggableWindowPosition'; diff --git a/src/common/index.ts b/src/common/index.ts new file mode 100644 index 0000000..d8c47ae --- /dev/null +++ b/src/common/index.ts @@ -0,0 +1,22 @@ + +export * from './AutoGrid'; +export * from './Base'; +export * from './Button'; +export * from './ButtonGroup'; +export * from './Column'; +export * from './Flex'; +export * from './FormGroup'; +export * from './Grid'; +export * from './GridContext'; +export * from './HorizontalRule'; +export * from './InfiniteScroll'; +export * from './Text'; +export * from './card'; +export * from './card/accordion'; +export * from './card/tabs'; +export * from './draggable-window'; +export * from './layout'; +export * from './layout/limited-edition'; +export * from './types'; +export * from "./Slider"; +export * from './utils'; diff --git a/src/common/layout/LayoutAvatarImageView.tsx b/src/common/layout/LayoutAvatarImageView.tsx new file mode 100644 index 0000000..86d589e --- /dev/null +++ b/src/common/layout/LayoutAvatarImageView.tsx @@ -0,0 +1,103 @@ +import { AvatarScaleType, AvatarSetType, GetAvatarRenderManager } from '@nitrots/nitro-renderer'; +import { CSSProperties, FC, useEffect, useMemo, useRef, useState } from 'react'; +import { Base, BaseProps } from '../Base'; + +const AVATAR_IMAGE_CACHE: Map = new Map(); + +export interface LayoutAvatarImageViewProps extends BaseProps +{ + figure: string; + gender?: string; + headOnly?: boolean; + direction?: number; + scale?: number; +} + +export const LayoutAvatarImageView: FC = props => +{ + const { figure = '', gender = 'M', headOnly = false, direction = 0, scale = 1, classNames = [], style = {}, ...rest } = props; + const [ avatarUrl, setAvatarUrl ] = useState(null); + const [ isReady, setIsReady ] = useState(false); + const isDisposed = useRef(false); + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'avatar-image relative w-[90px] h-[130px] bg-no-repeat bg-[center_-8px] pointer-events-none' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + const getStyle = useMemo(() => + { + let newStyle: CSSProperties = {}; + + if(avatarUrl && avatarUrl.length) newStyle.backgroundImage = `url('${ avatarUrl }')`; + + if(scale !== 1) + { + newStyle.transform = `scale(${ scale })`; + + if(!(scale % 1)) newStyle.imageRendering = 'pixelated'; + } + + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; + + return newStyle; + }, [ avatarUrl, scale, style ]); + + useEffect(() => + { + if(!isReady) return; + + const figureKey = [ figure, gender, direction, headOnly ].join('-'); + + if(AVATAR_IMAGE_CACHE.has(figureKey)) + { + setAvatarUrl(AVATAR_IMAGE_CACHE.get(figureKey)); + } + else + { + const resetFigure = (_figure: string) => + { + if(isDisposed.current) return; + + const avatarImage = GetAvatarRenderManager().createAvatarImage(_figure, AvatarScaleType.LARGE, gender, { resetFigure: (figure: string) => resetFigure(figure), dispose: null, disposed: false }); + + let setType = AvatarSetType.FULL; + + if(headOnly) setType = AvatarSetType.HEAD; + + avatarImage.setDirection(setType, direction); + + const imageUrl = avatarImage.processAsImageUrl(setType); + + if(imageUrl && !isDisposed.current) + { + if(!avatarImage.isPlaceholder()) AVATAR_IMAGE_CACHE.set(figureKey, imageUrl); + + setAvatarUrl(imageUrl); + } + + avatarImage.dispose(); + }; + + resetFigure(figure); + } + }, [ figure, gender, direction, headOnly, isReady ]); + + useEffect(() => + { + isDisposed.current = false; + + setIsReady(true); + + return () => + { + isDisposed.current = true; + }; + }, []); + + return ; +}; diff --git a/src/common/layout/LayoutBackgroundImage.tsx b/src/common/layout/LayoutBackgroundImage.tsx new file mode 100644 index 0000000..622d959 --- /dev/null +++ b/src/common/layout/LayoutBackgroundImage.tsx @@ -0,0 +1,23 @@ +import { FC, useMemo } from 'react'; +import { Base, BaseProps } from '../Base'; + +export interface LayoutBackgroundImageProps extends BaseProps +{ + imageUrl?: string; +} + +export const LayoutBackgroundImage: FC = props => +{ + const { imageUrl = null, fit = true, style = null, ...rest } = props; + + const getStyle = useMemo(() => + { + const newStyle = { ...style }; + + if(imageUrl) newStyle.background = `url(${ imageUrl }) center no-repeat`; + + return newStyle; + }, [ style, imageUrl ]); + + return ; +}; diff --git a/src/common/layout/LayoutBadgeImageView.tsx b/src/common/layout/LayoutBadgeImageView.tsx new file mode 100644 index 0000000..bac40bc --- /dev/null +++ b/src/common/layout/LayoutBadgeImageView.tsx @@ -0,0 +1,109 @@ +import { BadgeImageReadyEvent, GetEventDispatcher, GetSessionDataManager, NitroSprite, TextureUtils } from '@nitrots/nitro-renderer'; +import { CSSProperties, FC, useEffect, useMemo, useState } from 'react'; +import { GetConfigurationValue, LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../api'; +import { Base, BaseProps } from '../Base'; + +export interface LayoutBadgeImageViewProps extends BaseProps +{ + badgeCode: string; + isGroup?: boolean; + showInfo?: boolean; + customTitle?: string; + isGrayscale?: boolean; + scale?: number; +} + +export const LayoutBadgeImageView: FC = props => +{ + const { badgeCode = null, isGroup = false, showInfo = false, customTitle = null, isGrayscale = false, scale = 1, classNames = [], style = {}, children = null, ...rest } = props; + const [ imageElement, setImageElement ] = useState(null); + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'relative w-[40px] h-[40px] bg-no-repeat bg-center' ]; + + if(isGroup) newClassNames.push('group-badge'); + + if(isGrayscale) newClassNames.push('grayscale'); + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames, isGroup, isGrayscale ]); + + const getStyle = useMemo(() => + { + let newStyle: CSSProperties = {}; + + if(imageElement) + { + newStyle.backgroundImage = `url(${ (isGroup) ? imageElement.src : GetConfigurationValue('badge.asset.url').replace('%badgename%', badgeCode.toString()) })`; + newStyle.width = imageElement.width; + newStyle.height = imageElement.height; + + if(scale !== 1) + { + newStyle.transform = `scale(${ scale })`; + + if(!(scale % 1)) newStyle.imageRendering = 'pixelated'; + + newStyle.width = (imageElement.width * scale); + newStyle.height = (imageElement.height * scale); + } + } + + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; + + return newStyle; + }, [ badgeCode, isGroup, imageElement, scale, style ]); + + useEffect(() => + { + if(!badgeCode || !badgeCode.length) return; + + let didSetBadge = false; + + const onBadgeImageReadyEvent = async (event: BadgeImageReadyEvent) => + { + if(event.badgeId !== badgeCode) return; + + const element = await TextureUtils.generateImage(new NitroSprite(event.image)); + + console.log ('boe'); + + element.onload = () => setImageElement(element); + + didSetBadge = true; + + GetEventDispatcher().removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent); + }; + + GetEventDispatcher().addEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent); + + const texture = isGroup ? GetSessionDataManager().getGroupBadgeImage(badgeCode) : GetSessionDataManager().getBadgeImage(badgeCode); + + if(texture && !didSetBadge) + { + (async () => + { + const element = await TextureUtils.generateImage(new NitroSprite(texture)); + + + element.onload = () => setImageElement(element); + })(); + } + + return () => GetEventDispatcher().removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent); + }, [ badgeCode, isGroup ]); + + return ( + + { (showInfo && GetConfigurationValue('badge.descriptions.enabled', true)) && + +
{ isGroup ? customTitle : LocalizeBadgeName(badgeCode) }
+
{ isGroup ? LocalizeText('group.badgepopup.body') : LocalizeBadgeDescription(badgeCode) }
+ } + { children } + + ); +}; diff --git a/src/common/layout/LayoutCounterTimeView.tsx b/src/common/layout/LayoutCounterTimeView.tsx new file mode 100644 index 0000000..9b37095 --- /dev/null +++ b/src/common/layout/LayoutCounterTimeView.tsx @@ -0,0 +1,42 @@ +import { FC, useMemo } from 'react'; +import { LocalizeText } from '../../api'; +import { Base, BaseProps } from '../Base'; + +interface LayoutCounterTimeViewProps extends BaseProps +{ + day: string; + hour: string; + minutes: string; + seconds: string; +} + +export const LayoutCounterTimeView: FC = props => +{ + const { day = '00', hour = '00', minutes = '00', seconds = '00', classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'nitro-counter-time' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( +
+ +
{ day != '00' ? day : hour }{ day != '00' ? LocalizeText('countdown_clock_unit_days') : LocalizeText('countdown_clock_unit_hours') }
+ +
:
+ +
{ minutes }{ LocalizeText('countdown_clock_unit_minutes') }
+ + : + +
{ seconds }{ LocalizeText('countdown_clock_unit_seconds') }
+ + { children } +
+ ); +}; diff --git a/src/common/layout/LayoutCurrencyIcon.tsx b/src/common/layout/LayoutCurrencyIcon.tsx new file mode 100644 index 0000000..f311c22 --- /dev/null +++ b/src/common/layout/LayoutCurrencyIcon.tsx @@ -0,0 +1,44 @@ +import { CSSProperties, FC, useMemo } from 'react'; +import { GetConfigurationValue } from '../../api'; +import { Base, BaseProps } from '../Base'; + +export interface CurrencyIconProps extends BaseProps +{ + type: number | string; +} + +export const LayoutCurrencyIcon: FC = props => +{ + const { type = '', classNames = [], style = {}, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'nitro-currency-icon', 'bg-center bg-no-repeat w-[15px] h-[15px]' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + const urlString = useMemo(() => + { + let url = GetConfigurationValue('currency.asset.icon.url', ''); + + url = url.replace('%type%', type.toString()); + + return `url(${ url })`; + }, [ type ]); + + const getStyle = useMemo(() => + { + let newStyle: CSSProperties = {}; + + newStyle.backgroundImage = urlString; + + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; + + return newStyle; + }, [ style, urlString ]); + + return ; +}; diff --git a/src/common/layout/LayoutFurniIconImageView.tsx b/src/common/layout/LayoutFurniIconImageView.tsx new file mode 100644 index 0000000..b7eaeff --- /dev/null +++ b/src/common/layout/LayoutFurniIconImageView.tsx @@ -0,0 +1,17 @@ +import { FC } from 'react'; +import { GetImageIconUrlForProduct } from '../../api'; +import { LayoutImage, LayoutImageProps } from './LayoutImage'; + +interface LayoutFurniIconImageViewProps extends LayoutImageProps +{ + productType: string; + productClassId: number; + extraData?: string; +} + +export const LayoutFurniIconImageView: FC = props => +{ + const { productType = 's', productClassId = -1, extraData = '', ...rest } = props; + + return ; +}; diff --git a/src/common/layout/LayoutFurniImageView.tsx b/src/common/layout/LayoutFurniImageView.tsx new file mode 100644 index 0000000..b83d811 --- /dev/null +++ b/src/common/layout/LayoutFurniImageView.tsx @@ -0,0 +1,70 @@ +import { GetRoomEngine, IGetImageListener, ImageResult, TextureUtils, Vector3d } from '@nitrots/nitro-renderer'; +import { CSSProperties, FC, useEffect, useMemo, useState } from 'react'; +import { ProductTypeEnum } from '../../api'; +import { Base, BaseProps } from '../Base'; + +interface LayoutFurniImageViewProps extends BaseProps +{ + productType: string; + productClassId: number; + direction?: number; + extraData?: string; + scale?: number; +} + +export const LayoutFurniImageView: FC = props => +{ + const { productType = 's', productClassId = -1, direction = 2, extraData = '', scale = 1, style = {}, ...rest } = props; + const [ imageElement, setImageElement ] = useState(null); + + const getStyle = useMemo(() => + { + let newStyle: CSSProperties = {}; + + if(imageElement?.src?.length) + { + newStyle.backgroundImage = `url('${ imageElement.src }')`; + newStyle.width = imageElement.width; + newStyle.height = imageElement.height; + } + + if(scale !== 1) + { + newStyle.transform = `scale(${ scale })`; + + if(!(scale % 1)) newStyle.imageRendering = 'pixelated'; + } + + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; + + return newStyle; + }, [ imageElement, scale, style ]); + + useEffect(() => + { + let imageResult: ImageResult = null; + + const listener: IGetImageListener = { + imageReady: async (id, texture, image) => setImageElement(await TextureUtils.generateImage(texture)), + imageFailed: null + }; + + switch(productType.toLocaleLowerCase()) + { + case ProductTypeEnum.FLOOR: + imageResult = GetRoomEngine().getFurnitureFloorImage(productClassId, new Vector3d(direction), 64, listener, 0, extraData); + break; + case ProductTypeEnum.WALL: + imageResult = GetRoomEngine().getFurnitureWallImage(productClassId, new Vector3d(direction), 64, listener, 0, extraData); + break; + } + + if(!imageResult) return; + + (async () => setImageElement(await TextureUtils.generateImage(imageResult.data)))(); + }, [ productType, productClassId, direction, extraData ]); + + if(!imageElement) return null; + + return ; +}; diff --git a/src/common/layout/LayoutGiftTagView.tsx b/src/common/layout/LayoutGiftTagView.tsx new file mode 100644 index 0000000..75004ec --- /dev/null +++ b/src/common/layout/LayoutGiftTagView.tsx @@ -0,0 +1,41 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../api'; +import { Column } from '../Column'; +import { Flex } from '../Flex'; +import { Text } from '../Text'; +import { LayoutAvatarImageView } from './LayoutAvatarImageView'; + +interface LayoutGiftTagViewProps +{ + figure?: string; + userName?: string; + message?: string; + editable?: boolean; + onChange?: (value: string) => void; +} + +export const LayoutGiftTagView: FC = props => +{ + const { figure = null, userName = null, message = null, editable = false, onChange = null } = props; + + return ( + +
+ { !userName &&
} + { figure &&
+ +
} +
+ + + { !editable && + { message } } + { editable && (onChange !== null) && + } + { userName && + { LocalizeText('catalog.gift_wrapping_new.message_from', [ 'name' ], [ userName ]) } } + + +
+ ); +}; diff --git a/src/common/layout/LayoutGridItem.tsx b/src/common/layout/LayoutGridItem.tsx new file mode 100644 index 0000000..5bf73ea --- /dev/null +++ b/src/common/layout/LayoutGridItem.tsx @@ -0,0 +1,76 @@ +import { FC, useMemo } from 'react'; +import { Base } from '../Base'; +import { Column, ColumnProps } from '../Column'; +import { LayoutItemCountView } from './LayoutItemCountView'; +import { LayoutLimitedEditionStyledNumberView } from './limited-edition'; + +export interface LayoutGridItemProps extends ColumnProps +{ + itemImage?: string; + itemColor?: string; + itemActive?: boolean; + itemCount?: number; + itemCountMinimum?: number; + itemUniqueSoldout?: boolean; + itemUniqueNumber?: number; + itemUnseen?: boolean; + itemHighlight?: boolean; + disabled?: boolean; +} + +export const LayoutGridItem: FC = props => +{ + const { itemImage = undefined, itemColor = undefined, itemActive = false, itemCount = 1, itemCountMinimum = 1, itemUniqueSoldout = false, itemUniqueNumber = -2, itemUnseen = false, itemHighlight = false, disabled = false, center = true, column = true, style = {}, classNames = [], position = 'relative', overflow = 'hidden', children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'layout-grid-item', 'border', 'border-2', 'border-muted', 'rounded' ]; + + + if(itemActive) newClassNames.push('!bg-[#ececec] !border-[#fff]'); + + if(itemUniqueSoldout || (itemUniqueNumber > 0)) newClassNames.push('unique-item'); + + if(itemUniqueSoldout) newClassNames.push('sold-out'); + + if(itemUnseen) newClassNames.push('unseen'); + + if(itemHighlight) newClassNames.push('has-highlight'); + + if(disabled) newClassNames.push('disabled'); + + if(itemImage === null) newClassNames.push('icon', 'loading-icon'); + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ itemActive, itemUniqueSoldout, itemUniqueNumber, itemUnseen, itemHighlight, disabled, itemImage, classNames ]); + + const getStyle = useMemo(() => + { + let newStyle = { ...style }; + + if(itemImage && !(itemUniqueSoldout || (itemUniqueNumber > 0))) newStyle.backgroundImage = `url(${ itemImage })`; + + if(itemColor) newStyle.backgroundColor = itemColor; + + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; + + return newStyle; + }, [ style, itemImage, itemColor, itemUniqueSoldout, itemUniqueNumber ]); + + return ( + + { (itemCount > itemCountMinimum) && + } + { (itemUniqueNumber > 0) && + <> + +
+ +
+ } + { children } +
+ ); +}; diff --git a/src/common/layout/LayoutImage.tsx b/src/common/layout/LayoutImage.tsx new file mode 100644 index 0000000..f3db3bd --- /dev/null +++ b/src/common/layout/LayoutImage.tsx @@ -0,0 +1,13 @@ +import { DetailedHTMLProps, FC, HTMLAttributes } from 'react'; + +export interface LayoutImageProps extends DetailedHTMLProps, HTMLImageElement> +{ + imageUrl?: string; +} + +export const LayoutImage: FC = props => +{ + const { imageUrl = null, className = '', ...rest } = props; + + return ; +}; diff --git a/src/common/layout/LayoutItemCountView.tsx b/src/common/layout/LayoutItemCountView.tsx new file mode 100644 index 0000000..5b14ed5 --- /dev/null +++ b/src/common/layout/LayoutItemCountView.tsx @@ -0,0 +1,28 @@ +import { FC, useMemo } from 'react'; +import { Base, BaseProps } from '../Base'; + +interface LayoutItemCountViewProps extends BaseProps +{ + count: number; +} + +export const LayoutItemCountView: FC = props => +{ + const { count = 0, position = 'absolute', classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'inline-block px-[.65em] py-[.35em] text-[.75em] font-bold leading-none text-[#fff] text-center whitespace-nowrap align-baseline rounded-[.25rem]', '!border-[1px] !border-[solid] !border-[#283F5D]', 'border-black', 'bg-danger', 'px-1', 'top-[2px] right-[2px] text-[9.5px] px-[3px] py-[2px] ' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( + + { count } + { children } + + ); +}; diff --git a/src/common/layout/LayoutLoadingSpinnerView.tsx b/src/common/layout/LayoutLoadingSpinnerView.tsx new file mode 100644 index 0000000..1d2641f --- /dev/null +++ b/src/common/layout/LayoutLoadingSpinnerView.tsx @@ -0,0 +1,15 @@ +import { FC } from 'react'; +import { Base, BaseProps } from '../Base'; + +export const LayoutLoadingSpinnerView: FC> = props => +{ + const { ...rest } = props; + + return ( + + + + + + ); +}; diff --git a/src/common/layout/LayoutMiniCameraView.tsx b/src/common/layout/LayoutMiniCameraView.tsx new file mode 100644 index 0000000..70962bb --- /dev/null +++ b/src/common/layout/LayoutMiniCameraView.tsx @@ -0,0 +1,73 @@ +import { GetRoomEngine, NitroRectangle, NitroTexture } from '@nitrots/nitro-renderer'; +import { FC, useRef } from 'react'; +import { LocalizeText, PlaySound, SoundNames } from '../../api'; +import { DraggableWindow } from '../draggable-window'; + +interface LayoutMiniCameraViewProps { + roomId: number; + textureReceiver: (texture: NitroTexture) => Promise; + onClose: () => void; +} + +export const LayoutMiniCameraView: FC = props => { + const { roomId = -1, textureReceiver = null, onClose = null } = props; + const elementRef = useRef(); + + const getCameraBounds = () => { + if (!elementRef || !elementRef.current) return null; + + const frameBounds = elementRef.current.getBoundingClientRect(); + + return new NitroRectangle( + Math.floor(frameBounds.x), + Math.floor(frameBounds.y), + Math.floor(frameBounds.width), + Math.floor(frameBounds.height) + ); + }; + + const takePicture = () => { + PlaySound(SoundNames.CAMERA_SHUTTER); + textureReceiver(GetRoomEngine().createTextureFromRoom(roomId, 1, getCameraBounds())); + }; + + return ( + +
+
+
+
+ + +
+
+
+ + ); +}; \ No newline at end of file diff --git a/src/common/layout/LayoutNotificationAlertView.tsx b/src/common/layout/LayoutNotificationAlertView.tsx new file mode 100644 index 0000000..b1cdf53 --- /dev/null +++ b/src/common/layout/LayoutNotificationAlertView.tsx @@ -0,0 +1,35 @@ +import { FC, useMemo } from 'react'; +import { NotificationAlertType } from '../../api'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroCardViewProps } from '../card'; + +export interface LayoutNotificationAlertViewProps extends NitroCardViewProps +{ + title?: string; + type?: string; + onClose: () => void; +} + +export const LayoutNotificationAlertView: FC = props => +{ + const { title = '', onClose = null, classNames = [], children = null,type = NotificationAlertType.DEFAULT, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'nitro-alert' ]; + + newClassNames.push('nitro-alert-' + type); + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames, type ]); + + return ( + + + + { children } + + + ); +}; diff --git a/src/common/layout/LayoutNotificationBubbleView.tsx b/src/common/layout/LayoutNotificationBubbleView.tsx new file mode 100644 index 0000000..0b62a84 --- /dev/null +++ b/src/common/layout/LayoutNotificationBubbleView.tsx @@ -0,0 +1,58 @@ +import { AnimatePresence, motion } from 'framer-motion'; +import { FC, useEffect, useMemo, useState } from 'react'; +import { Flex, FlexProps } from '../Flex'; + +export interface LayoutNotificationBubbleViewProps extends FlexProps +{ + fadesOut?: boolean; + timeoutMs?: number; + onClose: () => void; +} + +export const LayoutNotificationBubbleView: FC = props => +{ + const { fadesOut = true, timeoutMs = 8000, onClose = null, overflow = 'hidden', classNames = [], ...rest } = props; + const [ isVisible, setIsVisible ] = useState(false); + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'text-sm bg-[#1c1c20f2] px-[5px] py-[6px] [box-shadow:inset_0_5px_#22222799,_inset_0_-4px_#12121599] ', 'rounded' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + useEffect(() => + { + setIsVisible(true); + + return () => setIsVisible(false); + }, []); + + useEffect(() => + { + if(!fadesOut) return; + + const timeout = setTimeout(() => + { + setIsVisible(false); + + setTimeout(() => onClose(), 300); + }, timeoutMs); + + return () => clearTimeout(timeout); + }, [ fadesOut, timeoutMs, onClose ]); + + return ( + + { isVisible && + + + } + + ); +}; diff --git a/src/common/layout/LayoutPetImageView.tsx b/src/common/layout/LayoutPetImageView.tsx new file mode 100644 index 0000000..acf1a79 --- /dev/null +++ b/src/common/layout/LayoutPetImageView.tsx @@ -0,0 +1,123 @@ +import { GetRoomEngine, IPetCustomPart, PetFigureData, TextureUtils, Vector3d } from '@nitrots/nitro-renderer'; +import { CSSProperties, FC, useEffect, useMemo, useRef, useState } from 'react'; +import { Base, BaseProps } from '../Base'; + +interface LayoutPetImageViewProps extends BaseProps +{ + figure?: string; + typeId?: number; + paletteId?: number; + petColor?: number; + customParts?: IPetCustomPart[]; + posture?: string; + headOnly?: boolean; + direction?: number; + scale?: number; +} + +export const LayoutPetImageView: FC = props => +{ + const { figure = '', typeId = -1, paletteId = -1, petColor = 0xFFFFFF, customParts = [], posture = 'std', headOnly = false, direction = 0, scale = 1, style = {}, ...rest } = props; + const [ petUrl, setPetUrl ] = useState(null); + const [ width, setWidth ] = useState(0); + const [ height, setHeight ] = useState(0); + const isDisposed = useRef(false); + + const getStyle = useMemo(() => + { + let newStyle: CSSProperties = {}; + + if(petUrl && petUrl.length) newStyle.backgroundImage = `url(${ petUrl })`; + + if(scale !== 1) + { + newStyle.transform = `scale(${ scale })`; + + if(!(scale % 1)) newStyle.imageRendering = 'pixelated'; + } + + newStyle.width = width; + newStyle.height = height; + + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; + + return newStyle; + }, [ petUrl, scale, style, width, height ]); + + useEffect(() => + { + let url = null; + + let petTypeId = typeId; + let petPaletteId = paletteId; + let petColor1 = petColor; + let petCustomParts: IPetCustomPart[] = customParts; + let petHeadOnly = headOnly; + + if(figure && figure.length) + { + const petFigureData = new PetFigureData(figure); + + petTypeId = petFigureData.typeId; + petPaletteId = petFigureData.paletteId; + petColor1 = petFigureData.color; + petCustomParts = petFigureData.customParts; + } + + if(petTypeId === 16) petHeadOnly = false; + + const imageResult = GetRoomEngine().getRoomObjectPetImage(petTypeId, petPaletteId, petColor1, new Vector3d((direction * 45)), 64, { + imageReady: async (id, texture, image) => + { + if(isDisposed.current) return; + + if(image) + { + setPetUrl(image.src); + setWidth(image.width); + setHeight(image.height); + } + + else if(texture) + { + setPetUrl(await TextureUtils.generateImageUrl(texture)); + setWidth(texture.width); + setHeight(texture.height); + } + }, + imageFailed: (id) => + { + + } + }, petHeadOnly, 0, petCustomParts, posture); + + if(imageResult) + { + (async () => + { + const image = await imageResult.getImage(); + + if(image) + { + setPetUrl(image.src); + setWidth(image.width); + setHeight(image.height); + } + })(); + } + }, [ figure, typeId, paletteId, petColor, customParts, posture, headOnly, direction ]); + + useEffect(() => + { + isDisposed.current = false; + + return () => + { + isDisposed.current = true; + }; + }, []); + + const url = `url('${ petUrl }')`; + + return ; +}; diff --git a/src/common/layout/LayoutPrizeProductImageView.tsx b/src/common/layout/LayoutPrizeProductImageView.tsx new file mode 100644 index 0000000..a83861e --- /dev/null +++ b/src/common/layout/LayoutPrizeProductImageView.tsx @@ -0,0 +1,30 @@ +import { FC } from 'react'; +import { ProductTypeEnum } from '../../api'; +import { LayoutBadgeImageView } from './LayoutBadgeImageView'; +import { LayoutCurrencyIcon } from './LayoutCurrencyIcon'; +import { LayoutFurniImageView } from './LayoutFurniImageView'; + +interface LayoutPrizeProductImageViewProps +{ + productType: string; + classId: number; + extraParam?: string; +} + +export const LayoutPrizeProductImageView: FC = props => +{ + const { productType = ProductTypeEnum.FLOOR, classId = -1, extraParam = undefined } = props; + + switch(productType) + { + case ProductTypeEnum.WALL: + case ProductTypeEnum.FLOOR: + return ; + case ProductTypeEnum.BADGE: + return ; + case ProductTypeEnum.HABBO_CLUB: + return ; + } + + return null; +}; diff --git a/src/common/layout/LayoutProgressBar.tsx b/src/common/layout/LayoutProgressBar.tsx new file mode 100644 index 0000000..dbbd8f9 --- /dev/null +++ b/src/common/layout/LayoutProgressBar.tsx @@ -0,0 +1,32 @@ +import { FC, useMemo } from 'react'; +import { Base, Column, ColumnProps, Flex } from '..'; + +interface LayoutProgressBarProps extends ColumnProps +{ + text?: string; + progress: number; + maxProgress?: number; +} + +export const LayoutProgressBar: FC = props => +{ + const { text = '', progress = 0, maxProgress = 100, position = 'relative', justifyContent = 'center', classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'border-[1px] border-[solid] border-[#fff] p-[2px] h-[20px] rounded-[.25rem] overflow-hidden bg-[#1E7295] ', 'text-white' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( + + { text && (text.length > 0) && + { text } } + + { children } + + ); +}; diff --git a/src/common/layout/LayoutRarityLevelView.tsx b/src/common/layout/LayoutRarityLevelView.tsx new file mode 100644 index 0000000..f6711aa --- /dev/null +++ b/src/common/layout/LayoutRarityLevelView.tsx @@ -0,0 +1,28 @@ +import { FC, useMemo } from 'react'; +import { Base, BaseProps } from '../Base'; + +interface LayoutRarityLevelViewProps extends BaseProps +{ + level: number; +} + +export const LayoutRarityLevelView: FC = props => +{ + const { level = 0, classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'nitro-rarity-level' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( + +
{ level }
+ { children } + + ); +}; diff --git a/src/common/layout/LayoutRoomObjectImageView.tsx b/src/common/layout/LayoutRoomObjectImageView.tsx new file mode 100644 index 0000000..01328af --- /dev/null +++ b/src/common/layout/LayoutRoomObjectImageView.tsx @@ -0,0 +1,59 @@ +import { GetRoomEngine, TextureUtils, Vector3d } from '@nitrots/nitro-renderer'; +import { CSSProperties, FC, useEffect, useMemo, useState } from 'react'; +import { Base, BaseProps } from '../Base'; + +interface LayoutRoomObjectImageViewProps extends BaseProps +{ + roomId: number; + objectId: number; + category: number; + direction?: number; + scale?: number; +} + +export const LayoutRoomObjectImageView: FC = props => +{ + const { roomId = -1, objectId = 1, category = -1, direction = 2, scale = 1, style = {}, ...rest } = props; + const [ imageElement, setImageElement ] = useState(null); + + const getStyle = useMemo(() => + { + let newStyle: CSSProperties = {}; + + if(imageElement?.src?.length) + { + newStyle.backgroundImage = `url('${ imageElement.src }')`; + newStyle.width = imageElement.width; + newStyle.height = imageElement.height; + } + + if(scale !== 1) + { + newStyle.transform = `scale(${ scale })`; + + if(!(scale % 1)) newStyle.imageRendering = 'pixelated'; + } + + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; + + return newStyle; + }, [ imageElement, scale, style ]); + + useEffect(() => + { + const imageResult = GetRoomEngine().getRoomObjectImage(roomId, objectId, category, new Vector3d(direction * 45), 64, { + imageReady: async (id, texture, image) => setImageElement(await TextureUtils.generateImage(texture)), + imageFailed: null + }); + + // needs (roomObjectImage.data.width > 140) || (roomObjectImage.data.height > 200) scale 1 + + if(!imageResult) return; + + (async () => setImageElement(await TextureUtils.generateImage(imageResult.data)))(); + }, [ roomId, objectId, category, direction, scale ]); + + if(!imageElement) return null; + + return ; +}; diff --git a/src/common/layout/LayoutRoomPreviewerView.tsx b/src/common/layout/LayoutRoomPreviewerView.tsx new file mode 100644 index 0000000..daceeef --- /dev/null +++ b/src/common/layout/LayoutRoomPreviewerView.tsx @@ -0,0 +1,89 @@ +import { GetRenderer, GetTicker, NitroTicker, RoomPreviewer, TextureUtils } from '@nitrots/nitro-renderer'; +import { FC, MouseEvent, useEffect, useRef } from 'react'; + +export const LayoutRoomPreviewerView: FC<{ + roomPreviewer: RoomPreviewer; + height?: number; +}> = props => +{ + const { roomPreviewer = null, height = 0 } = props; + const elementRef = useRef(); + + const onClick = (event: MouseEvent) => + { + if(!roomPreviewer) return; + + if(event.shiftKey) roomPreviewer.changeRoomObjectDirection(); + else roomPreviewer.changeRoomObjectState(); + }; + + useEffect(() => + { + if(!elementRef) return; + + const width = elementRef.current.parentElement.clientWidth; + const texture = TextureUtils.createRenderTexture(width, height); + + const update = async (ticker: NitroTicker) => + { + if(!roomPreviewer || !elementRef.current) return; + + roomPreviewer.updatePreviewRoomView(); + + const renderingCanvas = roomPreviewer.getRenderingCanvas(); + + if(!renderingCanvas.canvasUpdated) return; + + GetRenderer().render({ + target: texture, + container: renderingCanvas.master, + clear: true + }); + + let canvas = GetRenderer().texture.generateCanvas(texture); + const base64 = canvas.toDataURL('image/png'); + + canvas = null; + + elementRef.current.style.backgroundImage = `url(${ base64 })`; + }; + + GetTicker().add(update); + + const resizeObserver = new ResizeObserver(() => + { + if(!roomPreviewer || !elementRef.current) return; + + const width = elementRef.current.parentElement.offsetWidth; + + roomPreviewer.modifyRoomCanvas(width, height); + + update(GetTicker()); + }); + + roomPreviewer.getRoomCanvas(width, height); + + resizeObserver.observe(elementRef.current); + + return () => + { + GetTicker().remove(update); + + resizeObserver.disconnect(); + + texture.destroy(true); + }; + }, [ roomPreviewer, elementRef, height ]); + + return ( +
+ ); +}; diff --git a/src/common/layout/LayoutRoomThumbnailView.tsx b/src/common/layout/LayoutRoomThumbnailView.tsx new file mode 100644 index 0000000..d6626ff --- /dev/null +++ b/src/common/layout/LayoutRoomThumbnailView.tsx @@ -0,0 +1,37 @@ +import { FC, useMemo } from 'react'; +import { GetConfigurationValue } from '../../api'; +import { Base, BaseProps } from '../Base'; + +export interface LayoutRoomThumbnailViewProps extends BaseProps +{ + roomId?: number; + customUrl?: string; +} + +export const LayoutRoomThumbnailView: FC = props => +{ + const { roomId = -1, customUrl = null, shrink = true, overflow = 'hidden', classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'relative w-[110px] h-[110px] bg-[url("@/assets/images/navigator/thumbnail_placeholder.png")] bg-no-repeat bg-center', 'rounded', '!border-[1px] !border-[solid] !border-[#283F5D]' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + const getImageUrl = useMemo(() => + { + if(customUrl && customUrl.length) return (GetConfigurationValue('image.library.url') + customUrl); + + return (GetConfigurationValue('thumbnails.url').replace('%thumbnail%', roomId.toString())); + }, [ customUrl, roomId ]); + + return ( + + { getImageUrl && } + { children } + + ); +}; diff --git a/src/common/layout/LayoutTrophyView.tsx b/src/common/layout/LayoutTrophyView.tsx new file mode 100644 index 0000000..dd2c284 --- /dev/null +++ b/src/common/layout/LayoutTrophyView.tsx @@ -0,0 +1,42 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../api'; +import { Base } from '../Base'; +import { Column } from '../Column'; +import { Flex } from '../Flex'; +import { Text } from '../Text'; +import { DraggableWindow } from '../draggable-window'; + +interface LayoutTrophyViewProps +{ + color: string; + message: string; + date: string; + senderName: string; + customTitle?: string; + onCloseClick: () => void; +} + +export const LayoutTrophyView: FC = props => +{ + const { color = '', message = '', date = '', senderName = '', customTitle = null, onCloseClick = null } = props; + + return ( + + + + + { LocalizeText('widget.furni.trophy.title') } + + + { customTitle && + { customTitle } } + { message } + + + { date } + { senderName } + + + + ); +}; diff --git a/src/common/layout/UserProfileIconView.tsx b/src/common/layout/UserProfileIconView.tsx new file mode 100644 index 0000000..8898c70 --- /dev/null +++ b/src/common/layout/UserProfileIconView.tsx @@ -0,0 +1,29 @@ +import { FC, useMemo } from 'react'; +import { GetUserProfile } from '../../api'; +import { Base, BaseProps } from '../Base'; + +export interface UserProfileIconViewProps extends BaseProps +{ + userId?: number; + userName?: string; +} + +export const UserProfileIconView: FC = props => +{ + const { userId = 0, userName = null, classNames = [], pointer = true, children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'bg-[url("@/assets/images/friends/friends-spritesheet.png")]', 'w-[13px] h-[11px] bg-[-51px_-91px]' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( + GetUserProfile(userId) } { ...rest }> + { children } + + ); +}; diff --git a/src/common/layout/index.ts b/src/common/layout/index.ts new file mode 100644 index 0000000..cbb0568 --- /dev/null +++ b/src/common/layout/index.ts @@ -0,0 +1,24 @@ +export * from './LayoutAvatarImageView'; +export * from './LayoutBackgroundImage'; +export * from './LayoutBadgeImageView'; +export * from './LayoutCounterTimeView'; +export * from './LayoutCurrencyIcon'; +export * from './LayoutFurniIconImageView'; +export * from './LayoutFurniImageView'; +export * from './LayoutGiftTagView'; +export * from './LayoutGridItem'; +export * from './LayoutImage'; +export * from './LayoutItemCountView'; +export * from './LayoutLoadingSpinnerView'; +export * from './LayoutMiniCameraView'; +export * from './LayoutNotificationAlertView'; +export * from './LayoutNotificationBubbleView'; +export * from './LayoutPetImageView'; +export * from './LayoutProgressBar'; +export * from './LayoutRarityLevelView'; +export * from './LayoutRoomObjectImageView'; +export * from './LayoutRoomPreviewerView'; +export * from './LayoutRoomThumbnailView'; +export * from './LayoutTrophyView'; +export * from './UserProfileIconView'; +export * from './limited-edition'; diff --git a/src/common/layout/limited-edition/LayoutLimitedEditionCompactPlateView.tsx b/src/common/layout/limited-edition/LayoutLimitedEditionCompactPlateView.tsx new file mode 100644 index 0000000..86fc977 --- /dev/null +++ b/src/common/layout/limited-edition/LayoutLimitedEditionCompactPlateView.tsx @@ -0,0 +1,35 @@ +import { FC, useMemo } from 'react'; +import { Base, BaseProps } from '../../Base'; +import { LayoutLimitedEditionStyledNumberView } from './LayoutLimitedEditionStyledNumberView'; + +interface LayoutLimitedEditionCompactPlateViewProps extends BaseProps +{ + uniqueNumber: number; + uniqueSeries: number; +} + +export const LayoutLimitedEditionCompactPlateView: FC = props => +{ + const { uniqueNumber = 0, uniqueSeries = 0, classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'unique-compact-plate', 'z-index-1' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( + +
+ +
+
+ +
+ { children } + + ); +}; diff --git a/src/common/layout/limited-edition/LayoutLimitedEditionCompletePlateView.tsx b/src/common/layout/limited-edition/LayoutLimitedEditionCompletePlateView.tsx new file mode 100644 index 0000000..5bcf776 --- /dev/null +++ b/src/common/layout/limited-edition/LayoutLimitedEditionCompletePlateView.tsx @@ -0,0 +1,40 @@ +import { FC, useMemo } from 'react'; +import { LocalizeText } from '../../../api'; +import { Base, BaseProps } from '../../Base'; +import { Column } from '../../Column'; +import { LayoutLimitedEditionStyledNumberView } from './LayoutLimitedEditionStyledNumberView'; + +interface LayoutLimitedEditionCompletePlateViewProps extends BaseProps +{ + uniqueLimitedItemsLeft: number; + uniqueLimitedSeriesSize: number; +} + +export const LayoutLimitedEditionCompletePlateView: FC = props => +{ + const { uniqueLimitedItemsLeft = 0, uniqueLimitedSeriesSize = 0, classNames = [], ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'unique-complete-plate' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( + + +
+ { LocalizeText('unique.items.left') } +
+
+
+ { LocalizeText('unique.items.number.sold') } +
+
+
+ + ); +}; diff --git a/src/common/layout/limited-edition/LayoutLimitedEditionStyledNumberView.tsx b/src/common/layout/limited-edition/LayoutLimitedEditionStyledNumberView.tsx new file mode 100644 index 0000000..992ad09 --- /dev/null +++ b/src/common/layout/limited-edition/LayoutLimitedEditionStyledNumberView.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react'; + +interface LayoutLimitedEditionStyledNumberViewProps +{ + value: number; +} + +export const LayoutLimitedEditionStyledNumberView: FC = props => +{ + const { value = 0 } = props; + const numbers = value.toString().split(''); + + return ( + <> + { numbers.map((number, index) => ) } + + ); +}; diff --git a/src/common/layout/limited-edition/index.ts b/src/common/layout/limited-edition/index.ts new file mode 100644 index 0000000..ee41cf9 --- /dev/null +++ b/src/common/layout/limited-edition/index.ts @@ -0,0 +1,3 @@ +export * from './LayoutLimitedEditionCompactPlateView'; +export * from './LayoutLimitedEditionCompletePlateView'; +export * from './LayoutLimitedEditionStyledNumberView'; diff --git a/src/common/transitions/TransitionAnimation.tsx b/src/common/transitions/TransitionAnimation.tsx new file mode 100644 index 0000000..8e34849 --- /dev/null +++ b/src/common/transitions/TransitionAnimation.tsx @@ -0,0 +1,52 @@ +import { FC, ReactNode, useEffect, useState } from 'react'; +import { Transition } from 'react-transition-group'; +import { getTransitionAnimationStyle } from './TransitionAnimationStyles'; + +interface TransitionAnimationProps +{ + type: string; + inProp: boolean; + timeout?: number; + className?: string; + children?: ReactNode; +} + +export const TransitionAnimation: FC = props => +{ + const { type = null, inProp = false, timeout = 300, className = null, children = null } = props; + + const [ isChildrenVisible, setChildrenVisible ] = useState(false); + + useEffect(() => + { + let timeoutData: ReturnType = null; + + if(inProp) + { + setChildrenVisible(true); + } + else + { + timeoutData = setTimeout(() => + { + setChildrenVisible(false); + clearTimeout(timeout); + }, timeout); + } + + return () => + { + if(timeoutData) clearTimeout(timeoutData); + }; + }, [ inProp, timeout ]); + + return ( + + { state => ( +
+ { isChildrenVisible && children } +
+ ) } +
+ ); +}; diff --git a/src/common/transitions/TransitionAnimationStyles.ts b/src/common/transitions/TransitionAnimationStyles.ts new file mode 100644 index 0000000..feebdcc --- /dev/null +++ b/src/common/transitions/TransitionAnimationStyles.ts @@ -0,0 +1,136 @@ +import { CSSProperties } from 'react'; +import { TransitionStatus } from 'react-transition-group'; +import { ENTERING, EXITING } from 'react-transition-group/Transition'; +import { TransitionAnimationTypes } from './TransitionAnimationTypes'; + +export function getTransitionAnimationStyle(type: string, transition: TransitionStatus, timeout: number = 300): Partial +{ + switch(type) + { + case TransitionAnimationTypes.BOUNCE: + switch(transition) + { + default: + return {}; + case ENTERING: + return { + animationName: 'bounceIn', + animationDuration: `${ timeout }ms` + }; + case EXITING: + return { + animationName: 'bounceOut', + animationDuration: `${ timeout }ms` + }; + } + case TransitionAnimationTypes.SLIDE_LEFT: + switch(transition) + { + default: + return {}; + case ENTERING: + return { + animationName: 'slideInLeft', + animationDuration: `${ timeout }ms` + }; + case EXITING: + return { + animationName: 'slideOutLeft', + animationDuration: `${ timeout }ms` + }; + } + case TransitionAnimationTypes.SLIDE_RIGHT: + switch(transition) + { + default: + return {}; + case ENTERING: + return { + animationName: 'slideInRight', + animationDuration: `${ timeout }ms` + }; + case EXITING: + return { + animationName: 'slideOutRight', + animationDuration: `${ timeout }ms` + }; + } + case TransitionAnimationTypes.FLIP_X: + switch(transition) + { + default: + return {}; + case ENTERING: + return { + animationName: 'flipInX', + animationDuration: `${ timeout }ms` + }; + case EXITING: + return { + animationName: 'flipOutX', + animationDuration: `${ timeout }ms` + }; + } + case TransitionAnimationTypes.FADE_UP: + switch(transition) + { + default: + return {}; + case ENTERING: + return { + animationName: 'fadeInUp', + animationDuration: `${ timeout }ms` + }; + case EXITING: + return { + animationName: 'fadeOutDown', + animationDuration: `${ timeout }ms` + }; + } + case TransitionAnimationTypes.FADE_IN: + switch(transition) + { + default: + return {}; + case ENTERING: + return { + animationName: 'fadeIn', + animationDuration: `${ timeout }ms` + }; + case EXITING: + return { + animationName: 'fadeOut', + animationDuration: `${ timeout }ms` + }; + } + case TransitionAnimationTypes.FADE_DOWN: + switch(transition) + { + default: + return {}; + case ENTERING: + return { + animationName: 'fadeInDown', + animationDuration: `${ timeout }ms` + }; + case EXITING: + return { + animationName: 'fadeOutUp', + animationDuration: `${ timeout }ms` + }; + } + case TransitionAnimationTypes.HEAD_SHAKE: + switch(transition) + { + default: + return {}; + case ENTERING: + return { + animationName: 'headShake', + animationDuration: `${ timeout }ms` + }; + } + } + + return null; +} diff --git a/src/common/transitions/TransitionAnimationTypes.ts b/src/common/transitions/TransitionAnimationTypes.ts new file mode 100644 index 0000000..4ecc23b --- /dev/null +++ b/src/common/transitions/TransitionAnimationTypes.ts @@ -0,0 +1,11 @@ +export class TransitionAnimationTypes +{ + public static BOUNCE: string = 'bounce'; + public static SLIDE_LEFT: string = 'slideLeft'; + public static SLIDE_RIGHT: string = 'slideRight'; + public static FLIP_X: string = 'flipX'; + public static FADE_IN: string = 'fadeIn'; + public static FADE_DOWN: string = 'fadeDown'; + public static FADE_UP: string = 'fadeUp'; + public static HEAD_SHAKE: string = 'headShake'; +} diff --git a/src/common/transitions/index.ts b/src/common/transitions/index.ts new file mode 100644 index 0000000..283a005 --- /dev/null +++ b/src/common/transitions/index.ts @@ -0,0 +1,3 @@ +export * from './TransitionAnimation'; +export * from './TransitionAnimationStyles'; +export * from './TransitionAnimationTypes'; diff --git a/src/common/types/AlignItemType.ts b/src/common/types/AlignItemType.ts new file mode 100644 index 0000000..5a61476 --- /dev/null +++ b/src/common/types/AlignItemType.ts @@ -0,0 +1 @@ +export type AlignItemType = 'start' | 'end' | 'center' | 'baseline' | 'stretch'; diff --git a/src/common/types/AlignSelfType.ts b/src/common/types/AlignSelfType.ts new file mode 100644 index 0000000..8e26378 --- /dev/null +++ b/src/common/types/AlignSelfType.ts @@ -0,0 +1 @@ +export type AlignSelfType = 'start' | 'end' | 'center' | 'baseline' | 'stretch'; diff --git a/src/common/types/ButtonSizeType.ts b/src/common/types/ButtonSizeType.ts new file mode 100644 index 0000000..a520c88 --- /dev/null +++ b/src/common/types/ButtonSizeType.ts @@ -0,0 +1 @@ +export type ButtonSizeType = 'lg' | 'sm' | 'md'; diff --git a/src/common/types/ColorVariantType.ts b/src/common/types/ColorVariantType.ts new file mode 100644 index 0000000..945b64d --- /dev/null +++ b/src/common/types/ColorVariantType.ts @@ -0,0 +1 @@ +export type ColorVariantType = 'primary' | 'success' | 'danger' | 'secondary' | 'link' | 'black' | 'white' | 'dark' | 'warning' | 'muted' | 'light' | 'gray'; diff --git a/src/common/types/ColumnSizesType.ts b/src/common/types/ColumnSizesType.ts new file mode 100644 index 0000000..2b130d8 --- /dev/null +++ b/src/common/types/ColumnSizesType.ts @@ -0,0 +1 @@ +export type ColumnSizesType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; diff --git a/src/common/types/DisplayType.ts b/src/common/types/DisplayType.ts new file mode 100644 index 0000000..7551d72 --- /dev/null +++ b/src/common/types/DisplayType.ts @@ -0,0 +1 @@ +export type DisplayType = 'none' | 'inline' | 'inline-block' | 'block' | 'grid' | 'table' | 'table-cell' | 'table-row' | 'flex' | 'inline-flex'; diff --git a/src/common/types/FloatType.ts b/src/common/types/FloatType.ts new file mode 100644 index 0000000..63e495f --- /dev/null +++ b/src/common/types/FloatType.ts @@ -0,0 +1 @@ +export type FloatType = 'start' | 'end' | 'none'; diff --git a/src/common/types/FontSizeType.ts b/src/common/types/FontSizeType.ts new file mode 100644 index 0000000..120c11c --- /dev/null +++ b/src/common/types/FontSizeType.ts @@ -0,0 +1 @@ +export type FontSizeType = 1 | 2 | 3 | 4 | 5 | 6; diff --git a/src/common/types/FontWeightType.ts b/src/common/types/FontWeightType.ts new file mode 100644 index 0000000..c7c9286 --- /dev/null +++ b/src/common/types/FontWeightType.ts @@ -0,0 +1 @@ +export type FontWeightType = 'bold' | 'bolder' | 'normal' | 'light' | 'lighter'; diff --git a/src/common/types/JustifyContentType.ts b/src/common/types/JustifyContentType.ts new file mode 100644 index 0000000..73a318d --- /dev/null +++ b/src/common/types/JustifyContentType.ts @@ -0,0 +1 @@ +export type JustifyContentType = 'start' | 'end' | 'center' | 'between' | 'around' | 'evenly'; diff --git a/src/common/types/OverflowType.ts b/src/common/types/OverflowType.ts new file mode 100644 index 0000000..9231ff9 --- /dev/null +++ b/src/common/types/OverflowType.ts @@ -0,0 +1 @@ +export type OverflowType = 'auto' | 'hidden' | 'visible' | 'scroll' | 'y-scroll' | 'unset'; diff --git a/src/common/types/PositionType.ts b/src/common/types/PositionType.ts new file mode 100644 index 0000000..4e20b2f --- /dev/null +++ b/src/common/types/PositionType.ts @@ -0,0 +1 @@ +export type PositionType = 'static' | 'relative' | 'fixed' | 'absolute' | 'sticky'; diff --git a/src/common/types/SpacingType.ts b/src/common/types/SpacingType.ts new file mode 100644 index 0000000..91c2bb5 --- /dev/null +++ b/src/common/types/SpacingType.ts @@ -0,0 +1 @@ +export type SpacingType = 0 | 1 | 2 | 3 | 4 | 5; diff --git a/src/common/types/TextAlignType.ts b/src/common/types/TextAlignType.ts new file mode 100644 index 0000000..cb82648 --- /dev/null +++ b/src/common/types/TextAlignType.ts @@ -0,0 +1 @@ +export type TextAlignType = 'start' | 'center' | 'end'; diff --git a/src/common/types/index.ts b/src/common/types/index.ts new file mode 100644 index 0000000..333177e --- /dev/null +++ b/src/common/types/index.ts @@ -0,0 +1,14 @@ +export * from './AlignItemType'; +export * from './AlignSelfType'; +export * from './ButtonSizeType'; +export * from './ColorVariantType'; +export * from './ColumnSizesType'; +export * from './DisplayType'; +export * from './FloatType'; +export * from './FontSizeType'; +export * from './FontWeightType'; +export * from './JustifyContentType'; +export * from './OverflowType'; +export * from './PositionType'; +export * from './SpacingType'; +export * from './TextAlignType'; diff --git a/src/common/utils/CreateTransitionToIcon.ts b/src/common/utils/CreateTransitionToIcon.ts new file mode 100644 index 0000000..20e9885 --- /dev/null +++ b/src/common/utils/CreateTransitionToIcon.ts @@ -0,0 +1,13 @@ +import { GetEventDispatcher, NitroToolbarAnimateIconEvent } from '@nitrots/nitro-renderer'; + +export const CreateTransitionToIcon = (image: HTMLImageElement, fromElement: HTMLElement, icon: string) => +{ + const bounds = fromElement.getBoundingClientRect(); + const x = (bounds.x + (bounds.width / 2)); + const y = (bounds.y + (bounds.height / 2)); + const event = new NitroToolbarAnimateIconEvent(image, x, y); + + event.iconName = icon; + + GetEventDispatcher().dispatchEvent(event); +}; diff --git a/src/common/utils/FriendlyTimeView.tsx b/src/common/utils/FriendlyTimeView.tsx new file mode 100644 index 0000000..4a85c4a --- /dev/null +++ b/src/common/utils/FriendlyTimeView.tsx @@ -0,0 +1,28 @@ +import { FC, useEffect, useMemo, useState } from 'react'; +import { FriendlyTime } from '../../api'; +import { Base, BaseProps } from '../Base'; + +interface FriendlyTimeViewProps extends BaseProps +{ + seconds: number; + isShort?: boolean; +} + +export const FriendlyTimeView: FC = props => +{ + const { seconds = 0, isShort = false, children = null, ...rest } = props; + const [ updateId, setUpdateId ] = useState(-1); + + const getStartSeconds = useMemo(() => (Math.round(new Date().getSeconds()) - seconds), [ seconds ]); + + useEffect(() => + { + const interval = setInterval(() => setUpdateId(prevValue => (prevValue + 1)), 10000); + + return () => clearInterval(interval); + }, []); + + const value = (Math.round(new Date().getSeconds()) - getStartSeconds); + + return { isShort ? FriendlyTime.shortFormat(value) : FriendlyTime.format(value) }; +}; diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts new file mode 100644 index 0000000..11d60a3 --- /dev/null +++ b/src/common/utils/index.ts @@ -0,0 +1,2 @@ +export * from './CreateTransitionToIcon'; +export * from './FriendlyTimeView'; diff --git a/src/components/MainView.tsx b/src/components/MainView.tsx new file mode 100644 index 0000000..e8b838d --- /dev/null +++ b/src/components/MainView.tsx @@ -0,0 +1,119 @@ +import { AddLinkEventTracker, GetCommunication, HabboWebTools, ILinkEventTracker, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer'; +import { AnimatePresence, motion } from 'framer-motion'; +import { FC, useEffect, useState } from 'react'; +import { useNitroEvent } from '../hooks'; +import { AchievementsView } from './achievements/AchievementsView'; +import { AvatarEditorView } from './avatar-editor'; +import { CameraWidgetView } from './camera/CameraWidgetView'; +import { CampaignView } from './campaign/CampaignView'; +import { CatalogView } from './catalog/CatalogView'; +import { ChatHistoryView } from './chat-history/ChatHistoryView'; +import { FloorplanEditorView } from './floorplan-editor/FloorplanEditorView'; +import { FriendsView } from './friends/FriendsView'; +import { GameCenterView } from './game-center/GameCenterView'; +import { GroupsView } from './groups/GroupsView'; +import { GuideToolView } from './guide-tool/GuideToolView'; +import { HcCenterView } from './hc-center/HcCenterView'; +import { HelpView } from './help/HelpView'; +import { HotelView } from './hotel-view/HotelView'; +import { InventoryView } from './inventory/InventoryView'; +import { ModToolsView } from './mod-tools/ModToolsView'; +import { NavigatorView } from './navigator/NavigatorView'; +import { NitrobubbleHiddenView } from './nitrobubblehidden/NitrobubbleHiddenView'; +import { NitropediaView } from './nitropedia/NitropediaView'; +import { RightSideView } from './right-side/RightSideView'; +import { RoomView } from './room/RoomView'; +import { ToolbarView } from './toolbar/ToolbarView'; +import { UserProfileView } from './user-profile/UserProfileView'; +import { UserSettingsView } from './user-settings/UserSettingsView'; +import { WiredView } from './wired/WiredView'; + +export const MainView: FC<{}> = props => +{ + const [ isReady, setIsReady ] = useState(false); + const [ landingViewVisible, setLandingViewVisible ] = useState(true); + + useNitroEvent(RoomSessionEvent.CREATED, event => setLandingViewVisible(false)); + useNitroEvent(RoomSessionEvent.ENDED, event => setLandingViewVisible(event.openLandingView)); + + useEffect(() => + { + setIsReady(true); + + GetCommunication().connection.ready(); + }, []); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'open': + if(parts.length > 2) + { + switch(parts[2]) + { + case 'credits': + //HabboWebTools.openWebPageAndMinimizeClient(this._windowManager.getProperty(ExternalVariables.WEB_SHOP_RELATIVE_URL)); + break; + default: { + const name = parts[2]; + HabboWebTools.openHabblet(name); + } + } + } + return; + } + }, + eventUrlPrefix: 'habblet/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + return ( + <> + + { landingViewVisible && + + + } + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/components/achievements/AchievementBadgeView.tsx b/src/components/achievements/AchievementBadgeView.tsx new file mode 100644 index 0000000..5365265 --- /dev/null +++ b/src/components/achievements/AchievementBadgeView.tsx @@ -0,0 +1,19 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { AchievementUtilities } from '../../api'; +import { BaseProps, LayoutBadgeImageView } from '../../common'; + +interface AchievementBadgeViewProps extends BaseProps +{ + achievement: AchievementData; + scale?: number; +} + +export const AchievementBadgeView: FC = props => +{ + const { achievement = null, scale = 1, ...rest } = props; + + if(!achievement) return null; + + return ; +}; diff --git a/src/components/achievements/AchievementCategoryView.tsx b/src/components/achievements/AchievementCategoryView.tsx new file mode 100644 index 0000000..3a6f99f --- /dev/null +++ b/src/components/achievements/AchievementCategoryView.tsx @@ -0,0 +1,42 @@ +import { FC, useEffect } from 'react'; +import { AchievementCategory } from '../../api'; +import { Column } from '../../common'; +import { useAchievements } from '../../hooks'; +import { AchievementDetailsView } from './AchievementDetailsView'; +import { AchievementListView } from './achievement-list'; + +interface AchievementCategoryViewProps { + category: AchievementCategory; +} + +export const AchievementCategoryView: FC = ( + props, +) => +{ + const { category = null } = props; + const { selectedAchievement = null, setSelectedAchievementId = null } = + useAchievements(); + + useEffect(() => + { + if(!category) return; + + if(!selectedAchievement) + { + setSelectedAchievementId( + category?.achievements?.[0]?.achievementId, + ); + } + }, [category, selectedAchievement, setSelectedAchievementId]); + + if(!category) return null; + + return ( + + + {!!selectedAchievement && ( + + )} + + ); +}; diff --git a/src/components/achievements/AchievementDetailsView.tsx b/src/components/achievements/AchievementDetailsView.tsx new file mode 100644 index 0000000..a413883 --- /dev/null +++ b/src/components/achievements/AchievementDetailsView.tsx @@ -0,0 +1,53 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { AchievementUtilities, LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../api'; +import { Column, Flex, LayoutCurrencyIcon, LayoutProgressBar, Text } from '../../common'; +import { AchievementBadgeView } from './AchievementBadgeView'; + +interface AchievementDetailsViewProps +{ + achievement: AchievementData; +} + +export const AchievementDetailsView: FC = props => +{ + const { achievement = null } = props; + + if(!achievement) return null; + + return ( + + + + + { LocalizeText('achievements.details.level', [ 'level', 'limit' ], [ AchievementUtilities.getAchievementLevel(achievement).toString(), achievement.levelCount.toString() ]) } + + + +
+ + { LocalizeBadgeName(AchievementUtilities.getAchievementBadgeCode(achievement)) } + + + { LocalizeBadgeDescription(AchievementUtilities.getAchievementBadgeCode(achievement)) } + +
+ { ((achievement.levelRewardPoints > 0) || (achievement.scoreLimit > 0)) && +
+ { (achievement.levelRewardPoints > 0) && +
+ + { LocalizeText('achievements.details.reward') } + + + { achievement.levelRewardPoints } + + +
} + { (achievement.scoreLimit > 0) && + } +
} +
+
+ ); +}; diff --git a/src/components/achievements/AchievementsView.tsx b/src/components/achievements/AchievementsView.tsx new file mode 100644 index 0000000..bdb80ae --- /dev/null +++ b/src/components/achievements/AchievementsView.tsx @@ -0,0 +1,143 @@ +import +{ + AddLinkEventTracker, + ILinkEventTracker, + RemoveLinkEventTracker, +} from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { AchievementUtilities, LocalizeText } from '../../api'; +import { Column, LayoutImage, LayoutProgressBar, Text } from '../../common'; +import { useAchievements } from '../../hooks'; +import { NitroCard } from '../../layout'; +import { AchievementCategoryView } from './AchievementCategoryView'; +import { AchievementsCategoryListView } from './category-list'; + +export const AchievementsView: FC<{}> = (props) => +{ + const [isVisible, setIsVisible] = useState(false); + const { + achievementCategories = [], + selectedCategoryCode = null, + setSelectedCategoryCode = null, + achievementScore = 0, + getProgress = 0, + getMaxProgress = 0, + selectedCategory = null, + } = useAchievements(); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible((prevValue) => !prevValue); + return; + } + }, + eventUrlPrefix: 'achievements/', + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + if(!isVisible) return null; + + return ( + + setIsVisible(false)} + /> + {selectedCategory && ( +
+
setSelectedCategoryCode(null)} + /> + + + {LocalizeText( + `quests.${selectedCategory.code}.name` + )} + + + {LocalizeText( + 'achievements.details.categoryprogress', + ['progress', 'limit'], + [ + selectedCategory.getProgress().toString(), + selectedCategory + .getMaxProgress() + .toString(), + ] + )} + + + +
+ )} + + {!selectedCategory && ( + <> + +
+ + {LocalizeText( + 'achievements.categories.score', + ['score'], + [achievementScore.toString()] + )} + + +
+ + )} + {selectedCategory && ( + + )} +
+ + ); +}; diff --git a/src/components/achievements/achievement-list/AchievementListItemView.tsx b/src/components/achievements/achievement-list/AchievementListItemView.tsx new file mode 100644 index 0000000..88386f3 --- /dev/null +++ b/src/components/achievements/achievement-list/AchievementListItemView.tsx @@ -0,0 +1,24 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { LayoutGridItem } from '../../../common'; +import { useAchievements } from '../../../hooks'; +import { AchievementBadgeView } from '../AchievementBadgeView'; + +interface AchievementListItemViewProps +{ + achievement: AchievementData; +} + +export const AchievementListItemView: FC = props => +{ + const { achievement = null } = props; + const { selectedAchievement = null, setSelectedAchievementId = null } = useAchievements(); + + if(!achievement) return null; + + return ( + 0) } onClick={ event => setSelectedAchievementId(achievement.achievementId) }> + + + ); +}; diff --git a/src/components/achievements/achievement-list/AchievementListView.tsx b/src/components/achievements/achievement-list/AchievementListView.tsx new file mode 100644 index 0000000..0d46472 --- /dev/null +++ b/src/components/achievements/achievement-list/AchievementListView.tsx @@ -0,0 +1,20 @@ +import { AchievementData } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { AutoGrid } from '../../../common'; +import { AchievementListItemView } from './AchievementListItemView'; + +interface AchievementListViewProps +{ + achievements: AchievementData[]; +} + +export const AchievementListView: FC = props => +{ + const { achievements = null } = props; + + return ( + + { achievements && (achievements.length > 0) && achievements.map((achievement, index) => ) } + + ); +}; diff --git a/src/components/achievements/achievement-list/index.ts b/src/components/achievements/achievement-list/index.ts new file mode 100644 index 0000000..87ccb43 --- /dev/null +++ b/src/components/achievements/achievement-list/index.ts @@ -0,0 +1,2 @@ +export * from './AchievementListItemView'; +export * from './AchievementListView'; diff --git a/src/components/achievements/category-list/AchievementsCategoryListItemView.tsx b/src/components/achievements/category-list/AchievementsCategoryListItemView.tsx new file mode 100644 index 0000000..f73c096 --- /dev/null +++ b/src/components/achievements/category-list/AchievementsCategoryListItemView.tsx @@ -0,0 +1,31 @@ +import { Dispatch, FC, SetStateAction } from 'react'; +import { AchievementUtilities, IAchievementCategory, LocalizeText } from '../../../api'; +import { LayoutBackgroundImage, LayoutGridItem, Text } from '../../../common'; + +interface AchievementCategoryListItemViewProps +{ + category: IAchievementCategory; + selectedCategoryCode: string; + setSelectedCategoryCode: Dispatch>; +} + +export const AchievementsCategoryListItemView: FC = props => +{ + const { category = null, selectedCategoryCode = null, setSelectedCategoryCode = null } = props; + + if(!category) return null; + + const progress = AchievementUtilities.getAchievementCategoryProgress(category); + const maxProgress = AchievementUtilities.getAchievementCategoryMaxProgress(category); + const getCategoryImage = AchievementUtilities.getAchievementCategoryImageUrl(category, progress); + const getTotalUnseen = AchievementUtilities.getAchievementCategoryTotalUnseen(category); + + return ( + setSelectedCategoryCode(category.code) }> + { LocalizeText(`quests.${ category.code }.name`) } + + { progress } / { maxProgress } + + + ); +}; diff --git a/src/components/achievements/category-list/AchievementsCategoryListView.tsx b/src/components/achievements/category-list/AchievementsCategoryListView.tsx new file mode 100644 index 0000000..c641f62 --- /dev/null +++ b/src/components/achievements/category-list/AchievementsCategoryListView.tsx @@ -0,0 +1,22 @@ +import { Dispatch, FC, SetStateAction } from 'react'; +import { IAchievementCategory } from '../../../api'; +import { AutoGrid } from '../../../common'; +import { AchievementsCategoryListItemView } from './AchievementsCategoryListItemView'; + +interface AchievementsCategoryListViewProps +{ + categories: IAchievementCategory[]; + selectedCategoryCode: string; + setSelectedCategoryCode: Dispatch>; +} + +export const AchievementsCategoryListView: FC = props => +{ + const { categories = null, selectedCategoryCode = null, setSelectedCategoryCode = null } = props; + + return ( + + { categories && (categories.length > 0) && categories.map((category, index) => ) } + + ); +}; diff --git a/src/components/achievements/category-list/index.ts b/src/components/achievements/category-list/index.ts new file mode 100644 index 0000000..5a367f8 --- /dev/null +++ b/src/components/achievements/category-list/index.ts @@ -0,0 +1,2 @@ +export * from './AchievementsCategoryListItemView'; +export * from './AchievementsCategoryListView'; diff --git a/src/components/achievements/index.ts b/src/components/achievements/index.ts new file mode 100644 index 0000000..89f6737 --- /dev/null +++ b/src/components/achievements/index.ts @@ -0,0 +1,6 @@ +export * from './AchievementBadgeView'; +export * from './AchievementCategoryView'; +export * from './AchievementDetailsView'; +export * from './AchievementsView'; +export * from './achievement-list'; +export * from './category-list'; diff --git a/src/components/avatar-editor/AvatarEditorFigurePreviewView.tsx b/src/components/avatar-editor/AvatarEditorFigurePreviewView.tsx new file mode 100644 index 0000000..25bb609 --- /dev/null +++ b/src/components/avatar-editor/AvatarEditorFigurePreviewView.tsx @@ -0,0 +1,40 @@ +import { AvatarDirectionAngle } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { LayoutAvatarImageView } from '../../common'; +import { useAvatarEditor } from '../../hooks'; +import { AvatarEditorIcon } from './AvatarEditorIcon'; + +const DEFAULT_DIRECTION: number = 4; + +export const AvatarEditorFigurePreviewView: FC<{}> = props => +{ + const [ direction, setDirection ] = useState(DEFAULT_DIRECTION); + const { getFigureString = null } = useAvatarEditor(); + + const rotateFigure = (newDirection: number) => + { + if(direction < AvatarDirectionAngle.MIN_DIRECTION) + { + newDirection = (AvatarDirectionAngle.MAX_DIRECTION + (direction + 1)); + } + + if(direction > AvatarDirectionAngle.MAX_DIRECTION) + { + newDirection = (direction - (AvatarDirectionAngle.MAX_DIRECTION + 1)); + } + + setDirection(newDirection); + }; + + return ( +
+ + +
+
+ rotateFigure(direction + 1) } /> + rotateFigure(direction - 1) } /> +
+
+ ); +}; diff --git a/src/components/avatar-editor/AvatarEditorIcon.tsx b/src/components/avatar-editor/AvatarEditorIcon.tsx new file mode 100644 index 0000000..f5623ed --- /dev/null +++ b/src/components/avatar-editor/AvatarEditorIcon.tsx @@ -0,0 +1,46 @@ +import { DetailedHTMLProps, HTMLAttributes, PropsWithChildren, forwardRef } from 'react'; +import { classNames } from '../../layout'; + +type AvatarIconType = 'male' | 'female' | 'clear' | 'sellable'; + +export const AvatarEditorIcon = forwardRef & DetailedHTMLProps, HTMLDivElement>>((props, ref) => +{ + const { icon = null, selected = false, className = null, ...rest } = props; + + /* + switch (icon) + { + case 'male': + + + break; + + case 'arrow-left': + + break; + + default: + //statements; + break; + + } +*/ + return ( +
+ ); +}); + +AvatarEditorIcon.displayName = 'AvatarEditorIcon'; diff --git a/src/components/avatar-editor/AvatarEditorModelView.tsx b/src/components/avatar-editor/AvatarEditorModelView.tsx new file mode 100644 index 0000000..53a49ab --- /dev/null +++ b/src/components/avatar-editor/AvatarEditorModelView.tsx @@ -0,0 +1,80 @@ +import { AvatarEditorFigureCategory, AvatarFigurePartType, FigureDataContainer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { IAvatarEditorCategory } from '../../api'; +import { useAvatarEditor } from '../../hooks'; +import { AvatarEditorIcon } from './AvatarEditorIcon'; +import { AvatarEditorFigureSetView } from './figure-set'; +import { AvatarEditorPaletteSetView } from './palette-set'; + +export const AvatarEditorModelView: FC<{ + name: string, + categories: IAvatarEditorCategory[] +}> = props => +{ + const { name = '', categories = [] } = props; + const [ didChange, setDidChange ] = useState(false); + const [ activeSetType, setActiveSetType ] = useState(''); + const { maxPaletteCount = 1, gender = null, setGender = null, selectedColorParts = null, getFirstSelectableColor = null, selectEditorColor = null } = useAvatarEditor(); + + const activeCategory = useMemo(() => + { + return categories.find(category => category.setType === activeSetType) ?? null; + }, [ categories, activeSetType ]); + + const selectSet = useCallback((setType: string) => + { + const selectedPalettes = selectedColorParts[setType]; + + if(!selectedPalettes || !selectedPalettes.length) selectEditorColor(setType, 0, getFirstSelectableColor(setType)); + + setActiveSetType(setType); + }, [ getFirstSelectableColor, selectEditorColor, selectedColorParts ]); + + useEffect(() => + { + if(!categories || !categories.length || !didChange) return; + + selectSet(categories[0]?.setType); + setDidChange(false); + }, [ categories, didChange, selectSet ]); + + useEffect(() => + { + setDidChange(true); + }, [ categories ]); + + if(!activeCategory) return null; + + return ( +
+
+ { (name === AvatarEditorFigureCategory.GENERIC) && + <> +
setGender(AvatarFigurePartType.MALE) }> + +
+
setGender(AvatarFigurePartType.FEMALE) }> + +
+ } + { (name !== AvatarEditorFigureCategory.GENERIC) && (categories.length > 0) && categories.map(category => + { + return ( +
selectSet(category.setType) }> + +
+ ); + }) } +
+
+ +
+
+ { (maxPaletteCount >= 1) && + } + { (maxPaletteCount === 2) && + } +
+
+ ); +}; diff --git a/src/components/avatar-editor/AvatarEditorView.tsx b/src/components/avatar-editor/AvatarEditorView.tsx new file mode 100644 index 0000000..5dea113 --- /dev/null +++ b/src/components/avatar-editor/AvatarEditorView.tsx @@ -0,0 +1,122 @@ +import { AddLinkEventTracker, AvatarEditorFigureCategory, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker, UserFigureComposer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { FaDice, FaRedo, FaTrash } from 'react-icons/fa'; +import { AvatarEditorAction, LocalizeText, SendMessageComposer } from '../../api'; +import { Button, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; +import { useAvatarEditor } from '../../hooks'; +import { AvatarEditorFigurePreviewView } from './AvatarEditorFigurePreviewView'; +import { AvatarEditorModelView } from './AvatarEditorModelView'; +import { AvatarEditorWardrobeView } from './AvatarEditorWardrobeView'; + +const DEFAULT_MALE_FIGURE: string = 'hr-100.hd-180-7.ch-215-66.lg-270-79.sh-305-62.ha-1002-70.wa-2007'; +const DEFAULT_FEMALE_FIGURE: string = 'hr-515-33.hd-600-1.ch-635-70.lg-716-66-62.sh-735-68'; + +export const AvatarEditorView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const { setIsVisible: setEditorVisibility, avatarModels, activeModelKey, setActiveModelKey, loadAvatarData, getFigureStringWithFace, gender, figureSetIds = [], randomizeCurrentFigure = null, getFigureString = null } = useAvatarEditor(); + + const processAction = (action: string) => + { + switch(action) + { + case AvatarEditorAction.ACTION_RESET: + loadAvatarData(GetSessionDataManager().figure, GetSessionDataManager().gender); + return; + case AvatarEditorAction.ACTION_CLEAR: + loadAvatarData(getFigureStringWithFace(0, false), gender); + return; + case AvatarEditorAction.ACTION_RANDOMIZE: + randomizeCurrentFigure(); + return; + case AvatarEditorAction.ACTION_SAVE: + SendMessageComposer(new UserFigureComposer(gender, getFigureString)); + setIsVisible(false); + return; + } + }; + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; + } + }, + eventUrlPrefix: 'avatar-editor/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + useEffect(() => + { + setEditorVisibility(isVisible); + }, [ isVisible, setEditorVisibility ]); + + if(!isVisible) return null; + + return ( + + setIsVisible(false) } /> + + { Object.keys(avatarModels).map(modelKey => + { + const isActive = (activeModelKey === modelKey); + + return ( + setActiveModelKey(modelKey) }> + { LocalizeText(`avatareditor.category.${ modelKey }`) } + + ); + }) } + + + +
+ { ((activeModelKey.length > 0) && (activeModelKey !== AvatarEditorFigureCategory.WARDROBE)) && + } + { (activeModelKey === AvatarEditorFigureCategory.WARDROBE) && + } +
+
+ +
+
+ + + +
+ +
+
+
+
+
+ ); +}; diff --git a/src/components/avatar-editor/AvatarEditorWardrobeView.tsx b/src/components/avatar-editor/AvatarEditorWardrobeView.tsx new file mode 100644 index 0000000..6981bd5 --- /dev/null +++ b/src/components/avatar-editor/AvatarEditorWardrobeView.tsx @@ -0,0 +1,61 @@ +import { GetAvatarRenderManager, IAvatarFigureContainer, SaveWardrobeOutfitMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback } from 'react'; +import { GetClubMemberLevel, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../api'; +import { Button, LayoutAvatarImageView, LayoutCurrencyIcon } from '../../common'; +import { useAvatarEditor } from '../../hooks'; +import { InfiniteGrid } from '../../layout'; + +export const AvatarEditorWardrobeView: FC<{}> = props => +{ + const { savedFigures = [], setSavedFigures = null, loadAvatarData = null, getFigureString = null, gender = null } = useAvatarEditor(); + + const hcDisabled = GetConfigurationValue('hc.disabled', false); + + const wearFigureAtIndex = useCallback((index: number) => + { + if((index >= savedFigures.length) || (index < 0)) return; + + const [ figure, gender ] = savedFigures[index]; + + loadAvatarData(figure.getFigureString(), gender); + }, [ savedFigures, loadAvatarData ]); + + const saveFigureAtWardrobeIndex = useCallback((index: number) => + { + if((index >= savedFigures.length) || (index < 0)) return; + + const newFigures = [ ...savedFigures ]; + + const figure = getFigureString; + + newFigures[index] = [ GetAvatarRenderManager().createFigureContainer(figure), gender ]; + + setSavedFigures(newFigures); + SendMessageComposer(new SaveWardrobeOutfitMessageComposer((index + 1), figure, gender)); + }, [ getFigureString, gender, savedFigures, setSavedFigures ]); + + return ( + + { + const [ figureContainer, gender ] = item; + + let clubLevel = 0; + + if(figureContainer) clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender); + + return ( + + { figureContainer && + } +
+ { !hcDisabled && (clubLevel > 0) && } +
+ + { figureContainer && + } +
+ + ); + } } items={ savedFigures } overscan={ 5 } /> + ); +}; diff --git a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx new file mode 100644 index 0000000..4126fa3 --- /dev/null +++ b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx @@ -0,0 +1,56 @@ +import { AvatarFigurePartType } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { AvatarEditorThumbnailsHelper, GetConfigurationValue, IAvatarEditorCategoryPartItem } from '../../../api'; +import { LayoutCurrencyIcon, LayoutGridItemProps } from '../../../common'; +import { useAvatarEditor } from '../../../hooks'; +import { InfiniteGrid } from '../../../layout'; +import { AvatarEditorIcon } from '../AvatarEditorIcon'; + +export const AvatarEditorFigureSetItemView: FC<{ + setType: string; + partItem: IAvatarEditorCategoryPartItem; + isSelected: boolean; + width?: string; +} & LayoutGridItemProps> = props => +{ + const { setType = null, partItem = null, isSelected = false, width = '100%', ...rest } = props; + const [ assetUrl, setAssetUrl ] = useState(''); + const { selectedColorParts = null, getFigureStringWithFace = null } = useAvatarEditor(); + + const isHC = !GetConfigurationValue('hc.disabled', false) && ((partItem.partSet?.clubLevel ?? 0) > 0); + + useEffect(() => + { + if(!setType || !setType.length || !partItem) return; + + const loadImage = async () => + { + const isHC = !GetConfigurationValue('hc.disabled', false) && ((partItem.partSet?.clubLevel ?? 0) > 0); + + let url: string = null; + + if(setType === AvatarFigurePartType.HEAD) + { + url = await AvatarEditorThumbnailsHelper.buildForFace(getFigureStringWithFace(partItem.id), isHC); + } + else + { + url = await AvatarEditorThumbnailsHelper.build(setType, partItem, partItem.usesColor, selectedColorParts[setType] ?? null, isHC); + } + + if(url && url.length) setAssetUrl(url); + }; + + loadImage(); + }, [ setType, partItem, selectedColorParts, getFigureStringWithFace ]); + + if(!partItem) return null; + + return ( + + { !partItem.isClear && isHC && } + { partItem.isClear && } + { !partItem.isClear && partItem.partSet.isSellable && } + + ); +}; diff --git a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetView.tsx b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetView.tsx new file mode 100644 index 0000000..9a5b543 --- /dev/null +++ b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetView.tsx @@ -0,0 +1,41 @@ +import { FC } from 'react'; +import { IAvatarEditorCategory, IAvatarEditorCategoryPartItem } from '../../../api'; +import { useAvatarEditor } from '../../../hooks'; +import { InfiniteGrid } from '../../../layout'; +import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView'; + +export const AvatarEditorFigureSetView: FC<{ + category: IAvatarEditorCategory; + columnCount: number; +}> = props => +{ + const { category = null, columnCount = 3 } = props; + const { selectedParts = null, selectEditorPart } = useAvatarEditor(); + + const isPartItemSelected = (partItem: IAvatarEditorCategoryPartItem) => + { + if(!category || !category.setType || !selectedParts) return false; + + if(!selectedParts[category.setType]) + { + if(partItem.isClear) return true; + + return false; + } + + const partId = selectedParts[category.setType]; + + return (partId === partItem.id); + }; + + return ( + columnCount={ columnCount } itemRender={ (item: IAvatarEditorCategoryPartItem) => + { + if(!item) return null; + + return ( + selectEditorPart(category.setType, item.partSet?.id ?? -1) } /> + ); + } } items={ category.partItems } overscan={ columnCount } /> + ); +}; diff --git a/src/components/avatar-editor/figure-set/index.ts b/src/components/avatar-editor/figure-set/index.ts new file mode 100644 index 0000000..0c5880b --- /dev/null +++ b/src/components/avatar-editor/figure-set/index.ts @@ -0,0 +1,2 @@ +export * from './AvatarEditorFigureSetItemView'; +export * from './AvatarEditorFigureSetView'; diff --git a/src/components/avatar-editor/index.ts b/src/components/avatar-editor/index.ts new file mode 100644 index 0000000..5ae66e5 --- /dev/null +++ b/src/components/avatar-editor/index.ts @@ -0,0 +1,7 @@ +export * from './AvatarEditorFigurePreviewView'; +export * from './AvatarEditorIcon'; +export * from './AvatarEditorModelView'; +export * from './AvatarEditorView'; +export * from './AvatarEditorWardrobeView'; +export * from './figure-set'; +export * from './palette-set'; diff --git a/src/components/avatar-editor/palette-set/AvatarEditorPaletteSetItemView.tsx b/src/components/avatar-editor/palette-set/AvatarEditorPaletteSetItemView.tsx new file mode 100644 index 0000000..8a520bc --- /dev/null +++ b/src/components/avatar-editor/palette-set/AvatarEditorPaletteSetItemView.tsx @@ -0,0 +1,25 @@ +import { ColorConverter, IPartColor } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { GetConfigurationValue } from '../../../api'; +import { LayoutCurrencyIcon, LayoutGridItemProps } from '../../../common'; +import { InfiniteGrid } from '../../../layout'; + +export const AvatarEditorPaletteSetItem: FC<{ + setType: string; + partColor: IPartColor; + isSelected: boolean; + width?: string; +} & LayoutGridItemProps> = props => +{ + const { setType = null, partColor = null, isSelected = false, width = '100%', ...rest } = props; + + if(!partColor) return null; + + const isHC = !GetConfigurationValue('hc.disabled', false) && (partColor.clubLevel > 0); + + return ( + + { isHC && } + + ); +}; diff --git a/src/components/avatar-editor/palette-set/AvatarEditorPaletteSetView.tsx b/src/components/avatar-editor/palette-set/AvatarEditorPaletteSetView.tsx new file mode 100644 index 0000000..d40c8d1 --- /dev/null +++ b/src/components/avatar-editor/palette-set/AvatarEditorPaletteSetView.tsx @@ -0,0 +1,36 @@ +import { IPartColor } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { IAvatarEditorCategory } from '../../../api'; +import { useAvatarEditor } from '../../../hooks'; +import { InfiniteGrid } from '../../../layout'; +import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView'; + +export const AvatarEditorPaletteSetView: FC<{ + category: IAvatarEditorCategory; + paletteIndex: number; + columnCount: number; +}> = props => +{ + const { category = null, paletteIndex = -1, columnCount = 3 } = props; + const { selectedColorParts = null, selectEditorColor = null } = useAvatarEditor(); + + const isPartColorSelected = (partColor: IPartColor) => + { + if(!category || !category.setType || !selectedColorParts || !selectedColorParts[category.setType] || !selectedColorParts[category.setType][paletteIndex]) return false; + + const selectedColorPart = selectedColorParts[category.setType][paletteIndex]; + + return (selectedColorPart.id === partColor.id); + }; + + return ( + columnCount={ columnCount } itemRender={ (item: IPartColor) => + { + if(!item) return null; + + return ( + selectEditorColor(category.setType, paletteIndex, item.id) } /> + ); + } } items={ category.colorItems[paletteIndex] } overscan={ columnCount } /> + ); +}; diff --git a/src/components/avatar-editor/palette-set/index.ts b/src/components/avatar-editor/palette-set/index.ts new file mode 100644 index 0000000..977e5b9 --- /dev/null +++ b/src/components/avatar-editor/palette-set/index.ts @@ -0,0 +1,2 @@ +export * from './AvatarEditorPaletteSetItemView'; +export * from './AvatarEditorPaletteSetView'; diff --git a/src/components/backgrounds/BackgroundsView.tsx b/src/components/backgrounds/BackgroundsView.tsx new file mode 100644 index 0000000..4813b47 --- /dev/null +++ b/src/components/backgrounds/BackgroundsView.tsx @@ -0,0 +1,111 @@ +import { GetSessionDataManager, HabboClubLevelEnum} from '@nitrots/nitro-renderer'; +import { Dispatch, FC, SetStateAction, useCallback, useMemo, useState } from 'react'; +import { Base, Grid, Flex, NitroCardView, NitroCardHeaderView, NitroCardTabsView, NitroCardTabsItemView, NitroCardContentView, Text, LayoutCurrencyIcon } from '../../common'; +import { useRoom } from '../../hooks'; +import { GetClubMemberLevel, GetConfigurationValue } from '../../api'; + +interface ItemData { + id: number; + isHcOnly: boolean; + minRank: number; + isAmbassadorOnly: boolean; + selectable: boolean; +} + +interface BackgroundsViewProps { + setIsVisible: Dispatch>; + selectedBackground: number; + setSelectedBackground: Dispatch>; + selectedStand: number; + setSelectedStand: Dispatch>; + selectedOverlay: number; + setSelectedOverlay: Dispatch>; +} + +const TABS = ['backgrounds', 'stands', 'overlays'] as const; +type TabType = typeof TABS[number]; + +export const BackgroundsView: FC = ({ + setIsVisible, + selectedBackground, + setSelectedBackground, + selectedStand, + setSelectedStand, + selectedOverlay, + setSelectedOverlay +}) => { + const [activeTab, setActiveTab] = useState('backgrounds'); + const { roomSession } = useRoom(); + + const userData = useMemo(() => ({ + isHcMember: GetClubMemberLevel() >= HabboClubLevelEnum.CLUB, + securityLevel: GetSessionDataManager().canChangeName, + isAmbassador: GetSessionDataManager().isAmbassador + }), []); + + const processData = useCallback((configData: any[], dataType: string): ItemData[] => { + if (!configData?.length) return []; + + return configData + .filter(item => { + const meetsRank = userData.securityLevel >= item.minRank; + const ambassadorEligible = !item.isAmbassadorOnly || userData.isAmbassador; + return item.isHcOnly || (meetsRank && ambassadorEligible); + }) + .map(item => ({ id: item[`${dataType}Id`], ...item, selectable: !item.isHcOnly || userData.isHcMember })); + }, [userData]); + + const allData = useMemo(() => ({ + backgrounds: processData(GetConfigurationValue('backgrounds.data'), 'background'), + stands: processData(GetConfigurationValue('stands.data'), 'stand'), + overlays: processData(GetConfigurationValue('overlays.data'), 'overlay') + }), [processData]); + + const handleSelection = useCallback((id: number) => { + if (!roomSession) return; + + const setters = { backgrounds: setSelectedBackground, stands: setSelectedStand, overlays: setSelectedOverlay }; + + const currentValues = { backgrounds: selectedBackground, stands: selectedStand, overlays: selectedOverlay }; + + setters[activeTab](id); + const newValues = { ...currentValues, [activeTab]: id }; + roomSession.sendBackgroundMessage( newValues.backgrounds, newValues.stands, newValues.overlays ); + }, [activeTab, roomSession, selectedBackground, selectedStand, selectedOverlay, setSelectedBackground, setSelectedStand, setSelectedOverlay]); + + const renderItem = useCallback((item: ItemData, type: string) => ( + item.selectable && handleSelection(item.id)} + className={item.selectable ? '' : 'non-selectable'} + > + + {item.isHcOnly && } + + ), [handleSelection]); + + return ( + + setIsVisible(false)} /> + + {TABS.map(tab => ( + setActiveTab(tab)} + > + {tab.charAt(0).toUpperCase() + tab.slice(1)} + + ))} + + + Select an Option + + {allData[activeTab].map(item => renderItem(item, activeTab.slice(0, -1)))} + + + + ); +}; \ No newline at end of file diff --git a/src/components/camera/CameraWidgetView.tsx b/src/components/camera/CameraWidgetView.tsx new file mode 100644 index 0000000..d4221e8 --- /dev/null +++ b/src/components/camera/CameraWidgetView.tsx @@ -0,0 +1,97 @@ +import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { useCamera, useNitroEvent } from '../../hooks'; +import { CameraWidgetCaptureView } from './views/CameraWidgetCaptureView'; +import { CameraWidgetCheckoutView } from './views/CameraWidgetCheckoutView'; +import { CameraWidgetEditorView } from './views/editor/CameraWidgetEditorView'; + +const MODE_NONE: number = 0; +const MODE_CAPTURE: number = 1; +const MODE_EDITOR: number = 2; +const MODE_CHECKOUT: number = 3; + +export const CameraWidgetView: FC<{}> = props => +{ + const [ mode, setMode ] = useState(MODE_NONE); + const [ base64Url, setSavedPictureUrl ] = useState(null); + const { availableEffects = [], selectedPictureIndex = -1, cameraRoll = [], setCameraRoll = null, myLevel = 0, price = { credits: 0, duckets: 0, publishDucketPrice: 0 } } = useCamera(); + + + const processAction = (type: string) => + { + switch(type) + { + case 'close': + setMode(MODE_NONE); + return; + case 'edit': + setMode(MODE_EDITOR); + return; + case 'delete': + setCameraRoll(prevValue => + { + const clone = [ ...prevValue ]; + + clone.splice(selectedPictureIndex, 1); + + return clone; + }); + return; + case 'editor_cancel': + setMode(MODE_CAPTURE); + return; + } + }; + + const checkoutPictureUrl = (pictureUrl: string) => + { + setSavedPictureUrl(pictureUrl); + setMode(MODE_CHECKOUT); + }; + + useNitroEvent(RoomSessionEvent.ENDED, event => setMode(MODE_NONE)); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setMode(MODE_CAPTURE); + return; + case 'hide': + setMode(MODE_NONE); + return; + case 'toggle': + setMode(prevValue => + { + if(!prevValue) return MODE_CAPTURE; + else return MODE_NONE; + }); + return; + } + }, + eventUrlPrefix: 'camera/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + if(mode === MODE_NONE) return null; + + return ( + <> + { (mode === MODE_CAPTURE) && processAction('close') } onDelete={ () => processAction('delete') } onEdit={ () => processAction('edit') } /> } + { (mode === MODE_EDITOR) && processAction('editor_cancel') } onCheckout={ checkoutPictureUrl } onClose={ () => processAction('close') } /> } + { (mode === MODE_CHECKOUT) && processAction('editor_cancel') } onCloseClick={ () => processAction('close') }> } + + ); +}; diff --git a/src/components/camera/index.ts b/src/components/camera/index.ts new file mode 100644 index 0000000..43c10cd --- /dev/null +++ b/src/components/camera/index.ts @@ -0,0 +1,4 @@ +export * from './CameraWidgetView'; +export * from './views'; +export * from './views/editor'; +export * from './views/editor/effect-list'; diff --git a/src/components/camera/views/CameraWidgetCaptureView.tsx b/src/components/camera/views/CameraWidgetCaptureView.tsx new file mode 100644 index 0000000..89fe25e --- /dev/null +++ b/src/components/camera/views/CameraWidgetCaptureView.tsx @@ -0,0 +1,90 @@ +import { GetRoomEngine, NitroRectangle, TextureUtils } from '@nitrots/nitro-renderer'; +import { FC, useRef } from 'react'; +import { FaTimes } from 'react-icons/fa'; +import { CameraPicture, GetRoomSession, LocalizeText, PlaySound, SoundNames } from '../../../api'; +import { Button, Column, DraggableWindow } from '../../../common'; +import { useCamera, useNotification } from '../../../hooks'; + +export interface CameraWidgetCaptureViewProps +{ + onClose: () => void; + onEdit: () => void; + onDelete: () => void; +} + +const CAMERA_ROLL_LIMIT: number = 5; + +export const CameraWidgetCaptureView: FC = props => +{ + const { onClose = null, onEdit = null, onDelete = null } = props; + const { cameraRoll = null, setCameraRoll = null, selectedPictureIndex = -1, setSelectedPictureIndex = null } = useCamera(); + const { simpleAlert = null } = useNotification(); + const elementRef = useRef(); + + const selectedPicture = ((selectedPictureIndex > -1) ? cameraRoll[selectedPictureIndex] : null); + + const getCameraBounds = () => + { + if(!elementRef || !elementRef.current) return null; + + const frameBounds = elementRef.current.getBoundingClientRect(); + + return new NitroRectangle(Math.floor(frameBounds.x), Math.floor(frameBounds.y), Math.floor(frameBounds.width), Math.floor(frameBounds.height)); + }; + + const takePicture = async () => + { + if(selectedPictureIndex > -1) + { + setSelectedPictureIndex(-1); + return; + } + + const texture = GetRoomEngine().createTextureFromRoom(GetRoomSession().roomId, 1, getCameraBounds()); + + const clone = [ ...cameraRoll ]; + + if(clone.length >= CAMERA_ROLL_LIMIT) + { + simpleAlert(LocalizeText('camera.full.body')); + + clone.pop(); + } + + PlaySound(SoundNames.CAMERA_SHUTTER); + clone.push(new CameraPicture(texture, await TextureUtils.generateImageUrl(texture))); + + setCameraRoll(clone); + }; + + return ( + + + { selectedPicture && } +
+
+ +
+ { !selectedPicture &&
} + { selectedPicture && +
+
+ + +
+
} +
+
+
+
+ { (cameraRoll.length > 0) && +
+ { cameraRoll.map((picture, index) => + { + return setSelectedPictureIndex(index) } />; + }) } +
} + + + ); +}; diff --git a/src/components/camera/views/CameraWidgetCheckoutView.tsx b/src/components/camera/views/CameraWidgetCheckoutView.tsx new file mode 100644 index 0000000..2ab2f5b --- /dev/null +++ b/src/components/camera/views/CameraWidgetCheckoutView.tsx @@ -0,0 +1,159 @@ +import { CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, CameraStorageUrlMessageEvent, CreateLinkEvent, GetRoomEngine, PublishPhotoMessageComposer, PurchasePhotoMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useMemo, useState } from 'react'; +import { GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../../api'; +import { Button, Column, LayoutCurrencyIcon, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; +import { useMessageEvent } from '../../../hooks'; + +export interface CameraWidgetCheckoutViewProps +{ + base64Url: string; + onCloseClick: () => void; + onCancelClick: () => void; + price: { credits: number, duckets: number, publishDucketPrice: number }; +} + +export const CameraWidgetCheckoutView: FC = props => +{ + const { base64Url = null, onCloseClick = null, onCancelClick = null, price = null } = props; + const [ pictureUrl, setPictureUrl ] = useState(null); + const [ publishUrl, setPublishUrl ] = useState(null); + const [ picturesBought, setPicturesBought ] = useState(0); + const [ wasPicturePublished, setWasPicturePublished ] = useState(false); + const [ isWaiting, setIsWaiting ] = useState(false); + const [ publishCooldown, setPublishCooldown ] = useState(0); + + const publishDisabled = useMemo(() => GetConfigurationValue('camera.publish.disabled', false), []); + + useMessageEvent(CameraPurchaseOKMessageEvent, event => + { + setPicturesBought(value => (value + 1)); + setIsWaiting(false); + }); + + useMessageEvent(CameraPublishStatusMessageEvent, event => + { + const parser = event.getParser(); + + setPublishUrl(parser.extraDataId); + setPublishCooldown(parser.secondsToWait); + setWasPicturePublished(parser.ok); + setIsWaiting(false); + }); + + useMessageEvent(CameraStorageUrlMessageEvent, event => + { + const parser = event.getParser(); + + setPictureUrl(GetConfigurationValue('camera.url') + '/' + parser.url); + }); + + const processAction = (type: string, value: string | number = null) => + { + switch(type) + { + case 'close': + onCloseClick(); + return; + case 'buy': + if(isWaiting) return; + + setIsWaiting(true); + SendMessageComposer(new PurchasePhotoMessageComposer('')); + return; + case 'publish': + if(isWaiting) return; + + setIsWaiting(true); + SendMessageComposer(new PublishPhotoMessageComposer()); + return; + case 'cancel': + onCancelClick(); + return; + } + }; + + useEffect(() => + { + if(!base64Url) return; + + GetRoomEngine().saveBase64AsScreenshot(base64Url); + }, [ base64Url ]); + + if(!price) return null; + + return ( + + processAction('close') } /> + +
+ { (pictureUrl && pictureUrl.length) && + } + { (!pictureUrl || !pictureUrl.length) && +
+ { LocalizeText('camera.loading') } +
} +
+
+ + + { LocalizeText('camera.purchase.header') } + + { ((price.credits > 0) || (price.duckets > 0)) && +
+ { LocalizeText('catalog.purchase.confirmation.dialog.cost') } + { (price.credits > 0) && +
+ { price.credits } + +
} + { (price.duckets > 0) && +
+ { price.duckets } + +
} +
} + { (picturesBought > 0) && + + { LocalizeText('camera.purchase.count.info') } { picturesBought } + CreateLinkEvent('inventory/toggle') }>{ LocalizeText('camera.open.inventory') } + } +
+
+ +
+
+ { !publishDisabled && +
+
+ + { LocalizeText(wasPicturePublished ? 'camera.publish.successful' : 'camera.publish.explanation') } + + + { LocalizeText(wasPicturePublished ? 'camera.publish.success.short.info' : 'camera.publish.detailed.explanation') } + + { wasPicturePublished && { LocalizeText('camera.link.to.published') } } + { !wasPicturePublished && (price.publishDucketPrice > 0) && +
+ { LocalizeText('catalog.purchase.confirmation.dialog.cost') } +
+ { price.publishDucketPrice } + +
+
} + { (publishCooldown > 0) &&
{ LocalizeText('camera.publish.wait', [ 'minutes' ], [ Math.ceil(publishCooldown / 60).toString() ]) }
} +
+ { !wasPicturePublished && +
+ +
} +
} + { LocalizeText('camera.warning.disclaimer') } +
+ +
+
+
+ ); +}; diff --git a/src/components/camera/views/CameraWidgetShowPhotoView.tsx b/src/components/camera/views/CameraWidgetShowPhotoView.tsx new file mode 100644 index 0000000..212bee3 --- /dev/null +++ b/src/components/camera/views/CameraWidgetShowPhotoView.tsx @@ -0,0 +1,68 @@ +import { GetRoomEngine, RoomObjectCategory, RoomObjectVariable } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'; +import { GetUserProfile, IPhotoData, LocalizeText } from '../../../api'; +import { Flex, Grid, Text } from '../../../common'; + +export interface CameraWidgetShowPhotoViewProps { + currentIndex: number; + currentPhotos: IPhotoData[]; + onClick?: () => void; +} + +export const CameraWidgetShowPhotoView: FC = props => { + const { currentIndex = -1, currentPhotos = null, onClick = null } = props; + const [imageIndex, setImageIndex] = useState(0); + + const currentImage = currentPhotos && currentPhotos.length ? currentPhotos[imageIndex] : null; + + const next = () => { + setImageIndex(prevValue => { + let newIndex = prevValue + 1; + if (newIndex >= currentPhotos.length) newIndex = 0; + return newIndex; + }); + }; + + const previous = () => { + setImageIndex(prevValue => { + let newIndex = prevValue - 1; + if (newIndex < 0) newIndex = currentPhotos.length - 1; + return newIndex; + }); + }; + + useEffect(() => { + setImageIndex(currentIndex); + }, [currentIndex]); + + if (!currentImage) return null; + + const getUserData = (roomId: number, objectId: number, type: string): number | string => + { + const roomObject = GetRoomEngine().getRoomObject(roomId, objectId, RoomObjectCategory.WALL); + if (!roomObject) return; + return type == 'username' ? roomObject.model.getValue(RoomObjectVariable.FURNITURE_OWNER_NAME) : roomObject.model.getValue(RoomObjectVariable.FURNITURE_OWNER_ID); + } + + return ( + + + {!currentImage.w && {LocalizeText('camera.loading')}} + + {currentImage.m && currentImage.m.length && {currentImage.m}} +
+ {currentImage.n || ''} + GetUserProfile(Number(getUserData(currentImage.s, Number(currentImage.u), 'id')))}> { getUserData(currentImage.s, Number(currentImage.u), 'username') } + GetUserProfile(currentImage.oi)}>{currentImage.o} + {new Date(currentImage.t * 1000).toLocaleDateString()} +
+ {currentPhotos.length > 1 && ( + + + + + )} +
+ ); +}; \ No newline at end of file diff --git a/src/components/camera/views/editor/CameraWidgetEditorView.tsx b/src/components/camera/views/editor/CameraWidgetEditorView.tsx new file mode 100644 index 0000000..75ae074 --- /dev/null +++ b/src/components/camera/views/editor/CameraWidgetEditorView.tsx @@ -0,0 +1,222 @@ +import { GetRoomCameraWidgetManager, IRoomCameraWidgetEffect, IRoomCameraWidgetSelectedEffect, NitroLogger, RoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { FaSave, FaSearchMinus, FaSearchPlus, FaTrash } from 'react-icons/fa'; +import ReactSlider from 'react-slider'; +import { CameraEditorTabs, CameraPicture, CameraPictureThumbnail, LocalizeText } from '../../../../api'; +import { Button, Column, Flex, Grid, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../../../common'; +import { CameraWidgetEffectListView } from './effect-list'; + +export interface CameraWidgetEditorViewProps { + picture: CameraPicture; + availableEffects: IRoomCameraWidgetEffect[]; + myLevel: number; + onClose: () => void; + onCancel: () => void; + onCheckout: (pictureUrl: string) => void; +} + +const TABS: string[] = [ CameraEditorTabs.COLORMATRIX, CameraEditorTabs.COMPOSITE ]; + +export const CameraWidgetEditorView: FC = props => { + const { picture = null, availableEffects = null, myLevel = 1, onClose = null, onCancel = null, onCheckout = null } = props; + const [ currentTab, setCurrentTab ] = useState(TABS[0]); + const [ selectedEffectName, setSelectedEffectName ] = useState(null); + const [ selectedEffects, setSelectedEffects ] = useState([]); + const [ effectsThumbnails, setEffectsThumbnails ] = useState([]); + const [ isZoomed, setIsZoomed ] = useState(false); + const [ currentPictureUrl, setCurrentPictureUrl ] = useState(''); + const isBusy = useRef(false); + + const getColorMatrixEffects = useMemo(() => { + return availableEffects.filter(effect => effect.colorMatrix); + }, [ availableEffects ]); + + const getCompositeEffects = useMemo(() => { + return availableEffects.filter(effect => effect.texture); + }, [ availableEffects ]); + + const getEffectList = useCallback(() => { + return currentTab === CameraEditorTabs.COLORMATRIX ? getColorMatrixEffects : getCompositeEffects; + }, [ currentTab, getColorMatrixEffects, getCompositeEffects ]); + + const getSelectedEffectIndex = useCallback((name: string) => { + if (!name || !name.length || !selectedEffects || !selectedEffects.length) return -1; + return selectedEffects.findIndex(effect => effect.effect.name === name); + }, [ selectedEffects ]); + + const getCurrentEffectIndex = useMemo(() => { + return getSelectedEffectIndex(selectedEffectName); + }, [ selectedEffectName, getSelectedEffectIndex ]); + + const getCurrentEffect = useMemo(() => { + if (!selectedEffectName) return null; + return selectedEffects[getCurrentEffectIndex] || null; + }, [ selectedEffectName, getCurrentEffectIndex, selectedEffects ]); + + const setSelectedEffectAlpha = useCallback((alpha: number) => { + const index = getCurrentEffectIndex; + if (index === -1) return; + + setSelectedEffects(prevValue => { + const clone = [ ...prevValue ]; + const currentEffect = clone[index]; + clone[index] = new RoomCameraWidgetSelectedEffect(currentEffect.effect, alpha); + return clone; + }); + }, [ getCurrentEffectIndex ]); + + const processAction = useCallback((type: string, effectName: string = null) => { + switch (type) { + case 'close': + onClose(); + return; + case 'cancel': + onCancel(); + return; + case 'checkout': + onCheckout(currentPictureUrl); + return; + case 'change_tab': + setCurrentTab(String(effectName)); + return; + case 'select_effect': { + const existingIndex = getSelectedEffectIndex(effectName); + if (existingIndex >= 0) return; + + const effect = availableEffects.find(effect => effect.name === effectName); + if (!effect) return; + + setSelectedEffects(prevValue => [ ...prevValue, new RoomCameraWidgetSelectedEffect(effect, 1) ]); + setSelectedEffectName(effect.name); + return; + } + case 'remove_effect': { + const existingIndex = getSelectedEffectIndex(effectName); + if (existingIndex === -1) return; + + setSelectedEffects(prevValue => { + const clone = [ ...prevValue ]; + clone.splice(existingIndex, 1); + return clone; + }); + + if (selectedEffectName === effectName) setSelectedEffectName(null); + return; + } + case 'clear_effects': + setSelectedEffectName(null); + setSelectedEffects([]); + return; + case 'download': { + (async () => { + const image = new Image(); + image.src = currentPictureUrl; + const newWindow = window.open(''); + newWindow.document.write(image.outerHTML); + })(); + return; + } + case 'zoom': + setIsZoomed(prev => !prev); + return; + } + }, [ availableEffects, selectedEffectName, currentPictureUrl, getSelectedEffectIndex, onCancel, onCheckout, onClose ]); + + useEffect(() => { + const processThumbnails = async () => { + const renderedEffects = await Promise.all( + availableEffects.map(effect => + GetRoomCameraWidgetManager().applyEffects(picture.texture, [ new RoomCameraWidgetSelectedEffect(effect, 1) ], false) + ) + ); + setEffectsThumbnails(renderedEffects.map((image, index) => new CameraPictureThumbnail(availableEffects[index].name, image.src))); + }; + processThumbnails(); + }, [ picture, availableEffects ]); + + useEffect(() => { + GetRoomCameraWidgetManager() + .applyEffects(picture.texture, selectedEffects, false) // Remove isZoomed from here + .then(imageElement => { + setCurrentPictureUrl(imageElement.src); + }) + .catch(error => { + NitroLogger.error('Failed to apply effects to picture', error); + }); + }, [ picture, selectedEffects ]); // Remove isZoomed from dependency array + + return ( + + processAction('close') } /> + + { TABS.map(tab => ( + processAction('change_tab', tab) }> + + + )) } + + + + + + + + + + { selectedEffectName && ( + + { LocalizeText('camera.effect.name.' + selectedEffectName) } + setSelectedEffectAlpha(event) } + renderThumb={ ({ key, ...props }, state) =>
{ state.valueNow }
} + /> +
+ ) } +
+
+
+ + + +
+
+ + +
+
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/components/camera/views/editor/effect-list/CameraWidgetEffectListItemView.tsx b/src/components/camera/views/editor/effect-list/CameraWidgetEffectListItemView.tsx new file mode 100644 index 0000000..0b891a7 --- /dev/null +++ b/src/components/camera/views/editor/effect-list/CameraWidgetEffectListItemView.tsx @@ -0,0 +1,40 @@ +import { IRoomCameraWidgetEffect } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { FaLock, FaTimes } from 'react-icons/fa'; +import { LocalizeText } from '../../../../../api'; +import { Button, LayoutGridItem, Text } from '../../../../../common'; + +export interface CameraWidgetEffectListItemViewProps +{ + effect: IRoomCameraWidgetEffect; + thumbnailUrl: string; + isActive: boolean; + isLocked: boolean; + selectEffect: () => void; + removeEffect: () => void; +} + +export const CameraWidgetEffectListItemView: FC = props => +{ + const { effect = null, thumbnailUrl = null, isActive = false, isLocked = false, selectEffect = null, removeEffect = null } = props; + + return ( + (!isActive && selectEffect()) }> + { isActive && + } + { !isLocked && (thumbnailUrl && thumbnailUrl.length > 0) && +
+ +
} + { isLocked && + +
+ +
+ { effect.minLevel } +
} +
+ ); +}; diff --git a/src/components/camera/views/editor/effect-list/CameraWidgetEffectListView.tsx b/src/components/camera/views/editor/effect-list/CameraWidgetEffectListView.tsx new file mode 100644 index 0000000..5f9b965 --- /dev/null +++ b/src/components/camera/views/editor/effect-list/CameraWidgetEffectListView.tsx @@ -0,0 +1,33 @@ +import { IRoomCameraWidgetEffect, IRoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { CameraPictureThumbnail } from '../../../../../api'; +import { Grid } from '../../../../../common'; +import { CameraWidgetEffectListItemView } from './CameraWidgetEffectListItemView'; + +export interface CameraWidgetEffectListViewProps +{ + myLevel: number; + selectedEffects: IRoomCameraWidgetSelectedEffect[]; + effects: IRoomCameraWidgetEffect[]; + thumbnails: CameraPictureThumbnail[]; + processAction: (type: string, name: string) => void; +} + +export const CameraWidgetEffectListView: FC = props => +{ + const { myLevel = 0, selectedEffects = [], effects = [], thumbnails = [], processAction = null } = props; + + return ( + + { effects && (effects.length > 0) && effects.map((effect, index) => + { + const thumbnailUrl = (thumbnails.find(thumbnail => (thumbnail.effectName === effect.name))); + const isActive = (selectedEffects.findIndex(selectedEffect => (selectedEffect.effect.name === effect.name)) > -1); + + // return myLevel) } removeEffect={ () => processAction('remove_effect', effect.name) } selectEffect={ () => processAction('select_effect', effect.name) } thumbnailUrl={ ((thumbnailUrl && thumbnailUrl.thumbnailUrl) || null) } />; + + return myLevel) } selectEffect={ () => processAction('select_effect', effect.name) } removeEffect={ () => processAction('remove_effect', effect.name) } /> + }) } + + ); +}; diff --git a/src/components/camera/views/editor/effect-list/index.ts b/src/components/camera/views/editor/effect-list/index.ts new file mode 100644 index 0000000..7a4ebdb --- /dev/null +++ b/src/components/camera/views/editor/effect-list/index.ts @@ -0,0 +1,2 @@ +export * from './CameraWidgetEffectListItemView'; +export * from './CameraWidgetEffectListView'; diff --git a/src/components/camera/views/editor/index.ts b/src/components/camera/views/editor/index.ts new file mode 100644 index 0000000..49c615e --- /dev/null +++ b/src/components/camera/views/editor/index.ts @@ -0,0 +1,2 @@ +export * from './CameraWidgetEditorView'; +export * from './effect-list'; diff --git a/src/components/camera/views/index.ts b/src/components/camera/views/index.ts new file mode 100644 index 0000000..cf44449 --- /dev/null +++ b/src/components/camera/views/index.ts @@ -0,0 +1,5 @@ +export * from './CameraWidgetCaptureView'; +export * from './CameraWidgetCheckoutView'; +export * from './CameraWidgetShowPhotoView'; +export * from './editor'; +export * from './editor/effect-list'; diff --git a/src/components/campaign/CalendarItemView.tsx b/src/components/campaign/CalendarItemView.tsx new file mode 100644 index 0000000..234387a --- /dev/null +++ b/src/components/campaign/CalendarItemView.tsx @@ -0,0 +1,53 @@ +import { GetRoomEngine, GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { CalendarItemState, GetConfigurationValue, ICalendarItem } from '../../api'; +import { Column, Flex, LayoutImage } from '../../common'; + +interface CalendarItemViewProps +{ + itemId: number; + state: number; + active?: boolean; + product?: ICalendarItem; + onClick: (itemId: number) => void; +} + +export const CalendarItemView: FC = props => +{ + const { itemId = -1, state = null, product = null, active = false, onClick = null } = props; + + const getFurnitureIcon = (name: string) => + { + let furniData = GetSessionDataManager().getFloorItemDataByName(name); + let url = null; + + if(furniData) url = GetRoomEngine().getFurnitureFloorIconUrl(furniData.id); + else + { + furniData = GetSessionDataManager().getWallItemDataByName(name); + + if(furniData) url = GetRoomEngine().getFurnitureWallIconUrl(furniData.id); + } + + return url; + }; + + return ( + onClick(itemId) }> + { (state === CalendarItemState.STATE_UNLOCKED) && + + + { product && + ('image.library.url') + product.customImage : getFurnitureIcon(product.productName) } /> } + + } + { (state !== CalendarItemState.STATE_UNLOCKED) && + + { (state === CalendarItemState.STATE_LOCKED_AVAILABLE) && +
} + { ((state === CalendarItemState.STATE_LOCKED_EXPIRED) || (state === CalendarItemState.STATE_LOCKED_FUTURE)) && +
} + } + + ); +}; diff --git a/src/components/campaign/CalendarView.tsx b/src/components/campaign/CalendarView.tsx new file mode 100644 index 0000000..057d088 --- /dev/null +++ b/src/components/campaign/CalendarView.tsx @@ -0,0 +1,144 @@ +import { GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { CalendarItemState, ICalendarItem, LocalizeText } from '../../api'; +import { Button, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common'; +import { CalendarItemView } from './CalendarItemView'; + +interface CalendarViewProps +{ + onClose(): void; + openPackage(id: number, asStaff: boolean): void; + receivedProducts: Map; + campaignName: string; + currentDay: number; + numDays: number; + openedDays: number[]; + missedDays: number[]; +} + +const TOTAL_SHOWN_ITEMS = 5; + +export const CalendarView: FC = props => +{ + const { onClose = null, campaignName = null, currentDay = null, numDays = null, missedDays = null, openedDays = null, openPackage = null, receivedProducts = null } = props; + const [ selectedDay, setSelectedDay ] = useState(currentDay); + const [ index, setIndex ] = useState(Math.max(0, (selectedDay - 1))); + + const getDayState = (day: number) => + { + if(openedDays.includes(day)) return CalendarItemState.STATE_UNLOCKED; + + if(day > currentDay) return CalendarItemState.STATE_LOCKED_FUTURE; + + if(missedDays.includes(day)) return CalendarItemState.STATE_LOCKED_EXPIRED; + + return CalendarItemState.STATE_LOCKED_AVAILABLE; + }; + + const dayMessage = (day: number) => + { + const state = getDayState(day); + + switch(state) + { + case CalendarItemState.STATE_UNLOCKED: + return LocalizeText('campaign.calendar.info.unlocked'); + case CalendarItemState.STATE_LOCKED_FUTURE: + return LocalizeText('campaign.calendar.info.future'); + case CalendarItemState.STATE_LOCKED_EXPIRED: + return LocalizeText('campaign.calendar.info.expired'); + default: + return LocalizeText('campaign.calendar.info.available.desktop'); + } + }; + + const onClickNext = () => + { + const nextDay = (selectedDay + 1); + + if(nextDay === numDays) return; + + setSelectedDay(nextDay); + + if((index + TOTAL_SHOWN_ITEMS) < (nextDay + 1)) setIndex(index + 1); + }; + + const onClickPrev = () => + { + const prevDay = (selectedDay - 1); + + if(prevDay < 0) return; + + setSelectedDay(prevDay); + + if(index > prevDay) setIndex(index - 1); + }; + + const onClickItem = (item: number) => + { + if(selectedDay === item) + { + const state = getDayState(item); + + if(state === CalendarItemState.STATE_LOCKED_AVAILABLE) openPackage(item, false); + + return; + } + + setSelectedDay(item); + }; + + const forceOpen = () => + { + const id = selectedDay; + const state = getDayState(id); + + if(state !== CalendarItemState.STATE_UNLOCKED) openPackage(id, true); + }; + + return ( + + + + + + +
+
+ { LocalizeText('campaign.calendar.heading.day', [ 'number' ], [ (selectedDay + 1).toString() ]) } + { dayMessage(selectedDay) } +
+
+ { GetSessionDataManager().isModerator && + } +
+
+
+ +
+
+
+
+
+ + + { [ ...Array(TOTAL_SHOWN_ITEMS) ].map((e, i) => + { + const day = (index + i); + + return ( + + + + ); + }) } + + +
+
+
+
+ + + ); +}; diff --git a/src/components/campaign/CampaignView.tsx b/src/components/campaign/CampaignView.tsx new file mode 100644 index 0000000..76230f4 --- /dev/null +++ b/src/components/campaign/CampaignView.tsx @@ -0,0 +1,101 @@ +import { AddLinkEventTracker, CampaignCalendarData, CampaignCalendarDataMessageEvent, CampaignCalendarDoorOpenedMessageEvent, ILinkEventTracker, OpenCampaignCalendarDoorAsStaffComposer, OpenCampaignCalendarDoorComposer, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { CalendarItem, SendMessageComposer } from '../../api'; +import { useMessageEvent } from '../../hooks'; +import { CalendarView } from './CalendarView'; + +export const CampaignView: FC<{}> = props => +{ + const [ calendarData, setCalendarData ] = useState(null); + const [ lastOpenAttempt, setLastOpenAttempt ] = useState(-1); + const [ receivedProducts, setReceivedProducts ] = useState>(new Map()); + const [ isCalendarOpen, setCalendarOpen ] = useState(false); + + const openPackage = (id: number, asStaff = false) => + { + if(!calendarData) return; + + setLastOpenAttempt(id); + + if(asStaff) + { + SendMessageComposer(new OpenCampaignCalendarDoorAsStaffComposer(calendarData.campaignName, id)); + } + + else + { + SendMessageComposer(new OpenCampaignCalendarDoorComposer(calendarData.campaignName, id)); + } + }; + + useMessageEvent(CampaignCalendarDataMessageEvent, event => + { + const parser = event.getParser(); + + if(!parser) return; + + setCalendarData(parser.calendarData); + }); + + useMessageEvent(CampaignCalendarDoorOpenedMessageEvent, event => + { + const parser = event.getParser(); + + if(!parser) return; + + const lastAttempt = lastOpenAttempt; + + if(parser.doorOpened) + { + setCalendarData(prev => + { + const copy = prev.clone(); + copy.openedDays.push(lastOpenAttempt); + + return copy; + }); + + setReceivedProducts(prev => + { + const copy = new Map(prev); + copy.set(lastAttempt, new CalendarItem(parser.productName, parser.customImage,parser.furnitureClassName)); + + return copy; + }); + } + + setLastOpenAttempt(-1); + }); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const value = url.split('/'); + + if(value.length < 2) return; + + switch(value[1]) + { + case 'calendar': + setCalendarOpen(true); + break; + } + }, + eventUrlPrefix: 'openView/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + return ( + <> + { (calendarData && isCalendarOpen) && + setCalendarOpen(false) } /> + } + + ); +}; diff --git a/src/components/catalog/CatalogView.tsx b/src/components/catalog/CatalogView.tsx new file mode 100644 index 0000000..d7ee89f --- /dev/null +++ b/src/components/catalog/CatalogView.tsx @@ -0,0 +1,111 @@ +import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, useEffect } from 'react'; +import { GetConfigurationValue, LocalizeText } from '../../api'; +import { Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; +import { useCatalog } from '../../hooks'; +import { CatalogIconView } from './views/catalog-icon/CatalogIconView'; +import { CatalogGiftView } from './views/gift/CatalogGiftView'; +import { CatalogNavigationView } from './views/navigation/CatalogNavigationView'; +import { GetCatalogLayout } from './views/page/layout/GetCatalogLayout'; +import { MarketplacePostOfferView } from './views/page/layout/marketplace/MarketplacePostOfferView'; + +export const CatalogView: FC<{}> = props => +{ + const { isVisible = false, setIsVisible = null, rootNode = null, currentPage = null, navigationHidden = false, setNavigationHidden = null, activeNodes = [], searchResult = null, setSearchResult = null, openPageByName = null, openPageByOfferId = null, activateNode = null, getNodeById } = useCatalog(); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; + case 'open': + if(parts.length > 2) + { + if(parts.length === 4) + { + switch(parts[2]) + { + case 'offerId': + openPageByOfferId(parseInt(parts[3])); + return; + } + } + else + { + openPageByName(parts[2]); + } + } + else + { + setIsVisible(true); + } + + return; + } + }, + eventUrlPrefix: 'catalog/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, [ setIsVisible, openPageByOfferId, openPageByName ]); + + return ( + <> + { isVisible && + + setIsVisible(false) } /> + + { rootNode && (rootNode.children.length > 0) && rootNode.children.map(child => + { + if(!child.isVisible) return null; + + return ( + + { + if(searchResult) setSearchResult(null); + + activateNode(child); + } } > +
+ { GetConfigurationValue('catalog.tab.icons') && } + { child.localization } +
+
+ ); + }) } +
+ + + { !navigationHidden && + + { activeNodes && (activeNodes.length > 0) && + } + } + + { GetCatalogLayout(currentPage, () => setNavigationHidden(true)) } + + + +
} + + + + ); +}; diff --git a/src/components/catalog/views/CatalogPurchaseConfirmView.tsx b/src/components/catalog/views/CatalogPurchaseConfirmView.tsx new file mode 100644 index 0000000..84ce086 --- /dev/null +++ b/src/components/catalog/views/CatalogPurchaseConfirmView.tsx @@ -0,0 +1,10 @@ +import { FC } from 'react'; + +export const CatalogPurchaseConfirmView: FC<{}> = props => +{ + const {} = props; + + return ( +
+ ); +}; diff --git a/src/components/catalog/views/catalog-header/CatalogHeaderView.tsx b/src/components/catalog/views/catalog-header/CatalogHeaderView.tsx new file mode 100644 index 0000000..09ec089 --- /dev/null +++ b/src/components/catalog/views/catalog-header/CatalogHeaderView.tsx @@ -0,0 +1,25 @@ +import { FC, useEffect, useState } from 'react'; +import { GetConfigurationValue } from '../../../../api'; + +export interface CatalogHeaderViewProps +{ + imageUrl?: string; +} + +export const CatalogHeaderView: FC = props => +{ + const { imageUrl = null } = props; + const [ displayImageUrl, setDisplayImageUrl ] = useState(''); + + useEffect(() => + { + setDisplayImageUrl(imageUrl ?? GetConfigurationValue('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder')); + }, [ imageUrl ]); + + return
+ + { + currentTarget.src = GetConfigurationValue('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder'); + } } /> +
; +}; diff --git a/src/components/catalog/views/catalog-icon/CatalogIconView.tsx b/src/components/catalog/views/catalog-icon/CatalogIconView.tsx new file mode 100644 index 0000000..0178662 --- /dev/null +++ b/src/components/catalog/views/catalog-icon/CatalogIconView.tsx @@ -0,0 +1,20 @@ +import { FC, useMemo } from 'react'; +import { GetConfigurationValue } from '../../../../api'; +import { LayoutImage } from '../../../../common'; + +export interface CatalogIconViewProps +{ + icon: number; +} + +export const CatalogIconView: FC = props => +{ + const { icon = 0 } = props; + + const getIconUrl = useMemo(() => + { + return ((GetConfigurationValue('catalog.asset.icon.url')).replace('%name%', icon.toString())); + }, [ icon ]); + + return ; +}; diff --git a/src/components/catalog/views/catalog-room-previewer/CatalogRoomPreviewerView.tsx b/src/components/catalog/views/catalog-room-previewer/CatalogRoomPreviewerView.tsx new file mode 100644 index 0000000..87738d5 --- /dev/null +++ b/src/components/catalog/views/catalog-room-previewer/CatalogRoomPreviewerView.tsx @@ -0,0 +1,47 @@ +import { GetEventDispatcher, NitroToolbarAnimateIconEvent, RoomPreviewer, TextureUtils, ToolbarIconEnum } from '@nitrots/nitro-renderer'; +import { FC, useRef } from 'react'; +import { LayoutRoomPreviewerView } from '../../../../common'; +import { CatalogPurchasedEvent } from '../../../../events'; +import { useUiEvent } from '../../../../hooks'; + +export const CatalogRoomPreviewerView: FC<{ + roomPreviewer: RoomPreviewer; + height?: number; +}> = props => +{ + const { roomPreviewer = null } = props; + const elementRef = useRef(null); + + useUiEvent(CatalogPurchasedEvent.PURCHASE_SUCCESS, event => + { + if(!elementRef) return; + + const renderTexture = roomPreviewer.getRoomObjectCurrentImage(); + + if(!renderTexture) return; + + (async () => + { + const image = await TextureUtils.generateImage(renderTexture); + + if(!image) return; + + const bounds = elementRef.current.getBoundingClientRect(); + + const x = (bounds.x + (bounds.width / 2)); + const y = (bounds.y + (bounds.height / 2)); + + const animateEvent = new NitroToolbarAnimateIconEvent(image, x, y); + + animateEvent.iconName = ToolbarIconEnum.INVENTORY; + + GetEventDispatcher().dispatchEvent(animateEvent); + })(); + }); + + return ( +
+ +
+ ); +}; diff --git a/src/components/catalog/views/gift/CatalogGiftView.tsx b/src/components/catalog/views/gift/CatalogGiftView.tsx new file mode 100644 index 0000000..c027fba --- /dev/null +++ b/src/components/catalog/views/gift/CatalogGiftView.tsx @@ -0,0 +1,290 @@ +import { GetSessionDataManager, GiftReceiverNotFoundEvent, PurchaseFromCatalogAsGiftComposer } from '@nitrots/nitro-renderer'; +import { ChangeEvent, FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; +import { ColorUtils, LocalizeText, MessengerFriend, ProductTypeEnum, SendMessageComposer } from '../../../../api'; +import { Button, Column, Flex, FormGroup, LayoutCurrencyIcon, LayoutFurniImageView, LayoutGiftTagView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { CatalogEvent, CatalogInitGiftEvent, CatalogPurchasedEvent } from '../../../../events'; +import { useCatalog, useFriends, useMessageEvent, useUiEvent } from '../../../../hooks'; +import { classNames } from '../../../../layout'; + +export const CatalogGiftView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ pageId, setPageId ] = useState(0); + const [ offerId, setOfferId ] = useState(0); + const [ extraData, setExtraData ] = useState(''); + const [ receiverName, setReceiverName ] = useState(''); + const [ showMyFace, setShowMyFace ] = useState(true); + const [ message, setMessage ] = useState(''); + const [ colors, setColors ] = useState<{ id: number, color: string }[]>([]); + const [ selectedBoxIndex, setSelectedBoxIndex ] = useState(0); + const [ selectedRibbonIndex, setSelectedRibbonIndex ] = useState(0); + const [ selectedColorId, setSelectedColorId ] = useState(0); + const [ maxBoxIndex, setMaxBoxIndex ] = useState(0); + const [ maxRibbonIndex, setMaxRibbonIndex ] = useState(0); + const [ receiverNotFound, setReceiverNotFound ] = useState(false); + const { catalogOptions = null } = useCatalog(); + const { friends } = useFriends(); + const { giftConfiguration = null } = catalogOptions; + const [ boxTypes, setBoxTypes ] = useState([]); + const [ suggestions, setSuggestions ] = useState([]); + const [ isAutocompleteVisible, setIsAutocompleteVisible ] = useState(true); + + const onClose = useCallback(() => + { + setIsVisible(false); + setPageId(0); + setOfferId(0); + setExtraData(''); + setReceiverName(''); + setShowMyFace(true); + setMessage(''); + setSelectedBoxIndex(0); + setSelectedRibbonIndex(0); + setIsAutocompleteVisible(false); + setSuggestions([]); + + if(colors.length) setSelectedColorId(colors[0].id); + }, [ colors ]); + + const isBoxDefault = useMemo(() => + { + return giftConfiguration ? (giftConfiguration.defaultStuffTypes.findIndex(s => (s === boxTypes[selectedBoxIndex])) > -1) : false; + }, [ boxTypes, giftConfiguration, selectedBoxIndex ]); + + const boxExtraData = useMemo(() => + { + if(!giftConfiguration) return ''; + + return ((boxTypes[selectedBoxIndex] * 1000) + giftConfiguration.ribbonTypes[selectedRibbonIndex]).toString(); + }, [ giftConfiguration, selectedBoxIndex, selectedRibbonIndex, boxTypes ]); + + const isColorable = useMemo(() => + { + if(!giftConfiguration) return false; + + if(isBoxDefault) return false; + + const boxType = boxTypes[selectedBoxIndex]; + + return (boxType === 8 || (boxType >= 3 && boxType <= 6)) ? false : true; + }, [ giftConfiguration, selectedBoxIndex, isBoxDefault, boxTypes ]); + + const colourId = useMemo(() => + { + return isBoxDefault ? boxTypes[selectedBoxIndex] : selectedColorId; + }, [ isBoxDefault, boxTypes, selectedBoxIndex, selectedColorId ]); + + const allFriends = friends.filter((friend: MessengerFriend) => friend.id !== -1); + + const onTextChanged = (e: ChangeEvent) => + { + const value = e.target.value; + + let suggestions = []; + + if(value.length > 0) + { + suggestions = allFriends.sort().filter((friend: MessengerFriend) => friend.name.includes(value)); + } + + setReceiverName(value); + setIsAutocompleteVisible(true); + setSuggestions(suggestions); + }; + + const selectedReceiverName = (friendName: string) => + { + setReceiverName(friendName); + setIsAutocompleteVisible(false); + }; + + const handleAction = useCallback((action: string) => + { + switch(action) + { + case 'prev_box': + setSelectedBoxIndex(value => (value === 0 ? maxBoxIndex : value - 1)); + return; + case 'next_box': + setSelectedBoxIndex(value => (value === maxBoxIndex ? 0 : value + 1)); + return; + case 'prev_ribbon': + setSelectedRibbonIndex(value => (value === 0 ? maxRibbonIndex : value - 1)); + return; + case 'next_ribbon': + setSelectedRibbonIndex(value => (value === maxRibbonIndex ? 0 : value + 1)); + return; + case 'buy': + if(!receiverName || (receiverName.length === 0)) + { + setReceiverNotFound(true); + return; + } + + SendMessageComposer(new PurchaseFromCatalogAsGiftComposer(pageId, offerId, extraData, receiverName, message, colourId, selectedBoxIndex, selectedRibbonIndex, showMyFace)); + return; + } + }, [ colourId, extraData, maxBoxIndex, maxRibbonIndex, message, offerId, pageId, receiverName, selectedBoxIndex, selectedRibbonIndex, showMyFace ]); + + useMessageEvent(GiftReceiverNotFoundEvent, event => setReceiverNotFound(true)); + + useUiEvent([ + CatalogPurchasedEvent.PURCHASE_SUCCESS, + CatalogEvent.INIT_GIFT ], event => + { + switch(event.type) + { + case CatalogPurchasedEvent.PURCHASE_SUCCESS: + onClose(); + return; + case CatalogEvent.INIT_GIFT: + const castedEvent = (event as CatalogInitGiftEvent); + + onClose(); + + setPageId(castedEvent.pageId); + setOfferId(castedEvent.offerId); + setExtraData(castedEvent.extraData); + setIsVisible(true); + return; + } + }); + + useEffect(() => + { + setReceiverNotFound(false); + }, [ receiverName ]); + + const createBoxTypes = useCallback(() => + { + if(!giftConfiguration) return; + + setBoxTypes(prev => + { + let newPrev = [ ...giftConfiguration.boxTypes ]; + + newPrev.push(giftConfiguration.defaultStuffTypes[Math.floor((Math.random() * (giftConfiguration.defaultStuffTypes.length - 1)))]); + + setMaxBoxIndex(newPrev.length - 1); + setMaxRibbonIndex(newPrev.length - 1); + + return newPrev; + }); + }, [ giftConfiguration ]); + + useEffect(() => + { + if(!giftConfiguration) return; + + const newColors: { id: number, color: string }[] = []; + + for(const colorId of giftConfiguration.stuffTypes) + { + const giftData = GetSessionDataManager().getFloorItemData(colorId); + + if(!giftData) continue; + + if(giftData.colors && giftData.colors.length > 0) newColors.push({ id: colorId, color: ColorUtils.makeColorNumberHex(giftData.colors[0]) }); + } + + createBoxTypes(); + + if(newColors.length) + { + setSelectedColorId(newColors[0].id); + setColors(newColors); + } + }, [ giftConfiguration, createBoxTypes ]); + + useEffect(() => + { + if(!isVisible) return; + + createBoxTypes(); + }, [ createBoxTypes, isVisible ]); + + if(!giftConfiguration || !giftConfiguration.isEnabled || !isVisible) return null; + + const boxName = 'catalog.gift_wrapping_new.box.' + (isBoxDefault ? 'default' : boxTypes[selectedBoxIndex]); + const ribbonName = `catalog.gift_wrapping_new.ribbon.${ selectedRibbonIndex }`; + const priceText = 'catalog.gift_wrapping_new.' + (isBoxDefault ? 'freeprice' : 'price'); + + return ( + + + + + { LocalizeText('catalog.gift_wrapping.receiver') } + onTextChanged(e) } /> + { (suggestions.length > 0 && isAutocompleteVisible) && + + { suggestions.map((friend: MessengerFriend) => ( +
selectedReceiverName(friend.name) }>{ friend.name }
+ )) } +
+ } + { receiverNotFound && +
{ LocalizeText('catalog.gift_wrapping.receiver_not_found.title') }
} +
+ setMessage(value) } /> +
+ setShowMyFace(value => !value) } /> + +
+
+ { selectedColorId && +
+ +
} +
+
+
+ + +
+
+ { LocalizeText(boxName) } +
+ { LocalizeText(priceText, [ 'price' ], [ giftConfiguration.price.toString() ]) } + +
+
+
+ +
+ + +
+ { LocalizeText(ribbonName) } +
+
+
+ + + { LocalizeText('catalog.gift_wrapping.pick_color') } + +
+ { colors.map(color =>
+
+
+ + +
+
+
+ ); +}; diff --git a/src/components/catalog/views/navigation/CatalogNavigationItemView.tsx b/src/components/catalog/views/navigation/CatalogNavigationItemView.tsx new file mode 100644 index 0000000..1bb4373 --- /dev/null +++ b/src/components/catalog/views/navigation/CatalogNavigationItemView.tsx @@ -0,0 +1,35 @@ +import { FC } from 'react'; +import { FaCaretDown, FaCaretUp } from 'react-icons/fa'; +import { ICatalogNode } from '../../../../api'; +import { LayoutGridItem, Text } from '../../../../common'; +import { useCatalog } from '../../../../hooks'; +import { CatalogIconView } from '../catalog-icon/CatalogIconView'; +import { CatalogNavigationSetView } from './CatalogNavigationSetView'; + +export interface CatalogNavigationItemViewProps +{ + node: ICatalogNode; + child?: boolean; +} + +export const CatalogNavigationItemView: FC = props => +{ + const { node = null, child = false } = props; + const { activateNode = null } = useCatalog(); + + return ( +
+ activateNode(node) }> + + { node.localization } + { node.isBranch && + <> + { node.isOpen && } + { !node.isOpen && } + } + + { node.isOpen && node.isBranch && + } +
+ ); +}; diff --git a/src/components/catalog/views/navigation/CatalogNavigationSetView.tsx b/src/components/catalog/views/navigation/CatalogNavigationSetView.tsx new file mode 100644 index 0000000..92923fd --- /dev/null +++ b/src/components/catalog/views/navigation/CatalogNavigationSetView.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react'; +import { ICatalogNode } from '../../../../api'; +import { CatalogNavigationItemView } from './CatalogNavigationItemView'; + +export interface CatalogNavigationSetViewProps +{ + node: ICatalogNode; + child?: boolean; +} + +export const CatalogNavigationSetView: FC = props => +{ + const { node = null, child = false } = props; + + return ( + <> + { node && (node.children.length > 0) && node.children.map((n, index) => + { + if(!n.isVisible) return null; + + return ; + }) } + + ); +}; diff --git a/src/components/catalog/views/navigation/CatalogNavigationView.tsx b/src/components/catalog/views/navigation/CatalogNavigationView.tsx new file mode 100644 index 0000000..da5c850 --- /dev/null +++ b/src/components/catalog/views/navigation/CatalogNavigationView.tsx @@ -0,0 +1,34 @@ +import { FC } from 'react'; +import { ICatalogNode } from '../../../../api'; +import { AutoGrid, Column } from '../../../../common'; +import { useCatalog } from '../../../../hooks'; +import { CatalogSearchView } from '../page/common/CatalogSearchView'; +import { CatalogNavigationItemView } from './CatalogNavigationItemView'; +import { CatalogNavigationSetView } from './CatalogNavigationSetView'; + +export interface CatalogNavigationViewProps +{ + node: ICatalogNode; +} + +export const CatalogNavigationView: FC = props => +{ + const { node = null } = props; + const { searchResult = null } = useCatalog(); + + return ( + <> + + + + { searchResult && (searchResult.filteredNodes.length > 0) && searchResult.filteredNodes.map((n, index) => + { + return ; + }) } + { !searchResult && + } + + + + ); +}; diff --git a/src/components/catalog/views/page/common/CatalogGridOfferView.tsx b/src/components/catalog/views/page/common/CatalogGridOfferView.tsx new file mode 100644 index 0000000..f99bbad --- /dev/null +++ b/src/components/catalog/views/page/common/CatalogGridOfferView.tsx @@ -0,0 +1,59 @@ +import { MouseEventType } from '@nitrots/nitro-renderer'; +import { FC, MouseEvent, useMemo, useState } from 'react'; +import { IPurchasableOffer, Offer, ProductTypeEnum } from '../../../../../api'; +import { LayoutAvatarImageView, LayoutGridItem, LayoutGridItemProps } from '../../../../../common'; +import { useCatalog, useInventoryFurni } from '../../../../../hooks'; + +interface CatalogGridOfferViewProps extends LayoutGridItemProps +{ + offer: IPurchasableOffer; + selectOffer: (offer: IPurchasableOffer) => void; +} + +export const CatalogGridOfferView: FC = props => +{ + const { offer = null, selectOffer = null, itemActive = false, ...rest } = props; + const [ isMouseDown, setMouseDown ] = useState(false); + const { requestOfferToMover = null } = useCatalog(); + const { isVisible = false } = useInventoryFurni(); + + const iconUrl = useMemo(() => + { + if(offer.pricingModel === Offer.PRICING_MODEL_BUNDLE) + { + return null; + } + + return offer.product.getIconUrl(offer); + }, [ offer ]); + + const onMouseEvent = (event: MouseEvent) => + { + switch(event.type) + { + case MouseEventType.MOUSE_DOWN: + selectOffer(offer); + setMouseDown(true); + return; + case MouseEventType.MOUSE_UP: + setMouseDown(false); + return; + case MouseEventType.ROLL_OUT: + if(!isMouseDown || !itemActive || !isVisible) return; + + requestOfferToMover(offer); + return; + } + }; + + const product = offer.product; + + if(!product) return null; + + return ( + + { (offer.product.productType === ProductTypeEnum.ROBOT) && + } + + ); +}; diff --git a/src/components/catalog/views/page/common/CatalogRedeemVoucherView.tsx b/src/components/catalog/views/page/common/CatalogRedeemVoucherView.tsx new file mode 100644 index 0000000..c3469ec --- /dev/null +++ b/src/components/catalog/views/page/common/CatalogRedeemVoucherView.tsx @@ -0,0 +1,67 @@ +import { RedeemVoucherMessageComposer, VoucherRedeemErrorMessageEvent, VoucherRedeemOkMessageEvent } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { FaTag } from 'react-icons/fa'; +import { LocalizeText, SendMessageComposer } from '../../../../../api'; +import { Button } from '../../../../../common'; +import { useMessageEvent, useNotification } from '../../../../../hooks'; +import { NitroInput } from '../../../../../layout'; + +export interface CatalogRedeemVoucherViewProps +{ + text: string; +} + +export const CatalogRedeemVoucherView: FC = props => +{ + const { text = null } = props; + const [ voucher, setVoucher ] = useState(''); + const [ isWaiting, setIsWaiting ] = useState(false); + const { simpleAlert = null } = useNotification(); + + const redeemVoucher = () => + { + if(!voucher || !voucher.length || isWaiting) return; + + SendMessageComposer(new RedeemVoucherMessageComposer(voucher)); + + setIsWaiting(true); + }; + + useMessageEvent(VoucherRedeemOkMessageEvent, event => + { + const parser = event.getParser(); + + let message = LocalizeText('catalog.alert.voucherredeem.ok.description'); + + if(parser.productName) message = LocalizeText('catalog.alert.voucherredeem.ok.description.furni', [ 'productName', 'productDescription' ], [ parser.productName, parser.productDescription ]); + + simpleAlert(message, null, null, null, LocalizeText('catalog.alert.voucherredeem.ok.title')); + + setIsWaiting(false); + setVoucher(''); + }); + + useMessageEvent(VoucherRedeemErrorMessageEvent, event => + { + const parser = event.getParser(); + + simpleAlert(LocalizeText(`catalog.alert.voucherredeem.error.description.${ parser.errorCode }`), null, null, null, LocalizeText('catalog.alert.voucherredeem.error.title')); + + setIsWaiting(false); + }); + + return ( +
+ + + + setVoucher(event.target.value) } /> + +
+ ); +}; diff --git a/src/components/catalog/views/page/common/CatalogSearchView.tsx b/src/components/catalog/views/page/common/CatalogSearchView.tsx new file mode 100644 index 0000000..dc3f34b --- /dev/null +++ b/src/components/catalog/views/page/common/CatalogSearchView.tsx @@ -0,0 +1,106 @@ +import { GetSessionDataManager, IFurnitureData } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { FaSearch, FaTimes } from 'react-icons/fa'; +import { CatalogPage, CatalogType, FilterCatalogNode, FurnitureOffer, GetOfferNodes, ICatalogNode, ICatalogPage, IPurchasableOffer, LocalizeText, PageLocalization, SearchResult } from '../../../../../api'; +import { Button, Flex } from '../../../../../common'; +import { useCatalog } from '../../../../../hooks'; +import { NitroInput } from '../../../../../layout'; + +export const CatalogSearchView: FC<{}> = props => +{ + const [ searchValue, setSearchValue ] = useState(''); + const { currentType = null, rootNode = null, offersToNodes = null, searchResult = null, setSearchResult = null, setCurrentPage = null } = useCatalog(); + + useEffect(() => + { + let search = searchValue?.toLocaleLowerCase().replace(' ', ''); + + if(!search || !search.length) + { + setSearchResult(null); + + return; + } + + const timeout = setTimeout(() => + { + const furnitureDatas = GetSessionDataManager().getAllFurnitureData(); + + if(!furnitureDatas || !furnitureDatas.length) return; + + const foundFurniture: IFurnitureData[] = []; + const foundFurniLines: string[] = []; + + for(const furniture of furnitureDatas) + { + if((currentType === CatalogType.BUILDER) && !furniture.availableForBuildersClub) continue; + + if((currentType === CatalogType.NORMAL) && furniture.excludeDynamic) continue; + + const searchValues = [ furniture.className, furniture.name, furniture.description ].join(' ').replace(/ /gi, '').toLowerCase(); + + if((currentType === CatalogType.BUILDER) && (furniture.purchaseOfferId === -1) && (furniture.rentOfferId === -1)) + { + if((furniture.furniLine !== '') && (foundFurniLines.indexOf(furniture.furniLine) < 0)) + { + if(searchValues.indexOf(search) >= 0) foundFurniLines.push(furniture.furniLine); + } + } + else + { + const foundNodes = [ + ...GetOfferNodes(offersToNodes, furniture.purchaseOfferId), + ...GetOfferNodes(offersToNodes, furniture.rentOfferId) + ]; + + if(foundNodes.length) + { + if(searchValues.indexOf(search) >= 0) foundFurniture.push(furniture); + + if(foundFurniture.length === 250) break; + } + } + } + + const offers: IPurchasableOffer[] = []; + + for(const furniture of foundFurniture) offers.push(new FurnitureOffer(furniture)); + + let nodes: ICatalogNode[] = []; + + FilterCatalogNode(search, foundFurniLines, rootNode, nodes); + + setSearchResult(new SearchResult(search, offers, nodes.filter(node => (node.isVisible)))); + setCurrentPage((new CatalogPage(-1, 'default_3x3', new PageLocalization([], []), offers, false, 1) as ICatalogPage)); + }, 300); + + return () => clearTimeout(timeout); + }, [ offersToNodes, currentType, rootNode, searchValue, setCurrentPage, setSearchResult ]); + + return ( +
+ + + + + + + + setSearchValue(event.target.value) } /> + + + + { (!searchValue || !searchValue.length) && + } + { searchValue && !!searchValue.length && + } +
+ ); +}; diff --git a/src/components/catalog/views/page/layout/CatalogLayout.types.ts b/src/components/catalog/views/page/layout/CatalogLayout.types.ts new file mode 100644 index 0000000..b05bccf --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayout.types.ts @@ -0,0 +1,7 @@ +import { ICatalogPage } from '../../../../../api'; + +export interface CatalogLayoutProps +{ + page: ICatalogPage; + hideNavigation: () => void; +} diff --git a/src/components/catalog/views/page/layout/CatalogLayoutBadgeDisplayView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutBadgeDisplayView.tsx new file mode 100644 index 0000000..224946e --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutBadgeDisplayView.tsx @@ -0,0 +1,54 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { Column, Grid, Text } from '../../../../../common'; +import { useCatalog } from '../../../../../hooks'; +import { CatalogBadgeSelectorWidgetView } from '../widgets/CatalogBadgeSelectorWidgetView'; +import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView'; +import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView'; +import { CatalogLimitedItemWidgetView } from '../widgets/CatalogLimitedItemWidgetView'; +import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView'; +import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget'; +import { CatalogViewProductWidgetView } from '../widgets/CatalogViewProductWidgetView'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayoutBadgeDisplayView: FC = props => +{ + const { page = null } = props; + const { currentOffer = null } = useCatalog(); + + return ( + <> + + + + + + { LocalizeText('catalog_selectbadge') } + + + + + { !currentOffer && + <> + { !!page.localization.getImage(1) && } + + } + { currentOffer && + <> +
+ +
+ + + { currentOffer.localizationName } +
+ +
+ +
+ } +
+
+ + ); +}; diff --git a/src/components/catalog/views/page/layout/CatalogLayoutColorGroupingView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutColorGroupingView.tsx new file mode 100644 index 0000000..41d2014 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutColorGroupingView.tsx @@ -0,0 +1,176 @@ +import { ColorConverter } from '@nitrots/nitro-renderer'; +import { FC, useMemo, useState } from 'react'; +import { FaFillDrip } from 'react-icons/fa'; +import { IPurchasableOffer } from '../../../../../api'; +import { AutoGrid, Button, Column, Grid, LayoutGridItem, Text } from '../../../../../common'; +import { useCatalog } from '../../../../../hooks'; +import { CatalogGridOfferView } from '../common/CatalogGridOfferView'; +import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView'; +import { CatalogLimitedItemWidgetView } from '../widgets/CatalogLimitedItemWidgetView'; +import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView'; +import { CatalogSpinnerWidgetView } from '../widgets/CatalogSpinnerWidgetView'; +import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget'; +import { CatalogViewProductWidgetView } from '../widgets/CatalogViewProductWidgetView'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export interface CatalogLayoutColorGroupViewProps extends CatalogLayoutProps +{ + +} + +export const CatalogLayoutColorGroupingView: FC = props => +{ + const { page = null } = props; + const [ colorableItems, setColorableItems ] = useState>(new Map()); + const { currentOffer = null, setCurrentOffer = null } = useCatalog(); + const [ colorsShowing, setColorsShowing ] = useState(false); + + const sortByColorIndex = (a: IPurchasableOffer, b: IPurchasableOffer) => + { + if(((!(a.product.furnitureData.colorIndex)) || (!(b.product.furnitureData.colorIndex)))) + { + return 1; + } + if(a.product.furnitureData.colorIndex > b.product.furnitureData.colorIndex) + { + return 1; + } + if(a == b) + { + return 0; + } + return -1; + }; + + const sortyByFurnitureClassName = (a: IPurchasableOffer, b: IPurchasableOffer) => + { + if(a.product.furnitureData.className > b.product.furnitureData.className) + { + return 1; + } + if(a == b) + { + return 0; + } + return -1; + }; + + const selectOffer = (offer: IPurchasableOffer) => + { + offer.activate(); + setCurrentOffer(offer); + }; + + const selectColor = (colorIndex: number, productName: string) => + { + const fullName = `${ productName }*${ colorIndex }`; + const index = page.offers.findIndex(offer => offer.product.furnitureData.fullName === fullName); + if(index > -1) + { + selectOffer(page.offers[index]); + } + }; + + const offers = useMemo(() => + { + const offers: IPurchasableOffer[] = []; + const addedColorableItems = new Map(); + const updatedColorableItems = new Map(); + + page.offers.sort(sortByColorIndex); + + page.offers.forEach(offer => + { + if(!offer.product) return; + + const furniData = offer.product.furnitureData; + + if(!furniData || !furniData.hasIndexedColor) + { + offers.push(offer); + } + else + { + const name = furniData.className; + const colorIndex = furniData.colorIndex; + + if(!updatedColorableItems.has(name)) + { + updatedColorableItems.set(name, []); + } + + let selectedColor = 0xFFFFFF; + + if(furniData.colors) + { + for(let color of furniData.colors) + { + if(color !== 0xFFFFFF) // skip the white colors + { + selectedColor = color; + } + } + + if(updatedColorableItems.get(name).indexOf(selectedColor) === -1) + { + updatedColorableItems.get(name)[colorIndex] = selectedColor; + } + + } + + if(!addedColorableItems.has(name)) + { + offers.push(offer); + addedColorableItems.set(name, true); + } + } + }); + offers.sort(sortyByFurnitureClassName); + setColorableItems(updatedColorableItems); + return offers; + }, [ page.offers ]); + + return ( + + + + { (!colorsShowing || !currentOffer || !colorableItems.has(currentOffer.product.furnitureData.className)) && + offers.map((offer, index) => ) + } + { (colorsShowing && currentOffer && colorableItems.has(currentOffer.product.furnitureData.className)) && + colorableItems.get(currentOffer.product.furnitureData.className).map((color, index) => selectColor(index, currentOffer.product.furnitureData.className) } />) + } + + + + { !currentOffer && + <> + { !!page.localization.getImage(1) && } + + } + { currentOffer && + <> +
+ + + { currentOffer.product.furnitureData.hasIndexedColor && + } +
+ + + { currentOffer.localizationName } +
+
+ +
+ +
+ +
+ } +
+
+ ); +}; diff --git a/src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx new file mode 100644 index 0000000..4b86622 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx @@ -0,0 +1,61 @@ +import { FC } from 'react'; +import { GetConfigurationValue, ProductTypeEnum } from '../../../../../api'; +import { Column, Flex, Grid, LayoutImage, Text } from '../../../../../common'; +import { useCatalog } from '../../../../../hooks'; +import { CatalogHeaderView } from '../../catalog-header/CatalogHeaderView'; +import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView'; +import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView'; +import { CatalogLimitedItemWidgetView } from '../widgets/CatalogLimitedItemWidgetView'; +import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView'; +import { CatalogSpinnerWidgetView } from '../widgets/CatalogSpinnerWidgetView'; +import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget'; +import { CatalogViewProductWidgetView } from '../widgets/CatalogViewProductWidgetView'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayoutDefaultView: FC = props => +{ + const { page = null } = props; + const { currentOffer = null, currentPage = null } = useCatalog(); + + return ( + <> + + + { GetConfigurationValue('catalog.headers') && + } + + + + { !currentOffer && + <> + { !!page.localization.getImage(1) && + } + + } + { currentOffer && + <> + + { (currentOffer.product.productType !== ProductTypeEnum.BADGE) && + <> + + + } + { (currentOffer.product.productType === ProductTypeEnum.BADGE) && } + + + + { currentOffer.localizationName } +
+
+ +
+ +
+ +
+ } +
+
+ + ); +}; diff --git a/src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx new file mode 100644 index 0000000..20805ba --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutGuildCustomFurniView.tsx @@ -0,0 +1,48 @@ +import { FC } from 'react'; +import { Column, Grid, Text } from '../../../../../common'; +import { useCatalog } from '../../../../../hooks'; +import { CatalogGuildBadgeWidgetView } from '../widgets/CatalogGuildBadgeWidgetView'; +import { CatalogGuildSelectorWidgetView } from '../widgets/CatalogGuildSelectorWidgetView'; +import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView'; +import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView'; +import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget'; +import { CatalogViewProductWidgetView } from '../widgets/CatalogViewProductWidgetView'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayouGuildCustomFurniView: FC = props => +{ + const { page = null } = props; + const { currentOffer = null } = useCatalog(); + + return ( + + + + + + { !currentOffer && + <> + { !!page.localization.getImage(1) && } + + } + { currentOffer && + <> +
+ + +
+ + { currentOffer.localizationName } +
+ +
+
+ +
+ +
+ } +
+
+ ); +}; diff --git a/src/components/catalog/views/page/layout/CatalogLayoutGuildForumView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutGuildForumView.tsx new file mode 100644 index 0000000..ed87f49 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutGuildForumView.tsx @@ -0,0 +1,49 @@ +import { CatalogGroupsComposer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { SendMessageComposer } from '../../../../../api'; +import { Column, Grid, Text } from '../../../../../common'; +import { useCatalog } from '../../../../../hooks'; +import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView'; +import { CatalogGuildSelectorWidgetView } from '../widgets/CatalogGuildSelectorWidgetView'; +import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView'; +import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayouGuildForumView: FC = props => +{ + const { page = null } = props; + const [ selectedGroupIndex, setSelectedGroupIndex ] = useState(0); + const { currentOffer = null, setCurrentOffer = null, catalogOptions = null } = useCatalog(); + const { groups = null } = catalogOptions; + + useEffect(() => + { + SendMessageComposer(new CatalogGroupsComposer()); + }, [ page ]); + + return ( + <> + + + +
+ + + { !!currentOffer && + <> + + { currentOffer.localizationName } +
+ +
+
+ +
+ +
+ } +
+ + + ); +}; diff --git a/src/components/catalog/views/page/layout/CatalogLayoutGuildFrontpageView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutGuildFrontpageView.tsx new file mode 100644 index 0000000..44f66b8 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutGuildFrontpageView.tsx @@ -0,0 +1,29 @@ +import { CreateLinkEvent } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { Button } from '../../../../../common/Button'; +import { Column } from '../../../../../common/Column'; +import { Grid } from '../../../../../common/Grid'; +import { LayoutImage } from '../../../../../common/layout/LayoutImage'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayouGuildFrontpageView: FC = props => +{ + const { page = null } = props; + + return ( + + +
+
+
+ + + + + + + ); +}; diff --git a/src/components/catalog/views/page/layout/CatalogLayoutInfoLoyaltyView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutInfoLoyaltyView.tsx new file mode 100644 index 0000000..a2a6a62 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutInfoLoyaltyView.tsx @@ -0,0 +1,15 @@ +import { FC } from 'react'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayoutInfoLoyaltyView: FC = props => +{ + const { page = null } = props; + + return ( +
+
+
+
+
+ ); +}; diff --git a/src/components/catalog/views/page/layout/CatalogLayoutPets2View.tsx b/src/components/catalog/views/page/layout/CatalogLayoutPets2View.tsx new file mode 100644 index 0000000..3498fe0 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutPets2View.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { CatalogLayoutProps } from './CatalogLayout.types'; +import { CatalogLayoutPets3View } from './CatalogLayoutPets3View'; + +export const CatalogLayoutPets2View: FC = props => +{ + return ; +}; diff --git a/src/components/catalog/views/page/layout/CatalogLayoutPets3View.tsx b/src/components/catalog/views/page/layout/CatalogLayoutPets3View.tsx new file mode 100644 index 0000000..8c2e085 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutPets3View.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react'; +import { Column } from '../../../../../common'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayoutPets3View: FC = props => +{ + const { page = null } = props; + + const imageUrl = page.localization.getImage(1); + + return ( + +
+ { imageUrl && } +
+
+ +
+ +
+
+
+ + ); +}; diff --git a/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx new file mode 100644 index 0000000..4a62f88 --- /dev/null +++ b/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx @@ -0,0 +1,116 @@ +import { GetRoomAdPurchaseInfoComposer, GetUserEventCatsMessageComposer, PurchaseRoomAdMessageComposer, RoomAdPurchaseInfoEvent, RoomEntryData } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, SendMessageComposer } from '../../../../../api'; +import { Button, Column, Text } from '../../../../../common'; +import { useCatalog, useMessageEvent, useNavigator, useRoomPromote } from '../../../../../hooks'; +import { NitroInput } from '../../../../../layout'; +import { CatalogLayoutProps } from './CatalogLayout.types'; + +export const CatalogLayoutRoomAdsView: FC = props => +{ + const { page = null } = props; + const [ eventName, setEventName ] = useState(''); + const [ eventDesc, setEventDesc ] = useState(''); + const [ roomId, setRoomId ] = useState(-1); + const [ availableRooms, setAvailableRooms ] = useState([]); + const [ extended, setExtended ] = useState(false); + const [ categoryId, setCategoryId ] = useState(1); + const { categories = null } = useNavigator(); + const { setIsVisible = null } = useCatalog(); + const { promoteInformation, isExtended, setIsExtended } = useRoomPromote(); + + useEffect(() => + { + if(isExtended) + { + setRoomId(promoteInformation.data.flatId); + setEventName(promoteInformation.data.eventName); + setEventDesc(promoteInformation.data.eventDescription); + setCategoryId(promoteInformation.data.categoryId); + setExtended(isExtended); // This is for sending to packet + setIsExtended(false); // This is from hook useRoomPromotte + } + + }, [ isExtended, eventName, eventDesc, categoryId, promoteInformation.data, setIsExtended ]); + + const resetData = () => + { + setRoomId(-1); + setEventName(''); + setEventDesc(''); + setCategoryId(1); + setIsExtended(false); + setIsVisible(false); + }; + + const purchaseAd = () => + { + const pageId = page.pageId; + const offerId = page.offers.length >= 1 ? page.offers[0].offerId : -1; + const flatId = roomId; + const name = eventName; + const desc = eventDesc; + const catId = categoryId; + + SendMessageComposer(new PurchaseRoomAdMessageComposer(pageId, offerId, flatId, name, extended, desc, catId)); + resetData(); + }; + + useMessageEvent(RoomAdPurchaseInfoEvent, event => + { + const parser = event.getParser(); + + if(!parser) return; + + setAvailableRooms(parser.rooms); + }); + + useEffect(() => + { + SendMessageComposer(new GetRoomAdPurchaseInfoComposer()); + // TODO: someone needs to fix this for morningstar + SendMessageComposer(new GetUserEventCatsMessageComposer()); + }, []); + + return (<> + { LocalizeText('roomad.catalog_header') } + +
{ LocalizeText('roomad.catalog_text', [ 'duration' ], [ '120' ]) }
+
+ + { LocalizeText('navigator.category') } + + +
+ { LocalizeText('roomad.catalog_name') } + setEventName(event.target.value) } /> + +
+
+ { LocalizeText('roomad.catalog_description') } + + { LocalizeText('friendlist.invite.note') } +
+ + +
+ + + ); +}; diff --git a/src/components/friends/views/friends-list/FriendsListSearchView.tsx b/src/components/friends/views/friends-list/FriendsListSearchView.tsx new file mode 100644 index 0000000..fd53481 --- /dev/null +++ b/src/components/friends/views/friends-list/FriendsListSearchView.tsx @@ -0,0 +1,103 @@ +import { HabboSearchComposer, HabboSearchResultData, HabboSearchResultEvent } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, OpenMessengerChat, SendMessageComposer } from '../../../../api'; +import { Column, NitroCardAccordionItemView, NitroCardAccordionSetView, NitroCardAccordionSetViewProps, Text, UserProfileIconView } from '../../../../common'; +import { useFriends, useMessageEvent } from '../../../../hooks'; + +interface FriendsSearchViewProps extends NitroCardAccordionSetViewProps +{ + +} + +export const FriendsSearchView: FC = props => +{ + const { ...rest } = props; + const [ searchValue, setSearchValue ] = useState(''); + const [ friendResults, setFriendResults ] = useState(null); + const [ otherResults, setOtherResults ] = useState(null); + const { canRequestFriend = null, requestFriend = null } = useFriends(); + + useMessageEvent(HabboSearchResultEvent, event => + { + const parser = event.getParser(); + + setFriendResults(parser.friends); + setOtherResults(parser.others); + }); + + useEffect(() => + { + if(!searchValue || !searchValue.length) return; + + const timeout = setTimeout(() => + { + if(!searchValue || !searchValue.length) return; + + SendMessageComposer(new HabboSearchComposer(searchValue)); + }, 500); + + return () => clearTimeout(timeout); + }, [ searchValue ]); + + return ( + + setSearchValue(event.target.value) } /> +
+ { friendResults && + <> + { (friendResults.length === 0) && + { LocalizeText('friendlist.search.nofriendsfound') } } + { (friendResults.length > 0) && + + { LocalizeText('friendlist.search.friendscaption', [ 'cnt' ], [ friendResults.length.toString() ]) } +
+ + { friendResults.map(result => + { + return ( + +
+ +
{ result.avatarName }
+
+
+ { result.isAvatarOnline && +
OpenMessengerChat(result.avatarId) } /> } +
+ + ); + }) } + + } + } + { otherResults && + <> + { (otherResults.length === 0) && + { LocalizeText('friendlist.search.noothersfound') } } + { (otherResults.length > 0) && + + { LocalizeText('friendlist.search.otherscaption', [ 'cnt' ], [ otherResults.length.toString() ]) } +
+ + { otherResults.map(result => + { + return ( + +
+ +
{ result.avatarName }
+
+
+ { canRequestFriend(result.avatarId) && +
requestFriend(result.avatarId, result.avatarName) } /> } +
+ + ); + }) } + + } + } +
+ + ); +}; diff --git a/src/components/friends/views/friends-list/FriendsListView.tsx b/src/components/friends/views/friends-list/FriendsListView.tsx new file mode 100644 index 0000000..ef30f23 --- /dev/null +++ b/src/components/friends/views/friends-list/FriendsListView.tsx @@ -0,0 +1,150 @@ +import { AddLinkEventTracker, ILinkEventTracker, RemoveFriendComposer, RemoveLinkEventTracker, SendRoomInviteComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { LocalizeText, MessengerFriend, SendMessageComposer } from '../../../../api'; +import { Button, Flex, NitroCardAccordionSetView, NitroCardAccordionView, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { useFriends } from '../../../../hooks'; +import { FriendsRemoveConfirmationView } from './FriendsListRemoveConfirmationView'; +import { FriendsRoomInviteView } from './FriendsListRoomInviteView'; +import { FriendsSearchView } from './FriendsListSearchView'; +import { FriendsListGroupView } from './friends-list-group/FriendsListGroupView'; +import { FriendsListRequestView } from './friends-list-request/FriendsListRequestView'; + +export const FriendsListView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ selectedFriendsIds, setSelectedFriendsIds ] = useState([]); + const [ showRoomInvite, setShowRoomInvite ] = useState(false); + const [ showRemoveFriendsConfirmation, setShowRemoveFriendsConfirmation ] = useState(false); + const { onlineFriends = [], offlineFriends = [], requests = [], requestFriend = null } = useFriends(); + + const removeFriendsText = useMemo(() => + { + if(!selectedFriendsIds || !selectedFriendsIds.length) return ''; + + const userNames: string[] = []; + + for(const userId of selectedFriendsIds) + { + let existingFriend: MessengerFriend = onlineFriends.find(f => f.id === userId); + + if(!existingFriend) existingFriend = offlineFriends.find(f => f.id === userId); + + if(!existingFriend) continue; + + userNames.push(existingFriend.name); + } + + return LocalizeText('friendlist.removefriendconfirm.userlist', [ 'user_names' ], [ userNames.join(', ') ]); + }, [ offlineFriends, onlineFriends, selectedFriendsIds ]); + + const selectFriend = useCallback((userId: number) => + { + if(userId < 0) return; + + setSelectedFriendsIds(prevValue => + { + const newValue = [ ...prevValue ]; + + const existingUserIdIndex: number = newValue.indexOf(userId); + + if(existingUserIdIndex > -1) + { + newValue.splice(existingUserIdIndex, 1); + } + else + { + newValue.push(userId); + } + + return newValue; + }); + }, [ setSelectedFriendsIds ]); + + const sendRoomInvite = (message: string) => + { + if(!selectedFriendsIds.length || !message || !message.length || (message.length > 255)) return; + + SendMessageComposer(new SendRoomInviteComposer(message, selectedFriendsIds)); + + setShowRoomInvite(false); + }; + + const removeSelectedFriends = () => + { + if(selectedFriendsIds.length === 0) return; + + setSelectedFriendsIds(prevValue => + { + SendMessageComposer(new RemoveFriendComposer(...prevValue)); + + return []; + }); + + setShowRemoveFriendsConfirmation(false); + }; + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; + case 'request': + if(parts.length < 4) return; + + requestFriend(parseInt(parts[2]), parts[3]); + } + }, + eventUrlPrefix: 'friends/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, [ requestFriend ]); + + if(!isVisible) return null; + + return ( + <> + + setIsVisible(false) } /> + + + + + + + + + + + + { selectedFriendsIds && selectedFriendsIds.length > 0 && + + + + } + + + { showRoomInvite && + setShowRoomInvite(false) } /> } + { showRemoveFriendsConfirmation && + setShowRemoveFriendsConfirmation(false) } /> } + + ); +}; diff --git a/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupItemView.tsx b/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupItemView.tsx new file mode 100644 index 0000000..5096460 --- /dev/null +++ b/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupItemView.tsx @@ -0,0 +1,85 @@ +import { FC, MouseEvent, useState } from 'react'; +import { LocalizeText, MessengerFriend, OpenMessengerChat } from '../../../../../api'; +import { NitroCardAccordionItemView, UserProfileIconView } from '../../../../../common'; +import { useFriends } from '../../../../../hooks'; + +export const FriendsListGroupItemView: FC<{ friend: MessengerFriend, selected: boolean, selectFriend: (userId: number) => void }> = props => +{ + const { friend = null, selected = false, selectFriend = null } = props; + const [ isRelationshipOpen, setIsRelationshipOpen ] = useState(false); + const { followFriend = null, updateRelationship = null } = useFriends(); + + const clickFollowFriend = (event: MouseEvent) => + { + event.stopPropagation(); + + followFriend(friend); + }; + + const openMessengerChat = (event: MouseEvent) => + { + event.stopPropagation(); + + OpenMessengerChat(friend.id); + }; + + const openRelationship = (event: MouseEvent) => + { + event.stopPropagation(); + + setIsRelationshipOpen(true); + }; + + const clickUpdateRelationship = (event: MouseEvent, type: number) => + { + event.stopPropagation(); + + updateRelationship(friend, type); + + setIsRelationshipOpen(false); + }; + + const getCurrentRelationshipName = () => + { + if(!friend) return 'none'; + + switch(friend.relationshipStatus) + { + case MessengerFriend.RELATIONSHIP_HEART: return 'heart'; + case MessengerFriend.RELATIONSHIP_SMILE: return 'smile'; + case MessengerFriend.RELATIONSHIP_BOBBA: return 'bobba'; + default: return 'none'; + } + }; + + if(!friend) return null; + + return ( + selectFriend(friend.id) }> +
+
event.stopPropagation() }> + +
+
{ friend.name }
+
+
+ { !isRelationshipOpen && + <> + { friend.followingAllowed && +
} + { friend.online && +
} + { (friend.id > 0) && +
} + } + { isRelationshipOpen && + <> +
clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_HEART) } /> +
clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_SMILE) } /> +
clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_BOBBA) } /> +
clickUpdateRelationship(event, MessengerFriend.RELATIONSHIP_NONE) } /> + } +
+ + ); +}; diff --git a/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupView.tsx b/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupView.tsx new file mode 100644 index 0000000..ffd4cde --- /dev/null +++ b/src/components/friends/views/friends-list/friends-list-group/FriendsListGroupView.tsx @@ -0,0 +1,23 @@ +import { FC } from 'react'; +import { MessengerFriend } from '../../../../../api'; +import { FriendsListGroupItemView } from './FriendsListGroupItemView'; + +interface FriendsListGroupViewProps +{ + list: MessengerFriend[]; + selectedFriendsIds: number[]; + selectFriend: (userId: number) => void; +} + +export const FriendsListGroupView: FC = props => +{ + const { list = null, selectedFriendsIds = null, selectFriend = null } = props; + + if(!list || !list.length) return null; + + return ( + <> + { list.map((item, index) => = 0) } selectFriend={ selectFriend } />) } + + ); +}; diff --git a/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestItemView.tsx b/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestItemView.tsx new file mode 100644 index 0000000..c06840e --- /dev/null +++ b/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestItemView.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react'; +import { MessengerRequest } from '../../../../../api'; +import { NitroCardAccordionItemView, UserProfileIconView } from '../../../../../common'; +import { useFriends } from '../../../../../hooks'; + +export const FriendsListRequestItemView: FC<{ request: MessengerRequest }> = props => +{ + const { request = null } = props; + const { requestResponse = null } = useFriends(); + + if(!request) return null; + + return ( + +
+ +
{ request.name }
+
+
+
requestResponse(request.id, true) } /> +
requestResponse(request.id, false) } /> +
+ + ); +}; diff --git a/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestView.tsx b/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestView.tsx new file mode 100644 index 0000000..686b32d --- /dev/null +++ b/src/components/friends/views/friends-list/friends-list-request/FriendsListRequestView.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { Button, Column, NitroCardAccordionSetView, NitroCardAccordionSetViewProps } from '../../../../../common'; +import { useFriends } from '../../../../../hooks'; +import { FriendsListRequestItemView } from './FriendsListRequestItemView'; + +export const FriendsListRequestView: FC = props => +{ + const { children = null, ...rest } = props; + const { requests = [], requestResponse = null } = useFriends(); + + if(!requests.length) return null; + + return ( + + + + { requests.map((request, index) => ) } + +
+ +
+
+ { children } +
+ ); +}; diff --git a/src/components/friends/views/messenger/FriendsMessengerView.tsx b/src/components/friends/views/messenger/FriendsMessengerView.tsx new file mode 100644 index 0000000..354ddd5 --- /dev/null +++ b/src/components/friends/views/messenger/FriendsMessengerView.tsx @@ -0,0 +1,178 @@ +import { AddLinkEventTracker, FollowFriendMessageComposer, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, KeyboardEvent, useEffect, useRef, useState } from 'react'; +import { FaTimes } from 'react-icons/fa'; +import { GetUserProfile, LocalizeText, ReportType, SendMessageComposer } from '../../../../api'; +import { Button, Column, Flex, Grid, LayoutAvatarImageView, LayoutBadgeImageView, LayoutGridItem, LayoutItemCountView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { useHelp, useMessenger } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { FriendsMessengerThreadView } from './messenger-thread/FriendsMessengerThreadView'; + +export const FriendsMessengerView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ lastThreadId, setLastThreadId ] = useState(-1); + const [ messageText, setMessageText ] = useState(''); + const { visibleThreads = [], activeThread = null, getMessageThread = null, sendMessage = null, setActiveThreadId = null, closeThread = null } = useMessenger(); + const { report = null } = useHelp(); + const messagesBox = useRef(); + + const followFriend = () => (activeThread && activeThread.participant && SendMessageComposer(new FollowFriendMessageComposer(activeThread.participant.id))); + const openProfile = () => (activeThread && activeThread.participant && GetUserProfile(activeThread.participant.id)); + + const send = () => + { + if(!activeThread || !messageText.length) return; + + sendMessage(activeThread, GetSessionDataManager().userId, messageText); + + setMessageText(''); + }; + + const onKeyDown = (event: KeyboardEvent) => + { + if(event.key !== 'Enter') return; + + send(); + }; + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length === 2) + { + if(parts[1] === 'open') + { + setIsVisible(true); + + return; + } + + if(parts[1] === 'toggle') + { + setIsVisible(prevValue => !prevValue); + + return; + } + + const thread = getMessageThread(parseInt(parts[1])); + + if(!thread) return; + + setActiveThreadId(thread.threadId); + setIsVisible(true); + } + }, + eventUrlPrefix: 'friends-messenger/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, [ getMessageThread, setActiveThreadId ]); + + useEffect(() => + { + if(!isVisible || !activeThread) return; + + messagesBox.current.scrollTop = messagesBox.current.scrollHeight; + }, [ isVisible, activeThread ]); + + useEffect(() => + { + if(isVisible && !activeThread) + { + if(lastThreadId > 0) + { + setActiveThreadId(lastThreadId); + } + else + { + if(visibleThreads.length > 0) setActiveThreadId(visibleThreads[0].threadId); + } + + return; + } + + if(!isVisible && activeThread) + { + setLastThreadId(activeThread.threadId); + setActiveThreadId(-1); + } + }, [ isVisible, activeThread, lastThreadId, visibleThreads, setActiveThreadId ]); + + if(!isVisible) return null; + + return ( + + setIsVisible(false) } /> + + + + { LocalizeText('toolbar.icon.label.messenger') } + +
+ { visibleThreads && (visibleThreads.length > 0) && visibleThreads.map(thread => + { + return ( + setActiveThreadId(thread.threadId) }> + { thread.unread && + } +
+
+ { (thread.participant.id > 0) && + } + { (thread.participant.id <= 0) && + } +
+ { thread.participant.name } +
+
+ ); + }) } +
+
+
+ + { activeThread && + <> + { LocalizeText('messenger.window.separator', [ 'FRIEND_NAME' ], [ activeThread.participant.name ]) } + +
+
+ + +
+ +
+ +
+ + + + + +
+ setMessageText(event.target.value) } onKeyDown={ onKeyDown } /> + +
+ } +
+
+
+
+ ); +}; diff --git a/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadGroup.tsx b/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadGroup.tsx new file mode 100644 index 0000000..a6c35b4 --- /dev/null +++ b/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadGroup.tsx @@ -0,0 +1,73 @@ +import { GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { FC, useMemo } from 'react'; +import { GetGroupChatData, LocalizeText, MessengerGroupType, MessengerThread, MessengerThreadChat, MessengerThreadChatGroup } from '../../../../../api'; +import { Base, Flex, LayoutAvatarImageView } from '../../../../../common'; + +export const FriendsMessengerThreadGroup: FC<{ thread: MessengerThread, group: MessengerThreadChatGroup }> = props => +{ + const { thread = null, group = null } = props; + + const groupChatData = useMemo(() => ((group.type === MessengerGroupType.GROUP_CHAT) && GetGroupChatData(group.chats[0].extraData)), [ group ]); + + const isOwnChat = useMemo(() => + { + if(!thread || !group) return false; + + if((group.type === MessengerGroupType.PRIVATE_CHAT) && (group.userId === GetSessionDataManager().userId)) return true; + + if(groupChatData && group.chats.length && (groupChatData.userId === GetSessionDataManager().userId)) return true; + + return false; + }, [ thread, group, groupChatData ]); + + if(!thread || !group) return null; + + if(!group.userId) + { + return ( + <> + { group.chats.map((chat, index) => + { + return ( + + + { (chat.type === MessengerThreadChat.SECURITY_NOTIFICATION) && + + + { chat.message } + } + { (chat.type === MessengerThreadChat.ROOM_INVITE) && + + + { (LocalizeText('messenger.invitation') + ' ') }{ chat.message } + } + + + ); + }) } + + ); + } + + return ( + + + { ((group.type === MessengerGroupType.PRIVATE_CHAT) && !isOwnChat) && + } + { (groupChatData && !isOwnChat) && + } + + + + { isOwnChat && GetSessionDataManager().userName } + { !isOwnChat && (groupChatData ? groupChatData.username : thread.participant.name) } + + { group.chats.map((chat, index) => { chat.message }) } + + { isOwnChat && + + + } + + ); +}; diff --git a/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadView.tsx b/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadView.tsx new file mode 100644 index 0000000..8636997 --- /dev/null +++ b/src/components/friends/views/messenger/messenger-thread/FriendsMessengerThreadView.tsx @@ -0,0 +1,16 @@ +import { FC } from 'react'; +import { MessengerThread } from '../../../../../api'; +import { FriendsMessengerThreadGroup } from './FriendsMessengerThreadGroup'; + +export const FriendsMessengerThreadView: FC<{ thread: MessengerThread }> = props => +{ + const { thread = null } = props; + + thread.setRead(); + + return ( + <> + { (thread.groups.length > 0) && thread.groups.map((group, index) => ) } + + ); +}; diff --git a/src/components/game-center/GameCenterView.tsx b/src/components/game-center/GameCenterView.tsx new file mode 100644 index 0000000..3b398d5 --- /dev/null +++ b/src/components/game-center/GameCenterView.tsx @@ -0,0 +1,49 @@ +import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { useEffect } from 'react'; +import { Flex } from '../../common'; +import { useGameCenter } from '../../hooks'; +import { GameListView } from './views/GameListView'; +import { GameStageView } from './views/GameStageView'; +import { GameView } from './views/GameView'; + +export const GameCenterView = () => +{ + const { isVisible, setIsVisible, games, accountStatus } = useGameCenter(); + + useEffect(() => + { + const toggleGameCenter = () => + { + setIsVisible(prev => !prev); + }; + + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const value = url.split('/'); + + switch(value[1]) + { + case 'toggle': + toggleGameCenter(); + break; + } + }, + eventUrlPrefix: 'games/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, [ setIsVisible ]); + + if(!isVisible || !games || !accountStatus) return; + + return + + + + + + ; +}; diff --git a/src/components/game-center/views/GameListView.tsx b/src/components/game-center/views/GameListView.tsx new file mode 100644 index 0000000..a6cced9 --- /dev/null +++ b/src/components/game-center/views/GameListView.tsx @@ -0,0 +1,31 @@ +import { GameConfigurationData } from '@nitrots/nitro-renderer'; +import { LocalizeText } from '../../../api'; +import { useGameCenter } from '../../../hooks'; + +export const GameListView = () => +{ + const { games, selectedGame, setSelectedGame } = useGameCenter(); + + const getClasses = (game: GameConfigurationData) => + { + let classes = [ 'game-icon' ]; + + if(selectedGame === game) classes.push('selected'); + + return classes.join(' '); + }; + + const getIconImage = (game: GameConfigurationData): string => + { + return `url(${ game.assetUrl }${ game.gameNameId }_icon.png)`; + }; + + return
+ { LocalizeText('gamecenter.game_list_title') } +
+ { games && games.map((game, index) => +
setSelectedGame(game) } /> + ) } +
+
; +}; diff --git a/src/components/game-center/views/GameStageView.tsx b/src/components/game-center/views/GameStageView.tsx new file mode 100644 index 0000000..06cd25f --- /dev/null +++ b/src/components/game-center/views/GameStageView.tsx @@ -0,0 +1,46 @@ +import { Game2ExitGameMessageComposer } from '@nitrots/nitro-renderer'; +import { useEffect, useRef, useState } from 'react'; +import { SendMessageComposer } from '../../../api'; +import { useGameCenter } from '../../../hooks'; + +export const GameStageView = () => +{ + const { gameURL, setGameURL } = useGameCenter(); + const [ loadTimes, setLoadTimes ] = useState(0); + const ref = useRef(); + + useEffect(() => + { + if(!ref || ref && !ref.current) return; + + setLoadTimes(0); + + let frame: HTMLIFrameElement = document.createElement('iframe'); + + frame.src = gameURL; + frame.classList.add('game-center-stage'); + frame.classList.add('h-full'); + + frame.onload = () => + { + setLoadTimes(prev => prev += 1); + }; + + ref.current.innerHTML = ''; + ref.current.appendChild(frame); + + }, [ ref, gameURL ]); + + useEffect(() => + { + if(loadTimes > 1) + { + setGameURL(null); + SendMessageComposer(new Game2ExitGameMessageComposer()); + } + }, [ loadTimes, setGameURL ]); + + if(!gameURL) return null; + + return
; +}; diff --git a/src/components/game-center/views/GameView.tsx b/src/components/game-center/views/GameView.tsx new file mode 100644 index 0000000..c5d3561 --- /dev/null +++ b/src/components/game-center/views/GameView.tsx @@ -0,0 +1,55 @@ +import { Game2GetAccountGameStatusMessageComposer, GetGameStatusMessageComposer, JoinQueueMessageComposer } from '@nitrots/nitro-renderer'; +import { useEffect } from 'react'; +import { ColorUtils, LocalizeText, SendMessageComposer } from '../../../api'; +import { Button, Flex, LayoutItemCountView, Text } from '../../../common'; +import { useGameCenter } from '../../../hooks'; + +export const GameView = () => +{ + const { selectedGame, accountStatus } = useGameCenter(); + + useEffect(()=> + { + if(selectedGame) + { + SendMessageComposer(new GetGameStatusMessageComposer(selectedGame.gameId)); + SendMessageComposer(new Game2GetAccountGameStatusMessageComposer(selectedGame.gameId)); + } + },[ selectedGame ]); + + const getBgColour = (): string => + { + return ColorUtils.uintHexColor(selectedGame.bgColor); + }; + + const getBgImage = (): string => + { + return `url(${ selectedGame.assetUrl }${ selectedGame.gameNameId }_theme.png)`; + }; + + const getColor = () => + { + return ColorUtils.uintHexColor(selectedGame.textColor); + }; + + const onPlay = () => + { + SendMessageComposer(new JoinQueueMessageComposer(selectedGame.gameId)); + }; + + return + + { LocalizeText(`gamecenter.${ selectedGame.gameNameId }.description_title`) } + + { (accountStatus.hasUnlimitedGames || accountStatus.freeGamesLeft > 0) && <> + + } + { LocalizeText(`gamecenter.${ selectedGame.gameNameId }.description_content`) } + +
+ ; +}; diff --git a/src/components/groups/GroupsView.tsx b/src/components/groups/GroupsView.tsx new file mode 100644 index 0000000..0399861 --- /dev/null +++ b/src/components/groups/GroupsView.tsx @@ -0,0 +1,63 @@ +import { AddLinkEventTracker, GroupPurchasedEvent, GroupSettingsComposer, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { SendMessageComposer, TryVisitRoom } from '../../api'; +import { useGroup, useMessageEvent } from '../../hooks'; +import { GroupCreatorView } from './views/GroupCreatorView'; +import { GroupInformationStandaloneView } from './views/GroupInformationStandaloneView'; +import { GroupManagerView } from './views/GroupManagerView'; +import { GroupMembersView } from './views/GroupMembersView'; + +export const GroupsView: FC<{}> = props => +{ + const [ isCreatorVisible, setCreatorVisible ] = useState(false); + const {} = useGroup(); + + useMessageEvent(GroupPurchasedEvent, event => + { + const parser = event.getParser(); + + setCreatorVisible(false); + TryVisitRoom(parser.roomId); + }); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'create': + setCreatorVisible(true); + return; + case 'manage': + if(!parts[2]) return; + + setCreatorVisible(false); + SendMessageComposer(new GroupSettingsComposer(Number(parts[2]))); + return; + } + }, + eventUrlPrefix: 'groups/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + return ( + <> + { isCreatorVisible && + setCreatorVisible(false) } /> } + { !isCreatorVisible && + } + + + + ); +}; diff --git a/src/components/groups/views/GroupBadgeCreatorView.tsx b/src/components/groups/views/GroupBadgeCreatorView.tsx new file mode 100644 index 0000000..3f2e905 --- /dev/null +++ b/src/components/groups/views/GroupBadgeCreatorView.tsx @@ -0,0 +1,83 @@ +import { Dispatch, FC, SetStateAction, useState } from 'react'; +import { FaPlus, FaTimes } from 'react-icons/fa'; +import { GroupBadgePart } from '../../../api'; +import { Column, Flex, Grid, LayoutBadgeImageView } from '../../../common'; +import { useGroup } from '../../../hooks'; + +interface GroupBadgeCreatorViewProps +{ + badgeParts: GroupBadgePart[]; + setBadgeParts: Dispatch>; +} + +const POSITIONS: number[] = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]; + +export const GroupBadgeCreatorView: FC = props => +{ + const { badgeParts = [], setBadgeParts = null } = props; + const [ selectedIndex, setSelectedIndex ] = useState(-1); + const { groupCustomize = null } = useGroup(); + + const setPartProperty = (partIndex: number, property: string, value: number) => + { + const newBadgeParts = [ ...badgeParts ]; + + newBadgeParts[partIndex][property] = value; + + setBadgeParts(newBadgeParts); + + if(property === 'key') setSelectedIndex(-1); + }; + + if(!badgeParts || !badgeParts.length) return null; + + return ( + <> + { ((selectedIndex < 0) && badgeParts && (badgeParts.length > 0)) && badgeParts.map((part, index) => + { + return ( + + setSelectedIndex(index) }> + { (badgeParts[index].code && (badgeParts[index].code.length > 0)) && + } + { (!badgeParts[index].code || !badgeParts[index].code.length) && + + + } + + { (part.type !== GroupBadgePart.BASE) && + + { POSITIONS.map((position, posIndex) => + { + return
setPartProperty(index, 'position', position) } />; + }) } + } + + { (groupCustomize.badgePartColors.length > 0) && groupCustomize.badgePartColors.map((item, colorIndex) => + { + return
setPartProperty(index, 'color', (colorIndex + 1)) } />; + }) } + + + ); + }) } + { (selectedIndex >= 0) && + + { (badgeParts[selectedIndex].type === GroupBadgePart.SYMBOL) && + setPartProperty(selectedIndex, 'key', 0) }> + + + + } + { ((badgeParts[selectedIndex].type === GroupBadgePart.BASE) ? groupCustomize.badgeBases : groupCustomize.badgeSymbols).map((item, index) => + { + return ( + setPartProperty(selectedIndex, 'key', item.id) }> + + + ); + }) } + } + + ); +}; diff --git a/src/components/groups/views/GroupCreatorView.tsx b/src/components/groups/views/GroupCreatorView.tsx new file mode 100644 index 0000000..ee113ea --- /dev/null +++ b/src/components/groups/views/GroupCreatorView.tsx @@ -0,0 +1,165 @@ +import { GroupBuyComposer, GroupBuyDataComposer, GroupBuyDataEvent } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { HasHabboClub, IGroupData, LocalizeText, SendMessageComposer } from '../../../api'; +import { Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; +import { useMessageEvent } from '../../../hooks'; +import { GroupTabBadgeView } from './tabs/GroupTabBadgeView'; +import { GroupTabColorsView } from './tabs/GroupTabColorsView'; +import { GroupTabCreatorConfirmationView } from './tabs/GroupTabCreatorConfirmationView'; +import { GroupTabIdentityView } from './tabs/GroupTabIdentityView'; + +interface GroupCreatorViewProps +{ + onClose: () => void; +} + +const TABS: number[] = [ 1, 2, 3, 4 ]; + +export const GroupCreatorView: FC = props => +{ + const { onClose = null } = props; + const [ currentTab, setCurrentTab ] = useState(1); + const [ closeAction, setCloseAction ] = useState<{ action: () => boolean }>(null); + const [ groupData, setGroupData ] = useState(null); + const [ availableRooms, setAvailableRooms ] = useState<{ id: number, name: string }[]>(null); + const [ purchaseCost, setPurchaseCost ] = useState(0); + + const onCloseClose = () => + { + setCloseAction(null); + setGroupData(null); + + if(onClose) onClose(); + }; + + const buyGroup = () => + { + if(!groupData) return; + + const badge = []; + + groupData.groupBadgeParts.forEach(part => + { + if(part.code) + { + badge.push(part.key); + badge.push(part.color); + badge.push(part.position); + } + }); + + SendMessageComposer(new GroupBuyComposer(groupData.groupName, groupData.groupDescription, groupData.groupHomeroomId, groupData.groupColors[0], groupData.groupColors[1], badge)); + }; + + const previousStep = () => + { + if(closeAction && closeAction.action) + { + if(!closeAction.action()) return; + } + + if(currentTab === 1) + { + onClose(); + + return; + } + + setCurrentTab(value => value - 1); + }; + + const nextStep = () => + { + if(closeAction && closeAction.action) + { + if(!closeAction.action()) return; + } + + if(currentTab === 4) + { + buyGroup(); + + return; + } + + setCurrentTab(value => (value === 4 ? value : value + 1)); + }; + + useMessageEvent(GroupBuyDataEvent, event => + { + const parser = event.getParser(); + + const rooms: { id: number, name: string }[] = []; + + parser.availableRooms.forEach((name, id) => rooms.push({ id, name })); + + setAvailableRooms(rooms); + setPurchaseCost(parser.groupCost); + }); + + useEffect(() => + { + setCurrentTab(1); + + setGroupData({ + groupId: -1, + groupName: null, + groupDescription: null, + groupHomeroomId: -1, + groupState: 1, + groupCanMembersDecorate: true, + groupColors: null, + groupBadgeParts: null + }); + + SendMessageComposer(new GroupBuyDataComposer()); + }, [ setGroupData ]); + + if(!groupData) return null; + + return ( + + + +
+ { TABS.map((tab, index) => + { + return ( + + { LocalizeText(`group.create.steplabel.${ tab }`) } + + ); + }) } +
+ +
+
+ + { LocalizeText(`group.create.stepcaption.${ currentTab }`) } + { LocalizeText(`group.create.stepdesc.${ currentTab }`) } + +
+ + { (currentTab === 1) && + } + { (currentTab === 2) && + } + { (currentTab === 3) && + } + { (currentTab === 4) && + } + +
+ + +
+ + + + ); +}; diff --git a/src/components/groups/views/GroupInformationStandaloneView.tsx b/src/components/groups/views/GroupInformationStandaloneView.tsx new file mode 100644 index 0000000..d4206d7 --- /dev/null +++ b/src/components/groups/views/GroupInformationStandaloneView.tsx @@ -0,0 +1,29 @@ +import { GroupInformationEvent, GroupInformationParser } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { LocalizeText } from '../../../api'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../common'; +import { useMessageEvent } from '../../../hooks'; +import { GroupInformationView } from './GroupInformationView'; + +export const GroupInformationStandaloneView: FC<{}> = props => +{ + const [ groupInformation, setGroupInformation ] = useState(null); + + useMessageEvent(GroupInformationEvent, event => + { + const parser = event.getParser(); + + if((groupInformation && (groupInformation.id === parser.id)) || parser.flag) setGroupInformation(parser); + }); + + if(!groupInformation) return null; + + return ( + + setGroupInformation(null) } /> + + setGroupInformation(null) } /> + + + ); +}; diff --git a/src/components/groups/views/GroupInformationView.tsx b/src/components/groups/views/GroupInformationView.tsx new file mode 100644 index 0000000..d2f8a80 --- /dev/null +++ b/src/components/groups/views/GroupInformationView.tsx @@ -0,0 +1,146 @@ +import { CreateLinkEvent, GetSessionDataManager, GroupInformationParser, GroupRemoveMemberComposer } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { CatalogPageName, GetGroupManager, GetGroupMembers, GroupMembershipType, GroupType, LocalizeText, SendMessageComposer, TryJoinGroup, TryVisitRoom } from '../../../api'; +import { Button, Column, Grid, GridProps, LayoutBadgeImageView, Text } from '../../../common'; +import { useNotification } from '../../../hooks'; + +const STATES: string[] = [ 'regular', 'exclusive', 'private' ]; + +interface GroupInformationViewProps extends GridProps +{ + groupInformation: GroupInformationParser; + onJoin?: () => void; + onClose?: () => void; +} + +export const GroupInformationView: FC = props => +{ + const { groupInformation = null, onClose = null, overflow = 'hidden', ...rest } = props; + const { showConfirm = null } = useNotification(); + + const isRealOwner = (groupInformation && (groupInformation.ownerName === GetSessionDataManager().userName)); + + const joinGroup = () => (groupInformation && TryJoinGroup(groupInformation.id)); + + const leaveGroup = () => + { + showConfirm(LocalizeText('group.leaveconfirm.desc'), () => + { + SendMessageComposer(new GroupRemoveMemberComposer(groupInformation.id, GetSessionDataManager().userId)); + + if(onClose) onClose(); + }, null); + }; + + const getRoleIcon = () => + { + if(groupInformation.membershipType === GroupMembershipType.NOT_MEMBER || groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) return null; + + if(isRealOwner) return ; + + if(groupInformation.isAdmin) return ; + + return ; + }; + + const getButtonText = () => + { + if(isRealOwner) return 'group.youareowner'; + + if(groupInformation.type === GroupType.PRIVATE && groupInformation.membershipType !== GroupMembershipType.MEMBER) return ''; + + if(groupInformation.membershipType === GroupMembershipType.MEMBER) return 'group.leave'; + + if((groupInformation.membershipType === GroupMembershipType.NOT_MEMBER) && groupInformation.type === GroupType.REGULAR) return 'group.join'; + + if(groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) return 'group.membershippending'; + + if((groupInformation.membershipType === GroupMembershipType.NOT_MEMBER) && groupInformation.type === GroupType.EXCLUSIVE) return 'group.requestmembership'; + }; + + const handleButtonClick = () => + { + if((groupInformation.type === GroupType.PRIVATE) && (groupInformation.membershipType === GroupMembershipType.NOT_MEMBER)) return; + + if(groupInformation.membershipType === GroupMembershipType.MEMBER) + { + leaveGroup(); + + return; + } + + joinGroup(); + }; + + const handleAction = (action: string) => + { + switch(action) + { + case 'members': + GetGroupMembers(groupInformation.id); + break; + case 'members_pending': + GetGroupMembers(groupInformation.id, 2); + break; + case 'manage': + GetGroupManager(groupInformation.id); + break; + case 'homeroom': + TryVisitRoom(groupInformation.roomId); + break; + case 'furniture': + CreateLinkEvent('catalog/open/' + CatalogPageName.GUILD_CUSTOM_FURNI); + break; + case 'popular_groups': + CreateLinkEvent('navigator/search/groups'); + break; + } + }; + + if(!groupInformation) return null; + + return ( + + +
+ +
+ + handleAction('members') }>{ LocalizeText('group.membercount', [ 'totalMembers' ], [ groupInformation.membersCount.toString() ]) } + { (groupInformation.pendingRequestsCount > 0) && + handleAction('members_pending') }>{ LocalizeText('group.pendingmembercount', [ 'amount' ], [ groupInformation.pendingRequestsCount.toString() ]) } } + { groupInformation.isOwner && + handleAction('manage') }>{ LocalizeText('group.manage') } } + + { getRoleIcon() } +
+
+
+
+
+ { groupInformation.title } +
+ + { groupInformation.canMembersDecorate && + } +
+
+ { LocalizeText('group.created', [ 'date', 'owner' ], [ groupInformation.createdAt, groupInformation.ownerName ]) } +
+ { groupInformation.description } +
+
+
+ handleAction('homeroom') }>{ LocalizeText('group.linktobase') } + handleAction('furniture') }>{ LocalizeText('group.buyfurni') } + handleAction('popular_groups') }>{ LocalizeText('group.showgroups') } +
+ { (groupInformation.type !== GroupType.PRIVATE || groupInformation.type === GroupType.PRIVATE && groupInformation.membershipType === GroupMembershipType.MEMBER) && + } +
+
+
+ ); +}; diff --git a/src/components/groups/views/GroupManagerView.tsx b/src/components/groups/views/GroupManagerView.tsx new file mode 100644 index 0000000..b8336ab --- /dev/null +++ b/src/components/groups/views/GroupManagerView.tsx @@ -0,0 +1,119 @@ +import { GroupBadgePart, GroupInformationEvent, GroupSettingsEvent } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { IGroupData, LocalizeText } from '../../../api'; +import { Column, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../../common'; +import { useMessageEvent } from '../../../hooks'; +import { GroupTabBadgeView } from './tabs/GroupTabBadgeView'; +import { GroupTabColorsView } from './tabs/GroupTabColorsView'; +import { GroupTabIdentityView } from './tabs/GroupTabIdentityView'; +import { GroupTabSettingsView } from './tabs/GroupTabSettingsView'; + +const TABS: number[] = [ 1, 2, 3, 5 ]; + +export const GroupManagerView: FC<{}> = props => +{ + const [ currentTab, setCurrentTab ] = useState(1); + const [ closeAction, setCloseAction ] = useState<{ action: () => boolean }>(null); + const [ groupData, setGroupData ] = useState(null); + + const onClose = () => + { + setCloseAction(prevValue => + { + if(prevValue && prevValue.action) prevValue.action(); + + return null; + }); + + setGroupData(null); + }; + + const changeTab = (tab: number) => + { + if(closeAction && closeAction.action) closeAction.action(); + + setCurrentTab(tab); + }; + + useMessageEvent(GroupInformationEvent, event => + { + const parser = event.getParser(); + + if(!groupData || (groupData.groupId !== parser.id)) return; + + setGroupData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.groupName = parser.title; + newValue.groupDescription = parser.description; + newValue.groupState = parser.type; + newValue.groupCanMembersDecorate = parser.canMembersDecorate; + + return newValue; + }); + }); + + useMessageEvent(GroupSettingsEvent, event => + { + const parser = event.getParser(); + + const groupBadgeParts: GroupBadgePart[] = []; + + parser.badgeParts.forEach((part, id) => + { + groupBadgeParts.push(new GroupBadgePart( + part.isBase ? GroupBadgePart.BASE : GroupBadgePart.SYMBOL, + part.key, + part.color, + part.position + )); + }); + + setGroupData({ + groupId: parser.id, + groupName: parser.title, + groupDescription: parser.description, + groupHomeroomId: parser.roomId, + groupState: parser.state, + groupCanMembersDecorate: parser.canMembersDecorate, + groupColors: [ parser.colorA, parser.colorB ], + groupBadgeParts + }); + }); + + if(!groupData || (groupData.groupId <= 0)) return null; + + return ( + + + + { TABS.map(tab => + { + return ( changeTab(tab) }> + { LocalizeText(`group.edit.tab.${ tab }`) } + ); + }) } + + +
+
+ + { LocalizeText(`group.edit.tabcaption.${ currentTab }`) } + { LocalizeText(`group.edit.tabdesc.${ currentTab }`) } + +
+ + { (currentTab === 1) && + } + { (currentTab === 2) && + } + { (currentTab === 3) && + } + { (currentTab === 5) && + } + + + + ); +}; diff --git a/src/components/groups/views/GroupMembersView.tsx b/src/components/groups/views/GroupMembersView.tsx new file mode 100644 index 0000000..af4d9ee --- /dev/null +++ b/src/components/groups/views/GroupMembersView.tsx @@ -0,0 +1,211 @@ +import { AddLinkEventTracker, GetSessionDataManager, GroupAdminGiveComposer, GroupAdminTakeComposer, GroupConfirmMemberRemoveEvent, GroupConfirmRemoveMemberComposer, GroupMemberParser, GroupMembersComposer, GroupMembersEvent, GroupMembershipAcceptComposer, GroupMembershipDeclineComposer, GroupMembersParser, GroupRank, GroupRemoveMemberComposer, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useState } from 'react'; +import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; +import { GetUserProfile, LocalizeText, SendMessageComposer } from '../../../api'; +import { Button, Column, Flex, Grid, LayoutAvatarImageView, LayoutBadgeImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; +import { useMessageEvent, useNotification } from '../../../hooks'; +import { classNames } from '../../../layout'; + +export const GroupMembersView: FC<{}> = props => +{ + const [ groupId, setGroupId ] = useState(-1); + const [ levelId, setLevelId ] = useState(-1); + const [ membersData, setMembersData ] = useState(null); + const [ pageId, setPageId ] = useState(-1); + const [ totalPages, setTotalPages ] = useState(0); + const [ searchQuery, setSearchQuery ] = useState(''); + const [ removingMemberName, setRemovingMemberName ] = useState(null); + const { showConfirm = null } = useNotification(); + + const getRankDescription = (member: GroupMemberParser) => + { + if(member.rank === GroupRank.OWNER) return 'group.members.owner'; + + if(membersData.admin) + { + if(member.rank === GroupRank.ADMIN) return 'group.members.removerights'; + + if(member.rank === GroupRank.MEMBER) return 'group.members.giverights'; + } + + return ''; + }; + + const refreshMembers = useCallback(() => + { + if((groupId === -1) || (levelId === -1) || (pageId === -1)) return; + + SendMessageComposer(new GroupMembersComposer(groupId, pageId, searchQuery, levelId)); + }, [ groupId, levelId, pageId, searchQuery ]); + + const toggleAdmin = (member: GroupMemberParser) => + { + if(!membersData.admin || (member.rank === GroupRank.OWNER)) return; + + if(member.rank !== GroupRank.ADMIN) SendMessageComposer(new GroupAdminGiveComposer(membersData.groupId, member.id)); + else SendMessageComposer(new GroupAdminTakeComposer(membersData.groupId, member.id)); + + refreshMembers(); + }; + + const acceptMembership = (member: GroupMemberParser) => + { + if(!membersData.admin || (member.rank !== GroupRank.REQUESTED)) return; + + SendMessageComposer(new GroupMembershipAcceptComposer(membersData.groupId, member.id)); + + refreshMembers(); + }; + + const removeMemberOrDeclineMembership = (member: GroupMemberParser) => + { + if(!membersData.admin) return; + + if(member.rank === GroupRank.REQUESTED) + { + SendMessageComposer(new GroupMembershipDeclineComposer(membersData.groupId, member.id)); + + refreshMembers(); + + return; + } + + setRemovingMemberName(member.name); + SendMessageComposer(new GroupConfirmRemoveMemberComposer(membersData.groupId, member.id)); + }; + + useMessageEvent(GroupMembersEvent, event => + { + const parser = event.getParser(); + + setMembersData(parser); + setLevelId(parser.level); + setTotalPages(Math.ceil(parser.totalMembersCount / parser.pageSize)); + }); + + useMessageEvent(GroupConfirmMemberRemoveEvent, event => + { + const parser = event.getParser(); + + showConfirm(LocalizeText(((parser.furnitureCount > 0) ? 'group.kickconfirm.desc' : 'group.kickconfirm_nofurni.desc'), [ 'user', 'amount' ], [ removingMemberName, parser.furnitureCount.toString() ]), () => + { + SendMessageComposer(new GroupRemoveMemberComposer(membersData.groupId, parser.userId)); + + refreshMembers(); + }, null); + + setRemovingMemberName(null); + }); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + const groupId = (parseInt(parts[1]) || -1); + const levelId = (parseInt(parts[2]) || 3); + + setGroupId(groupId); + setLevelId(levelId); + setPageId(0); + }, + eventUrlPrefix: 'group-members/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + useEffect(() => + { + setPageId(0); + }, [ groupId, levelId, searchQuery ]); + + useEffect(() => + { + if((groupId === -1) || (levelId === -1) || (pageId === -1)) return; + + SendMessageComposer(new GroupMembersComposer(groupId, pageId, searchQuery, levelId)); + }, [ groupId, levelId, pageId, searchQuery ]); + + useEffect(() => + { + if(groupId === -1) return; + + setLevelId(-1); + setMembersData(null); + setTotalPages(0); + setSearchQuery(''); + setRemovingMemberName(null); + }, [ groupId ]); + + if((groupId === -1) || !membersData) return null; + + return ( + + setGroupId(-1) } /> + +
+ + + + + setSearchQuery(event.target.value) } /> + + +
+ + { membersData.result.map((member, index) => + { + return ( + +
GetUserProfile(member.id) }> + +
+ + GetUserProfile(member.id) }>{ member.name } + { (member.rank !== GroupRank.REQUESTED) && + { LocalizeText('group.members.since', [ 'date' ], [ member.joinedAt ]) } } + +
+ { (member.rank !== GroupRank.REQUESTED) && +
+
toggleAdmin(member) } /> +
} + { membersData.admin && (member.rank === GroupRank.REQUESTED) && + +
acceptMembership(member) } /> + } + { membersData.admin && (member.rank !== GroupRank.OWNER) && (member.id !== GetSessionDataManager().userId) && + +
removeMemberOrDeclineMembership(member) } /> + } +
+
+ ); + }) } + + + + + { LocalizeText('group.members.pageinfo', [ 'amount', 'page', 'totalPages' ], [ membersData.totalMembersCount.toString(), (membersData.pageIndex + 1).toString(), totalPages.toString() ]) } + + + + + + ); +}; diff --git a/src/components/groups/views/GroupRoomInformationView.tsx b/src/components/groups/views/GroupRoomInformationView.tsx new file mode 100644 index 0000000..7be8a92 --- /dev/null +++ b/src/components/groups/views/GroupRoomInformationView.tsx @@ -0,0 +1,132 @@ +import { DesktopViewEvent, GetGuestRoomResultEvent, GetSessionDataManager, GroupInformationComposer, GroupInformationEvent, GroupInformationParser, GroupRemoveMemberComposer, HabboGroupDeactivatedMessageEvent, RoomEntryInfoMessageEvent } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; +import { GetGroupInformation, GetGroupManager, GroupMembershipType, GroupType, LocalizeText, SendMessageComposer, TryJoinGroup } from '../../../api'; +import { Button, Flex, LayoutBadgeImageView, Text } from '../../../common'; +import { useMessageEvent, useNotification } from '../../../hooks'; + +export const GroupRoomInformationView: FC<{}> = props => +{ + const [ expectedGroupId, setExpectedGroupId ] = useState(0); + const [ groupInformation, setGroupInformation ] = useState(null); + const [ isOpen, setIsOpen ] = useState(true); + const { showConfirm = null } = useNotification(); + + useMessageEvent(DesktopViewEvent, event => + { + setExpectedGroupId(0); + setGroupInformation(null); + }); + + useMessageEvent(RoomEntryInfoMessageEvent, event => + { + setExpectedGroupId(0); + setGroupInformation(null); + }); + + useMessageEvent(GetGuestRoomResultEvent, event => + { + const parser = event.getParser(); + + if(!parser.roomEnter) return; + + if(parser.data.habboGroupId > 0) + { + setExpectedGroupId(parser.data.habboGroupId); + SendMessageComposer(new GroupInformationComposer(parser.data.habboGroupId, false)); + } + else + { + setExpectedGroupId(0); + setGroupInformation(null); + } + }); + + useMessageEvent(HabboGroupDeactivatedMessageEvent, event => + { + const parser = event.getParser(); + + if(!groupInformation || ((parser.groupId !== groupInformation.id) && (parser.groupId !== expectedGroupId))) return; + + setExpectedGroupId(0); + setGroupInformation(null); + }); + + useMessageEvent(GroupInformationEvent, event => + { + const parser = event.getParser(); + + if(parser.id !== expectedGroupId) return; + + setGroupInformation(parser); + }); + + const leaveGroup = () => + { + showConfirm(LocalizeText('group.leaveconfirm.desc'), () => + { + SendMessageComposer(new GroupRemoveMemberComposer(groupInformation.id, GetSessionDataManager().userId)); + }, null); + }; + + const isRealOwner = (groupInformation && (groupInformation.ownerName === GetSessionDataManager().userName)); + + const getButtonText = () => + { + if(isRealOwner) return 'group.manage'; + + if(groupInformation.type === GroupType.PRIVATE) return ''; + + if(groupInformation.membershipType === GroupMembershipType.MEMBER) return 'group.leave'; + + if((groupInformation.membershipType === GroupMembershipType.NOT_MEMBER) && groupInformation.type === GroupType.REGULAR) return 'group.join'; + + if(groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) return 'group.membershippending'; + + if((groupInformation.membershipType === GroupMembershipType.NOT_MEMBER) && groupInformation.type === GroupType.EXCLUSIVE) return 'group.requestmembership'; + }; + + const handleButtonClick = () => + { + if(isRealOwner) return GetGroupManager(groupInformation.id); + + if((groupInformation.type === GroupType.PRIVATE) && (groupInformation.membershipType === GroupMembershipType.NOT_MEMBER)) return; + + if(groupInformation.membershipType === GroupMembershipType.MEMBER) + { + leaveGroup(); + + return; + } + + TryJoinGroup(groupInformation.id); + }; + + if(!groupInformation) return null; + + return ( +
+
+ setIsOpen(value => !value) }> + { LocalizeText('group.homeroominfo.title') } + { isOpen && } + { !isOpen && } + + { isOpen && + <> + GetGroupInformation(groupInformation.id) }> +
+ +
+ { groupInformation.title } +
+ { (groupInformation.type !== GroupType.PRIVATE || isRealOwner) && + + } + } +
+
+ ); +}; diff --git a/src/components/groups/views/tabs/GroupTabBadgeView.tsx b/src/components/groups/views/tabs/GroupTabBadgeView.tsx new file mode 100644 index 0000000..33c3cd3 --- /dev/null +++ b/src/components/groups/views/tabs/GroupTabBadgeView.tsx @@ -0,0 +1,120 @@ +import { GroupSaveBadgeComposer } from '@nitrots/nitro-renderer'; +import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react'; +import { GroupBadgePart, IGroupData, SendMessageComposer } from '../../../../api'; +import { Column, Flex, Grid, LayoutBadgeImageView } from '../../../../common'; +import { useGroup } from '../../../../hooks'; +import { GroupBadgeCreatorView } from '../GroupBadgeCreatorView'; + +interface GroupTabBadgeViewProps +{ + skipDefault?: boolean; + setCloseAction: Dispatch boolean }>>; + groupData: IGroupData; + setGroupData: Dispatch>; +} + +export const GroupTabBadgeView: FC = props => +{ + const { groupData = null, setGroupData = null, setCloseAction = null, skipDefault = null } = props; + const [ badgeParts, setBadgeParts ] = useState(null); + const { groupCustomize = null } = useGroup(); + + const getModifiedBadgeCode = () => + { + if(!badgeParts || !badgeParts.length) return ''; + + let badgeCode = ''; + + badgeParts.forEach(part => (part.code && (badgeCode += part.code))); + + return badgeCode; + }; + + const saveBadge = useCallback(() => + { + if(!groupData || !badgeParts || !badgeParts.length) return false; + + if((groupData.groupBadgeParts === badgeParts)) return true; + + if(groupData.groupId <= 0) + { + setGroupData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.groupBadgeParts = badgeParts; + + return newValue; + }); + + return true; + } + + const badge = []; + + badgeParts.forEach(part => + { + if(!part.code) return; + + badge.push(part.key); + badge.push(part.color); + badge.push(part.position); + }); + + SendMessageComposer(new GroupSaveBadgeComposer(groupData.groupId, badge)); + + return true; + }, [ groupData, badgeParts, setGroupData ]); + + useEffect(() => + { + if(groupData.groupBadgeParts) return; + + const badgeParts = [ + new GroupBadgePart(GroupBadgePart.BASE, groupCustomize.badgeBases[0].id, groupCustomize.badgePartColors[0].id), + new GroupBadgePart(GroupBadgePart.SYMBOL, 0, groupCustomize.badgePartColors[0].id), + new GroupBadgePart(GroupBadgePart.SYMBOL, 0, groupCustomize.badgePartColors[0].id), + new GroupBadgePart(GroupBadgePart.SYMBOL, 0, groupCustomize.badgePartColors[0].id), + new GroupBadgePart(GroupBadgePart.SYMBOL, 0, groupCustomize.badgePartColors[0].id) + ]; + + setGroupData(prevValue => + { + const groupBadgeParts = badgeParts; + + return { ...prevValue, groupBadgeParts }; + }); + }, [ groupData.groupBadgeParts, groupCustomize, setGroupData ]); + + useEffect(() => + { + if(groupData.groupId <= 0) + { + setBadgeParts(groupData.groupBadgeParts ? [ ...groupData.groupBadgeParts ] : null); + + return; + } + + setBadgeParts(groupData.groupBadgeParts); + }, [ groupData ]); + + useEffect(() => + { + setCloseAction({ action: saveBadge }); + + return () => setCloseAction(null); + }, [ setCloseAction, saveBadge ]); + + return ( + + + + + + + + + + + ); +}; diff --git a/src/components/groups/views/tabs/GroupTabColorsView.tsx b/src/components/groups/views/tabs/GroupTabColorsView.tsx new file mode 100644 index 0000000..37a7fbf --- /dev/null +++ b/src/components/groups/views/tabs/GroupTabColorsView.tsx @@ -0,0 +1,128 @@ +import { GroupSaveColorsComposer } from '@nitrots/nitro-renderer'; +import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react'; +import { IGroupData, LocalizeText, SendMessageComposer } from '../../../../api'; +import { AutoGrid, Column, Grid, Text } from '../../../../common'; +import { useGroup } from '../../../../hooks'; +import { classNames } from '../../../../layout'; + +interface GroupTabColorsViewProps +{ + groupData: IGroupData; + setGroupData: Dispatch>; + setCloseAction: Dispatch boolean }>>; +} + +export const GroupTabColorsView: FC = props => +{ + const { groupData = null, setGroupData = null, setCloseAction = null } = props; + const [ colors, setColors ] = useState(null); + const { groupCustomize = null } = useGroup(); + + const getGroupColor = (colorIndex: number) => + { + if(colorIndex === 0) return groupCustomize.groupColorsA.find(color => (color.id === colors[colorIndex])).color; + + return groupCustomize.groupColorsB.find(color => (color.id === colors[colorIndex])).color; + }; + + const selectColor = (colorIndex: number, colorId: number) => + { + setColors(prevValue => + { + const newColors = [ ...prevValue ]; + + newColors[colorIndex] = colorId; + + return newColors; + }); + }; + + const saveColors = useCallback(() => + { + if(!groupData || !colors || !colors.length) return false; + + if(groupData.groupColors === colors) return true; + + if(groupData.groupId <= 0) + { + setGroupData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.groupColors = [ ...colors ]; + + return newValue; + }); + + return true; + } + + SendMessageComposer(new GroupSaveColorsComposer(groupData.groupId, colors[0], colors[1])); + + return true; + }, [ groupData, colors, setGroupData ]); + + useEffect(() => + { + if(!groupCustomize.groupColorsA || !groupCustomize.groupColorsB || groupData.groupColors) return; + + const groupColors = [ groupCustomize.groupColorsA[0].id, groupCustomize.groupColorsB[0].id ]; + + setGroupData(prevValue => + { + return { ...prevValue, groupColors }; + }); + }, [ groupCustomize, groupData.groupColors, setGroupData ]); + + useEffect(() => + { + if(groupData.groupId <= 0) + { + setColors(groupData.groupColors ? [ ...groupData.groupColors ] : null); + + return; + } + + setColors(groupData.groupColors); + }, [ groupData ]); + + useEffect(() => + { + setCloseAction({ action: saveColors }); + + return () => setCloseAction(null); + }, [ setCloseAction, saveColors ]); + + if(!colors) return null; + + return ( + + + { LocalizeText('group.edit.color.guild.color') } + { groupData.groupColors && (groupData.groupColors.length > 0) && +
+
+
+
} + + + { LocalizeText('group.edit.color.primary.color') } + + { groupData.groupColors && groupCustomize.groupColorsA && groupCustomize.groupColorsA.map((item, index) => + { + return
selectColor(0, item.id) }>
; + }) } +
+
+ + { LocalizeText('group.edit.color.secondary.color') } + + { groupData.groupColors && groupCustomize.groupColorsB && groupCustomize.groupColorsB.map((item, index) => + { + return
selectColor(1, item.id) }>
; + }) } +
+
+ + ); +}; diff --git a/src/components/groups/views/tabs/GroupTabCreatorConfirmationView.tsx b/src/components/groups/views/tabs/GroupTabCreatorConfirmationView.tsx new file mode 100644 index 0000000..9c76e25 --- /dev/null +++ b/src/components/groups/views/tabs/GroupTabCreatorConfirmationView.tsx @@ -0,0 +1,67 @@ +import { Dispatch, FC, SetStateAction } from 'react'; +import { IGroupData, LocalizeText } from '../../../../api'; +import { Column, Flex, Grid, LayoutBadgeImageView, Text } from '../../../../common'; +import { useGroup } from '../../../../hooks'; + +interface GroupTabCreatorConfirmationViewProps +{ + groupData: IGroupData; + setGroupData: Dispatch>; + purchaseCost: number; +} + +export const GroupTabCreatorConfirmationView: FC = props => +{ + const { groupData = null, setGroupData = null, purchaseCost = 0 } = props; + const { groupCustomize = null } = useGroup(); + + const getCompleteBadgeCode = () => + { + if(!groupData || !groupData.groupBadgeParts || !groupData.groupBadgeParts.length) return ''; + + let badgeCode = ''; + + groupData.groupBadgeParts.forEach(part => (part.code && (badgeCode += part.code))); + + return badgeCode; + }; + + const getGroupColor = (colorIndex: number) => + { + if(colorIndex === 0) return groupCustomize.groupColorsA.find(c => c.id === groupData.groupColors[colorIndex]).color; + + return groupCustomize.groupColorsB.find(c => c.id === groupData.groupColors[colorIndex]).color; + }; + + if(!groupData) return null; + + return ( + + + + { LocalizeText('group.create.confirm.guildbadge') } + + + + { LocalizeText('group.edit.color.guild.color') } + +
+
+ + + + +
+
+ { groupData.groupName } + { groupData.groupDescription } +
+ { LocalizeText('group.create.confirm.info') } +
+ + { LocalizeText('group.create.confirm.buyinfo', [ 'amount' ], [ purchaseCost.toString() ]) } + +
+ + ); +}; diff --git a/src/components/groups/views/tabs/GroupTabIdentityView.tsx b/src/components/groups/views/tabs/GroupTabIdentityView.tsx new file mode 100644 index 0000000..11e3e96 --- /dev/null +++ b/src/components/groups/views/tabs/GroupTabIdentityView.tsx @@ -0,0 +1,203 @@ +import +{ + CreateLinkEvent, + GroupDeleteComposer, + GroupSaveInformationComposer, +} from '@nitrots/nitro-renderer'; +import +{ + Dispatch, + FC, + SetStateAction, + useCallback, + useEffect, + useState, +} from 'react'; +import { IGroupData, LocalizeText, SendMessageComposer } from '../../../../api'; +import { Button, Column, Text } from '../../../../common'; +import { useNotification } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; + +interface GroupTabIdentityViewProps { + groupData: IGroupData; + setGroupData: Dispatch>; + setCloseAction: Dispatch boolean }>>; + onClose: () => void; + isCreator?: boolean; + availableRooms?: { id: number; name: string }[]; +} + +export const GroupTabIdentityView: FC = (props) => +{ + const { + groupData = null, + setGroupData = null, + setCloseAction = null, + onClose = null, + isCreator = false, + availableRooms = [], + } = props; + const [groupName, setGroupName] = useState(''); + const [groupDescription, setGroupDescription] = useState(''); + const [groupHomeroomId, setGroupHomeroomId] = useState(-1); + const { showConfirm = null } = useNotification(); + + const deleteGroup = () => + { + if(!groupData || groupData.groupId <= 0) return; + + showConfirm( + LocalizeText('group.deleteconfirm.desc'), + () => + { + SendMessageComposer(new GroupDeleteComposer(groupData.groupId)); + + if(onClose) onClose(); + }, + null, + null, + null, + LocalizeText('group.deleteconfirm.title') + ); + }; + + const saveIdentity = useCallback(() => + { + if(!groupData || !groupName || !groupName.length) return false; + + if( + groupName === groupData.groupName && + groupDescription === groupData.groupDescription + ) + return true; + + if(groupData.groupId <= 0) + { + if(groupHomeroomId <= 0) return false; + + setGroupData((prevValue) => + { + const newValue = { ...prevValue }; + + newValue.groupName = groupName; + newValue.groupDescription = groupDescription; + newValue.groupHomeroomId = groupHomeroomId; + + return newValue; + }); + + return true; + } + + SendMessageComposer( + new GroupSaveInformationComposer( + groupData.groupId, + groupName, + groupDescription || '' + ) + ); + + return true; + }, [groupData, groupName, groupDescription, groupHomeroomId, setGroupData]); + + useEffect(() => + { + setGroupName(groupData.groupName || ''); + setGroupDescription(groupData.groupDescription || ''); + setGroupHomeroomId(groupData.groupHomeroomId); + }, [groupData]); + + useEffect(() => + { + setCloseAction({ action: saveIdentity }); + + return () => setCloseAction(null); + }, [setCloseAction, saveIdentity]); + + if(!groupData) return null; + + return ( + +
+
+ + {LocalizeText('group.edit.name')} + + setGroupName(event.target.value)} + /> +
+
+ + {LocalizeText('group.edit.desc')} + + + +
+ ); +}; diff --git a/src/components/guide-tool/views/GuideToolUserFeedbackView.tsx b/src/components/guide-tool/views/GuideToolUserFeedbackView.tsx new file mode 100644 index 0000000..e76b669 --- /dev/null +++ b/src/components/guide-tool/views/GuideToolUserFeedbackView.tsx @@ -0,0 +1,43 @@ +import { GuideSessionFeedbackMessageComposer } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { LocalizeText, SendMessageComposer } from '../../../api'; +import { Button, Column, Flex, Text } from '../../../common'; + +interface GuideToolUserFeedbackViewProps +{ + userName: string; +} + +export const GuideToolUserFeedbackView: FC = props => +{ + const { userName = null } = props; + + const giveFeedback = (recommend: boolean) => SendMessageComposer(new GuideSessionFeedbackMessageComposer(recommend)); + + return ( +
+ + + { userName } + { LocalizeText('guide.help.request.user.feedback.guide.desc') } + + + +
+ { LocalizeText('guide.help.request.user.feedback.closed.title') } + { LocalizeText('guide.help.request.user.feedback.closed.desc') } +
+ { userName && (userName.length > 0) && + <> +
+
+ { LocalizeText('guide.help.request.user.feedback.question') } +
+ + +
+
+ } +
+ ); +}; diff --git a/src/components/guide-tool/views/GuideToolUserNoHelpersView.tsx b/src/components/guide-tool/views/GuideToolUserNoHelpersView.tsx new file mode 100644 index 0000000..9469e78 --- /dev/null +++ b/src/components/guide-tool/views/GuideToolUserNoHelpersView.tsx @@ -0,0 +1,13 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../api'; +import { Text } from '../../../common'; + +export const GuideToolUserNoHelpersView: FC<{}> = props => +{ + return ( +
+ { LocalizeText('guide.help.request.no_tour_guides.title') } + { LocalizeText('guide.help.request.no_tour_guides.message') } +
+ ); +}; diff --git a/src/components/guide-tool/views/GuideToolUserPendingView.tsx b/src/components/guide-tool/views/GuideToolUserPendingView.tsx new file mode 100644 index 0000000..d897ced --- /dev/null +++ b/src/components/guide-tool/views/GuideToolUserPendingView.tsx @@ -0,0 +1,33 @@ +import { GuideSessionRequesterCancelsMessageComposer } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { LocalizeText, SendMessageComposer } from '../../../api'; +import { Button, Column, Text } from '../../../common'; + +interface GuideToolUserPendingViewProps +{ + helpRequestDescription: string; + helpRequestAverageTime: number; +} + +export const GuideToolUserPendingView: FC = props => +{ + const { helpRequestDescription = null, helpRequestAverageTime = 0 } = props; + + const cancelRequest = () => SendMessageComposer(new GuideSessionRequesterCancelsMessageComposer()); + + return ( +
+ + { LocalizeText('guide.help.request.guide.accept.request.title') } + { LocalizeText('guide.help.request.type.1') } + { helpRequestDescription } + +
+ { LocalizeText('guide.help.request.user.pending.info.title') } + { LocalizeText('guide.help.request.user.pending.info.message') } + { LocalizeText('guide.help.request.user.pending.info.waiting', [ 'waitingtime' ], [ helpRequestAverageTime.toString() ]) } +
+ +
+ ); +}; diff --git a/src/components/guide-tool/views/GuideToolUserSomethingWrogView.tsx b/src/components/guide-tool/views/GuideToolUserSomethingWrogView.tsx new file mode 100644 index 0000000..6943379 --- /dev/null +++ b/src/components/guide-tool/views/GuideToolUserSomethingWrogView.tsx @@ -0,0 +1,12 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../api'; +import { Text } from '../../../common'; + +export const GuideToolUserSomethingWrogView: FC<{}> = props => +{ + return ( +
+ { LocalizeText('guide.help.request.user.guide.disconnected.error.desc') } +
+ ); +}; diff --git a/src/components/guide-tool/views/GuideToolUserThanksView.tsx b/src/components/guide-tool/views/GuideToolUserThanksView.tsx new file mode 100644 index 0000000..cc74925 --- /dev/null +++ b/src/components/guide-tool/views/GuideToolUserThanksView.tsx @@ -0,0 +1,13 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../api'; +import { Text } from '../../../common'; + +export const GuideToolUserThanksView: FC<{}> = props => +{ + return ( +
+ { LocalizeText('guide.help.request.user.thanks.info.title') } + { LocalizeText('guide.help.request.user.thanks.info.desc') } +
+ ); +}; diff --git a/src/components/hc-center/HcCenterView.tsx b/src/components/hc-center/HcCenterView.tsx new file mode 100644 index 0000000..206994e --- /dev/null +++ b/src/components/hc-center/HcCenterView.tsx @@ -0,0 +1,199 @@ +import { AddLinkEventTracker, ClubGiftInfoEvent, CreateLinkEvent, GetClubGiftInfo, ILinkEventTracker, RemoveLinkEventTracker, ScrGetKickbackInfoMessageComposer, ScrKickbackData, ScrSendKickbackInfoMessageEvent } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { ClubStatus, FriendlyTime, GetClubBadge, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../api'; +import { Button, Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common'; +import { useInventoryBadges, useMessageEvent, usePurse, useSessionInfo } from '../../hooks'; + + +export const HcCenterView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ kickbackData, setKickbackData ] = useState(null); + const [ unclaimedGifts, setUnclaimedGifts ] = useState(0); + const [ badgeCode, setBadgeCode ] = useState(null); + const { userFigure = null } = useSessionInfo(); + const { purse = null, clubStatus = null } = usePurse(); + const { badgeCodes = [], activate = null, deactivate = null } = useInventoryBadges(); + + const getClubText = () => + { + if(purse.clubDays <= 0) return LocalizeText('purse.clubdays.zero.amount.text'); + + if((purse.minutesUntilExpiration > -1) && (purse.minutesUntilExpiration < (60 * 24))) + { + return FriendlyTime.shortFormat(purse.minutesUntilExpiration * 60); + } + + return FriendlyTime.shortFormat(((purse.clubPeriods * 31) + purse.clubDays) * 86400); + }; + + const getInfoText = () => + { + switch(clubStatus) + { + case ClubStatus.ACTIVE: + return LocalizeText(`hccenter.status.${ clubStatus }.info`, [ 'timeleft', 'joindate', 'streakduration' ], [ getClubText(), kickbackData?.firstSubscriptionDate, FriendlyTime.shortFormat(kickbackData?.currentHcStreak * 86400) ]); + case ClubStatus.EXPIRED: + return LocalizeText(`hccenter.status.${ clubStatus }.info`, [ 'joindate' ], [ kickbackData?.firstSubscriptionDate ]); + default: + return LocalizeText(`hccenter.status.${ clubStatus }.info`); + } + }; + + const getHcPaydayTime = () => (!kickbackData || kickbackData.timeUntilPayday < 60) ? LocalizeText('hccenter.special.time.soon') : FriendlyTime.shortFormat(kickbackData.timeUntilPayday * 60); + const getHcPaydayAmount = () => LocalizeText('hccenter.special.sum', [ 'credits' ], [ (kickbackData?.creditRewardForStreakBonus + kickbackData?.creditRewardForMonthlySpent).toString() ]); + + useMessageEvent(ClubGiftInfoEvent, event => + { + const parser = event.getParser(); + + setUnclaimedGifts(parser.giftsAvailable); + }); + + useMessageEvent(ScrSendKickbackInfoMessageEvent, event => + { + const parser = event.getParser(); + + setKickbackData(parser.data); + }); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'open': + if(parts.length > 2) + { + switch(parts[2]) + { + case 'hccenter': + setIsVisible(true); + break; + } + } + return; + } + }, + eventUrlPrefix: 'habboUI/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + useEffect(() => + { + setBadgeCode(GetClubBadge(badgeCodes)); + }, [ badgeCodes ]); + + useEffect(() => + { + if(!isVisible) return; + + const id = activate(); + + return () => deactivate(id); + }, [ isVisible, activate, deactivate ]); + + useEffect(() => + { + SendMessageComposer(new GetClubGiftInfo()); + SendMessageComposer(new ScrGetKickbackInfoMessageComposer()); + }, []); + + if(!isVisible) return null; + + const popover = ( + <> +
{ LocalizeText('hccenter.breakdown.title') }
+
{ LocalizeText('hccenter.breakdown.creditsspent', [ 'credits' ], [ kickbackData?.totalCreditsSpent.toString() ]) }
+
{ LocalizeText('hccenter.breakdown.paydayfactor.percent', [ 'percent' ], [ (kickbackData?.kickbackPercentage * 100).toString() ]) }
+
{ LocalizeText('hccenter.breakdown.streakbonus', [ 'credits' ], [ kickbackData?.creditRewardForStreakBonus.toString() ]) }
+
+
{ LocalizeText('hccenter.breakdown.total', [ 'credits', 'actual' ], [ getHcPaydayAmount(), ((((kickbackData?.kickbackPercentage * kickbackData?.totalCreditsSpent) + kickbackData?.creditRewardForStreakBonus) * 100) / 100).toString() ]) }
+
CreateLinkEvent('habbopages/' + GetConfigurationValue('hc.center')['payday.habbopage']) }> + { LocalizeText('hccenter.special.infolink') } +
+ + ); + + return ( + + setIsVisible(false) } /> + +
+
+ + + +
+
+ +
+ + +
+ + + { LocalizeText('hccenter.status.' + clubStatus) } + + +
+ { GetConfigurationValue('hc.center')['payday.info'] && + + + +

{ LocalizeText('hccenter.special.title') }

+
{ LocalizeText('hccenter.special.info') }
+
CreateLinkEvent('habbopages/' + GetConfigurationValue('hc.center')['payday.habbopage']) }>{ LocalizeText('hccenter.special.infolink') }
+
+
+
{ LocalizeText('hccenter.special.time.title') }
+
+
+
{ getHcPaydayTime() }
+
+ { clubStatus === ClubStatus.ACTIVE && +
+
{ LocalizeText('hccenter.special.amount.title') }
+
+
{ getHcPaydayAmount() }
+
+ { LocalizeText('hccenter.breakdown.infolink') } +
+
+
} +
+ } + { GetConfigurationValue('hc.center')['gift.info'] && +
+
+

{ LocalizeText('hccenter.gift.title') }

+
0 ? LocalizeText('hccenter.unclaimedgifts', [ 'unclaimedgifts' ], [ unclaimedGifts.toString() ]) : LocalizeText('hccenter.gift.info') } }>
+
+ +
} + { GetConfigurationValue('hc.center')['benefits.info'] && +
+
{ LocalizeText('hccenter.general.title') }
+
+ +
} + + + ); +}; diff --git a/src/components/help/HelpView.tsx b/src/components/help/HelpView.tsx new file mode 100644 index 0000000..87ec07d --- /dev/null +++ b/src/components/help/HelpView.tsx @@ -0,0 +1,116 @@ +import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, ReportState } from '../../api'; +import { Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common'; +import { useHelp } from '../../hooks'; +import { DescribeReportView } from './views/DescribeReportView'; +import { HelpIndexView } from './views/HelpIndexView'; +import { ReportSummaryView } from './views/ReportSummaryView'; +import { SanctionSatusView } from './views/SanctionStatusView'; +import { SelectReportedChatsView } from './views/SelectReportedChatsView'; +import { SelectReportedUserView } from './views/SelectReportedUserView'; +import { SelectTopicView } from './views/SelectTopicView'; +import { NameChangeView } from './views/name-change/NameChangeView'; + +export const HelpView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const { activeReport = null, setActiveReport = null, report = null } = useHelp(); + + const onClose = () => + { + setActiveReport(null); + setIsVisible(false); + }; + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; + case 'tour': + // todo: launch tour + return; + case 'report': + if((parts.length >= 5) && (parts[2] === 'room')) + { + const roomId = parseInt(parts[3]); + const unknown = unescape(parts.splice(4).join('/')); + //this.reportRoom(roomId, unknown, ""); + } + return; + } + }, + eventUrlPrefix: 'help/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + useEffect(() => + { + if(!activeReport) return; + + setIsVisible(true); + }, [ activeReport ]); + + const CurrentStepView = () => + { + if(activeReport) + { + switch(activeReport.currentStep) + { + case ReportState.SELECT_USER: + return ; + case ReportState.SELECT_CHATS: + return ; + case ReportState.SELECT_TOPICS: + return ; + case ReportState.INPUT_REPORT_MESSAGE: + return ; + case ReportState.REPORT_SUMMARY: + return ; + } + } + + return ; + }; + + return ( + <> + { isVisible && + + + + + +
+ + + + + + + } + + + + ); +}; diff --git a/src/components/help/views/DescribeReportView.tsx b/src/components/help/views/DescribeReportView.tsx new file mode 100644 index 0000000..1a4360b --- /dev/null +++ b/src/components/help/views/DescribeReportView.tsx @@ -0,0 +1,48 @@ +import { FC, useState } from 'react'; +import { LocalizeText, ReportState, ReportType } from '../../../api'; +import { Button, Flex, Text } from '../../../common'; +import { useHelp } from '../../../hooks'; + +export const DescribeReportView: FC<{}> = props => +{ + const [ message, setMessage ] = useState(''); + const { activeReport = null, setActiveReport = null } = useHelp(); + + const submitMessage = () => + { + if(message.length < 15) return; + + setActiveReport(prevValue => + { + const currentStep = ReportState.REPORT_SUMMARY; + + return { ...prevValue, message, currentStep }; + }); + }; + + const back = () => + { + setActiveReport(prevValue => + { + return { ...prevValue, currentStep: (prevValue.currentStep - 1) }; + }); + }; + + return ( + <> +
+ { LocalizeText('help.emergency.chat_report.subtitle') } + { LocalizeText('help.cfh.input.text') } +
+ +
+ + +
+ + + ); +}; diff --git a/src/components/mod-tools/views/tickets/CfhChatlogView.tsx b/src/components/mod-tools/views/tickets/CfhChatlogView.tsx new file mode 100644 index 0000000..9923fa9 --- /dev/null +++ b/src/components/mod-tools/views/tickets/CfhChatlogView.tsx @@ -0,0 +1,41 @@ +import { CfhChatlogData, CfhChatlogEvent, GetCfhChatlogMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { SendMessageComposer } from '../../../../api'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { useMessageEvent } from '../../../../hooks'; +import { ChatlogView } from '../chatlog/ChatlogView'; + +interface CfhChatlogViewProps +{ + issueId: number; + onCloseClick(): void; +} + +export const CfhChatlogView: FC = props => +{ + const { onCloseClick = null, issueId = null } = props; + const [ chatlogData, setChatlogData ] = useState(null); + + useMessageEvent(CfhChatlogEvent, event => + { + const parser = event.getParser(); + + if(!parser || parser.data.issueId !== issueId) return; + + setChatlogData(parser.data); + }); + + useEffect(() => + { + SendMessageComposer(new GetCfhChatlogMessageComposer(issueId)); + }, [ issueId ]); + + return ( + + + + { chatlogData && } + + + ); +}; diff --git a/src/components/mod-tools/views/tickets/ModToolsIssueInfoView.tsx b/src/components/mod-tools/views/tickets/ModToolsIssueInfoView.tsx new file mode 100644 index 0000000..7444a73 --- /dev/null +++ b/src/components/mod-tools/views/tickets/ModToolsIssueInfoView.tsx @@ -0,0 +1,86 @@ +import { CloseIssuesMessageComposer, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { GetIssueCategoryName, LocalizeText, SendMessageComposer } from '../../../../api'; +import { Button, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { useModTools } from '../../../../hooks'; +import { CfhChatlogView } from './CfhChatlogView'; + +interface IssueInfoViewProps +{ + issueId: number; + onIssueInfoClosed(issueId: number): void; +} + +export const ModToolsIssueInfoView: FC = props => +{ + const { issueId = null, onIssueInfoClosed = null } = props; + const [ cfhChatlogOpen, setcfhChatlogOpen ] = useState(false); + const { tickets = [], openUserInfo = null } = useModTools(); + const ticket = tickets.find(issue => (issue.issueId === issueId)); + + const releaseIssue = (issueId: number) => + { + SendMessageComposer(new ReleaseIssuesMessageComposer([ issueId ])); + + onIssueInfoClosed(issueId); + }; + + const closeIssue = (resolutionType: number) => + { + SendMessageComposer(new CloseIssuesMessageComposer([ issueId ], resolutionType)); + + onIssueInfoClosed(issueId); + }; + + return ( + <> + + onIssueInfoClosed(issueId) } /> + + Issue Information + + + + + + + + + + + + + + + + + + + + + + + + + +
Source{ GetIssueCategoryName(ticket.categoryId) }
Category{ LocalizeText('help.cfh.topic.' + ticket.reportedCategoryId) }
Description{ ticket.message }
Caller + openUserInfo(ticket.reporterUserId) }>{ ticket.reporterUserName } +
Reported User + openUserInfo(ticket.reportedUserId) }>{ ticket.reportedUserName } +
+
+ + + + + + + +
+
+
+ { cfhChatlogOpen && + setcfhChatlogOpen(false) }/> } + + ); +}; diff --git a/src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx b/src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx new file mode 100644 index 0000000..a8de00e --- /dev/null +++ b/src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx @@ -0,0 +1,47 @@ +import { IssueMessageData, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { SendMessageComposer } from '../../../../api'; +import { Button, Column, Grid } from '../../../../common'; + +interface ModToolsMyIssuesTabViewProps +{ + myIssues: IssueMessageData[]; + handleIssue: (issueId: number) => void; +} + +export const ModToolsMyIssuesTabView: FC = props => +{ + const { myIssues = null, handleIssue = null } = props; + + return ( + + + +
Type
+
Room/Player
+
Opened
+
+
+
+
+ + { myIssues && (myIssues.length > 0) && myIssues.map(issue => + { + return ( + +
{ issue.categoryId }
+
{ issue.reportedUserName }
+
{ new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() }
+
+ +
+
+ +
+
+ ); + }) } +
+
+ ); +}; diff --git a/src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx b/src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx new file mode 100644 index 0000000..17e4901 --- /dev/null +++ b/src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx @@ -0,0 +1,42 @@ +import { IssueMessageData, PickIssuesMessageComposer } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { SendMessageComposer } from '../../../../api'; +import { Button, Column, Grid } from '../../../../common'; + +interface ModToolsOpenIssuesTabViewProps +{ + openIssues: IssueMessageData[]; +} + +export const ModToolsOpenIssuesTabView: FC = props => +{ + const { openIssues = null } = props; + + return ( + + + +
Type
+
Room/Player
+
Opened
+
+
+
+ + { openIssues && (openIssues.length > 0) && openIssues.map(issue => + { + return ( + +
{ issue.categoryId }
+
{ issue.reportedUserName }
+
{ new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() }
+
+ +
+
+ ); + }) } +
+
+ ); +}; diff --git a/src/components/mod-tools/views/tickets/ModToolsPickedIssuesTabView.tsx b/src/components/mod-tools/views/tickets/ModToolsPickedIssuesTabView.tsx new file mode 100644 index 0000000..ca6003e --- /dev/null +++ b/src/components/mod-tools/views/tickets/ModToolsPickedIssuesTabView.tsx @@ -0,0 +1,39 @@ +import { IssueMessageData } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { Column, Grid } from '../../../../common'; + +interface ModToolsPickedIssuesTabViewProps +{ + pickedIssues: IssueMessageData[]; +} + +export const ModToolsPickedIssuesTabView: FC = props => +{ + const { pickedIssues = null } = props; + + return ( + + + +
Type
+
Room/Player
+
Opened
+
Picker
+
+
+ + { pickedIssues && (pickedIssues.length > 0) && pickedIssues.map(issue => + { + return ( + +
{ issue.categoryId }
+
{ issue.reportedUserName }
+
{ new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() }
+
{ issue.pickerUserName }
+
+ ); + }) } +
+
+ ); +}; diff --git a/src/components/mod-tools/views/tickets/ModToolsTicketsView.tsx b/src/components/mod-tools/views/tickets/ModToolsTicketsView.tsx new file mode 100644 index 0000000..ab7ac35 --- /dev/null +++ b/src/components/mod-tools/views/tickets/ModToolsTicketsView.tsx @@ -0,0 +1,90 @@ +import { GetSessionDataManager, IssueMessageData } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../common'; +import { useModTools } from '../../../../hooks'; +import { ModToolsIssueInfoView } from './ModToolsIssueInfoView'; +import { ModToolsMyIssuesTabView } from './ModToolsMyIssuesTabView'; +import { ModToolsOpenIssuesTabView } from './ModToolsOpenIssuesTabView'; +import { ModToolsPickedIssuesTabView } from './ModToolsPickedIssuesTabView'; + +interface ModToolsTicketsViewProps +{ + onCloseClick: () => void; +} + +const TABS: string[] = [ + 'Open Issues', + 'My Issues', + 'Picked Issues' +]; + +export const ModToolsTicketsView: FC = props => +{ + const { onCloseClick = null } = props; + const [ currentTab, setCurrentTab ] = useState(0); + const [ issueInfoWindows, setIssueInfoWindows ] = useState([]); + const { tickets = [] } = useModTools(); + + const openIssues = tickets.filter(issue => issue.state === IssueMessageData.STATE_OPEN); + const myIssues = tickets.filter(issue => (issue.state === IssueMessageData.STATE_PICKED) && (issue.pickerUserId === GetSessionDataManager().userId)); + const pickedIssues = tickets.filter(issue => issue.state === IssueMessageData.STATE_PICKED); + + const closeIssue = (issueId: number) => + { + setIssueInfoWindows(prevValue => + { + const newValue = [ ...prevValue ]; + const existingIndex = newValue.indexOf(issueId); + + if(existingIndex >= 0) newValue.splice(existingIndex, 1); + + return newValue; + }); + }; + + const handleIssue = (issueId: number) => + { + setIssueInfoWindows(prevValue => + { + const newValue = [ ...prevValue ]; + const existingIndex = newValue.indexOf(issueId); + + if(existingIndex === -1) newValue.push(issueId); + else newValue.splice(existingIndex, 1); + + return newValue; + }); + }; + + const CurrentTabComponent = () => + { + switch(currentTab) + { + case 0: return ; + case 1: return ; + case 2: return ; + } + + return null; + }; + + return ( + <> + + + + { TABS.map((tab, index) => + { + return ( setCurrentTab(index) }> + { tab } + ); + }) } + + + + + + { issueInfoWindows && (issueInfoWindows.length > 0) && issueInfoWindows.map(issueId => ) } + + ); +}; diff --git a/src/components/mod-tools/views/user/ModToolsUserChatlogView.tsx b/src/components/mod-tools/views/user/ModToolsUserChatlogView.tsx new file mode 100644 index 0000000..acae308 --- /dev/null +++ b/src/components/mod-tools/views/user/ModToolsUserChatlogView.tsx @@ -0,0 +1,44 @@ +import { ChatRecordData, GetUserChatlogMessageComposer, UserChatlogEvent } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { SendMessageComposer } from '../../../../api'; +import { DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { useMessageEvent } from '../../../../hooks'; +import { ChatlogView } from '../chatlog/ChatlogView'; + +interface ModToolsUserChatlogViewProps +{ + userId: number; + onCloseClick: () => void; +} + +export const ModToolsUserChatlogView: FC = props => +{ + const { userId = null, onCloseClick = null } = props; + const [ userChatlog, setUserChatlog ] = useState(null); + const [ username, setUsername ] = useState(null); + + useMessageEvent(UserChatlogEvent, event => + { + const parser = event.getParser(); + + if(!parser || parser.data.userId !== userId) return; + + setUsername(parser.data.username); + setUserChatlog(parser.data.roomChatlogs); + }); + + useEffect(() => + { + SendMessageComposer(new GetUserChatlogMessageComposer(userId)); + }, [ userId ]); + + return ( + + + + { userChatlog && + } + + + ); +}; diff --git a/src/components/mod-tools/views/user/ModToolsUserModActionView.tsx b/src/components/mod-tools/views/user/ModToolsUserModActionView.tsx new file mode 100644 index 0000000..2dcdd3e --- /dev/null +++ b/src/components/mod-tools/views/user/ModToolsUserModActionView.tsx @@ -0,0 +1,176 @@ +import { CallForHelpTopicData, DefaultSanctionMessageComposer, ModAlertMessageComposer, ModBanMessageComposer, ModKickMessageComposer, ModMessageMessageComposer, ModMuteMessageComposer, ModTradingLockMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useMemo, useState } from 'react'; +import { ISelectedUser, LocalizeText, ModActionDefinition, NotificationAlertType, SendMessageComposer } from '../../../../api'; +import { Button, DraggableWindowPosition, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { useModTools, useNotification } from '../../../../hooks'; + +interface ModToolsUserModActionViewProps +{ + user: ISelectedUser; + onCloseClick: () => void; +} + +const MOD_ACTION_DEFINITIONS = [ + new ModActionDefinition(1, 'Alert', ModActionDefinition.ALERT, 1, 0), + new ModActionDefinition(2, 'Mute 1h', ModActionDefinition.MUTE, 2, 0), + new ModActionDefinition(3, 'Ban 18h', ModActionDefinition.BAN, 3, 0), + new ModActionDefinition(4, 'Ban 7 days', ModActionDefinition.BAN, 4, 0), + new ModActionDefinition(5, 'Ban 30 days (step 1)', ModActionDefinition.BAN, 5, 0), + new ModActionDefinition(7, 'Ban 30 days (step 2)', ModActionDefinition.BAN, 7, 0), + new ModActionDefinition(6, 'Ban 100 years', ModActionDefinition.BAN, 6, 0), + new ModActionDefinition(106, 'Ban avatar-only 100 years', ModActionDefinition.BAN, 6, 0), + new ModActionDefinition(101, 'Kick', ModActionDefinition.KICK, 0, 0), + new ModActionDefinition(102, 'Lock trade 1 week', ModActionDefinition.TRADE_LOCK, 0, 168), + new ModActionDefinition(104, 'Lock trade permanent', ModActionDefinition.TRADE_LOCK, 0, 876000), + new ModActionDefinition(105, 'Message', ModActionDefinition.MESSAGE, 0, 0), +]; + +export const ModToolsUserModActionView: FC = props => +{ + const { user = null, onCloseClick = null } = props; + const [ selectedTopic, setSelectedTopic ] = useState(-1); + const [ selectedAction, setSelectedAction ] = useState(-1); + const [ message, setMessage ] = useState(''); + const { cfhCategories = null, settings = null } = useModTools(); + const { simpleAlert = null } = useNotification(); + + const topics = useMemo(() => + { + const values: CallForHelpTopicData[] = []; + + if(cfhCategories && cfhCategories.length) + { + for(const category of cfhCategories) + { + for(const topic of category.topics) values.push(topic); + } + } + + return values; + }, [ cfhCategories ]); + + const sendAlert = (message: string) => simpleAlert(message, NotificationAlertType.DEFAULT, null, null, 'Error'); + + const sendDefaultSanction = () => + { + let errorMessage: string = null; + + const category = topics[selectedTopic]; + + if(selectedTopic === -1) errorMessage = 'You must select a CFH topic'; + + if(errorMessage) return sendAlert(errorMessage); + + const messageOrDefault = (message.trim().length === 0) ? LocalizeText(`help.cfh.topic.${ category.id }`) : message; + + SendMessageComposer(new DefaultSanctionMessageComposer(user.userId, selectedTopic, messageOrDefault)); + + onCloseClick(); + }; + + const sendSanction = () => + { + let errorMessage: string = null; + + const category = topics[selectedTopic]; + const sanction = MOD_ACTION_DEFINITIONS[selectedAction]; + + if((selectedTopic === -1) || (selectedAction === -1)) errorMessage = 'You must select a CFH topic and Sanction'; + else if(!settings || !settings.cfhPermission) errorMessage = 'You do not have permission to do this'; + else if(!category) errorMessage = 'You must select a CFH topic'; + else if(!sanction) errorMessage = 'You must select a sanction'; + + if(errorMessage) + { + sendAlert(errorMessage); + + return; + } + + const messageOrDefault = (message.trim().length === 0) ? LocalizeText(`help.cfh.topic.${ category.id }`) : message; + + switch(sanction.actionType) + { + case ModActionDefinition.ALERT: { + if(!settings.alertPermission) + { + sendAlert('You have insufficient permissions'); + + return; + } + + SendMessageComposer(new ModAlertMessageComposer(user.userId, messageOrDefault, category.id)); + break; + } + case ModActionDefinition.MUTE: + SendMessageComposer(new ModMuteMessageComposer(user.userId, messageOrDefault, category.id)); + break; + case ModActionDefinition.BAN: { + if(!settings.banPermission) + { + sendAlert('You have insufficient permissions'); + + return; + } + + SendMessageComposer(new ModBanMessageComposer(user.userId, messageOrDefault, category.id, selectedAction, (sanction.actionId === 106))); + break; + } + case ModActionDefinition.KICK: { + if(!settings.kickPermission) + { + sendAlert('You have insufficient permissions'); + return; + } + + SendMessageComposer(new ModKickMessageComposer(user.userId, messageOrDefault, category.id)); + break; + } + case ModActionDefinition.TRADE_LOCK: { + const numSeconds = (sanction.actionLengthHours * 60); + + SendMessageComposer(new ModTradingLockMessageComposer(user.userId, messageOrDefault, numSeconds, category.id)); + break; + } + case ModActionDefinition.MESSAGE: { + if(message.trim().length === 0) + { + sendAlert('Please write a message to user'); + + return; + } + + SendMessageComposer(new ModMessageMessageComposer(user.userId, message, category.id)); + break; + } + } + + onCloseClick(); + }; + + if(!user) return null; + + return ( + + onCloseClick() } /> + + + +
+ Optional message type, overrides default + + + + + ); +}; diff --git a/src/components/mod-tools/views/user/ModToolsUserView.tsx b/src/components/mod-tools/views/user/ModToolsUserView.tsx new file mode 100644 index 0000000..6f65700 --- /dev/null +++ b/src/components/mod-tools/views/user/ModToolsUserView.tsx @@ -0,0 +1,156 @@ +import { CreateLinkEvent, GetModeratorUserInfoMessageComposer, ModeratorUserInfoData, ModeratorUserInfoEvent } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useMemo, useState } from 'react'; +import { FriendlyTime, LocalizeText, SendMessageComposer } from '../../../../api'; +import { Button, Column, DraggableWindowPosition, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { useMessageEvent } from '../../../../hooks'; +import { ModToolsUserModActionView } from './ModToolsUserModActionView'; +import { ModToolsUserRoomVisitsView } from './ModToolsUserRoomVisitsView'; +import { ModToolsUserSendMessageView } from './ModToolsUserSendMessageView'; + +interface ModToolsUserViewProps +{ + userId: number; + onCloseClick: () => void; +} + +export const ModToolsUserView: FC = props => +{ + const { onCloseClick = null, userId = null } = props; + const [ userInfo, setUserInfo ] = useState(null); + const [ sendMessageVisible, setSendMessageVisible ] = useState(false); + const [ modActionVisible, setModActionVisible ] = useState(false); + const [ roomVisitsVisible, setRoomVisitsVisible ] = useState(false); + + const userProperties = useMemo(() => + { + if(!userInfo) return null; + + return [ + { + localeKey: 'modtools.userinfo.userName', + value: userInfo.userName, + showOnline: true + }, + { + localeKey: 'modtools.userinfo.cfhCount', + value: userInfo.cfhCount.toString() + }, + { + localeKey: 'modtools.userinfo.abusiveCfhCount', + value: userInfo.abusiveCfhCount.toString() + }, + { + localeKey: 'modtools.userinfo.cautionCount', + value: userInfo.cautionCount.toString() + }, + { + localeKey: 'modtools.userinfo.banCount', + value: userInfo.banCount.toString() + }, + { + localeKey: 'modtools.userinfo.lastSanctionTime', + value: userInfo.lastSanctionTime + }, + { + localeKey: 'modtools.userinfo.tradingLockCount', + value: userInfo.tradingLockCount.toString() + }, + { + localeKey: 'modtools.userinfo.tradingExpiryDate', + value: userInfo.tradingExpiryDate + }, + { + localeKey: 'modtools.userinfo.minutesSinceLastLogin', + value: FriendlyTime.format(userInfo.minutesSinceLastLogin * 60, '.ago', 2) + }, + { + localeKey: 'modtools.userinfo.lastPurchaseDate', + value: userInfo.lastPurchaseDate + }, + { + localeKey: 'modtools.userinfo.primaryEmailAddress', + value: userInfo.primaryEmailAddress + }, + { + localeKey: 'modtools.userinfo.identityRelatedBanCount', + value: userInfo.identityRelatedBanCount.toString() + }, + { + localeKey: 'modtools.userinfo.registrationAgeInMinutes', + value: FriendlyTime.format(userInfo.registrationAgeInMinutes * 60, '.ago', 2) + }, + { + localeKey: 'modtools.userinfo.userClassification', + value: userInfo.userClassification + } + ]; + }, [ userInfo ]); + + useMessageEvent(ModeratorUserInfoEvent, event => + { + const parser = event.getParser(); + + if(!parser || parser.data.userId !== userId) return; + + setUserInfo(parser.data); + }); + + useEffect(() => + { + SendMessageComposer(new GetModeratorUserInfoMessageComposer(userId)); + }, [ userId ]); + + if(!userInfo) return null; + + return ( + <> + + onCloseClick() } /> + + + + + + { userProperties.map( (property, index) => + { + + return ( + + + + + ); + }) } + +
{ LocalizeText(property.localeKey) } + { property.value } + { property.showOnline && + } +
+
+ + + + + + +
+
+
+ { sendMessageVisible && + setSendMessageVisible(false) } /> } + { modActionVisible && + setModActionVisible(false) } /> } + { roomVisitsVisible && + setRoomVisitsVisible(false) } /> } + + ); +}; diff --git a/src/components/navigator/NavigatorView.tsx b/src/components/navigator/NavigatorView.tsx new file mode 100644 index 0000000..813c390 --- /dev/null +++ b/src/components/navigator/NavigatorView.tsx @@ -0,0 +1,240 @@ +import { NitroCard } from '@layout/NitroCard'; +import { AddLinkEventTracker, ConvertGlobalRoomIdMessageComposer, HabboWebTools, ILinkEventTracker, LegacyExternalInterface, NavigatorInitComposer, NavigatorSearchComposer, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useRef, useState } from 'react'; +import { FaPlus } from 'react-icons/fa'; +import { LocalizeText, SendMessageComposer, TryVisitRoom } from '../../api'; +import { useNavigator, useNitroEvent } from '../../hooks'; +import { NavigatorDoorStateView } from './views/NavigatorDoorStateView'; +import { NavigatorRoomCreatorView } from './views/NavigatorRoomCreatorView'; +import { NavigatorRoomInfoView } from './views/NavigatorRoomInfoView'; +import { NavigatorRoomLinkView } from './views/NavigatorRoomLinkView'; +import { NavigatorRoomSettingsView } from './views/room-settings/NavigatorRoomSettingsView'; +import { NavigatorSearchResultView } from './views/search/NavigatorSearchResultView'; +import { NavigatorSearchView } from './views/search/NavigatorSearchView'; + +export const NavigatorView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ isReady, setIsReady ] = useState(false); + const [ isCreatorOpen, setCreatorOpen ] = useState(false); + const [ isRoomInfoOpen, setRoomInfoOpen ] = useState(false); + const [ isRoomLinkOpen, setRoomLinkOpen ] = useState(false); + const [ isLoading, setIsLoading ] = useState(false); + const [ needsInit, setNeedsInit ] = useState(true); + const [ needsSearch, setNeedsSearch ] = useState(false); + const { searchResult = null, topLevelContext = null, topLevelContexts = null, navigatorData = null } = useNavigator(); + const pendingSearch = useRef<{ value: string, code: string }>(null); + const elementRef = useRef(); + + useNitroEvent(RoomSessionEvent.CREATED, event => + { + setIsVisible(false); + setCreatorOpen(false); + }); + + const sendSearch = useCallback((searchValue: string, contextCode: string) => + { + setCreatorOpen(false); + + SendMessageComposer(new NavigatorSearchComposer(contextCode, searchValue)); + + setIsLoading(true); + }, []); + + const reloadCurrentSearch = useCallback(() => + { + if(!isReady) + { + setNeedsSearch(true); + + return; + } + + if(pendingSearch.current) + { + sendSearch(pendingSearch.current.value, pendingSearch.current.code); + + pendingSearch.current = null; + + return; + } + + if(searchResult) + { + sendSearch(searchResult.data, searchResult.code); + + return; + } + + if(!topLevelContext) return; + + sendSearch('', topLevelContext.code); + }, [ isReady, searchResult, topLevelContext, sendSearch ]); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': { + setIsVisible(true); + setNeedsSearch(true); + return; + } + case 'hide': + setIsVisible(false); + return; + case 'toggle': { + if(isVisible) + { + setIsVisible(false); + + return; + } + + setIsVisible(true); + setNeedsSearch(true); + return; + } + case 'toggle-room-info': + setRoomInfoOpen(value => !value); + return; + case 'toggle-room-link': + setRoomLinkOpen(value => !value); + return; + case 'goto': + if(parts.length <= 2) return; + + switch(parts[2]) + { + case 'home': + if(navigatorData.homeRoomId <= 0) return; + + TryVisitRoom(navigatorData.homeRoomId); + break; + default: { + const roomId = parseInt(parts[2]); + + TryVisitRoom(roomId); + } + } + return; + case 'create': + setIsVisible(true); + setCreatorOpen(true); + return; + case 'search': + if(parts.length > 2) + { + const topLevelContextCode = parts[2]; + + let searchValue = ''; + + if(parts.length > 3) searchValue = parts[3]; + + pendingSearch.current = { value: searchValue, code: topLevelContextCode }; + + setIsVisible(true); + setNeedsSearch(true); + } + return; + } + }, + eventUrlPrefix: 'navigator/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, [ isVisible, navigatorData ]); + + useEffect(() => + { + if(!searchResult) return; + + setIsLoading(false); + + if(elementRef && elementRef.current) elementRef.current.scrollTop = 0; + }, [ searchResult ]); + + useEffect(() => + { + if(!isVisible || !isReady || !needsSearch) return; + + reloadCurrentSearch(); + + setNeedsSearch(false); + }, [ isVisible, isReady, needsSearch, reloadCurrentSearch ]); + + useEffect(() => + { + if(isReady || !topLevelContext) return; + + setIsReady(true); + }, [ isReady, topLevelContext ]); + + useEffect(() => + { + if(!isVisible || !needsInit) return; + + SendMessageComposer(new NavigatorInitComposer()); + + setNeedsInit(false); + }, [ isVisible, needsInit ]); + + useEffect(() => + { + LegacyExternalInterface.addCallback(HabboWebTools.OPENROOM, (k: string, _arg_2: boolean = false, _arg_3: string = null) => SendMessageComposer(new ConvertGlobalRoomIdMessageComposer(k))); + }, []); + + return ( + <> + { isVisible && + + setIsVisible(false) } /> + + { topLevelContexts && (topLevelContexts.length > 0) && topLevelContexts.map((context, index) => + { + return ( + sendSearch('', context.code) }> + { LocalizeText(('navigator.toplevelview.' + context.code)) } + + ); + }) } + setCreatorOpen(true) }> + + + + + { !isCreatorOpen && + <> + +
+ { (searchResult && searchResult.results.map((result, index) => )) } +
+ } + { isCreatorOpen && } +
+
} + + { isRoomInfoOpen && setRoomInfoOpen(false) } /> } + { isRoomLinkOpen && setRoomLinkOpen(false) } /> } + + + ); +}; diff --git a/src/components/navigator/views/NavigatorDoorStateView.tsx b/src/components/navigator/views/NavigatorDoorStateView.tsx new file mode 100644 index 0000000..0dcaa45 --- /dev/null +++ b/src/components/navigator/views/NavigatorDoorStateView.tsx @@ -0,0 +1,111 @@ +import { FC, useEffect, useState } from 'react'; +import { CreateRoomSession, DoorStateType, GoToDesktop, LocalizeText } from '../../../api'; +import { Button, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; +import { useNavigator } from '../../../hooks'; +import { NitroInput } from '../../../layout'; + +const VISIBLE_STATES = [ DoorStateType.START_DOORBELL, DoorStateType.STATE_WAITING, DoorStateType.STATE_NO_ANSWER, DoorStateType.START_PASSWORD, DoorStateType.STATE_WRONG_PASSWORD ]; +const DOORBELL_STATES = [ DoorStateType.START_DOORBELL, DoorStateType.STATE_WAITING, DoorStateType.STATE_NO_ANSWER ]; +const PASSWORD_STATES = [ DoorStateType.START_PASSWORD, DoorStateType.STATE_WRONG_PASSWORD ]; + +export const NavigatorDoorStateView: FC<{}> = props => +{ + const [ password, setPassword ] = useState(''); + const { doorData = null, setDoorData = null } = useNavigator(); + + const onClose = () => + { + if(doorData && (doorData.state === DoorStateType.STATE_WAITING)) GoToDesktop(); + + setDoorData(null); + }; + + const ring = () => + { + if(!doorData || !doorData.roomInfo) return; + + CreateRoomSession(doorData.roomInfo.roomId); + + setDoorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.state = DoorStateType.STATE_PENDING_SERVER; + + return newValue; + }); + }; + + const tryEntering = () => + { + if(!doorData || !doorData.roomInfo) return; + + CreateRoomSession(doorData.roomInfo.roomId, password); + + setDoorData(prevValue => + { + const newValue = { ...prevValue }; + + newValue.state = DoorStateType.STATE_PENDING_SERVER; + + return newValue; + }); + }; + + useEffect(() => + { + if(!doorData || (doorData.state !== DoorStateType.STATE_NO_ANSWER)) return; + + GoToDesktop(); + }, [ doorData ]); + + if(!doorData || (doorData.state === DoorStateType.NONE) || (VISIBLE_STATES.indexOf(doorData.state) === -1)) return null; + + const isDoorbell = (DOORBELL_STATES.indexOf(doorData.state) >= 0); + + return ( + + + +
+ { doorData && doorData.roomInfo && doorData.roomInfo.roomName } + { (doorData.state === DoorStateType.START_DOORBELL) && + { LocalizeText('navigator.doorbell.info') } } + { (doorData.state === DoorStateType.STATE_WAITING) && + { LocalizeText('navigator.doorbell.waiting') } } + { (doorData.state === DoorStateType.STATE_NO_ANSWER) && + { LocalizeText('navigator.doorbell.no.answer') } } + { (doorData.state === DoorStateType.START_PASSWORD) && + { LocalizeText('navigator.password.info') } } + { (doorData.state === DoorStateType.STATE_WRONG_PASSWORD) && + { LocalizeText('navigator.password.retryinfo') } } +
+ { isDoorbell && +
+ { (doorData.state === DoorStateType.START_DOORBELL) && + } + +
} + { !isDoorbell && + <> +
+ { LocalizeText('navigator.password.enter') } + setPassword(event.target.value) } /> +
+
+ + +
+ } +
+
+ ); +}; diff --git a/src/components/navigator/views/NavigatorRoomCreatorView.tsx b/src/components/navigator/views/NavigatorRoomCreatorView.tsx new file mode 100644 index 0000000..8469af3 --- /dev/null +++ b/src/components/navigator/views/NavigatorRoomCreatorView.tsx @@ -0,0 +1,123 @@ + +import { CreateFlatMessageComposer, HabboClubLevelEnum } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { GetClubMemberLevel, GetConfigurationValue, IRoomModel, LocalizeText, SendMessageComposer } from '../../../api'; +import { Button, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, Text } from '../../../common'; +import { useNavigator } from '../../../hooks'; +import { NitroInput } from '../../../layout'; + +export const NavigatorRoomCreatorView: FC<{}> = props => +{ + const [ maxVisitorsList, setMaxVisitorsList ] = useState(null); + const [ name, setName ] = useState(null); + const [ description, setDescription ] = useState(null); + const [ category, setCategory ] = useState(null); + const [ visitorsCount, setVisitorsCount ] = useState(null); + const [ tradesSetting, setTradesSetting ] = useState(0); + const [ roomModels, setRoomModels ] = useState([]); + const [ selectedModelName, setSelectedModelName ] = useState(''); + const { categories = null } = useNavigator(); + + const hcDisabled = GetConfigurationValue('hc.disabled', false); + + const getRoomModelImage = (name: string) => GetConfigurationValue('images.url') + `/navigator/models/model_${ name }.png`; + + const selectModel = (model: IRoomModel, index: number) => + { + if(!model || (model.clubLevel > GetClubMemberLevel())) return; + + setSelectedModelName(roomModels[index].name); + }; + + const createRoom = () => + { + SendMessageComposer(new CreateFlatMessageComposer(name, description, 'model_' + selectedModelName, Number(category), Number(visitorsCount), tradesSetting)); + }; + + useEffect(() => + { + if(!maxVisitorsList) + { + const list = []; + + for(let i = 10; i <= 100; i = i + 10) list.push(i); + + setMaxVisitorsList(list); + setVisitorsCount(list[0]); + } + }, [ maxVisitorsList ]); + + useEffect(() => + { + if(categories && categories.length) setCategory(categories[0].id); + }, [ categories ]); + + useEffect(() => + { + const models = GetConfigurationValue('navigator.room.models'); + + if(models && models.length) + { + setRoomModels(models); + setSelectedModelName(models[0].name); + } + }, []); + + return ( +
+ +
+
+ { LocalizeText('navigator.createroom.roomnameinfo') } + setName(event.target.value) } /> +
+
+ { LocalizeText('navigator.createroom.roomdescinfo') } + +
+
+ + ); +}; diff --git a/src/components/room/widgets/furniture/FurnitureStackHeightView.tsx b/src/components/room/widgets/furniture/FurnitureStackHeightView.tsx new file mode 100644 index 0000000..741a35e --- /dev/null +++ b/src/components/room/widgets/furniture/FurnitureStackHeightView.tsx @@ -0,0 +1,58 @@ +import { FurnitureStackHeightComposer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { LocalizeText, SendMessageComposer } from '../../../../api'; +import { Button, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { useFurnitureStackHeightWidget } from '../../../../hooks'; + +export const FurnitureStackHeightView: FC<{}> = props => +{ + const { objectId = -1, height = 0, maxHeight = 40, onClose = null, updateHeight = null } = useFurnitureStackHeightWidget(); + const [ tempHeight, setTempHeight ] = useState(''); + + const updateTempHeight = (value: string) => + { + setTempHeight(value); + + const newValue = parseFloat(value); + + if(isNaN(newValue) || (newValue === height)) return; + + updateHeight(newValue); + }; + + useEffect(() => + { + setTempHeight(height.toString()); + }, [ height ]); + + if(objectId === -1) return null; + + return ( + + + + { LocalizeText('widget.custom.stack.height.text') } +
+
{ state.valueNow }
} + step={ 0.01 } + value={ height } + onChange={ event => updateHeight(event) } /> + updateTempHeight(event.target.value) } /> +
+
+ + +
+
+
+ ); +}; diff --git a/src/components/room/widgets/furniture/FurnitureStickieView.tsx b/src/components/room/widgets/furniture/FurnitureStickieView.tsx new file mode 100644 index 0000000..fec4845 --- /dev/null +++ b/src/components/room/widgets/furniture/FurnitureStickieView.tsx @@ -0,0 +1,66 @@ +import { FC, useEffect, useState } from 'react'; +import { ColorUtils } from '../../../../api'; +import { DraggableWindow, DraggableWindowPosition } from '../../../../common'; +import { useFurnitureStickieWidget } from '../../../../hooks'; + +const STICKIE_COLORS = [ '9CCEFF', 'FF9CFF', '9CFF9C', 'FFFF33' ]; +const STICKIE_COLOR_NAMES = [ 'blue', 'pink', 'green', 'yellow' ]; +const STICKIE_TYPES = [ 'post_it', 'post_it_shakesp', 'post_it_dreams', 'post_it_xmas', 'post_it_vd', 'post_it_juninas' ]; +const STICKIE_TYPE_NAMES = [ 'post_it', 'shakesp', 'dreams', 'christmas', 'heart', 'juninas' ]; + +const getStickieColorName = (color: string) => +{ + let index = STICKIE_COLORS.indexOf(color); + + if(index === -1) index = 0; + + return STICKIE_COLOR_NAMES[index]; +}; + +const getStickieTypeName = (type: string) => +{ + let index = STICKIE_TYPES.indexOf(type); + + if(index === -1) index = 0; + + return STICKIE_TYPE_NAMES[index]; +}; + +export const FurnitureStickieView: FC<{}> = props => +{ + const { objectId = -1, color = '0', text = '', type = '', canModify = false, updateColor = null, updateText = null, trash = null, onClose = null } = useFurnitureStickieWidget(); + const [ isEditing, setIsEditing ] = useState(false); + + useEffect(() => + { + setIsEditing(false); + }, [ objectId, color, text, type ]); + + if(objectId === -1) return null; + + return ( + +
+
+
+ { canModify && + <> +
+ { type == 'post_it' && + <> + { STICKIE_COLORS.map(color => + { + return
updateColor(color) } />; + }) } + } + } +
+
+
+
+ { (!isEditing || !canModify) ?
(canModify && setIsEditing(true)) }>{ text }
: } +
+
+ + ); +}; diff --git a/src/components/room/widgets/furniture/FurnitureTrophyView.tsx b/src/components/room/widgets/furniture/FurnitureTrophyView.tsx new file mode 100644 index 0000000..2e08af0 --- /dev/null +++ b/src/components/room/widgets/furniture/FurnitureTrophyView.tsx @@ -0,0 +1,12 @@ +import { FC } from 'react'; +import { LayoutTrophyView } from '../../../../common'; +import { useFurnitureTrophyWidget } from '../../../../hooks'; + +export const FurnitureTrophyView: FC<{}> = props => +{ + const { objectId = -1, color = '1', senderName = '', date = '', message = '', onClose = null } = useFurnitureTrophyWidget(); + + if(objectId === -1) return null; + + return ; +}; diff --git a/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx b/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx new file mode 100644 index 0000000..8c6dba2 --- /dev/null +++ b/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx @@ -0,0 +1,47 @@ +import { FC } from 'react'; +import { FurnitureBackgroundColorView } from './FurnitureBackgroundColorView'; +import { FurnitureBadgeDisplayView } from './FurnitureBadgeDisplayView'; +import { FurnitureCraftingView } from './FurnitureCraftingView'; +import { FurnitureDimmerView } from './FurnitureDimmerView'; +import { FurnitureExchangeCreditView } from './FurnitureExchangeCreditView'; +import { FurnitureExternalImageView } from './FurnitureExternalImageView'; +import { FurnitureFriendFurniView } from './FurnitureFriendFurniView'; +import { FurnitureGiftOpeningView } from './FurnitureGiftOpeningView'; +import { FurnitureHighScoreView } from './FurnitureHighScoreView'; +import { FurnitureInternalLinkView } from './FurnitureInternalLinkView'; +import { FurnitureMannequinView } from './FurnitureMannequinView'; +import { FurnitureRoomLinkView } from './FurnitureRoomLinkView'; +import { FurnitureSpamWallPostItView } from './FurnitureSpamWallPostItView'; +import { FurnitureStackHeightView } from './FurnitureStackHeightView'; +import { FurnitureStickieView } from './FurnitureStickieView'; +import { FurnitureTrophyView } from './FurnitureTrophyView'; +import { FurnitureYoutubeDisplayView } from './FurnitureYoutubeDisplayView'; +import { FurnitureContextMenuView } from './context-menu/FurnitureContextMenuView'; +import { FurniturePlaylistEditorWidgetView } from './playlist-editor/FurniturePlaylistEditorWidgetView'; + +export const FurnitureWidgetsView: FC<{}> = props => +{ + return ( + <> + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx b/src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx new file mode 100644 index 0000000..0d8dd5e --- /dev/null +++ b/src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx @@ -0,0 +1,109 @@ +import { FC, useEffect, useState } from 'react'; +import YouTube, { Options } from 'react-youtube'; +import { YouTubePlayer } from 'youtube-player/dist/types'; +import { LocalizeText, YoutubeVideoPlaybackStateEnum } from '../../../../api'; +import { AutoGrid, AutoGridProps, LayoutGridItem, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { useFurnitureYoutubeWidget } from '../../../../hooks'; + +interface FurnitureYoutubeDisplayViewProps extends AutoGridProps +{ + +} + +export const FurnitureYoutubeDisplayView: FC<{}> = FurnitureYoutubeDisplayViewProps => +{ + const [ player, setPlayer ] = useState(null); + const { objectId = -1, videoId = null, videoStart = 0, videoEnd = 0, currentVideoState = null, selectedVideo = null, playlists = [], onClose = null, previous = null, next = null, pause = null, play = null, selectVideo = null } = useFurnitureYoutubeWidget(); + + const onStateChange = (event: { target: YouTubePlayer; data: number }) => + { + setPlayer(event.target); + + if(objectId === -1) return; + + switch(event.target.getPlayerState()) + { + case -1: + case 1: + if(currentVideoState === 2) + { + //event.target.pauseVideo(); + } + + if(currentVideoState !== 1) play(); + return; + case 2: + if(currentVideoState !== 2) pause(); + } + }; + + useEffect(() => + { + if((currentVideoState === null) || !player) return; + + if((currentVideoState === YoutubeVideoPlaybackStateEnum.PLAYING) && (player.getPlayerState() !== YoutubeVideoPlaybackStateEnum.PLAYING)) + { + player.playVideo(); + + return; + } + + if((currentVideoState === YoutubeVideoPlaybackStateEnum.PAUSED) && (player.getPlayerState() !== YoutubeVideoPlaybackStateEnum.PAUSED)) + { + player.pauseVideo(); + + return; + } + }, [ currentVideoState, player ]); + + if(objectId === -1) return null; + + const youtubeOptions: Options = { + height: '375', + width: '500', + playerVars: { + autoplay: 1, + disablekb: 1, + controls: 0, + origin: window.origin, + modestbranding: 1, + start: videoStart, + end: videoEnd + } + }; + + return ( + + + +
+
+ { (videoId && videoId.length > 0) && + setPlayer(event.target) } onStateChange={ onStateChange } /> + } + { (!videoId || videoId.length === 0) && +
{ LocalizeText('widget.furni.video_viewer.no_videos') }
+ } +
+
+ + + + +
{ LocalizeText('widget.furni.video_viewer.playlists') }
+ + { playlists && playlists.map((entry, index) => + { + return ( + selectVideo(entry.video) }> + { entry.title } + + ); + }) } + +
+
+
+
+ ); +}; diff --git a/src/components/room/widgets/furniture/context-menu/EffectBoxConfirmView.tsx b/src/components/room/widgets/furniture/context-menu/EffectBoxConfirmView.tsx new file mode 100644 index 0000000..a818152 --- /dev/null +++ b/src/components/room/widgets/furniture/context-menu/EffectBoxConfirmView.tsx @@ -0,0 +1,40 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { Button, Column, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../../common'; +import { useRoom } from '../../../../../hooks'; + +interface EffectBoxConfirmViewProps +{ + objectId: number; + onClose: () => void; +} + +export const EffectBoxConfirmView: FC = props => +{ + const { objectId = -1, onClose = null } = props; + const { roomSession = null } = useRoom(); + + const useProduct = () => + { + roomSession.useMultistateItem(objectId); + + onClose(); + }; + + return ( + + + +
+ + { LocalizeText('effectbox.header.description') } +
+ + +
+
+
+
+
+ ); +}; diff --git a/src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx b/src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx new file mode 100644 index 0000000..7976d99 --- /dev/null +++ b/src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx @@ -0,0 +1,130 @@ +import { ContextMenuEnum, CustomUserNotificationMessageEvent, GetSessionDataManager, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { GetGroupInformation, LocalizeText } from '../../../../../api'; +import { EFFECTBOX_OPEN, GROUP_FURNITURE, MONSTERPLANT_SEED_CONFIRMATION, MYSTERYTROPHY_OPEN_DIALOG, PURCHASABLE_CLOTHING_CONFIRMATION, useFurnitureContextMenuWidget, useMessageEvent, useNotification } from '../../../../../hooks'; +import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView'; +import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView'; +import { ContextMenuView } from '../../context-menu/ContextMenuView'; +import { FurnitureMysteryBoxOpenDialogView } from '../FurnitureMysteryBoxOpenDialogView'; +import { FurnitureMysteryTrophyOpenDialogView } from '../FurnitureMysteryTrophyOpenDialogView'; +import { EffectBoxConfirmView } from './EffectBoxConfirmView'; +import { MonsterPlantSeedConfirmView } from './MonsterPlantSeedConfirmView'; +import { PurchasableClothingConfirmView } from './PurchasableClothingConfirmView'; + +export const FurnitureContextMenuView: FC<{}> = props => +{ + const { closeConfirm = null, processAction = null, onClose = null, objectId = -1, mode = null, confirmMode = null, confirmingObjectId = -1, groupData = null, isGroupMember = false, objectOwnerId = -1 } = useFurnitureContextMenuWidget(); + const { simpleAlert = null } = useNotification(); + + useMessageEvent(CustomUserNotificationMessageEvent, event => + { + const parser = event.getParser(); + + if(!parser) return; + + // HOPPER_NO_COSTUME = 1; HOPPER_NO_HC = 2; GATE_NO_HC = 3; STARS_NOT_CANDIDATE = 4 (not coded in Emulator); STARS_NOT_ENOUGH_USERS = 5 (not coded in Emulator); + + switch(parser.count) + { + case 1: + simpleAlert(LocalizeText('costumehopper.costumerequired.bodytext'), null, 'catalog/open/temporary_effects' , LocalizeText('costumehopper.costumerequired.buy'), LocalizeText('costumehopper.costumerequired.header'), null); + break; + case 2: + simpleAlert(LocalizeText('viphopper.viprequired.bodytext'), null, 'catalog/open/habbo_club' , LocalizeText('viprequired.buy.vip'), LocalizeText('viprequired.header'), null); + break; + case 3: + simpleAlert(LocalizeText('gate.viprequired.bodytext'), null, 'catalog/open/habbo_club' , LocalizeText('viprequired.buy.vip'), LocalizeText('gate.viprequired.title'), null); + break; + } + }); + + const isOwner = GetSessionDataManager().userId === objectOwnerId; + + return ( + <> + { (confirmMode === MONSTERPLANT_SEED_CONFIRMATION) && + } + { (confirmMode === PURCHASABLE_CLOTHING_CONFIRMATION) && + } + { (confirmMode === EFFECTBOX_OPEN) && + } + { (confirmMode === MYSTERYTROPHY_OPEN_DIALOG) && + } + + { (objectId >= 0) && mode && + + { (mode === ContextMenuEnum.FRIEND_FURNITURE) && + <> + + { LocalizeText('friendfurni.context.title') } + + processAction('use_friend_furni') }> + { LocalizeText('friendfurni.context.use') } + + } + { (mode === ContextMenuEnum.MONSTERPLANT_SEED) && + <> + + { LocalizeText('furni.mnstr_seed.name') } + + processAction('use_monsterplant_seed') }> + { LocalizeText('widget.monsterplant_seed.button.use') } + + } + { (mode === ContextMenuEnum.RANDOM_TELEPORT) && + <> + + { LocalizeText('furni.random_teleport.name') } + + processAction('use_random_teleport') }> + { LocalizeText('widget.random_teleport.button.use') } + + } + { (mode === ContextMenuEnum.PURCHASABLE_CLOTHING) && + <> + + { LocalizeText('furni.generic_usable.name') } + + processAction('use_purchaseable_clothing') }> + { LocalizeText('widget.generic_usable.button.use') } + + } + { (mode === ContextMenuEnum.MYSTERY_BOX) && + <> + + { LocalizeText('mysterybox.context.title') } + + processAction('use_mystery_box') }> + { LocalizeText('mysterybox.context.' + ((isOwner) ? 'owner' : 'other') + '.use') } + + } + { (mode === ContextMenuEnum.MYSTERY_TROPHY) && + <> + + { LocalizeText('mysterytrophy.header.title') } + + processAction('use_mystery_trophy') }> + { LocalizeText('friendfurni.context.use') } + + } + { (mode === GROUP_FURNITURE) && groupData && + <> + GetGroupInformation(groupData.guildId) }> + { groupData.guildName } + + { !isGroupMember && + processAction('join_group') }> + { LocalizeText('widget.furniture.button.join.group') } + } + processAction('go_to_group_homeroom') }> + { LocalizeText('widget.furniture.button.go.to.group.home.room') } + + { groupData.guildHasReadableForum && + processAction('open_forum') }> + { LocalizeText('widget.furniture.button.open_group_forum') } + } + } + } + + ); +}; diff --git a/src/components/room/widgets/furniture/context-menu/MonsterPlantSeedConfirmView.tsx b/src/components/room/widgets/furniture/context-menu/MonsterPlantSeedConfirmView.tsx new file mode 100644 index 0000000..4a5ed98 --- /dev/null +++ b/src/components/room/widgets/furniture/context-menu/MonsterPlantSeedConfirmView.tsx @@ -0,0 +1,85 @@ +import { IFurnitureData, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { FurniCategory, GetFurnitureDataForRoomObject, LocalizeText } from '../../../../../api'; +import { Button, Column, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../../common'; +import { useRoom } from '../../../../../hooks'; + +interface MonsterPlantSeedConfirmViewProps +{ + objectId: number; + onClose: () => void; +} + +const MODE_DEFAULT: number = -1; +const MODE_MONSTERPLANT_SEED: number = 0; + +export const MonsterPlantSeedConfirmView: FC = props => +{ + const { objectId = -1, onClose = null } = props; + const [ furniData, setFurniData ] = useState(null); + const [ mode, setMode ] = useState(MODE_DEFAULT); + const { roomSession = null } = useRoom(); + + const useProduct = () => + { + roomSession.useMultistateItem(objectId); + + onClose(); + }; + + useEffect(() => + { + if(!roomSession || (objectId === -1)) return; + + const furniData = GetFurnitureDataForRoomObject(roomSession.roomId, objectId, RoomObjectCategory.FLOOR); + + if(!furniData) return; + + setFurniData(furniData); + + let mode = MODE_DEFAULT; + + switch(furniData.specialType) + { + case FurniCategory.MONSTERPLANT_SEED: + mode = MODE_MONSTERPLANT_SEED; + break; + } + + if(mode === MODE_DEFAULT) + { + onClose(); + + return; + } + + setMode(mode); + }, [ roomSession, objectId, onClose ]); + + if(mode === MODE_DEFAULT) return null; + + return ( + + + +
+
+
+
+
+
+
+ + { LocalizeText('useproduct.widget.text.plant_seed', [ 'productName' ], [ furniData.name ]) } + { LocalizeText('useproduct.widget.info.plant_seed') } + +
+ + +
+
+
+ + + ); +}; diff --git a/src/components/room/widgets/furniture/context-menu/PurchasableClothingConfirmView.tsx b/src/components/room/widgets/furniture/context-menu/PurchasableClothingConfirmView.tsx new file mode 100644 index 0000000..85a6f80 --- /dev/null +++ b/src/components/room/widgets/furniture/context-menu/PurchasableClothingConfirmView.tsx @@ -0,0 +1,104 @@ +import { AvatarFigurePartType, GetAvatarRenderManager, GetSessionDataManager, RedeemItemClothingComposer, RoomObjectCategory, UserFigureComposer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { FurniCategory, GetFurnitureDataForRoomObject, LocalizeText, SendMessageComposer } from '../../../../../api'; +import { Button, Column, LayoutAvatarImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../../common'; +import { useRoom } from '../../../../../hooks'; + +interface PurchasableClothingConfirmViewProps +{ + objectId: number; + onClose: () => void; +} + +const MODE_DEFAULT: number = -1; +const MODE_PURCHASABLE_CLOTHING: number = 0; + +export const PurchasableClothingConfirmView: FC = props => +{ + const { objectId = -1, onClose = null } = props; + const [ mode, setMode ] = useState(MODE_DEFAULT); + const [ gender, setGender ] = useState(AvatarFigurePartType.MALE); + const [ newFigure, setNewFigure ] = useState(null); + const { roomSession = null } = useRoom(); + + const useProduct = () => + { + SendMessageComposer(new RedeemItemClothingComposer(objectId)); + SendMessageComposer(new UserFigureComposer(gender, newFigure)); + + onClose(); + }; + + useEffect(() => + { + let mode = MODE_DEFAULT; + + const figure = GetSessionDataManager().figure; + const gender = GetSessionDataManager().gender; + const validSets: number[] = []; + + if(roomSession && (objectId >= 0)) + { + const furniData = GetFurnitureDataForRoomObject(roomSession.roomId, objectId, RoomObjectCategory.FLOOR); + + if(furniData) + { + switch(furniData.specialType) + { + case FurniCategory.FIGURE_PURCHASABLE_SET: + mode = MODE_PURCHASABLE_CLOTHING; + + const setIds = furniData.customParams.split(',').map(part => parseInt(part)); + + for(const setId of setIds) + { + if(GetAvatarRenderManager().isValidFigureSetForGender(setId, gender)) validSets.push(setId); + } + + break; + } + } + } + + if(mode === MODE_DEFAULT) + { + onClose(); + + return; + } + + setGender(gender); + setNewFigure(GetAvatarRenderManager().getFigureStringWithFigureIds(figure, gender, validSets)); + + // if owns clothing, change to it + + setMode(mode); + }, [ roomSession, objectId, onClose ]); + + if(mode === MODE_DEFAULT) return null; + + return ( + + + +
+
+
+ +
+
+
+ + { LocalizeText('useproduct.widget.text.bind_clothing') } + { LocalizeText('useproduct.widget.info.bind_clothing') } + +
+ + +
+
+
+
+
+ ); +}; diff --git a/src/components/room/widgets/furniture/playlist-editor/DiskInventoryView.tsx b/src/components/room/widgets/furniture/playlist-editor/DiskInventoryView.tsx new file mode 100644 index 0000000..7550af9 --- /dev/null +++ b/src/components/room/widgets/furniture/playlist-editor/DiskInventoryView.tsx @@ -0,0 +1,94 @@ +import { CreateLinkEvent, GetSoundManager, IAdvancedMap, MusicPriorities } from '@nitrots/nitro-renderer'; +import { FC, MouseEvent, useCallback, useEffect, useState } from 'react'; +import { CatalogPageName, GetConfigurationValue, GetDiskColor, LocalizeText } from '../../../../../api'; +import { AutoGrid, Button, Flex, LayoutGridItem, Text } from '../../../../../common'; + +export interface DiskInventoryViewProps +{ + diskInventory: IAdvancedMap; + addToPlaylist: (diskId: number, slotNumber: number) => void; +} + +export const DiskInventoryView: FC = props => +{ + const { diskInventory = null, addToPlaylist = null } = props; + const [ selectedItem, setSelectedItem ] = useState(-1); + const [ previewSongId, setPreviewSongId ] = useState(-1); + + const previewSong = useCallback((event: MouseEvent, songId: number) => + { + event.stopPropagation(); + + setPreviewSongId(prevValue => (prevValue === songId) ? -1 : songId); + }, []); + + const addSong = useCallback((event: MouseEvent, diskId: number) => + { + event.stopPropagation(); + + addToPlaylist(diskId, GetSoundManager().musicController?.getRoomItemPlaylist()?.length); + }, [ addToPlaylist ]); + + const openCatalogPage = () => + { + CreateLinkEvent('catalog/open/' + CatalogPageName.TRAX_SONGS); + }; + + useEffect(() => + { + if(previewSongId === -1) return; + + GetSoundManager().musicController?.playSong(previewSongId, MusicPriorities.PRIORITY_SONG_PLAY, 0, 0, 0, 0); + + return () => + { + GetSoundManager().musicController?.stop(MusicPriorities.PRIORITY_SONG_PLAY); + }; + }, [ previewSongId ]); + + useEffect(() => + { + return () => setPreviewSongId(-1); + }, []); + + return (<> +
+ +

{ LocalizeText('playlist.editor.my.music') }

+
+
+ + { diskInventory && diskInventory.getKeys().map((key, index) => + { + const diskId = diskInventory.getKey(index); + const songId = diskInventory.getWithIndex(index); + const songInfo = GetSoundManager().musicController?.getSongInfo(songId); + + return ( + setSelectedItem(prev => prev === index ? -1 : index) }> +
+
+ { songInfo?.name } + { (selectedItem === index) && + + + + + } +
); + }) } +
+
+
+
{ LocalizeText('playlist.editor.text.get.more.music') }
+
{ LocalizeText('playlist.editor.text.you.have.no.songdisks.available') }
+
{ LocalizeText('playlist.editor.text.you.can.buy.some.from.the.catalogue') }
+ +
+ + ); +}; diff --git a/src/components/room/widgets/furniture/playlist-editor/FurniturePlaylistEditorWidgetView.tsx b/src/components/room/widgets/furniture/playlist-editor/FurniturePlaylistEditorWidgetView.tsx new file mode 100644 index 0000000..611eba2 --- /dev/null +++ b/src/components/room/widgets/furniture/playlist-editor/FurniturePlaylistEditorWidgetView.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../common'; +import { useFurniturePlaylistEditorWidget } from '../../../../../hooks'; +import { DiskInventoryView } from './DiskInventoryView'; +import { SongPlaylistView } from './SongPlaylistView'; + +export const FurniturePlaylistEditorWidgetView: FC<{}> = props => +{ + const { objectId = -1, currentPlayingIndex = -1, playlist = null, diskInventory = null, onClose = null, togglePlayPause = null, removeFromPlaylist = null, addToPlaylist = null } = useFurniturePlaylistEditorWidget(); + + if(objectId === -1) return null; + + return ( + + + +
+
+ +
+
+ +
+
+
+
+ ); +}; diff --git a/src/components/room/widgets/furniture/playlist-editor/SongPlaylistView.tsx b/src/components/room/widgets/furniture/playlist-editor/SongPlaylistView.tsx new file mode 100644 index 0000000..95289a7 --- /dev/null +++ b/src/components/room/widgets/furniture/playlist-editor/SongPlaylistView.tsx @@ -0,0 +1,78 @@ +import { ISongInfo } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { GetConfigurationValue, GetDiskColor, LocalizeText } from '../../../../../api'; +import { Button, Text } from '../../../../../common'; + +export interface SongPlaylistViewProps +{ + furniId: number; + playlist: ISongInfo[]; + currentPlayingIndex: number; + removeFromPlaylist(slotNumber: number): void; + togglePlayPause(furniId: number, position: number): void; +} + +export const SongPlaylistView: FC = props => +{ + const { furniId = -1, playlist = null, currentPlayingIndex = -1, removeFromPlaylist = null, togglePlayPause = null } = props; + const [ selectedItem, setSelectedItem ] = useState(-1); + + const action = (index: number) => + { + if(selectedItem === index) removeFromPlaylist(index); + }; + + const playPause = (furniId: number, selectedItem: number) => + { + togglePlayPause(furniId, selectedItem !== -1 ? selectedItem : 0); + }; + + return (<> +
+ +

{ LocalizeText('playlist.editor.playlist') }

+
+
+
+ { playlist && playlist.map((songInfo, index) => + { + return
setSelectedItem(prev => prev === index ? -1 : index) }> +
action(index) } /> + { songInfo.name } +
; + }) } + +
+
+ { (!playlist || playlist.length === 0) && + <>
+
{ LocalizeText('playlist.editor.add.songs.to.your.playlist') }
+
{ LocalizeText('playlist.editor.text.click.song.to.choose.click.again.to.move') }
+
+ + } + { (playlist && playlist.length > 0) && + <> + { (currentPlayingIndex === -1) && + + } + { (currentPlayingIndex !== -1) && +
+ +
+ { LocalizeText('playlist.editor.text.now.playing.in.your.room') } + + { playlist[currentPlayingIndex]?.name + ' - ' + playlist[currentPlayingIndex]?.creator } + +
+
+ } + + } + + ); +}; diff --git a/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.tsx b/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.tsx new file mode 100644 index 0000000..1e18286 --- /dev/null +++ b/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.tsx @@ -0,0 +1,67 @@ +import { MysteryBoxKeysUpdateEvent } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; +import { ColorUtils, LocalizeText } from '../../../../api'; +import { Flex, LayoutGridItem, Text } from '../../../../common'; +import { useNitroEvent } from '../../../../hooks'; + +const colorMap = { + 'purple': 9452386, + 'blue': 3891856, + 'green': 6459451, + 'yellow': 10658089, + 'lilac': 6897548, + 'orange': 10841125, + 'turquoise': 2661026, + 'red': 10104881 +}; + +export const MysteryBoxExtensionView: FC<{}> = props => +{ + const [ isOpen, setIsOpen ] = useState(true); + const [ keyColor, setKeyColor ] = useState(''); + const [ boxColor, setBoxColor ] = useState(''); + + useNitroEvent(MysteryBoxKeysUpdateEvent.MYSTERY_BOX_KEYS_UPDATE, event => + { + setKeyColor(event.keyColor); + setBoxColor(event.boxColor); + }); + + const getRgbColor = (color: string) => + { + const colorInt = colorMap[color]; + + return ColorUtils.int2rgb(colorInt); + }; + + if(keyColor === '' && boxColor === '') return null; + + return ( +
+
+ setIsOpen(value => !value) }> + { LocalizeText('mysterybox.tracker.title') } + { isOpen && } + { !isOpen && } + + { isOpen && + <> + { LocalizeText('mysterybox.tracker.description') } +
+ +
+
+
+ + +
+
+
+ +
+ } +
+
+ ); +}; diff --git a/src/components/room/widgets/object-location/ObjectLocationView.tsx b/src/components/room/widgets/object-location/ObjectLocationView.tsx new file mode 100644 index 0000000..0bd1a48 --- /dev/null +++ b/src/components/room/widgets/object-location/ObjectLocationView.tsx @@ -0,0 +1,61 @@ +import { GetTicker } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useRef, useState } from 'react'; +import { GetRoomObjectBounds, GetRoomSession } from '../../../../api'; +import { BaseProps } from '../../../../common'; + +interface ObjectLocationViewProps extends BaseProps +{ + objectId: number; + category: number; + noFollow?: boolean; +} + +export const ObjectLocationView: FC = props => +{ + const { objectId = -1, category = -1, noFollow = false, ...rest } = props; + const [ pos, setPos ] = useState<{ x: number, y: number }>({ x: -1, y: -1 }); + const elementRef = useRef(); + + useEffect(() => + { + let remove = false; + + const getObjectLocation = () => + { + const roomSession = GetRoomSession(); + const objectBounds = GetRoomObjectBounds(roomSession.roomId, objectId, category, 1); + + return objectBounds; + }; + + const updatePosition = () => + { + const bounds = getObjectLocation(); + + if(!bounds || !elementRef.current) return; + + setPos({ + x: Math.round(((bounds.left + (bounds.width / 2)) - (elementRef.current.offsetWidth / 2))), + y: Math.round((bounds.top - elementRef.current.offsetHeight) + 10) + }); + }; + + if(noFollow) + { + updatePosition(); + } + else + { + remove = true; + + GetTicker().add(updatePosition); + } + + return () => + { + if(remove) GetTicker().remove(updatePosition); + }; + }, [ objectId, category, noFollow ]); + + return
-1) ? 'visible' : 'hidden' } } { ...rest } />; +}; diff --git a/src/components/room/widgets/pet-package/PetPackageWidgetView.tsx b/src/components/room/widgets/pet-package/PetPackageWidgetView.tsx new file mode 100644 index 0000000..9b4ff4a --- /dev/null +++ b/src/components/room/widgets/pet-package/PetPackageWidgetView.tsx @@ -0,0 +1,41 @@ +import { FC } from 'react'; +import { GetConfigurationValue, LocalizeText } from '../../../../api'; +import { Button, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { usePetPackageWidget } from '../../../../hooks'; + +export const PetPackageWidgetView: FC<{}> = props => +{ + const { isVisible = false, errorResult = null, petName = null, objectType = null, onChangePetName = null, onConfirm = null, onClose = null } = usePetPackageWidget(); + + return ( + <> + { isVisible && + + onClose() } /> + +
+
+
+ { objectType === 'gnome_box' ? LocalizeText('widgets.gnomepackage.name.title') : LocalizeText('furni.petpackage') } +
+
+
+
+
+ onChangePetName(event.target.value) } /> +
+
+ { (errorResult.length > 0) && +
{ errorResult }
} +
+ onClose() }>{ LocalizeText('cancel') } + +
+
+
+
+
+ } + + ); +}; diff --git a/src/components/room/widgets/room-filter-words/RoomFilterWordsWidgetView.tsx b/src/components/room/widgets/room-filter-words/RoomFilterWordsWidgetView.tsx new file mode 100644 index 0000000..bbed44b --- /dev/null +++ b/src/components/room/widgets/room-filter-words/RoomFilterWordsWidgetView.tsx @@ -0,0 +1,75 @@ +import { UpdateRoomFilterMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { LocalizeText, SendMessageComposer } from '../../../../api'; +import { Button, Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { useFilterWordsWidget, useNavigator } from '../../../../hooks'; +import { NitroInput, classNames } from '../../../../layout'; + +export const RoomFilterWordsWidgetView: FC<{}> = props => +{ + const [ word, setWord ] = useState('bobba'); + const [ selectedWord, setSelectedWord ] = useState(''); + const [ isSelectingWord, setIsSelectingWord ] = useState(false); + const { wordsFilter = [], isVisible = null, setWordsFilter, onClose = null } = useFilterWordsWidget(); + const { navigatorData = null } = useNavigator(); + + const processAction = (isAddingWord: boolean) => + { + if((isSelectingWord) ? (!selectedWord) : (!word)) return; + + SendMessageComposer(new UpdateRoomFilterMessageComposer(navigatorData.enteredGuestRoom.roomId, isAddingWord, (isSelectingWord ? selectedWord : word))); + setSelectedWord(''); + setWord('bobba'); + setIsSelectingWord(false); + + if(isAddingWord && wordsFilter.includes((isSelectingWord ? selectedWord : word))) return; + + setWordsFilter(prevValue => + { + const newWords = [ ...prevValue ]; + + isAddingWord ? newWords.push((isSelectingWord ? selectedWord : word)) : newWords.splice(newWords.indexOf((isSelectingWord ? selectedWord : word)), 1); + + return newWords; + }); + }; + + const onTyping = (word: string) => + { + setWord(word); + setIsSelectingWord(false); + }; + + const onSelectedWord = (word: string) => + { + setSelectedWord(word); + setIsSelectingWord(true); + }; + + if(!isVisible) return null; + + return ( + + onClose() } /> + + + onTyping(event.target.value) } /> + + + + { wordsFilter && (wordsFilter.length > 0) && wordsFilter.map((word, index) => + { + return ( + onSelectedWord(word) }> + { word } + + ); + }) } + + + + + + + ); +}; diff --git a/src/components/room/widgets/room-promotes/RoomPromotesWidgetView.tsx b/src/components/room/widgets/room-promotes/RoomPromotesWidgetView.tsx new file mode 100644 index 0000000..07cf841 --- /dev/null +++ b/src/components/room/widgets/room-promotes/RoomPromotesWidgetView.tsx @@ -0,0 +1,55 @@ +import { DesktopViewEvent, GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; +import { Flex, Text } from '../../../../common'; +import { useMessageEvent, useRoomPromote } from '../../../../hooks'; +import { RoomPromoteEditWidgetView, RoomPromoteMyOwnEventWidgetView, RoomPromoteOtherEventWidgetView } from './views'; + +export const RoomPromotesWidgetView: FC<{}> = props => +{ + const [ isEditingPromote, setIsEditingPromote ] = useState(false); + const [ isOpen, setIsOpen ] = useState(true); + const { promoteInformation, setPromoteInformation } = useRoomPromote(); + + useMessageEvent(DesktopViewEvent, event => + { + setPromoteInformation(null); + }); + + if(!promoteInformation) return null; + + return ( + <> + { promoteInformation.data.adId !== -1 && +
+
+ setIsOpen(value => !value) }> + { promoteInformation.data.eventName } + { isOpen && } + { !isOpen && } + + { (isOpen && GetSessionDataManager().userId !== promoteInformation.data.ownerAvatarId) && + + } + { (isOpen && GetSessionDataManager().userId === promoteInformation.data.ownerAvatarId) && + setIsEditingPromote(true) } + /> + } + { isEditingPromote && + setIsEditingPromote(false) } + /> + } +
+
+ } + + ); +}; diff --git a/src/components/room/widgets/room-promotes/views/RoomPromoteEditWidgetView.tsx b/src/components/room/widgets/room-promotes/views/RoomPromoteEditWidgetView.tsx new file mode 100644 index 0000000..bb3e610 --- /dev/null +++ b/src/components/room/widgets/room-promotes/views/RoomPromoteEditWidgetView.tsx @@ -0,0 +1,45 @@ +import { EditEventMessageComposer } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { LocalizeText, SendMessageComposer } from '../../../../../api'; +import { Button, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../../common'; +import { NitroInput } from '../../../../../layout'; + +interface RoomPromoteEditWidgetViewProps +{ + eventId: number; + eventName: string; + eventDescription: string; + setIsEditingPromote: (value: boolean) => void; +} + +export const RoomPromoteEditWidgetView: FC = props => +{ + const { eventId = -1, eventName = '', eventDescription = '', setIsEditingPromote = null } = props; + const [ newEventName, setNewEventName ] = useState(eventName); + const [ newEventDescription, setNewEventDescription ] = useState(eventDescription); + + const updatePromote = () => + { + SendMessageComposer(new EditEventMessageComposer(eventId, newEventName, newEventDescription)); + setIsEditingPromote(false); + }; + + return ( + + setIsEditingPromote(false) } /> + +
+ { LocalizeText('navigator.eventsettings.name') } + setNewEventName(event.target.value) } /> +
+
+ { LocalizeText('navigator.eventsettings.desc') } + +
+
+ +
+
+
+ ); +}; diff --git a/src/components/room/widgets/room-promotes/views/RoomPromoteMyOwnEventWidgetView.tsx b/src/components/room/widgets/room-promotes/views/RoomPromoteMyOwnEventWidgetView.tsx new file mode 100644 index 0000000..e53497d --- /dev/null +++ b/src/components/room/widgets/room-promotes/views/RoomPromoteMyOwnEventWidgetView.tsx @@ -0,0 +1,36 @@ +import { CreateLinkEvent } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { Button, Flex, Grid, Text } from '../../../../../common'; +import { useRoomPromote } from '../../../../../hooks'; + +interface RoomPromoteMyOwnEventWidgetViewProps +{ + eventDescription: string; + setIsEditingPromote: (value: boolean) => void; +} + +export const RoomPromoteMyOwnEventWidgetView: FC = props => +{ + const { eventDescription = '', setIsEditingPromote = null } = props; + const { setIsExtended } = useRoomPromote(); + + const extendPromote = () => + { + setIsExtended(true); + CreateLinkEvent('catalog/open/room_event'); + }; + + return ( + <> + + { eventDescription } + +

+ + + + + + ); +}; diff --git a/src/components/room/widgets/room-promotes/views/RoomPromoteOtherEventWidgetView.tsx b/src/components/room/widgets/room-promotes/views/RoomPromoteOtherEventWidgetView.tsx new file mode 100644 index 0000000..3a3ed08 --- /dev/null +++ b/src/components/room/widgets/room-promotes/views/RoomPromoteOtherEventWidgetView.tsx @@ -0,0 +1,30 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../../../api'; +import { Column, Flex, Text } from '../../../../../common'; + +interface RoomPromoteOtherEventWidgetViewProps +{ + eventDescription: string; +} + +export const RoomPromoteOtherEventWidgetView: FC = props => +{ + const { eventDescription = '' } = props; + + return ( + <> + + { eventDescription } + +

+ +
+
+ { LocalizeText('navigator.eventinprogress') } +
+   +
+
+ + ); +}; diff --git a/src/components/room/widgets/room-promotes/views/index.ts b/src/components/room/widgets/room-promotes/views/index.ts new file mode 100644 index 0000000..da74691 --- /dev/null +++ b/src/components/room/widgets/room-promotes/views/index.ts @@ -0,0 +1,3 @@ +export * from './RoomPromoteEditWidgetView'; +export * from './RoomPromoteMyOwnEventWidgetView'; +export * from './RoomPromoteOtherEventWidgetView'; diff --git a/src/components/room/widgets/room-thumbnail/RoomThumbnailWidgetView.tsx b/src/components/room/widgets/room-thumbnail/RoomThumbnailWidgetView.tsx new file mode 100644 index 0000000..a14744c --- /dev/null +++ b/src/components/room/widgets/room-thumbnail/RoomThumbnailWidgetView.tsx @@ -0,0 +1,41 @@ +import { GetRoomEngine, NitroRenderTexture } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { LayoutMiniCameraView } from '../../../../common'; +import { RoomWidgetThumbnailEvent } from '../../../../events'; +import { useRoom, useUiEvent } from '../../../../hooks'; + +export const RoomThumbnailWidgetView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const { roomSession = null } = useRoom(); + + useUiEvent([ + RoomWidgetThumbnailEvent.SHOW_THUMBNAIL, + RoomWidgetThumbnailEvent.HIDE_THUMBNAIL, + RoomWidgetThumbnailEvent.TOGGLE_THUMBNAIL ], event => + { + switch(event.type) + { + case RoomWidgetThumbnailEvent.SHOW_THUMBNAIL: + setIsVisible(true); + return; + case RoomWidgetThumbnailEvent.HIDE_THUMBNAIL: + setIsVisible(false); + return; + case RoomWidgetThumbnailEvent.TOGGLE_THUMBNAIL: + setIsVisible(value => !value); + return; + } + }); + + const receiveTexture = async (texture: NitroRenderTexture) => + { + await GetRoomEngine().saveTextureAsScreenshot(texture, true); + + setIsVisible(false); + }; + + if(!isVisible) return null; + + return setIsVisible(false) } />; +}; diff --git a/src/components/room/widgets/room-tools/RoomToolsWidgetView.tsx b/src/components/room/widgets/room-tools/RoomToolsWidgetView.tsx new file mode 100644 index 0000000..19d382a --- /dev/null +++ b/src/components/room/widgets/room-tools/RoomToolsWidgetView.tsx @@ -0,0 +1,162 @@ +import { CreateLinkEvent, GetGuestRoomResultEvent, GetRoomEngine, NavigatorSearchComposer, RateFlatMessageComposer } from '@nitrots/nitro-renderer'; +import { AnimatePresence, motion } from 'framer-motion'; +import { classNames } from '../../../../layout'; +import { FC, useEffect, useState } from 'react'; +import { GetConfigurationValue, LocalizeText, SendMessageComposer, SetLocalStorage, TryVisitRoom } from '../../../../api'; +import { Text } from '../../../../common'; +import { useMessageEvent, useNavigator, useRoom } from '../../../../hooks'; + +export const RoomToolsWidgetView: FC<{}> = props => { + const [areBubblesMuted, setAreBubblesMuted] = useState(false); + const [isZoomedIn, setIsZoomedIn] = useState(false); + const [roomName, setRoomName] = useState(null); + const [roomOwner, setRoomOwner] = useState(null); + const [roomTags, setRoomTags] = useState(null); + const [isOpen, setIsOpen] = useState(false); + const [isOpenHistory, setIsOpenHistory] = useState(false); + const [roomHistory, setRoomHistory] = useState<{ roomId: number, roomName: string }[]>([]); + const { navigatorData = null } = useNavigator(); + const { roomSession = null } = useRoom(); + + const handleToolClick = (action: string, value?: string) => { + if (!roomSession) return; + + switch (action) { + case 'settings': + CreateLinkEvent('navigator/toggle-room-info'); + return; + case 'zoom': + setIsZoomedIn(prevValue => { + if (GetConfigurationValue('room.zoom.enabled', true)) { + const scale = GetRoomEngine().getRoomInstanceRenderingCanvasScale(roomSession.roomId, 1); + GetRoomEngine().setRoomInstanceRenderingCanvasScale(roomSession.roomId, 1, scale === 1 ? 0.5 : 1); + } else { + const geometry = GetRoomEngine().getRoomInstanceGeometry(roomSession.roomId, 1); + if (geometry) geometry.performZoom(); + } + return !prevValue; + }); + return; + case 'chat_history': + CreateLinkEvent('chat-history/toggle'); + return; + case 'hiddenbubbles': + CreateLinkEvent('nitrobubblehidden/toggle'); + setAreBubblesMuted(prev => !prev); + return; + case 'like_room': + SendMessageComposer(new RateFlatMessageComposer(1)); + return; + case 'toggle_room_link': + CreateLinkEvent('navigator/toggle-room-link'); + return; + case 'navigator_search_tag': + CreateLinkEvent(`navigator/search/${value}`); + SendMessageComposer(new NavigatorSearchComposer('hotel_view', `tag:${value}`)); + return; + case 'room_history': + if (roomHistory.length > 0) setIsOpenHistory(prev => !prev); + return; + case 'room_history_back': + const prevIndex = roomHistory.findIndex(room => room.roomId === navigatorData.currentRoomId) - 1; + if (prevIndex >= 0) TryVisitRoom(roomHistory[prevIndex].roomId); + return; + case 'room_history_next': + const nextIndex = roomHistory.findIndex(room => room.roomId === navigatorData.currentRoomId) + 1; + if (nextIndex < roomHistory.length) TryVisitRoom(roomHistory[nextIndex].roomId); + return; + } + }; + + const onChangeRoomHistory = (roomId: number, roomName: string) => { + let newStorage = JSON.parse(window.localStorage.getItem('nitro.room.history') || '[]'); + if (newStorage.some((room: { roomId: number }) => room.roomId === roomId)) return; + + if (newStorage.length >= 10) newStorage.shift(); + newStorage = [...newStorage, { roomId, roomName }]; + + setRoomHistory(newStorage); + SetLocalStorage('nitro.room.history', newStorage); + }; + + useMessageEvent(GetGuestRoomResultEvent, event => { + const parser = event.getParser(); + if (!parser.roomEnter || (parser.data.roomId !== roomSession.roomId)) return; + + if (roomName !== parser.data.roomName) setRoomName(parser.data.roomName); + if (roomOwner !== parser.data.ownerName) setRoomOwner(parser.data.ownerName); + if (roomTags !== parser.data.tags) setRoomTags(parser.data.tags); + onChangeRoomHistory(parser.data.roomId, parser.data.roomName); + }); + + useEffect(() => { + setIsOpen(true); + const timeout = setTimeout(() => setIsOpen(false), 5000); + return () => clearTimeout(timeout); + }, [roomName, roomOwner, roomTags]); + + useEffect(() => { + setRoomHistory(JSON.parse(window.localStorage.getItem('nitro.room.history') || '[]')); + }, []); + + useEffect(() => { + const handleTabClose = () => { + window.localStorage.removeItem('nitro.room.history'); + }; + window.addEventListener('beforeunload', handleTabClose); + return () => window.removeEventListener('beforeunload', handleTabClose); + }, []); + + return ( +
+
+
handleToolClick('settings')} /> +
handleToolClick('zoom')} /> +
handleToolClick('chat_history')} /> +
handleToolClick('hiddenbubbles')} /> + + {navigatorData.canRate && ( +
handleToolClick('like_room')} /> + )} +
handleToolClick('toggle_room_link')} /> +
handleToolClick('room_history')} /> +
+
+ + {isOpen && ( + +
+
+
+ {roomName} + {roomOwner} +
+ {roomTags && roomTags.length > 0 && ( +
+ {roomTags.map((tag, index) => ( + handleToolClick('navigator_search_tag', tag)}> + #{tag} + + ))} +
+ )} +
+
+
+ )} + {isOpenHistory && ( + +
+ {roomHistory.map(history => ( + TryVisitRoom(history.roomId)}> + {history.roomName} + + ))} +
+
+ )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/components/room/widgets/user-location/UserLocationView.tsx b/src/components/room/widgets/user-location/UserLocationView.tsx new file mode 100644 index 0000000..f7b9daa --- /dev/null +++ b/src/components/room/widgets/user-location/UserLocationView.tsx @@ -0,0 +1,24 @@ +import { RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { BaseProps } from '../../../../common'; +import { useRoom } from '../../../../hooks'; +import { ObjectLocationView } from '../object-location/ObjectLocationView'; + +interface UserLocationViewProps extends BaseProps +{ + userId: number; +} + +export const UserLocationView: FC = props => +{ + const { userId = -1, ...rest } = props; + const { roomSession = null } = useRoom(); + + if((userId === -1) || !roomSession) return null; + + const userData = roomSession.userDataManager.getUserData(userId); + + if(!userData) return null; + + return ; +}; diff --git a/src/components/room/widgets/word-quiz/WordQuizQuestionView.tsx b/src/components/room/widgets/word-quiz/WordQuizQuestionView.tsx new file mode 100644 index 0000000..92f0b96 --- /dev/null +++ b/src/components/room/widgets/word-quiz/WordQuizQuestionView.tsx @@ -0,0 +1,44 @@ +import { FC } from 'react'; +import { VALUE_KEY_DISLIKE, VALUE_KEY_LIKE } from '../../../../api'; +import { Column, Flex, Text } from '../../../../common'; + +interface WordQuizQuestionViewProps +{ + question: string; + canVote: boolean; + vote(value: string): void; + noVotes: number; + yesVotes: number; +} + +export const WordQuizQuestionView: FC = props => +{ + const { question = null, canVote = null, vote = null, noVotes = null, yesVotes = null } = props; + + return ( + + { !canVote && +
+
+ { noVotes } +
+ { question } +
+ { yesVotes } +
+
} + { canVote && +
+ { question } +
+ vote(VALUE_KEY_DISLIKE) }> +
+ + vote(VALUE_KEY_LIKE) }> +
+ +
+
} + + ); +}; diff --git a/src/components/room/widgets/word-quiz/WordQuizVoteView.tsx b/src/components/room/widgets/word-quiz/WordQuizVoteView.tsx new file mode 100644 index 0000000..b1925a0 --- /dev/null +++ b/src/components/room/widgets/word-quiz/WordQuizVoteView.tsx @@ -0,0 +1,24 @@ +import { RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { VALUE_KEY_DISLIKE } from '../../../../api'; +import { BaseProps } from '../../../../common'; +import { ObjectLocationView } from '../object-location/ObjectLocationView'; + +interface WordQuizVoteViewProps extends BaseProps +{ + userIndex: number; + vote: string; +} + +export const WordQuizVoteView: FC = props => +{ + const { userIndex = null, vote = null, ...rest } = props; + + return ( + +
+
+
+ + ); +}; diff --git a/src/components/room/widgets/word-quiz/WordQuizWidgetView.tsx b/src/components/room/widgets/word-quiz/WordQuizWidgetView.tsx new file mode 100644 index 0000000..d433d35 --- /dev/null +++ b/src/components/room/widgets/word-quiz/WordQuizWidgetView.tsx @@ -0,0 +1,19 @@ +import { FC } from 'react'; +import { VALUE_KEY_DISLIKE, VALUE_KEY_LIKE } from '../../../../api'; +import { useWordQuizWidget } from '../../../../hooks'; +import { WordQuizQuestionView } from './WordQuizQuestionView'; +import { WordQuizVoteView } from './WordQuizVoteView'; + +export const WordQuizWidgetView: FC<{}> = props => +{ + const { question = null, answerSent = false, answerCounts = null, userAnswers = null, vote = null } = useWordQuizWidget(); + + return ( + <> + { question && + } + { userAnswers && + Array.from(userAnswers.entries()).map(([ key, value ], index) => ) } + + ); +}; diff --git a/src/components/toolbar/ToolbarItemView.tsx b/src/components/toolbar/ToolbarItemView.tsx new file mode 100644 index 0000000..3a0822a --- /dev/null +++ b/src/components/toolbar/ToolbarItemView.tsx @@ -0,0 +1,22 @@ +import { DetailedHTMLProps, forwardRef, HTMLAttributes, PropsWithChildren } from 'react'; +import { classNames } from '../../layout'; + +export const ToolbarItemView = forwardRef & DetailedHTMLProps, HTMLDivElement>>((props, ref) => +{ + const { icon = null, className = null, ...rest } = props; + + return ( +
+ ); +}); + +ToolbarItemView.displayName = 'ToolbarItemView'; diff --git a/src/components/toolbar/ToolbarMeView.tsx b/src/components/toolbar/ToolbarMeView.tsx new file mode 100644 index 0000000..c420fce --- /dev/null +++ b/src/components/toolbar/ToolbarMeView.tsx @@ -0,0 +1,49 @@ +import { CreateLinkEvent, GetRoomEngine, GetSessionDataManager, MouseEventType, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { Dispatch, FC, PropsWithChildren, SetStateAction, useEffect, useRef } from 'react'; +import { DispatchUiEvent, GetConfigurationValue, GetRoomSession, GetUserProfile } from '../../api'; +import { Flex, LayoutItemCountView } from '../../common'; +import { GuideToolEvent } from '../../events'; + +export const ToolbarMeView: FC>; +}>> = props => +{ + const { useGuideTool = false, unseenAchievementCount = 0, setMeExpanded = null, children = null, ...rest } = props; + const elementRef = useRef(); + + useEffect(() => + { + const roomSession = GetRoomSession(); + + if(!roomSession) return; + + GetRoomEngine().selectRoomObject(roomSession.roomId, roomSession.ownRoomIndex, RoomObjectCategory.UNIT); + }, []); + + useEffect(() => + { + const onClick = (event: MouseEvent) => setMeExpanded(false); + + document.addEventListener('click', onClick); + + return () => document.removeEventListener(MouseEventType.MOUSE_CLICK, onClick); + }, [ setMeExpanded ]); + + return ( + + { (GetConfigurationValue('guides.enabled') && useGuideTool) && +
DispatchUiEvent(new GuideToolEvent(GuideToolEvent.TOGGLE_GUIDE_TOOL)) } /> } +
CreateLinkEvent('achievements/toggle') }> + { (unseenAchievementCount > 0) && + } +
+
GetUserProfile(GetSessionDataManager().userId) } /> +
CreateLinkEvent('navigator/search/myworld_view') } /> +
CreateLinkEvent('avatar-editor/toggle') } /> +
CreateLinkEvent('user-settings/toggle') } /> + { children } + + ); +}; diff --git a/src/components/toolbar/ToolbarView.tsx b/src/components/toolbar/ToolbarView.tsx new file mode 100644 index 0000000..27e6a4a --- /dev/null +++ b/src/components/toolbar/ToolbarView.tsx @@ -0,0 +1,117 @@ +import { CreateLinkEvent, Dispose, DropBounce, EaseOut, GetSessionDataManager, JumpBy, Motions, NitroToolbarAnimateIconEvent, PerkAllowancesMessageEvent, PerkEnum, Queue, Wait } from '@nitrots/nitro-renderer'; +import { AnimatePresence, motion } from 'framer-motion'; +import { FC, useState } from 'react'; +import { GetConfigurationValue, MessengerIconState, OpenMessengerChat, VisitDesktop } from '../../api'; +import { Flex, LayoutAvatarImageView, LayoutItemCountView } from '../../common'; +import { useAchievements, useFriends, useInventoryUnseenTracker, useMessageEvent, useMessenger, useNitroEvent, useSessionInfo } from '../../hooks'; +import { ToolbarItemView } from './ToolbarItemView'; +import { ToolbarMeView } from './ToolbarMeView'; + +export const ToolbarView: FC<{ isInRoom: boolean }> = props => +{ + const { isInRoom } = props; + const [ isMeExpanded, setMeExpanded ] = useState(false); + const [ useGuideTool, setUseGuideTool ] = useState(false); + const { userFigure = null } = useSessionInfo(); + const { getFullCount = 0 } = useInventoryUnseenTracker(); + const { getTotalUnseen = 0 } = useAchievements(); + const { requests = [] } = useFriends(); + const { iconState = MessengerIconState.HIDDEN } = useMessenger(); + const isMod = GetSessionDataManager().isModerator; + + useMessageEvent(PerkAllowancesMessageEvent, event => + { + setUseGuideTool(event.getParser().isAllowed(PerkEnum.USE_GUIDE_TOOL)); + }); + + useNitroEvent(NitroToolbarAnimateIconEvent.ANIMATE_ICON, event => + { + const animationIconToToolbar = (iconName: string, image: HTMLImageElement, x: number, y: number) => + { + const target = (document.body.getElementsByClassName(iconName)[0] as HTMLElement); + + if(!target) return; + + image.className = 'toolbar-icon-animation'; + image.style.visibility = 'visible'; + image.style.left = (x + 'px'); + image.style.top = (y + 'px'); + + document.body.append(image); + + const targetBounds = target.getBoundingClientRect(); + const imageBounds = image.getBoundingClientRect(); + + const left = (imageBounds.x - targetBounds.x); + const top = (imageBounds.y - targetBounds.y); + const squared = Math.sqrt(((left * left) + (top * top))); + const wait = (500 - Math.abs(((((1 / squared) * 100) * 500) * 0.5))); + const height = 20; + + const motionName = (`ToolbarBouncing[${ iconName }]`); + + if(!Motions.getMotionByTag(motionName)) + { + Motions.runMotion(new Queue(new Wait((wait + 8)), new DropBounce(target, 400, 12))).tag = motionName; + } + + const motion = new Queue(new EaseOut(new JumpBy(image, wait, ((targetBounds.x - imageBounds.x) + height), (targetBounds.y - imageBounds.y), 100, 1), 1), new Dispose(image)); + + Motions.runMotion(motion); + }; + + animationIconToToolbar('icon-inventory', event.image, event.x, event.y); + }); + + return ( + <> + { isMeExpanded && ( + + )} + + + + + + { + setMeExpanded(!isMeExpanded); + event.stopPropagation(); + } }> + + { (getTotalUnseen > 0) && + } + + { isInRoom && + VisitDesktop() } /> } + { !isInRoom && + CreateLinkEvent('navigator/goto/home') } /> } + CreateLinkEvent('navigator/toggle') } /> + { GetConfigurationValue('game.center.enabled') && + CreateLinkEvent('games/toggle') } /> } + CreateLinkEvent('catalog/toggle') } /> + CreateLinkEvent('inventory/toggle') }> + { (getFullCount > 0) && + } + + { isInRoom && + CreateLinkEvent('camera/toggle') } /> } + { isMod && + CreateLinkEvent('mod-tools/toggle') } /> } + + + + + + CreateLinkEvent('friends/toggle') }> + { (requests.length > 0) && + } + + { ((iconState === MessengerIconState.SHOW) || (iconState === MessengerIconState.UNREAD)) && + OpenMessengerChat() } /> } + +
+ + + + ); +}; diff --git a/src/components/user-profile/FriendsContainerView.tsx b/src/components/user-profile/FriendsContainerView.tsx new file mode 100644 index 0000000..b92b82d --- /dev/null +++ b/src/components/user-profile/FriendsContainerView.tsx @@ -0,0 +1,27 @@ +import { RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { LocalizeText } from '../../api'; +import { RelationshipsContainerView } from './RelationshipsContainerView'; + +interface FriendsContainerViewProps +{ + relationships: RelationshipStatusInfoMessageParser; + friendsCount: number; +} + +export const FriendsContainerView: FC = props => +{ + const { relationships = null, friendsCount = null } = props; + + return ( +
+

+ { LocalizeText('extendedprofile.friends.count') } { friendsCount } +

+
+

{ LocalizeText('extendedprofile.relstatus') }

+ +
+
+ ); +}; diff --git a/src/components/user-profile/GroupsContainerView.tsx b/src/components/user-profile/GroupsContainerView.tsx new file mode 100644 index 0000000..3ffd787 --- /dev/null +++ b/src/components/user-profile/GroupsContainerView.tsx @@ -0,0 +1,90 @@ +import { GroupInformationComposer, GroupInformationEvent, GroupInformationParser, HabboGroupEntryData } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { SendMessageComposer, ToggleFavoriteGroup } from '../../api'; +import { AutoGrid, Column, Grid, GridProps, LayoutBadgeImageView, LayoutGridItem } from '../../common'; +import { useMessageEvent } from '../../hooks'; +import { GroupInformationView } from '../groups/views/GroupInformationView'; + +interface GroupsContainerViewProps extends GridProps +{ + itsMe: boolean; + groups: HabboGroupEntryData[]; + onLeaveGroup: () => void; +} + +export const GroupsContainerView: FC = props => +{ + const { itsMe = null, groups = null, onLeaveGroup = null, overflow = 'hidden', gap = 2, ...rest } = props; + const [ selectedGroupId, setSelectedGroupId ] = useState(null); + const [ groupInformation, setGroupInformation ] = useState(null); + + useMessageEvent(GroupInformationEvent, event => + { + const parser = event.getParser(); + + if(!selectedGroupId || (selectedGroupId !== parser.id) || parser.flag) return; + + setGroupInformation(parser); + }); + + useEffect(() => + { + if(!selectedGroupId) return; + + SendMessageComposer(new GroupInformationComposer(selectedGroupId, false)); + }, [ selectedGroupId ]); + + useEffect(() => + { + setGroupInformation(null); + + if(groups.length > 0) + { + setSelectedGroupId(prevValue => + { + if(prevValue === groups[0].groupId) + { + SendMessageComposer(new GroupInformationComposer(groups[0].groupId, false)); + } + + return groups[0].groupId; + }); + } + }, [ groups ]); + + if(!groups || !groups.length) + { + return ( + +
+
+
+
+
+ + ); + } + + return ( + + + + { groups.map((group, index) => + { + return ( + setSelectedGroupId(group.groupId) }> + { itsMe && + ToggleFavoriteGroup(group) } /> } + + + ); + }) } + + + + { groupInformation && + } + + + ); +}; diff --git a/src/components/user-profile/RelationshipsContainerView.tsx b/src/components/user-profile/RelationshipsContainerView.tsx new file mode 100644 index 0000000..7bdca66 --- /dev/null +++ b/src/components/user-profile/RelationshipsContainerView.tsx @@ -0,0 +1,62 @@ +import { RelationshipStatusEnum, RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { GetUserProfile, LocalizeText } from '../../api'; +import { Flex, LayoutAvatarImageView } from '../../common'; + +interface RelationshipsContainerViewProps +{ + relationships: RelationshipStatusInfoMessageParser; +} + +interface RelationshipsContainerRelationshipViewProps +{ + type: number; +} + +export const RelationshipsContainerView: FC = props => +{ + const { relationships = null } = props; + + const RelationshipComponent = ({ type }: RelationshipsContainerRelationshipViewProps) => + { + const relationshipInfo = (relationships && relationships.relationshipStatusMap.hasKey(type)) ? relationships.relationshipStatusMap.getValue(type) : null; + const relationshipName = RelationshipStatusEnum.RELATIONSHIP_NAMES[type].toLocaleLowerCase(); + + return ( +
+ + + +
+
+

(relationshipInfo && (relationshipInfo.randomFriendId >= 1) && GetUserProfile(relationshipInfo.randomFriendId)) }> + { (!relationshipInfo || (relationshipInfo.friendCount === 0)) && + LocalizeText('extendedprofile.add.friends') } + { (relationshipInfo && (relationshipInfo.friendCount >= 1)) && + relationshipInfo.randomFriendName } +

+ { (relationshipInfo && (relationshipInfo.friendCount >= 1)) && +
+ +
} +
+

+ { (!relationshipInfo || (relationshipInfo.friendCount === 0)) && + LocalizeText('extendedprofile.no.friends.in.this.category') } + { (relationshipInfo && (relationshipInfo.friendCount > 1)) && + LocalizeText(`extendedprofile.relstatus.others.${ relationshipName }`, [ 'count' ], [ (relationshipInfo.friendCount - 1).toString() ]) } +   +

+
+
+ ); + }; + + return ( + <> + + + + + ); +}; diff --git a/src/components/user-profile/UserContainerView.tsx b/src/components/user-profile/UserContainerView.tsx new file mode 100644 index 0000000..20d0768 --- /dev/null +++ b/src/components/user-profile/UserContainerView.tsx @@ -0,0 +1,71 @@ +import { GetSessionDataManager, RequestFriendComposer, UserProfileParser } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { FriendlyTime, LocalizeText, SendMessageComposer } from '../../api'; +import { LayoutAvatarImageView, Text } from '../../common'; + +export const UserContainerView: FC<{ + userProfile: UserProfileParser; +}> = props => +{ + const { userProfile = null } = props; + const [ requestSent, setRequestSent ] = useState(userProfile.requestSent); + const isOwnProfile = (userProfile.id === GetSessionDataManager().userId); + const canSendFriendRequest = !requestSent && (!isOwnProfile && !userProfile.isMyFriend && !userProfile.requestSent); + + const addFriend = () => + { + setRequestSent(true); + + SendMessageComposer(new RequestFriendComposer(userProfile.username)); + }; + + useEffect(() => + { + setRequestSent(userProfile.requestSent); + }, [ userProfile ]); + + return ( +
+
+ +
+
+
+

{ userProfile.username }

+

{ userProfile.motto }

+
+
+

+ { LocalizeText('extendedprofile.created') } { userProfile.registration } +

+

+ { LocalizeText('extendedprofile.last.login') } { FriendlyTime.format(userProfile.secondsSinceLastVisit, '.ago', 2) } +

+

+ { LocalizeText('extendedprofile.achievementscore') } { userProfile.achievementPoints } +

+
+
+ { userProfile.isOnline && + } + { !userProfile.isOnline && + } +
+ { canSendFriendRequest && + { LocalizeText('extendedprofile.addasafriend') } } + { !canSendFriendRequest && + <> + + { isOwnProfile && +

{ LocalizeText('extendedprofile.me') }

} + { userProfile.isMyFriend && +

{ LocalizeText('extendedprofile.friend') }

} + { (requestSent || userProfile.requestSent) && +

{ LocalizeText('extendedprofile.friendrequestsent') }

} + } +
+
+
+
+ ); +}; diff --git a/src/components/user-profile/UserProfileView.tsx b/src/components/user-profile/UserProfileView.tsx new file mode 100644 index 0000000..f57612b --- /dev/null +++ b/src/components/user-profile/UserProfileView.tsx @@ -0,0 +1,125 @@ +import { CreateLinkEvent, ExtendedProfileChangedMessageEvent, GetSessionDataManager, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomEngineObjectEvent, RoomObjectCategory, RoomObjectType, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserProfileEvent, UserProfileParser, UserRelationshipsComposer } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { GetRoomSession, GetUserProfile, LocalizeText, SendMessageComposer } from '../../api'; +import { Flex, Grid, LayoutBadgeImageView, Text } from '../../common'; +import { useMessageEvent, useNitroEvent } from '../../hooks'; +import { NitroCard } from '../../layout'; +import { FriendsContainerView } from './FriendsContainerView'; +import { GroupsContainerView } from './GroupsContainerView'; +import { UserContainerView } from './UserContainerView'; + +export const UserProfileView: FC<{}> = props => +{ + const [ userProfile, setUserProfile ] = useState(null); + const [ userBadges, setUserBadges ] = useState([]); + const [ userRelationships, setUserRelationships ] = useState(null); + + const onClose = () => + { + setUserProfile(null); + setUserBadges([]); + setUserRelationships(null); + }; + + const onLeaveGroup = () => + { + if(!userProfile || (userProfile.id !== GetSessionDataManager().userId)) return; + + GetUserProfile(userProfile.id); + }; + + useMessageEvent(UserCurrentBadgesEvent, event => + { + const parser = event.getParser(); + + if(!userProfile || (parser.userId !== userProfile.id)) return; + + setUserBadges(parser.badges); + }); + + useMessageEvent(RelationshipStatusInfoEvent, event => + { + const parser = event.getParser(); + + if(!userProfile || (parser.userId !== userProfile.id)) return; + + setUserRelationships(parser); + }); + + useMessageEvent(UserProfileEvent, event => + { + const parser = event.getParser(); + + let isSameProfile = false; + + setUserProfile(prevValue => + { + if(prevValue && prevValue.id) isSameProfile = (prevValue.id === parser.id); + + return parser; + }); + + if(!isSameProfile) + { + setUserBadges([]); + setUserRelationships(null); + } + + SendMessageComposer(new UserCurrentBadgesComposer(parser.id)); + SendMessageComposer(new UserRelationshipsComposer(parser.id)); + }); + + useMessageEvent(ExtendedProfileChangedMessageEvent, event => + { + const parser = event.getParser(); + + if(parser.userId != userProfile?.id) return; + + GetUserProfile(parser.userId); + }); + + useNitroEvent(RoomEngineObjectEvent.SELECTED, event => + { + if(!userProfile) return; + + if(event.category !== RoomObjectCategory.UNIT) return; + + const userData = GetRoomSession().userDataManager.getUserDataByIndex(event.objectId); + + if(userData.type !== RoomObjectType.USER) return; + + GetUserProfile(userData.webID); + }); + + if(!userProfile) return null; + + return ( + + + + +
+ +
+ { userBadges && (userBadges.length > 0) && userBadges.map((badge, index) => ) } +
+
+
+ { userRelationships && + } +
+
+ + CreateLinkEvent(`navigator/search/hotel_view/owner:${ userProfile.username }`) }> + + { LocalizeText('extendedprofile.rooms') } + + + +
+
+ ); +}; diff --git a/src/components/user-settings/UserSettingsView.tsx b/src/components/user-settings/UserSettingsView.tsx new file mode 100644 index 0000000..d52df74 --- /dev/null +++ b/src/components/user-settings/UserSettingsView.tsx @@ -0,0 +1,188 @@ +import { AddLinkEventTracker, ILinkEventTracker, NitroSettingsEvent, RemoveLinkEventTracker, UserSettingsCameraFollowComposer, UserSettingsEvent, UserSettingsOldChatComposer, UserSettingsRoomInvitesComposer, UserSettingsSoundComposer } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { FaVolumeDown, FaVolumeMute, FaVolumeUp } from 'react-icons/fa'; +import { DispatchMainEvent, DispatchUiEvent, LocalizeText, SendMessageComposer } from '../../api'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common'; +import { useCatalogPlaceMultipleItems, useCatalogSkipPurchaseConfirmation, useMessageEvent } from '../../hooks'; +import { classNames } from '../../layout'; + +export const UserSettingsView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ userSettings, setUserSettings ] = useState(null); + const [ catalogPlaceMultipleObjects, setCatalogPlaceMultipleObjects ] = useCatalogPlaceMultipleItems(); + const [ catalogSkipPurchaseConfirmation, setCatalogSkipPurchaseConfirmation ] = useCatalogSkipPurchaseConfirmation(); + + const processAction = (type: string, value?: boolean | number | string) => + { + let doUpdate = true; + + const clone = userSettings.clone(); + + switch(type) + { + case 'close_view': + setIsVisible(false); + doUpdate = false; + return; + case 'oldchat': + clone.oldChat = value as boolean; + SendMessageComposer(new UserSettingsOldChatComposer(clone.oldChat)); + break; + case 'room_invites': + clone.roomInvites = value as boolean; + SendMessageComposer(new UserSettingsRoomInvitesComposer(clone.roomInvites)); + break; + case 'camera_follow': + clone.cameraFollow = value as boolean; + SendMessageComposer(new UserSettingsCameraFollowComposer(clone.cameraFollow)); + break; + case 'system_volume': + clone.volumeSystem = value as number; + clone.volumeSystem = Math.max(0, clone.volumeSystem); + clone.volumeSystem = Math.min(100, clone.volumeSystem); + break; + case 'furni_volume': + clone.volumeFurni = value as number; + clone.volumeFurni = Math.max(0, clone.volumeFurni); + clone.volumeFurni = Math.min(100, clone.volumeFurni); + break; + case 'trax_volume': + clone.volumeTrax = value as number; + clone.volumeTrax = Math.max(0, clone.volumeTrax); + clone.volumeTrax = Math.min(100, clone.volumeTrax); + break; + } + + if(doUpdate) setUserSettings(clone); + + DispatchMainEvent(clone); + }; + + const saveRangeSlider = (type: string) => + { + switch(type) + { + case 'volume': + SendMessageComposer(new UserSettingsSoundComposer(Math.round(userSettings.volumeSystem), Math.round(userSettings.volumeFurni), Math.round(userSettings.volumeTrax))); + break; + } + }; + + useMessageEvent(UserSettingsEvent, event => + { + const parser = event.getParser(); + const settingsEvent = new NitroSettingsEvent(); + + settingsEvent.volumeSystem = parser.volumeSystem; + settingsEvent.volumeFurni = parser.volumeFurni; + settingsEvent.volumeTrax = parser.volumeTrax; + settingsEvent.oldChat = parser.oldChat; + settingsEvent.roomInvites = parser.roomInvites; + settingsEvent.cameraFollow = parser.cameraFollow; + settingsEvent.flags = parser.flags; + settingsEvent.chatType = parser.chatType; + + setUserSettings(settingsEvent); + DispatchMainEvent(settingsEvent); + }); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; + } + }, + eventUrlPrefix: 'user-settings/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + useEffect(() => + { + if(!userSettings) return; + + DispatchUiEvent(userSettings); + }, [ userSettings ]); + + if(!isVisible || !userSettings) return null; + + return ( + + processAction('close_view') } /> + +
+
+ processAction('oldchat', event.target.checked) } /> + { LocalizeText('memenu.settings.chat.prefer.old.chat') } +
+
+ processAction('room_invites', event.target.checked) } /> + { LocalizeText('memenu.settings.other.ignore.room.invites') } +
+
+ processAction('camera_follow', event.target.checked) } /> + { LocalizeText('memenu.settings.other.disable.room.camera.follow') } +
+
+ setCatalogPlaceMultipleObjects(event.target.checked) } /> + { LocalizeText('memenu.settings.other.place.multiple.objects') } +
+
+ setCatalogSkipPurchaseConfirmation(event.target.checked) } /> + { LocalizeText('memenu.settings.other.skip.purchase.confirmation') } +
+
+
+ { LocalizeText('widget.memenu.settings.volume') } +
+ { LocalizeText('widget.memenu.settings.volume.ui') } +
+ { (userSettings.volumeSystem === 0) && = 50) && 'text-muted', 'fa-icon') } /> } + { (userSettings.volumeSystem > 0) && = 50) && 'text-muted', 'fa-icon') } /> } + processAction('system_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } /> + +
+
+
+ { LocalizeText('widget.memenu.settings.volume.furni') } +
+ { (userSettings.volumeFurni === 0) && = 50) && 'text-muted', 'fa-icon') } /> } + { (userSettings.volumeFurni > 0) && = 50) && 'text-muted', 'fa-icon') } /> } + processAction('furni_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } /> + +
+
+
+ { LocalizeText('widget.memenu.settings.volume.trax') } +
+ { (userSettings.volumeTrax === 0) && = 50) && 'text-muted', 'fa-icon') } /> } + { (userSettings.volumeTrax > 0) && = 50) && 'text-muted', 'fa-icon') } /> } + processAction('trax_volume', event.target.value) } onMouseUp={ () => saveRangeSlider('volume') } /> + +
+
+
+
+
+ ); +}; diff --git a/src/components/wired/WiredView.tsx b/src/components/wired/WiredView.tsx new file mode 100644 index 0000000..0f073ab --- /dev/null +++ b/src/components/wired/WiredView.tsx @@ -0,0 +1,21 @@ +import { ConditionDefinition, TriggerDefinition, WiredActionDefinition } from '@nitrots/nitro-renderer'; +import { FC } from 'react'; +import { useWired } from '../../hooks'; +import { WiredActionLayoutView } from './views/actions/WiredActionLayoutView'; +import { WiredConditionLayoutView } from './views/conditions/WiredConditionLayoutView'; +import { WiredTriggerLayoutView } from './views/triggers/WiredTriggerLayoutView'; + +export const WiredView: FC<{}> = props => +{ + const { trigger = null } = useWired(); + + if(!trigger) return null; + + if(trigger instanceof WiredActionDefinition) return WiredActionLayoutView(trigger.code); + + if(trigger instanceof TriggerDefinition) return WiredTriggerLayoutView(trigger.code); + + if(trigger instanceof ConditionDefinition) return WiredConditionLayoutView(trigger.code); + + return null; +}; diff --git a/src/components/wired/views/WiredBaseView.tsx b/src/components/wired/views/WiredBaseView.tsx new file mode 100644 index 0000000..8e8fb37 --- /dev/null +++ b/src/components/wired/views/WiredBaseView.tsx @@ -0,0 +1,114 @@ +import { GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { FC, PropsWithChildren, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../api'; +import { Button, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; +import { useWired } from '../../../hooks'; +import { WiredFurniSelectorView } from './WiredFurniSelectorView'; + +export interface WiredBaseViewProps +{ + wiredType: string; + requiresFurni: number; + hasSpecialInput: boolean; + save: () => void; + validate?: () => boolean; +} + +export const WiredBaseView: FC> = props => +{ + const { wiredType = '', requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, validate = null, children = null, hasSpecialInput = false } = props; + const [ wiredName, setWiredName ] = useState(null); + const [ wiredDescription, setWiredDescription ] = useState(null); + const [ needsSave, setNeedsSave ] = useState(false); + const { trigger = null, setTrigger = null, setIntParams = null, setStringParam = null, setFurniIds = null, setAllowsFurni = null, saveWired = null } = useWired(); + + const onClose = () => setTrigger(null); + + const onSave = () => + { + if(validate && !validate()) return; + + if(save) save(); + + setNeedsSave(true); + }; + + useEffect(() => + { + if(!needsSave) return; + + saveWired(); + + setNeedsSave(false); + }, [ needsSave, saveWired ]); + + useEffect(() => + { + if(!trigger) return; + + const spriteId = (trigger.spriteId || -1); + const furniData = GetSessionDataManager().getFloorItemData(spriteId); + + if(!furniData) + { + setWiredName(('NAME: ' + spriteId)); + setWiredDescription(('NAME: ' + spriteId)); + } + else + { + setWiredName(furniData.name); + setWiredDescription(furniData.description); + } + + if(hasSpecialInput) + { + setIntParams(trigger.intData); + setStringParam(trigger.stringData); + } + + if(requiresFurni > WiredFurniType.STUFF_SELECTION_OPTION_NONE) + { + setFurniIds(prevValue => + { + if(prevValue && prevValue.length) WiredSelectionVisualizer.clearSelectionShaderFromFurni(prevValue); + + if(trigger.selectedItems && trigger.selectedItems.length) + { + WiredSelectionVisualizer.applySelectionShaderToFurni(trigger.selectedItems); + + return trigger.selectedItems; + } + + return []; + }); + } + + setAllowsFurni(requiresFurni); + }, [ trigger, hasSpecialInput, requiresFurni, setIntParams, setStringParam, setFurniIds, setAllowsFurni ]); + + return ( + + + +
+
+ + { wiredName } +
+ { wiredDescription } +
+ { !!children &&
} + { children } + { (requiresFurni > WiredFurniType.STUFF_SELECTION_OPTION_NONE) && + <> +
+ + } +
+ + +
+
+
+ ); +}; diff --git a/src/components/wired/views/WiredFurniSelectorView.tsx b/src/components/wired/views/WiredFurniSelectorView.tsx new file mode 100644 index 0000000..1363326 --- /dev/null +++ b/src/components/wired/views/WiredFurniSelectorView.tsx @@ -0,0 +1,16 @@ +import { FC } from 'react'; +import { LocalizeText } from '../../../api'; +import { Text } from '../../../common'; +import { useWired } from '../../../hooks'; + +export const WiredFurniSelectorView: FC<{}> = props => +{ + const { trigger = null, furniIds = [] } = useWired(); + + return ( +
+ { LocalizeText('wiredfurni.pickfurnis.caption', [ 'count', 'limit' ], [ furniIds.length.toString(), trigger.maximumItemSelectionCount.toString() ]) } + { LocalizeText('wiredfurni.pickfurnis.desc') } +
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionBaseView.tsx b/src/components/wired/views/actions/WiredActionBaseView.tsx new file mode 100644 index 0000000..aef1cdb --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBaseView.tsx @@ -0,0 +1,41 @@ +import { WiredActionDefinition } from '@nitrots/nitro-renderer'; +import { FC, PropsWithChildren, useEffect } from 'react'; +import ReactSlider from 'react-slider'; +import { GetWiredTimeLocale, LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredBaseView } from '../WiredBaseView'; + +export interface WiredActionBaseViewProps +{ + hasSpecialInput: boolean; + requiresFurni: number; + save: () => void; +} + +export const WiredActionBaseView: FC> = props => +{ + const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, hasSpecialInput = false, children = null } = props; + const { trigger = null, actionDelay = 0, setActionDelay = null } = useWired(); + + useEffect(() => + { + setActionDelay((trigger as WiredActionDefinition).delayInPulses); + }, [ trigger, setActionDelay ]); + + return ( + + { children } + { !!children &&
} +
+ { LocalizeText('wiredfurni.params.delay', [ 'seconds' ], [ GetWiredTimeLocale(actionDelay) ]) } + setActionDelay(event) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionBotChangeFigureView.tsx b/src/components/wired/views/actions/WiredActionBotChangeFigureView.tsx new file mode 100644 index 0000000..e359037 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBotChangeFigureView.tsx @@ -0,0 +1,39 @@ +import { GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WIRED_STRING_DELIMETER, WiredFurniType } from '../../../../api'; +import { Button, LayoutAvatarImageView, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +const DEFAULT_FIGURE: string = 'hd-180-1.ch-210-66.lg-270-82.sh-290-81'; + +export const WiredActionBotChangeFigureView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const [ figure, setFigure ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam((botName + WIRED_STRING_DELIMETER + figure)); + + useEffect(() => + { + const data = trigger.stringData.split(WIRED_STRING_DELIMETER); + + if(data.length > 0) setBotName(data[0]); + if(data.length > 1) setFigure(data[1].length > 0 ? data[1] : DEFAULT_FIGURE); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
+
+ + +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionBotFollowAvatarView.tsx b/src/components/wired/views/actions/WiredActionBotFollowAvatarView.tsx new file mode 100644 index 0000000..9576ea0 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBotFollowAvatarView.tsx @@ -0,0 +1,44 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionBotFollowAvatarView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const [ followMode, setFollowMode ] = useState(-1); + const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); + + const save = () => + { + setStringParam(botName); + setIntParams([ followMode ]); + }; + + useEffect(() => + { + setBotName(trigger.stringData); + setFollowMode((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
+
+
+ setFollowMode(1) } /> + { LocalizeText('wiredfurni.params.start.following') } +
+
+ setFollowMode(0) } /> + { LocalizeText('wiredfurni.params.stop.following') } +
+
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionBotGiveHandItemView.tsx b/src/components/wired/views/actions/WiredActionBotGiveHandItemView.tsx new file mode 100644 index 0000000..0dc2bc4 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBotGiveHandItemView.tsx @@ -0,0 +1,43 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +const ALLOWED_HAND_ITEM_IDS: number[] = [ 2, 5, 7, 8, 9, 10, 27 ]; + +export const WiredActionBotGiveHandItemView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const [ handItemId, setHandItemId ] = useState(-1); + const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); + + const save = () => + { + setStringParam(botName); + setIntParams([ handItemId ]); + }; + + useEffect(() => + { + setBotName(trigger.stringData); + setHandItemId((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
+
+ { LocalizeText('wiredfurni.params.handitem') } + +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionBotMoveView.tsx b/src/components/wired/views/actions/WiredActionBotMoveView.tsx new file mode 100644 index 0000000..644b34e --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBotMoveView.tsx @@ -0,0 +1,28 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionBotMoveView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam(botName); + + useEffect(() => + { + setBotName(trigger.stringData); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionBotTalkToAvatarView.tsx b/src/components/wired/views/actions/WiredActionBotTalkToAvatarView.tsx new file mode 100644 index 0000000..b69a972 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBotTalkToAvatarView.tsx @@ -0,0 +1,53 @@ +import { FC, useEffect, useState } from 'react'; +import { GetConfigurationValue, LocalizeText, WIRED_STRING_DELIMETER, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionBotTalkToAvatarView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const [ message, setMessage ] = useState(''); + const [ talkMode, setTalkMode ] = useState(-1); + const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); + + const save = () => + { + setStringParam(botName + WIRED_STRING_DELIMETER + message); + setIntParams([ talkMode ]); + }; + + useEffect(() => + { + const data = trigger.stringData.split(WIRED_STRING_DELIMETER); + + if(data.length > 0) setBotName(data[0]); + if(data.length > 1) setMessage(data[1].length > 0 ? data[1] : ''); + + setTalkMode((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
+
+ { LocalizeText('wiredfurni.params.message') } + ('wired.action.bot.talk.to.avatar.max.length', 64) } type="text" value={ message } onChange={ event => setMessage(event.target.value) } /> +
+
+
+ setTalkMode(0) } /> + { LocalizeText('wiredfurni.params.talk') } +
+
+ setTalkMode(1) } /> + { LocalizeText('wiredfurni.params.whisper') } +
+
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionBotTalkView.tsx b/src/components/wired/views/actions/WiredActionBotTalkView.tsx new file mode 100644 index 0000000..31b6a14 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBotTalkView.tsx @@ -0,0 +1,53 @@ +import { FC, useEffect, useState } from 'react'; +import { GetConfigurationValue, LocalizeText, WIRED_STRING_DELIMETER, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionBotTalkView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const [ message, setMessage ] = useState(''); + const [ talkMode, setTalkMode ] = useState(-1); + const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); + + const save = () => + { + setStringParam(botName + WIRED_STRING_DELIMETER + message); + setIntParams([ talkMode ]); + }; + + useEffect(() => + { + const data = trigger.stringData.split(WIRED_STRING_DELIMETER); + + if(data.length > 0) setBotName(data[0]); + if(data.length > 1) setMessage(data[1].length > 0 ? data[1] : ''); + + setTalkMode((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
+
+ { LocalizeText('wiredfurni.params.message') } + ('wired.action.bot.talk.max.length', 64) } type="text" value={ message } onChange={ event => setMessage(event.target.value) } /> +
+
+
+ setTalkMode(0) } /> + { LocalizeText('wiredfurni.params.talk') } +
+
+ setTalkMode(1) } /> + { LocalizeText('wiredfurni.params.shout') } +
+
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionBotTeleportView.tsx b/src/components/wired/views/actions/WiredActionBotTeleportView.tsx new file mode 100644 index 0000000..1979930 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionBotTeleportView.tsx @@ -0,0 +1,28 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionBotTeleportView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam(botName); + + useEffect(() => + { + setBotName(trigger.stringData); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionCallAnotherStackView.tsx b/src/components/wired/views/actions/WiredActionCallAnotherStackView.tsx new file mode 100644 index 0000000..69c17fe --- /dev/null +++ b/src/components/wired/views/actions/WiredActionCallAnotherStackView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionCallAnotherStackView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/actions/WiredActionChaseView.tsx b/src/components/wired/views/actions/WiredActionChaseView.tsx new file mode 100644 index 0000000..d0e1c41 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionChaseView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionChaseView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/actions/WiredActionChatView.tsx b/src/components/wired/views/actions/WiredActionChatView.tsx new file mode 100644 index 0000000..8f622c2 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionChatView.tsx @@ -0,0 +1,28 @@ +import { FC, useEffect, useState } from 'react'; +import { GetConfigurationValue, LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionChatView: FC<{}> = props => +{ + const [ message, setMessage ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam(message); + + useEffect(() => + { + setMessage(trigger.stringData); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.message') } + ('wired.action.chat.max.length', 100) } type="text" value={ message } onChange={ event => setMessage(event.target.value) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionFleeView.tsx b/src/components/wired/views/actions/WiredActionFleeView.tsx new file mode 100644 index 0000000..e3e5776 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionFleeView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionFleeView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/actions/WiredActionGiveRewardView.tsx b/src/components/wired/views/actions/WiredActionGiveRewardView.tsx new file mode 100644 index 0000000..8e87167 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionGiveRewardView.tsx @@ -0,0 +1,161 @@ +import { FC, useEffect, useState } from 'react'; +import { FaPlus, FaTrash } from 'react-icons/fa'; +import ReactSlider from 'react-slider'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Button, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionGiveRewardView: FC<{}> = props => +{ + const [ limitEnabled, setLimitEnabled ] = useState(false); + const [ rewardTime, setRewardTime ] = useState(1); + const [ uniqueRewards, setUniqueRewards ] = useState(false); + const [ rewardsLimit, setRewardsLimit ] = useState(1); + const [ limitationInterval, setLimitationInterval ] = useState(1); + const [ rewards, setRewards ] = useState<{ isBadge: boolean, itemCode: string, probability: number }[]>([]); + const { trigger = null, setIntParams = null, setStringParam = null } = useWired(); + + const addReward = () => setRewards(rewards => [ ...rewards, { isBadge: false, itemCode: '', probability: null } ]); + + const removeReward = (index: number) => + { + setRewards(prevValue => + { + const newValues = Array.from(prevValue); + + newValues.splice(index, 1); + + return newValues; + }); + }; + + const updateReward = (index: number, isBadge: boolean, itemCode: string, probability: number) => + { + const rewardsClone = Array.from(rewards); + const reward = rewardsClone[index]; + + if(!reward) return; + + reward.isBadge = isBadge; + reward.itemCode = itemCode; + reward.probability = probability; + + setRewards(rewardsClone); + }; + + const save = () => + { + let stringRewards = []; + + for(const reward of rewards) + { + if(!reward.itemCode) continue; + + const rewardsString = [ reward.isBadge ? '0' : '1', reward.itemCode, reward.probability.toString() ]; + stringRewards.push(rewardsString.join(',')); + } + + if(stringRewards.length > 0) + { + setStringParam(stringRewards.join(';')); + setIntParams([ rewardTime, uniqueRewards ? 1 : 0, rewardsLimit, limitationInterval ]); + } + }; + + useEffect(() => + { + const readRewards: { isBadge: boolean, itemCode: string, probability: number }[] = []; + + if(trigger.stringData.length > 0 && trigger.stringData.includes(';')) + { + const splittedRewards = trigger.stringData.split(';'); + + for(const rawReward of splittedRewards) + { + const reward = rawReward.split(','); + + if(reward.length !== 3) continue; + + readRewards.push({ isBadge: reward[0] === '0', itemCode: reward[1], probability: Number(reward[2]) }); + } + } + + if(readRewards.length === 0) readRewards.push({ isBadge: false, itemCode: '', probability: null }); + + setRewardTime((trigger.intData.length > 0) ? trigger.intData[0] : 0); + setUniqueRewards((trigger.intData.length > 1) ? (trigger.intData[1] === 1) : false); + setRewardsLimit((trigger.intData.length > 2) ? trigger.intData[2] : 0); + setLimitationInterval((trigger.intData.length > 3) ? trigger.intData[3] : 0); + setLimitEnabled((trigger.intData.length > 3) ? trigger.intData[3] > 0 : false); + setRewards(readRewards); + }, [ trigger ]); + + return ( + +
+ setLimitEnabled(event.target.checked) } /> + { LocalizeText('wiredfurni.params.prizelimit', [ 'amount' ], [ limitEnabled ? rewardsLimit.toString() : '' ]) } +
+ { !limitEnabled && + + Reward limit not set. Make sure rewards are badges or non-tradeable items. + } + { limitEnabled && + setRewardsLimit(event) } /> } +
+
+ How often can a user be rewarded? +
+ + { (rewardTime > 0) && setLimitationInterval(Number(event.target.value)) } /> } +
+
+
+
+ setUniqueRewards(e.target.checked) } /> + Unique rewards +
+ + If checked each reward will be given once to each user. This will disable the probabilities option. + +
+
+ Rewards + +
+
+ { rewards && rewards.map((reward, index) => + { + return ( +
+
+ updateReward(index, e.target.checked, reward.itemCode, reward.probability) } /> + Badge? +
+ updateReward(index, reward.isBadge, e.target.value, reward.probability) } /> + updateReward(index, reward.isBadge, reward.itemCode, Number(e.target.value)) } /> + { (index > 0) && + } +
+ ); + }) } +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionGiveScoreToPredefinedTeamView.tsx b/src/components/wired/views/actions/WiredActionGiveScoreToPredefinedTeamView.tsx new file mode 100644 index 0000000..02d2281 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionGiveScoreToPredefinedTeamView.tsx @@ -0,0 +1,67 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionGiveScoreToPredefinedTeamView: FC<{}> = props => +{ + const [ points, setPoints ] = useState(1); + const [ time, setTime ] = useState(1); + const [ selectedTeam, setSelectedTeam ] = useState(1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ points, time, selectedTeam ]); + + useEffect(() => + { + if(trigger.intData.length >= 2) + { + setPoints(trigger.intData[0]); + setTime(trigger.intData[1]); + setSelectedTeam(trigger.intData[2]); + } + else + { + setPoints(1); + setTime(1); + setSelectedTeam(1); + } + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.setpoints', [ 'points' ], [ points.toString() ]) } + setPoints(event) } /> +
+
+ { LocalizeText('wiredfurni.params.settimesingame', [ 'times' ], [ time.toString() ]) } + setTime(event) } /> +
+
+ { LocalizeText('wiredfurni.params.team') } + { [ 1, 2, 3, 4 ].map(value => + { + return ( +
+ setSelectedTeam(value) } /> + { LocalizeText('wiredfurni.params.team.' + value) } +
+ ); + }) } +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionGiveScoreView.tsx b/src/components/wired/views/actions/WiredActionGiveScoreView.tsx new file mode 100644 index 0000000..a2b9f86 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionGiveScoreView.tsx @@ -0,0 +1,52 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionGiveScoreView: FC<{}> = props => +{ + const [ points, setPoints ] = useState(1); + const [ time, setTime ] = useState(1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ points, time ]); + + useEffect(() => + { + if(trigger.intData.length >= 2) + { + setPoints(trigger.intData[0]); + setTime(trigger.intData[1]); + } + else + { + setPoints(1); + setTime(1); + } + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.setpoints', [ 'points' ], [ points.toString() ]) } + setPoints(event) } /> +
+
+ { LocalizeText('wiredfurni.params.settimesingame', [ 'times' ], [ time.toString() ]) } + setTime(event) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionJoinTeamView.tsx b/src/components/wired/views/actions/WiredActionJoinTeamView.tsx new file mode 100644 index 0000000..10b8c24 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionJoinTeamView.tsx @@ -0,0 +1,35 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionJoinTeamView: FC<{}> = props => +{ + const [ selectedTeam, setSelectedTeam ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ selectedTeam ]); + + useEffect(() => + { + setSelectedTeam((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.team') } + { [ 1, 2, 3, 4 ].map(team => + { + return ( +
+ setSelectedTeam(team) } /> + { LocalizeText(`wiredfurni.params.team.${ team }`) } +
+ ); + }) } +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionKickFromRoomView.tsx b/src/components/wired/views/actions/WiredActionKickFromRoomView.tsx new file mode 100644 index 0000000..002426d --- /dev/null +++ b/src/components/wired/views/actions/WiredActionKickFromRoomView.tsx @@ -0,0 +1,28 @@ +import { FC, useEffect, useState } from 'react'; +import { GetConfigurationValue, LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionKickFromRoomView: FC<{}> = props => +{ + const [ message, setMessage ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam(message); + + useEffect(() => + { + setMessage(trigger.stringData); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.message') } + ('wired.action.kick.from.room.max.length', 100) } type="text" value={ message } onChange={ event => setMessage(event.target.value) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionLayoutView.tsx b/src/components/wired/views/actions/WiredActionLayoutView.tsx new file mode 100644 index 0000000..36d14d4 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionLayoutView.tsx @@ -0,0 +1,85 @@ +import { WiredActionLayoutCode } from '../../../../api'; +import { WiredActionBotChangeFigureView } from './WiredActionBotChangeFigureView'; +import { WiredActionBotFollowAvatarView } from './WiredActionBotFollowAvatarView'; +import { WiredActionBotGiveHandItemView } from './WiredActionBotGiveHandItemView'; +import { WiredActionBotMoveView } from './WiredActionBotMoveView'; +import { WiredActionBotTalkToAvatarView } from './WiredActionBotTalkToAvatarView'; +import { WiredActionBotTalkView } from './WiredActionBotTalkView'; +import { WiredActionBotTeleportView } from './WiredActionBotTeleportView'; +import { WiredActionCallAnotherStackView } from './WiredActionCallAnotherStackView'; +import { WiredActionChaseView } from './WiredActionChaseView'; +import { WiredActionChatView } from './WiredActionChatView'; +import { WiredActionFleeView } from './WiredActionFleeView'; +import { WiredActionGiveRewardView } from './WiredActionGiveRewardView'; +import { WiredActionGiveScoreToPredefinedTeamView } from './WiredActionGiveScoreToPredefinedTeamView'; +import { WiredActionGiveScoreView } from './WiredActionGiveScoreView'; +import { WiredActionJoinTeamView } from './WiredActionJoinTeamView'; +import { WiredActionKickFromRoomView } from './WiredActionKickFromRoomView'; +import { WiredActionLeaveTeamView } from './WiredActionLeaveTeamView'; +import { WiredActionMoveAndRotateFurniView } from './WiredActionMoveAndRotateFurniView'; +import { WiredActionMoveFurniToView } from './WiredActionMoveFurniToView'; +import { WiredActionMoveFurniView } from './WiredActionMoveFurniView'; +import { WiredActionMuteUserView } from './WiredActionMuteUserView'; +import { WiredActionResetView } from './WiredActionResetView'; +import { WiredActionSetFurniStateToView } from './WiredActionSetFurniStateToView'; +import { WiredActionTeleportView } from './WiredActionTeleportView'; +import { WiredActionToggleFurniStateView } from './WiredActionToggleFurniStateView'; + +export const WiredActionLayoutView = (code: number) => +{ + switch(code) + { + case WiredActionLayoutCode.BOT_CHANGE_FIGURE: + return ; + case WiredActionLayoutCode.BOT_FOLLOW_AVATAR: + return ; + case WiredActionLayoutCode.BOT_GIVE_HAND_ITEM: + return ; + case WiredActionLayoutCode.BOT_MOVE: + return ; + case WiredActionLayoutCode.BOT_TALK: + return ; + case WiredActionLayoutCode.BOT_TALK_DIRECT_TO_AVTR: + return ; + case WiredActionLayoutCode.BOT_TELEPORT: + return ; + case WiredActionLayoutCode.CALL_ANOTHER_STACK: + return ; + case WiredActionLayoutCode.CHASE: + return ; + case WiredActionLayoutCode.CHAT: + return ; + case WiredActionLayoutCode.FLEE: + return ; + case WiredActionLayoutCode.GIVE_REWARD: + return ; + case WiredActionLayoutCode.GIVE_SCORE: + return ; + case WiredActionLayoutCode.GIVE_SCORE_TO_PREDEFINED_TEAM: + return ; + case WiredActionLayoutCode.JOIN_TEAM: + return ; + case WiredActionLayoutCode.KICK_FROM_ROOM: + return ; + case WiredActionLayoutCode.LEAVE_TEAM: + return ; + case WiredActionLayoutCode.MOVE_FURNI: + return ; + case WiredActionLayoutCode.MOVE_AND_ROTATE_FURNI: + return ; + case WiredActionLayoutCode.MOVE_FURNI_TO: + return ; + case WiredActionLayoutCode.MUTE_USER: + return ; + case WiredActionLayoutCode.RESET: + return ; + case WiredActionLayoutCode.SET_FURNI_STATE: + return ; + case WiredActionLayoutCode.TELEPORT: + return ; + case WiredActionLayoutCode.TOGGLE_FURNI_STATE: + return ; + } + + return null; +}; diff --git a/src/components/wired/views/actions/WiredActionLeaveTeamView.tsx b/src/components/wired/views/actions/WiredActionLeaveTeamView.tsx new file mode 100644 index 0000000..9202ed3 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionLeaveTeamView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionLeaveTeamView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/actions/WiredActionMoveAndRotateFurniView.tsx b/src/components/wired/views/actions/WiredActionMoveAndRotateFurniView.tsx new file mode 100644 index 0000000..d30b43b --- /dev/null +++ b/src/components/wired/views/actions/WiredActionMoveAndRotateFurniView.tsx @@ -0,0 +1,82 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +const directionOptions: { value: number, icon: string }[] = [ + { + value: 0, + icon: 'ne' + }, + { + value: 2, + icon: 'se' + }, + { + value: 4, + icon: 'sw' + }, + { + value: 6, + icon: 'nw' + } +]; + +const rotationOptions: number[] = [ 0, 1, 2, 3, 4, 5, 6 ]; + +export const WiredActionMoveAndRotateFurniView: FC<{}> = props => +{ + const [ movement, setMovement ] = useState(-1); + const [ rotation, setRotation ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ movement, rotation ]); + + useEffect(() => + { + if(trigger.intData.length >= 2) + { + setMovement(trigger.intData[0]); + setRotation(trigger.intData[1]); + } + else + { + setMovement(-1); + setRotation(-1); + } + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.startdir') } +
+ { directionOptions.map(option => + { + return ( +
+ setMovement(option.value) } /> + + + +
+ ); + }) } +
+
+
+ { LocalizeText('wiredfurni.params.turn') } + { rotationOptions.map(option => + { + return ( +
+ setRotation(option) } /> + { LocalizeText(`wiredfurni.params.turn.${ option }`) } +
+ ); + }) } +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionMoveFurniToView.tsx b/src/components/wired/views/actions/WiredActionMoveFurniToView.tsx new file mode 100644 index 0000000..0b6d781 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionMoveFurniToView.tsx @@ -0,0 +1,76 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +const directionOptions: { value: number, icon: string }[] = [ + { + value: 0, + icon: 'ne' + }, + { + value: 2, + icon: 'se' + }, + { + value: 4, + icon: 'sw' + }, + { + value: 6, + icon: 'nw' + } +]; + +export const WiredActionMoveFurniToView: FC<{}> = props => +{ + const [ spacing, setSpacing ] = useState(-1); + const [ movement, setMovement ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ movement, spacing ]); + + useEffect(() => + { + if(trigger.intData.length >= 2) + { + setSpacing(trigger.intData[1]); + setMovement(trigger.intData[0]); + } + else + { + setSpacing(-1); + setMovement(-1); + } + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.emptytiles', [ 'tiles' ], [ spacing.toString() ]) } + setSpacing(event) } /> +
+
+ { LocalizeText('wiredfurni.params.startdir') } +
+ { directionOptions.map(value => + { + return ( +
+ setMovement(value.value) } /> + +
+ ); + }) } +
+
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionMoveFurniView.tsx b/src/components/wired/views/actions/WiredActionMoveFurniView.tsx new file mode 100644 index 0000000..1a7c0ed --- /dev/null +++ b/src/components/wired/views/actions/WiredActionMoveFurniView.tsx @@ -0,0 +1,100 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +const directionOptions: { value: number, icon: string }[] = [ + { + value: 4, + icon: 'ne' + }, + { + value: 5, + icon: 'se' + }, + { + value: 6, + icon: 'sw' + }, + { + value: 7, + icon: 'nw' + }, + { + value: 2, + icon: 'mv-2' + }, + { + value: 3, + icon: 'mv-3' + }, + { + value: 1, + icon: 'mv-1' + } +]; + +const rotationOptions: number[] = [ 0, 1, 2, 3 ]; + +export const WiredActionMoveFurniView: FC<{}> = props => +{ + const [ movement, setMovement ] = useState(-1); + const [ rotation, setRotation ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ movement, rotation ]); + + useEffect(() => + { + if(trigger.intData.length >= 2) + { + setMovement(trigger.intData[0]); + setRotation(trigger.intData[1]); + } + else + { + setMovement(-1); + setRotation(-1); + } + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.movefurni') } +
+ setMovement(0) } /> + { LocalizeText('wiredfurni.params.movefurni.0') } +
+
+ { directionOptions.map(option => + { + return ( +
+ setMovement(option.value) } /> + +
+ ); + }) } +
+
+
+
+ { LocalizeText('wiredfurni.params.rotatefurni') } + { rotationOptions.map(option => + { + return ( +
+ setRotation(option) } /> + + { [ 1, 2 ].includes(option) && } + { LocalizeText(`wiredfurni.params.rotatefurni.${ option }`) } + +
+ ); + }) } +
+ + ); +}; diff --git a/src/components/wired/views/actions/WiredActionMuteUserView.tsx b/src/components/wired/views/actions/WiredActionMuteUserView.tsx new file mode 100644 index 0000000..a5e2a3d --- /dev/null +++ b/src/components/wired/views/actions/WiredActionMuteUserView.tsx @@ -0,0 +1,44 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { GetConfigurationValue, LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionMuteUserView: FC<{}> = props => +{ + const [ time, setTime ] = useState(-1); + const [ message, setMessage ] = useState(''); + const { trigger = null, setIntParams = null, setStringParam = null } = useWired(); + + const save = () => + { + setIntParams([ time ]); + setStringParam(message); + }; + + useEffect(() => + { + setTime((trigger.intData.length > 0) ? trigger.intData[0] : 0); + setMessage(trigger.stringData); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.length.minutes', [ 'minutes' ], [ time.toString() ]) } + setTime(event) } /> +
+
+ { LocalizeText('wiredfurni.params.message') } + ('wired.action.mute.user.max.length', 100) } type="text" value={ message } onChange={ event => setMessage(event.target.value) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionResetView.tsx b/src/components/wired/views/actions/WiredActionResetView.tsx new file mode 100644 index 0000000..eed03e3 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionResetView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionResetView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/actions/WiredActionSetFurniStateToView.tsx b/src/components/wired/views/actions/WiredActionSetFurniStateToView.tsx new file mode 100644 index 0000000..96b7237 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionSetFurniStateToView.tsx @@ -0,0 +1,42 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionSetFurniStateToView: FC<{}> = props => +{ + const [ stateFlag, setStateFlag ] = useState(0); + const [ directionFlag, setDirectionFlag ] = useState(0); + const [ positionFlag, setPositionFlag ] = useState(0); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ stateFlag, directionFlag, positionFlag ]); + + useEffect(() => + { + setStateFlag(trigger.getBoolean(0) ? 1 : 0); + setDirectionFlag(trigger.getBoolean(1) ? 1 : 0); + setPositionFlag(trigger.getBoolean(2) ? 1 : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.conditions') } +
+ setStateFlag(event.target.checked ? 1 : 0) } /> + { LocalizeText('wiredfurni.params.condition.state') } +
+
+ setDirectionFlag(event.target.checked ? 1 : 0) } /> + { LocalizeText('wiredfurni.params.condition.direction') } +
+
+ setPositionFlag(event.target.checked ? 1 : 0) } /> + { LocalizeText('wiredfurni.params.condition.position') } +
+
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionTeleportView.tsx b/src/components/wired/views/actions/WiredActionTeleportView.tsx new file mode 100644 index 0000000..04ef42d --- /dev/null +++ b/src/components/wired/views/actions/WiredActionTeleportView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionTeleportView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/actions/WiredActionToggleFurniStateView.tsx b/src/components/wired/views/actions/WiredActionToggleFurniStateView.tsx new file mode 100644 index 0000000..486aa8e --- /dev/null +++ b/src/components/wired/views/actions/WiredActionToggleFurniStateView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +export const WiredActionToggleFurniStateView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/conditions/WiredConditionActorHasHandItem.tsx b/src/components/wired/views/conditions/WiredConditionActorHasHandItem.tsx new file mode 100644 index 0000000..5c6c391 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionActorHasHandItem.tsx @@ -0,0 +1,34 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +const ALLOWED_HAND_ITEM_IDS: number[] = [ 2, 5, 7, 8, 9, 10, 27 ]; + +export const WiredConditionActorHasHandItemView: FC<{}> = props => +{ + const [ handItemId, setHandItemId ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ handItemId ]); + + useEffect(() => + { + setHandItemId((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.handitem') } + +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionActorIsGroupMemberView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsGroupMemberView.tsx new file mode 100644 index 0000000..0f425f3 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionActorIsGroupMemberView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionActorIsGroupMemberView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/conditions/WiredConditionActorIsOnFurniView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsOnFurniView.tsx new file mode 100644 index 0000000..9aedea6 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionActorIsOnFurniView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionActorIsOnFurniView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/conditions/WiredConditionActorIsTeamMemberView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsTeamMemberView.tsx new file mode 100644 index 0000000..7fc69ba --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionActorIsTeamMemberView.tsx @@ -0,0 +1,37 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +const teamIds: number[] = [ 1, 2, 3, 4 ]; + +export const WiredConditionActorIsTeamMemberView: FC<{}> = props => +{ + const [ selectedTeam, setSelectedTeam ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ selectedTeam ]); + + useEffect(() => + { + setSelectedTeam((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.team') } + { teamIds.map(value => + { + return ( +
+ setSelectedTeam(value) } /> + { LocalizeText(`wiredfurni.params.team.${ value }`) } +
+ ); + }) } +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionActorIsWearingBadgeView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsWearingBadgeView.tsx new file mode 100644 index 0000000..5a46f64 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionActorIsWearingBadgeView.tsx @@ -0,0 +1,28 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionActorIsWearingBadgeView: FC<{}> = props => +{ + const [ badge, setBadge ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam(badge); + + useEffect(() => + { + setBadge(trigger.stringData); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.badgecode') } + setBadge(event.target.value) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionActorIsWearingEffectView.tsx b/src/components/wired/views/conditions/WiredConditionActorIsWearingEffectView.tsx new file mode 100644 index 0000000..42188cc --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionActorIsWearingEffectView.tsx @@ -0,0 +1,28 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionActorIsWearingEffectView: FC<{}> = props => +{ + const [ effect, setEffect ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ effect ]); + + useEffect(() => + { + setEffect(trigger?.intData[0] ?? 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.tooltip.effectid') } + setEffect(parseInt(event.target.value)) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionBaseView.tsx b/src/components/wired/views/conditions/WiredConditionBaseView.tsx new file mode 100644 index 0000000..5782942 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionBaseView.tsx @@ -0,0 +1,23 @@ +import { FC, PropsWithChildren } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredBaseView } from '../WiredBaseView'; + +export interface WiredConditionBaseViewProps +{ + hasSpecialInput: boolean; + requiresFurni: number; + save: () => void; +} + +export const WiredConditionBaseView: FC> = props => +{ + const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, hasSpecialInput = false, children = null } = props; + + const onSave = () => (save && save()); + + return ( + + { children } + + ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionDateRangeView.tsx b/src/components/wired/views/conditions/WiredConditionDateRangeView.tsx new file mode 100644 index 0000000..8eedbf3 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionDateRangeView.tsx @@ -0,0 +1,59 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredDateToString, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionDateRangeView: FC<{}> = props => +{ + const [ startDate, setStartDate ] = useState(''); + const [ endDate, setEndDate ] = useState(''); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => + { + let startDateMili = 0; + let endDateMili = 0; + + const startDateInstance = new Date(startDate); + const endDateInstance = new Date(endDate); + + if(startDateInstance && endDateInstance) + { + startDateMili = startDateInstance.getTime() / 1000; + endDateMili = endDateInstance.getTime() / 1000; + } + + setIntParams([ startDateMili, endDateMili ]); + }; + + useEffect(() => + { + if(trigger.intData.length >= 2) + { + let startDate = new Date(); + let endDate = new Date(); + + if(trigger.intData[0] > 0) startDate = new Date((trigger.intData[0] * 1000)); + + if(trigger.intData[1] > 0) endDate = new Date((trigger.intData[1] * 1000)); + + setStartDate(WiredDateToString(startDate)); + setEndDate(WiredDateToString(endDate)); + } + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.startdate') } + setStartDate(e.target.value) } /> +
+
+ { LocalizeText('wiredfurni.params.enddate') } + setEndDate(e.target.value) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionFurniHasAvatarOnView.tsx b/src/components/wired/views/conditions/WiredConditionFurniHasAvatarOnView.tsx new file mode 100644 index 0000000..5575e13 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionFurniHasAvatarOnView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionFurniHasAvatarOnView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/conditions/WiredConditionFurniHasFurniOnView.tsx b/src/components/wired/views/conditions/WiredConditionFurniHasFurniOnView.tsx new file mode 100644 index 0000000..7759f94 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionFurniHasFurniOnView.tsx @@ -0,0 +1,35 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionFurniHasFurniOnView: FC<{}> = props => +{ + const [ requireAll, setRequireAll ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ requireAll ]); + + useEffect(() => + { + setRequireAll((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.requireall') } + { [ 0, 1 ].map(value => + { + return ( +
+ setRequireAll(value) } /> + { LocalizeText('wiredfurni.params.requireall.' + value) } +
+ ); + }) } +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionFurniHasNotFurniOnView.tsx b/src/components/wired/views/conditions/WiredConditionFurniHasNotFurniOnView.tsx new file mode 100644 index 0000000..44c1935 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionFurniHasNotFurniOnView.tsx @@ -0,0 +1,35 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionFurniHasNotFurniOnView: FC<{}> = props => +{ + const [ requireAll, setRequireAll ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ requireAll ]); + + useEffect(() => + { + setRequireAll((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.not_requireall') } + { [ 0, 1 ].map(value => + { + return ( +
+ setRequireAll(value) } /> + { LocalizeText(`wiredfurni.params.not_requireall.${ value }`) } +
+ ); + }) } +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionFurniIsOfTypeView.tsx b/src/components/wired/views/conditions/WiredConditionFurniIsOfTypeView.tsx new file mode 100644 index 0000000..5cda9f7 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionFurniIsOfTypeView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionFurniIsOfTypeView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/conditions/WiredConditionFurniMatchesSnapshotView.tsx b/src/components/wired/views/conditions/WiredConditionFurniMatchesSnapshotView.tsx new file mode 100644 index 0000000..176a0e6 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionFurniMatchesSnapshotView.tsx @@ -0,0 +1,42 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionFurniMatchesSnapshotView: FC<{}> = props => +{ + const [ stateFlag, setStateFlag ] = useState(0); + const [ directionFlag, setDirectionFlag ] = useState(0); + const [ positionFlag, setPositionFlag ] = useState(0); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ stateFlag, directionFlag, positionFlag ]); + + useEffect(() => + { + setStateFlag(trigger.getBoolean(0) ? 1 : 0); + setDirectionFlag(trigger.getBoolean(1) ? 1 : 0); + setPositionFlag(trigger.getBoolean(2) ? 1 : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.conditions') } +
+ setStateFlag(event.target.checked ? 1 : 0) } /> + { LocalizeText('wiredfurni.params.condition.state') } +
+
+ setDirectionFlag(event.target.checked ? 1 : 0) } /> + { LocalizeText('wiredfurni.params.condition.direction') } +
+
+ setPositionFlag(event.target.checked ? 1 : 0) } /> + { LocalizeText('wiredfurni.params.condition.position') } +
+
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionLayoutView.tsx b/src/components/wired/views/conditions/WiredConditionLayoutView.tsx new file mode 100644 index 0000000..a1a88c2 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionLayoutView.tsx @@ -0,0 +1,64 @@ +import { WiredConditionlayout } from '../../../../api'; +import { WiredConditionActorHasHandItemView } from './WiredConditionActorHasHandItem'; +import { WiredConditionActorIsGroupMemberView } from './WiredConditionActorIsGroupMemberView'; +import { WiredConditionActorIsOnFurniView } from './WiredConditionActorIsOnFurniView'; +import { WiredConditionActorIsTeamMemberView } from './WiredConditionActorIsTeamMemberView'; +import { WiredConditionActorIsWearingBadgeView } from './WiredConditionActorIsWearingBadgeView'; +import { WiredConditionActorIsWearingEffectView } from './WiredConditionActorIsWearingEffectView'; +import { WiredConditionDateRangeView } from './WiredConditionDateRangeView'; +import { WiredConditionFurniHasAvatarOnView } from './WiredConditionFurniHasAvatarOnView'; +import { WiredConditionFurniHasFurniOnView } from './WiredConditionFurniHasFurniOnView'; +import { WiredConditionFurniHasNotFurniOnView } from './WiredConditionFurniHasNotFurniOnView'; +import { WiredConditionFurniIsOfTypeView } from './WiredConditionFurniIsOfTypeView'; +import { WiredConditionFurniMatchesSnapshotView } from './WiredConditionFurniMatchesSnapshotView'; +import { WiredConditionTimeElapsedLessView } from './WiredConditionTimeElapsedLessView'; +import { WiredConditionTimeElapsedMoreView } from './WiredConditionTimeElapsedMoreView'; +import { WiredConditionUserCountInRoomView } from './WiredConditionUserCountInRoomView'; + +export const WiredConditionLayoutView = (code: number) => +{ + switch(code) + { + case WiredConditionlayout.ACTOR_HAS_HANDITEM: + return ; + case WiredConditionlayout.ACTOR_IS_GROUP_MEMBER: + case WiredConditionlayout.NOT_ACTOR_IN_GROUP: + return ; + case WiredConditionlayout.ACTOR_IS_ON_FURNI: + case WiredConditionlayout.NOT_ACTOR_ON_FURNI: + return ; + case WiredConditionlayout.ACTOR_IS_IN_TEAM: + case WiredConditionlayout.NOT_ACTOR_IN_TEAM: + return ; + case WiredConditionlayout.ACTOR_IS_WEARING_BADGE: + case WiredConditionlayout.NOT_ACTOR_WEARS_BADGE: + return ; + case WiredConditionlayout.ACTOR_IS_WEARING_EFFECT: + case WiredConditionlayout.NOT_ACTOR_WEARING_EFFECT: + return ; + case WiredConditionlayout.DATE_RANGE_ACTIVE: + return ; + case WiredConditionlayout.FURNIS_HAVE_AVATARS: + case WiredConditionlayout.FURNI_NOT_HAVE_HABBO: + return ; + case WiredConditionlayout.HAS_STACKED_FURNIS: + return ; + case WiredConditionlayout.NOT_HAS_STACKED_FURNIS: + return ; + case WiredConditionlayout.STUFF_TYPE_MATCHES: + case WiredConditionlayout.NOT_FURNI_IS_OF_TYPE: + return ; + case WiredConditionlayout.STATES_MATCH: + case WiredConditionlayout.NOT_STATES_MATCH: + return ; + case WiredConditionlayout.TIME_ELAPSED_LESS: + return ; + case WiredConditionlayout.TIME_ELAPSED_MORE: + return ; + case WiredConditionlayout.USER_COUNT_IN: + case WiredConditionlayout.NOT_USER_COUNT_IN: + return ; + } + + return null; +}; diff --git a/src/components/wired/views/conditions/WiredConditionTimeElapsedLessView.tsx b/src/components/wired/views/conditions/WiredConditionTimeElapsedLessView.tsx new file mode 100644 index 0000000..9054fe3 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionTimeElapsedLessView.tsx @@ -0,0 +1,33 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { GetWiredTimeLocale, LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionTimeElapsedLessView: FC<{}> = props => +{ + const [ time, setTime ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ time ]); + + useEffect(() => + { + setTime((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.allowbefore', [ 'seconds' ], [ GetWiredTimeLocale(time) ]) } + setTime(event) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionTimeElapsedMoreView.tsx b/src/components/wired/views/conditions/WiredConditionTimeElapsedMoreView.tsx new file mode 100644 index 0000000..a31efdb --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionTimeElapsedMoreView.tsx @@ -0,0 +1,33 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { GetWiredTimeLocale, LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionTimeElapsedMoreView: FC<{}> = props => +{ + const [ time, setTime ] = useState(-1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ time ]); + + useEffect(() => + { + setTime((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.allowafter', [ 'seconds' ], [ GetWiredTimeLocale(time) ]) } + setTime(event) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionUserCountInRoomView.tsx b/src/components/wired/views/conditions/WiredConditionUserCountInRoomView.tsx new file mode 100644 index 0000000..d655721 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionUserCountInRoomView.tsx @@ -0,0 +1,52 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +export const WiredConditionUserCountInRoomView: FC<{}> = props => +{ + const [ min, setMin ] = useState(1); + const [ max, setMax ] = useState(0); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ min, max ]); + + useEffect(() => + { + if(trigger.intData.length >= 2) + { + setMin(trigger.intData[0]); + setMax(trigger.intData[1]); + } + else + { + setMin(1); + setMax(0); + } + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.usercountmin', [ 'value' ], [ min.toString() ]) } + setMin(event) } /> +
+
+ { LocalizeText('wiredfurni.params.usercountmax', [ 'value' ], [ max.toString() ]) } + setMax(event) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/triggers/WiredTriggerAvatarEnterRoomView.tsx b/src/components/wired/views/triggers/WiredTriggerAvatarEnterRoomView.tsx new file mode 100644 index 0000000..2d948a4 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerAvatarEnterRoomView.tsx @@ -0,0 +1,39 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerAvatarEnterRoomView: FC<{}> = props => +{ + const [ username, setUsername ] = useState(''); + const [ avatarMode, setAvatarMode ] = useState(0); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam((avatarMode === 1) ? username : ''); + + useEffect(() => + { + setUsername(trigger.stringData); + setAvatarMode(trigger.stringData ? 1 : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.picktriggerer') } +
+ setAvatarMode(0) } /> + { LocalizeText('wiredfurni.params.anyavatar') } +
+
+ setAvatarMode(1) } /> + { LocalizeText('wiredfurni.params.certainavatar') } +
+ { (avatarMode === 1) && + setUsername(event.target.value) } /> } +
+
+ ); +}; diff --git a/src/components/wired/views/triggers/WiredTriggerAvatarSaysSomethingView.tsx b/src/components/wired/views/triggers/WiredTriggerAvatarSaysSomethingView.tsx new file mode 100644 index 0000000..e2d9dd3 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerAvatarSaysSomethingView.tsx @@ -0,0 +1,46 @@ +import { GetSessionDataManager } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerAvatarSaysSomethingView: FC<{}> = props => +{ + const [ message, setMessage ] = useState(''); + const [ triggererAvatar, setTriggererAvatar ] = useState(-1); + const { trigger = null, setStringParam = null, setIntParams = null } = useWired(); + + const save = () => + { + setStringParam(message); + setIntParams([ triggererAvatar ]); + }; + + useEffect(() => + { + setMessage(trigger.stringData); + setTriggererAvatar((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.whatissaid') } + setMessage(event.target.value) } /> +
+
+ { LocalizeText('wiredfurni.params.picktriggerer') } +
+ setTriggererAvatar(0) } /> + { LocalizeText('wiredfurni.params.anyavatar') } +
+
+ setTriggererAvatar(1) } /> + { GetSessionDataManager().userName } +
+
+
+ ); +}; diff --git a/src/components/wired/views/triggers/WiredTriggerAvatarWalksOffFurniView.tsx b/src/components/wired/views/triggers/WiredTriggerAvatarWalksOffFurniView.tsx new file mode 100644 index 0000000..fc6c198 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerAvatarWalksOffFurniView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerAvatarWalksOffFurniView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/triggers/WiredTriggerAvatarWalksOnFurni.tsx b/src/components/wired/views/triggers/WiredTriggerAvatarWalksOnFurni.tsx new file mode 100644 index 0000000..217cbd5 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerAvatarWalksOnFurni.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerAvatarWalksOnFurniView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/triggers/WiredTriggerBaseView.tsx b/src/components/wired/views/triggers/WiredTriggerBaseView.tsx new file mode 100644 index 0000000..7590d9a --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerBaseView.tsx @@ -0,0 +1,23 @@ +import { FC, PropsWithChildren } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredBaseView } from '../WiredBaseView'; + +export interface WiredTriggerBaseViewProps +{ + hasSpecialInput: boolean; + requiresFurni: number; + save: () => void; +} + +export const WiredTriggerBaseView: FC> = props => +{ + const { requiresFurni = WiredFurniType.STUFF_SELECTION_OPTION_NONE, save = null, hasSpecialInput = false, children = null } = props; + + const onSave = () => (save && save()); + + return ( + + { children } + + ); +}; diff --git a/src/components/wired/views/triggers/WiredTriggerBotReachedAvatarView.tsx b/src/components/wired/views/triggers/WiredTriggerBotReachedAvatarView.tsx new file mode 100644 index 0000000..6aa3fde --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerBotReachedAvatarView.tsx @@ -0,0 +1,28 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerBotReachedAvatarView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam(botName); + + useEffect(() => + { + setBotName(trigger.stringData); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/triggers/WiredTriggerBotReachedStuffView.tsx b/src/components/wired/views/triggers/WiredTriggerBotReachedStuffView.tsx new file mode 100644 index 0000000..0034603 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerBotReachedStuffView.tsx @@ -0,0 +1,28 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerBotReachedStuffView: FC<{}> = props => +{ + const [ botName, setBotName ] = useState(''); + const { trigger = null, setStringParam = null } = useWired(); + + const save = () => setStringParam(botName); + + useEffect(() => + { + setBotName(trigger.stringData); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.bot.name') } + setBotName(event.target.value) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/triggers/WiredTriggerCollisionView.tsx b/src/components/wired/views/triggers/WiredTriggerCollisionView.tsx new file mode 100644 index 0000000..d7efc34 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerCollisionView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerCollisionView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/triggers/WiredTriggerExecuteOnceView.tsx b/src/components/wired/views/triggers/WiredTriggerExecuteOnceView.tsx new file mode 100644 index 0000000..0b91a90 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerExecuteOnceView.tsx @@ -0,0 +1,33 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { GetWiredTimeLocale, LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggeExecuteOnceView: FC<{}> = props => +{ + const [ time, setTime ] = useState(1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ time ]); + + useEffect(() => + { + setTime((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.settime', [ 'seconds' ], [ GetWiredTimeLocale(time) ]) } + setTime(event) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyLongView.tsx b/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyLongView.tsx new file mode 100644 index 0000000..b20e7ed --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyLongView.tsx @@ -0,0 +1,33 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { FriendlyTime, LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggeExecutePeriodicallyLongView: FC<{}> = props => +{ + const [ time, setTime ] = useState(1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ time ]); + + useEffect(() => + { + setTime((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.setlongtime', [ 'time' ], [ FriendlyTime.format(time * 5).toString() ]) } + setTime(event) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyView.tsx b/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyView.tsx new file mode 100644 index 0000000..35471ce --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyView.tsx @@ -0,0 +1,33 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { GetWiredTimeLocale, LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggeExecutePeriodicallyView: FC<{}> = props => +{ + const [ time, setTime ] = useState(1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ time ]); + + useEffect(() => + { + setTime((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.settime', [ 'seconds' ], [ GetWiredTimeLocale(time) ]) } + setTime(event) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/triggers/WiredTriggerGameEndsView.tsx b/src/components/wired/views/triggers/WiredTriggerGameEndsView.tsx new file mode 100644 index 0000000..476ed70 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerGameEndsView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerGameEndsView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/triggers/WiredTriggerGameStartsView.tsx b/src/components/wired/views/triggers/WiredTriggerGameStartsView.tsx new file mode 100644 index 0000000..5c9b937 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerGameStartsView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerGameStartsView: FC<{}> = props => +{ + return ; +}; diff --git a/src/components/wired/views/triggers/WiredTriggerLayoutView.tsx b/src/components/wired/views/triggers/WiredTriggerLayoutView.tsx new file mode 100644 index 0000000..0b01922 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerLayoutView.tsx @@ -0,0 +1,52 @@ +import { WiredTriggerLayout } from '../../../../api'; +import { WiredTriggerAvatarEnterRoomView } from './WiredTriggerAvatarEnterRoomView'; +import { WiredTriggerAvatarSaysSomethingView } from './WiredTriggerAvatarSaysSomethingView'; +import { WiredTriggerAvatarWalksOffFurniView } from './WiredTriggerAvatarWalksOffFurniView'; +import { WiredTriggerAvatarWalksOnFurniView } from './WiredTriggerAvatarWalksOnFurni'; +import { WiredTriggerBotReachedAvatarView } from './WiredTriggerBotReachedAvatarView'; +import { WiredTriggerBotReachedStuffView } from './WiredTriggerBotReachedStuffView'; +import { WiredTriggerCollisionView } from './WiredTriggerCollisionView'; +import { WiredTriggeExecuteOnceView } from './WiredTriggerExecuteOnceView'; +import { WiredTriggeExecutePeriodicallyLongView } from './WiredTriggerExecutePeriodicallyLongView'; +import { WiredTriggeExecutePeriodicallyView } from './WiredTriggerExecutePeriodicallyView'; +import { WiredTriggerGameEndsView } from './WiredTriggerGameEndsView'; +import { WiredTriggerGameStartsView } from './WiredTriggerGameStartsView'; +import { WiredTriggeScoreAchievedView } from './WiredTriggerScoreAchievedView'; +import { WiredTriggerToggleFurniView } from './WiredTriggerToggleFurniView'; + +export const WiredTriggerLayoutView = (code: number) => +{ + switch(code) + { + case WiredTriggerLayout.AVATAR_ENTERS_ROOM: + return ; + case WiredTriggerLayout.AVATAR_SAYS_SOMETHING: + return ; + case WiredTriggerLayout.AVATAR_WALKS_OFF_FURNI: + return ; + case WiredTriggerLayout.AVATAR_WALKS_ON_FURNI: + return ; + case WiredTriggerLayout.BOT_REACHED_AVATAR: + return ; + case WiredTriggerLayout.BOT_REACHED_STUFF: + return ; + case WiredTriggerLayout.COLLISION: + return ; + case WiredTriggerLayout.EXECUTE_ONCE: + return ; + case WiredTriggerLayout.EXECUTE_PERIODICALLY: + return ; + case WiredTriggerLayout.EXECUTE_PERIODICALLY_LONG: + return ; + case WiredTriggerLayout.GAME_ENDS: + return ; + case WiredTriggerLayout.GAME_STARTS: + return ; + case WiredTriggerLayout.SCORE_ACHIEVED: + return ; + case WiredTriggerLayout.TOGGLE_FURNI: + return ; + } + + return null; +}; diff --git a/src/components/wired/views/triggers/WiredTriggerScoreAchievedView.tsx b/src/components/wired/views/triggers/WiredTriggerScoreAchievedView.tsx new file mode 100644 index 0000000..0c4c738 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerScoreAchievedView.tsx @@ -0,0 +1,33 @@ +import { FC, useEffect, useState } from 'react'; +import ReactSlider from 'react-slider'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggeScoreAchievedView: FC<{}> = props => +{ + const [ points, setPoints ] = useState(1); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ points ]); + + useEffect(() => + { + setPoints((trigger.intData.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + +
+ { LocalizeText('wiredfurni.params.setscore', [ 'points' ], [ points.toString() ]) } + setPoints(event) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx b/src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx new file mode 100644 index 0000000..4748481 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerToggleFurniView: FC<{}> = props => +{ + return ; +}; diff --git a/src/css/backgrounds/BackgroundsView.css b/src/css/backgrounds/BackgroundsView.css new file mode 100644 index 0000000..d71a52c --- /dev/null +++ b/src/css/backgrounds/BackgroundsView.css @@ -0,0 +1,947 @@ +.backgrounds-view-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 100; + pointer-events: none; +} + +@keyframes fadeIn { + to { opacity: 1; } +} + +.non-selectable { + cursor: default; +} + +.non-selectable .profile-background { + filter: opacity(0.5); + transition: linear 0.25s; +} + +.non-selectable .profile-background:hover { + filter: opacity(1); +} + +.background-edit-icon { + background-image: url('@/assets/images/infostand/icon_edit.gif'); + width: 19px; + height: 19px; + pointer-events: auto; + cursor: pointer; + z-index: 10; + display: block; + transition: opacity 0.2s ease; +} + +.background-edit-icon:hover { + opacity: 0.8; +} + +.background-edit-position { + position: absolute; + left: 8px; +} + +.profile-background { + background-repeat: no-repeat; + background-position: center; + height: 135px; + width: 68px; +} + +.profile-background.background-default { + background-color: #f0f0f0; +} + +.profile-stand { + background-repeat: no-repeat; + background-position: center; + height: 135px; + width: 68px; +} + +.profile-stand.stand-default { + background: none; +} + +.profile-overlay { + background-repeat: no-repeat; + background-position: center; + height: 135px; + width: 68px; +} + +.profile-overlay.overlay-default { + background: none; +} + +.profile-background { + background-repeat: no-repeat; + background-position: center; + height: 135px; + width: 68px; + + &.background-0 { + background-image: url('@/assets/images/backgrounds/background/bg_0.png'); + } + + &.background-1 { + background-image: url('@/assets/images/backgrounds/background/bg_1.gif'); + } + + &.background-2 { + background-image: url('@/assets/images/backgrounds/background/bg_2.png'); + } + + &.background-3 { + background-image: url('@/assets/images/backgrounds/background/bg_3.png'); + } + + &.background-4 { + background-image: url('@/assets/images/backgrounds/background/bg_4.png'); + } + + &.background-5 { + background-image: url('@/assets/images/backgrounds/background/bg_5.png'); + } + + &.background-6 { + background-image: url('@/assets/images/backgrounds/background/bg_6.png'); + } + + &.background-7 { + background-image: url('@/assets/images/backgrounds/background/bg_7.png'); + } + + &.background-8 { + background-image: url('@/assets/images/backgrounds/background/bg_8.png'); + } + + &.background-9 { + background-image: url('@/assets/images/backgrounds/background/bg_9.png'); + } + + &.background-10 { + background-image: url('@/assets/images/backgrounds/background/bg_10.png'); + } + + &.background-11 { + background-image: url('@/assets/images/backgrounds/background/bg_11.png'); + } + + &.background-12 { + background-image: url('@/assets/images/backgrounds/background/bg_12.png'); + } + + &.background-13 { + background-image: url('@/assets/images/backgrounds/background/bg_13.png'); + } + + &.background-14 { + background-image: url('@/assets/images/backgrounds/background/bg_14.png'); + } + + &.background-15 { + background-image: url('@/assets/images/backgrounds/background/bg_15.png'); + } + + &.background-16 { + background-image: url('@/assets/images/backgrounds/background/bg_16.png'); + } + + &.background-17 { + background-image: url('@/assets/images/backgrounds/background/bg_17.png'); + } + + &.background-18 { + background-image: url('@/assets/images/backgrounds/background/bg_18.png'); + } + + &.background-19 { + background-image: url('@/assets/images/backgrounds/background/bg_19.png'); + } + + &.background-20 { + background-image: url('@/assets/images/backgrounds/background/bg_20.png'); + } + + &.background-21 { + background-image: url('@/assets/images/backgrounds/background/bg_21.png'); + } + + &.background-22 { + background-image: url('@/assets/images/backgrounds/background/bg_22.png'); + } + + &.background-23 { + background-image: url('@/assets/images/backgrounds/background/bg_23.png'); + } + + &.background-24 { + background-image: url('@/assets/images/backgrounds/background/bg_24.png'); + } + + &.background-25 { + background-image: url('@/assets/images/backgrounds/background/bg_25.png'); + } + + &.background-26 { + background-image: url('@/assets/images/backgrounds/background/bg_26.png'); + } + + &.background-27 { + background-image: url('@/assets/images/backgrounds/background/bg_27.png'); + } + + &.background-28 { + background-image: url('@/assets/images/backgrounds/background/bg_28.png'); + } + + &.background-29 { + background-image: url('@/assets/images/backgrounds/background/bg_29.png'); + } + + &.background-30 { + background-image: url('@/assets/images/backgrounds/background/bg_30.png'); + } + + &.background-31 { + background-image: url('@/assets/images/backgrounds/background/bg_31.png'); + } + + &.background-32 { + background-image: url('@/assets/images/backgrounds/background/bg_32.png'); + } + + &.background-33 { + background-image: url('@/assets/images/backgrounds/background/bg_33.png'); + } + + &.background-34 { + background-image: url('@/assets/images/backgrounds/background/bg_34.png'); + } + + &.background-35 { + background-image: url('@/assets/images/backgrounds/background/bg_35.png'); + } + + &.background-36 { + background-image: url('@/assets/images/backgrounds/background/bg_36.gif'); + } + + &.background-37 { + background-image: url('@/assets/images/backgrounds/background/bg_37.png'); + } + + &.background-38 { + background-image: url('@/assets/images/backgrounds/background/bg_38.png'); + } + + &.background-39 { + background-image: url('@/assets/images/backgrounds/background/bg_39.png'); + } + + &.background-40 { + background-image: url('@/assets/images/backgrounds/background/bg_40.png'); + } + + &.background-41 { + background-image: url('@/assets/images/backgrounds/background/bg_41.png'); + } + + &.background-42 { + background-image: url('@/assets/images/backgrounds/background/bg_42.png'); + } + + &.background-43 { + background-image: url('@/assets/images/backgrounds/background/bg_43.png'); + } + + &.background-44 { + background-image: url('@/assets/images/backgrounds/background/bg_44.png'); + } + + &.background-45 { + background-image: url('@/assets/images/backgrounds/background/bg_45.png'); + } + + &.background-46 { + background-image: url('@/assets/images/backgrounds/background/bg_46.png'); + } + + &.background-47 { + background-image: url('@/assets/images/backgrounds/background/bg_47.png'); + } + + &.background-48 { + background-image: url('@/assets/images/backgrounds/background/bg_48.png'); + } + + &.background-49 { + background-image: url('@/assets/images/backgrounds/background/bg_49.png'); + } + + &.background-50 { + background-image: url('@/assets/images/backgrounds/background/bg_50.png'); + } + + &.background-51 { + background-image: url('@/assets/images/backgrounds/background/bg_51.gif'); + } + + &.background-52 { + background-image: url('@/assets/images/backgrounds/background/bg_52.gif'); + } + + &.background-53 { + background-image: url('@/assets/images/backgrounds/background/bg_53.gif'); + } + + &.background-54 { + background-image: url('@/assets/images/backgrounds/background/bg_54.gif'); + } + + &.background-55 { + background-image: url('@/assets/images/backgrounds/background/bg_55.gif'); + } + + &.background-56 { + background-image: url('@/assets/images/backgrounds/background/bg_56.gif'); + } + + &.background-57 { + background-image: url('@/assets/images/backgrounds/background/bg_57.gif'); + } + + &.background-58 { + background-image: url('@/assets/images/backgrounds/background/bg_58.gif'); + } + + &.background-59 { + background-image: url('@/assets/images/backgrounds/background/bg_59.gif'); + } + + &.background-60 { + background-image: url('@/assets/images/backgrounds/background/bg_60.gif'); + } + + &.background-61 { + background-image: url('@/assets/images/backgrounds/background/bg_61.gif'); + } + + &.background-62 { + background-image: url('@/assets/images/backgrounds/background/bg_62.gif'); + } + + &.background-63 { + background-image: url('@/assets/images/backgrounds/background/bg_63.gif'); + } + + &.background-64 { + background-image: url('@/assets/images/backgrounds/background/bg_64.gif'); + } + + &.background-65 { + background-image: url('@/assets/images/backgrounds/background/bg_65.gif'); + } + + &.background-66 { + background-image: url('@/assets/images/backgrounds/background/bg_66.gif'); + } + + &.background-67 { + background-image: url('@/assets/images/backgrounds/background/bg_67.gif'); + } + + &.background-68 { + background-image: url('@/assets/images/backgrounds/background/bg_68.gif'); + } + + &.background-69 { + background-image: url('@/assets/images/backgrounds/background/bg_69.gif'); + } + + &.background-70 { + background-image: url('@/assets/images/backgrounds/background/bg_70.gif'); + } + + &.background-71 { + background-image: url('@/assets/images/backgrounds/background/bg_71.gif'); + } + + &.background-72 { + background-image: url('@/assets/images/backgrounds/background/bg_72.gif'); + } + + &.background-73 { + background-image: url('@/assets/images/backgrounds/background/bg_73.gif'); + } + + &.background-74 { + background-image: url('@/assets/images/backgrounds/background/bg_74.gif'); + } + + &.background-75 { + background-image: url('@/assets/images/backgrounds/background/bg_75.gif'); + } + + &.background-76 { + background-image: url('@/assets/images/backgrounds/background/bg_76.gif'); + } + + &.background-77 { + background-image: url('@/assets/images/backgrounds/background/bg_77.gif'); + } + + &.background-78 { + background-image: url('@/assets/images/backgrounds/background/bg_78.gif'); + } + + &.background-79 { + background-image: url('@/assets/images/backgrounds/background/bg_79.gif'); + } + + &.background-80 { + background-image: url('@/assets/images/backgrounds/background/bg_80.gif'); + } + + &.background-81 { + background-image: url('@/assets/images/backgrounds/background/bg_81.gif'); + } + + &.background-82 { + background-image: url('@/assets/images/backgrounds/background/bg_82.gif'); + } + + &.background-83 { + background-image: url('@/assets/images/backgrounds/background/bg_83.gif'); + } + + &.background-84 { + background-image: url('@/assets/images/backgrounds/background/bg_84.gif'); + } + + &.background-85 { + background-image: url('@/assets/images/backgrounds/background/bg_85.gif'); + } + + &.background-86 { + background-image: url('@/assets/images/backgrounds/background/bg_86.png'); + } + + &.background-87 { + background-image: url('@/assets/images/backgrounds/background/bg_87.gif'); + } + + &.background-88 { + background-image: url('@/assets/images/backgrounds/background/bg_88.gif'); + } + + &.background-89 { + background-image: url('@/assets/images/backgrounds/background/bg_89.gif'); + } + + &.background-90 { + background-image: url('@/assets/images/backgrounds/background/bg_90.gif'); + } + + &.background-91 { + background-image: url('@/assets/images/backgrounds/background/bg_91.gif'); + } + + &.background-92 { + background-image: url('@/assets/images/backgrounds/background/bg_92.gif'); + } + + &.background-93 { + background-image: url('@/assets/images/backgrounds/background/bg_93.gif'); + } + + &.background-94 { + background-image: url('@/assets/images/backgrounds/background/bg_94.gif'); + } + + &.background-95 { + background-image: url('@/assets/images/backgrounds/background/bg_95.gif'); + } + + &.background-96 { + background-image: url('@/assets/images/backgrounds/background/bg_96.gif'); + } + + &.background-97 { + background-image: url('@/assets/images/backgrounds/background/bg_97.gif'); + } + + &.background-98 { + background-image: url('@/assets/images/backgrounds/background/bg_98.gif'); + } + + &.background-99 { + background-image: url('@/assets/images/backgrounds/background/bg_99.gif'); + } + + &.background-100 { + background-image: url('@/assets/images/backgrounds/background/bg_100.gif'); + } + + &.background-101 { + background-image: url('@/assets/images/backgrounds/background/bg_101.png'); + } + + &.background-102 { + background-image: url('@/assets/images/backgrounds/background/bg_102.gif'); + } + + &.background-103 { + background-image: url('@/assets/images/backgrounds/background/bg_103.gif'); + } + + &.background-104 { + background-image: url('@/assets/images/backgrounds/background/bg_104.gif'); + } + + &.background-105 { + background-image: url('@/assets/images/backgrounds/background/bg_105.gif'); + } + + &.background-106 { + background-image: url('@/assets/images/backgrounds/background/bg_106.gif'); + } + + &.background-107 { + background-image: url('@/assets/images/backgrounds/background/bg_107.gif'); + } + + &.background-108 { + background-image: url('@/assets/images/backgrounds/background/bg_108.gif'); + } + + &.background-109 { + background-image: url('@/assets/images/backgrounds/background/bg_109.gif'); + } + + &.background-110 { + background-image: url('@/assets/images/backgrounds/background/bg_110.gif'); + } + + &.background-111 { + background-image: url('@/assets/images/backgrounds/background/bg_111.gif'); + } + + &.background-112 { + background-image: url('@/assets/images/backgrounds/background/bg_112.gif'); + } + + &.background-113 { + background-image: url('@/assets/images/backgrounds/background/bg_113.gif'); + } + + &.background-114 { + background-image: url('@/assets/images/backgrounds/background/bg_114.gif'); + } + + &.background-115 { + background-image: url('@/assets/images/backgrounds/background/bg_115.gif'); + } + + &.background-116 { + background-image: url('@/assets/images/backgrounds/background/bg_116.gif'); + } + + &.background-117 { + background-image: url('@/assets/images/backgrounds/background/bg_117.gif'); + } + + &.background-118 { + background-image: url('@/assets/images/backgrounds/background/bg_118.gif'); + } + + &.background-119 { + background-image: url('@/assets/images/backgrounds/background/bg_119.gif'); + } + + &.background-120 { + background-image: url('@/assets/images/backgrounds/background/bg_120.gif'); + } + + &.background-121 { + background-image: url('@/assets/images/backgrounds/background/bg_121.gif'); + } + + &.background-122 { + background-image: url('@/assets/images/backgrounds/background/bg_122.gif'); + } + + &.background-123 { + background-image: url('@/assets/images/backgrounds/background/bg_123.gif'); + } + + &.background-124 { + background-image: url('@/assets/images/backgrounds/background/bg_124.gif'); + } + + &.background-125 { + background-image: url('@/assets/images/backgrounds/background/bg_125.gif'); + } + + &.background-126 { + background-image: url('@/assets/images/backgrounds/background/bg_126.gif'); + } + + &.background-127 { + background-image: url('@/assets/images/backgrounds/background/bg_127.gif'); + } + + &.background-128 { + background-image: url('@/assets/images/backgrounds/background/bg_128.gif'); + } + + &.background-129 { + background-image: url('@/assets/images/backgrounds/background/bg_129.gif'); + } + + &.background-130 { + background-image: url('@/assets/images/backgrounds/background/bg_130.gif'); + } + + &.background-131 { + background-image: url('@/assets/images/backgrounds/background/bg_131.gif'); + } + + &.background-132 { + background-image: url('@/assets/images/backgrounds/background/bg_132.gif'); + } + + &.background-133 { + background-image: url('@/assets/images/backgrounds/background/bg_133.gif'); + } + + &.background-134 { + background-image: url('@/assets/images/backgrounds/background/bg_134.gif'); + } + + &.background-135 { + background-image: url('@/assets/images/backgrounds/background/bg_135.gif'); + } + + &.background-136 { + background-image: url('@/assets/images/backgrounds/background/bg_136.gif'); + } + + &.background-137 { + background-image: url('@/assets/images/backgrounds/background/bg_137.gif'); + } + + &.background-138 { + background-image: url('@/assets/images/backgrounds/background/bg_138.gif'); + } + + &.background-139 { + background-image: url('@/assets/images/backgrounds/background/bg_139.gif'); + } + + &.background-140 { + background-image: url('@/assets/images/backgrounds/background/bg_140.gif'); + } + + &.background-141 { + background-image: url('@/assets/images/backgrounds/background/bg_141.gif'); + } + + &.background-142 { + background-image: url('@/assets/images/backgrounds/background/bg_142.gif'); + } + + &.background-143 { + background-image: url('@/assets/images/backgrounds/background/bg_143.gif'); + } + + &.background-144 { + background-image: url('@/assets/images/backgrounds/background/bg_144.gif'); + } + + &.background-145 { + background-image: url('@/assets/images/backgrounds/background/bg_145.gif'); + } + + &.background-146 { + background-image: url('@/assets/images/backgrounds/background/bg_146.gif'); + } + + &.background-147 { + background-image: url('@/assets/images/backgrounds/background/bg_147.gif'); + } + + &.background-148 { + background-image: url('@/assets/images/backgrounds/background/bg_148.gif'); + } + + &.background-149 { + background-image: url('@/assets/images/backgrounds/background/bg_149.gif'); + } + + &.background-150 { + background-image: url('@/assets/images/backgrounds/background/bg_150.gif'); + } + + &.background-151 { + background-image: url('@/assets/images/backgrounds/background/bg_151.gif'); + } + + &.background-152 { + background-image: url('@/assets/images/backgrounds/background/bg_152.gif'); + } + + &.background-153 { + background-image: url('@/assets/images/backgrounds/background/bg_153.gif'); + } + + &.background-154 { + background-image: url('@/assets/images/backgrounds/background/bg_154.gif'); + } + + &.background-155 { + background-image: url('@/assets/images/backgrounds/background/bg_155.gif'); + } + + &.background-156 { + background-image: url('@/assets/images/backgrounds/background/bg_156.gif'); + } + + &.background-157 { + background-image: url('@/assets/images/backgrounds/background/bg_157.gif'); + } + + &.background-158 { + background-image: url('@/assets/images/backgrounds/background/bg_158.gif'); + } + + &.background-159 { + background-image: url('@/assets/images/backgrounds/background/bg_159.gif'); + } + + &.background-160 { + background-image: url('@/assets/images/backgrounds/background/bg_160.gif'); + } + + &.background-161 { + background-image: url('@/assets/images/backgrounds/background/bg_161.gif'); + } + + &.background-162 { + background-image: url('@/assets/images/backgrounds/background/bg_162.gif'); + } + + &.background-163 { + background-image: url('@/assets/images/backgrounds/background/bg_163.gif'); + } + + &.background-164 { + background-image: url('@/assets/images/backgrounds/background/bg_164.gif'); + } + + &.background-165 { + background-image: url('@/assets/images/backgrounds/background/bg_165.gif'); + } + + &.background-166 { + background-image: url('@/assets/images/backgrounds/background/bg_166.gif'); + } + + &.background-167 { + background-image: url('@/assets/images/backgrounds/background/bg_167.gif'); + } + + &.background-168 { + background-image: url('@/assets/images/backgrounds/background/bg_168.gif'); + } + + &.background-169 { + background-image: url('@/assets/images/backgrounds/background/bg_169.gif'); + } + + &.background-170 { + background-image: url('@/assets/images/backgrounds/background/bg_170.png'); + } + + &.background-171 { + background-image: url('@/assets/images/backgrounds/background/bg_171.png'); + } + + &.background-172 { + background-image: url('@/assets/images/backgrounds/background/bg_172.png'); + } + + &.background-173 { + background-image: url('@/assets/images/backgrounds/background/bg_173.png'); + } + + &.background-174 { + background-image: url('@/assets/images/backgrounds/background/bg_174.png'); + } + + &.background-175 { + background-image: url('@/assets/images/backgrounds/background/bg_175.png'); + } + + &.background-176 { + background-image: url('@/assets/images/backgrounds/background/bg_176.png'); + } + + &.background-177 { + background-image: url('@/assets/images/backgrounds/background/bg_177.gif'); + } + + &.background-178 { + background-image: url('@/assets/images/backgrounds/background/bg_178.png'); + } + + &.background-179 { + background-image: url('@/assets/images/backgrounds/background/bg_179.png'); + } + + &.background-180 { + background-image: url('@/assets/images/backgrounds/background/bg_180.png'); + } + + &.background-181 { + background-image: url('@/assets/images/backgrounds/background/bg_181.png'); + } + + &.background-182 { + background-image: url('@/assets/images/backgrounds/background/bg_182.png'); + } + + &.background-183 { + background-image: url('@/assets/images/backgrounds/background/bg_183.png'); + } + + &.background-184 { + background-image: url('@/assets/images/backgrounds/background/bg_184.png'); + } + + &.background-185 { + background-image: url('@/assets/images/backgrounds/background/bg_185.png'); + } + + &.background-186 { + background-image: url('@/assets/images/backgrounds/background/bg_186.png'); + } + + &.background-187 { + background-image: url('@/assets/images/backgrounds/background/bg_187.gif'); + } +} + +.profile-stand { + background-repeat: no-repeat; + background-position: center; + height: 135px; + width: 68px; + + &.stand-0 { + background-image: url('@/assets/images/backgrounds/stand_0.png'); + } + &.stand-1 { + background-image: url('@/assets/images/backgrounds/stand_1.png'); + } + &.stand-2 { + background-image: url('@/assets/images/backgrounds/stand_2.png'); + } + &.stand-3 { + background-image: url('@/assets/images/backgrounds/stand_3.png'); + } + &.stand-4 { + background-image: url('@/assets/images/backgrounds/stand_4.png'); + } + &.stand-5 { + background-image: url('@/assets/images/backgrounds/stand_5.png'); + } + &.stand-6 { + background-image: url('@/assets/images/backgrounds/stand_6.png'); + } + &.stand-7 { + background-image: url('@/assets/images/backgrounds/stand_7.png'); + } + &.stand-8 { + background-image: url('@/assets/images/backgrounds/stand_8.png'); + } + &.stand-9 { + background-image: url('@/assets/images/backgrounds/stand_9.png'); + } + &.stand-10 { + background-image: url('@/assets/images/backgrounds/stand_10.png'); + } + &.stand-11 { + background-image: url('@/assets/images/backgrounds/stand_11.png'); + } + &.stand-12 { + background-image: url('@/assets/images/backgrounds/stand_12.png'); + } + &.stand-13 { + background-image: url('@/assets/images/backgrounds/stand_13.png'); + } + &.stand-14 { + background-image: url('@/assets/images/backgrounds/stand_14.png'); + } + &.stand-15 { + background-image: url('@/assets/images/backgrounds/stand_15.png'); + } + &.stand-16 { + background-image: url('@/assets/images/backgrounds/stand_16.png'); + } + &.stand-17 { + background-image: url('@/assets/images/backgrounds/stand_17.png'); + } + &.stand-18 { + background-image: url('@/assets/images/backgrounds/stand_18.png'); + } + &.stand-19 { + background-image: url('@/assets/images/backgrounds/stand_19.png'); + } + &.stand-20 { + background-image: url('@/assets/images/backgrounds/stand_20.png'); + } + &.stand-21 { + background-image: url('@/assets/images/backgrounds/stand_21.gif'); + } +} + +.profile-overlay { + background-repeat: no-repeat; + background-position: center; + height: 135px; + width: 68px; + + &.overlay-0 { + background-image: url('@/assets/images/backgrounds/overlay/overlay_0.png'); + } + &.overlay-1 { + background-image: url('@/assets/images/backgrounds/overlay/overlay_1.png'); + } + &.overlay-2 { + background-image: url('@/assets/images/backgrounds/overlay/overlay_2.png'); + } + &.overlay-3 { + background-image: url('@/assets/images/backgrounds/overlay/overlay_3.png'); + } + &.overlay-4 { + background-image: url('@/assets/images/backgrounds/overlay/overlay_4.png'); + } + &.overlay-5 { + background-image: url('@/assets/images/backgrounds/overlay/overlay_5.gif'); + } + &.overlay-6 { + background-image: url('@/assets/images/backgrounds/overlay/overlay_6.png'); + } + &.overlay-7 { + background-image: url('@/assets/images/backgrounds/overlay/overlay_7.png'); + } + &.overlay-8 { + background-image: url('@/assets/images/backgrounds/overlay/overlay_8.png'); + } +} \ No newline at end of file diff --git a/src/css/chat/ChatHistoryView.css b/src/css/chat/ChatHistoryView.css new file mode 100644 index 0000000..815aafd --- /dev/null +++ b/src/css/chat/ChatHistoryView.css @@ -0,0 +1,28 @@ +.nitro-chat-history { + background-color: #f0f0f0; + width: 400px; + height: 400px; + } + +.nitro-chat-history .nitro-card-content { + height: 100%; + background-image: url('@/assets/images/chat/chathistory_background.png'); + background-repeat: repeat; + background-size: auto; + background-color: #f0f0f0; +} + +.nitro-chat-history .p-1.slide-in { + animation: slideIn 0.3s ease-out; +} + +@keyframes slideIn { + 0% { + transform: translateY(-20px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} \ No newline at end of file diff --git a/src/css/chat/chats.css b/src/css/chat/chats.css new file mode 100644 index 0000000..7d9c173 --- /dev/null +++ b/src/css/chat/chats.css @@ -0,0 +1,856 @@ +.bubble-container { + transition: top 0.2s ease 0s; + + .chat-bubble { + border-image-slice: 17 6 6 29 fill; + border-image-width: 17px 6px 6px 29px; + border-image-outset: 2px 0px 0px 0px; + border-image-repeat: repeat repeat; + + &.type-0 { + + // normal + .message { + font-weight: 400; + } + } + + &.type-1 { + + // whisper + .message { + font-weight: 400; + font-style: italic; + color: #595959; + } + } + + &.type-2 { + + // shout + .message { + font-weight: 700; + } + } + + &.bubble-0 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_0_transparent.png'); + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_0_1_33_34_pointer.png'); + bottom: -5px; + } + } + + &.bubble-1 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_1.png'); + + border-image-slice: 18 6 6 29 fill; + border-image-width: 18px 6px 6px 29px; + border-image-outset: 3px 0px 0px 0px; + + .user-container { + display: none; + } + + .username { + display: none; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_0_1_33_34_pointer.png'); + } + } + + &.bubble-2, + &.bubble-31 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_2.png'); + + .user-container { + display: none; + } + + .username { + color: rgba(#FFF, 1); + } + + .message { + color: rgba(#FFF, 1) !important; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_2_31_pointer.png'); + height: 7px; + } + } + + &.bubble-3 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_3.png'); + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_3_pointer.png'); + } + } + + &.bubble-4 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_4.png'); + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_4_pointer.png'); + } + } + + &.bubble-5 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_5.png'); + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_5_pointer.png'); + } + } + + &.bubble-6 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_6.png'); + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_6_pointer.png'); + } + } + + &.bubble-7 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_7.png'); + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_7_pointer.png'); + } + } + + &.bubble-8 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_8.png'); + + border-image-slice: 20 6 6 27 fill; + border-image-width: 20px 6px 6px 27px; + border-image-outset: 5px 0px 0px 0px; + + .chat-content { + color: #fff; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_8_pointer.png'); + } + } + + &.bubble-9 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_9.png'); + + border-image-slice: 17 18 12 19 fill; + border-image-width: 17px 18px 12px 19px; + border-image-outset: 7px 7px 0px 9px; + + .chat-content { + margin-left: 20px; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_9_pointer.png'); + width: 7px; + height: 10px; + bottom: -6px; + } + } + + &.bubble-10 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_10.png'); + + border-image-slice: 29 18 8 37 fill; + border-image-width: 29px 18px 8px 37px; + border-image-outset: 12px 7px 1px 5px; + + .chat-content { + margin-left: 24px; + color: #fff; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_10_pointer.png'); + width: 7px; + height: 8px; + bottom: -3px; + } + } + + &.bubble-11 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_11.png'); + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_11_pointer.png'); + } + } + + &.bubble-12 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_12.png'); + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_12_pointer.png'); + } + } + + &.bubble-13 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_13.png'); + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_13_pointer.png'); + } + } + + &.bubble-14 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_14.png'); + + .chat-content { + color: #fff; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_14_pointer.png'); + } + } + + &.bubble-15 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_15.png'); + + .chat-content { + color: #fff; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_15_pointer.png'); + } + } + + &.bubble-16 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_16.png'); + + border-image-slice: 13 6 10 31 fill; + border-image-width: 13px 6px 10px 31px; + border-image-outset: 6px 0px 0px 0px; + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_16_pointer.png'); + height: 8px; + } + } + + &.bubble-17 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_17.png'); + + border-image-slice: 24 6 8 35 fill; + border-image-width: 24px 6px 8px 35px; + border-image-outset: 9px 0px 2px 5px; + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_17_pointer.png'); + } + } + + &.bubble-18 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_18.png'); + + border-image-slice: 7 16 8 16 fill; + border-image-width: 7px 16px 8px 16px; + border-image-outset: 3px 10px 2px 11px; + + .chat-content { + margin-left: 20px; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_18_pointer.png'); + height: 8px; + } + } + + &.bubble-19 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_19.png'); + + border-image-slice: 17 6 9 19 fill; + border-image-width: 17px 6px 9px 19px; + border-image-outset: 5px 0px 0px 8px; + + .chat-content { + margin-left: 20px; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_19_20_pointer.png'); + } + } + + &.bubble-20 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_20.png'); + + border-image-slice: 18 6 8 19 fill; + border-image-width: 18px 6px 8px 19px; + border-image-outset: 5px 0px 0px 8px; + + .chat-content { + margin-left: 20px; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_19_20_pointer.png'); + } + } + + &.bubble-21 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_21.png'); + + border-image-slice: 20 6 12 24 fill; + border-image-width: 20px 6px 12px 24px; + border-image-outset: 13px 2px 1px 3px; + + .chat-content { + margin-left: 20px; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_21_pointer.png'); + bottom: -4px; + } + } + + &.bubble-22 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_22.png'); + + border-image-slice: 18 19 11 33 fill; + border-image-width: 18px 19px 11px 33px; + border-image-outset: 7px 1px 1px 5px; + + .chat-content { + margin-left: 20px; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_22_pointer.png'); + } + } + + &.bubble-23 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_23.png'); + + border-image-slice: 16 6 7 32 fill; + border-image-width: 16px 6px 7px 32px; + border-image-outset: 5px 0px 0px 3px; + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_23_37_pointer.png'); + } + } + + &.bubble-24 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_24.png'); + + border-image-slice: 23 8 6 40 fill; + border-image-width: 23px 8px 6px 40px; + border-image-outset: 6px 0px 0px 6px; + + .chat-content { + margin-left: 30px; + color: #fff; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_24_pointer.png'); + bottom: -4px; + } + } + + &.bubble-25 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_25.png'); + + border-image-slice: 10 13 8 28 fill; + border-image-width: 10px 13px 8px 28px; + border-image-outset: 6px 3px 2px 0px; + + .chat-content { + color: #fff; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_25_pointer.png'); + height: 9px; + bottom: -7px; + } + } + + &.bubble-26 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_26.png'); + + border-image-slice: 16 9 8 29 fill; + border-image-width: 16px 9px 8px 29px; + border-image-outset: 2px 2px 2px 0px; + + .chat-content { + color: #c59432; + text-shadow: 1px 1px rgba(0, 0, 0, 0.3); + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_26_pointer.png'); + height: 10px; + bottom: -6px; + } + } + + &.bubble-27 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_27.png'); + + border-image-slice: 25 6 5 36 fill; + border-image-width: 25px 6px 5px 36px; + border-image-outset: 8px 0px 0px 5px; + + .chat-content { + margin-left: 30px; + color: #fff; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_27_pointer.png'); + } + } + + &.bubble-28 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_28.png'); + + border-image-slice: 16 7 7 27 fill; + border-image-width: 16px 7px 7px 27px; + border-image-outset: 3px 0px 0px 0px; + + .chat-content { + margin-left: 25px; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_28_pointer.png'); + } + } + + &.bubble-29 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_29.png'); + + border-image-slice: 10 7 15 31 fill; + border-image-width: 10px 7px 15px 31px; + border-image-outset: 2px 0px 0px 1px; + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_29_pointer.png'); + bottom: -4px; + } + } + + &.bubble-30 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_30.png'); + + .user-container { + display: none; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_30_pointer.png'); + height: 7px; + } + } + + &.bubble-32 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_32.png'); + + border-image-slice: 15 7 7 30 fill; + border-image-width: 15px 7px 7px 30px; + border-image-outset: 2px 0px 0px 0px; + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_32_pointer.png'); + } + } + + &.bubble-33 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_33_34.png'); + + border-image-slice: 7 6 6 39 fill; + border-image-width: 7px 6px 6px 39px; + border-image-outset: 2px 0px 0px 0px; + + .user-container { + display: none; + } + + .chat-content { + margin-left: 35px; + } + + &::before { + content: ' '; + position: absolute; + width: 19px; + height: 19px; + left: 9px; + top: 2px; + background: url('@/assets/images/chat/chatbubbles/bubble_33_extra.png'); + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_0_1_33_34_pointer.png'); + } + } + + &.bubble-34 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_33_34.png'); + + border-image-slice: 7 6 6 39 fill; + border-image-width: 7px 6px 6px 39px; + border-image-outset: 2px 0px 0px 0px; + + &.type-1 { + .message { + font-style: unset; + color: inherit; + } + } + + .user-container { + display: none; + } + + .username { + display: none; + } + + .chat-content { + margin-left: 35px; + } + + &::before { + content: ' '; + position: absolute; + width: 19px; + height: 19px; + left: 9px; + top: 2px; + background: url('@/assets/images/chat/chatbubbles/bubble_34_extra.png'); + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_0_1_33_34_pointer.png'); + } + } + + &.bubble-35 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_35.png'); + + border-image-slice: 19 6 5 29 fill; + border-image-width: 19px 6px 5px 29px; + border-image-outset: 4px 0px 0px 0px; + + .user-container { + display: none; + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_35_pointer.png'); + } + } + + &.bubble-36 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_36.png'); + + border-image-slice: 17 7 5 30 fill; + border-image-width: 17px 7px 5px 30px; + border-image-outset: 2px 0px 0px 0px; + + .user-container { + display: none; + } + + &::before { + content: ' '; + position: absolute; + width: 13px; + height: 18px; + left: 5px; + top: 2px; + background: url('@/assets/images/chat/chatbubbles/bubble_36_extra.png'); + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_36_pointer.png'); + } + } + + &.bubble-37 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_37.png'); + + border-image-slice: 16 6 7 32 fill; + border-image-width: 16px 6px 7px 32px; + border-image-outset: 5px 0px 0px 3px; + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_23_37_pointer.png'); + } + } + + &.bubble-38 { + border-image-source: url('@/assets/images/chat/chatbubbles/bubble_38.png'); + + border-image-slice: 17 7 5 30 fill; + border-image-width: 17px 7px 5px 30px; + border-image-outset: 2px 0px 0px 0px; + + .user-container { + display: none; + } + + &::before { + content: ' '; + position: absolute; + width: 19px; + height: 19px; + left: 3px; + top: 2px; + background: url('@/assets/images/chat/chatbubbles/bubble_38_extra.png'); + } + + .pointer { + background: url('@/assets/images/chat/chatbubbles/bubble_38_pointer.png'); + } + } + + .user-container { + z-index: 3; + display: flex; + align-items: center; + justify-content: center; + height: 100%; + max-height: 24px; + overflow: hidden; + + .user-image { + position: absolute; + top: -15px; + left: -9.25px; + width: 45px; + height: 65px; + background-repeat: no-repeat; + background-position: center; + transform: scale(0.5); + overflow: hidden; + image-rendering: initial; + } + } + + .chat-content { + padding: 5px 6px 5px 4px; + margin-left: 27px; + line-height: 1; + color: #000; + min-height: 25px; + } + } +} + +.chat-bubble-icon { + background-repeat: no-repeat; + background-position: center; + + &.bubble-0 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_0.png'); + } + + &.bubble-1 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_1.png'); + height: 25px; + } + + &.bubble-2, + &.bubble-31 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_2.png'); + } + + &.bubble-3 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_3.png'); + } + + &.bubble-4 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_4.png'); + } + + &.bubble-5 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_5.png'); + } + + &.bubble-6 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_6.png'); + } + + &.bubble-7 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_7.png'); + } + + &.bubble-8 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_8.png'); + } + + &.bubble-9 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_9.png'); + } + + &.bubble-10 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_10.png'); + } + + &.bubble-11 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_11.png'); + } + + &.bubble-12 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_12.png'); + } + + &.bubble-13 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_13.png'); + } + + &.bubble-14 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_14.png'); + } + + &.bubble-15 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_15.png'); + } + + &.bubble-16 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_16.png'); + } + + &.bubble-17 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_17.png'); + } + + &.bubble-18 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_18.png'); + } + + &.bubble-19 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_19.png'); + } + + &.bubble-20 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_20.png'); + } + + &.bubble-21 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_21.png'); + } + + &.bubble-22 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_22.png'); + } + + &.bubble-23 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_23.png'); + } + + &.bubble-24 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_24.png'); + } + + &.bubble-25 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_25.png'); + } + + &.bubble-26 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_26.png'); + } + + &.bubble-27 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_27.png'); + } + + &.bubble-28 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_28.png'); + } + + &.bubble-29 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_29.png'); + } + + &.bubble-30 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_30.png'); + } + + &.bubble-32 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_32.png'); + } + + &.bubble-33 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_33_34.png'); + + &::before { + content: ' '; + position: absolute; + width: 19px; + height: 19px; + left: 11px; + top: 10px; + background: url('@/assets/images/chat/chatbubbles/bubble_33_extra.png'); + } + } + + &.bubble-34 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_33_34.png'); + + &::before { + content: ' '; + position: absolute; + width: 19px; + height: 19px; + left: 11px; + top: 10px; + background: url('@/assets/images/chat/chatbubbles/bubble_34_extra.png'); + } + } + + &.bubble-35 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_35.png'); + } + + &.bubble-36 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_36.png'); + + &::before { + content: ' '; + position: absolute; + width: 13px; + height: 18px; + left: 13px; + top: 10px; + background: url('@/assets/images/chat/chatbubbles/bubble_36_extra.png'); + } + } + + &.bubble-37 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_35.png'); + } + + &.bubble-38 { + background-image: url('@/assets/images/chat/chatbubbles/bubble_38.png'); + + &::before { + content: ' '; + position: absolute; + width: 19px; + height: 19px; + left: 11px; + top: 10px; + background: url('@/assets/images/chat/chatbubbles/bubble_38_extra.png'); + } + } +} \ No newline at end of file diff --git a/src/css/common/Buttons.css b/src/css/common/Buttons.css new file mode 100644 index 0000000..106a3cc --- /dev/null +++ b/src/css/common/Buttons.css @@ -0,0 +1,109 @@ +.btn-sm { + min-height: 28px; +} + +textarea { + resize: none; +} + +/* Chrome, Safari, Edge, Opera */ +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* Firefox */ +input[type=number] { + -moz-appearance: textfield; +} + +.rounded { + border-radius: 0.5rem; +} + +.btn-primary { + color: #fff; + background-color: #3c6d82; + border: 2px solid #1a617f; + padding: 0.25rem 0.5rem; + font-size: .7875rem; + border-radius: 0.5rem; + box-shadow: none!important; +} + +.btn-primary:hover { + border: 2px solid #1a617f; + box-shadow: none!important; +} + +.btn-success{ + color: #fff; + background-color: #3c8243; + border: 2px solid #006d09; + padding: 0.25rem 0.5rem; + font-size: .7875rem; + border-radius: 0.5rem; + box-shadow: none!important; +} + +.btn-success:hover{ + box-shadow: none!important; +} + +.btn-danger{ + color: #fff; + background-color: #a81a12; + border: 2px solid #b9322a; + padding: 0.25rem 0.5rem; + font-size: .7875rem; + border-radius: 0.5rem; + box-shadow: none!important; +} + +.btn-danger:hover{ + box-shadow: none!important; +} + +.btn-warning{ + color: #222; + background-color: #ffc107; + border: 2px solid #f3c12a; + padding: 0.25rem 0.5rem; + font-size: .7875rem; + border-radius: 0.5rem; + box-shadow: none!important; +} + +.btn-warning:hover{ + box-shadow: none!important; +} + +.btn-dark { + color: #fff; + background-color: #212131; + border: 2px solid #1c1c2a; + box-shadow: none!important; + border-radius: 8px; + padding: 4px 11px 4px 11px; +} + +.btn-dark:hover{ + background-color: #212131; + border: 2px solid #1c1c2a; + box-shadow: none!important; + border-radius: 8px; + padding: 4px 11px 4px 11px; +} + +.btn-friendsgen{ + background-color: #424354; + border: 2px solid #63647a; + border-radius: 10px; +} + +.btn-friendsgensuccess{ + background-color: #b69b83; + border: 2px solid #e2c1a3; + border-radius: 10px; +} diff --git a/src/css/common/MiniCamera.css b/src/css/common/MiniCamera.css new file mode 100644 index 0000000..fd4a145 --- /dev/null +++ b/src/css/common/MiniCamera.css @@ -0,0 +1,13 @@ +.nitro-room-thumbnail-camera { + width: 132px; + height: 192px; + background-image: url('@/assets/images/room-widgets/thumbnail-widget/thumbnail-camera-spritesheet.png'); + + .camera-frame { + position: absolute; + width: 110px; + height: 110px; + margin-top: 30px; + margin-left: 3px; + } +} \ No newline at end of file diff --git a/src/css/floorplan/FloorplanEditorView.css b/src/css/floorplan/FloorplanEditorView.css new file mode 100644 index 0000000..219a197 --- /dev/null +++ b/src/css/floorplan/FloorplanEditorView.css @@ -0,0 +1,9 @@ +.nitro-floorplan-editor { + width: 760px; + height: 500px; +} + +.floorplan-import-export { + width: 630px; + height: 475px; +} \ No newline at end of file diff --git a/src/css/forms/form_select.css b/src/css/forms/form_select.css new file mode 100644 index 0000000..8336b6f --- /dev/null +++ b/src/css/forms/form_select.css @@ -0,0 +1,24 @@ +/* Styling for text inputs (e.g., password fields) */ +.form-control-sm { + padding: 0.25rem 0.5rem; /* Reduced padding */ + font-size: 0.75rem; /* Small font size, adjust to match */ + line-height: 1.5; + border-radius: 0.2rem; + min-height: calc(1.5em + 0.5rem + 2px); /* Matches your inline style */ +} + +/* Optional: Styling for radio/checkbox inputs */ +.form-check-input { + /* No font-size here since it’s an input’s appearance, not text */ + margin-top: 0.25rem; /* Align with small text */ +} + +/* If you have + ); +}); + +NitroInput.displayName = 'NitroInput'; diff --git a/src/layout/NitroItemCountBadge.tsx b/src/layout/NitroItemCountBadge.tsx new file mode 100644 index 0000000..f8b0782 --- /dev/null +++ b/src/layout/NitroItemCountBadge.tsx @@ -0,0 +1,33 @@ +import { DetailedHTMLProps, forwardRef, HTMLAttributes, PropsWithChildren } from 'react'; +import { classNames } from './classNames'; + +const classes = { + base: 'text-[white] font-bold leading-none text-[9.5px] absolute right-0 top-0 py-0.5 px-[3px] z-[1] rounded border', + themes: { + 'primary': 'border-black bg-red-700' + } +}; + +export const NitroItemCountBadge = forwardRef & DetailedHTMLProps, HTMLDivElement>>((props, ref) => +{ + const { theme = 'primary', count = 0, className = null, children = null, ...rest } = props; + + return ( +
+ { count } + { children } +
+ ); +}); + +NitroItemCountBadge.displayName = 'NitroItemCountBadge'; diff --git a/src/layout/classNames.ts b/src/layout/classNames.ts new file mode 100644 index 0000000..2127d85 --- /dev/null +++ b/src/layout/classNames.ts @@ -0,0 +1 @@ +export const classNames = (...classes: string[]) => classes.filter(Boolean).join(' '); diff --git a/src/layout/index.ts b/src/layout/index.ts new file mode 100644 index 0000000..a7041de --- /dev/null +++ b/src/layout/index.ts @@ -0,0 +1,8 @@ +export * from './InfiniteGrid'; +export * from './NitroButton'; +export * from './NitroCard'; +export * from './NitroInput'; +export * from './NitroItemCountBadge'; +export * from './classNames'; +export * from './limited-edition'; +export * from './styleNames'; diff --git a/src/layout/limited-edition/NitroLimitedEditionStyledNumberView.tsx b/src/layout/limited-edition/NitroLimitedEditionStyledNumberView.tsx new file mode 100644 index 0000000..0cd0250 --- /dev/null +++ b/src/layout/limited-edition/NitroLimitedEditionStyledNumberView.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react'; + +export const NitroLimitedEditionStyledNumberView: FC<{ + value: number; +}> = props => +{ + const { value = 0 } = props; + + return ( + <> + { value.toString().split('').map((number, index) => + + ) } + + ); +}; diff --git a/src/layout/limited-edition/index.ts b/src/layout/limited-edition/index.ts new file mode 100644 index 0000000..079a6d4 --- /dev/null +++ b/src/layout/limited-edition/index.ts @@ -0,0 +1 @@ +export * from './NitroLimitedEditionStyledNumberView'; diff --git a/src/layout/styleNames.ts b/src/layout/styleNames.ts new file mode 100644 index 0000000..ac58f77 --- /dev/null +++ b/src/layout/styleNames.ts @@ -0,0 +1,8 @@ +export const styleNames = (...styles: object[]) => +{ + let mergedStyle = {}; + + styles.filter(Boolean).forEach(style => mergedStyle = { ...mergedStyle, ...style }); + + return mergedStyle; +}; diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts new file mode 100644 index 0000000..6431bc5 --- /dev/null +++ b/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/workers/IntervalWebWorker.ts b/src/workers/IntervalWebWorker.ts new file mode 100644 index 0000000..add63f6 --- /dev/null +++ b/src/workers/IntervalWebWorker.ts @@ -0,0 +1,26 @@ +export default () => +{ + let interval: ReturnType = null; + + + self.onmessage = (message: MessageEvent) => + { + if(!message) return; + + const data: { [index: string]: any } = message.data; + + switch(data.action) + { + case 'START': + interval = setInterval(() => postMessage(null), data.content); + break; + case 'STOP': + if(interval) + { + clearInterval(interval); + interval = null; + } + break; + } + }; +}; diff --git a/src/workers/WorkerBuilder.ts b/src/workers/WorkerBuilder.ts new file mode 100644 index 0000000..b848893 --- /dev/null +++ b/src/workers/WorkerBuilder.ts @@ -0,0 +1,10 @@ +export class WorkerBuilder extends Worker +{ + constructor(worker) + { + const code = worker.toString(); + const blob = new Blob([ `(${ code })()` ]); + + super(URL.createObjectURL(blob)); + } +} diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..9ac9330 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,160 @@ +/** @type {import('tailwindcss').Config} */ + +const { generateShades } = require('./css-utils/CSSColorUtils'); + +const colors = { + 'toolbar': '#555555', + 'card-header': '#1E7295', + 'card-close': '#921911', + 'card-tabs': '#185D79', + 'card-border': '#283F5D', + 'card-tab-item': '#B6BEC5', + 'card-tab-item-active': '#DFDFDF', + 'card-content-area': '#DFDFDF', + 'card-grid-item': '#CDD3D9', + 'card-grid-item-active': '#ECECEC', + 'card-grid-item-border': '#B6BEC5', + 'card-grid-item-border-active': '#FFFFFF', + 'loading': '#393A85', + 'muted': 'rgba(182, 190, 197)', + 'blue': '#0d6efd', + 'indigo': '#6610f2', + 'pink': '#d63384', + 'red': '#a81a12', + 'orange': '#fd7e14', + 'yellow': '#ffc107', + 'green': '#00800b', + 'teal': '#20c997', + 'cyan': '#0dcaf0', + 'gray': '#6c757d', + 'gray-dark': '#343a40', + 'gray-100': '#f8f9fa', + 'gray-200': '#e9ecef', + 'gray-300': '#dee2e6', + 'gray-400': '#ced4da', + 'gray-500': '#adb5bd', + 'gray-600': '#6c757d', + 'gray-700': '#495057', + 'gray-800': '#343a40', + 'gray-900': '#212529', + 'primary': '#1E7295', + 'secondary': '#185D79', + 'success': '#00800b', + 'info': '#0dcaf0', + 'warning': '#ffc107', + 'danger': '#a81a12', + 'light': '#DFDFDF', + 'dark': 'rgba(28, 28, 32, .9803921569)', + 'light-dark': '#343a40', + 'white': '#fff', + 'black': '#000', + 'muted': '#B6BEC5', + 'purple': '#6f42c1', + 'gainsboro': '#d9d9d9' +}; + +const boxShadow = { + 'inner1px': 'inset 0 0 0 1px rgba(255,255,255,.3)', + 'room-previewer': '-2px -2px rgba(0, 0, 0, 0.4), inset 3px 3px rgba(0, 0, 0, 0.2);' +}; + + +module.exports = { + theme: { + extend: { + fontSize: { + base: '0.9rem', + sm: '0.7875rem', + xl: '1.25rem', + '2xl': '1.563rem', + '3xl': '1.953rem', + '4xl': '2.441rem', + '5xl': '3.052rem', + }, + + fontFamily: { + sans: [ 'Ubuntu' ], + }, + colors: generateShades(colors), + boxShadow, + backgroundImage: { + 'button-gradient-gray': 'linear-gradient(to bottom, #e2e2e2 50%, #c8c8c8 50%)', + }, + spacing: { + 'card-header': '33px', + 'card-tabs': '33px', + 'navigator-w': '420px', + 'navigator-h': '440px', + 'inventory-w': '528px', + 'inventory-h': '320px' + }, + borderRadius: { + + '3': '0.3rem', + + }, + zIndex: { + 'toolbar': '', + 'loading': '100', + 'chat-zindex': '20' + }, + dropShadow: { + 'hover': '2px 2px 0 rgba(0,0,0,0.8)' + }, + }, + }, + safelist: [ + 'grid-cols-1', + 'grid-cols-2', + 'grid-cols-3', + 'grid-cols-4', + 'grid-cols-5', + 'grid-cols-6', + 'grid-cols-7', + 'grid-cols-8', + 'grid-cols-9', + 'grid-cols-10', + 'grid-cols-11', + 'grid-cols-12', + 'col-span-1', + 'col-span-2', + 'col-span-3', + 'col-span-4', + 'col-span-5', + 'col-span-6', + 'col-span-7', + 'col-span-8', + 'col-span-9', + 'col-span-10', + 'col-span-11', + 'col-span-12', + 'grid-rows-1', + 'grid-rows-2', + 'grid-rows-3', + 'grid-rows-4', + 'grid-rows-5', + 'grid-rows-6', + 'grid-rows-7', + 'grid-rows-8', + 'grid-rows-9', + 'grid-rows-10', + 'grid-rows-11', + 'grid-rows-12', + 'justify-end', + 'items-end' + ], + darkMode: 'class', + variants: { + extend: { + divideColor: [ 'group-hover' ], + backgroundColor: [ 'group-focus' ], + } + }, + plugins: [ + require('@tailwindcss/forms'), + ], + content: [ + './index.html', + './src/**/*.{html,js,jsx,ts,tsx}' + ] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e5c6ba1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "target": "es2022", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": false, + "downlevelIteration": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": false, + "module": "ES2022", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "paths": { + "@layout/*": [ + "layout/*" + ] + } + }, + "include": [ + "src", + "node_modules/@nitrots/nitro-renderer/src/**/*.ts" + ] +} diff --git a/vite.config.mjs b/vite.config.mjs new file mode 100644 index 0000000..3267645 --- /dev/null +++ b/vite.config.mjs @@ -0,0 +1,32 @@ +import react from '@vitejs/plugin-react'; +import { resolve } from 'path'; +import { defineConfig } from 'vite'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + plugins: [ react(), tsconfigPaths() ], + resolve: { + alias: { + '@': resolve(__dirname, 'src'), + '~': resolve(__dirname, 'node_modules') + } + }, + build: { + assetsInlineLimit: 102400, + chunkSizeWarningLimit: 200000, + rollupOptions: { + output: { + assetFileNames: 'src/assets/[name].[ext]', + manualChunks: id => + { + if(id.includes('node_modules')) + { + if(id.includes('@nitrots/nitro-renderer')) return 'nitro-renderer'; + + return 'vendor'; + } + } + } + } + } +})