@hexah/create-skin 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -4
- package/index.js +3 -1
- package/package.json +1 -1
- package/template/AGENTS.md +95 -0
- package/template/CLAUDE.md +6 -0
- package/template/README.md +41 -11
- package/template/rspack.config.js +9 -1
- package/template/src/ArticleScreen.jsx +74 -0
- package/template/src/GptListScreen.jsx +87 -0
- package/template/src/SkinFrame.jsx +84 -0
- package/template/src/examples/dataHooks.jsx +133 -0
- package/template/src/examples/slots.jsx +31 -0
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ localStorage.setItem('hexah.skinRemote', JSON.stringify({
|
|
|
38
38
|
entry: 'https://<rand>.trycloudflare.com/remoteEntry.js',
|
|
39
39
|
template: 'sdkremote',
|
|
40
40
|
screens: { report: './ReportScreen' },
|
|
41
|
-
apiVersion: '0.
|
|
41
|
+
apiVersion: '0.11.0',
|
|
42
42
|
}))
|
|
43
43
|
```
|
|
44
44
|
|
|
@@ -46,13 +46,111 @@ Odśwież stronę, w **Ustawieniach Gry** wybierz motyw **„SDK Remote (zdalna
|
|
|
46
46
|
ekran zgłoszeń — zobaczysz swój `ReportScreen` na realnych danych. Po każdej zmianie w `src/`
|
|
47
47
|
odśwież stronę (remote przebuduje się przez HMR).
|
|
48
48
|
|
|
49
|
+
## Budowanie motywu — co masz do dyspozycji
|
|
50
|
+
|
|
51
|
+
Importuj **wyłącznie** z `@hexah/skin-sdk` (+ `react` i `@mui/*` jako współdzielone singletony
|
|
52
|
+
hosta). Nie masz dostępu do kodu gry — host dostarcza w runtime read-modele danych, usługi
|
|
53
|
+
platformy, prymitywy UI i ciężkie sloty. Kontrakt jest wersjonowany (`SKIN_API_VERSION`,
|
|
54
|
+
obecnie **0.11.0**); deklarowane `apiVersion` musi mieć ten sam major i minor ≤ host.
|
|
55
|
+
|
|
56
|
+
### Trzy sposoby na skórkę
|
|
57
|
+
|
|
58
|
+
1. **Pojedynczy ekran** — komponent zarejestrowany pod kluczem z `SCREEN_KEYS`. W
|
|
59
|
+
`rspack.config.js` wystawiasz go (`exposes`), w configu `localStorage` mapujesz na klucz
|
|
60
|
+
(`screens: { report: './ReportScreen' }`). Host renderuje go w miejscu danej podstrony.
|
|
61
|
+
2. **Rama całej strony (layout/chrome)** — komponent dostaje `children` (aktywny ekran) i owija
|
|
62
|
+
go własnym paskiem/nawigacją/stopką. Wystawiasz np. `./SkinFrame` i podajesz w configu
|
|
63
|
+
`layout: './SkinFrame'`. Możesz dać samą ramę, same ekrany, albo jedno i drugie. Tutorial:
|
|
64
|
+
[`docs/skin-sdk/tutorial-frame.md`](https://github.com/Kroniki-Fallathanu/hexah/blob/develop/docs/skin-sdk/tutorial-frame.md).
|
|
65
|
+
3. **Rozmieszczanie slotów** — ciężkie fragmenty z logiką/danymi (np. mapa świata, statystyki
|
|
66
|
+
zgłoszeń) renderuje host; Ty pobierasz je gotowe przez `useSlots()` i tylko ustawiasz w layoutcie.
|
|
67
|
+
|
|
68
|
+
### Konwencja read-modeli
|
|
69
|
+
|
|
70
|
+
Każdy read-model to hook zwracający obiekt metod. Metoda przyjmuje **jeden obiekt opcji** z
|
|
71
|
+
polem `callback` (odbiera odpowiedź socketu) — pozostałe pola lecą do hosta jako `data`:
|
|
72
|
+
|
|
73
|
+
```jsx
|
|
74
|
+
const reports = useReports()
|
|
75
|
+
reports.list({ page: 1, pageSize: 10, callback: (ret) => {
|
|
76
|
+
if (ret?.error) return
|
|
77
|
+
setIssues(ret.issues) // kształt zwrotki zależy od metody
|
|
78
|
+
}})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Hooki danych (jeden na podstronę menu)
|
|
82
|
+
|
|
83
|
+
| Hook | Podstrona | Metody |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| `useArticles` | Terminal / Aktualności / Kurier | `list`, `getBySlug`, `recordView`, `markKurierRead`, `listComments`, `createComment`, `toggleReaction` |
|
|
86
|
+
| `useGptThreads` | Harold (GPT) | `list`, `get`, `create`, `remove`, `sendMessage` |
|
|
87
|
+
| `useTales` | Operacje (opowieści) | `list`, `search`, `get`, `getFavorites`, `getPreferences`, `togglePreference`, `join`, `leave`, `toggleSubscribe`, `checkSubscribe` |
|
|
88
|
+
| `useInn` | Mesa (karczma) | `listTales`, `getMembersCount` |
|
|
89
|
+
| `useTown` | Metropolia (budynki) | `bankSend`, `innRest`, `innBuyTravelerKit`, `healerPlantsList`, `healerHeal`, `voltarToolsList`, `voltarBuy`, `hallTypes`, `hallPriceList`, `hallBuy`, `workshopPlansList`, `workshopPlanGet`, `workshopCraftItem`, `forgePlansList`, `forgePlanGet`, `forgeCraftMaterial`, `workPerform` |
|
|
90
|
+
| `useGuild` | Frakcje (gildie) | `create`, `join`, `list`, `search`, `get`, `sendCrabs`, `headquartersRestInfo`, `headquartersRest`, `leave`, `getPermissions`, `specialActions`, `getProperties`, `hexmapPopulationTotal`, `hexmapMappingActivityMonth`, `provinceInfo`, `publicStats`, `hexmapPopulations`, `hexmapPopulationsByMember`, `warehouseGet`, `warehouseDeposit`, `warehouseYardWithdraw`, `materialsGet`, `materialsDonate`, `materialsDistribute` |
|
|
91
|
+
| `useFamily` | Dynastie (rody) | `create`, `list`, `get`, `getProperties`, `choseName`, `invite`, `acceptInvitation`, `removeMember`, `editMember`, `editSpouse`, `transferOwner`, `setPermission`, `changeDescription`, `npcAccessGrant`, `npcAccessRevoke`, `npcAccessList`, `depositCrabs`, `depositInfluencePoints` |
|
|
92
|
+
| `useVillages` | Kolonie (osady) | `found`, `takeAbandoned`, `get`, `list`, `buildCreate`, `buildCost`, `buildUpgrade`, `buildDemolish`, `buildMove`, `buildResetAll`, `depositCrabs`, `withdrawCrabs`, `sendResourcesToGuild`, `abandon`, `rebellionResolve`, `taxesSet`, `governorAssign`, `techniqueList`, `techniqueUpgrade`, `hexDecorationsUpdate`, `nameUpdate` |
|
|
93
|
+
| `useShop` | Zaopatrzenie (sklep) | `getOffers`, `getItems`, `buyItem`, `startPayment` |
|
|
94
|
+
| `useRanking` | Notowania (rankingi) | `ranking`, `rankingExplorer` |
|
|
95
|
+
| `useAccountProfile` | Profil (konto) | `getAccountInfo`, `getBlockedList`, `blockUser`, `unblockUser`, `linkDiscord`, `getGlobalStats` |
|
|
96
|
+
| `useSettings` | Konfiguracja | `updateSetting`, `setInvisibleOnline`, `updateNewsletterSubscription` |
|
|
97
|
+
| `useReports` | Zgłoszenia (bugtracker) | `list`, `markAllRead`, `setIssueNotification` |
|
|
98
|
+
|
|
99
|
+
### Read-modele stanu (czytają aktywny stan, bez argumentów)
|
|
100
|
+
|
|
101
|
+
`useShard()` · `useShardUser()` · `useCharacter()` · `useAccount()` · `usePageData()` ·
|
|
102
|
+
`useOnlineCharacters()` — zwracają bieżące dane świata/postaci/konta (lub `null`, gdy brak).
|
|
103
|
+
|
|
104
|
+
### Usługi platformy
|
|
105
|
+
|
|
106
|
+
- `useSnackbar()` → `{ notify }` — `notify([{ severity, message }])` pokazuje powiadomienie hosta.
|
|
107
|
+
- `useDialog()` → `{ open, close }` — modal hosta (`open(config)` / `close()`).
|
|
108
|
+
- `useGamePageShell({ title })` — ustawia tytuł/powłokę strony (wołaj w `useMemo`, jak w przykładzie).
|
|
109
|
+
|
|
110
|
+
### Prymitywy UI (motywowalne komponenty MUI hosta)
|
|
111
|
+
|
|
112
|
+
`PageBox` (kontener strony, pełna wysokość) · `HexahCharacterSection` · `AngularPanel` ·
|
|
113
|
+
`StandardButton` (tekst ≤ 16 znaków) · `RoundAvatar` · `DataList` (lista wierszy z opisem/akcją) ·
|
|
114
|
+
`HexahChip` · `HexahPagination` + `HexahPaginationItem`. Poza nimi używaj zwykłego `@mui/material`
|
|
115
|
+
(`Box`, `Typography`, …) — to też instancja hosta.
|
|
116
|
+
|
|
117
|
+
### Sloty (host renderuje, skórka rozmieszcza)
|
|
118
|
+
|
|
119
|
+
```jsx
|
|
120
|
+
import { useSlots, SLOT_KEYS } from '@hexah/skin-sdk'
|
|
121
|
+
const slots = useSlots()
|
|
122
|
+
const WorldMap = slots[SLOT_KEYS.WORLD_MAP]
|
|
123
|
+
return WorldMap ? <WorldMap /> : null // łagodna degradacja, gdy host nie dostarcza slotu
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Zarejestrowane: `WORLD_MAP` (Mapa Sektora — mapa świata + ruiny), `ISSUE_STATS` (statystyki
|
|
127
|
+
zgłoszeń). Zarezerwowane (jeszcze bez komponentu): `GAME_PAGE`, `ISSUE_RICH_CONTENT`,
|
|
128
|
+
`ARTICLE_COMMENTS`, `ARTICLE_ISSUE_GRID`.
|
|
129
|
+
|
|
130
|
+
### Stałe i utile
|
|
131
|
+
|
|
132
|
+
- `SCREEN_KEYS` — klucze ekranów: `ARTICLE`, `KURIER`, `KURIER_REDAKCJA`, `KURIER_SKRZYNKA`,
|
|
133
|
+
`GPT_LIST`, `GPT_THREAD`, `REPORT`, `REPORT_DETAIL`, `ARTICLE_DETAIL`.
|
|
134
|
+
- `SLOT_KEYS` — klucze slotów (jak wyżej). `DEFAULT_TEMPLATE` · `SKIN_API_VERSION`.
|
|
135
|
+
- Utile nawigacji ekranu zgłoszeń (czyste funkcje): `buildReportListHref`, `buildReportDetailHref`,
|
|
136
|
+
`buildReportBackHref`, `parseReportListPage`, `parseReportStateFilter`, `parseReportTypeFilter`,
|
|
137
|
+
`parseReportMineFilter`, `parseReportPlannedFilter`; style chipów: `getStateChipSx`,
|
|
138
|
+
`getIssueTypeChipSx`.
|
|
139
|
+
|
|
140
|
+
> Pełna mapa „podstrona menu → hook/slot" i decyzje projektowe:
|
|
141
|
+
> [`docs/skin-sdk/data-hooks.md`](https://github.com/Kroniki-Fallathanu/hexah/blob/develop/docs/skin-sdk/data-hooks.md).
|
|
142
|
+
|
|
49
143
|
## Co generuje
|
|
50
144
|
|
|
51
145
|
Gotowy projekt skórki:
|
|
52
146
|
|
|
53
|
-
- `rspack.config.js` — `ModuleFederationPlugin` wystawiający
|
|
54
|
-
|
|
55
|
-
- `src/ReportScreen.jsx` —
|
|
147
|
+
- `rspack.config.js` — `ModuleFederationPlugin` wystawiający ramę i ekrany, paczki współdzielone
|
|
148
|
+
z hostem (`import: false`), dev server na `:3001` (CORS + `allowedHosts`).
|
|
149
|
+
- `src/SkinFrame.jsx`, `src/ReportScreen.jsx`, `src/ArticleScreen.jsx`, `src/GptListScreen.jsx` —
|
|
150
|
+
gotowe wzorce (rama całej strony + ekrany) na `@hexah/skin-sdk`.
|
|
151
|
+
- `src/examples/` — referencja: wywołania pozostałych read-modeli danych i slotów (do kopiowania).
|
|
152
|
+
- **`AGENTS.md` + `CLAUDE.md`** — zasady i granice dla asystentów AI (import tylko z SDK, brak
|
|
153
|
+
dostępu do kodu/backendu gry, współdzielone singletony). AI czyta je automatycznie.
|
|
56
154
|
- `package.json`, `.gitignore`, `README.md` z instrukcją podłączenia do środowiska testowego.
|
|
57
155
|
|
|
58
156
|
Nazwa kontenera Module Federation jest automatycznie sanityzowana z nazwy katalogu do
|
package/index.js
CHANGED
|
@@ -45,11 +45,13 @@ function main(argv) {
|
|
|
45
45
|
console.log(
|
|
46
46
|
" entry: 'http://localhost:3001/remoteEntry.js', template: 'sdkremote',",
|
|
47
47
|
);
|
|
48
|
-
console.log(" screens: { report: './ReportScreen' }, apiVersion: '0.
|
|
48
|
+
console.log(" screens: { report: './ReportScreen' }, apiVersion: '0.11.0' }))");
|
|
49
49
|
console.log("Zdeployowany test (https) wymaga publicznego URL — odpal tunel:");
|
|
50
50
|
console.log(" npx cloudflared tunnel --url http://localhost:3001 # → https://<rand>.trycloudflare.com");
|
|
51
51
|
console.log("i użyj tego URL w entry + hexah.skinRemoteAllowedOrigins. Patrz README.");
|
|
52
52
|
console.log("Następnie w Ustawieniach Gry wybierz motyw „SDK Remote (zdalna skórka)\".");
|
|
53
|
+
console.log("");
|
|
54
|
+
console.log("Używasz AI (Claude/Cursor/Copilot)? Zasady i granice skórki są w AGENTS.md — wskaż je asystentowi.");
|
|
53
55
|
return 0;
|
|
54
56
|
}
|
|
55
57
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# AGENTS.md — zasady dla asystentów AI (skórka „__SKIN_NAME__")
|
|
2
|
+
|
|
3
|
+
Ten plik czyta asystent AI (Claude Code, Cursor, Copilot i in.) pracujący w tym repozytorium.
|
|
4
|
+
Trzymaj się go ściśle — wyznacza **twarde granice** skórki Hexah. Jeśli prośba użytkownika
|
|
5
|
+
łamie te zasady, powiedz to wprost zamiast obchodzić regułę.
|
|
6
|
+
|
|
7
|
+
## Czym jest ten projekt
|
|
8
|
+
|
|
9
|
+
To **skórka (motyw) gry Hexah** zbudowana jako **zdalny kontener Module Federation** (rspack).
|
|
10
|
+
Nie jest samodzielną aplikacją: ładuje ją działający host Hexah w runtime i renderuje Twoje
|
|
11
|
+
komponenty wewnątrz gry. Budujesz **wyłącznie** na publicznym kontrakcie `@hexah/skin-sdk` —
|
|
12
|
+
**nie masz i nie będziesz mieć dostępu do kodu gry (hosta)**.
|
|
13
|
+
|
|
14
|
+
## Twarde granice (nie do złamania)
|
|
15
|
+
|
|
16
|
+
1. **Importuj tylko z `@hexah/skin-sdk`** (oraz `react`, `react-dom`, `@mui/*`, `@emotion/*`,
|
|
17
|
+
`jotai`). **Nigdy** nie importuj z wnętrza gry (`@/...`, `frontend/...`, ścieżki hosta) —
|
|
18
|
+
takie moduły nie istnieją w buildzie skórki. Nie wymyślaj eksportów z SDK: jeśli czegoś nie
|
|
19
|
+
ma w `@hexah/skin-sdk`, to nie jest dostępne.
|
|
20
|
+
2. **Nie pakuj współdzielonych bibliotek.** `react`/`react-dom`/`jotai`/`@mui/*`/`@emotion/*`/
|
|
21
|
+
`@hexah/skin-sdk` są w `rspack.config.js` jako `shared` z `import: false` (singletony hosta).
|
|
22
|
+
Nie zmieniaj tego na bundlowanie — dwa Reacty/store'y rozsypią aplikację.
|
|
23
|
+
3. **Nie obchodź backendu gry.** Dane bierz **wyłącznie** z read-modeli SDK (`useReports`,
|
|
24
|
+
`useArticles`, …). Zakaz: własny `fetch`/`axios`/WebSocket do API gry, własny klient socket,
|
|
25
|
+
czytanie cookie/tokenów sesji, scrapowanie globalnego stanu.
|
|
26
|
+
4. **Nie zaglądaj do wnętrza slotów.** Ciężkie fragmenty (mapa, statystyki) dostajesz jako
|
|
27
|
+
gotowe komponenty przez `useSlots()` — renderuje je host. Nie próbuj odtwarzać ich logiki ani
|
|
28
|
+
sięgać po ich dane.
|
|
29
|
+
5. **`apiVersion` zgodne z hostem** (ten sam major, minor ≤ host). Nie podbijaj go „na zapas".
|
|
30
|
+
6. **Zero sekretów w repo** (klucze, tokeny). Skórka jest publiczna i ładowana w przeglądarce.
|
|
31
|
+
7. **Nie zakładaj nawigacji/motywu hosta.** Linki to zwykłe publiczne trasy `/game/*`. Routing,
|
|
32
|
+
motyw (kolory/spacing) i powłokę kontroluje host — używaj tokenów MUI (`sx`, `theme`), nie
|
|
33
|
+
hardkoduj kolorów/pikseli.
|
|
34
|
+
|
|
35
|
+
## Powierzchnia, na której wolno budować
|
|
36
|
+
|
|
37
|
+
- **Read-modele danych** (jeden hook na podstronę menu): `useArticles`, `useGptThreads`,
|
|
38
|
+
`useTales`, `useInn`, `useTown`, `useGuild`, `useFamily`, `useVillages`, `useShop`,
|
|
39
|
+
`useRanking`, `useAccountProfile`, `useSettings`, `useReports`.
|
|
40
|
+
- **Read-modele stanu**: `useShard`, `useShardUser`, `useCharacter`, `useAccount`, `usePageData`,
|
|
41
|
+
`useOnlineCharacters`.
|
|
42
|
+
- **Usługi platformy**: `useSnackbar` (`{ notify }`), `useDialog` (`{ open, close }`),
|
|
43
|
+
`useGamePageShell({ title })`.
|
|
44
|
+
- **Prymitywy UI**: `PageBox`, `HexahCharacterSection`, `AngularPanel`, `StandardButton`
|
|
45
|
+
(tekst ≤ 16 znaków), `RoundAvatar`, `DataList`, `HexahChip`, `HexahPagination`,
|
|
46
|
+
`HexahPaginationItem`. Poza nimi zwykłe `@mui/material`.
|
|
47
|
+
- **Sloty**: `useSlots()` + `SLOT_KEYS` (`WORLD_MAP`, `ISSUE_STATS`, …).
|
|
48
|
+
- **Stałe**: `SCREEN_KEYS`, `SLOT_KEYS`, `DEFAULT_TEMPLATE`, `SKIN_API_VERSION`.
|
|
49
|
+
|
|
50
|
+
> Pełna lista metod każdego hooka i opis API: README scaffolderu `@hexah/create-skin`
|
|
51
|
+
> (sekcja „Budowanie motywu — co masz do dyspozycji") oraz README paczki `@hexah/skin-sdk`.
|
|
52
|
+
> Najpierw sprawdź te dokumenty — nie zgaduj sygnatur.
|
|
53
|
+
|
|
54
|
+
## Konwencja read-modeli
|
|
55
|
+
|
|
56
|
+
Hook zwraca obiekt metod. Metoda przyjmuje **jeden obiekt opcji** z polem `callback`; pozostałe
|
|
57
|
+
pola lecą do hosta jako `data`. Kształt zwrotki (`ret`) zależy od metody — najczęściej
|
|
58
|
+
`{ results, pagination }` albo `{ error }`. Zawsze obsłuż `ret?.error` (np. przez `useSnackbar`).
|
|
59
|
+
|
|
60
|
+
```jsx
|
|
61
|
+
const articles = useArticles()
|
|
62
|
+
articles.list({ page: 1, pageSize: 10, callback: (ret) => {
|
|
63
|
+
if (ret?.error) { notify([{ severity: 'error', message: ret.error }]); return }
|
|
64
|
+
setItems(ret.results || [])
|
|
65
|
+
}})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Trzy sposoby budowania skórki
|
|
69
|
+
|
|
70
|
+
1. **Ekran** — komponent pod kluczem z `SCREEN_KEYS`. Wystaw w `rspack.config.js` → `exposes`,
|
|
71
|
+
zmapuj w configu `screens: { report: './ReportScreen' }`. Przykłady: `src/ReportScreen.jsx`,
|
|
72
|
+
`src/ArticleScreen.jsx`, `src/GptListScreen.jsx`.
|
|
73
|
+
2. **Rama całej strony (chrome)** — komponent dostaje `children` (aktywny ekran) i owija go
|
|
74
|
+
paskiem/nawigacją/stopką. Wystaw `./SkinFrame`, podaj `layout: './SkinFrame'`. Przykład:
|
|
75
|
+
`src/SkinFrame.jsx`.
|
|
76
|
+
3. **Rozmieszczanie slotów** — `const Map = useSlots()[SLOT_KEYS.WORLD_MAP]; return Map ? <Map/> : null`.
|
|
77
|
+
Zawsze degraduj łagodnie, gdy slot jest `undefined`. Przykłady: `src/examples/slots.jsx`.
|
|
78
|
+
|
|
79
|
+
Wzorce wszystkich pozostałych read-modeli: `src/examples/dataHooks.jsx` (referencja do kopiowania).
|
|
80
|
+
|
|
81
|
+
## Workflow i weryfikacja
|
|
82
|
+
|
|
83
|
+
- Dev: `npm run dev` (rspack serve, HMR) → `remoteEntry.js` na `:3001`.
|
|
84
|
+
- Test w grze: wystaw przez tunel (cloudflared), zautoryzuj origin i wskaż remote przez
|
|
85
|
+
`localStorage` na środowisku testowym — instrukcja w `README.md`.
|
|
86
|
+
- Po zmianach upewnij się, że build przechodzi i import-checki trzymają granice z tego pliku.
|
|
87
|
+
- Nie dodawaj zależności runtime, które dublują współdzielone singletony hosta.
|
|
88
|
+
|
|
89
|
+
## Czego NIE robić (skrót)
|
|
90
|
+
|
|
91
|
+
- ❌ import z kodu gry / „magiczne" eksporty SDK, których nie ma w dokumentacji
|
|
92
|
+
- ❌ własny dostęp do backendu/socketów/sesji gry
|
|
93
|
+
- ❌ bundlowanie react/jotai/@mui/@emotion/@hexah/skin-sdk
|
|
94
|
+
- ❌ hardkodowane kolory/piksele zamiast tokenów MUI
|
|
95
|
+
- ❌ sekrety w repo; podbijanie `apiVersion` ponad hosta
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Zasady i granice tego projektu (skórka Hexah) są w **[`AGENTS.md`](./AGENTS.md)** — przeczytaj je
|
|
4
|
+
przed jakąkolwiek zmianą. W skrócie: importuj wyłącznie z `@hexah/skin-sdk` (+ React/MUI), nie
|
|
5
|
+
sięgaj po kod gry ani jej backend, nie pakuj współdzielonych singletonów hosta. Pełna powierzchnia
|
|
6
|
+
API: README scaffolderu `@hexah/create-skin` i README paczki `@hexah/skin-sdk`.
|
package/template/README.md
CHANGED
|
@@ -4,6 +4,10 @@ Skórka Hexah zbudowana jako **zdalny kontener Module Federation**. Renderuje si
|
|
|
4
4
|
aplikacji Hexah na środowisku testowym — budujesz ją wyłącznie na publicznym SDK
|
|
5
5
|
`@hexah/skin-sdk`, bez dostępu do kodu gry.
|
|
6
6
|
|
|
7
|
+
> **Pracujesz z AI (Claude, Cursor, Copilot…)?** Zasady i granice skórki są w
|
|
8
|
+
> [`AGENTS.md`](./AGENTS.md) (Claude czyta też `CLAUDE.md`). Wskaż ten plik asystentowi na starcie —
|
|
9
|
+
> pilnuje, że skórka importuje tylko z `@hexah/skin-sdk` i nie sięga po kod ani backend gry.
|
|
10
|
+
|
|
7
11
|
## Start
|
|
8
12
|
|
|
9
13
|
```bash
|
|
@@ -31,22 +35,44 @@ localStorage.setItem('hexah.skinRemote', JSON.stringify({
|
|
|
31
35
|
name: '__SKIN_FEDERATION_NAME__',
|
|
32
36
|
entry: 'https://<rand>.trycloudflare.com/remoteEntry.js',
|
|
33
37
|
template: 'sdkremote',
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
layout: './SkinFrame', // rama całej strony (chrome) — opcjonalna
|
|
39
|
+
screens: { // ekrany pod kluczami SCREEN_KEYS — opcjonalne
|
|
40
|
+
report: './ReportScreen',
|
|
41
|
+
article: './ArticleScreen',
|
|
42
|
+
gptList: './GptListScreen',
|
|
43
|
+
},
|
|
44
|
+
apiVersion: '0.11.0',
|
|
36
45
|
}))
|
|
37
46
|
```
|
|
38
47
|
|
|
39
|
-
Odśwież, w **Ustawieniach Gry** wybierz motyw **„SDK Remote (zdalna skórka)"
|
|
40
|
-
|
|
41
|
-
odświeżeniu (i ponownym
|
|
48
|
+
Odśwież, w **Ustawieniach Gry** wybierz motyw **„SDK Remote (zdalna skórka)"**. `SkinFrame` owinie
|
|
49
|
+
wszystkie ekrany pod tym motywem, a `ReportScreen`/`ArticleScreen`/`GptListScreen` podmienią
|
|
50
|
+
odpowiednie podstrony — na realnych danych. HMR pokazuje zmiany w `src/` po odświeżeniu (i ponownym
|
|
51
|
+
pobraniu remote'a). Możesz podać samą `layout`, same `screens`, albo jedno i drugie.
|
|
42
52
|
|
|
43
53
|
> Adres tunelu zmienia się po każdym restarcie cloudflared — wklej wtedy nowy URL w oba wpisy
|
|
44
54
|
> `localStorage`.
|
|
45
55
|
|
|
46
56
|
## Co możesz edytować
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
Gotowe przykłady w `src/` (wystawione w `rspack.config.js` → `exposes`):
|
|
59
|
+
|
|
60
|
+
- `src/SkinFrame.jsx` — **rama całej strony** (chrome): górny pasek + nawigacja + stopka, na
|
|
61
|
+
read-modelach stanu (`useCharacter`, `useShard`, `useShardUser`, `useOnlineCharacters`).
|
|
62
|
+
- `src/ReportScreen.jsx` — ekran **Zgłoszeń** (`useReports` + `DataList`/`HexahChip`).
|
|
63
|
+
- `src/ArticleScreen.jsx` — ekran **Aktualności** (`useArticles`).
|
|
64
|
+
- `src/GptListScreen.jsx` — ekran **Harolda** (`useGptThreads` + `StandardButton`).
|
|
65
|
+
|
|
66
|
+
Referencja (nie wystawiona — kopiuj do własnych ekranów):
|
|
67
|
+
|
|
68
|
+
- `src/examples/dataHooks.jsx` — wywołanie **każdego** pozostałego read-modelu danych
|
|
69
|
+
(`useTales`, `useInn`, `useTown`, `useGuild`, `useFamily`, `useVillages`, `useShop`,
|
|
70
|
+
`useRanking`, `useAccountProfile`, `useSettings`).
|
|
71
|
+
- `src/examples/slots.jsx` — sloty hosta przez `useSlots()` (`WORLD_MAP`, `ISSUE_STATS`).
|
|
72
|
+
|
|
73
|
+
Dokładasz własne ekrany w `rspack.config.js` → `exposes` i mapujesz je w configu na klucze
|
|
74
|
+
`SCREEN_KEYS`. **Pełna referencja** (trzy sposoby budowania, tabela hooków z metodami, UI, sloty)
|
|
75
|
+
jest w README scaffolderu `@hexah/create-skin`: <https://www.npmjs.com/package/@hexah/create-skin>.
|
|
50
76
|
|
|
51
77
|
## Zasady
|
|
52
78
|
|
|
@@ -66,9 +92,13 @@ npm install @hexah/skin-sdk
|
|
|
66
92
|
```
|
|
67
93
|
|
|
68
94
|
- Paczka na npm (pełna lista hooków i kontrakt): <https://www.npmjs.com/package/@hexah/skin-sdk>
|
|
69
|
-
- Hooki danych per strona gry (`useArticles`, `useTales`, `
|
|
70
|
-
`useReports`, `useSettings`, `useShop`, `useAccountProfile`,
|
|
71
|
-
|
|
72
|
-
|
|
95
|
+
- Hooki danych per strona gry (`useArticles`, `useTales`, `useInn`, `useTown`, `useGuild`,
|
|
96
|
+
`useFamily`, `useVillages`, `useReports`, `useSettings`, `useShop`, `useAccountProfile`,
|
|
97
|
+
`useRanking`, `useGptThreads`), stan (`useShard`/`useCharacter`/…), usługi
|
|
98
|
+
(`useSnackbar`/`useDialog`), sloty (`useSlots`) i prymitywy UI.
|
|
99
|
+
|
|
100
|
+
**Pełna referencja motywu** (trzy sposoby budowania, tabela hooków z metodami, prymitywy UI,
|
|
101
|
+
sloty, stałe) jest w README scaffolderu `@hexah/create-skin` → sekcja **„Budowanie motywu — co
|
|
102
|
+
masz do dyspozycji"**: <https://www.npmjs.com/package/@hexah/create-skin>.
|
|
73
103
|
|
|
74
104
|
Importuj **wyłącznie** z `@hexah/skin-sdk`. Zadeklarowane `apiVersion` musi być zgodne z hostem.
|
|
@@ -39,7 +39,15 @@ module.exports = {
|
|
|
39
39
|
new ModuleFederationPlugin({
|
|
40
40
|
name: "__SKIN_FEDERATION_NAME__",
|
|
41
41
|
filename: "remoteEntry.js",
|
|
42
|
-
|
|
42
|
+
// Wystawione moduły skórki. Ekrany mapujesz w configu na klucze `SCREEN_KEYS`
|
|
43
|
+
// (`screens: { report: './ReportScreen', article: './ArticleScreen', ... }`), a ramę
|
|
44
|
+
// całej strony na pole `layout` (`layout: './SkinFrame'`). Dorzuć kolejne wg potrzeb.
|
|
45
|
+
exposes: {
|
|
46
|
+
"./SkinFrame": "./src/SkinFrame.jsx",
|
|
47
|
+
"./ReportScreen": "./src/ReportScreen.jsx",
|
|
48
|
+
"./ArticleScreen": "./src/ArticleScreen.jsx",
|
|
49
|
+
"./GptListScreen": "./src/GptListScreen.jsx",
|
|
50
|
+
},
|
|
43
51
|
shared: {
|
|
44
52
|
react: singleton("^19"),
|
|
45
53
|
"react-dom": singleton("^19"),
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRZYKŁAD ekranu Aktualności — rejestrowany pod `SCREEN_KEYS.ARTICLE`
|
|
3
|
+
* (config: `screens: { article: './ArticleScreen' }`).
|
|
4
|
+
*
|
|
5
|
+
* Pokazuje read-model `useArticles` (lista artykułów) + prymitywy UI hosta (`PageBox`,
|
|
6
|
+
* `DataList`, `HexahChip`) + `useGamePageShell` (tytuł strony). Konwencja read-modeli:
|
|
7
|
+
* metoda dostaje obiekt opcji z `callback`, reszta pól leci do hosta jako `data`.
|
|
8
|
+
*/
|
|
9
|
+
import { useEffect, useMemo, useState } from "react";
|
|
10
|
+
import Box from "@mui/material/Box";
|
|
11
|
+
import Typography from "@mui/material/Typography";
|
|
12
|
+
import {
|
|
13
|
+
PageBox,
|
|
14
|
+
DataList,
|
|
15
|
+
HexahChip,
|
|
16
|
+
useArticles,
|
|
17
|
+
useSnackbar,
|
|
18
|
+
useGamePageShell,
|
|
19
|
+
} from "@hexah/skin-sdk";
|
|
20
|
+
|
|
21
|
+
export default function ArticleScreen() {
|
|
22
|
+
const articles = useArticles();
|
|
23
|
+
const { notify } = useSnackbar();
|
|
24
|
+
const [items, setItems] = useState([]);
|
|
25
|
+
useGamePageShell(useMemo(() => ({ title: "Aktualności (__SKIN_NAME__)" }), []));
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
articles.list({
|
|
29
|
+
page: 1,
|
|
30
|
+
pageSize: 10,
|
|
31
|
+
callback: (ret) => {
|
|
32
|
+
if (ret?.error) {
|
|
33
|
+
notify([{ severity: "error", message: ret.error }]);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
setItems(ret.results || []);
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}, [articles, notify]);
|
|
40
|
+
|
|
41
|
+
const rows = useMemo(
|
|
42
|
+
() =>
|
|
43
|
+
items.map((article) => ({
|
|
44
|
+
id: article.id,
|
|
45
|
+
title: article.title,
|
|
46
|
+
icon: article.cover?.url,
|
|
47
|
+
chips: article.pinned ? (
|
|
48
|
+
<HexahChip label="Przypięte" size="small" />
|
|
49
|
+
) : null,
|
|
50
|
+
// Nawigacja do szczegółu to zwykły link do publicznej trasy gry.
|
|
51
|
+
rowClick: () => {
|
|
52
|
+
window.location.href = `/game/article/${article.slug}`;
|
|
53
|
+
},
|
|
54
|
+
})),
|
|
55
|
+
[items],
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<PageBox>
|
|
60
|
+
<Box sx={{ px: { xs: 2, sm: 3 }, pt: 2 }}>
|
|
61
|
+
<Typography variant="body2" sx={{ mb: 2, opacity: 0.8 }}>
|
|
62
|
+
Aktualności na realnych danych hosta (read-model `useArticles`).
|
|
63
|
+
</Typography>
|
|
64
|
+
{rows.length === 0 ? (
|
|
65
|
+
<Typography color="primary" sx={{ py: 2 }}>
|
|
66
|
+
Brak artykułów do wyświetlenia.
|
|
67
|
+
</Typography>
|
|
68
|
+
) : (
|
|
69
|
+
<DataList itemsList={rows} />
|
|
70
|
+
)}
|
|
71
|
+
</Box>
|
|
72
|
+
</PageBox>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRZYKŁAD ekranu wątków Harolda (GPT) — rejestrowany pod `SCREEN_KEYS.GPT_LIST`
|
|
3
|
+
* (config: `screens: { gptList: './GptListScreen' }`).
|
|
4
|
+
*
|
|
5
|
+
* Pokazuje read-model `useGptThreads` (lista + tworzenie wątku), usługę `useDialog`
|
|
6
|
+
* (modal hosta) oraz prymityw `StandardButton` (tekst ≤ 16 znaków).
|
|
7
|
+
*/
|
|
8
|
+
import { useEffect, useMemo, useState } from "react";
|
|
9
|
+
import Box from "@mui/material/Box";
|
|
10
|
+
import Typography from "@mui/material/Typography";
|
|
11
|
+
import {
|
|
12
|
+
PageBox,
|
|
13
|
+
DataList,
|
|
14
|
+
StandardButton,
|
|
15
|
+
useGptThreads,
|
|
16
|
+
useSnackbar,
|
|
17
|
+
useGamePageShell,
|
|
18
|
+
} from "@hexah/skin-sdk";
|
|
19
|
+
|
|
20
|
+
export default function GptListScreen() {
|
|
21
|
+
const gpt = useGptThreads();
|
|
22
|
+
const { notify } = useSnackbar();
|
|
23
|
+
const [threads, setThreads] = useState([]);
|
|
24
|
+
useGamePageShell(useMemo(() => ({ title: "Harold (__SKIN_NAME__)" }), []));
|
|
25
|
+
|
|
26
|
+
const reload = useMemo(
|
|
27
|
+
() => () =>
|
|
28
|
+
gpt.list({
|
|
29
|
+
page: 1,
|
|
30
|
+
pageSize: 20,
|
|
31
|
+
callback: (ret) => {
|
|
32
|
+
if (ret?.error) {
|
|
33
|
+
notify([{ severity: "error", message: ret.error }]);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
setThreads(ret.results || ret.threads || []);
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
[gpt, notify],
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
reload();
|
|
44
|
+
}, [reload]);
|
|
45
|
+
|
|
46
|
+
const startThread = () => {
|
|
47
|
+
gpt.create({
|
|
48
|
+
message: "Witaj, Haroldzie!",
|
|
49
|
+
callback: (ret) => {
|
|
50
|
+
if (ret?.error) {
|
|
51
|
+
notify([{ severity: "error", message: ret.error }]);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
reload();
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const rows = useMemo(
|
|
60
|
+
() =>
|
|
61
|
+
threads.map((thread) => ({
|
|
62
|
+
id: thread.id,
|
|
63
|
+
title: thread.title || `Wątek #${thread.id}`,
|
|
64
|
+
rowClick: () => {
|
|
65
|
+
window.location.href = `/game/harold/${thread.id}`;
|
|
66
|
+
},
|
|
67
|
+
})),
|
|
68
|
+
[threads],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<PageBox>
|
|
73
|
+
<Box sx={{ px: { xs: 2, sm: 3 }, pt: 2 }}>
|
|
74
|
+
<Box sx={{ display: "flex", justifyContent: "flex-end", mb: 2 }}>
|
|
75
|
+
<StandardButton onClick={startThread}>Nowy wątek</StandardButton>
|
|
76
|
+
</Box>
|
|
77
|
+
{rows.length === 0 ? (
|
|
78
|
+
<Typography color="primary" sx={{ py: 2 }}>
|
|
79
|
+
Brak wątków — załóż pierwszy.
|
|
80
|
+
</Typography>
|
|
81
|
+
) : (
|
|
82
|
+
<DataList itemsList={rows} />
|
|
83
|
+
)}
|
|
84
|
+
</Box>
|
|
85
|
+
</PageBox>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRZYKŁAD: skórka jako RAMA CAŁEJ STRONY (chrome). Host renderuje aktywny ekran i podaje go
|
|
3
|
+
* jako `children` — skórka owija go własnym górnym paskiem, nawigacją i stopką. To podmienia
|
|
4
|
+
* cały layout gry pod Twoim szablonem (rejestrowane przez config z polem `layout: './SkinFrame'`).
|
|
5
|
+
*
|
|
6
|
+
* Pokazuje read-modele STANU: `useCharacter`, `useShard`, `useShardUser`, `useOnlineCharacters`.
|
|
7
|
+
* Importuj WYŁĄCZNIE z `@hexah/skin-sdk` (+ React/MUI). Nawigacja to zwykłe linki do tras
|
|
8
|
+
* `/game/*` (publiczne adresy) — nie masz dostępu do wewnętrznego routingu gry.
|
|
9
|
+
*/
|
|
10
|
+
import Box from "@mui/material/Box";
|
|
11
|
+
import Typography from "@mui/material/Typography";
|
|
12
|
+
import {
|
|
13
|
+
useCharacter,
|
|
14
|
+
useShard,
|
|
15
|
+
useShardUser,
|
|
16
|
+
useOnlineCharacters,
|
|
17
|
+
} from "@hexah/skin-sdk";
|
|
18
|
+
|
|
19
|
+
const NAV = [
|
|
20
|
+
["/game/article", "Aktualności"],
|
|
21
|
+
["/game/tale?filter=MojeAktywne", "Operacje"],
|
|
22
|
+
["/game/town", "Metropolia"],
|
|
23
|
+
["/game/guild", "Frakcje"],
|
|
24
|
+
["/game/map", "Mapa"],
|
|
25
|
+
["/game/rank", "Notowania"],
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export default function SkinFrame({ children }) {
|
|
29
|
+
const character = useCharacter();
|
|
30
|
+
const shard = useShard();
|
|
31
|
+
const shardUser = useShardUser();
|
|
32
|
+
const online = useOnlineCharacters();
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Box sx={{ display: "flex", flexDirection: "column", minHeight: "100vh" }}>
|
|
36
|
+
<Box
|
|
37
|
+
component="header"
|
|
38
|
+
sx={{
|
|
39
|
+
px: 3,
|
|
40
|
+
py: 1.5,
|
|
41
|
+
display: "flex",
|
|
42
|
+
alignItems: "center",
|
|
43
|
+
gap: 2,
|
|
44
|
+
flexWrap: "wrap",
|
|
45
|
+
bgcolor: "background.paper",
|
|
46
|
+
borderBottom: "2px solid",
|
|
47
|
+
borderColor: "primary.main",
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<Typography variant="h6" sx={{ fontWeight: 700 }}>
|
|
51
|
+
__SKIN_NAME__
|
|
52
|
+
</Typography>
|
|
53
|
+
<Box component="nav" sx={{ display: "flex", gap: 2, flex: 1, flexWrap: "wrap" }}>
|
|
54
|
+
{NAV.map(([href, label]) => (
|
|
55
|
+
<Typography
|
|
56
|
+
key={href}
|
|
57
|
+
component="a"
|
|
58
|
+
href={href}
|
|
59
|
+
variant="body2"
|
|
60
|
+
sx={{ textDecoration: "none", color: "text.primary" }}
|
|
61
|
+
>
|
|
62
|
+
{label}
|
|
63
|
+
</Typography>
|
|
64
|
+
))}
|
|
65
|
+
</Box>
|
|
66
|
+
<Typography variant="caption" color="text.secondary">
|
|
67
|
+
{character?.name ?? "—"}
|
|
68
|
+
{shardUser?.user?.username ? ` (${shardUser.user.username})` : ""} ·{" "}
|
|
69
|
+
{shard?.name ?? ""} · online: {Array.isArray(online) ? online.length : 0}
|
|
70
|
+
</Typography>
|
|
71
|
+
</Box>
|
|
72
|
+
|
|
73
|
+
<Box component="main" sx={{ flex: 1 }}>
|
|
74
|
+
{children}
|
|
75
|
+
</Box>
|
|
76
|
+
|
|
77
|
+
<Box component="footer" sx={{ px: 3, py: 1, textAlign: "center", opacity: 0.6 }}>
|
|
78
|
+
<Typography variant="caption">
|
|
79
|
+
„__SKIN_NAME__" — rama całej strony (Skin SDK)
|
|
80
|
+
</Typography>
|
|
81
|
+
</Box>
|
|
82
|
+
</Box>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KSIĄŻKA KUCHARSKA read-modeli danych (REFERENCJA — nieeksportowana w `rspack.config.js`).
|
|
3
|
+
*
|
|
4
|
+
* Te komponenty nie trafiają do builda, dopóki nie dodasz ich do `exposes` albo nie zaimportujesz
|
|
5
|
+
* z ekranu/ramy. Pokazują wywołanie KAŻDEGO pozostałego read-modelu danych z `@hexah/skin-sdk`.
|
|
6
|
+
* Wzorzec stały: hook zwraca obiekt metod; metoda dostaje obiekt opcji z `callback`, a pozostałe
|
|
7
|
+
* pola lecą do hosta jako `data`. Kształt zwrotki (`ret`) zależy od metody — najczęściej
|
|
8
|
+
* `{ results, pagination }` lub `{ error }`.
|
|
9
|
+
*
|
|
10
|
+
* Pełna lista metod każdego hooka: README scaffolderu `@hexah/create-skin`
|
|
11
|
+
* (sekcja „Budowanie motywu — co masz do dyspozycji").
|
|
12
|
+
*/
|
|
13
|
+
import { useEffect, useState } from "react";
|
|
14
|
+
import Typography from "@mui/material/Typography";
|
|
15
|
+
import {
|
|
16
|
+
useTales,
|
|
17
|
+
useInn,
|
|
18
|
+
useTown,
|
|
19
|
+
useGuild,
|
|
20
|
+
useFamily,
|
|
21
|
+
useVillages,
|
|
22
|
+
useShop,
|
|
23
|
+
useRanking,
|
|
24
|
+
useAccountProfile,
|
|
25
|
+
useSettings,
|
|
26
|
+
} from "@hexah/skin-sdk";
|
|
27
|
+
|
|
28
|
+
/** Operacje (opowieści): lista moich aktywnych. */
|
|
29
|
+
export function TalesExample() {
|
|
30
|
+
const tales = useTales();
|
|
31
|
+
const [list, setList] = useState([]);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
tales.list({
|
|
34
|
+
filter: "MojeAktywne",
|
|
35
|
+
page: 1,
|
|
36
|
+
callback: (ret) => setList(ret?.results || []),
|
|
37
|
+
});
|
|
38
|
+
}, [tales]);
|
|
39
|
+
return <Typography>Opowieści: {list.length}</Typography>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Mesa (karczma): opowieści karczmiane + licznik bywalców. */
|
|
43
|
+
export function InnExample() {
|
|
44
|
+
const inn = useInn();
|
|
45
|
+
const [counts, setCounts] = useState({});
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
inn.listTales({ page: 1, callback: () => {} });
|
|
48
|
+
inn.getMembersCount({ callback: (ret) => setCounts(ret || {}) });
|
|
49
|
+
}, [inn]);
|
|
50
|
+
return <Typography>Bywalców łącznie: {counts.countAll ?? 0}</Typography>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Metropolia: lista planów warsztatu (przykład jednej z 17 akcji `useTown`). */
|
|
54
|
+
export function TownExample() {
|
|
55
|
+
const town = useTown();
|
|
56
|
+
const [plans, setPlans] = useState([]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
town.workshopPlansList({ callback: (ret) => setPlans(ret?.results || ret || []) });
|
|
59
|
+
}, [town]);
|
|
60
|
+
return <Typography>Plany warsztatu: {plans.length}</Typography>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Frakcje (gildie): publiczna lista. */
|
|
64
|
+
export function GuildExample() {
|
|
65
|
+
const guild = useGuild();
|
|
66
|
+
const [guilds, setGuilds] = useState([]);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
guild.list({ page: 1, callback: (ret) => setGuilds(ret?.results || ret || []) });
|
|
69
|
+
}, [guild]);
|
|
70
|
+
return <Typography>Frakcje: {guilds.length}</Typography>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Dynastie (rody): lista rodów. */
|
|
74
|
+
export function FamilyExample() {
|
|
75
|
+
const family = useFamily();
|
|
76
|
+
const [families, setFamilies] = useState([]);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
family.list({ callback: (ret) => setFamilies(ret?.results || ret || []) });
|
|
79
|
+
}, [family]);
|
|
80
|
+
return <Typography>Rody: {families.length}</Typography>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Kolonie (osady): moje osady. */
|
|
84
|
+
export function VillagesExample() {
|
|
85
|
+
const villages = useVillages();
|
|
86
|
+
const [list, setList] = useState([]);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
villages.list({ callback: (ret) => setList(ret?.results || ret || []) });
|
|
89
|
+
}, [villages]);
|
|
90
|
+
return <Typography>Kolonie: {list.length}</Typography>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Zaopatrzenie (sklep): dostępne oferty premium. */
|
|
94
|
+
export function ShopExample() {
|
|
95
|
+
const shop = useShop();
|
|
96
|
+
const [offers, setOffers] = useState([]);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
shop.getOffers({ callback: (ret) => setOffers(ret?.results || ret || []) });
|
|
99
|
+
}, [shop]);
|
|
100
|
+
return <Typography>Oferty: {offers.length}</Typography>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Notowania: ranking główny z filtrami. */
|
|
104
|
+
export function RankingExample() {
|
|
105
|
+
const ranking = useRanking();
|
|
106
|
+
const [rows, setRows] = useState([]);
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
ranking.ranking({ page: 1, callback: (ret) => setRows(ret?.results || []) });
|
|
109
|
+
}, [ranking]);
|
|
110
|
+
return <Typography>Ranking — wpisów: {rows.length}</Typography>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Profil (konto): informacje o koncie. */
|
|
114
|
+
export function AccountProfileExample() {
|
|
115
|
+
const profile = useAccountProfile();
|
|
116
|
+
const [info, setInfo] = useState(null);
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
profile.getAccountInfo({ callback: (ret) => setInfo(ret || null) });
|
|
119
|
+
}, [profile]);
|
|
120
|
+
return <Typography>Konto: {info ? "wczytane" : "—"}</Typography>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Konfiguracja: zmiana ustawienia (np. niewidoczność online). */
|
|
124
|
+
export function SettingsExample() {
|
|
125
|
+
const settings = useSettings();
|
|
126
|
+
const toggleInvisible = () =>
|
|
127
|
+
settings.setInvisibleOnline({ enabled: true, callback: () => {} });
|
|
128
|
+
return (
|
|
129
|
+
<Typography component="button" onClick={toggleInvisible}>
|
|
130
|
+
Ukryj online
|
|
131
|
+
</Typography>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRZYKŁAD slotów (REFERENCJA — nieeksportowana). Sloty to ciężkie fragmenty z logiką/danymi,
|
|
3
|
+
* które renderuje HOST (mapa świata, statystyki zgłoszeń itp.). Skórka pobiera je gotowe przez
|
|
4
|
+
* `useSlots()` i tylko ROZMIESZCZA — nie ma dostępu do ich wnętrza ani do kodu gry.
|
|
5
|
+
*
|
|
6
|
+
* Zawsze degraduj łagodnie: gdy host nie dostarcza danego slotu, `slots[KLUCZ]` jest `undefined`.
|
|
7
|
+
*/
|
|
8
|
+
import Box from "@mui/material/Box";
|
|
9
|
+
import Typography from "@mui/material/Typography";
|
|
10
|
+
import { useSlots, SLOT_KEYS } from "@hexah/skin-sdk";
|
|
11
|
+
|
|
12
|
+
/** Mapa Sektora (mapa świata + ruiny) wstawiona we własny kontener skórki. */
|
|
13
|
+
export function WorldMapExample() {
|
|
14
|
+
const slots = useSlots();
|
|
15
|
+
const WorldMap = slots[SLOT_KEYS.WORLD_MAP];
|
|
16
|
+
if (!WorldMap) {
|
|
17
|
+
return <Typography>Slot mapy niedostępny.</Typography>;
|
|
18
|
+
}
|
|
19
|
+
return (
|
|
20
|
+
<Box sx={{ position: "relative", height: "70vh" }}>
|
|
21
|
+
<WorldMap />
|
|
22
|
+
</Box>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Statystyki zgłoszeń (wykresy) — np. obok listy na ekranie zgłoszeń. */
|
|
27
|
+
export function IssueStatsExample() {
|
|
28
|
+
const slots = useSlots();
|
|
29
|
+
const IssueStats = slots[SLOT_KEYS.ISSUE_STATS];
|
|
30
|
+
return IssueStats ? <IssueStats /> : null;
|
|
31
|
+
}
|