* Send files to a tailnet machine with Taildrop The panel gets a send button next to the copy one on every machine that Tailscale grades as a Taildrop target, and `s` does the same from the keyboard. Picking runs through the XDG portal chooser, so it looks like the file dialog every other app opens. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJQJfHXXApUk6En8EZisHg * Save incoming Taildrop files and say so Linux keeps Taildrop files in the daemon's inbox until someone asks for them, so nothing arrived until you ran `tailscale file get` by hand. A user service now stages each delivery next to the downloads directory, hands it over under a free name, and announces it — with a preview when it's an image, and a click to open it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJQJfHXXApUk6En8EZisHg * Float every portal dialog, not just the titled ones The portal only ever shows dialogs, and the title regex missed any chooser an app names something else — ours included. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJQJfHXXApUk6En8EZisHg * Re-run the Taildrop enable now that the unit ships The unit was never installed to /usr/lib/systemd/user/, so the enable had nothing to act on and machines that already ran the migration carry a marker for a no-op. Rename it so they get a working pass, and report what systemctl says instead of a bare failure line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Wait for the file chooser on the connection that asked for it The portal answers a request with a Response signal directed at the connection that made it, and dbus-daemon delivers directed signals only to that connection. gdbus monitor registers with AddMatch rather than BecomeMonitor, so it never saw the reply: every pick left omarchy-file-select blocked on a read that could not arrive, taking omarchy-tailscale-send down with it before it reached either its notification or the transfer. Make the call and wait for the signal on one connection, and give up after ten minutes so an unanswered dialog cannot strand the caller. Drop the "Sending to" notification while here, so a send reports once. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Mark Taildrop notifications with the panel's send glyph Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Stop a hung tailscale poll from freezing the panel Each poll is skipped while its own process is still running, so one that never exits leaves the panel showing whatever it last read, for good: the peer list keeps a woken machine missing, and opening the panel cannot help because open runs the same refresh that hits the same guard. Reap anything still running fifteen seconds after a refresh, well inside the thirty second interval, so the next tick starts clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Place a moved widget where an added one lands 'move omarchy.media left' named a section, not a slot, but the section went through as an explicit target, which resolves a missing index by appending. The widget landed on the far end of the row instead of after the section anchor where 'add' puts it. Its test has never run: the assertion covering this went in four hours after an unrelated layout change had already stopped the file, and the runner stops the whole suite at the first failure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Keep the config test from failing on things it is not about The center layout assertion pinned the whole row, so parking the indicators left of the clock broke a test named for update sitting next to weather. Assert that adjacency instead. The package-defaults check reads PKGBUILDs from the omarchy-pkgs repo and blew up with a traceback wherever that is not a sibling checkout. Skip it when the checkout is absent, honour OMARCHY_PKGS_ROOT when it is somewhere else, and keep failing when it is present and wrong. Between them these stopped the suite eighty files early. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Make the file chooser a Python command rather than a bash host for one The portal work was a heredoc wedged inside a bash script that existed only to parse two flags. Drop the host: argparse covers the flags, and the file says at the top why it is the one command here not written in bash. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Tell a chooser that never opened apart from one that was dismissed Three fixes from review: The poll watchdog rearmed on every refresh, so a refresh interval shorter than its timeout — the setting goes down to five seconds — pushed the deadline ahead of a hung process forever. Arm it on the launch that needs watching and leave it alone. omarchy-file-select exited 1 both for nothing picked and for a chooser that could not run, and omarchy-tailscale-send read it through a process substitution, which drops the status anyway. A session bus that was not there looked exactly like someone changing their mind. Separate the two exits and read them with a command substitution. Delivery picked a free name and then renamed, which overwrites anything that takes the name in between. Link to the name instead: link(2) refuses one that is taken, so the check and the claim are the same step. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
496 lines
18 KiB
Bash
Executable File
496 lines
18 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# omarchy:summary=Add, move, remove, and configure bar plugin widgets in the layout
|
|
# omarchy:group=bar
|
|
# omarchy:args=add <id> [placement] | move <id> [placement] | remove <id> [placement] | set <id> <key> <value> [--json] [placement] | replace <old-id> <new-id>
|
|
# omarchy:examples=omarchy bar plugin add omarchy.tailscale | omarchy bar plugin add omarchy.clock center | omarchy bar plugin move omarchy.media left | omarchy bar plugin move omarchy.clock --section center --index 0 | omarchy bar plugin remove omarchy.tailscale | omarchy bar plugin set omarchy.clock format HH:mm
|
|
|
|
set -euo pipefail
|
|
|
|
source omarchy-shell-config
|
|
|
|
usage() {
|
|
cat <<USAGE
|
|
Usage: omarchy bar plugin <command> [args...]
|
|
|
|
add <id> [placement] Add a bar widget
|
|
move <id> [placement] [--from-section S --from-index N]
|
|
Move a widget within/between sections
|
|
remove <id> [placement] (alias: rm, drop)
|
|
Remove a widget
|
|
set <id> <key> <value> [--json] [placement]
|
|
Set a per-widget option
|
|
replace <old-id> <new-id> Replace the first instance of a widget id
|
|
|
|
Placement (for add/move/remove/set):
|
|
--section <left|center|right> Target section
|
|
--index <n> Insert/remove at index in the target section
|
|
--before <id> Insert before the first matching widget
|
|
--after <id> Insert after the first matching widget
|
|
--from-section <section> Source section (move/remove/set)
|
|
--from-index <n> Source index (move/remove/set)
|
|
--duplicate add: allow a second instance of the widget
|
|
--all remove: remove every matching instance
|
|
|
|
Without placement flags, 'add' inserts after the section anchor (workspaces on
|
|
left, weather on center, tray on right) and dedupes across sections; 'move'
|
|
preserves the source section; 'remove' drops the first match (or every match
|
|
with --all).
|
|
|
|
Widget ids are listed by 'omarchy plugin list'.
|
|
|
|
Examples:
|
|
omarchy bar plugin add omarchy.tailscale
|
|
omarchy bar plugin add omarchy.clock center
|
|
omarchy bar plugin move omarchy.media left
|
|
omarchy bar plugin move omarchy.clock --section center --index 0
|
|
omarchy bar plugin remove omarchy.tailscale
|
|
omarchy bar plugin remove omarchy.tailscale --all
|
|
omarchy bar plugin set omarchy.clock format HH:mm
|
|
omarchy bar plugin set omarchy.indicators items '["Dnd","NightLight"]' --json
|
|
USAGE
|
|
}
|
|
|
|
# jq defs used by the mutation pipelines. All `def`s come first so the pipeline
|
|
# that follows them stays valid jq; NORMALIZE adds its own shape helpers.
|
|
JQ_DEFS='
|
|
def entry_id: if type == "object" then (.id // "" | tostring) else tostring end;
|
|
def anchor_for($section): { left: "omarchy.workspaces", center: "omarchy.weather", right: "omarchy.tray" }[$section];
|
|
def find_all($id; $only):
|
|
[ ["left","center","right"][] as $section
|
|
| ((.bar.layout[$section] // []) | to_entries[])
|
|
| select((($only == "" or $section == $only)) and ((.value | entry_id) == $id))
|
|
| {section: $section, index: .key, entry: .value} ];
|
|
def entry_at($section; $index):
|
|
if $index < 0 or $index >= (.bar.layout[$section] | length) then
|
|
error("no widget at " + $section + "[" + ($index | tostring) + "]")
|
|
else .bar.layout[$section][$index] end;
|
|
def resolve_target($defaultSection):
|
|
. as $root
|
|
| (if $before != "" or $after != "" then
|
|
($root | find_all($before + $after; $targetSection) | .[0]
|
|
// error("could not find target widget " + ($before + $after) + ($targetSection | if . == "" then "" else " in " + . end)))
|
|
| .section as $section | (.index + (if $after == "" then 0 else 1 end)) as $index
|
|
| {section: $section, index: $index, anchor: false}
|
|
elif $targetSection != "" or $targetIndex != "" then
|
|
{ section: ($targetSection | if . == "" then $defaultSection else . end),
|
|
index: (if $targetIndex == "" then null else ($targetIndex | tonumber) end),
|
|
anchor: false }
|
|
else
|
|
{ section: $defaultSection, index: null, anchor: true }
|
|
end)
|
|
| .section as $section
|
|
| .index as $rawIndex
|
|
| (if $rawIndex == null then
|
|
if .anchor then
|
|
($root.bar.layout[$section] | map(entry_id) | index(anchor_for($section))) as $anchorIndex
|
|
| if $anchorIndex == null then ($root.bar.layout[$section] | length) else ($anchorIndex + 1) end
|
|
else ($root.bar.layout[$section] | length) end
|
|
elif $rawIndex > ($root.bar.layout[$section] | length) then ($root.bar.layout[$section] | length)
|
|
else $rawIndex end) as $index
|
|
| {section: $section, index: $index};
|
|
def resolve_source:
|
|
if $fromIndex != "" then
|
|
if $fromSection == "" then error("--from-index requires --from-section")
|
|
else
|
|
($fromIndex | tonumber) as $index
|
|
| entry_at($fromSection; $index) as $entry
|
|
| if $widgetId != "" and ($entry | entry_id) != $widgetId then
|
|
error("widget at " + $fromSection + "[" + ($index | tostring) + "] is not " + $widgetId)
|
|
else {section: $fromSection, index: $index, entry: $entry} end
|
|
end
|
|
else
|
|
(find_all($widgetId; $fromSection)
|
|
| if length == 0 then
|
|
error("could not find widget " + $widgetId + ($fromSection | if . == "" then "" else " in " + . end))
|
|
else .[0] end)
|
|
end;
|
|
'
|
|
|
|
validate_section() {
|
|
[[ $1 =~ ^(left|center|right)$ ]] || fail "section must be left, center, or right"
|
|
}
|
|
|
|
validate_index() {
|
|
[[ $1 =~ ^[0-9]+$ ]] || fail "index must be a non-negative integer"
|
|
}
|
|
|
|
widget_is_known() {
|
|
omarchy-plugin-catalog | jq -e --arg id "$1" '
|
|
any(.[]; (.kinds | index("bar-widget")) and .id == $id)
|
|
' >/dev/null
|
|
}
|
|
|
|
# --------------------------------------------------------------------- placement
|
|
|
|
# Parse placement flags shared by add/move/remove/set. Sets the globals below.
|
|
PLACEMENT_SECTION=""
|
|
PLACEMENT_INDEX=""
|
|
PLACEMENT_BEFORE=""
|
|
PLACEMENT_AFTER=""
|
|
PLACEMENT_FROM_SECTION=""
|
|
PLACEMENT_FROM_INDEX=""
|
|
PLACEMENT_DUPLICATE="false"
|
|
PLACEMENT_ALL="false"
|
|
|
|
parse_placement() {
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
--section)
|
|
PLACEMENT_SECTION="${2:-}"
|
|
validate_section "$PLACEMENT_SECTION"
|
|
shift 2
|
|
;;
|
|
--index)
|
|
PLACEMENT_INDEX="${2:-}"
|
|
validate_index "$PLACEMENT_INDEX"
|
|
shift 2
|
|
;;
|
|
--before)
|
|
PLACEMENT_BEFORE="${2:-}"
|
|
[[ -n $PLACEMENT_BEFORE ]] || fail "--before requires a widget id"
|
|
shift 2
|
|
;;
|
|
--after)
|
|
PLACEMENT_AFTER="${2:-}"
|
|
[[ -n $PLACEMENT_AFTER ]] || fail "--after requires a widget id"
|
|
shift 2
|
|
;;
|
|
--from-section)
|
|
PLACEMENT_FROM_SECTION="${2:-}"
|
|
validate_section "$PLACEMENT_FROM_SECTION"
|
|
shift 2
|
|
;;
|
|
--from-index)
|
|
PLACEMENT_FROM_INDEX="${2:-}"
|
|
validate_index "$PLACEMENT_FROM_INDEX"
|
|
shift 2
|
|
;;
|
|
--duplicate)
|
|
PLACEMENT_DUPLICATE="true"
|
|
shift
|
|
;;
|
|
--all)
|
|
PLACEMENT_ALL="true"
|
|
shift
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
fail "unknown option: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
[[ -z $PLACEMENT_BEFORE || -z $PLACEMENT_AFTER ]] || fail "use only one of --before or --after"
|
|
}
|
|
|
|
# --------------------------------------------------------------------- commands
|
|
|
|
cmd_add() {
|
|
local id="${1:-}"
|
|
[[ -n $id ]] || fail "add requires a widget id"
|
|
shift
|
|
|
|
local positional_section=""
|
|
while (( $# > 0 )); do
|
|
if [[ $1 == --* ]]; then
|
|
break
|
|
fi
|
|
[[ -z $positional_section ]] || fail "unexpected argument: $1"
|
|
positional_section="$1"
|
|
shift
|
|
done
|
|
|
|
parse_placement "$@"
|
|
[[ -z $positional_section || -z $PLACEMENT_SECTION ]] || fail "specify a section positionally or with --section, not both"
|
|
[[ -z $positional_section || -z $PLACEMENT_INDEX ]] || fail "specify a section positionally or use --index, not both"
|
|
[[ -z $positional_section || -z $PLACEMENT_BEFORE ]] || fail "specify a section positionally or use --before, not both"
|
|
[[ -z $positional_section || -z $PLACEMENT_AFTER ]] || fail "specify a section positionally or use --after, not both"
|
|
[[ -z $positional_section || ( $PLACEMENT_DUPLICATE == false ) ]] || fail "--duplicate needs explicit placement flags"
|
|
if [[ -n $positional_section ]]; then
|
|
validate_section "$positional_section"
|
|
PLACEMENT_SECTION="$positional_section"
|
|
fi
|
|
|
|
widget_is_known "$id" || fail "$id is not a known bar widget; run 'omarchy plugin list' to see valid ids"
|
|
|
|
local default_section="${PLACEMENT_SECTION:-right}"
|
|
local explicit="false"
|
|
if [[ -n $PLACEMENT_INDEX || -n $PLACEMENT_BEFORE || -n $PLACEMENT_AFTER || $PLACEMENT_DUPLICATE == true ]]; then
|
|
explicit="true"
|
|
fi
|
|
|
|
if [[ $explicit == "false" ]]; then
|
|
# Anchor-based dedupe insert: remove any existing instance across sections,
|
|
# then insert after the section anchor (workspaces/weather/tray) — or append
|
|
# when the anchor is absent.
|
|
commit "$JQ_DEFS $NORMALIZE
|
|
| .bar.layout.left = (.bar.layout.left | map(select(entry_id != \$id)))
|
|
| .bar.layout.center = (.bar.layout.center | map(select(entry_id != \$id)))
|
|
| .bar.layout.right = (.bar.layout.right | map(select(entry_id != \$id)))
|
|
| (.bar.layout[\$section] | map(entry_id) | index(anchor_for(\$section))) as \$anchorIndex
|
|
| .bar.layout[\$section] = (
|
|
if \$anchorIndex == null then .bar.layout[\$section] + [{id: \$id}]
|
|
else .bar.layout[\$section][0:\$anchorIndex+1] + [{id: \$id}] + .bar.layout[\$section][\$anchorIndex+1:] end)
|
|
" \
|
|
--arg id "$id" \
|
|
--arg section "$default_section"
|
|
else
|
|
local prog
|
|
prog=$(cat <<JQ
|
|
$JQ_DEFS $NORMALIZE
|
|
| (.bar.layout.left + .bar.layout.center + .bar.layout.right | map(entry_id) | index(\$id)) as \$existing
|
|
| if \$existing != null and \$allowDuplicate != "true" then
|
|
error(\$id + " is already in the bar; use 'omarchy bar plugin move' or pass --duplicate")
|
|
else . end
|
|
| resolve_target(\$defaultSection) as \$target
|
|
| .bar.layout[\$target.section] = (.bar.layout[\$target.section][0:\$target.index] + [{id: \$id}] + .bar.layout[\$target.section][\$target.index:])
|
|
JQ
|
|
)
|
|
commit "$prog" \
|
|
--arg id "$id" \
|
|
--arg defaultSection "$default_section" \
|
|
--arg targetSection "$PLACEMENT_SECTION" \
|
|
--arg targetIndex "$PLACEMENT_INDEX" \
|
|
--arg before "$PLACEMENT_BEFORE" \
|
|
--arg after "$PLACEMENT_AFTER" \
|
|
--arg allowDuplicate "$PLACEMENT_DUPLICATE"
|
|
fi
|
|
echo "Added $id to the bar"
|
|
}
|
|
|
|
cmd_move() {
|
|
local id="${1:-}"
|
|
[[ -n $id ]] || fail "move requires a widget id"
|
|
shift
|
|
|
|
local positional_section=""
|
|
while (( $# > 0 )); do
|
|
if [[ $1 == --* ]]; then
|
|
break
|
|
fi
|
|
[[ -z $positional_section ]] || fail "unexpected argument: $1"
|
|
positional_section="$1"
|
|
shift
|
|
done
|
|
|
|
parse_placement "$@"
|
|
[[ -z $positional_section || -z $PLACEMENT_SECTION ]] || fail "specify a section positionally or with --section, not both"
|
|
[[ -z $positional_section || -z $PLACEMENT_INDEX ]] || fail "specify a section positionally or use --index, not both"
|
|
[[ -z $positional_section || -z $PLACEMENT_BEFORE ]] || fail "specify a section positionally or use --before, not both"
|
|
[[ -z $positional_section || -z $PLACEMENT_AFTER ]] || fail "specify a section positionally or use --after, not both"
|
|
if [[ -n $positional_section ]]; then
|
|
validate_section "$positional_section"
|
|
PLACEMENT_SECTION="$positional_section"
|
|
fi
|
|
|
|
local default_section="${PLACEMENT_SECTION:-}"
|
|
|
|
# A bare section names the section, not the slot, so let it fall through to
|
|
# the same anchor placement 'add' uses. Passing it as an explicit target
|
|
# instead drops the widget on the far end of the row.
|
|
local target_section="$PLACEMENT_SECTION"
|
|
if [[ -z $PLACEMENT_INDEX && -z $PLACEMENT_BEFORE && -z $PLACEMENT_AFTER ]]; then
|
|
target_section=""
|
|
fi
|
|
|
|
local prog
|
|
prog=$(cat <<JQ
|
|
$JQ_DEFS $NORMALIZE
|
|
| resolve_source as \$source
|
|
| .bar.layout[\$source.section] = (.bar.layout[\$source.section][0:\$source.index] + .bar.layout[\$source.section][\$source.index+1:])
|
|
| (\$defaultSection | if . == "" then \$source.section else . end) as \$fallback
|
|
| resolve_target(\$fallback) as \$target
|
|
| (\$source.entry | if type == "object" then .id = \$id else . end) as \$entry
|
|
| .bar.layout[\$target.section] = (.bar.layout[\$target.section][0:\$target.index] + [\$entry] + .bar.layout[\$target.section][\$target.index:])
|
|
JQ
|
|
)
|
|
commit "$prog" \
|
|
--arg id "$id" \
|
|
--arg defaultSection "$default_section" \
|
|
--arg targetSection "$target_section" \
|
|
--arg targetIndex "$PLACEMENT_INDEX" \
|
|
--arg before "$PLACEMENT_BEFORE" \
|
|
--arg after "$PLACEMENT_AFTER" \
|
|
--arg fromSection "$PLACEMENT_FROM_SECTION" \
|
|
--arg fromIndex "$PLACEMENT_FROM_INDEX" \
|
|
--arg widgetId "$id"
|
|
echo "Moved $id"
|
|
}
|
|
|
|
cmd_remove() {
|
|
local id=""
|
|
if (( $# > 0 )) && [[ $1 != --* ]]; then
|
|
id="$1"
|
|
shift
|
|
fi
|
|
parse_placement "$@"
|
|
|
|
local prog
|
|
prog=$(cat <<JQ
|
|
$JQ_DEFS $NORMALIZE
|
|
| if \$fromIndex != "" then
|
|
resolve_source as \$source
|
|
| .bar.layout[\$source.section] = (.bar.layout[\$source.section][0:\$source.index] + .bar.layout[\$source.section][\$source.index+1:])
|
|
elif \$targetIndex != "" then
|
|
if \$targetSection == "" then error("--index requires --section for remove") else . end
|
|
| (\$targetIndex | tonumber) as \$index
|
|
| .bar.layout[\$targetSection][\$index] as \$entry
|
|
| if \$widgetId != "" and (\$entry | entry_id) != \$widgetId then
|
|
error("widget at " + \$targetSection + "[" + (\$index | tostring) + "] is not " + \$widgetId)
|
|
else . end
|
|
| .bar.layout[\$targetSection] = (.bar.layout[\$targetSection][0:\$index] + .bar.layout[\$targetSection][\$index+1:])
|
|
else
|
|
find_all(\$widgetId; \$targetSection) as \$matches
|
|
| if (\$matches | length) == 0 then
|
|
error("could not find widget " + \$widgetId + (\$targetSection | if . == "" then "" else " in " + . end))
|
|
elif \$removeAll != "true" then
|
|
(\$matches | sort_by(.section) | reverse | .[0]) as \$first
|
|
| .bar.layout[\$first.section] = (.bar.layout[\$first.section][0:\$first.index] + .bar.layout[\$first.section][\$first.index+1:])
|
|
else
|
|
reduce (\$matches | sort_by(.section) | reverse | .[]) as \$m (.;
|
|
.bar.layout[\$m.section] = (.bar.layout[\$m.section][0:\$m.index] + .bar.layout[\$m.section][\$m.index+1:]))
|
|
end
|
|
end
|
|
JQ
|
|
)
|
|
commit "$prog" \
|
|
--arg widgetId "$id" \
|
|
--arg targetSection "$PLACEMENT_SECTION" \
|
|
--arg targetIndex "$PLACEMENT_INDEX" \
|
|
--arg fromSection "$PLACEMENT_FROM_SECTION" \
|
|
--arg fromIndex "$PLACEMENT_FROM_INDEX" \
|
|
--arg removeAll "$PLACEMENT_ALL"
|
|
if [[ -n $id ]]; then
|
|
echo "Removed $id from the bar"
|
|
else
|
|
echo "Removed bar widget"
|
|
fi
|
|
}
|
|
|
|
cmd_set() {
|
|
local id="${1:-}"
|
|
local key="${2:-}"
|
|
local value="${3:-}"
|
|
[[ -n $id ]] || fail "set requires a widget id"
|
|
[[ -n $key ]] || fail "set requires a setting key"
|
|
(( $# >= 3 )) || fail "set requires a value"
|
|
shift 3
|
|
local value_is_json="false"
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
--json) value_is_json="true" ;;
|
|
-h | --help) usage; exit 0 ;;
|
|
*) break ;;
|
|
esac
|
|
shift
|
|
done
|
|
parse_placement "$@"
|
|
|
|
if [[ $value_is_json == "true" ]]; then
|
|
jq -n --argjson value "$value" empty >/dev/null 2>&1 || fail "invalid JSON value: $value"
|
|
fi
|
|
|
|
local value_arg
|
|
if [[ $value_is_json == "true" ]]; then
|
|
value_arg=(--argjson value "$value")
|
|
else
|
|
value_arg=(--arg value "$value")
|
|
fi
|
|
|
|
local prog
|
|
prog=$(cat <<JQ
|
|
$JQ_DEFS $NORMALIZE
|
|
| if \$fromIndex != "" then
|
|
resolve_source as \$source
|
|
| .bar.layout[\$source.section][\$source.index] = (.bar.layout[\$source.section][\$source.index] | if type == "object" then .[\$key] = \$value else error("widget entry must be an object") end)
|
|
elif \$targetIndex != "" then
|
|
if \$targetSection == "" then error("--index requires --section for set") else . end
|
|
| (\$targetIndex | tonumber) as \$index
|
|
| .bar.layout[\$targetSection][\$index] as \$entry
|
|
| if (\$entry | entry_id) != \$widgetId then
|
|
error("widget at " + \$targetSection + "[" + (\$index | tostring) + "] is not " + \$widgetId)
|
|
elif \$entry | type != "object" then
|
|
error("widget entry must be an object")
|
|
else . end
|
|
| .bar.layout[\$targetSection][\$index][\$key] = \$value
|
|
else
|
|
find_all(\$widgetId; \$targetSection) as \$matches
|
|
| if (\$matches | length) == 0 then
|
|
error("could not find widget " + \$widgetId + (\$targetSection | if . == "" then "" else " in " + . end))
|
|
else
|
|
(\$matches | sort_by(.section) | .[0]) as \$first
|
|
| if \$first.entry | type != "object" then error("widget entry must be an object") else . end
|
|
| .bar.layout[\$first.section][\$first.index][\$key] = \$value
|
|
end
|
|
end
|
|
JQ
|
|
)
|
|
commit "$prog" \
|
|
--arg widgetId "$id" \
|
|
--arg key "$key" \
|
|
"${value_arg[@]}" \
|
|
--arg targetSection "$PLACEMENT_SECTION" \
|
|
--arg targetIndex "$PLACEMENT_INDEX" \
|
|
--arg fromSection "$PLACEMENT_FROM_SECTION" \
|
|
--arg fromIndex "$PLACEMENT_FROM_INDEX"
|
|
echo "Set $key on $id"
|
|
}
|
|
|
|
cmd_replace() {
|
|
local old="${1:-}"
|
|
local new="${2:-}"
|
|
[[ -n $old ]] || fail "replace requires the source widget id"
|
|
[[ -n $new ]] || fail "replace requires the replacement widget id"
|
|
(( $# == 2 )) || fail "replace takes two widget ids"
|
|
|
|
local prog
|
|
prog=$(cat <<JQ
|
|
$JQ_DEFS $NORMALIZE
|
|
| resolve_source as \$source
|
|
| if \$source.entry | type != "object" then error("widget entry must be an object") else . end
|
|
| .bar.layout[\$source.section][\$source.index].id = \$new
|
|
JQ
|
|
)
|
|
# resolve_source finds by widget id when no placement is given; replace
|
|
# takes no placement flags, so pass empty placement explicitly.
|
|
commit "$prog" \
|
|
--arg widgetId "$old" \
|
|
--arg new "$new" \
|
|
--arg fromSection "" \
|
|
--arg fromIndex ""
|
|
echo "Replaced $old with $new"
|
|
}
|
|
|
|
# --------------------------------------------------------------------- dispatch
|
|
|
|
command="${1:-}"
|
|
(( $# > 0 )) && shift || true
|
|
|
|
case "$command" in
|
|
add)
|
|
cmd_add "$@"
|
|
;;
|
|
move)
|
|
cmd_move "$@"
|
|
;;
|
|
remove | rm | drop)
|
|
cmd_remove "$@"
|
|
;;
|
|
set)
|
|
cmd_set "$@"
|
|
;;
|
|
replace)
|
|
cmd_replace "$@"
|
|
;;
|
|
-h | --help | help | "")
|
|
usage
|
|
;;
|
|
*)
|
|
fail "unknown command: $command"
|
|
;;
|
|
esac
|