152 lines
3.7 KiB
Markdown
152 lines
3.7 KiB
Markdown
# Adding a feature
|
|
|
|
Walk-through for building a new side-panel feature. We'll use a hypothetical
|
|
"NOTAMs" panel (no-fly-zone overlays) as the example.
|
|
|
|
## 1. Pick a feature folder
|
|
|
|
```
|
|
src/lib/features/notams/
|
|
├── index.ts
|
|
├── store.ts
|
|
├── types.ts
|
|
├── NotamsPanel.svelte
|
|
└── NotamRenderer.svelte # if you draw on the map
|
|
```
|
|
|
|
Re-export the public surface from `index.ts`.
|
|
|
|
## 2. Define the domain
|
|
|
|
If the feature introduces a durable type (something saved to a server or
|
|
localStorage), put the type in `src/lib/domain/notam.ts` and re-export from
|
|
`src/lib/domain/index.ts`. Ephemeral state types can live in the feature's
|
|
`types.ts`.
|
|
|
|
```ts
|
|
// src/lib/domain/notam.ts
|
|
export interface Notam {
|
|
id: string;
|
|
code: string;
|
|
polygon: LatLngTuple[];
|
|
validFrom: string;
|
|
validTo: string;
|
|
}
|
|
```
|
|
|
|
## 3. Add state
|
|
|
|
Feature-scoped stores go in the feature folder. Use `persisted()` if the user
|
|
should not lose the state on reload, plain `writable()` otherwise.
|
|
|
|
```ts
|
|
// src/lib/features/notams/store.ts
|
|
import { persisted } from '$state';
|
|
import type { Notam } from '$domain';
|
|
|
|
export const notamsStore = persisted<Notam[]>('notams', []);
|
|
```
|
|
|
|
## 4. Add API (if needed)
|
|
|
|
```ts
|
|
// src/lib/api/notams.ts
|
|
import { api } from './client';
|
|
import type { Notam } from '$domain';
|
|
export const notamsApi = { list: () => api.get<Notam[]>('/notams/') };
|
|
```
|
|
|
|
And export from `src/lib/api/index.ts`.
|
|
|
|
## 5. Build the panel
|
|
|
|
Re-use UI primitives. Wrap everything in `CollapsibleCard` so it lands in the
|
|
side panel.
|
|
|
|
```svelte
|
|
<!-- src/lib/features/notams/NotamsPanel.svelte -->
|
|
<script lang="ts">
|
|
import { CollapsibleCard } from '$ui';
|
|
import { t } from '$i18n';
|
|
import { notamsStore } from './store';
|
|
</script>
|
|
|
|
<CollapsibleCard title={$t('notams.title')}>
|
|
{#each $notamsStore as notam (notam.id)}
|
|
<div>{notam.code}</div>
|
|
{/each}
|
|
</CollapsibleCard>
|
|
```
|
|
|
|
## 6. Add i18n keys
|
|
|
|
Append to `src/lib/i18n/locales/ru.json` and `en.json`. The lookup is
|
|
dot-separated:
|
|
|
|
```json
|
|
{
|
|
"notams": {
|
|
"title": "NOTAMs",
|
|
"empty": "Нет активных NOTAMs"
|
|
}
|
|
}
|
|
```
|
|
|
|
## 7. Draw on the map (optional)
|
|
|
|
Every on-map drawing goes through a Scene. One scene per feature, or per sub-
|
|
entity if the feature manages many independent overlays (like workspaces).
|
|
|
|
```svelte
|
|
<!-- src/lib/features/notams/NotamRenderer.svelte -->
|
|
<script lang="ts">
|
|
import { getMap } from '$map';
|
|
import { notamsStore } from './store';
|
|
|
|
const map = getMap();
|
|
if (!map) throw new Error('NotamRenderer requires a <Map /> ancestor');
|
|
|
|
$effect(() => {
|
|
const scene = map.scene('notams');
|
|
scene.clear();
|
|
for (const n of $notamsStore) {
|
|
// scene.addLine/addCircle/etc.
|
|
}
|
|
});
|
|
</script>
|
|
```
|
|
|
|
Add `<NotamRenderer />` as a child of `<MapView />` in the predict route.
|
|
|
|
## 8. Wire into the route
|
|
|
|
```svelte
|
|
<!-- src/routes/predict/+page.svelte -->
|
|
<MapView>
|
|
<WorkspaceRenderer />
|
|
<NotamRenderer />
|
|
<PanelContainer position="right">
|
|
<TabBar tabs={[...] } bind:active={rightTab} />
|
|
{#if rightTab === 'notams'}
|
|
<NotamsPanel />
|
|
{/if}
|
|
</PanelContainer>
|
|
</MapView>
|
|
```
|
|
|
|
## 9. Add a settings entry (optional)
|
|
|
|
If the feature should be toggleable, extend `src/lib/features/settings/store.ts`
|
|
and `schema.ts`. Keep the field declarative (kind + labelKey + path) — the
|
|
SettingsPanel renders it automatically.
|
|
|
|
## 10. Checklist before you open a PR
|
|
|
|
- [ ] `npm run check` passes
|
|
- [ ] `npm run build` passes
|
|
- [ ] All user-visible strings routed through `$t`
|
|
- [ ] Feature only depends on shared modules (`$domain`, `$state`, `$api`,
|
|
`$ui`, `$map`) — not on other features' internals
|
|
- [ ] `index.ts` exports only what the outside world needs
|
|
- [ ] Panel matches existing visual language (CollapsibleCard, Bootstrap sm
|
|
form controls, same spacing)
|