Add QR Code Encoder plugin (#226)
* init * feat: notify options; all, minimal and none * update: plugin.toml * update: add README * feat: add button toggle * feat: add configurable glyph * fix: valid image radius * fix: shell escape entry * feat: add settings * update: add thumbnail.webp * feat: add titlebar and titlebar toggle * feat: add custom image widget setting, reorder keys * fix: auto-size image with titlebar * update: README (new settings) * fix: lowercase correction_level label keys * fix: file type and move shellEscape to the cmd
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# QR Code encoder
|
||||
|
||||
Transform any text or URL into a QR code completely offline. Then scan or copy the code.
|
||||
|
||||
## Plugin
|
||||
|
||||
| Field | Value |
|
||||
| --- | --- |
|
||||
| ID | `yocraft/qrcode` |
|
||||
| Entries | Bar widget: `widget`; panel: `panel` |
|
||||
|
||||
## Requirements
|
||||
|
||||
Install `qrencode` on `PATH`.
|
||||
|
||||
## Usage
|
||||
|
||||
Open the panel from the bar widget or with this command:
|
||||
```sh
|
||||
noctalia msg panel-toggle yocraft/qrcode:panel
|
||||
```
|
||||
|
||||
Enter your text or URL and click on Generate or press enter to generate the QR code, then scan it from the panel or copy it by clicking on it.
|
||||
|
||||
## Settings
|
||||
|
||||
| Setting | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `titlebar` | `bool` | `true` | Show titlebar, panel name and buttons like settings and close. |
|
||||
| `generate_button` | `bool` | `true` | Show Generate button, disable will submit on enter. |
|
||||
| `close_on_copy` | `bool` | `false` | Close the panel when copying the QR code. |
|
||||
| `notify` | `select` | `minimal` | Controls the notifications, minimal only notifies when Close on Copy is used. |
|
||||
| `size` | `int` | `8` | Specify module size in dots (pixels). |
|
||||
| `correction_level` | `select` | `M` | Specify error correction level. |
|
||||
| `glyph` | `glyph` | `qrcode` | Bar widget icon glyph name. |
|
||||
| `custom_image` | `image` | `""` | Path to a custom image; leave empty to use the icon glyph. |
|
||||
|
||||
## Notes
|
||||
|
||||
The plugin runs entirely locally and does not require network access.
|
||||
@@ -0,0 +1,226 @@
|
||||
local currentText = ""
|
||||
local genCount = 0
|
||||
local errorMessage = nil
|
||||
local imagePath = nil
|
||||
local status = "idle"
|
||||
|
||||
local imageSize = 400
|
||||
|
||||
local notifyConf = noctalia.getConfig("notify")
|
||||
|
||||
local function notify(text, error)
|
||||
local cmd = error and noctalia.notifyError or noctalia.notify
|
||||
cmd("QR Code encoder", text)
|
||||
end
|
||||
|
||||
local function statusText()
|
||||
if status == "generating" then
|
||||
return noctalia.tr("panel.status.generating")
|
||||
elseif status == "error" then
|
||||
return noctalia.tr("panel.status.error", { message = errorMessage })
|
||||
elseif status == "ready" then
|
||||
return noctalia.tr("panel.status.ready")
|
||||
elseif status == "copied" then
|
||||
return noctalia.tr("panel.status.copied")
|
||||
else
|
||||
return noctalia.tr("panel.status.idle")
|
||||
end
|
||||
end
|
||||
|
||||
local function render()
|
||||
local rowContent = {
|
||||
ui.input({
|
||||
flexGrow = 1,
|
||||
focus = true,
|
||||
placeholder = noctalia.tr("panel.placeholder"),
|
||||
onChange = "onTextChange",
|
||||
onSubmit = "onTextSubmit",
|
||||
})
|
||||
}
|
||||
|
||||
if noctalia.getConfig("generate_button") then
|
||||
table.insert(
|
||||
rowContent,
|
||||
ui.button({
|
||||
text = "Generate",
|
||||
variant = "primary",
|
||||
enabled = status ~= "generating",
|
||||
onClick = "onGenerateClick",
|
||||
})
|
||||
)
|
||||
end
|
||||
|
||||
local content = {
|
||||
ui.row({ gap = 8 }, rowContent),
|
||||
ui.label({
|
||||
flexGrow = 1,
|
||||
textAlign = "center",
|
||||
text = statusText(),
|
||||
color = status == "error" and "error" or "on_surface/0.75",
|
||||
})
|
||||
}
|
||||
|
||||
if (status == "ready"
|
||||
or status == "copied"
|
||||
or (status == "error" and errorMessage == noctalia.tr("panel.error.copy_failed")))
|
||||
and imagePath ~= nil then
|
||||
table.insert(
|
||||
content,
|
||||
ui.row({ justify = "center" }, {
|
||||
ui.image({
|
||||
path = imagePath,
|
||||
width = imageSize,
|
||||
height = imageSize,
|
||||
radius = 24,
|
||||
onClick = "onImageClick",
|
||||
})
|
||||
})
|
||||
)
|
||||
end
|
||||
|
||||
if noctalia.getConfig("titlebar") then
|
||||
imageSize = 350
|
||||
table.insert(
|
||||
content, 1,
|
||||
ui.row({ align = "center", justify = "space_between", gap = 8 }, {
|
||||
ui.label({ text = "QR Code Encoder", fontSize = 16, fontWeight = "bold", flexGrow = 1 }),
|
||||
ui.button({ glyph = "settings", onClick = noctalia.openSettings }),
|
||||
ui.button({ glyph = "close", onClick = "onCloseClicked" }),
|
||||
})
|
||||
)
|
||||
end
|
||||
|
||||
panel.render(ui.column({ flexGrow = 1, gap = 16 }, content))
|
||||
end
|
||||
|
||||
local function shellEscape(raw)
|
||||
return "'" .. raw:gsub("'", "'\\''") .. "'"
|
||||
end
|
||||
|
||||
local function generate(text)
|
||||
if status == "generating" then
|
||||
return
|
||||
end
|
||||
|
||||
text = noctalia.string.trim(text or "")
|
||||
|
||||
if text == "" then
|
||||
if imagePath ~= nil then
|
||||
noctalia.removeFile(imagePath)
|
||||
end
|
||||
status = "idle"
|
||||
errorMessage = nil
|
||||
imagePath = nil
|
||||
render()
|
||||
return
|
||||
end
|
||||
|
||||
if not noctalia.commandExists("qrencode") then
|
||||
status = "error"
|
||||
errorMessage = noctalia.tr("panel.error.missing_qrencode")
|
||||
|
||||
if notifyConf == "all" then
|
||||
notify(statusText(), status == "error")
|
||||
end
|
||||
|
||||
render()
|
||||
return
|
||||
end
|
||||
|
||||
status = "generating"
|
||||
render()
|
||||
|
||||
local dir = noctalia.pluginDir()
|
||||
local outputPath = string.format("%s/qr-%s.png", dir, genCount)
|
||||
local previousPath = imagePath
|
||||
genCount += 1
|
||||
|
||||
local size = noctalia.getConfig("size") or 8
|
||||
local correctionLevel = noctalia.getConfig("correction_level") or "M"
|
||||
|
||||
local cmd = string.format(
|
||||
'qrencode %s -o %s -s %s -l %s',
|
||||
shellEscape(text),
|
||||
outputPath,
|
||||
size,
|
||||
correctionLevel
|
||||
)
|
||||
|
||||
noctalia.runAsync(
|
||||
cmd,
|
||||
function(result)
|
||||
if result.exitCode == 0 and noctalia.fileExists(outputPath) then
|
||||
imagePath = outputPath
|
||||
status = "ready"
|
||||
if previousPath ~= nil then
|
||||
noctalia.removeFile(previousPath)
|
||||
end
|
||||
else
|
||||
status = "error"
|
||||
errorMessage = noctalia.tr("panel.error.generate_failed")
|
||||
end
|
||||
|
||||
if notifyConf == "all" then
|
||||
notify(statusText(), status == "error")
|
||||
end
|
||||
|
||||
render()
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function onOpen(_context)
|
||||
status = "idle"
|
||||
render()
|
||||
end
|
||||
|
||||
function onTextChange(value)
|
||||
currentText = value
|
||||
end
|
||||
|
||||
function onTextSubmit(value)
|
||||
currentText = value
|
||||
generate(currentText)
|
||||
end
|
||||
|
||||
function onGenerateClick()
|
||||
generate(currentText)
|
||||
end
|
||||
|
||||
function onImageClick()
|
||||
local bytes, err = noctalia.readFile(imagePath)
|
||||
errorMessage = noctalia.tr("panel.error.copy_failed")
|
||||
|
||||
if bytes == nil then
|
||||
status = "error"
|
||||
else
|
||||
local ok = noctalia.copyToClipboard(bytes, "image/png")
|
||||
|
||||
status = ok and "copied" or "error"
|
||||
end
|
||||
|
||||
if notifyConf == "all" then
|
||||
notify(statusText(), status == "error")
|
||||
end
|
||||
|
||||
if noctalia.getConfig("close_on_copy") then
|
||||
if notifyConf == "minimal" then
|
||||
notify(statusText(), status == "error")
|
||||
end
|
||||
|
||||
panel.close()
|
||||
return
|
||||
end
|
||||
|
||||
render()
|
||||
end
|
||||
|
||||
function onCloseClicked()
|
||||
panel.close()
|
||||
end
|
||||
|
||||
function onClose()
|
||||
if imagePath ~= nil then
|
||||
noctalia.removeFile(imagePath)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,93 @@
|
||||
id = "yocraft/qrcode"
|
||||
name = "QR Code Encoder"
|
||||
version = "1.0.0"
|
||||
plugin_api = 15
|
||||
author = "yocraft"
|
||||
license = "MIT"
|
||||
deprecated = false
|
||||
icon = "qrcode"
|
||||
description = "Transform any text or URL in a QR code"
|
||||
tags = [ "panel", "fun", "network", "privacy", "productivity", "utility" ]
|
||||
dependencies = [ "qrencode" ]
|
||||
|
||||
[[widget]]
|
||||
id = "widget"
|
||||
entry = "widget.luau"
|
||||
|
||||
[[widget.setting]]
|
||||
key = "glyph"
|
||||
type = "glyph"
|
||||
label_key = "widget.glyph.label"
|
||||
description_key = "widget.glyph.description"
|
||||
default = "qrcode"
|
||||
|
||||
[[widget.setting]]
|
||||
key = "custom_image"
|
||||
type = "file"
|
||||
label_key = "widget.custom_image.label"
|
||||
description_key = "widget.custom_image.description"
|
||||
default = ""
|
||||
extensions = [".png", ".jpg", ".jpeg", ".webp", ".svg"]
|
||||
|
||||
[[panel]]
|
||||
id = "panel"
|
||||
entry = "panel.luau"
|
||||
width = 500
|
||||
height = 550
|
||||
placement = "floating"
|
||||
position = "center"
|
||||
|
||||
[[setting]]
|
||||
key = "titlebar"
|
||||
type = "bool"
|
||||
label_key = "settings.titlebar.label"
|
||||
description_key = "settings.titlebar.description"
|
||||
default = true
|
||||
|
||||
[[setting]]
|
||||
key = "generate_button"
|
||||
type = "bool"
|
||||
label_key = "settings.generate_button.label"
|
||||
description_key = "settings.generate_button.description"
|
||||
default = true
|
||||
|
||||
[[setting]]
|
||||
key = "close_on_copy"
|
||||
type = "bool"
|
||||
label_key = "settings.close_on_copy.label"
|
||||
description_key = "settings.close_on_copy.description"
|
||||
default = false
|
||||
|
||||
[[setting]]
|
||||
key = "notify"
|
||||
type = "select"
|
||||
label_key = "settings.notify.label"
|
||||
description_key = "settings.notify.description"
|
||||
default = "minimal"
|
||||
options = [
|
||||
{ value = "all", label_key = "settings.notify.all" },
|
||||
{ value = "minimal", label_key = "settings.notify.minimal" },
|
||||
{ value = "none", label_key = "settings.notify.none" },
|
||||
]
|
||||
|
||||
[[setting]]
|
||||
key = "size"
|
||||
type = "int"
|
||||
label_key = "settings.size.label"
|
||||
description_key = "settings.size.description"
|
||||
default = 8
|
||||
advanced = true
|
||||
|
||||
[[setting]]
|
||||
key = "correction_level"
|
||||
type = "select"
|
||||
label_key = "settings.correction_level.label"
|
||||
description_key = "settings.correction_level.description"
|
||||
default = "M"
|
||||
options = [
|
||||
{ value = "L", label_key = "settings.correction_level.l" },
|
||||
{ value = "M", label_key = "settings.correction_level.m" },
|
||||
{ value = "Q", label_key = "settings.correction_level.q" },
|
||||
{ value = "H", label_key = "settings.correction_level.h" },
|
||||
]
|
||||
advanced = true
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"settings": {
|
||||
"titlebar": {
|
||||
"label": "Show Titlebar",
|
||||
"description": "Show panel name and buttons like settings and close."
|
||||
},
|
||||
"generate_button": {
|
||||
"label": "Show Generate Button",
|
||||
"description": "Disable will submit on enter."
|
||||
},
|
||||
"close_on_copy": {
|
||||
"label": "Close on Copy",
|
||||
"description": "Close the panel when copying the QR code."
|
||||
},
|
||||
"notify": {
|
||||
"label": "Notify",
|
||||
"description": "Controls the notifications, minimal only notifies when Close on Copy is used.",
|
||||
"all": "All",
|
||||
"minimal": "Minimal",
|
||||
"none": "None"
|
||||
},
|
||||
"size": {
|
||||
"label": "QR Code Size",
|
||||
"description": "Specify module size in dots (pixels)."
|
||||
},
|
||||
"correction_level": {
|
||||
"label": "Correction Level",
|
||||
"description": "Specify error correction level.",
|
||||
"l": "Lowest",
|
||||
"m": "Medium",
|
||||
"q": "High",
|
||||
"h": "Highest"
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"tooltip": "Generate a QR Code",
|
||||
"glyph": {
|
||||
"label": "Glyph",
|
||||
"description": "Icon glyph name"
|
||||
},
|
||||
"custom_image": {
|
||||
"label": "Custom image",
|
||||
"description": "Path to a custom image; leave empty to use the icon glyph"
|
||||
}
|
||||
},
|
||||
"panel": {
|
||||
"placeholder": "Enter text or url...",
|
||||
"status": {
|
||||
"generating": "Generating...",
|
||||
"error": "Error: {message}",
|
||||
"ready": "QR code ready. Scan it here or click it to copy.",
|
||||
"copied": "QR code copied to clipboard.",
|
||||
"idle": "Enter some text, then press Generate."
|
||||
},
|
||||
"error": {
|
||||
"generate_failed": "failed to generate the QR code.",
|
||||
"missing_qrencode": "qrencode not installed.",
|
||||
"copy_failed": "failed to copy the QR code."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
local image = noctalia.getConfig("custom_image")
|
||||
|
||||
if image ~= "" and image ~= nil then
|
||||
barWidget.setImage(image)
|
||||
else
|
||||
barWidget.setGlyph(noctalia.getConfig("glyph"))
|
||||
end
|
||||
|
||||
barWidget.setTooltip(noctalia.tr("widget.tooltip"))
|
||||
|
||||
function onClick()
|
||||
noctalia.togglePanel("yocraft/qrcode:panel")
|
||||
end
|
||||
Reference in New Issue
Block a user