Skip to content

Commit

Permalink
Merge pull request #49 from korapp/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
korapp authored Jul 31, 2024
2 parents 0b266fa + 3b57fc6 commit 0e43dfb
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 141 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
- name: Package
run: |
cd $ASSET_DIR
zip -r ../$ASSET_BASE_NAME-$RELEASE_VERSION.plasmoid *
zip -r ../$ASSET_BASE_NAME-$RELEASE_VERSION.plasmoid * -x "*.git*"
cd ..
- uses: ncipollo/release-action@v1
Expand Down
92 changes: 45 additions & 47 deletions package/contents/code/model.mjs
Original file line number Diff line number Diff line change
@@ -1,75 +1,73 @@
const activeStates = ['on', 'open'];
const activeStates = ['on', 'open', 'idle'];

function getDisplayValue({ state, attribute, attributes, unit }) {
if (attribute) {
return attributes[attribute] || ''
}
if (state && state !== 'unknown') {
return state + (unit === '%' ? unit : ' ' + unit)
}
if (attribute && attributes[attribute]) return attributes[attribute].toString()
if (state && state !== 'unknown') return state + (unit === '%' ? unit : ' ' + unit)
return ''
}

export function Entity({ entity_id = '', name, icon, attribute = '', unit, default_action = {} } = {}, data = {}) {
const { s: state = '', a: attributes = {} } = data
export function Entity({ entity_id = '', name, icon, state, attribute = '', attributes, unit, default_action = {}, scroll_action = {} } = {}, data = {}) {
this.entity_id = entity_id
this.name = name || attributes.friendly_name || ''
this.icon = icon || attributes.icon || ''
this.unit = unit || attributes.unit_of_measurement || ''
this.attributes = Object.assign({}, attributes, data.a)
this.state = data.s || state || ''
this.name = name || this.attributes.friendly_name || ''
this.icon = icon || this.attributes.icon || ''
this.unit = unit || this.attributes.unit_of_measurement || ''
this.attribute = attribute
this.default_action = default_action
this.active = activeStates.includes(state)
this.scroll_action = scroll_action
this.active = activeStates.includes(this.state)
this.domain = entity_id.substring(0, entity_id.indexOf('.'))
this.state = state
this.value = getDisplayValue({ state, attribute, attributes, unit: this.unit })
this.value = getDisplayValue(this)
}

export function ConfigEntity({ entity_id = '', name, icon, attribute, default_action, notify } = {}) {
export function ConfigEntity({ entity_id = '', name, icon, attribute, default_action, scroll_action, notify } = {}) {
Object.defineProperties(this, {
entity_id: {
get: function() { return this._entity_id },
enumerable: true,
get: function() { return this[Symbol.for('entity_id')] },
set: function(id) {
this._entity_id = id
this._domain = id.substring(0, id.indexOf('.'))
if (!this._default_action) return
if (this._domain === this._default_action.domain) {
this._default_action.target.entity_id = id
} else {
delete this._default_action
}
this[Symbol.for('entity_id')] = id
this[Symbol.for('domain')] = id.substring(0, id.indexOf('.'))
updateAction(this, 'default_action')
updateAction(this, 'scroll_action')
}
},
domain: {
get: function() { return this._domain }
},
default_action: {
get: function() { return this._default_action },
set: function({ domain = this.domain, service, target = { entity_id: this.entity_id }} = {}) {
if (!service) return delete this._default_action
this._default_action = {
domain,
service,
target
}
}
},
enumerable: true,
get: function() { return this[Symbol.for('domain')] }
}
});

addActionProperty(this, 'default_action')
addActionProperty(this, 'scroll_action')

this.entity_id = entity_id
this.name = name
this.icon = icon
this.attribute = attribute
this.default_action = default_action
this.scroll_action = scroll_action
this.notify = notify
}

ConfigEntity.prototype.toJSON = function() {
return {
attribute: this.attribute,
entity_id: this.entity_id,
icon: this.icon,
name: this.name,
default_action: this.default_action,
notify: this.notify
}
function addActionProperty(o, name) {
Object.defineProperty(o, name, {
enumerable: true,
get: function() { return o[Symbol.for(name)] },
set: function(action) {
o[Symbol.for(name)] = !action?.service ? null : {
service: action.service,
domain: action.domain || o.domain,
target: action.target || { entity_id: o.entity_id },
data_field: action.data_field
}
}
})
}

function updateAction(o, name) {
if (!o[name]) return
if (o.domain !== o[name].domain) return o[name] = null
o[name].target.entity_id = o.entity_id
}
2 changes: 1 addition & 1 deletion package/contents/config/config.qml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ ConfigModel {
}
ConfigCategory {
name: i18n("Items")
icon: "view-list-symbolic"
icon: "view-list-details"
source: "ConfigItems.qml"
}
}
17 changes: 3 additions & 14 deletions package/contents/ui/Client.qml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ BaseObject {
property var getServices: ws.getServices
property var getStates: ws.getStates
property string errorString: ""
readonly property alias ready: ws.ready
readonly property bool configured: ws.url && token

onBaseUrlChanged: ws.url = baseUrl.replace('http', 'ws') + "/api/websocket"
Expand All @@ -19,17 +20,7 @@ BaseObject {
Connections {
target: ws
function onError(msg) { errorString = msg }
function onEstablished() { errorString = "" }
}

readonly property QtObject ready: QtObject {
function connect (fn) {
if (ws.ready) fn()
ws.established.connect(fn)
}
function disconnect (fn) {
ws.established.disconnect(fn)
}
function onReadyChanged() { ws.ready && (errorString = "") }
}

Timer {
Expand Down Expand Up @@ -59,11 +50,9 @@ BaseObject {
property var subscriptions: new Map()
property var promises: new Map()
readonly property bool open: status === WebSocket.Open
signal established
signal error(string msg)

onOpenChanged: ready = false
onReadyChanged: ready && established()

onTextMessageReceived: message => {
pingPongTimer.reset()
Expand Down Expand Up @@ -117,7 +106,7 @@ BaseObject {
.then(() => subscriptions.delete(subscription))
}

function callService({ domain, service, data, target } = {}) {
function callService({ domain, service, target } = {}, data) {
return commandAsync({
"type": "call_service",
"domain": domain,
Expand Down
81 changes: 61 additions & 20 deletions package/contents/ui/ConfigItem.qml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "components"
Kirigami.FormLayout {
property var item
readonly property var source: item.entity_id && entities[item.entity_id] || {}
readonly property var itemServices: item.domain && services[item.domain] || {}

TextField {
Kirigami.FormData.label: i18n("Entity")
Expand Down Expand Up @@ -39,26 +40,6 @@ Kirigami.FormLayout {
}
}

Row {
Kirigami.FormData.label: i18n("Action")
visible: !!actionSelector.count
CheckBox {
id: useAction
anchors.verticalCenter: parent.verticalCenter
}
ComboBox {
id: actionSelector
model: item.domain && services[item.domain] ? Object.keys(services[item.domain]) : []
onActivated: index => item.default_action = { service: model[index] }
enabled: useAction.checked
onModelChanged: {
currentIndex = item.default_action && model ? model.indexOf(item.default_action.service) : -1
useAction.checked = ~currentIndex
}
onEnabledChanged: activated(enabled ? currentIndex : -1)
}
}

TextField {
text: item.name || ''
placeholderText: source.attributes?.friendly_name || ''
Expand All @@ -85,4 +66,64 @@ Kirigami.FormLayout {
checked: !!item.notify
onCheckedChanged: item.notify = checked
}

component ServiceSelector: Row {
visible: !!serviceSelector.count
property alias currentValue: serviceSelector.currentValue
property var initialValue
property var serviceFilter
default property alias data: nested.data
CheckBox {
id: useAction
anchors.verticalCenter: parent.verticalCenter
}
ComboBox {
id: serviceSelector
model: serviceFilter ? Object.keys(itemServices).filter(serviceFilter) : Object.keys(itemServices)
enabled: useAction.checked
onEnabledChanged: if (!enabled) currentIndex = -1
onCurrentIndexChanged: useAction.checked = ~currentIndex
onModelChanged: resetIndex()
Component.onCompleted: resetIndex()
function resetIndex() {
currentIndex = initialValue && count ? indexOfValue(initialValue) : -1
}
}
Column {
enabled: serviceSelector.enabled
id: nested
}
}

ServiceSelector {
Kirigami.FormData.label: i18n("Click action")
initialValue: item.default_action?.service
onCurrentValueChanged: s => item.default_action = { service: currentValue }
}

ServiceSelector {
id: scrollActionSelector
Kirigami.FormData.label: i18n("Scroll action")
serviceFilter: k => getNumberFields(itemServices[k]).length
initialValue: item.scroll_action?.service
onCurrentValueChanged: scrollFieldSelector.model = getNumberFields(itemServices[currentValue])

ComboBox {
id: scrollFieldSelector
model: scrollActionSelector.currentValue
onCurrentValueChanged: item.scroll_action = { service: scrollActionSelector.currentValue, data_field: currentValue }
onModelChanged: currentIndex = item.scroll_action?.data_field
? indexOfValue(item.scroll_action.data_field)
: count === 1 ? 0 : -1
}
}

function getNumberFields({ fields = {} } = {}) {
return Object.keys(fields).reduce((f, id) => {
const field = fields[id]
if (field.fields) f.push(...getNumberFields(field))
if (field.selector?.number && id in source.attributes) f.push(id)
return f
}, [])
}
}
8 changes: 5 additions & 3 deletions package/contents/ui/ConfigItems.qml
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,15 @@ KCM.ScrollViewKCM {
Component.onCompleted: {
setItems(cfg_items)
ha = ClientFactory.getClient(this, plasmoid.configuration.url)
ha.ready.connect(fetchData)
ha.readyChanged.connect(fetchData)
}

function setItems(data) {
items.append(JSON.parse(data))
}

function fetchData() {
if (!ha?.ready) return
return Promise.all([ha.getStates(), ha.getServices()])
.then(([e, s]) => {
entities = arrayToObject(e, 'entity_id')
Expand Down Expand Up @@ -191,10 +192,11 @@ KCM.ScrollViewKCM {
dialog.open()
dialog.onItemAccepted.connect((index, item) => {
if (~index) {
return updateItem(item.toJSON(), index)
return updateItem(item, index)
}
return addItem(item.toJSON())
return addItem(item)
})
dialog.closed.connect(dialog.destroy)
}

function save() {
Expand Down
Loading

0 comments on commit 0e43dfb

Please sign in to comment.