Tablet Architecture
Data Flow
UI → Server (Save)
React Component
↓ calls saveSettingsToDB() / saveAppDataToDB()
lib/dbStorage.js
↓ fetch('https://wtf_group/tablet:saveSettings', { settings })
client/tablet.lua
↓ RegisterNUICallback → TriggerServerEvent
server/tablet.lua
↓ event handler → DB.SaveSettings() / DB.SetAppData()
server/database.lua
↓ MySQL.insert.await(...)
MySQL DatabaseServer → UI (Load)
React Component (on mount)
↓ calls loadSettingsFromDBAsync() / loadAppDataFromDB()
lib/dbStorage.js
↓ fetch('https://wtf_group/tablet:loadSettings')
client/tablet.lua
↓ RegisterNUICallback → TriggerServerEvent
server/tablet.lua
↓ DB.LoadSettings() → TriggerClientEvent('wtf_group:client:tablet:settingsLoaded')
client/tablet.lua
↓ RegisterNetEvent → SendNUIMessage({ action: 'tablet:settingsLoaded', settings })
React window.addEventListener('message')
↓ dbStorage.js waitForEvent() resolves
React Component
↓ setState with DB dataContext Provider Hierarchy
jsx
<GroupProvider> // GroupContext — legacy group state
<TabletProvider> // TabletContext — settings, theme, open/close
<AppProvider> // AppContext — app registry, window management
<AppContent /> // Root component, NUI message handler
</AppProvider>
</TabletProvider>
</GroupProvider>TabletContext (Settings)
State: All settings (theme, colors, font, scale, etc.) + isOpen flag
Provides:
isOpen,openTablet(),closeTablet(),toggleTablet()- All setting values (
themeColors,accentColor,fontFamily, etc.) updateSetting(key, value)— single setting changeupdateSettings({...})— batch updateresetSettings()— reset to defaultsthemeColors— derived from base theme + user custom colors
Persistence:
- Load:
loadSettings()sync from localStorage,loadSettingsFromDBAsync()async from DB - Save:
saveSettings()writes to both localStorage and DB
AppContext (Windows)
State: registeredApps, windows[], focusedWindowId, zIndexCounter
Provides:
registerApp(app)/unregisterApp(appId)— app registrationopenApp(appId, options)— creates windowcloseWindow(id),focusWindow(id),minimizeWindow(id),maximizeWindow(id)moveWindow(id, x, y),resizeWindow(id, w, h)updateWindowProps(id, props)— update window component propsminimizeAll()— minimize all windows
GroupContext (Legacy)
State: Group data, members, tasks, cooldowns, invites, notifications
Provides: Group CRUD, member management, task operations, real-time sync
NUI Message Protocol
Lua → React (via SendNUIMessage)
| Action | Data | Purpose |
|---|---|---|
tablet:open | — | Open tablet shell |
tablet:close | — | Close tablet shell |
tablet:settingsLoaded | settings | Settings loaded from DB |
tablet:installedAppsLoaded | apps | Installed apps loaded from DB |
tablet:appDataLoaded | appId, key, value | App data loaded from DB |
tablet:allAppDataLoaded | appId, data | All app data loaded |
tablet:notification | appId, title, message, type | Show notification |
openUI | data, data2 | Legacy group open |
closeUI | — | Legacy group close |
groupUpdated | data | Group state sync |
showNotification | title, description, type | Show in-UI toast |
React → Lua (via fetch NUI callback)
| Callback | Data | Purpose |
|---|---|---|
tablet:loadSettings | — | Request settings from DB |
tablet:saveSettings | settings | Save settings to DB |
tablet:loadInstalledApps | — | Request installed apps from DB |
tablet:saveInstalledApps | apps | Save installed apps to DB |
tablet:setAppData | appId, key, value | Save app data to DB |
tablet:getAppData | appId, key | Request app data from DB |
tablet:getAllAppData | appId | Request all data for an app |
tablet:deleteAppData | appId, key | Delete app data from DB |
tablet:openApp | appId | Notify server app opened |
tablet:closeApp | windowId | Notify server app closed |
tablet:sendMessage | appId, messageType, data | Send message to server app |
closeUI | — | Close tablet from UI |
Window Lifecycle
- Open:
openApp(appId)→ AppContext creates window with unique ID, position, size - Focus: Click window or taskbar item → sets
isFocused: true, increments z-index - Minimize: Click minimize button → sets
isMinimized: true, removes focus - Maximize: Click maximize button → toggles
isMaximized, restores if minimized - Drag: Mouse down on titlebar → updates x/y on mousemove
- Resize: Mouse down on edge/corner handle → updates width/height on mousemove
- Close: Click close button → removes window from array
Persistence Strategy
- localStorage: Instant UI load, acts as cache
- MySQL: Durable storage, loaded async on mount
- Dual Write: Every save writes to both localStorage and DB
- DB Override: On mount, if DB has data, it overrides localStorage
This ensures:
- UI loads instantly from localStorage (no flash)
- Data survives server restarts and player reconnects
- If DB is unavailable, localStorage provides fallback