een-api-toolkit 0.3.91 → 0.3.101

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.
@@ -205,40 +205,82 @@ Implemented: [brief list of what IS implemented]
205
205
 
206
206
  #### Document 4: `docs/een-api-coverage.html`
207
207
 
208
- Generate a self-contained HTML file with:
209
-
210
- **Header section**:
211
- - Title: "Eagle Eye Networks API v3.0 - Toolkit Coverage"
212
- - Generation date
213
- - Summary stat cards: Total endpoints, Implemented, Missing, Coverage percentage with progress bar
214
-
215
- **Filter bar**:
216
- - Status filter dropdown (All / Implemented / Missing)
217
- - Category filter dropdown (All + each category)
218
- - Method filter dropdown (All / GET / POST / PATCH / DELETE / PUT)
219
- - Text search input for path/function/description
220
-
221
- **Data table**:
222
- - Columns: #, Category, Subcategory, Method, Path, Description, Status, Toolkit Function
223
- - Sortable by clicking column headers
224
- - Color-coded method badges (GET=blue, POST=green, PATCH=yellow, DELETE=red, PUT=indigo)
225
- - Status badges (Implemented=green, Missing=red)
226
- - Monospace font for function names
227
-
228
- **Styling**:
229
- - Clean, modern CSS with system font stack
230
- - Light background (#f5f7fa)
231
- - White card-style table with subtle shadows
232
- - Responsive layout
233
- - All styles inline (self-contained, no external dependencies)
208
+ Generate a self-contained HTML file matching this exact visual style:
209
+
210
+ **Global reset and body**:
211
+ - `*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }`
212
+ - Font: `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, sans-serif`
213
+ - Background: `#f5f7fa`, color: `#1a202c`
214
+
215
+ **Header** (`<header>`):
216
+ - Dark navy background: `#1a365d`, white text
217
+ - Padding: `24px 32px`
218
+ - `<h1>` at `1.5rem` bold: "Eagle Eye Networks API v3.0 Toolkit Coverage"
219
+ - `<p>` at `0.875rem`, color `#bee3f8`: generation date and source info
220
+
221
+ **Container**: `max-width: 100%` (use full page width), centered, padding `24px 32px`
222
+
223
+ **Stat cards** (`.stats` grid):
224
+ - `grid-template-columns: repeat(auto-fit, minmax(180px, 1fr))`, gap `16px`
225
+ - Each card: white background, `border-radius: 8px`, padding `20px`, `box-shadow: 0 1px 3px rgba(0,0,0,.08)`
226
+ - Colored `border-top: 4px solid` — blue (`#4299e1`) for Total, green (`#48bb78`) for Implemented, red (`#fc8181`) for Missing, indigo (`#667eea`) for Coverage
227
+ - Label: `0.75rem` uppercase, `letter-spacing: .05em`, color `#718096`
228
+ - Value: `2rem` bold, color `#2d3748`
229
+ - Sub text: `0.8rem`, color `#718096`
230
+
231
+ **Progress bar** (`.progress-wrap`):
232
+ - White card with same shadow/radius as stat cards
233
+ - Label row: flex with `justify-content: space-between`, `0.875rem`, color `#4a5568`
234
+ - Bar background: `#e2e8f0`, `border-radius: 9999px`, height `12px`
235
+ - Fill: `linear-gradient(90deg, #48bb78, #38a169)`, same radius, `transition: width .4s ease`
236
+
237
+ **Filter bar** (`.filter-bar`):
238
+ - White card, flex with `flex-wrap: wrap`, gap `12px`
239
+ - Labels: `0.8rem`, color `#718096`
240
+ - Inputs/selects: border `1px solid #e2e8f0`, `border-radius: 6px`, padding `6px 10px`, `0.875rem`
241
+ - Focus: `border-color: #4299e1`, `box-shadow: 0 0 0 3px rgba(66,153,225,.15)`
242
+ - Text input width: `220px`
243
+ - Filter count: `margin-left: auto`, `0.8rem`, color `#718096`
244
+ - Dropdowns: Status (All/Implemented/Missing), Category (All + each category), Method (All/GET/POST/PATCH/DELETE/PUT)
245
+ - Text search placeholder: "path, function, description..."
246
+
247
+ **Table** (`.table-wrap`):
248
+ - White card with `overflow-x: auto` for horizontal scrollbar on narrow viewports
249
+ - The `<table>` element must have `min-width: 1200px` so content is never clipped — the scrollbar activates instead of truncating columns
250
+ - `<thead>`: dark background `#2d3748`, white text, uppercase `0.75rem`, `letter-spacing: .05em`
251
+ - Headers clickable (cursor pointer) with sort icon `⇅`, changing to `▲`/`▼` when sorted
252
+ - `<tbody>` rows: `border-bottom: 1px solid #edf2f7`, hover `#f7fafc`
253
+ - Cell padding: `10px 14px`
254
+ - Column classes:
255
+ - `.td-num`: color `#a0aec0`, `0.75rem`, width `44px`
256
+ - `.td-cat`: color `#4a5568`, `font-weight: 500`
257
+ - `.td-sub`: color `#718096`
258
+ - `.td-path`: monospace (`'SFMono-Regular', Consolas, monospace`), `0.8rem`, color `#2d3748`
259
+ - `.td-desc`: color `#4a5568`, `max-width: 280px`
260
+ - `.td-func`: monospace, `0.78rem`, color `#553c9a`
261
+
262
+ **Badges** (`.badge`):
263
+ - `display: inline-flex`, padding `2px 8px`, `border-radius: 4px`, `0.72rem` bold uppercase
264
+ - Method colors: GET (`#ebf8ff`/`#2b6cb0`), POST (`#f0fff4`/`#276749`), PATCH (`#fffff0`/`#975a16`), DELETE (`#fff5f5`/`#c53030`), PUT (`#f0e6ff`/`#553c9a`)
265
+ - Status colors: Implemented (`#f0fff4`/`#276749`), Missing (`#fff5f5`/`#c53030`)
266
+
267
+ **Empty state**: centered, padding `48px`, color `#a0aec0`, with a search SVG icon
268
+
269
+ **Footer**: centered, padding `24px`, color `#a0aec0`, `0.8rem`
270
+
271
+ **Columns**: #, Category, Subcategory, Method, Path, Description, Status, Toolkit Function (8 columns)
234
272
 
235
273
  **JavaScript**:
236
274
  - All endpoint data in a `const endpoints = [...]` array with objects: `{cat, sub, method, path, desc, status, func}`
275
+ - Endpoints grouped by category/subcategory with `// ─── Category - Subcategory ───` comment dividers
237
276
  - `status` values: `"impl"` for implemented, `"miss"` for missing
238
277
  - `func` is empty string for missing endpoints
239
- - `renderTable(data)` function to populate tbody
240
- - `filterTable()` function combining all filter inputs
241
- - `sortTable(colIndex)` function with toggle direction
278
+ - `methodBadge(m)` and `statusBadge(s)` helper functions
279
+ - `renderTable(data)` function to populate tbody and update filter count
280
+ - `filterTable()` function combining all four filter inputs (status, category, method, text search across path+func+desc+sub)
281
+ - `sortTable(colIndex)` function with toggle direction, updating header sort icons
282
+ - `sortCol`/`sortAsc` state variables
283
+ - Initial call to `renderTable(endpoints)` at the end
242
284
  - All code inline (no external dependencies)
243
285
 
244
286
  ## Important Guidelines
@@ -221,14 +221,24 @@ nested `capabilities.ptz.capable` field. The structure is:
221
221
  ```
222
222
 
223
223
  **IMPORTANT:** The PTZ capability is at `capabilities.ptz.capable` (nested under a `ptz` object),
224
- NOT at `capabilities.ptzCapable` (flat). Always use `capabilities?.ptz?.capable` to check.
224
+ NOT at `capabilities.ptzCapable` (flat). Fisheye cameras report `capabilities.ptz.capable: true`
225
+ but are NOT true PTZ cameras — always exclude them. Use this pattern:
226
+
227
+ ```typescript
228
+ import { computed } from 'vue'
229
+
230
+ const isPtzCapable = computed(() => {
231
+ const ptz = camera.value?.capabilities?.ptz
232
+ return ptz?.capable === true && ptz?.fisheye !== true
233
+ })
234
+ ```
225
235
 
226
236
  Also check `effectivePermissions.controlPTZ` to verify the user has permission to move the camera,
227
237
  and `effectivePermissions.editPTZStations` for managing presets.
228
238
 
229
239
  ## Constraints
230
240
  - Always check authentication before API calls
231
- - Verify camera has PTZ capability (`capabilities.ptz.capable`) before showing controls
241
+ - Verify camera has PTZ capability (`capabilities.ptz.capable`) and is not fisheye (`capabilities.ptz.fisheye !== true`) before showing controls
232
242
  - Check user permissions (`effectivePermissions.controlPTZ`) before enabling movement
233
243
  - Poll position periodically (every 5s) for position display
234
244
  - Handle 204 responses for PUT/PATCH (no response body)
package/CHANGELOG.md CHANGED
@@ -2,78 +2,66 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [0.3.91] - 2026-02-21
5
+ ## [0.3.101] - 2026-02-23
6
6
 
7
7
  ### Release Summary
8
8
 
9
- #### PR #118: Release v0.3.85 - API coverage docs and workflow security fix
9
+ #### PR #130: Release v0.3.96: PTZ fisheye exclusion and API coverage docs
10
10
  ## Summary
11
+ - Add fisheye camera exclusion to PTZ type definitions, agent docs, AI reference docs, and vue-ptz example
12
+ - Add PTZ sub-capability fields (`fisheye`, `panTilt`, `zoom`, `positionMove`, `directionMove`, `centerOnMove`) to `Camera.capabilities.ptz` type
13
+ - Regenerate API coverage docs with full-width layout and scrollbar fix
11
14
 
12
- - Add EEN API v3.0 coverage documentation (4 documents comparing 211 API endpoints against 51 implemented, 24.2% coverage)
13
- - Add `api-coverage-agent` for on-demand regeneration of coverage docs
14
- - Update README with agent list and coverage section
15
- - Fix workflow_dispatch restriction to production branch (security)
15
+ ## Version
16
+ v0.3.96
16
17
 
17
18
  ## Commits
19
+ - `1d9da62` docs: regenerate API coverage docs with full-width layout and scrollbar fix
20
+ - `044c9ae` docs: add fisheye camera exclusion guidance to PTZ agent
21
+ - `f65ff2e` fix: add PTZ sub-capability fields to Camera type and update docs
22
+ - `6ecf253` fix: address review findings for PTZ fisheye guidance
23
+ - `97cf632` fix: exclude fisheye cameras from PTZ selector in vue-ptz example
18
24
 
19
- - `baa7e1b` feat: add EEN API coverage documentation and agent
20
- - `eea17e8` docs: add missing agents to README agent list
21
- - `bb47cc2` docs: rename een-api-coverage-agent to api-coverage-agent
22
- - `c794fba` fix: restrict workflow_dispatch to production branch to prevent supply chain attack
23
- - `6e782e2` fix: restrict npm-publish workflow_dispatch to production branch
24
-
25
- ## Test Results (from PR #117)
26
-
27
- - **Lint**: Passed
28
- - **Unit tests**: 644 passed
29
- - **Build**: Succeeded
30
- - **E2E tests**: 236 passed across all 11 example apps
31
- - **Security review**: No vulnerabilities
32
- - **Code review**: Approved by Claude Sonnet 4.5
33
-
34
- ## Version
35
-
36
- v0.3.85
25
+ ## Test Results
26
+ - Lint: passed (1 pre-existing warning)
27
+ - Unit tests: 683/683 passed
28
+ - Build: passed
29
+ - E2E: 12/12 example apps passed
30
+ - Security: no vulnerabilities (type/docs changes only)
31
+ - Confidential data scan: clean across 280 changed .md files
37
32
 
38
33
  🤖 Generated with [Claude Code](https://claude.com/claude-code)
39
34
 
40
- #### PR #128: Release v0.3.89: PTZ camera control API and vue-ptz example
35
+ #### PR #134: release: v0.3.101 - multi-move-type PTZ support
41
36
  ## Summary
37
+ - Add move type dropdown (Position / Direction / Center On) to the PositionInput panel in `examples/vue-ptz`
38
+ - Dynamic input fields per move type with proper validation
39
+ - Replace auto-updating x/y/z with explicit "Import Current Position" button
40
+ - Refactored `apply()` with `buildMove()` extraction for cleaner code
41
+ - `try/finally` for defensive state reset, null-safe optional chaining
42
42
 
43
- This release adds PTZ (Pan/Tilt/Zoom) camera control support to the toolkit:
44
-
45
- - **PTZ API functions**: `getPtzPosition()`, `movePtz()`, `getPtzSettings()`, `updatePtzSettings()` with full TypeScript types
46
- - **PTZ types**: `PtzPosition`, `PtzMove` (discriminated union: position/direction/centerOn), `PtzSettings`, `PtzPreset`, `PtzSettingsUpdate`
47
- - **vue-ptz example app**: Complete PTZ control application with live video, direction pad, click-to-center, position display, preset management, and API call logging
48
- - **Camera capabilities**: Added `capabilities` field to `Camera` type for PTZ detection via `include: ['capabilities']`
49
- - **Documentation**: AI-PTZ.md reference doc, een-ptz-agent, updated AI-CONTEXT.md and CLAUDE.md
50
- - **E2E tests**: Conditional PTZ API tests that exercise position read, direction move, and preset loading when a PTZ camera is available
51
-
52
- ### Additional changes
53
- - Dependabot: CodeQL action bump to 4.32.3
54
- - CI: Use floating v1 tag for claude-code-action instead of SHA pin
43
+ ## Version
44
+ `0.3.101`
55
45
 
56
46
  ## Commits
57
-
58
- - `c552fd0` feat: add PTZ camera control API and vue-ptz example app
59
- - `6a12649` fix: use single getCameras call with include and pagination for PTZ discovery
60
- - `2b04b26` fix: address review findings in vue-ptz example app
61
- - `92a18e4` fix: address remaining review findings
62
- - `4d01c2c` fix: use floating v1 tag for claude-code-action instead of SHA pin
63
- - `3f197ba` chore(deps): bump github/codeql-action from 4.32.2 to 4.32.3
64
-
65
- ## Test Results
66
-
67
- | Check | Result |
68
- |-------|--------|
69
- | Lint | Pass (0 errors) |
70
- | Unit tests | 681 passed (24 suites) |
71
- | Build | Pass |
72
- | E2E (12 apps) | All passed |
73
- | Security review | No vulnerabilities found |
74
- | Confidential data scan | Clean |
75
-
76
- **Version**: 0.3.89
47
+ - `2a4aac8f` feat: add multi-move-type support to vue-ptz PositionInput
48
+ - `f125139c` fix: address code review findings for PositionInput
49
+ - `addcc0ec` fix: wrap movePtz calls in try/finally to always reset applying state
50
+ - `a1365f24` fix: address remaining review findings for PositionInput
51
+
52
+ ## Local test results
53
+ - Lint: passed (0 errors, 1 pre-existing warning)
54
+ - Unit tests: 684/684 passed
55
+ - Build: success
56
+ - E2E tests: 12/12 example apps passed
57
+ - Security review: no issues found
58
+ - Docs confidential data scan: clean (274 .md files scanned)
59
+
60
+ ## Test plan
61
+ - [ ] Select "Position" move type — verify x/y/z inputs, Import Current Position button
62
+ - [ ] Select "Direction" move type — verify checkboxes + step size, Apply sends direction move
63
+ - [ ] Select "Center On" move type — verify rX/rY inputs, Apply sends centerOn move
64
+ - [ ] Verify x/y/z fields do NOT auto-update when position refreshes
77
65
 
78
66
  🤖 Generated with [Claude Code](https://claude.com/claude-code)
79
67
 
@@ -81,22 +69,16 @@ This release adds PTZ (Pan/Tilt/Zoom) camera control support to the toolkit:
81
69
  ### Detailed Changes
82
70
 
83
71
  #### Features
84
- - feat: add PTZ camera control API and vue-ptz example app
72
+ - feat: add multi-move-type support to vue-ptz PositionInput
85
73
 
86
74
  #### Bug Fixes
87
- - fix: address remaining PR #128 review findings
88
- - fix: address PR #128 review findings
89
- - fix: address remaining review findings
90
- - fix: address review findings in vue-ptz example app
91
- - fix: use single getCameras call with include and pagination for PTZ discovery
92
- - fix: use floating v1 tag for claude-code-action instead of SHA pin
93
-
94
- #### Other Changes
95
- - chore(deps): bump github/codeql-action from 4.32.2 to 4.32.3
75
+ - fix: address remaining review findings for PositionInput
76
+ - fix: wrap movePtz calls in try/finally to always reset applying state
77
+ - fix: address code review findings for PositionInput
96
78
 
97
79
  ### Links
98
80
  - [npm package](https://www.npmjs.com/package/een-api-toolkit)
99
- - [Full Changelog](https://github.com/klaushofrichter/een-api-toolkit/compare/v0.3.85...v0.3.91)
81
+ - [Full Changelog](https://github.com/klaushofrichter/een-api-toolkit/compare/v0.3.97...v0.3.101)
100
82
 
101
83
  ---
102
- *Released: 2026-02-21 17:41:30 CST*
84
+ *Released: 2026-02-23 18:17:46 CST*
package/dist/index.d.ts CHANGED
@@ -610,6 +610,18 @@ export declare interface Camera {
610
610
  ptz?: {
611
611
  /** Whether this camera supports PTZ controls */
612
612
  capable?: boolean;
613
+ /** Whether this is a fisheye camera (not a true PTZ camera) */
614
+ fisheye?: boolean;
615
+ /** Whether the camera supports pan/tilt movements */
616
+ panTilt?: boolean;
617
+ /** Whether the camera supports zoom */
618
+ zoom?: boolean;
619
+ /** Whether the camera supports absolute position moves */
620
+ positionMove?: boolean;
621
+ /** Whether the camera supports directional moves */
622
+ directionMove?: boolean;
623
+ /** Whether the camera supports center-on moves */
624
+ centerOnMove?: boolean;
613
625
  };
614
626
  };
615
627
  /** List of enabled analytics on this camera */
@@ -1,6 +1,6 @@
1
1
  # EEN API Toolkit - AI Reference
2
2
 
3
- > **Version:** 0.3.91
3
+ > **Version:** 0.3.101
4
4
  >
5
5
  > This documentation is optimized for AI assistants. It provides focused, domain-specific
6
6
  > references to help you understand and use the een-api-toolkit efficiently.
@@ -1,6 +1,6 @@
1
1
  # Authentication - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.91
3
+ > **Version:** 0.3.101
4
4
  >
5
5
  > OAuth flow implementation, token management, and session handling.
6
6
  > Load this document when implementing login, logout, or auth guards.
@@ -1,6 +1,6 @@
1
1
  # Automations API - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.91
3
+ > **Version:** 0.3.101
4
4
  >
5
5
  > Complete reference for automation rules and alert actions.
6
6
  > Load this document when working with automated alert workflows.
@@ -1,6 +1,6 @@
1
1
  # Cameras & Bridges API - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.91
3
+ > **Version:** 0.3.101
4
4
  >
5
5
  > Complete reference for camera and bridge management.
6
6
  > Load this document when working with devices.
@@ -33,6 +33,17 @@ interface Camera {
33
33
  deviceInfo?: CameraDeviceInfo
34
34
  shareDetails?: CameraShareDetails
35
35
  devicePosition?: CameraDevicePosition
36
+ capabilities?: {
37
+ ptz?: {
38
+ capable?: boolean
39
+ fisheye?: boolean
40
+ panTilt?: boolean
41
+ zoom?: boolean
42
+ positionMove?: boolean
43
+ directionMove?: boolean
44
+ centerOnMove?: boolean
45
+ }
46
+ }
36
47
  createdAt?: string
37
48
  updatedAt?: string
38
49
  }
@@ -1,6 +1,6 @@
1
1
  # Event Type to Data Schemas Mapping - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.91
3
+ > **Version:** 0.3.101
4
4
  >
5
5
  > Complete reference for event type to data schema mappings.
6
6
  > Load this document when building dynamic event queries with the `include` parameter.
@@ -1,6 +1,6 @@
1
1
  # Events, Alerts & Real-Time Streaming - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.91
3
+ > **Version:** 0.3.101
4
4
  >
5
5
  > Complete reference for events, alerts, metrics, and SSE subscriptions.
6
6
  > Load this document when implementing event-driven features.
@@ -1,6 +1,6 @@
1
1
  # Layouts API - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.91
3
+ > **Version:** 0.3.101
4
4
  >
5
5
  > Complete reference for layout management (camera grouping).
6
6
  > Load this document when working with layouts.
@@ -1,6 +1,6 @@
1
1
  # Jobs, Exports, Files & Downloads - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.91
3
+ > **Version:** 0.3.101
4
4
  >
5
5
  > Complete reference for async jobs, video exports, and file management.
6
6
  > Load this document when implementing export workflows or file downloads.
@@ -1,6 +1,6 @@
1
1
  # Media & Live Video - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.91
3
+ > **Version:** 0.3.101
4
4
  >
5
5
  > Complete reference for media retrieval, live streaming, and video playback.
6
6
  > Load this document when implementing video features.
@@ -1,6 +1,6 @@
1
1
  # PTZ Camera Controls
2
2
 
3
- > **Version:** 0.3.91
3
+ > **Version:** 0.3.101
4
4
  >
5
5
  > Pan/Tilt/Zoom camera control: position, movement, presets, and automation.
6
6
 
@@ -142,6 +142,22 @@ function handleVideoClick(event: MouseEvent) {
142
142
  | FORBIDDEN | No permission | Show access denied |
143
143
  | VALIDATION_ERROR | Empty camera ID | Fix input |
144
144
 
145
+ ## Fisheye Camera Exclusion
146
+
147
+ **IMPORTANT:** Fisheye cameras report `capabilities.ptz.capable: true` but are NOT true PTZ cameras.
148
+ Always exclude fisheye cameras when checking PTZ capability:
149
+
150
+ ```typescript
151
+ import { computed } from 'vue'
152
+
153
+ const isPtzCapable = computed(() => {
154
+ const ptz = camera.value?.capabilities?.ptz
155
+ return ptz?.capable === true && ptz?.fisheye !== true
156
+ })
157
+ ```
158
+
159
+ Also check `effectivePermissions.controlPTZ` to verify the user has permission to move the camera.
160
+
145
161
  ---
146
162
 
147
163
  ## Reference Examples
@@ -1,6 +1,6 @@
1
1
  # Vue 3 Application Setup - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.91
3
+ > **Version:** 0.3.101
4
4
  >
5
5
  > Complete guide for setting up a Vue 3 application with the een-api-toolkit.
6
6
  > Load this document when creating a new project or troubleshooting setup issues.
@@ -1,6 +1,6 @@
1
1
  # Users API - EEN API Toolkit
2
2
 
3
- > **Version:** 0.3.91
3
+ > **Version:** 0.3.101
4
4
  >
5
5
  > Complete reference for user management.
6
6
  > Load this document when working with user data.
@@ -214,7 +214,7 @@ let pageToken: string | undefined
214
214
  do {
215
215
  const result = await getCameras({ pageSize: 100, include: ['capabilities'], pageToken })
216
216
  for (const cam of result.data?.results || []) {
217
- if (cam.capabilities?.ptz?.capable) ptzCameras.push(cam)
217
+ if (cam.capabilities?.ptz?.capable && !cam.capabilities?.ptz?.fisheye) ptzCameras.push(cam)
218
218
  }
219
219
  pageToken = result.data?.nextPageToken ?? undefined
220
220
  } while (pageToken)
@@ -35,7 +35,7 @@ async function loadPtzCameras() {
35
35
 
36
36
  const allCameras = result.data?.results || []
37
37
  for (const cam of allCameras) {
38
- if (cam.capabilities?.ptz?.capable) {
38
+ if (cam.capabilities?.ptz?.capable && !cam.capabilities?.ptz?.fisheye) {
39
39
  ptzCameras.push(cam)
40
40
  }
41
41
  }
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, watch } from 'vue'
3
3
  import { movePtz } from 'een-api-toolkit'
4
- import type { PtzPositionResponse } from 'een-api-toolkit'
4
+ import type { PtzPositionResponse, PtzDirection, PtzStepSize, PtzMoveType, PtzMove } from 'een-api-toolkit'
5
5
  import { useApiLog } from '../composables/useApiLog'
6
6
 
7
7
  const props = defineProps<{
@@ -14,45 +14,97 @@ const emit = defineEmits<{
14
14
  }>()
15
15
 
16
16
  const { log: apiLog } = useApiLog()
17
+
18
+ // Move type selection
19
+ const moveType = ref<PtzMoveType>('position')
20
+
21
+ // Position fields
17
22
  const x = ref<string>('0')
18
23
  const y = ref<string>('0')
19
24
  const z = ref<string>('0')
25
+
26
+ // Direction fields
27
+ const directions = ref<PtzDirection[]>([])
28
+ const stepSize = ref<PtzStepSize>('medium')
29
+ const allDirections: PtzDirection[] = ['up', 'down', 'left', 'right', 'in', 'out']
30
+
31
+ // CenterOn fields
32
+ const relativeX = ref<string>('0.5')
33
+ const relativeY = ref<string>('0.5')
34
+
20
35
  const applying = ref(false)
21
36
  const error = ref<string | null>(null)
22
37
 
23
- watch(() => props.currentPosition, (pos) => {
24
- if (pos) {
25
- x.value = pos.x?.toFixed(3) ?? '0'
26
- y.value = pos.y?.toFixed(3) ?? '0'
27
- z.value = pos.z?.toFixed(3) ?? '0'
38
+ watch(moveType, () => {
39
+ error.value = null
40
+ })
41
+
42
+ function importPosition() {
43
+ if (props.currentPosition) {
44
+ x.value = props.currentPosition.x?.toFixed(3) ?? '0'
45
+ y.value = props.currentPosition.y?.toFixed(3) ?? '0'
46
+ z.value = props.currentPosition.z?.toFixed(3) ?? '0'
28
47
  }
29
- }, { immediate: true })
48
+ }
30
49
 
31
- async function apply() {
32
- if (!props.cameraId || applying.value) return
50
+ function buildMove(): PtzMove | null {
51
+ if (moveType.value === 'position') {
52
+ const xVal = parseFloat(x.value)
53
+ const yVal = parseFloat(y.value)
54
+ const zVal = parseFloat(z.value)
55
+
56
+ if (isNaN(xVal) || isNaN(yVal) || isNaN(zVal)) {
57
+ error.value = 'Invalid numeric values'
58
+ return null
59
+ }
60
+
61
+ // Position coordinates are camera-specific absolute values (not normalised to 0-1)
62
+ return { moveType: 'position', x: xVal, y: yVal, z: zVal }
63
+ } else if (moveType.value === 'direction') {
64
+ if (directions.value.length === 0) {
65
+ error.value = 'Select at least one direction'
66
+ return null
67
+ }
33
68
 
34
- const xVal = parseFloat(x.value)
35
- const yVal = parseFloat(y.value)
36
- const zVal = parseFloat(z.value)
69
+ return { moveType: 'direction', direction: [...directions.value], stepSize: stepSize.value }
70
+ } else {
71
+ const rxVal = parseFloat(relativeX.value)
72
+ const ryVal = parseFloat(relativeY.value)
73
+
74
+ if (isNaN(rxVal) || isNaN(ryVal)) {
75
+ error.value = 'Invalid numeric values'
76
+ return null
77
+ }
37
78
 
38
- if (isNaN(xVal) || isNaN(yVal) || isNaN(zVal)) {
39
- error.value = 'Invalid numeric values'
40
- return
79
+ if (rxVal < 0 || rxVal > 1 || ryVal < 0 || ryVal > 1) {
80
+ error.value = 'Values must be between 0 and 1'
81
+ return null
82
+ }
83
+
84
+ return { moveType: 'centerOn', relativeX: rxVal, relativeY: ryVal }
41
85
  }
86
+ }
87
+
88
+ async function apply() {
89
+ if (!props.cameraId || applying.value) return
42
90
 
43
- applying.value = true
44
91
  error.value = null
45
92
 
46
- const move = { moveType: 'position' as const, x: xVal, y: yVal, z: zVal }
47
- const result = await movePtz(props.cameraId, move)
48
- apiLog('movePtz', { cameraId: props.cameraId, move }, result.error ?? result.data, !!result.error)
93
+ const move = buildMove()
94
+ if (!move) return
49
95
 
50
- applying.value = false
96
+ applying.value = true
97
+ try {
98
+ const result = await movePtz(props.cameraId, move)
99
+ apiLog('movePtz', { cameraId: props.cameraId, move }, result.error ?? result.data, !!result.error)
51
100
 
52
- if (result.error) {
53
- error.value = result.error.message
54
- } else {
55
- emit('move-complete')
101
+ if (result.error) {
102
+ error.value = result.error.message
103
+ } else {
104
+ emit('move-complete')
105
+ }
106
+ } finally {
107
+ applying.value = false
56
108
  }
57
109
  }
58
110
  </script>
@@ -60,39 +112,114 @@ async function apply() {
60
112
  <template>
61
113
  <div class="position-input" data-testid="position-input">
62
114
  <div class="input-row">
63
- <label>X</label>
64
- <input
65
- v-model="x"
66
- type="number"
67
- step="0.01"
68
- :disabled="!cameraId || applying"
69
- data-testid="input-x"
70
- @keyup.enter="apply"
71
- />
72
- </div>
73
- <div class="input-row">
74
- <label>Y</label>
75
- <input
76
- v-model="y"
77
- type="number"
78
- step="0.01"
79
- :disabled="!cameraId || applying"
80
- data-testid="input-y"
81
- @keyup.enter="apply"
82
- />
83
- </div>
84
- <div class="input-row">
85
- <label>Z</label>
86
- <input
87
- v-model="z"
88
- type="number"
89
- step="0.1"
90
- min="0"
91
- :disabled="!cameraId || applying"
92
- data-testid="input-z"
93
- @keyup.enter="apply"
94
- />
115
+ <label>Move</label>
116
+ <select v-model="moveType" :disabled="!cameraId || applying" data-testid="move-type-select">
117
+ <option value="position">Position</option>
118
+ <option value="direction">Direction</option>
119
+ <option value="centerOn">Center On</option>
120
+ </select>
95
121
  </div>
122
+
123
+ <!-- Position fields -->
124
+ <template v-if="moveType === 'position'">
125
+ <div class="input-row">
126
+ <label>X</label>
127
+ <input
128
+ v-model="x"
129
+ type="number"
130
+ step="0.01"
131
+ :disabled="!cameraId || applying"
132
+ data-testid="input-x"
133
+ @keyup.enter="apply"
134
+ />
135
+ </div>
136
+ <div class="input-row">
137
+ <label>Y</label>
138
+ <input
139
+ v-model="y"
140
+ type="number"
141
+ step="0.01"
142
+ :disabled="!cameraId || applying"
143
+ data-testid="input-y"
144
+ @keyup.enter="apply"
145
+ />
146
+ </div>
147
+ <div class="input-row">
148
+ <label>Z</label>
149
+ <input
150
+ v-model="z"
151
+ type="number"
152
+ step="0.1"
153
+ min="0"
154
+ :disabled="!cameraId || applying"
155
+ data-testid="input-z"
156
+ @keyup.enter="apply"
157
+ />
158
+ </div>
159
+ <button
160
+ class="import-btn"
161
+ :disabled="!cameraId || !currentPosition || applying"
162
+ @click="importPosition"
163
+ data-testid="import-position"
164
+ >
165
+ Import Current Position
166
+ </button>
167
+ </template>
168
+
169
+ <!-- Direction fields -->
170
+ <template v-else-if="moveType === 'direction'">
171
+ <div class="direction-checkboxes">
172
+ <label v-for="dir in allDirections" :key="dir" class="checkbox-label">
173
+ <input
174
+ type="checkbox"
175
+ :value="dir"
176
+ v-model="directions"
177
+ :disabled="!cameraId || applying"
178
+ :data-testid="'dir-' + dir"
179
+ />
180
+ {{ dir }}
181
+ </label>
182
+ </div>
183
+ <div class="input-row">
184
+ <label>Step</label>
185
+ <select v-model="stepSize" :disabled="!cameraId || applying" data-testid="step-size-select">
186
+ <option value="small">Small</option>
187
+ <option value="medium">Medium</option>
188
+ <option value="large">Large</option>
189
+ </select>
190
+ </div>
191
+ </template>
192
+
193
+ <!-- CenterOn fields -->
194
+ <template v-else>
195
+ <div class="input-row">
196
+ <label>rX</label>
197
+ <input
198
+ v-model="relativeX"
199
+ type="number"
200
+ step="0.01"
201
+ min="0"
202
+ max="1"
203
+ :disabled="!cameraId || applying"
204
+ data-testid="input-relative-x"
205
+ @keyup.enter="apply"
206
+ />
207
+ </div>
208
+ <div class="input-row">
209
+ <label>rY</label>
210
+ <input
211
+ v-model="relativeY"
212
+ type="number"
213
+ step="0.01"
214
+ min="0"
215
+ max="1"
216
+ :disabled="!cameraId || applying"
217
+ data-testid="input-relative-y"
218
+ @keyup.enter="apply"
219
+ />
220
+ </div>
221
+ </template>
222
+
96
223
  <button
97
224
  class="apply-btn"
98
225
  :disabled="!cameraId || applying"
@@ -127,7 +254,7 @@ async function apply() {
127
254
  font-size: 13px;
128
255
  font-weight: 600;
129
256
  color: #555;
130
- min-width: 20px;
257
+ min-width: 32px;
131
258
  text-align: right;
132
259
  }
133
260
 
@@ -140,10 +267,61 @@ async function apply() {
140
267
  font-family: monospace;
141
268
  }
142
269
 
143
- .input-row input:disabled {
270
+ .input-row select {
271
+ flex: 1;
272
+ padding: 5px 8px;
273
+ border: 1px solid #ddd;
274
+ border-radius: 4px;
275
+ font-size: 13px;
276
+ }
277
+
278
+ .input-row input:disabled,
279
+ .input-row select:disabled {
144
280
  opacity: 0.5;
145
281
  }
146
282
 
283
+ .direction-checkboxes {
284
+ display: grid;
285
+ grid-template-columns: 1fr 1fr;
286
+ gap: 4px 12px;
287
+ margin-bottom: 8px;
288
+ }
289
+
290
+ .checkbox-label {
291
+ display: flex;
292
+ align-items: center;
293
+ gap: 6px;
294
+ font-size: 13px;
295
+ color: #555;
296
+ cursor: pointer;
297
+ text-transform: capitalize;
298
+ }
299
+
300
+ .checkbox-label input[type="checkbox"] {
301
+ margin: 0;
302
+ }
303
+
304
+ .import-btn {
305
+ width: 100%;
306
+ padding: 6px;
307
+ font-size: 11px;
308
+ background: #6c757d;
309
+ color: white;
310
+ border: none;
311
+ border-radius: 4px;
312
+ cursor: pointer;
313
+ margin-bottom: 4px;
314
+ }
315
+
316
+ .import-btn:hover:not(:disabled) {
317
+ background: #5a6268;
318
+ }
319
+
320
+ .import-btn:disabled {
321
+ opacity: 0.4;
322
+ cursor: not-allowed;
323
+ }
324
+
147
325
  .apply-btn {
148
326
  width: 100%;
149
327
  padding: 8px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "een-api-toolkit",
3
- "version": "0.3.91",
3
+ "version": "0.3.101",
4
4
  "description": "EEN Video platform API v3.0 library for Vue 3",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",