Skip to content

Commit 24c4281

Browse files
committed
Bug fix + better hints
1 parent 6312fba commit 24c4281

41 files changed

Lines changed: 1253 additions & 253 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

HOMEY_COMMUNITY_LISTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
![large|500x350](upload://p5hslvVrgjtVwqB76PnfDcsZAWL.jpeg)
1+
![xlarge|690x483](upload://iSxhJPUltgcgPQ7gy4z5iisCv5F.jpeg)
22

33
# Smart (Components) Toolkit v1.8.0
44

@@ -16,7 +16,7 @@ Replace complex flow networks with powerful logic devices controlled by dynamic
1616
|--------|---------|
1717
| **Logic Device** | Boolean logic with visual wizard. Combine device states into TRUE/FALSE using formulas like `A AND B`. |
1818
| **Logic Unit** | Advanced boolean logic with multiple formulas per device. JSON configuration. |
19-
| **State Device** | Scene management. Capture states at setup, apply with one action. |
19+
| **State Device** | Scene management. Capture states at setup, apply with one action. "Virtual device". |
2020
| **State Capture Device** | Dynamic state capture at runtime. Push/pop stack for temporary changes. |
2121
| **Waiter Gates** | Flow control that pauses and waits for device states to change. |
2222

docs/tools/state-editor-api.html

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,28 @@
4949
<div class="flex items-center space-x-4">
5050
<a href="../README.md" class="text-xl font-bold text-purple-600 hover:text-purple-700">Smart (Components) Toolkit</a>
5151
<span class="text-gray-300">|</span>
52-
<span class="text-gray-700 font-semibold">State Editor (API)</span>
52+
<!-- Mode Selector -->
53+
<div class="flex bg-gray-100 rounded-lg p-0.5">
54+
<button @click="editorMode = 'state'"
55+
:class="editorMode === 'state' ? 'bg-white shadow text-purple-600' : 'text-gray-600 hover:text-gray-800'"
56+
class="px-3 py-1 rounded-md text-sm font-medium transition">
57+
<i class="fas fa-play-circle mr-1"></i>State Device
58+
</button>
59+
<button @click="editorMode = 'captured'"
60+
:class="editorMode === 'captured' ? 'bg-white shadow text-orange-600' : 'text-gray-600 hover:text-gray-800'"
61+
class="px-3 py-1 rounded-md text-sm font-medium transition">
62+
<i class="fas fa-camera mr-1"></i>Captured State
63+
</button>
64+
</div>
65+
<span class="text-xs text-gray-500">(API)</span>
5366
</div>
5467
<div class="space-x-3">
68+
<!-- Template button for captured mode -->
69+
<button v-if="editorMode === 'captured'" @click="showTemplateModal = true"
70+
:class="capturedTemplate ? 'bg-orange-600 hover:bg-orange-700' : 'bg-orange-400 hover:bg-orange-500'"
71+
class="text-white px-3 py-1.5 rounded text-sm transition shadow-sm">
72+
<i class="fas fa-list-check mr-2"></i>{{ capturedTemplate ? 'Template Loaded' : 'Load Template' }}
73+
</button>
5574
<button v-if="currentDevice" @click="saveToDevice" class="bg-purple-600 hover:bg-purple-700 text-white px-3 py-1.5 rounded text-sm transition shadow-sm">
5675
<i class="fas fa-save mr-2"></i>Save to Device
5776
</button>
@@ -361,6 +380,33 @@ <h2 class="text-xl font-bold text-gray-800">Import JSON</h2>
361380
</div>
362381
</div>
363382

383+
<!-- Template Modal (Captured State Mode) -->
384+
<div v-if="showTemplateModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4 backdrop-blur-sm">
385+
<div class="bg-white rounded-lg shadow-xl max-w-2xl w-full flex flex-col max-h-[90vh]">
386+
<div class="p-4 border-b border-gray-200 flex justify-between items-center bg-orange-50 rounded-t-lg">
387+
<h2 class="text-xl font-bold text-gray-800"><i class="fas fa-list-check mr-2 text-orange-600"></i>Load Template</h2>
388+
<button @click="showTemplateModal = false" class="text-gray-400 hover:text-gray-600 transition"><i class="fas fa-times text-lg"></i></button>
389+
</div>
390+
<div class="p-4 flex-grow flex flex-col">
391+
<p class="text-sm text-gray-600 mb-2">Paste the template JSON from your State Capture Device. This defines which devices are allowed in the captured state.</p>
392+
<textarea v-model="templateText" class="w-full flex-grow bg-gray-50 border border-gray-300 rounded p-3 font-mono text-xs text-gray-800 focus:border-orange-500 focus:ring-1 focus:ring-orange-500 focus:outline-none min-h-[200px]" placeholder='{ "items": [ { "device_id": "...", "device_name": "...", "capabilities": [...] } ] }'></textarea>
393+
394+
<div v-if="templateError" class="mt-2 text-red-600 text-sm bg-red-50 p-2 rounded border border-red-200">
395+
<i class="fas fa-exclamation-circle mr-1"></i> {{ templateError }}
396+
</div>
397+
398+
<div v-if="capturedTemplate" class="mt-2 text-green-600 text-sm bg-green-50 p-2 rounded border border-green-200">
399+
<i class="fas fa-check-circle mr-1"></i> Template loaded: {{ capturedTemplate.items?.length || 0 }} devices
400+
</div>
401+
</div>
402+
<div class="p-4 border-t border-gray-200 flex justify-end space-x-3 bg-orange-50 rounded-b-lg">
403+
<button @click="loadTemplate" class="bg-orange-600 hover:bg-orange-700 text-white px-6 py-2 rounded text-sm font-semibold shadow-sm transition">
404+
Load Template
405+
</button>
406+
</div>
407+
</div>
408+
</div>
409+
364410
<!-- Homey Selection Modal -->
365411
<div v-if="showHomeyModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4 backdrop-blur-sm">
366412
<div class="bg-white rounded-lg shadow-xl max-w-md w-full flex flex-col">
@@ -423,13 +469,22 @@ <h2 class="text-xl font-bold text-gray-800">Select State Device</h2>
423469

424470
createApp({
425471
setup() {
472+
// Editor Mode: 'state' (State Device) or 'captured' (Captured State)
473+
const editorMode = ref('state');
474+
475+
// Template for captured state mode (devices allowed in state)
476+
const capturedTemplate = ref(null);
477+
const showTemplateModal = ref(false);
478+
const templateText = ref('');
479+
const templateError = ref(null);
480+
426481
// Global Config
427482
const config = ref({
428483
default_delay: 200,
429484
ignore_errors: true,
430485
debug: false
431486
});
432-
487+
433488
// Editor State
434489
const zones = ref({});
435490
const zoneNameKeys = ref({});
@@ -889,6 +944,21 @@ <h2 class="text-xl font-bold text-gray-800">Select State Device</h2>
889944
};
890945

891946

947+
// Template Loading for Captured State Mode
948+
const loadTemplate = () => {
949+
templateError.value = null;
950+
try {
951+
const data = JSON.parse(templateText.value);
952+
if (!data.items || !Array.isArray(data.items)) {
953+
throw new Error("Invalid template: missing 'items' array");
954+
}
955+
capturedTemplate.value = data;
956+
showTemplateModal.value = false;
957+
} catch (e) {
958+
templateError.value = e.message;
959+
}
960+
};
961+
892962
// Import Logic
893963
const processImport = (mode) => {
894964
importError.value = null;
@@ -908,6 +978,24 @@ <h2 class="text-xl font-bold text-gray-800">Select State Device</h2>
908978
throw new Error("Invalid JSON: No 'zones' or 'items' found.");
909979
}
910980

981+
// Validate against template in captured mode
982+
if (editorMode.value === 'captured' && capturedTemplate.value) {
983+
const allowedIds = new Set(capturedTemplate.value.items.map(item => item.device_id));
984+
const invalidDevices = [];
985+
986+
for (const [zoneName, zoneData] of Object.entries(importedZones)) {
987+
for (const item of (zoneData.items || [])) {
988+
if (!allowedIds.has(item.id)) {
989+
invalidDevices.push(item.name || item.id);
990+
}
991+
}
992+
}
993+
994+
if (invalidDevices.length > 0) {
995+
throw new Error(`Devices not in template: ${invalidDevices.join(', ')}`);
996+
}
997+
}
998+
911999
if (mode === 'replace') {
9121000
config.value = { ...config.value, ...importedConfig };
9131001
zones.value = importedZones;
@@ -917,7 +1005,7 @@ <h2 class="text-xl font-bold text-gray-800">Select State Device</h2>
9171005
if (importedConfig) {
9181006
config.value = { ...config.value, ...importedConfig };
9191007
}
920-
1008+
9211009
for (const [zName, zData] of Object.entries(importedZones)) {
9221010
if (!zones.value[zName]) {
9231011
zones.value[zName] = zData;
@@ -1110,22 +1198,23 @@ <h2 class="text-xl font-bold text-gray-800">Select State Device</h2>
11101198
return {
11111199
config, zones, zoneNameKeys,
11121200
showConfig, showInactive, showImportModal, showApiConfig, showHomeyModal, showDeviceModal, importText, importError,
1113-
1201+
editorMode, capturedTemplate, showTemplateModal, templateText, templateError,
1202+
11141203
// Auth
11151204
clientId, clientSecret, hasCredentials, isLoggedIn, userName, userAvatar,
11161205
scopeMode, recommendedScopesText, grantedScopesText, activeAuthStrategy,
11171206
saveCredentials, resetCredentials, login, logout,
1118-
1207+
11191208
// API
11201209
isFetching, apiMessage, apiError, homeys, homeyDevices, fetchDevices, selectHomey, selectDevice,
11211210
deviceSearchQuery, filteredDevices, currentDevice, saveToDevice,
1122-
1211+
11231212
simulationState, isValid, generatedJson, getItemDomId,
11241213
toggleConfig, addZone, renameZone, deleteZone,
11251214
addItem: () => {}, removeItem, moveItem,
11261215
addCapability, removeCapability, moveCapability,
11271216
onDragStart, onDragEnd, onDropItem,
1128-
processImport, copyToClipboard,
1217+
processImport, copyToClipboard, loadTemplate,
11291218
startSimulation, stopSimulation
11301219
};
11311220
}

0 commit comments

Comments
 (0)