Plugin marketplace: omarchyplugins.com catalog, Plugin Manager/Library windows, registry.json with bundled plugins
swadhinbiswas committed 2 months ago
12362d0
1 parent 5255918
commit 12362d0
11 files changed
+5067 -225
Showing 11 changed files+5067-225
bin/omarchy-plugin-addbin/omarchy-plugin-librarybin/omarchy-plugin-managerbin/omarchy-plugin-removebin/omarchy-pluginsplugin-library/shell.qmlregistry.jsonshell/plugins/plugin-library/Panel.qmlshell/plugins/plugin-manager/BarWidget.qmlshell/plugins/plugin-manager/Panel.qmlshell/plugins/plugin-manager/manifest.json
▼bin/omarchy-plugin-add
+1-1
| @@ -139,7 +139,7 @@ fi | ||
139 | 139 | mv "$stage" "$target" |
140 | 140 | echo "Added $id into $target" |
141 | 141 | |
142 | -omarchy-shell shell rescanPlugins >/dev/null | |
142 | +omarchy-shell shell rescanPlugins >/dev/null 2>&1 || true | |
143 | 143 | |
144 | 144 | if [[ -z $enable_after ]]; then |
145 | 145 | if (( ASSUME_YES )) || ! interactive; then |
146 | 146 | |
▼bin/omarchy-plugin-library
+7-0
| @@ -0,0 +1,7 @@ | ||
1 | +#!/bin/bash | |
2 | + | |
3 | +# omarchy:summary=Open the Plugin Library and Marketplace window | |
4 | +# omarchy:group=plugin | |
5 | +# omarchy:alias=omarchy plugin library | |
6 | + | |
7 | +exec "${OMARCHY_PATH:-/usr/share/de-omarchy}/bin/omarchy-plugin-manager" "$@" | |
0 | 8 | |
▼bin/omarchy-plugin-manager
+79-0
| @@ -0,0 +1,79 @@ | ||
1 | +#!/bin/bash | |
2 | + | |
3 | +# omarchy:summary=Open or toggle the graphical Plugin Manager window | |
4 | +# omarchy:group=plugin | |
5 | +# omarchy:alias=omarchy plugin manager | |
6 | + | |
7 | +LOCKDIR="/tmp/omarchy-plugin-manager.lock" | |
8 | +if ! mkdir "$LOCKDIR" 2>/dev/null; then | |
9 | + lock_age=$(( $(date +%s) - $(stat -c %Y "$LOCKDIR" 2>/dev/null || echo 0) )) | |
10 | + if (( lock_age > 3 )); then | |
11 | + rm -rf "$LOCKDIR" | |
12 | + mkdir "$LOCKDIR" 2>/dev/null || exit 0 | |
13 | + else | |
14 | + exit 0 | |
15 | + fi | |
16 | +fi | |
17 | +trap 'rm -rf "$LOCKDIR"' EXIT | |
18 | + | |
19 | +# The installed runtime is authoritative — never reference the dev checkout | |
20 | +# here; an installed desktop must not depend on (or prefer) a home git clone. | |
21 | +PATH_CANDIDATES=( | |
22 | + "${OMARCHY_PATH:-/usr/share/de-omarchy}/plugin-library" | |
23 | + "/usr/share/de-omarchy/plugin-library" | |
24 | +) | |
25 | + | |
26 | +APP_PATH="" | |
27 | +for p in "${PATH_CANDIDATES[@]}"; do | |
28 | + if [[ -f "$p/shell.qml" ]]; then | |
29 | + APP_PATH="$p" | |
30 | + break | |
31 | + fi | |
32 | +done | |
33 | + | |
34 | +if [[ -z $APP_PATH ]]; then | |
35 | + echo "Plugin manager QML path not found!" >&2 | |
36 | + exit 1 | |
37 | +fi | |
38 | + | |
39 | +# Check if the Plugin Manager window already exists in Hyprland | |
40 | +existing_window=$(hyprctl clients -j 2>/dev/null | jq -r '.[] | select((.title | test("^(Omarchy Plugin Manager|Plugin Library)$")) or (.class == "org.quickshell" and (.title | test("Plugin")))) | .address' | head -n 1) | |
41 | +active_window=$(hyprctl activewindow -j 2>/dev/null | jq -r '.address // empty') | |
42 | +current_ws=$(hyprctl activeworkspace -j 2>/dev/null | jq -r '.id // "1"') | |
43 | + | |
44 | +if [[ -n "$existing_window" ]]; then | |
45 | + if [[ "$existing_window" == "$active_window" ]]; then | |
46 | + # Toggle off if currently focused | |
47 | + hyprctl dispatch "hl.dsp.window.close({ window = \"address:$existing_window\" })" >/dev/null 2>&1 || true | |
48 | + pkill -f "quickshell.*plugin-library" 2>/dev/null || true | |
49 | + else | |
50 | + # Move to current workspace if needed, focus and raise | |
51 | + hyprctl dispatch "hl.dsp.window.move({ workspace = \"$current_ws\", window = \"address:$existing_window\" })" >/dev/null 2>&1 || true | |
52 | + hyprctl dispatch "hl.dsp.focus({ window = \"address:$existing_window\" })" >/dev/null 2>&1 || true | |
53 | + fi | |
54 | + exit 0 | |
55 | +fi | |
56 | + | |
57 | +# Clean up any stale quickshell plugin-library instances | |
58 | +pkill -f "quickshell.*plugin-library" 2>/dev/null || true | |
59 | +sleep 0.05 | |
60 | + | |
61 | +# Launch detached | |
62 | +if command -v uwsm-app >/dev/null 2>&1; then | |
63 | + uwsm-app -- quickshell -p "$APP_PATH" >/dev/null 2>&1 & | |
64 | +else | |
65 | + setsid quickshell -p "$APP_PATH" >/dev/null 2>&1 & | |
66 | +fi | |
67 | + | |
68 | +# Ensure window appears on current workspace and gets focused | |
69 | +( | |
70 | + for (( i = 0; i < 30; i++ )); do | |
71 | + win_addr=$(hyprctl clients -j 2>/dev/null | jq -r '.[] | select((.title | test("^(Omarchy Plugin Manager|Plugin Library)$")) or (.class == "org.quickshell" and (.title | test("Plugin")))) | .address' | head -n 1) | |
72 | + if [[ -n "$win_addr" ]]; then | |
73 | + hyprctl dispatch "hl.dsp.window.move({ workspace = \"$current_ws\", window = \"address:$win_addr\" })" >/dev/null 2>&1 || true | |
74 | + hyprctl dispatch "hl.dsp.focus({ window = \"address:$win_addr\" })" >/dev/null 2>&1 || true | |
75 | + break | |
76 | + fi | |
77 | + sleep 0.05 | |
78 | + done | |
79 | +) & | |
0 | 80 | |
▼bin/omarchy-plugin-remove
+5-5
| @@ -72,16 +72,16 @@ target="$PLUGINS_DIR/$id" | ||
72 | 72 | [[ -e $target || -L $target ]] || fail "plugin '$id' is not installed" |
73 | 73 | |
74 | 74 | was_enabled="" |
75 | -shell_plugins=$(omarchy-shell shell listPlugins) | |
75 | +shell_plugins=$(omarchy-shell shell listPlugins 2>/dev/null || true) | |
76 | 76 | if [[ -n $shell_plugins ]]; then |
77 | 77 | was_enabled=$(jq -r --arg id "$id" ' |
78 | 78 | .[] | select(.id == $id) | .enabled |
79 | - ' <<<"$shell_plugins") | |
79 | + ' <<<"$shell_plugins" 2>/dev/null || true) | |
80 | 80 | fi |
81 | 81 | |
82 | 82 | cloned_from="" |
83 | 83 | if [[ -f $target/manifest.json ]]; then |
84 | - cloned_from=$(jq -r '.omarchy.clonedFrom // empty' "$target/manifest.json") | |
84 | + cloned_from=$(jq -r '.omarchy.clonedFrom // empty' "$target/manifest.json" 2>/dev/null || true) | |
85 | 85 | fi |
86 | 86 | |
87 | 87 | if [[ -L $target ]]; then |
| @@ -93,7 +93,7 @@ else | ||
93 | 93 | fi |
94 | 94 | |
95 | 95 | if [[ $was_enabled == "true" ]]; then |
96 | - omarchy-shell shell setPluginEnabled "$id" false >/dev/null | |
96 | + omarchy-shell shell setPluginEnabled "$id" false >/dev/null 2>&1 || true | |
97 | 97 | fi |
98 | 98 | |
99 | 99 | if [[ -L $target ]]; then |
| @@ -114,7 +114,7 @@ else | ||
114 | 114 | echo "Removed $id. Backup at: $backup" |
115 | 115 | fi |
116 | 116 | |
117 | -omarchy-shell shell rescanPlugins >/dev/null | |
117 | +omarchy-shell shell rescanPlugins >/dev/null 2>&1 || true | |
118 | 118 | |
119 | 119 | if [[ -n $cloned_from && $was_enabled == "true" ]]; then |
120 | 120 | echo "Restored $cloned_from." |
121 | 121 | |
▼bin/omarchy-plugins
+482-77
| @@ -1,87 +1,492 @@ | ||
1 | 1 | #!/bin/bash |
2 | -# omarchy-plugins — browse and install plugins from the de-omarchy registry | |
3 | -# Usage: omarchy-plugins [list|install|search|info] [plugin-name] | |
4 | -set -euo pipefail | |
5 | - | |
6 | -REGISTRY="${OMARCHY_PATH:-/usr/share/de-omarchy}/registry.json" | |
7 | -PLUGIN_DIR="$HOME/.config/omarchy/plugins" | |
8 | - | |
9 | -ACTION="${1:-list}" | |
10 | -QUERY="${2:-}" | |
11 | - | |
12 | -log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; } | |
13 | -err() { printf '\033[1;31mERR:\033[0m %s\n' "$*" >&2; exit 1; } | |
14 | - | |
15 | -[[ -f "$REGISTRY" ]] || err "Registry not found at $REGISTRY" | |
16 | - | |
17 | -list_plugins() { | |
18 | - python3 -c " | |
19 | -import json, sys | |
20 | -with open('$REGISTRY') as f: r = json.load(f) | |
21 | -for p in r.get('plugins', []): | |
22 | - installed = '$PLUGIN_DIR/' + p['id'].split('.')[-1] | |
23 | - import os | |
24 | - status = 'installed' if os.path.isdir(installed) else 'available' | |
25 | - print(f\" {p['id']:40s} {p['category']:12s} {p['status']:8s} [{status}]\") | |
26 | -" | |
2 | + | |
3 | +# omarchy:summary=Manage installed plugins and browse the Omarchy plugin marketplace | |
4 | +# omarchy:group=plugin | |
5 | +# omarchy:args=[list|marketplace|install|remove|enable|disable|update|gui] [args...] | |
6 | +# omarchy:examples=omarchy plugins list | omarchy plugins marketplace | omarchy plugins install game-of-life | omarchy plugins enable omarchy.dropbox | |
7 | + | |
8 | +set -eo pipefail | |
9 | + | |
10 | +OMARCHY_PATH="${OMARCHY_PATH:-/usr/share/de-omarchy}" | |
11 | +PLUGINS_DIR="$HOME/.config/omarchy/plugins" | |
12 | +CACHE_DIR="$HOME/.cache/omarchy" | |
13 | +MARKETPLACE_CACHE="$CACHE_DIR/marketplace_catalog.json" | |
14 | +MARKETPLACE_URL="https://omarchyplugins.com/catalog.json" | |
15 | +REGISTRY_FILE="$OMARCHY_PATH/registry.json" | |
16 | + | |
17 | +mkdir -p "$PLUGINS_DIR" "$CACHE_DIR" | |
18 | + | |
19 | +log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; } | |
20 | +warn() { printf '\033[1;33m ->\033[0m %s\n' "$*"; } | |
21 | +err() { printf '\033[1;31mERROR:\033[0m %s\n' "$*" >&2; exit 1; } | |
22 | + | |
23 | +ensure_marketplace_catalog() { | |
24 | + local force="${1:-false}" | |
25 | + if [[ $force == "true" || ! -f "$MARKETPLACE_CACHE" || $(find "$MARKETPLACE_CACHE" -mmin +1440 2>/dev/null) ]]; then | |
26 | + log "Fetching latest marketplace catalog from $MARKETPLACE_URL..." | |
27 | + if curl -sL --connect-timeout 5 --max-time 15 "$MARKETPLACE_URL" -o "$MARKETPLACE_CACHE.tmp" 2>/dev/null; then | |
28 | + if jq -e '.plugins | length > 0' "$MARKETPLACE_CACHE.tmp" >/dev/null 2>&1; then | |
29 | + mv "$MARKETPLACE_CACHE.tmp" "$MARKETPLACE_CACHE" | |
30 | + else | |
31 | + rm -f "$MARKETPLACE_CACHE.tmp" | |
32 | + fi | |
33 | + else | |
34 | + rm -f "$MARKETPLACE_CACHE.tmp" | |
35 | + fi | |
36 | + fi | |
37 | + | |
38 | + if [[ ! -f "$MARKETPLACE_CACHE" ]]; then | |
39 | + if [[ -f "$REGISTRY_FILE" ]]; then | |
40 | + cp "$REGISTRY_FILE" "$MARKETPLACE_CACHE" | |
41 | + else | |
42 | + echo '{"plugins":[]}' > "$MARKETPLACE_CACHE" | |
43 | + fi | |
44 | + fi | |
27 | 45 | } |
28 | 46 | |
29 | -search_plugins() { | |
30 | - python3 -c " | |
31 | -import json, sys | |
32 | -with open('$REGISTRY') as f: r = json.load(f) | |
33 | -q = '$QUERY'.lower() | |
34 | -for p in r.get('plugins', []): | |
35 | - searchable = (p['name'] + ' ' + p['description'] + ' ' + ' '.join(p.get('tags', []))).lower() | |
36 | - if q in searchable: | |
37 | - print(f\" {p['id']:40s} {p['name']}\") | |
38 | - print(f\" {p['description']}\") | |
39 | - print() | |
40 | -" | |
47 | +cmd_list() { | |
48 | + local json="false" | |
49 | + while (( $# > 0 )); do | |
50 | + case "$1" in | |
51 | + --json) json="true" ;; | |
52 | + *) ;; | |
53 | + esac | |
54 | + shift | |
55 | + done | |
56 | + | |
57 | + local shell_json | |
58 | + shell_json=$(omarchy-shell shell listPlugins 2>/dev/null || true) | |
59 | + | |
60 | + if [[ -z $shell_json || $shell_json == "null" || $shell_json == *"error"* ]]; then | |
61 | + # Fallback to scanning catalog directly | |
62 | + shell_json=$(omarchy-plugin-catalog 2>/dev/null | jq '[.[] | {id: .id, name: .name, kinds: .kinds, enabled: false, firstParty: .firstParty}]' 2>/dev/null || echo "[]") | |
63 | + fi | |
64 | + | |
65 | + if [[ $json == "true" ]]; then | |
66 | + printf '%s\n' "$shell_json" | |
67 | + return 0 | |
68 | + fi | |
69 | + | |
70 | + jq -r ' | |
71 | + .[] | | |
72 | + [ | |
73 | + .id, | |
74 | + (if .enabled then "active" else "inactive" end), | |
75 | + (if .firstParty then "built-in" else "user" end), | |
76 | + ((.kinds // []) | join(",")), | |
77 | + (.name // .id) | |
78 | + ] | @tsv | |
79 | + ' <<<"$shell_json" | awk -F '\t' ' | |
80 | + BEGIN { printf "\033[1m%-34s %-10s %-10s %-18s %s\033[0m\n", "ID", "STATE", "SOURCE", "KINDS", "NAME" } | |
81 | + { | |
82 | + state_color = ($2 == "active" ? "\033[32m" : "\033[90m") | |
83 | + src_color = ($3 == "built-in" ? "\033[36m" : "\033[33m") | |
84 | + printf "%-34s %s%-10s\033[0m %s%-10s\033[0m %-18s %s\n", $1, state_color, $2, src_color, $3, $4, $5 | |
85 | + } | |
86 | + ' | |
41 | 87 | } |
42 | 88 | |
43 | -show_info() { | |
44 | - python3 -c " | |
45 | -import json | |
46 | -with open('$REGISTRY') as f: r = json.load(f) | |
47 | -for p in r.get('plugins', []): | |
48 | - if p['id'] == '$QUERY' or p['name'].lower() == '$QUERY'.lower(): | |
49 | - print(f\"Name: {p['name']}\") | |
50 | - print(f\"ID: {p['id']}\") | |
51 | - print(f\"Description: {p['description']}\") | |
52 | - print(f\"Author: {p['author']}\") | |
53 | - print(f\"Category: {p['category']}\") | |
54 | - print(f\"Tags: {', '.join(p.get('tags', []))}\") | |
55 | - print(f\"Status: {p['status']}\") | |
56 | - print(f\"Kind: {p['kind']}\") | |
57 | - print(f\"Install: {p['installCommand']}\") | |
58 | - exit(0) | |
59 | -print('Plugin not found: $QUERY') | |
60 | -" | |
89 | +cmd_marketplace() { | |
90 | + local query="" | |
91 | + local category="" | |
92 | + local json="false" | |
93 | + local force="false" | |
94 | + | |
95 | + while (( $# > 0 )); do | |
96 | + case "$1" in | |
97 | + --fetch|-f) force="true" ;; | |
98 | + --json) json="true" ;; | |
99 | + --query|-q) query="${2:-}"; shift ;; | |
100 | + --category|-c) category="${2:-}"; shift ;; | |
101 | + *) [[ -z $query ]] && query="$1" ;; | |
102 | + esac | |
103 | + shift | |
104 | + done | |
105 | + | |
106 | + ensure_marketplace_catalog "$force" | |
107 | + | |
108 | + local filtered | |
109 | + filtered=$(jq \ | |
110 | + --arg q "$query" \ | |
111 | + --arg cat "$category" ' | |
112 | + .plugins | |
113 | + | map(select( | |
114 | + ($cat == "" or (.category // "" | ascii_downcase) == ($cat | ascii_downcase)) and | |
115 | + ($q == "" or | |
116 | + (.name // "" | ascii_downcase | contains($q | ascii_downcase)) or | |
117 | + (.id // "" | ascii_downcase | contains($q | ascii_downcase)) or | |
118 | + (.description // "" | ascii_downcase | contains($q | ascii_downcase)) or | |
119 | + ((.tags // []) | join(" ") | ascii_downcase | contains($q | ascii_downcase)) or | |
120 | + (.author // "" | ascii_downcase | contains($q | ascii_downcase)) | |
121 | + ) | |
122 | + )) | |
123 | + ' "$MARKETPLACE_CACHE") | |
124 | + | |
125 | + if [[ $json == "true" ]]; then | |
126 | + printf '%s\n' "$filtered" | |
127 | + return 0 | |
128 | + fi | |
129 | + | |
130 | + local count | |
131 | + count=$(jq 'length' <<<"$filtered") | |
132 | + log "Marketplace catalog: $count matching plugins" | |
133 | + echo "" | |
134 | + | |
135 | + jq -r ' | |
136 | + .[] | | |
137 | + [ | |
138 | + (.name // .id | gsub("\t|\n"; " ")), | |
139 | + (.category // "Misc" | gsub("\t|\n"; " ")), | |
140 | + (.kind // "Plugin" | gsub("\t|\n"; " ")), | |
141 | + (if ((.stars // 0) > 0) then ("★ " + ((.stars // 0)|tostring)) else "-" end), | |
142 | + .id, | |
143 | + (.description // "-" | gsub("\t|\n"; " ")) | |
144 | + ] | @tsv | |
145 | + ' <<<"$filtered" | while IFS=$'\t' read -r name cat kind stars id desc; do | |
146 | + printf ' \033[1;35m%-32s\033[0m \033[36m[%s]\033[0m \033[33m%s\033[0m \033[32m%s\033[0m\n' "$name" "$cat" "$kind" "$stars" | |
147 | + printf ' \033[90mID:\033[0m %-30s \033[90m%s\033[0m\n' "$id" "$desc" | |
148 | + echo "" | |
149 | + done | |
61 | 150 | } |
62 | 151 | |
63 | -install_plugin() { | |
64 | - local cmd | |
65 | - cmd=$(python3 -c " | |
66 | -import json | |
67 | -with open('$REGISTRY') as f: r = json.load(f) | |
68 | -for p in r.get('plugins', []): | |
69 | - if p['id'] == '$QUERY' or p['name'].lower() == '$QUERY'.lower(): | |
70 | - print(p['installCommand']) | |
71 | - exit(0) | |
72 | -print('NOT_FOUND') | |
73 | -") | |
74 | - [[ "$cmd" == "NOT_FOUND" ]] && err "Plugin not found: $QUERY" | |
75 | - log "Installing: $QUERY" | |
76 | - mkdir -p "$PLUGIN_DIR" | |
77 | - eval "$cmd" | |
78 | - log "Done. Restart the shell to load: pkill -x quickshell" | |
152 | +cmd_install() { | |
153 | + local target="${1:-}" | |
154 | + local enable_after=false | |
155 | + shift || true | |
156 | + | |
157 | + while (( $# > 0 )); do | |
158 | + case "$1" in | |
159 | + --enable|-e) enable_after=true ;; | |
160 | + *) ;; | |
161 | + esac | |
162 | + shift | |
163 | + done | |
164 | + | |
165 | + [[ -n $target ]] || err "Usage: omarchy plugins install <plugin-id-or-git-url> [--enable]" | |
166 | + | |
167 | + # 1. Direct Git URL | |
168 | + if [[ $target =~ ^https?:// || $target =~ ^git@ ]]; then | |
169 | + log "Installing plugin from Git URL: $target" | |
170 | + if [[ $enable_after == "true" ]]; then | |
171 | + "$OMARCHY_PATH/bin/omarchy-plugin-add" "$target" --yes --enable | |
172 | + else | |
173 | + "$OMARCHY_PATH/bin/omarchy-plugin-add" "$target" --yes | |
174 | + fi | |
175 | + return 0 | |
176 | + fi | |
177 | + | |
178 | + # 2. Marketplace / Catalog ID | |
179 | + ensure_marketplace_catalog false | |
180 | + local plugin_info | |
181 | + plugin_info=$(jq -c --arg id "$target" '[.plugins[] | select(.id == $id or (.name | ascii_downcase) == ($id | ascii_downcase))][0] // empty' "$MARKETPLACE_CACHE") | |
182 | + | |
183 | + if [[ -z $plugin_info || $plugin_info == "null" ]]; then | |
184 | + # Try fetching fresh | |
185 | + ensure_marketplace_catalog true | |
186 | + plugin_info=$(jq -c --arg id "$target" '[.plugins[] | select(.id == $id or (.name | ascii_downcase) == ($id | ascii_downcase))][0] // empty' "$MARKETPLACE_CACHE") | |
187 | + fi | |
188 | + | |
189 | + [[ -n $plugin_info && $plugin_info != "null" ]] || err "Plugin '$target' not found in marketplace catalog." | |
190 | + | |
191 | + local p_id p_name p_repo p_cmd p_manifest p_kind | |
192 | + p_id=$(jq -r '.id' <<<"$plugin_info") | |
193 | + p_name=$(jq -r '.name // .id' <<<"$plugin_info") | |
194 | + p_repo=$(jq -r '.repo // empty' <<<"$plugin_info") | |
195 | + p_cmd=$(jq -r '.installCommand // empty' <<<"$plugin_info") | |
196 | + p_manifest=$(jq -r '.manifestPath // empty' <<<"$plugin_info") | |
197 | + p_kind=$(jq -r '.kind // "plugin"' <<<"$plugin_info") | |
198 | + | |
199 | + log "Installing '$p_name' ($p_id)..." | |
200 | + | |
201 | + # If an explicit installCommand is provided, use it | |
202 | + if [[ -n $p_cmd && $p_cmd != "null" && $p_cmd != "" ]]; then | |
203 | + if [[ $p_cmd != *"--yes"* ]]; then | |
204 | + p_cmd="$p_cmd --yes" | |
205 | + fi | |
206 | + log "Executing install command: $p_cmd" | |
207 | + mkdir -p "$PLUGINS_DIR" | |
208 | + eval "$p_cmd" | |
209 | + # If monorepo manifestPath is specified (e.g. omni/manifest.json) | |
210 | + elif [[ -n $p_manifest && -n $p_repo ]]; then | |
211 | + local subfolder="${p_manifest%/*}" | |
212 | + local tmp_stage="$PLUGINS_DIR/.stage.$p_id.$$" | |
213 | + rm -rf "$tmp_stage" | |
214 | + log "Cloning repository $p_repo..." | |
215 | + git clone --depth 1 "$p_repo" "$tmp_stage" | |
216 | + | |
217 | + local source_dir="$tmp_stage" | |
218 | + [[ -n $subfolder && -d "$tmp_stage/$subfolder" ]] && source_dir="$tmp_stage/$subfolder" | |
219 | + | |
220 | + if [[ ! -f "$source_dir/manifest.json" ]]; then | |
221 | + rm -rf "$tmp_stage" | |
222 | + err "Could not find manifest.json inside $source_dir" | |
223 | + fi | |
224 | + | |
225 | + local target_dir="$PLUGINS_DIR/$p_id" | |
226 | + rm -rf "$target_dir" | |
227 | + mkdir -p "$target_dir" | |
228 | + cp -r "$source_dir"/* "$target_dir"/ | |
229 | + rm -rf "$tmp_stage" | |
230 | + # Otherwise standard repo | |
231 | + elif [[ -n $p_repo ]]; then | |
232 | + "$OMARCHY_PATH/bin/omarchy-plugin-add" "$p_repo" --yes | |
233 | + else | |
234 | + err "No install command or repository available for '$p_id'." | |
235 | + fi | |
236 | + | |
237 | + # Rescan shell plugins | |
238 | + omarchy-shell shell rescanPlugins >/dev/null 2>&1 || true | |
239 | + | |
240 | + if [[ $enable_after == "true" ]]; then | |
241 | + log "Enabling $p_id..." | |
242 | + "$OMARCHY_PATH/bin/omarchy-plugin-enable" "$p_id" 2>/dev/null || true | |
243 | + fi | |
244 | + | |
245 | + log "Successfully installed $p_name ($p_id)!" | |
246 | +} | |
247 | + | |
248 | +cmd_remove() { | |
249 | + local id="${1:-}" | |
250 | + [[ -n $id ]] || err "Usage: omarchy plugins remove <plugin-id>" | |
251 | + "$OMARCHY_PATH/bin/omarchy-plugin-remove" "$id" --yes | |
252 | +} | |
253 | + | |
254 | +cmd_enable() { | |
255 | + local id="${1:-}" | |
256 | + [[ -n $id ]] || err "Usage: omarchy plugins enable <plugin-id> [placement]" | |
257 | + shift | |
258 | + "$OMARCHY_PATH/bin/omarchy-plugin-enable" "$id" "$@" | |
259 | +} | |
260 | + | |
261 | +cmd_disable() { | |
262 | + local id="${1:-}" | |
263 | + [[ -n $id ]] || err "Usage: omarchy plugins disable <plugin-id>" | |
264 | + "$OMARCHY_PATH/bin/omarchy-plugin-disable" "$id" | |
265 | +} | |
266 | + | |
267 | +cmd_update() { | |
268 | + "$OMARCHY_PATH/bin/omarchy-plugin-update" "$@" | |
269 | +} | |
270 | + | |
271 | +cmd_check_updates() { | |
272 | + local count=0 | |
273 | + log "Checking git-managed plugins for remote updates..." | |
274 | + for dir in "$PLUGINS_DIR"/*/; do | |
275 | + [[ -d "$dir/.git" ]] || continue | |
276 | + local p_id | |
277 | + p_id=$(basename "$dir") | |
278 | + if git -C "$dir" fetch --quiet origin HEAD 2>/dev/null; then | |
279 | + local local_rev remote_rev | |
280 | + local_rev=$(git -C "$dir" rev-parse HEAD 2>/dev/null || echo "1") | |
281 | + remote_rev=$(git -C "$dir" rev-parse FETCH_HEAD 2>/dev/null || echo "2") | |
282 | + if [[ $local_rev != "$remote_rev" ]]; then | |
283 | + printf ' \033[1;33mUpdate available:\033[0m %s\n' "$p_id" | |
284 | + (( count++ )) || true | |
285 | + fi | |
286 | + fi | |
287 | + done | |
288 | + if (( count == 0 )); then | |
289 | + log "All installed plugins are up to date." | |
290 | + else | |
291 | + log "$count plugin(s) can be updated with: omarchy plugins update" | |
292 | + fi | |
293 | +} | |
294 | + | |
295 | +cmd_shortcut() { | |
296 | + local sub="${1:-list}" | |
297 | + shift || true | |
298 | + local keymap_file="$HOME/.config/hypr/keymap.lua" | |
299 | + | |
300 | + case "$sub" in | |
301 | + list) | |
302 | + local json="false" | |
303 | + [[ "${1:-}" == "--json" ]] && json="true" | |
304 | + local list="[]" | |
305 | + if [[ -f "$keymap_file" ]]; then | |
306 | + local raw | |
307 | + raw=$(grep -E 'omarchy-shell shell (summon|toggle)' "$keymap_file" 2>/dev/null || true) | |
308 | + if [[ -n "$raw" ]]; then | |
309 | + list=$(echo "$raw" | while read -r line; do | |
310 | + [[ -z "$line" ]] && continue | |
311 | + keys=$(echo "$line" | sed -n 's/.*keys *= *"*\([^",]*\)"*.*/\1/p') | |
312 | + cmd=$(echo "$line" | sed -n 's/.*dsp *= *exec("\([^"]*\)").*/\1/p') | |
313 | + desc=$(echo "$line" | sed -n 's/.*desc *= *"*\([^",]*\)"*.*/\1/p') | |
314 | + id=$(echo "$cmd" | awk '{print $NF}') | |
315 | + jq -cn --arg id "$id" --arg keys "$keys" --arg desc "$desc" --arg cmd "$cmd" '{id: $id, keys: $keys, desc: $desc, cmd: $cmd}' | |
316 | + done | jq -s '.') | |
317 | + fi | |
318 | + fi | |
319 | + | |
320 | + if [[ $json == "true" ]]; then | |
321 | + echo "$list" | |
322 | + else | |
323 | + echo "PLUGIN ID KEYBIND DESCRIPTION" | |
324 | + echo "------------------------------------------------------------------" | |
325 | + jq -r '.[] | [ .id, .keys, .desc ] | @tsv' <<<"$list" | while IFS=$'\t' read -r id keys desc; do | |
326 | + printf '%-36s %-16s %s\n' "$id" "$keys" "$desc" | |
327 | + done | |
328 | + fi | |
329 | + ;; | |
330 | + get) | |
331 | + local p_id="${1:-}" | |
332 | + [[ -n $p_id ]] || err "Plugin ID required" | |
333 | + local found="" | |
334 | + if [[ -f "$keymap_file" ]]; then | |
335 | + found=$(grep -F "$p_id" "$keymap_file" 2>/dev/null | grep -v 'omarchy-plugin-manager' 2>/dev/null | sed -n 's/.*keys *= *"*\([^",]*\)"*.*/\1/p' | head -n 1 || true) | |
336 | + fi | |
337 | + echo "$found" | |
338 | + ;; | |
339 | + set) | |
340 | + local p_id="${1:-}" | |
341 | + local keys="${2:-}" | |
342 | + [[ -n $p_id ]] || err "Plugin ID required" | |
343 | + [[ -n $keys ]] || err "Key sequence required (e.g. 'SUPER + ALT + D')" | |
344 | + | |
345 | + if [[ -f "$keymap_file" ]]; then | |
346 | + grep -v -F "$p_id" "$keymap_file" > "$keymap_file.tmp" || true | |
347 | + mv "$keymap_file.tmp" "$keymap_file" | |
348 | + fi | |
349 | + | |
350 | + keys=$(echo "$keys" | tr '[:lower:]' '[:upper:]' | sed 's/+/ + /g' | sed 's/ */ /g' | sed 's/^ *//;s/ *$//') | |
351 | + local plugin_name="$p_id" | |
352 | + local entry="k[#k + 1] = { cat = \"Plugins\", keys = \"$keys\", dsp = exec(\"omarchy-shell shell toggle $p_id\"), desc = \"$plugin_name\" }" | |
353 | + | |
354 | + if grep -q "return k" "$keymap_file"; then | |
355 | + sed -i "/return k/i $entry" "$keymap_file" | |
356 | + else | |
357 | + echo "$entry" >> "$keymap_file" | |
358 | + echo "return k" >> "$keymap_file" | |
359 | + fi | |
360 | + | |
361 | + log "Set shortcut for '$p_id' to: $keys" | |
362 | + hyprctl reload >/dev/null 2>&1 || true | |
363 | + ;; | |
364 | + remove|clear|unset) | |
365 | + local p_id="${1:-}" | |
366 | + [[ -n $p_id ]] || err "Plugin ID required" | |
367 | + if [[ -f "$keymap_file" ]]; then | |
368 | + grep -v -F "$p_id" "$keymap_file" > "$keymap_file.tmp" || true | |
369 | + mv "$keymap_file.tmp" "$keymap_file" | |
370 | + hyprctl reload >/dev/null 2>&1 || true | |
371 | + fi | |
372 | + log "Cleared shortcut for '$p_id'" | |
373 | + ;; | |
374 | + esac | |
375 | +} | |
376 | + | |
377 | +cmd_bar() { | |
378 | + local sub="${1:-list}" | |
379 | + shift || true | |
380 | + local shell_cfg="$HOME/.config/omarchy/shell.json" | |
381 | + | |
382 | + case "$sub" in | |
383 | + list) | |
384 | + if [[ ! -f "$shell_cfg" ]]; then | |
385 | + echo '{"inBar":[]}' | |
386 | + return | |
387 | + fi | |
388 | + jq -c '{ | |
389 | + inBar: (([.bar.layout.left[]?.id, .bar.layout.center[]?.id, .bar.layout.right[]?.id] | unique) // []), | |
390 | + layout: (.bar.layout // {left:[], center:[], right:[]}) | |
391 | + }' "$shell_cfg" 2>/dev/null || echo '{"inBar":[]}' | |
392 | + ;; | |
393 | + add) | |
394 | + local p_id="${1:-}" | |
395 | + local section="${2:-right}" | |
396 | + [[ -n $p_id ]] || err "Plugin ID required" | |
397 | + [[ $section =~ ^(left|center|right)$ ]] || section="right" | |
398 | + | |
399 | + if [[ -f "$shell_cfg" ]]; then | |
400 | + local updated | |
401 | + updated=$(jq --arg id "$p_id" --arg sec "$section" ' | |
402 | + if (([.bar.layout.left[]?.id, .bar.layout.center[]?.id, .bar.layout.right[]?.id] | index($id)) == null) then | |
403 | + .bar.layout[$sec] += [{"id": $id}] | |
404 | + else | |
405 | + . | |
406 | + end | |
407 | + ' "$shell_cfg") | |
408 | + echo "$updated" > "$shell_cfg" | |
409 | + fi | |
410 | + omarchy-shell shell enablePlugin "$p_id" "{\"section\":\"$section\"}" >/dev/null 2>&1 || true | |
411 | + log "Added '$p_id' to Omarchy top bar ($section section)." | |
412 | + ;; | |
413 | + remove) | |
414 | + local p_id="${1:-}" | |
415 | + [[ -n $p_id ]] || err "Plugin ID required" | |
416 | + if [[ -f "$shell_cfg" ]]; then | |
417 | + local updated | |
418 | + updated=$(jq --arg id "$p_id" ' | |
419 | + .bar.layout.left = [.bar.layout.left[]? | select(.id != $id)] | | |
420 | + .bar.layout.center = [.bar.layout.center[]? | select(.id != $id)] | | |
421 | + .bar.layout.right = [.bar.layout.right[]? | select(.id != $id)] | |
422 | + ' "$shell_cfg") | |
423 | + echo "$updated" > "$shell_cfg" | |
424 | + fi | |
425 | + omarchy-shell shell setPluginEnabled "$p_id" false >/dev/null 2>&1 || true | |
426 | + log "Removed '$p_id' from Omarchy top bar." | |
427 | + ;; | |
428 | + toggle) | |
429 | + local p_id="${1:-}" | |
430 | + local section="${2:-right}" | |
431 | + [[ -n $p_id ]] || err "Plugin ID required" | |
432 | + local in_bar="false" | |
433 | + if [[ -f "$shell_cfg" ]]; then | |
434 | + in_bar=$(jq -r --arg id "$p_id" '([.bar.layout.left[]?.id, .bar.layout.center[]?.id, .bar.layout.right[]?.id] | index($id)) != null' "$shell_cfg") | |
435 | + fi | |
436 | + if [[ $in_bar == "true" ]]; then | |
437 | + cmd_bar remove "$p_id" | |
438 | + else | |
439 | + cmd_bar add "$p_id" "$section" | |
440 | + fi | |
441 | + ;; | |
442 | + esac | |
79 | 443 | } |
80 | 444 | |
81 | -case "$ACTION" in | |
82 | - list) list_plugins ;; | |
83 | - search) [[ -z $QUERY ]] && err "Usage: omarchy-plugins search <query>"; search_plugins ;; | |
84 | - info) [[ -z $QUERY ]] && err "Usage: omarchy-plugins info <plugin-id>"; show_info ;; | |
85 | - install) [[ -z $QUERY ]] && err "Usage: omarchy-plugins install <plugin-id>"; install_plugin ;; | |
86 | - *) echo "Usage: omarchy-plugins [list|install|search|info] [plugin-name]" ;; | |
445 | +cmd_gui() { | |
446 | + quickshell -n -p "$OMARCHY_PATH/plugin-library" >/dev/null 2>&1 & | |
447 | +} | |
448 | + | |
449 | +# Main router | |
450 | +action="${1:-list}" | |
451 | +shift || true | |
452 | + | |
453 | +case "$action" in | |
454 | + list|ls) cmd_list "$@" ;; | |
455 | + marketplace|market|store) cmd_marketplace "$@" ;; | |
456 | + install|add) cmd_install "$@" ;; | |
457 | + remove|rm|delete|uninstall) cmd_remove "$@" ;; | |
458 | + enable|on) cmd_enable "$@" ;; | |
459 | + disable|off) cmd_disable "$@" ;; | |
460 | + update|upgrade) cmd_update "$@" ;; | |
461 | + check-updates|check) cmd_check_updates "$@" ;; | |
462 | + shortcut|keybind) cmd_shortcut "$@" ;; | |
463 | + bar) cmd_bar "$@" ;; | |
464 | + gui|manager|app) cmd_gui "$@" ;; | |
465 | + --help|-h) | |
466 | + cat <<EOF | |
467 | +Usage: | |
468 | + omarchy plugins [command] [options] | |
469 | + | |
470 | +Commands: | |
471 | + list [--json] List all installed and discovered plugins | |
472 | + marketplace [--fetch] [--query <q>] Search and browse online plugin marketplace | |
473 | + install <id-or-url> [--enable] Install a plugin from marketplace or Git URL | |
474 | + remove <id> Uninstall an installed user plugin | |
475 | + enable <id> [placement] Enable a plugin in the active shell | |
476 | + disable <id> Disable a plugin | |
477 | + shortcut [list|get|set|remove] Manage keyboard shortcuts for plugins | |
478 | + bar [list|add|remove|toggle] Manage Omarchy top bar placement for plugins | |
479 | + update [id] Update git-installed plugins to latest release | |
480 | + check-updates Check if updates are available for installed plugins | |
481 | + gui Open the graphical Plugin Manager window | |
482 | +EOF | |
483 | + ;; | |
484 | + *) | |
485 | + # Default to install if it looks like a URL or plugin id with args | |
486 | + if [[ $action == http* || $action == git@* ]]; then | |
487 | + cmd_install "$action" "$@" | |
488 | + else | |
489 | + err "Unknown plugins action: $action. Run 'omarchy plugins --help' for usage." | |
490 | + fi | |
491 | + ;; | |
87 | 492 | esac |
88 | 493 | |
▼plugin-library/shell.qml
+1896-0
| @@ -0,0 +1,1896 @@ | ||
1 | +import QtQuick | |
2 | +import QtQuick.Controls | |
3 | +import QtQuick.Layouts | |
4 | +import Quickshell | |
5 | +import Quickshell.Io | |
6 | + | |
7 | +ShellRoot { | |
8 | + id: root | |
9 | + | |
10 | + // ---------------------------------------------------------------- Visual Design System (Tokyo Night / Catppuccin inspired) | |
11 | + readonly property color bgBase: "#11121d" | |
12 | + readonly property color bgSurface: "#161726" | |
13 | + readonly property color bgCard: "#1d1f33" | |
14 | + readonly property color bgCardHover: "#252842" | |
15 | + readonly property color bgElevated: "#2a2e4a" | |
16 | + readonly property color borderSubtle: "#282b45" | |
17 | + readonly property color borderActive: "#3d426b" | |
18 | + | |
19 | + readonly property color fgMain: "#cdd6f4" | |
20 | + readonly property color fgMuted: "#a6adc8" | |
21 | + readonly property color fgDim: "#585b70" | |
22 | + | |
23 | + readonly property color accent: "#89b4fa" | |
24 | + readonly property color accentGlow: Qt.rgba(137/255, 180/255, 250/255, 0.15) | |
25 | + | |
26 | + readonly property color greenSuccess: "#a6e3a1" | |
27 | + readonly property color greenGlow: Qt.rgba(166/255, 227/255, 161/255, 0.15) | |
28 | + | |
29 | + readonly property color redDanger: "#f38ba8" | |
30 | + readonly property color redGlow: Qt.rgba(243/255, 139/255, 168/255, 0.15) | |
31 | + | |
32 | + readonly property color amberWarn: "#fab387" | |
33 | + readonly property color amberGlow: Qt.rgba(250/255, 179/255, 135/255, 0.15) | |
34 | + | |
35 | + readonly property color purpleTag: "#cba6f7" | |
36 | + readonly property color purpleGlow: Qt.rgba(203/255, 166/255, 247/255, 0.15) | |
37 | + | |
38 | + // ---------------------------------------------------------------- Iconography | |
39 | + // | |
40 | + // Vector glyphs from JetBrainsMono Nerd Font — the family the bar, panels, | |
41 | + // and terminal already render with — so icons stay monochrome, crisp at | |
42 | + // every size, and tintable instead of colored emoji. Most Nerd Font | |
43 | + // codepoints live beyond the BMP, so they are stored as ints and resolved | |
44 | + // with String.fromCodePoint rather than \u escapes. | |
45 | + readonly property string nfFontFamily: "JetBrainsMono Nerd Font" | |
46 | + function nf(code) { return String.fromCodePoint(code) } | |
47 | + | |
48 | + // Chrome icons used by this window's own controls. | |
49 | + readonly property var ic: ({ | |
50 | + puzzle: 0xF0327, // md-puzzle plugin manager brand | |
51 | + package: 0xF03D3, // md-package installed tab | |
52 | + globe: 0xF00AC, // fa-globe marketplace tab / web search | |
53 | + plus: 0xF0415, // md-plus add / pin actions | |
54 | + update: 0xF06B0, // md-update update-all button | |
55 | + bolt: 0xF140B, // md-lightning_bolt restart shell | |
56 | + magnify: 0xF0349, // md-magnify search fields | |
57 | + close: 0xF0156, // md-close clear buttons | |
58 | + check: 0xF012C, // md-check success states | |
59 | + closeCircle: 0xF0159, // md-close_circle failure states | |
60 | + checkCircle: 0xF05E0, // md-check_circle success toasts | |
61 | + pin: 0xF0403, // md-pin bar placement | |
62 | + keyboard: 0xF097B, // md-keyboard_outline shortcut keycap | |
63 | + trash: 0xF1F8, // fa-trash remove user plugin | |
64 | + star: 0xF04CE, // md-star marketplace stars | |
65 | + externalLink: 0xF005C, // md-arrow_top_right repo link | |
66 | + arrowLeft: 0xF004D, // md-arrow_left section picker | |
67 | + arrowRight: 0xF0054 // md-arrow_right section picker | |
68 | + }) | |
69 | + | |
70 | + // ---------------------------------------------------------------- State | |
71 | + property string currentTab: "installed" // "installed" | "marketplace" | |
72 | + property var installedPlugins: [] | |
73 | + property var marketplacePlugins: [] | |
74 | + property var filteredInstalled: [] | |
75 | + property var filteredMarketplace: [] | |
76 | + property var shortcutsMap: ({}) | |
77 | + property var barLayoutData: ({ inBar: [] }) | |
78 | + | |
79 | + property string installedSearch: "" | |
80 | + property string installedFilter: "all" // "all" | "active" | "inactive" | "bar" | "builtin" | "user" | |
81 | + | |
82 | + property string marketSearch: "" | |
83 | + property string marketCategory: "All" | |
84 | + property string marketSort: "stars" // "stars" | "name" | "date" | |
85 | + | |
86 | + property string installingId: "" | |
87 | + property string statusMessage: "" | |
88 | + property string statusType: "info" // "info" | "success" | "error" | |
89 | + property bool updatingAll: false | |
90 | + | |
91 | + // Modals | |
92 | + property bool urlDialogOpen: false | |
93 | + property string urlInput: "" | |
94 | + | |
95 | + property bool confirmUninstallOpen: false | |
96 | + property var pendingUninstallPlugin: null | |
97 | + | |
98 | + property bool restartDialogOpen: false | |
99 | + | |
100 | + property bool shortcutDialogOpen: false | |
101 | + property var shortcutTargetPlugin: null | |
102 | + property string shortcutInput: "" | |
103 | + | |
104 | + property bool barSectionDialogOpen: false | |
105 | + property var barTargetPlugin: null | |
106 | + | |
107 | + Component.onCompleted: { | |
108 | + loadInstalled() | |
109 | + loadMarketplace() | |
110 | + loadShortcuts() | |
111 | + loadBarLayout() | |
112 | + } | |
113 | + | |
114 | + // ---------------------------------------------------------------- Data Loaders | |
115 | + function loadInstalled() { | |
116 | + var om = Quickshell.env("OMARCHY_PATH") || "/usr/share/de-omarchy" | |
117 | + installedProc.command = ["bash", "-c", "omarchy-shell shell listPlugins 2>/dev/null || " + om + "/bin/omarchy-plugins list --json 2>/dev/null || omarchy plugins list --json 2>/dev/null || echo '[]'"] | |
118 | + installedProc.running = true | |
119 | + } | |
120 | + | |
121 | + function loadMarketplace() { | |
122 | + var home = Quickshell.env("HOME") | |
123 | + var om = Quickshell.env("OMARCHY_PATH") || "/usr/share/de-omarchy" | |
124 | + marketProc.command = ["bash", "-c", "cat " + home + "/.cache/omarchy/marketplace_catalog.json 2>/dev/null | jq -c '.plugins // []' 2>/dev/null || " + om + "/bin/omarchy-plugins marketplace --json 2>/dev/null || cat " + om + "/registry.json 2>/dev/null | jq -c '.plugins // []' 2>/dev/null || echo '[]'"] | |
125 | + marketProc.running = true | |
126 | + } | |
127 | + | |
128 | + function loadShortcuts() { | |
129 | + shortcutsProc.command = ["bash", "-c", "omarchy plugins shortcut list --json 2>/dev/null || echo '[]'"] | |
130 | + shortcutsProc.running = true | |
131 | + } | |
132 | + | |
133 | + function loadBarLayout() { | |
134 | + barProc.command = ["bash", "-c", "omarchy plugins bar list 2>/dev/null || echo '{\"inBar\":[]}'"] | |
135 | + barProc.running = true | |
136 | + } | |
137 | + | |
138 | + Process { | |
139 | + id: installedProc | |
140 | + running: false | |
141 | + stdout: StdioCollector { | |
142 | + waitForEnd: true | |
143 | + onStreamFinished: { | |
144 | + try { | |
145 | + var arr = JSON.parse(String(text).trim()) | |
146 | + if (Array.isArray(arr)) { | |
147 | + root.installedPlugins = arr | |
148 | + root.applyInstalledFilter() | |
149 | + } | |
150 | + } catch (e) { | |
151 | + console.warn("Failed to parse installed plugins: " + e) | |
152 | + } | |
153 | + } | |
154 | + } | |
155 | + } | |
156 | + | |
157 | + Process { | |
158 | + id: marketProc | |
159 | + running: false | |
160 | + stdout: StdioCollector { | |
161 | + waitForEnd: true | |
162 | + onStreamFinished: { | |
163 | + try { | |
164 | + var data = JSON.parse(String(text).trim()) | |
165 | + var arr = Array.isArray(data) ? data : (data && data.plugins ? data.plugins : []) | |
166 | + if (Array.isArray(arr)) { | |
167 | + root.marketplacePlugins = arr | |
168 | + root.applyMarketFilter() | |
169 | + } | |
170 | + } catch (e) { | |
171 | + console.warn("Failed to parse marketplace plugins: " + e) | |
172 | + } | |
173 | + } | |
174 | + } | |
175 | + } | |
176 | + | |
177 | + Process { | |
178 | + id: shortcutsProc | |
179 | + running: false | |
180 | + stdout: StdioCollector { | |
181 | + waitForEnd: true | |
182 | + onStreamFinished: { | |
183 | + try { | |
184 | + var arr = JSON.parse(String(text).trim()) | |
185 | + var map = {} | |
186 | + if (Array.isArray(arr)) { | |
187 | + for (var i = 0; i < arr.length; i++) { | |
188 | + if (arr[i] && arr[i].id) { | |
189 | + map[arr[i].id] = arr[i].keys || "" | |
190 | + } | |
191 | + } | |
192 | + } | |
193 | + root.shortcutsMap = map | |
194 | + } catch (e) { | |
195 | + console.warn("Failed to parse shortcuts: " + e) | |
196 | + } | |
197 | + } | |
198 | + } | |
199 | + } | |
200 | + | |
201 | + Process { | |
202 | + id: barProc | |
203 | + running: false | |
204 | + stdout: StdioCollector { | |
205 | + waitForEnd: true | |
206 | + onStreamFinished: { | |
207 | + try { | |
208 | + var data = JSON.parse(String(text).trim()) | |
209 | + if (data && data.inBar) { | |
210 | + root.barLayoutData = data | |
211 | + root.applyInstalledFilter() | |
212 | + } | |
213 | + } catch (e) { | |
214 | + console.warn("Failed to parse bar layout: " + e) | |
215 | + } | |
216 | + } | |
217 | + } | |
218 | + } | |
219 | + | |
220 | + // ---------------------------------------------------------------- Filter Logic | |
221 | + function isPluginInBar(id) { | |
222 | + if (!id || !root.barLayoutData || !root.barLayoutData.inBar) return false | |
223 | + return root.barLayoutData.inBar.indexOf(id) !== -1 | |
224 | + } | |
225 | + | |
226 | + function getPluginIcon(id, name) { | |
227 | + var map = { | |
228 | + "omarchy.menu": 0xF0463, // md-rocket | |
229 | + "omarchy.active-window": 0xF05B1, // md-window_open | |
230 | + "omarchy.workspaces": 0xF0570, // md-view_grid | |
231 | + "omarchy.bar": 0xF056E, // md-view_dashboard | |
232 | + "omarchy.agents": 0xF06A9, // md-robot | |
233 | + "omarchy.audio": 0xF057E, // md-volume_high | |
234 | + "omarchy.bluetooth": 0xF00AF, // md-bluetooth | |
235 | + "omarchy.network": 0xF05A9, // md-wifi | |
236 | + "omarchy.clock": 0xF0150, // md-clock_outline | |
237 | + "omarchy.battery": 0xF0079, // md-battery | |
238 | + "omarchy.power": 0xF0425, // md-power | |
239 | + "omarchy.sysmon": 0xF029A, // md-gauge | |
240 | + "omarchy.weather": 0xF0599, // md-weather_sunny | |
241 | + "omarchy.media": 0xF075A, // md-music | |
242 | + "omarchy.notifications": 0xF009A, // md-bell | |
243 | + "omarchy.clipboard": 0xF014D, // md-clipboard_text | |
244 | + "omarchy.emojis": 0xF01F2, // md-emoticon_outline | |
245 | + "omarchy.indicators": 0xF0335, // md-lightbulb | |
246 | + "omarchy.keyboard-layout": 0xF097B, // md-keyboard_outline | |
247 | + "omarchy.lock": 0xF033E, // md-lock | |
248 | + "omarchy.nightlight": 0xF0594, // md-weather_night | |
249 | + "omarchy.microphone": 0xF036C, // md-microphone | |
250 | + "omarchy.tray": 0xF1294, // md-tray | |
251 | + "omarchy.background": 0xF02E9, // md-image | |
252 | + "omarchy.idle": 0xF051F, // md-timer_sand | |
253 | + "omarchy.polkit": 0xF0BC5, // md-shield_key_outline | |
254 | + "omarchy.reminders": 0xF13AB, // md-timer | |
255 | + "omarchy.spacer": 0xF084E, // md-arrow_expand_horizontal | |
256 | + "omarchy.speedtest": 0xF04C5, // md-speedometer | |
257 | + "omarchy.system-update": 0xF06B0, // md-update | |
258 | + "omarchy.tailscale": 0xF0582, // md-vpn | |
259 | + "omarchy.wifiqr": 0xF0432, // md-qrcode | |
260 | + "deomarchy.docker-status": 0xF0868, // md-docker | |
261 | + "deomarchy.wallpaper-manager": 0xF0E09, // md-wallpaper | |
262 | + "deomarchy.power-profile": 0xF0241, // md-flash | |
263 | + "deomarchy.system-overview": 0xF154D, // md-chart_box | |
264 | + "deomarchy.monitor-manager": 0xF0379, // md-monitor | |
265 | + "deomarchy.plugin-manager": 0xF0327, // md-puzzle | |
266 | + "deomarchy.plugin-library": 0xF0327, // md-puzzle | |
267 | + "nosignal.motion-wallpaper": 0xF022F, // md-film | |
268 | + "omaplug": 0xF06A5 // md-power_plug | |
269 | + } | |
270 | + if (map[id] !== undefined) return String.fromCodePoint(map[id]) | |
271 | + var clean = (name || id || "").toLowerCase() | |
272 | + if (clean.indexOf("docker") !== -1) return String.fromCodePoint(0xF0868) // md-docker | |
273 | + if (clean.indexOf("game") !== -1) return String.fromCodePoint(0xF0296) // md-gamepad | |
274 | + if (clean.indexOf("music") !== -1 || clean.indexOf("audio") !== -1) return String.fromCodePoint(0xF075A) // md-music | |
275 | + if (clean.indexOf("wall") !== -1) return String.fromCodePoint(0xF02E9) // md-image | |
276 | + if (clean.indexOf("monitor") !== -1 || clean.indexOf("cpu") !== -1) return String.fromCodePoint(0xF029A) // md-gauge | |
277 | + if (clean.indexOf("calc") !== -1) return String.fromCodePoint(0xF00EC) // md-calculator | |
278 | + if (clean.indexOf("ai") !== -1 || clean.indexOf("gpt") !== -1) return String.fromCodePoint(0xF06A9) // md-robot | |
279 | + return String.fromCodePoint(0xF0068) // md-auto_fix | |
280 | + } | |
281 | + | |
282 | + function getPluginDescription(p) { | |
283 | + if (p.description && p.description.length > 0) return p.description | |
284 | + var map = { | |
285 | + "omarchy.active-window": "Displays the title and icon of the current focused window", | |
286 | + "omarchy.agents": "Quick switcher and status for background AI coding assistants", | |
287 | + "omarchy.audio": "Audio volume slider, output sink switcher, and per-app stream mixer", | |
288 | + "omarchy.background": "High-performance wallpaper backdrop rendering service", | |
289 | + "omarchy.bar": "Core desktop top bar container and layout supervisor", | |
290 | + "omarchy.battery": "Battery level indicator, charging status, and power threshold alerts", | |
291 | + "omarchy.bluetooth": "Bluetooth device scanner, pairing manager, and quick connect", | |
292 | + "omarchy.clipboard": "Searchable clipboard history popup overlay", | |
293 | + "omarchy.clock": "Date, time, world clocks, and integrated monthly calendar panel", | |
294 | + "omarchy.dev-gallery": "Developer gallery showcase for Quickshell UI components", | |
295 | + "omarchy.disk-speedtest": "Real-time disk read/write benchmark utility", | |
296 | + "omarchy.dropbox": "Dropbox file sync state and folder shortcut", | |
297 | + "omarchy.emojis": "Searchable emoji picker with instant clipboard copy", | |
298 | + "omarchy.idle": "Wayland idle manager and automatic screen locking service", | |
299 | + "omarchy.image-picker": "Desktop screenshot and image selector overlay", | |
300 | + "omarchy.indicators": "Quick-glance system status indicators (caps lock, mute, recording)", | |
301 | + "omarchy.keyboard-layout": "Keyboard layout indicator and XKB switcher", | |
302 | + "omarchy.lock": "Wayland lock screen overlay with password authentication", | |
303 | + "omarchy.media": "MPRIS media player controls (play/pause, track, seek)", | |
304 | + "omarchy.menu": "Application launcher and system command launcher", | |
305 | + "omarchy.microphone": "Microphone volume control and mute toggle", | |
306 | + "omarchy.monitor": "Display arrangement, brightness, and resolution manager", | |
307 | + "nosignal.motion-wallpaper": "Dynamic animated video and shader wallpaper background", | |
308 | + "omarchy.network": "Wi-Fi network scanner, signal strength, and Ethernet status", | |
309 | + "omarchy.nightlight": "Blue light reduction gamma adjustment service (Hyprsunset)", | |
310 | + "omarchy.notifications": "Desktop notification center and daemon", | |
311 | + "omarchy.osd": "On-screen display popup for volume and brightness adjustments", | |
312 | + "omarchy.polkit": "Polkit authentication agent for administrative privilege prompts", | |
313 | + "omarchy.power": "Power menu with shutdown, reboot, suspend, and logout options", | |
314 | + "omarchy.reminders": "Sticky notes and system reminder overlay", | |
315 | + "omarchy.spacer": "Flexible spacer element for custom bar layout separation", | |
316 | + "omarchy.speedtest": "Network internet speed test benchmark panel", | |
317 | + "omarchy.sysmon": "Live CPU, RAM, disk, network, and temperature monitor", | |
318 | + "omarchy.system-update": "Arch Linux pacman & AUR package update notification widget", | |
319 | + "omarchy.tailscale": "Tailscale VPN status toggle and exit node switcher", | |
320 | + "omarchy.tray": "System tray host for background app indicators", | |
321 | + "omarchy.weather": "Local weather forecast and temperature indicator", | |
322 | + "omarchy.wifiqr": "Generates a Wi-Fi QR code for quick mobile connection", | |
323 | + "omarchy.workspaces": "Visual Hyprland workspace indicator with active window dots", | |
324 | + "deomarchy.docker-status": "Docker container monitor with one-click lazydocker integration", | |
325 | + "deomarchy.power-profile": "Power profile switcher (Performance / Balanced / Power-saver)", | |
326 | + "deomarchy.wallpaper-manager": "Theme-synchronized wallpaper selector panel", | |
327 | + "deomarchy.system-overview": "Comprehensive system resource overview dashboard" | |
328 | + } | |
329 | + return map[p.id] || ("ID: " + p.id) | |
330 | + } | |
331 | + | |
332 | + function applyInstalledFilter() { | |
333 | + var list = root.installedPlugins || [] | |
334 | + var q = root.installedSearch.toLowerCase().trim() | |
335 | + var filter = root.installedFilter | |
336 | + | |
337 | + var res = [] | |
338 | + for (var i = 0; i < list.length; i++) { | |
339 | + var p = list[i] | |
340 | + if (filter === "active" && !p.enabled) continue | |
341 | + if (filter === "inactive" && p.enabled) continue | |
342 | + if (filter === "bar" && !isPluginInBar(p.id)) continue | |
343 | + if (filter === "builtin" && !p.firstParty) continue | |
344 | + if (filter === "user" && p.firstParty) continue | |
345 | + | |
346 | + if (q.length > 0) { | |
347 | + var name = (p.name || "").toLowerCase() | |
348 | + var id = (p.id || "").toLowerCase() | |
349 | + var desc = getPluginDescription(p).toLowerCase() | |
350 | + var kinds = (p.kinds || []).join(" ").toLowerCase() | |
351 | + if (name.indexOf(q) === -1 && id.indexOf(q) === -1 && desc.indexOf(q) === -1 && kinds.indexOf(q) === -1) continue | |
352 | + } | |
353 | + res.push(p) | |
354 | + } | |
355 | + root.filteredInstalled = res | |
356 | + } | |
357 | + | |
358 | + function applyMarketFilter() { | |
359 | + var list = root.marketplacePlugins || [] | |
360 | + var q = root.marketSearch.toLowerCase().trim() | |
361 | + var cat = root.marketCategory | |
362 | + | |
363 | + var res = [] | |
364 | + for (var i = 0; i < list.length; i++) { | |
365 | + var p = list[i] | |
366 | + if (cat !== "All" && (p.category || "").toLowerCase() !== cat.toLowerCase()) continue | |
367 | + | |
368 | + if (q.length > 0) { | |
369 | + var name = (p.name || "").toLowerCase() | |
370 | + var id = (p.id || "").toLowerCase() | |
371 | + var desc = (p.description || "").toLowerCase() | |
372 | + var author = (p.author || "").toLowerCase() | |
373 | + var tags = (p.tags || []).join(" ").toLowerCase() | |
374 | + if (name.indexOf(q) === -1 && id.indexOf(q) === -1 && desc.indexOf(q) === -1 && author.indexOf(q) === -1 && tags.indexOf(q) === -1) continue | |
375 | + } | |
376 | + res.push(p) | |
377 | + } | |
378 | + | |
379 | + if (root.marketSort === "stars") { | |
380 | + res.sort(function(a, b) { return (b.stars || 0) - (a.stars || 0) }) | |
381 | + } else if (root.marketSort === "name") { | |
382 | + res.sort(function(a, b) { return (a.name || a.id).localeCompare(b.name || b.id) }) | |
383 | + } else if (root.marketSort === "date") { | |
384 | + res.sort(function(a, b) { return String(b.addedAt || "").localeCompare(String(a.addedAt || "")) }) | |
385 | + } | |
386 | + | |
387 | + root.filteredMarketplace = res | |
388 | + } | |
389 | + | |
390 | + onInstalledSearchChanged: applyInstalledFilter() | |
391 | + onInstalledFilterChanged: applyInstalledFilter() | |
392 | + onMarketSearchChanged: applyMarketFilter() | |
393 | + onMarketCategoryChanged: applyMarketFilter() | |
394 | + onMarketSortChanged: applyMarketFilter() | |
395 | + | |
396 | + // ---------------------------------------------------------------- Actions | |
397 | + function isInstalled(id) { | |
398 | + if (!id) return false | |
399 | + var cleanId = id.replace(/^[a-z0-9_-]+\./, "") | |
400 | + for (var i = 0; i < root.installedPlugins.length; i++) { | |
401 | + var instId = root.installedPlugins[i].id | |
402 | + if (instId === id || instId === cleanId || instId.endsWith("." + id) || id.endsWith("." + instId)) { | |
403 | + return true | |
404 | + } | |
405 | + } | |
406 | + return false | |
407 | + } | |
408 | + | |
409 | + function showToast(msg, type) { | |
410 | + root.statusMessage = msg | |
411 | + root.statusType = type || "info" | |
412 | + toastTimer.restart() | |
413 | + } | |
414 | + | |
415 | + Timer { | |
416 | + id: toastTimer | |
417 | + interval: 3500 | |
418 | + onTriggered: root.statusMessage = "" | |
419 | + } | |
420 | + | |
421 | + function togglePlugin(plugin) { | |
422 | + if (!plugin || !plugin.id) return | |
423 | + var targetState = !plugin.enabled | |
424 | + plugin.enabled = targetState | |
425 | + applyInstalledFilter() | |
426 | + | |
427 | + actionProc.command = ["bash", "-c", "omarchy plugins " + (targetState ? "enable" : "disable") + " '" + plugin.id + "'"] | |
428 | + actionProc.targetAction = "toggle" | |
429 | + actionProc.targetName = plugin.name || plugin.id | |
430 | + actionProc.targetState = targetState | |
431 | + actionProc.running = true | |
432 | + } | |
433 | + | |
434 | + function toggleBarPlacement(plugin) { | |
435 | + if (!plugin || !plugin.id) return | |
436 | + if (isPluginInBar(plugin.id)) { | |
437 | + actionProc.command = ["bash", "-c", "omarchy plugins bar remove '" + plugin.id + "'"] | |
438 | + actionProc.targetAction = "bar_remove" | |
439 | + actionProc.targetName = plugin.name || plugin.id | |
440 | + actionProc.running = true | |
441 | + } else { | |
442 | + root.barTargetPlugin = plugin | |
443 | + root.barSectionDialogOpen = true | |
444 | + } | |
445 | + } | |
446 | + | |
447 | + function addPluginToBar(plugin, section) { | |
448 | + root.barSectionDialogOpen = false | |
449 | + root.barTargetPlugin = null | |
450 | + if (!plugin || !plugin.id) return | |
451 | + | |
452 | + showToast("Adding " + (plugin.name || plugin.id) + " to bar (" + section + ")...", "info") | |
453 | + actionProc.command = ["bash", "-c", "omarchy plugins bar add '" + plugin.id + "' " + section] | |
454 | + actionProc.targetAction = "bar_add" | |
455 | + actionProc.targetName = plugin.name || plugin.id | |
456 | + actionProc.running = true | |
457 | + } | |
458 | + | |
459 | + function openShortcutDialog(plugin) { | |
460 | + root.shortcutTargetPlugin = plugin | |
461 | + root.shortcutInput = root.shortcutsMap[plugin.id] || "" | |
462 | + root.shortcutDialogOpen = true | |
463 | + } | |
464 | + | |
465 | + function saveShortcut() { | |
466 | + var p = root.shortcutTargetPlugin | |
467 | + var keys = root.shortcutInput.trim() | |
468 | + root.shortcutDialogOpen = false | |
469 | + root.shortcutTargetPlugin = null | |
470 | + if (!p) return | |
471 | + | |
472 | + if (keys === "") { | |
473 | + actionProc.command = ["bash", "-c", "omarchy plugins shortcut remove '" + p.id + "'"] | |
474 | + actionProc.targetAction = "shortcut_remove" | |
475 | + actionProc.targetName = p.name || p.id | |
476 | + actionProc.running = true | |
477 | + } else { | |
478 | + actionProc.command = ["bash", "-c", "omarchy plugins shortcut set '" + p.id + "' '" + keys + "'"] | |
479 | + actionProc.targetAction = "shortcut_set" | |
480 | + actionProc.targetName = p.name || p.id | |
481 | + actionProc.running = true | |
482 | + } | |
483 | + } | |
484 | + | |
485 | + function clearShortcut() { | |
486 | + root.shortcutInput = "" | |
487 | + saveShortcut() | |
488 | + } | |
489 | + | |
490 | + function installPlugin(p) { | |
491 | + if (!p || root.installingId !== "") return | |
492 | + root.installingId = p.id | |
493 | + showToast("Installing " + (p.name || p.id) + "...", "info") | |
494 | + | |
495 | + actionProc.command = ["bash", "-c", "omarchy plugins install '" + p.id + "' --enable"] | |
496 | + actionProc.targetAction = "install" | |
497 | + actionProc.targetName = p.name || p.id | |
498 | + actionProc.running = true | |
499 | + } | |
500 | + | |
501 | + function installFromUrl(url) { | |
502 | + if (!url || url.trim() === "") return | |
503 | + url = url.trim() | |
504 | + root.urlDialogOpen = false | |
505 | + root.urlInput = "" | |
506 | + showToast("Installing: " + url + "...", "info") | |
507 | + | |
508 | + actionProc.command = ["bash", "-c", "omarchy plugins install '" + url + "' --enable"] | |
509 | + actionProc.targetAction = "install" | |
510 | + actionProc.targetName = url | |
511 | + actionProc.running = true | |
512 | + } | |
513 | + | |
514 | + function confirmUninstall(p) { | |
515 | + root.pendingUninstallPlugin = p | |
516 | + root.confirmUninstallOpen = true | |
517 | + } | |
518 | + | |
519 | + function executeUninstall() { | |
520 | + var p = root.pendingUninstallPlugin | |
521 | + root.confirmUninstallOpen = false | |
522 | + root.pendingUninstallPlugin = null | |
523 | + if (!p) return | |
524 | + | |
525 | + showToast("Removing " + (p.name || p.id) + "...", "info") | |
526 | + actionProc.command = ["bash", "-c", "omarchy plugins remove '" + p.id + "'"] | |
527 | + actionProc.targetAction = "remove" | |
528 | + actionProc.targetName = p.name || p.id | |
529 | + actionProc.running = true | |
530 | + } | |
531 | + | |
532 | + function updateAllPlugins() { | |
533 | + root.updatingAll = true | |
534 | + showToast("Checking and updating all plugins...", "info") | |
535 | + actionProc.command = ["bash", "-c", "omarchy plugins update"] | |
536 | + actionProc.targetAction = "update_all" | |
537 | + actionProc.targetName = "all plugins" | |
538 | + actionProc.running = true | |
539 | + } | |
540 | + | |
541 | + function restartShell() { | |
542 | + root.restartDialogOpen = false | |
543 | + showToast("Restarting Quickshell...", "info") | |
544 | + actionProc.command = ["bash", "-c", "omarchy-restart-shell"] | |
545 | + actionProc.targetAction = "restart" | |
546 | + actionProc.targetName = "shell" | |
547 | + actionProc.running = true | |
548 | + } | |
549 | + | |
550 | + Process { | |
551 | + id: actionProc | |
552 | + running: false | |
553 | + property string targetAction: "" | |
554 | + property string targetName: "" | |
555 | + property bool targetState: false | |
556 | + | |
557 | + onExited: function(code) { | |
558 | + root.installingId = "" | |
559 | + root.updatingAll = false | |
560 | + if (code === 0) { | |
561 | + var ok = root.nf(root.ic.checkCircle) | |
562 | + if (targetAction === "install") { | |
563 | + root.showToast(ok + " Installed " + targetName + "!", "success") | |
564 | + } else if (targetAction === "remove") { | |
565 | + root.showToast(ok + " Removed " + targetName + ".", "success") | |
566 | + } else if (targetAction === "toggle") { | |
567 | + root.showToast(ok + " " + (targetState ? "Enabled" : "Disabled") + " " + targetName, "success") | |
568 | + } else if (targetAction === "bar_add") { | |
569 | + root.showToast(ok + " Pinned " + targetName + " to top bar!", "success") | |
570 | + } else if (targetAction === "bar_remove") { | |
571 | + root.showToast(ok + " Unpinned " + targetName + " from bar.", "success") | |
572 | + } else if (targetAction === "shortcut_set") { | |
573 | + root.showToast(ok + " Shortcut saved for " + targetName + "!", "success") | |
574 | + } else if (targetAction === "shortcut_remove") { | |
575 | + root.showToast(ok + " Shortcut cleared for " + targetName, "success") | |
576 | + } else if (targetAction === "update_all") { | |
577 | + root.showToast(ok + " All plugins up to date!", "success") | |
578 | + } else if (targetAction === "restart") { | |
579 | + root.showToast(ok + " Shell restarted successfully!", "success") | |
580 | + } | |
581 | + } else { | |
582 | + root.showToast(root.nf(root.ic.closeCircle) + " Operation failed for " + targetName, "error") | |
583 | + } | |
584 | + root.loadInstalled() | |
585 | + root.loadShortcuts() | |
586 | + root.loadBarLayout() | |
587 | + } | |
588 | + } | |
589 | + | |
590 | + // ---------------------------------------------------------------- Main Floating Window | |
591 | + Window { | |
592 | + id: win | |
593 | + title: "Omarchy Plugin Manager" | |
594 | + visible: true | |
595 | + width: 980 | |
596 | + height: 740 | |
597 | + minimumWidth: 840 | |
598 | + minimumHeight: 600 | |
599 | + color: root.bgBase | |
600 | + | |
601 | + Rectangle { | |
602 | + anchors.fill: parent | |
603 | + color: root.bgBase | |
604 | + | |
605 | + ColumnLayout { | |
606 | + anchors.fill: parent | |
607 | + anchors.margins: 20 | |
608 | + spacing: 16 | |
609 | + | |
610 | + // ========================================================== Top Navigation Header | |
611 | + RowLayout { | |
612 | + Layout.fillWidth: true | |
613 | + spacing: 16 | |
614 | + | |
615 | + // Left Brand | |
616 | + RowLayout { | |
617 | + spacing: 12 | |
618 | + Rectangle { | |
619 | + Layout.preferredWidth: 42 | |
620 | + Layout.preferredHeight: 42 | |
621 | + radius: 12 | |
622 | + color: root.accentGlow | |
623 | + border.color: Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.4) | |
624 | + border.width: 1 | |
625 | + Text { | |
626 | + anchors.centerIn: parent | |
627 | + text: root.nf(root.ic.puzzle) | |
628 | + color: root.accent | |
629 | + font.family: root.nfFontFamily | |
630 | + font.pixelSize: 22 | |
631 | + } | |
632 | + } | |
633 | + | |
634 | + ColumnLayout { | |
635 | + spacing: 2 | |
636 | + Text { | |
637 | + text: "Plugin Manager" | |
638 | + color: root.fgMain | |
639 | + font.pixelSize: 18 | |
640 | + font.bold: true | |
641 | + } | |
642 | + Text { | |
643 | + text: "Omarchy Desktop & Extensions" | |
644 | + color: root.fgMuted | |
645 | + font.pixelSize: 11 | |
646 | + } | |
647 | + } | |
648 | + } | |
649 | + | |
650 | + Item { Layout.fillWidth: true } | |
651 | + | |
652 | + // Center Segmented Tab Bar | |
653 | + Rectangle { | |
654 | + Layout.preferredWidth: 320 | |
655 | + Layout.preferredHeight: 40 | |
656 | + radius: 20 | |
657 | + color: root.bgSurface | |
658 | + border.color: root.borderSubtle | |
659 | + border.width: 1 | |
660 | + | |
661 | + RowLayout { | |
662 | + anchors.fill: parent | |
663 | + anchors.margins: 4 | |
664 | + spacing: 4 | |
665 | + | |
666 | + Rectangle { | |
667 | + Layout.fillWidth: true | |
668 | + Layout.fillHeight: true | |
669 | + radius: 16 | |
670 | + color: root.currentTab === "installed" ? root.accent : "transparent" | |
671 | + | |
672 | + RowLayout { | |
673 | + anchors.centerIn: parent | |
674 | + spacing: 6 | |
675 | + Text { | |
676 | + text: root.nf(root.ic.package) | |
677 | + color: root.currentTab === "installed" ? "#11121d" : root.fgMuted | |
678 | + font.family: root.nfFontFamily | |
679 | + font.pixelSize: 13 | |
680 | + } | |
681 | + Text { | |
682 | + text: "Installed (" + root.installedPlugins.length + ")" | |
683 | + color: root.currentTab === "installed" ? "#11121d" : root.fgMuted | |
684 | + font.pixelSize: 12 | |
685 | + font.bold: root.currentTab === "installed" | |
686 | + } | |
687 | + } | |
688 | + | |
689 | + MouseArea { | |
690 | + anchors.fill: parent | |
691 | + cursorShape: Qt.PointingHandCursor | |
692 | + onClicked: root.currentTab = "installed" | |
693 | + } | |
694 | + } | |
695 | + | |
696 | + Rectangle { | |
697 | + Layout.fillWidth: true | |
698 | + Layout.fillHeight: true | |
699 | + radius: 16 | |
700 | + color: root.currentTab === "marketplace" ? root.accent : "transparent" | |
701 | + | |
702 | + RowLayout { | |
703 | + anchors.centerIn: parent | |
704 | + spacing: 6 | |
705 | + Text { | |
706 | + text: root.nf(root.ic.globe) | |
707 | + color: root.currentTab === "marketplace" ? "#11121d" : root.fgMuted | |
708 | + font.family: root.nfFontFamily | |
709 | + font.pixelSize: 13 | |
710 | + } | |
711 | + Text { | |
712 | + text: "Marketplace (" + (root.marketplacePlugins.length || "1000+") + ")" | |
713 | + color: root.currentTab === "marketplace" ? "#11121d" : root.fgMuted | |
714 | + font.pixelSize: 12 | |
715 | + font.bold: root.currentTab === "marketplace" | |
716 | + } | |
717 | + } | |
718 | + | |
719 | + MouseArea { | |
720 | + anchors.fill: parent | |
721 | + cursorShape: Qt.PointingHandCursor | |
722 | + onClicked: { | |
723 | + root.currentTab = "marketplace" | |
724 | + if (root.marketplacePlugins.length === 0) root.loadMarketplace() | |
725 | + } | |
726 | + } | |
727 | + } | |
728 | + } | |
729 | + } | |
730 | + | |
731 | + Item { Layout.fillWidth: true } | |
732 | + | |
733 | + // Right Actions | |
734 | + RowLayout { | |
735 | + spacing: 8 | |
736 | + | |
737 | + // Git URL | |
738 | + Rectangle { | |
739 | + Layout.preferredHeight: 36 | |
740 | + Layout.preferredWidth: btnGitText.implicitWidth + 28 | |
741 | + radius: 10 | |
742 | + color: root.bgSurface | |
743 | + border.color: root.borderSubtle | |
744 | + border.width: 1 | |
745 | + | |
746 | + RowLayout { | |
747 | + anchors.centerIn: parent | |
748 | + spacing: 6 | |
749 | + Text { | |
750 | + text: root.nf(root.ic.plus) | |
751 | + color: root.accent | |
752 | + font.family: root.nfFontFamily | |
753 | + font.pixelSize: 13 | |
754 | + } | |
755 | + Text { | |
756 | + id: btnGitText | |
757 | + text: "Git URL" | |
758 | + color: root.fgMain | |
759 | + font.pixelSize: 12 | |
760 | + font.bold: true | |
761 | + } | |
762 | + } | |
763 | + | |
764 | + MouseArea { | |
765 | + anchors.fill: parent | |
766 | + cursorShape: Qt.PointingHandCursor | |
767 | + onClicked: root.urlDialogOpen = true | |
768 | + } | |
769 | + } | |
770 | + | |
771 | + // Update All | |
772 | + Rectangle { | |
773 | + Layout.preferredHeight: 36 | |
774 | + Layout.preferredWidth: btnUpText.implicitWidth + 28 | |
775 | + radius: 10 | |
776 | + color: root.bgSurface | |
777 | + border.color: root.borderSubtle | |
778 | + border.width: 1 | |
779 | + | |
780 | + RowLayout { | |
781 | + anchors.centerIn: parent | |
782 | + spacing: 6 | |
783 | + Text { | |
784 | + text: root.nf(root.ic.update) | |
785 | + color: root.greenSuccess | |
786 | + font.family: root.nfFontFamily | |
787 | + font.pixelSize: 13 | |
788 | + } | |
789 | + Text { | |
790 | + id: btnUpText | |
791 | + text: "Update" | |
792 | + color: root.fgMain | |
793 | + font.pixelSize: 12 | |
794 | + font.bold: true | |
795 | + } | |
796 | + } | |
797 | + | |
798 | + MouseArea { | |
799 | + anchors.fill: parent | |
800 | + cursorShape: Qt.PointingHandCursor | |
801 | + onClicked: root.updateAllPlugins() | |
802 | + } | |
803 | + } | |
804 | + | |
805 | + // Restart Shell | |
806 | + Rectangle { | |
807 | + Layout.preferredHeight: 36 | |
808 | + Layout.preferredWidth: 38 | |
809 | + radius: 10 | |
810 | + color: root.bgSurface | |
811 | + border.color: root.borderSubtle | |
812 | + border.width: 1 | |
813 | + | |
814 | + Text { | |
815 | + anchors.centerIn: parent | |
816 | + text: root.nf(root.ic.bolt) | |
817 | + color: root.fgMain | |
818 | + font.family: root.nfFontFamily | |
819 | + font.pixelSize: 15 | |
820 | + } | |
821 | + | |
822 | + MouseArea { | |
823 | + anchors.fill: parent | |
824 | + cursorShape: Qt.PointingHandCursor | |
825 | + onClicked: root.restartDialogOpen = true | |
826 | + } | |
827 | + } | |
828 | + } | |
829 | + } | |
830 | + | |
831 | + // ========================================================== Toast Banner | |
832 | + Rectangle { | |
833 | + Layout.fillWidth: true | |
834 | + Layout.preferredHeight: root.statusMessage !== "" ? 36 : 0 | |
835 | + visible: root.statusMessage !== "" | |
836 | + radius: 10 | |
837 | + color: root.statusType === "success" ? root.greenGlow : (root.statusType === "error" ? root.redGlow : root.accentGlow) | |
838 | + border.color: root.statusType === "success" ? root.greenSuccess : (root.statusType === "error" ? root.redDanger : root.accent) | |
839 | + border.width: 1 | |
840 | + clip: true | |
841 | + | |
842 | + Text { | |
843 | + anchors.centerIn: parent | |
844 | + text: root.statusMessage | |
845 | + color: root.fgMain | |
846 | + font.family: root.nfFontFamily | |
847 | + font.pixelSize: 12 | |
848 | + font.bold: true | |
849 | + } | |
850 | + } | |
851 | + | |
852 | + // ========================================================== Views | |
853 | + StackLayout { | |
854 | + Layout.fillWidth: true | |
855 | + Layout.fillHeight: true | |
856 | + currentIndex: root.currentTab === "installed" ? 0 : 1 | |
857 | + | |
858 | + // -------------------------------------------------------- TAB 1: INSTALLED | |
859 | + Item { | |
860 | + ColumnLayout { | |
861 | + anchors.fill: parent | |
862 | + spacing: 12 | |
863 | + | |
864 | + // Filter & Search Toolbar | |
865 | + RowLayout { | |
866 | + Layout.fillWidth: true | |
867 | + spacing: 12 | |
868 | + | |
869 | + // Search Box | |
870 | + Rectangle { | |
871 | + Layout.fillWidth: true | |
872 | + Layout.preferredHeight: 40 | |
873 | + radius: 12 | |
874 | + color: root.bgSurface | |
875 | + border.color: root.borderSubtle | |
876 | + border.width: 1 | |
877 | + | |
878 | + RowLayout { | |
879 | + anchors.fill: parent | |
880 | + anchors.leftMargin: 12 | |
881 | + anchors.rightMargin: 12 | |
882 | + spacing: 10 | |
883 | + | |
884 | + Text { | |
885 | + text: root.nf(root.ic.magnify) | |
886 | + color: root.fgDim | |
887 | + font.family: root.nfFontFamily | |
888 | + font.pixelSize: 14 | |
889 | + } | |
890 | + | |
891 | + TextInput { | |
892 | + id: installedSearchInput | |
893 | + Layout.fillWidth: true | |
894 | + text: root.installedSearch | |
895 | + color: root.fgMain | |
896 | + font.pixelSize: 13 | |
897 | + selectByMouse: true | |
898 | + onTextChanged: root.installedSearch = text | |
899 | + | |
900 | + Text { | |
901 | + text: "Search plugins by name, ID, or description..." | |
902 | + color: root.fgDim | |
903 | + font.pixelSize: 13 | |
904 | + visible: !installedSearchInput.text | |
905 | + } | |
906 | + } | |
907 | + | |
908 | + Text { | |
909 | + visible: installedSearchInput.text.length > 0 | |
910 | + text: root.nf(root.ic.close) | |
911 | + color: root.fgDim | |
912 | + font.family: root.nfFontFamily | |
913 | + font.pixelSize: 13 | |
914 | + MouseArea { | |
915 | + anchors.fill: parent | |
916 | + cursorShape: Qt.PointingHandCursor | |
917 | + onClicked: root.installedSearch = "" | |
918 | + } | |
919 | + } | |
920 | + } | |
921 | + } | |
922 | + | |
923 | + // Filter Chips | |
924 | + RowLayout { | |
925 | + spacing: 6 | |
926 | + | |
927 | + Repeater { | |
928 | + model: [ | |
929 | + { id: "all", label: "All (" + root.installedPlugins.length + ")" }, | |
930 | + { id: "active", label: "Active" }, | |
931 | + { id: "inactive", label: "Inactive" }, | |
932 | + { id: "bar", label: "In Bar" }, | |
933 | + { id: "builtin", label: "Built-in" }, | |
934 | + { id: "user", label: "User" } | |
935 | + ] | |
936 | + | |
937 | + delegate: Rectangle { | |
938 | + required property var modelData | |
939 | + Layout.preferredHeight: 34 | |
940 | + Layout.preferredWidth: instChipText.implicitWidth + 22 | |
941 | + radius: 17 | |
942 | + color: root.installedFilter === modelData.id ? root.accentGlow : root.bgSurface | |
943 | + border.color: root.installedFilter === modelData.id ? root.accent : root.borderSubtle | |
944 | + border.width: 1 | |
945 | + | |
946 | + Text { | |
947 | + id: instChipText | |
948 | + anchors.centerIn: parent | |
949 | + text: modelData.label | |
950 | + color: root.installedFilter === modelData.id ? root.accent : root.fgMuted | |
951 | + font.pixelSize: 12 | |
952 | + font.bold: root.installedFilter === modelData.id | |
953 | + } | |
954 | + | |
955 | + MouseArea { | |
956 | + anchors.fill: parent | |
957 | + cursorShape: Qt.PointingHandCursor | |
958 | + onClicked: root.installedFilter = modelData.id | |
959 | + } | |
960 | + } | |
961 | + } | |
962 | + } | |
963 | + } | |
964 | + | |
965 | + // List of Installed Plugins | |
966 | + ListView { | |
967 | + Layout.fillWidth: true | |
968 | + Layout.fillHeight: true | |
969 | + clip: true | |
970 | + spacing: 8 | |
971 | + model: root.filteredInstalled | |
972 | + | |
973 | + delegate: Rectangle { | |
974 | + required property var modelData | |
975 | + property bool inBar: root.isPluginInBar(modelData.id) | |
976 | + property string shortcut: root.shortcutsMap[modelData.id] || "" | |
977 | + property bool isBarCompatible: { | |
978 | + var kinds = modelData.kinds || [] | |
979 | + return kinds.indexOf("bar-widget") !== -1 || kinds.indexOf("bar") !== -1 || kinds.indexOf("panel") !== -1 | |
980 | + } | |
981 | + | |
982 | + width: parent ? parent.width : 0 | |
983 | + height: 74 | |
984 | + radius: 12 | |
985 | + color: modelData.enabled ? root.bgCard : root.bgSurface | |
986 | + border.color: modelData.enabled ? root.borderActive : root.borderSubtle | |
987 | + border.width: 1 | |
988 | + | |
989 | + RowLayout { | |
990 | + anchors.fill: parent | |
991 | + anchors.margins: 12 | |
992 | + spacing: 14 | |
993 | + | |
994 | + // Dynamic Icon Badge | |
995 | + Rectangle { | |
996 | + Layout.preferredWidth: 46 | |
997 | + Layout.preferredHeight: 46 | |
998 | + radius: 12 | |
999 | + color: modelData.enabled ? root.accentGlow : root.bgElevated | |
1000 | + border.color: modelData.enabled ? Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.3) : root.borderSubtle | |
1001 | + border.width: 1 | |
1002 | + | |
1003 | + Text { | |
1004 | + anchors.centerIn: parent | |
1005 | + text: root.getPluginIcon(modelData.id, modelData.name) | |
1006 | + color: modelData.enabled ? root.accent : root.fgDim | |
1007 | + font.family: root.nfFontFamily | |
1008 | + font.pixelSize: 22 | |
1009 | + } | |
1010 | + } | |
1011 | + | |
1012 | + // Metadata | |
1013 | + ColumnLayout { | |
1014 | + Layout.fillWidth: true | |
1015 | + spacing: 3 | |
1016 | + | |
1017 | + RowLayout { | |
1018 | + spacing: 8 | |
1019 | + Text { | |
1020 | + text: modelData.name || modelData.id | |
1021 | + color: root.fgMain | |
1022 | + font.pixelSize: 14 | |
1023 | + font.bold: true | |
1024 | + } | |
1025 | + | |
1026 | + // Kind pills | |
1027 | + Repeater { | |
1028 | + model: modelData.kinds || [] | |
1029 | + delegate: Rectangle { | |
1030 | + required property var modelData | |
1031 | + Layout.preferredHeight: 20 | |
1032 | + Layout.preferredWidth: kindText.implicitWidth + 12 | |
1033 | + radius: 6 | |
1034 | + color: root.purpleGlow | |
1035 | + border.color: Qt.rgba(root.purpleTag.r, root.purpleTag.g, root.purpleTag.b, 0.3) | |
1036 | + border.width: 1 | |
1037 | + | |
1038 | + Text { | |
1039 | + id: kindText | |
1040 | + anchors.centerIn: parent | |
1041 | + text: modelData === "bar-widget" ? "Widget" : (modelData === "service" ? "Service" : (modelData === "panel" ? "Panel" : modelData)) | |
1042 | + color: root.purpleTag | |
1043 | + font.pixelSize: 10 | |
1044 | + font.bold: true | |
1045 | + } | |
1046 | + } | |
1047 | + } | |
1048 | + | |
1049 | + // Source pill | |
1050 | + Rectangle { | |
1051 | + Layout.preferredHeight: 20 | |
1052 | + Layout.preferredWidth: srcText.implicitWidth + 12 | |
1053 | + radius: 6 | |
1054 | + color: modelData.firstParty ? Qt.rgba(255, 255, 255, 0.05) : root.greenGlow | |
1055 | + border.color: modelData.firstParty ? root.borderSubtle : Qt.rgba(root.greenSuccess.r, root.greenSuccess.g, root.greenSuccess.b, 0.3) | |
1056 | + border.width: 1 | |
1057 | + | |
1058 | + Text { | |
1059 | + id: srcText | |
1060 | + anchors.centerIn: parent | |
1061 | + text: modelData.firstParty ? "Core" : "User" | |
1062 | + color: modelData.firstParty ? root.fgDim : root.greenSuccess | |
1063 | + font.pixelSize: 10 | |
1064 | + font.bold: true | |
1065 | + } | |
1066 | + } | |
1067 | + } | |
1068 | + | |
1069 | + Text { | |
1070 | + Layout.fillWidth: true | |
1071 | + text: root.getPluginDescription(modelData) | |
1072 | + color: root.fgMuted | |
1073 | + font.pixelSize: 11 | |
1074 | + elide: Text.ElideRight | |
1075 | + } | |
1076 | + } | |
1077 | + | |
1078 | + // Interactive Controls Row | |
1079 | + RowLayout { | |
1080 | + spacing: 8 | |
1081 | + | |
1082 | + // Bar Pin Button | |
1083 | + Rectangle { | |
1084 | + visible: isBarCompatible | |
1085 | + Layout.preferredHeight: 32 | |
1086 | + Layout.preferredWidth: barBtnText.implicitWidth + 24 | |
1087 | + radius: 8 | |
1088 | + color: inBar ? root.greenGlow : root.bgElevated | |
1089 | + border.color: inBar ? root.greenSuccess : root.borderSubtle | |
1090 | + border.width: 1 | |
1091 | + | |
1092 | + RowLayout { | |
1093 | + anchors.centerIn: parent | |
1094 | + spacing: 6 | |
1095 | + Text { | |
1096 | + text: inBar ? root.nf(root.ic.pin) : root.nf(root.ic.plus) | |
1097 | + color: inBar ? root.greenSuccess : root.fgMuted | |
1098 | + font.family: root.nfFontFamily | |
1099 | + font.pixelSize: 12 | |
1100 | + } | |
1101 | + Text { | |
1102 | + id: barBtnText | |
1103 | + text: inBar ? "In Top Bar" : "Pin to Bar" | |
1104 | + color: inBar ? root.greenSuccess : root.fgMuted | |
1105 | + font.pixelSize: 11 | |
1106 | + font.bold: inBar | |
1107 | + } | |
1108 | + } | |
1109 | + | |
1110 | + MouseArea { | |
1111 | + anchors.fill: parent | |
1112 | + cursorShape: Qt.PointingHandCursor | |
1113 | + onClicked: root.toggleBarPlacement(modelData) | |
1114 | + } | |
1115 | + } | |
1116 | + | |
1117 | + // Shortcut Keycap Button | |
1118 | + Rectangle { | |
1119 | + Layout.preferredHeight: 32 | |
1120 | + Layout.preferredWidth: scBtnText.implicitWidth + 24 | |
1121 | + radius: 8 | |
1122 | + color: shortcut !== "" ? root.amberGlow : root.bgElevated | |
1123 | + border.color: shortcut !== "" ? root.amberWarn : root.borderSubtle | |
1124 | + border.width: 1 | |
1125 | + | |
1126 | + RowLayout { | |
1127 | + anchors.centerIn: parent | |
1128 | + spacing: 6 | |
1129 | + Text { | |
1130 | + text: root.nf(root.ic.keyboard) | |
1131 | + color: shortcut !== "" ? root.amberWarn : root.fgDim | |
1132 | + font.family: root.nfFontFamily | |
1133 | + font.pixelSize: 13 | |
1134 | + } | |
1135 | + Text { | |
1136 | + id: scBtnText | |
1137 | + text: shortcut !== "" ? shortcut : "Shortcut" | |
1138 | + color: shortcut !== "" ? root.amberWarn : root.fgMuted | |
1139 | + font.pixelSize: 11 | |
1140 | + font.bold: shortcut !== "" | |
1141 | + } | |
1142 | + } | |
1143 | + | |
1144 | + MouseArea { | |
1145 | + anchors.fill: parent | |
1146 | + cursorShape: Qt.PointingHandCursor | |
1147 | + onClicked: root.openShortcutDialog(modelData) | |
1148 | + } | |
1149 | + } | |
1150 | + | |
1151 | + // Delete User Plugin | |
1152 | + Rectangle { | |
1153 | + visible: !modelData.firstParty | |
1154 | + Layout.preferredWidth: 32 | |
1155 | + Layout.preferredHeight: 32 | |
1156 | + radius: 8 | |
1157 | + color: root.redGlow | |
1158 | + border.color: Qt.rgba(root.redDanger.r, root.redDanger.g, root.redDanger.b, 0.4) | |
1159 | + border.width: 1 | |
1160 | + | |
1161 | + Text { | |
1162 | + anchors.centerIn: parent | |
1163 | + text: root.nf(root.ic.trash) | |
1164 | + color: root.redDanger | |
1165 | + font.family: root.nfFontFamily | |
1166 | + font.pixelSize: 14 | |
1167 | + } | |
1168 | + | |
1169 | + MouseArea { | |
1170 | + anchors.fill: parent | |
1171 | + cursorShape: Qt.PointingHandCursor | |
1172 | + onClicked: root.confirmUninstall(modelData) | |
1173 | + } | |
1174 | + } | |
1175 | + | |
1176 | + // Active Toggle Switch | |
1177 | + Rectangle { | |
1178 | + Layout.preferredWidth: 48 | |
1179 | + Layout.preferredHeight: 28 | |
1180 | + radius: 14 | |
1181 | + color: modelData.enabled ? root.accent : root.bgElevated | |
1182 | + border.color: modelData.enabled ? root.accent : root.borderSubtle | |
1183 | + border.width: 1 | |
1184 | + | |
1185 | + Rectangle { | |
1186 | + width: 22 | |
1187 | + height: 22 | |
1188 | + radius: 11 | |
1189 | + color: "#ffffff" | |
1190 | + anchors.verticalCenter: parent.verticalCenter | |
1191 | + x: modelData.enabled ? 23 : 3 | |
1192 | + Behavior on x { NumberAnimation { duration: 120 } } | |
1193 | + } | |
1194 | + | |
1195 | + MouseArea { | |
1196 | + anchors.fill: parent | |
1197 | + cursorShape: Qt.PointingHandCursor | |
1198 | + onClicked: root.togglePlugin(modelData) | |
1199 | + } | |
1200 | + } | |
1201 | + } | |
1202 | + } | |
1203 | + } | |
1204 | + } | |
1205 | + } | |
1206 | + } | |
1207 | + | |
1208 | + // -------------------------------------------------------- TAB 2: MARKETPLACE | |
1209 | + Item { | |
1210 | + ColumnLayout { | |
1211 | + anchors.fill: parent | |
1212 | + spacing: 12 | |
1213 | + | |
1214 | + // Filter & Search Toolbar | |
1215 | + RowLayout { | |
1216 | + Layout.fillWidth: true | |
1217 | + spacing: 12 | |
1218 | + | |
1219 | + // Search Box | |
1220 | + Rectangle { | |
1221 | + Layout.fillWidth: true | |
1222 | + Layout.preferredHeight: 40 | |
1223 | + radius: 12 | |
1224 | + color: root.bgSurface | |
1225 | + border.color: root.borderSubtle | |
1226 | + border.width: 1 | |
1227 | + | |
1228 | + RowLayout { | |
1229 | + anchors.fill: parent | |
1230 | + anchors.leftMargin: 12 | |
1231 | + anchors.rightMargin: 12 | |
1232 | + spacing: 10 | |
1233 | + | |
1234 | + Text { | |
1235 | + text: root.nf(root.ic.globe) | |
1236 | + color: root.fgDim | |
1237 | + font.family: root.nfFontFamily | |
1238 | + font.pixelSize: 14 | |
1239 | + } | |
1240 | + | |
1241 | + TextInput { | |
1242 | + id: marketSearchInput | |
1243 | + Layout.fillWidth: true | |
1244 | + text: root.marketSearch | |
1245 | + color: root.fgMain | |
1246 | + font.pixelSize: 13 | |
1247 | + selectByMouse: true | |
1248 | + onTextChanged: root.marketSearch = text | |
1249 | + | |
1250 | + Text { | |
1251 | + text: "Search 1,000+ community extensions from OmarchyPlugins.com..." | |
1252 | + color: root.fgDim | |
1253 | + font.pixelSize: 13 | |
1254 | + visible: !marketSearchInput.text | |
1255 | + } | |
1256 | + } | |
1257 | + | |
1258 | + Text { | |
1259 | + visible: marketSearchInput.text.length > 0 | |
1260 | + text: root.nf(root.ic.close) | |
1261 | + color: root.fgDim | |
1262 | + font.family: root.nfFontFamily | |
1263 | + font.pixelSize: 13 | |
1264 | + MouseArea { | |
1265 | + anchors.fill: parent | |
1266 | + cursorShape: Qt.PointingHandCursor | |
1267 | + onClicked: root.marketSearch = "" | |
1268 | + } | |
1269 | + } | |
1270 | + } | |
1271 | + } | |
1272 | + | |
1273 | + // Category Chips ScrollView | |
1274 | + ScrollView { | |
1275 | + Layout.preferredWidth: 460 | |
1276 | + Layout.preferredHeight: 40 | |
1277 | + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff | |
1278 | + ScrollBar.vertical.policy: ScrollBar.AlwaysOff | |
1279 | + | |
1280 | + RowLayout { | |
1281 | + spacing: 6 | |
1282 | + Repeater { | |
1283 | + model: ["All", "Widgets", "Desktop", "System", "Productivity", "Developer Tools", "Appearance", "Utilities"] | |
1284 | + delegate: Rectangle { | |
1285 | + required property var modelData | |
1286 | + Layout.preferredHeight: 34 | |
1287 | + Layout.preferredWidth: mCatText.implicitWidth + 20 | |
1288 | + radius: 17 | |
1289 | + color: root.marketCategory === modelData ? root.accentGlow : root.bgSurface | |
1290 | + border.color: root.marketCategory === modelData ? root.accent : root.borderSubtle | |
1291 | + border.width: 1 | |
1292 | + | |
1293 | + Text { | |
1294 | + id: mCatText | |
1295 | + anchors.centerIn: parent | |
1296 | + text: modelData | |
1297 | + color: root.marketCategory === modelData ? root.accent : root.fgMuted | |
1298 | + font.pixelSize: 12 | |
1299 | + font.bold: root.marketCategory === modelData | |
1300 | + } | |
1301 | + | |
1302 | + MouseArea { | |
1303 | + anchors.fill: parent | |
1304 | + cursorShape: Qt.PointingHandCursor | |
1305 | + onClicked: root.marketCategory = modelData | |
1306 | + } | |
1307 | + } | |
1308 | + } | |
1309 | + } | |
1310 | + } | |
1311 | + } | |
1312 | + | |
1313 | + // Marketplace Cards List | |
1314 | + ListView { | |
1315 | + Layout.fillWidth: true | |
1316 | + Layout.fillHeight: true | |
1317 | + clip: true | |
1318 | + spacing: 8 | |
1319 | + model: root.filteredMarketplace | |
1320 | + | |
1321 | + delegate: Rectangle { | |
1322 | + required property var modelData | |
1323 | + property bool installed: root.isInstalled(modelData.id) | |
1324 | + | |
1325 | + width: parent ? parent.width : 0 | |
1326 | + height: 78 | |
1327 | + radius: 12 | |
1328 | + color: installed ? root.bgCard : root.bgSurface | |
1329 | + border.color: installed ? Qt.rgba(root.greenSuccess.r, root.greenSuccess.g, root.greenSuccess.b, 0.3) : root.borderSubtle | |
1330 | + border.width: 1 | |
1331 | + | |
1332 | + RowLayout { | |
1333 | + anchors.fill: parent | |
1334 | + anchors.margins: 12 | |
1335 | + spacing: 14 | |
1336 | + | |
1337 | + // Initial Badge | |
1338 | + Rectangle { | |
1339 | + Layout.preferredWidth: 46 | |
1340 | + Layout.preferredHeight: 46 | |
1341 | + radius: 12 | |
1342 | + color: root.bgElevated | |
1343 | + border.color: root.borderSubtle | |
1344 | + border.width: 1 | |
1345 | + | |
1346 | + Text { | |
1347 | + anchors.centerIn: parent | |
1348 | + text: modelData.initials || root.getPluginIcon(modelData.id, modelData.name) | |
1349 | + color: root.accent | |
1350 | + font.family: root.nfFontFamily | |
1351 | + font.pixelSize: modelData.initials ? 16 : 22 | |
1352 | + font.bold: !!modelData.initials | |
1353 | + } | |
1354 | + } | |
1355 | + | |
1356 | + // Metadata | |
1357 | + ColumnLayout { | |
1358 | + Layout.fillWidth: true | |
1359 | + spacing: 3 | |
1360 | + | |
1361 | + RowLayout { | |
1362 | + spacing: 8 | |
1363 | + Text { | |
1364 | + text: modelData.name || modelData.id | |
1365 | + color: root.fgMain | |
1366 | + font.pixelSize: 14 | |
1367 | + font.bold: true | |
1368 | + } | |
1369 | + | |
1370 | + Text { | |
1371 | + visible: (modelData.stars || 0) > 0 | |
1372 | + text: root.nf(root.ic.star) + " " + modelData.stars | |
1373 | + color: "#e0af68" | |
1374 | + font.family: root.nfFontFamily | |
1375 | + font.pixelSize: 11 | |
1376 | + font.bold: true | |
1377 | + } | |
1378 | + | |
1379 | + Rectangle { | |
1380 | + visible: modelData.category !== undefined && modelData.category !== "" | |
1381 | + Layout.preferredHeight: 20 | |
1382 | + Layout.preferredWidth: catPill.implicitWidth + 12 | |
1383 | + radius: 6 | |
1384 | + color: root.purpleGlow | |
1385 | + border.color: Qt.rgba(root.purpleTag.r, root.purpleTag.g, root.purpleTag.b, 0.3) | |
1386 | + border.width: 1 | |
1387 | + | |
1388 | + Text { | |
1389 | + id: catPill | |
1390 | + anchors.centerIn: parent | |
1391 | + text: modelData.category || "Misc" | |
1392 | + color: root.purpleTag | |
1393 | + font.pixelSize: 10 | |
1394 | + } | |
1395 | + } | |
1396 | + | |
1397 | + Text { | |
1398 | + visible: modelData.author !== undefined && modelData.author !== "" | |
1399 | + text: "by " + modelData.author | |
1400 | + color: root.fgDim | |
1401 | + font.pixelSize: 11 | |
1402 | + } | |
1403 | + } | |
1404 | + | |
1405 | + Text { | |
1406 | + Layout.fillWidth: true | |
1407 | + text: modelData.description || "" | |
1408 | + color: root.fgMuted | |
1409 | + font.pixelSize: 11 | |
1410 | + elide: Text.ElideRight | |
1411 | + } | |
1412 | + } | |
1413 | + | |
1414 | + // Actions | |
1415 | + RowLayout { | |
1416 | + spacing: 8 | |
1417 | + | |
1418 | + // Repo Link | |
1419 | + Rectangle { | |
1420 | + visible: modelData.repo !== undefined && modelData.repo !== "" | |
1421 | + Layout.preferredWidth: 34 | |
1422 | + Layout.preferredHeight: 34 | |
1423 | + radius: 8 | |
1424 | + color: root.bgElevated | |
1425 | + border.color: root.borderSubtle | |
1426 | + border.width: 1 | |
1427 | + | |
1428 | + Text { | |
1429 | + anchors.centerIn: parent | |
1430 | + text: root.nf(root.ic.externalLink) | |
1431 | + color: root.fgMain | |
1432 | + font.family: root.nfFontFamily | |
1433 | + font.pixelSize: 14 | |
1434 | + } | |
1435 | + | |
1436 | + MouseArea { | |
1437 | + anchors.fill: parent | |
1438 | + cursorShape: Qt.PointingHandCursor | |
1439 | + onClicked: { | |
1440 | + if (modelData.repo) { | |
1441 | + Qt.openUrlExternally(modelData.repo) | |
1442 | + } | |
1443 | + } | |
1444 | + } | |
1445 | + } | |
1446 | + | |
1447 | + // Install / Installed Status Button | |
1448 | + Rectangle { | |
1449 | + Layout.preferredHeight: 34 | |
1450 | + Layout.preferredWidth: installed ? 100 : 84 | |
1451 | + radius: 8 | |
1452 | + color: installed ? root.greenGlow : (root.installingId === modelData.id ? root.bgElevated : root.accent) | |
1453 | + border.color: installed ? root.greenSuccess : "transparent" | |
1454 | + border.width: installed ? 1 : 0 | |
1455 | + | |
1456 | + Text { | |
1457 | + anchors.centerIn: parent | |
1458 | + text: installed ? root.nf(root.ic.check) + " Installed" : (root.installingId === modelData.id ? "Installing..." : "Install") | |
1459 | + color: installed ? root.greenSuccess : (root.installingId === modelData.id ? root.fgDim : "#11121d") | |
1460 | + font.family: root.nfFontFamily | |
1461 | + font.pixelSize: 11 | |
1462 | + font.bold: true | |
1463 | + } | |
1464 | + | |
1465 | + MouseArea { | |
1466 | + anchors.fill: parent | |
1467 | + cursorShape: Qt.PointingHandCursor | |
1468 | + enabled: !installed && root.installingId === "" | |
1469 | + onClicked: root.installPlugin(modelData) | |
1470 | + } | |
1471 | + } | |
1472 | + } | |
1473 | + } | |
1474 | + } | |
1475 | + } | |
1476 | + } | |
1477 | + } | |
1478 | + } | |
1479 | + } | |
1480 | + | |
1481 | + // ========================================================== MODALS | |
1482 | + // 1. Keyboard Shortcut Dialog Modal | |
1483 | + Rectangle { | |
1484 | + anchors.fill: parent | |
1485 | + color: Qt.rgba(0, 0, 0, 0.8) | |
1486 | + visible: root.shortcutDialogOpen | |
1487 | + | |
1488 | + MouseArea { anchors.fill: parent; onClicked: root.shortcutDialogOpen = false } | |
1489 | + | |
1490 | + Rectangle { | |
1491 | + anchors.centerIn: parent | |
1492 | + width: 480 | |
1493 | + height: 290 | |
1494 | + radius: 16 | |
1495 | + color: root.bgSurface | |
1496 | + border.color: root.borderActive | |
1497 | + border.width: 1 | |
1498 | + | |
1499 | + MouseArea { anchors.fill: parent } | |
1500 | + | |
1501 | + ColumnLayout { | |
1502 | + anchors.fill: parent | |
1503 | + anchors.margins: 22 | |
1504 | + spacing: 14 | |
1505 | + | |
1506 | + RowLayout { | |
1507 | + spacing: 10 | |
1508 | + Text { | |
1509 | + text: root.nf(root.ic.keyboard) | |
1510 | + color: root.amberWarn | |
1511 | + font.family: root.nfFontFamily | |
1512 | + font.pixelSize: 19 | |
1513 | + } | |
1514 | + Text { | |
1515 | + text: "Set Keyboard Shortcut" | |
1516 | + color: root.fgMain | |
1517 | + font.pixelSize: 16 | |
1518 | + font.bold: true | |
1519 | + } | |
1520 | + } | |
1521 | + | |
1522 | + Text { | |
1523 | + text: "Configure a global Hyprland keybind for '" + (root.shortcutTargetPlugin ? (root.shortcutTargetPlugin.name || root.shortcutTargetPlugin.id) : "") + "':" | |
1524 | + color: root.fgMuted | |
1525 | + font.pixelSize: 12 | |
1526 | + } | |
1527 | + | |
1528 | + // Key Input Box | |
1529 | + Rectangle { | |
1530 | + Layout.fillWidth: true | |
1531 | + Layout.preferredHeight: 42 | |
1532 | + radius: 10 | |
1533 | + color: root.bgBase | |
1534 | + border.color: root.amberWarn | |
1535 | + border.width: 1 | |
1536 | + | |
1537 | + RowLayout { | |
1538 | + anchors.fill: parent | |
1539 | + anchors.margins: 10 | |
1540 | + spacing: 10 | |
1541 | + | |
1542 | + TextInput { | |
1543 | + id: scInputField | |
1544 | + Layout.fillWidth: true | |
1545 | + text: root.shortcutInput | |
1546 | + color: root.fgMain | |
1547 | + font.pixelSize: 13 | |
1548 | + font.bold: true | |
1549 | + selectByMouse: true | |
1550 | + onTextChanged: root.shortcutInput = text | |
1551 | + | |
1552 | + Text { | |
1553 | + text: "e.g. SUPER + ALT + D" | |
1554 | + color: root.fgDim | |
1555 | + font.pixelSize: 13 | |
1556 | + visible: !scInputField.text | |
1557 | + } | |
1558 | + } | |
1559 | + } | |
1560 | + } | |
1561 | + | |
1562 | + // Quick Modifiers row | |
1563 | + RowLayout { | |
1564 | + spacing: 6 | |
1565 | + Text { text: "Add:"; color: root.fgDim; font.pixelSize: 11 } | |
1566 | + Repeater { | |
1567 | + model: ["SUPER + ", "ALT + ", "CTRL + ", "SHIFT + "] | |
1568 | + delegate: Rectangle { | |
1569 | + required property var modelData | |
1570 | + Layout.preferredHeight: 26 | |
1571 | + Layout.preferredWidth: qModText.implicitWidth + 14 | |
1572 | + radius: 6 | |
1573 | + color: root.bgElevated | |
1574 | + border.color: root.borderSubtle | |
1575 | + border.width: 1 | |
1576 | + | |
1577 | + Text { | |
1578 | + id: qModText | |
1579 | + anchors.centerIn: parent | |
1580 | + text: modelData | |
1581 | + color: root.fgMain | |
1582 | + font.pixelSize: 11 | |
1583 | + } | |
1584 | + | |
1585 | + MouseArea { | |
1586 | + anchors.fill: parent | |
1587 | + cursorShape: Qt.PointingHandCursor | |
1588 | + onClicked: { | |
1589 | + if (root.shortcutInput.indexOf(modelData) === -1) { | |
1590 | + root.shortcutInput = modelData + root.shortcutInput | |
1591 | + } | |
1592 | + } | |
1593 | + } | |
1594 | + } | |
1595 | + } | |
1596 | + } | |
1597 | + | |
1598 | + Item { Layout.fillHeight: true } | |
1599 | + | |
1600 | + RowLayout { | |
1601 | + Layout.fillWidth: true | |
1602 | + Button { | |
1603 | + text: "Clear Shortcut" | |
1604 | + onClicked: root.clearShortcut() | |
1605 | + } | |
1606 | + Item { Layout.fillWidth: true } | |
1607 | + Button { | |
1608 | + text: "Cancel" | |
1609 | + onClicked: root.shortcutDialogOpen = false | |
1610 | + } | |
1611 | + Button { | |
1612 | + text: "Save & Apply" | |
1613 | + highlighted: true | |
1614 | + onClicked: root.saveShortcut() | |
1615 | + } | |
1616 | + } | |
1617 | + } | |
1618 | + } | |
1619 | + } | |
1620 | + | |
1621 | + // 2. Bar Section Selector Modal | |
1622 | + Rectangle { | |
1623 | + anchors.fill: parent | |
1624 | + color: Qt.rgba(0, 0, 0, 0.8) | |
1625 | + visible: root.barSectionDialogOpen | |
1626 | + | |
1627 | + MouseArea { anchors.fill: parent; onClicked: root.barSectionDialogOpen = false } | |
1628 | + | |
1629 | + Rectangle { | |
1630 | + anchors.centerIn: parent | |
1631 | + width: 440 | |
1632 | + height: 230 | |
1633 | + radius: 16 | |
1634 | + color: root.bgSurface | |
1635 | + border.color: root.borderActive | |
1636 | + border.width: 1 | |
1637 | + | |
1638 | + MouseArea { anchors.fill: parent } | |
1639 | + | |
1640 | + ColumnLayout { | |
1641 | + anchors.fill: parent | |
1642 | + anchors.margins: 22 | |
1643 | + spacing: 14 | |
1644 | + | |
1645 | + RowLayout { | |
1646 | + spacing: 8 | |
1647 | + Text { | |
1648 | + text: root.nf(root.ic.pin) | |
1649 | + color: root.accent | |
1650 | + font.family: root.nfFontFamily | |
1651 | + font.pixelSize: 17 | |
1652 | + } | |
1653 | + Text { | |
1654 | + text: "Pin to Omarchy Top Bar" | |
1655 | + color: root.fgMain | |
1656 | + font.pixelSize: 16 | |
1657 | + font.bold: true | |
1658 | + } | |
1659 | + } | |
1660 | + | |
1661 | + Text { | |
1662 | + text: "Choose which section of the top bar to place '" + (root.barTargetPlugin ? (root.barTargetPlugin.name || root.barTargetPlugin.id) : "") + "':" | |
1663 | + color: root.fgMuted | |
1664 | + font.pixelSize: 12 | |
1665 | + } | |
1666 | + | |
1667 | + RowLayout { | |
1668 | + Layout.fillWidth: true | |
1669 | + spacing: 10 | |
1670 | + | |
1671 | + Button { | |
1672 | + Layout.fillWidth: true | |
1673 | + text: root.nf(root.ic.arrowLeft) + " Left" | |
1674 | + font.family: root.nfFontFamily | |
1675 | + onClicked: root.addPluginToBar(root.barTargetPlugin, "left") | |
1676 | + } | |
1677 | + Button { | |
1678 | + Layout.fillWidth: true | |
1679 | + text: "Center" | |
1680 | + onClicked: root.addPluginToBar(root.barTargetPlugin, "center") | |
1681 | + } | |
1682 | + Button { | |
1683 | + Layout.fillWidth: true | |
1684 | + text: "Right " + root.nf(root.ic.arrowRight) | |
1685 | + font.family: root.nfFontFamily | |
1686 | + highlighted: true | |
1687 | + onClicked: root.addPluginToBar(root.barTargetPlugin, "right") | |
1688 | + } | |
1689 | + } | |
1690 | + | |
1691 | + Item { Layout.fillHeight: true } | |
1692 | + | |
1693 | + RowLayout { | |
1694 | + Layout.fillWidth: true | |
1695 | + Item { Layout.fillWidth: true } | |
1696 | + Button { | |
1697 | + text: "Cancel" | |
1698 | + onClicked: root.barSectionDialogOpen = false | |
1699 | + } | |
1700 | + } | |
1701 | + } | |
1702 | + } | |
1703 | + } | |
1704 | + | |
1705 | + // 3. Git URL Dialog Modal | |
1706 | + Rectangle { | |
1707 | + anchors.fill: parent | |
1708 | + color: Qt.rgba(0, 0, 0, 0.8) | |
1709 | + visible: root.urlDialogOpen | |
1710 | + | |
1711 | + MouseArea { anchors.fill: parent; onClicked: root.urlDialogOpen = false } | |
1712 | + | |
1713 | + Rectangle { | |
1714 | + anchors.centerIn: parent | |
1715 | + width: 480 | |
1716 | + height: 210 | |
1717 | + radius: 16 | |
1718 | + color: root.bgSurface | |
1719 | + border.color: root.borderActive | |
1720 | + border.width: 1 | |
1721 | + | |
1722 | + MouseArea { anchors.fill: parent } | |
1723 | + | |
1724 | + ColumnLayout { | |
1725 | + anchors.fill: parent | |
1726 | + anchors.margins: 22 | |
1727 | + spacing: 14 | |
1728 | + | |
1729 | + Text { | |
1730 | + text: "Install from Git Repository" | |
1731 | + color: root.fgMain | |
1732 | + font.pixelSize: 16 | |
1733 | + font.bold: true | |
1734 | + } | |
1735 | + | |
1736 | + Rectangle { | |
1737 | + Layout.fillWidth: true | |
1738 | + Layout.preferredHeight: 40 | |
1739 | + radius: 10 | |
1740 | + color: root.bgBase | |
1741 | + border.color: root.accent | |
1742 | + border.width: 1 | |
1743 | + | |
1744 | + TextInput { | |
1745 | + anchors.fill: parent | |
1746 | + anchors.margins: 10 | |
1747 | + text: root.urlInput | |
1748 | + color: root.fgMain | |
1749 | + font.pixelSize: 13 | |
1750 | + selectByMouse: true | |
1751 | + onTextChanged: root.urlInput = text | |
1752 | + Text { | |
1753 | + text: "https://github.com/author/plugin.git" | |
1754 | + color: root.fgDim | |
1755 | + font.pixelSize: 13 | |
1756 | + visible: !parent.text | |
1757 | + } | |
1758 | + } | |
1759 | + } | |
1760 | + | |
1761 | + RowLayout { | |
1762 | + Layout.fillWidth: true | |
1763 | + Item { Layout.fillWidth: true } | |
1764 | + Button { | |
1765 | + text: "Cancel" | |
1766 | + onClicked: root.urlDialogOpen = false | |
1767 | + } | |
1768 | + Button { | |
1769 | + text: "Install & Enable" | |
1770 | + highlighted: true | |
1771 | + onClicked: root.installFromUrl(root.urlInput) | |
1772 | + } | |
1773 | + } | |
1774 | + } | |
1775 | + } | |
1776 | + } | |
1777 | + | |
1778 | + // 4. Confirm Uninstall Modal | |
1779 | + Rectangle { | |
1780 | + anchors.fill: parent | |
1781 | + color: Qt.rgba(0, 0, 0, 0.8) | |
1782 | + visible: root.confirmUninstallOpen | |
1783 | + | |
1784 | + MouseArea { anchors.fill: parent; onClicked: root.confirmUninstallOpen = false } | |
1785 | + | |
1786 | + Rectangle { | |
1787 | + anchors.centerIn: parent | |
1788 | + width: 420 | |
1789 | + height: 180 | |
1790 | + radius: 16 | |
1791 | + color: root.bgSurface | |
1792 | + border.color: root.redDanger | |
1793 | + border.width: 1 | |
1794 | + | |
1795 | + MouseArea { anchors.fill: parent } | |
1796 | + | |
1797 | + ColumnLayout { | |
1798 | + anchors.fill: parent | |
1799 | + anchors.margins: 22 | |
1800 | + spacing: 12 | |
1801 | + | |
1802 | + Text { | |
1803 | + text: "Uninstall Plugin" | |
1804 | + color: root.redDanger | |
1805 | + font.pixelSize: 16 | |
1806 | + font.bold: true | |
1807 | + } | |
1808 | + | |
1809 | + Text { | |
1810 | + text: "Are you sure you want to completely remove '" + (root.pendingUninstallPlugin ? (root.pendingUninstallPlugin.name || root.pendingUninstallPlugin.id) : "") + "'?" | |
1811 | + color: root.fgMain | |
1812 | + font.pixelSize: 12 | |
1813 | + } | |
1814 | + | |
1815 | + RowLayout { | |
1816 | + Layout.fillWidth: true | |
1817 | + Item { Layout.fillWidth: true } | |
1818 | + Button { | |
1819 | + text: "Cancel" | |
1820 | + onClicked: root.confirmUninstallOpen = false | |
1821 | + } | |
1822 | + Button { | |
1823 | + text: "Remove Plugin" | |
1824 | + highlighted: true | |
1825 | + onClicked: root.executeUninstall() | |
1826 | + } | |
1827 | + } | |
1828 | + } | |
1829 | + } | |
1830 | + } | |
1831 | + | |
1832 | + // 5. Restart Shell Modal | |
1833 | + Rectangle { | |
1834 | + anchors.fill: parent | |
1835 | + color: Qt.rgba(0, 0, 0, 0.8) | |
1836 | + visible: root.restartDialogOpen | |
1837 | + | |
1838 | + MouseArea { anchors.fill: parent; onClicked: root.restartDialogOpen = false } | |
1839 | + | |
1840 | + Rectangle { | |
1841 | + anchors.centerIn: parent | |
1842 | + width: 420 | |
1843 | + height: 180 | |
1844 | + radius: 16 | |
1845 | + color: root.bgSurface | |
1846 | + border.color: root.borderActive | |
1847 | + border.width: 1 | |
1848 | + | |
1849 | + MouseArea { anchors.fill: parent } | |
1850 | + | |
1851 | + ColumnLayout { | |
1852 | + anchors.fill: parent | |
1853 | + anchors.margins: 22 | |
1854 | + spacing: 12 | |
1855 | + | |
1856 | + RowLayout { | |
1857 | + spacing: 8 | |
1858 | + Text { | |
1859 | + text: root.nf(root.ic.bolt) | |
1860 | + color: root.amberWarn | |
1861 | + font.family: root.nfFontFamily | |
1862 | + font.pixelSize: 17 | |
1863 | + } | |
1864 | + Text { | |
1865 | + text: "Restart Quickshell" | |
1866 | + color: root.fgMain | |
1867 | + font.pixelSize: 16 | |
1868 | + font.bold: true | |
1869 | + } | |
1870 | + } | |
1871 | + | |
1872 | + Text { | |
1873 | + text: "Restart the Omarchy desktop shell to reload all widgets and panels?" | |
1874 | + color: root.fgMuted | |
1875 | + font.pixelSize: 12 | |
1876 | + } | |
1877 | + | |
1878 | + RowLayout { | |
1879 | + Layout.fillWidth: true | |
1880 | + Item { Layout.fillWidth: true } | |
1881 | + Button { | |
1882 | + text: "Cancel" | |
1883 | + onClicked: root.restartDialogOpen = false | |
1884 | + } | |
1885 | + Button { | |
1886 | + text: "Restart Shell" | |
1887 | + highlighted: true | |
1888 | + onClicked: root.restartShell() | |
1889 | + } | |
1890 | + } | |
1891 | + } | |
1892 | + } | |
1893 | + } | |
1894 | + } | |
1895 | + } | |
1896 | +} | |
0 | 1897 | |
▼registry.json
+23-1
| @@ -3,7 +3,29 @@ | ||
3 | 3 | "version": "1.0.0", |
4 | 4 | "plugins": [ |
5 | 5 | { |
6 | - "id": "deomarchy.sysmon", | |
6 | + "id": "deomarchy.plugin-manager", | |
7 | + "name": "Plugin Manager", | |
8 | + "description": "Manage installed plugins and discover 1,000+ community extensions from OmarchyPlugins.com", | |
9 | + "author": "de-omarchy", | |
10 | + "category": "System", | |
11 | + "tags": ["management", "marketplace", "panel", "bar-widget"], | |
12 | + "kind": "panel", | |
13 | + "status": "stable", | |
14 | + "installCommand": "cp -r shell/plugins/plugin-manager ~/.config/omarchy/plugins/" | |
15 | + }, | |
16 | + { | |
17 | + "id": "omaplug", | |
18 | + "name": "Omaplug", | |
19 | + "description": "Standalone Omarchy plugin manager: enable/disable, update, install, and remove plugins", | |
20 | + "author": "Fross", | |
21 | + "category": "System", | |
22 | + "tags": ["management", "bar-widget"], | |
23 | + "kind": "bar-widget", | |
24 | + "status": "stable", | |
25 | + "installCommand": "git clone https://github.com/fross100/omaplug.git ~/.config/omarchy/plugins/omaplug" | |
26 | + }, | |
27 | + { | |
28 | + "id": "omarchy.sysmon", | |
7 | 29 | "name": "System Monitor", |
8 | 30 | "description": "CPU, RAM, disk, GPU temp, network in bar", |
9 | 31 | "author": "de-omarchy", |
10 | 32 | |
▼shell/plugins/plugin-library/Panel.qml
+1158-141
| @@ -1,4 +1,6 @@ | ||
1 | 1 | import QtQuick |
2 | +import QtQuick.Controls | |
3 | +import QtQuick.Layouts | |
2 | 4 | import Quickshell |
3 | 5 | import Quickshell.Io |
4 | 6 | import qs.Commons |
| @@ -6,219 +8,1234 @@ import qs.Ui | ||
6 | 8 | |
7 | 9 | Panel { |
8 | 10 | id: root |
9 | - moduleName: "deomarchy.plugin-library" | |
11 | + moduleName: "deomarchy.plugin-manager" | |
12 | + ipcTarget: "deomarchy.plugin-manager" | |
10 | 13 | |
11 | - property var plugins: [] | |
12 | - property int selectedIndex: -1 | |
13 | - property string statusText: "" | |
14 | - property bool installing: false | |
14 | + implicitWidth: 700 | |
15 | + implicitHeight: 720 | |
15 | 16 | |
16 | - implicitWidth: 520 | |
17 | - implicitHeight: 600 | |
17 | + property var anchorItem: null | |
18 | + property var hostWidget: null | |
19 | + readonly property var barIdentity: hostWidget || root | |
18 | 20 | |
19 | - Component.onCompleted: loadRegistry() | |
21 | + // ---------------------------------------------------------------- State | |
22 | + property string currentTab: "installed" // "installed" | "marketplace" | |
23 | + property var installedPlugins: [] | |
24 | + property var marketplacePlugins: [] | |
25 | + property var filteredInstalled: [] | |
26 | + property var filteredMarketplace: [] | |
27 | + property var shortcutsMap: ({}) | |
28 | + property var barLayoutData: ({ inBar: [] }) | |
20 | 29 | |
21 | - function loadRegistry() { | |
22 | - var path = Quickshell.env("HOME") + "/.config/omarchy/plugins/registry.json" | |
23 | - var fallback = (Quickshell.env("OMARCHY_PATH") || "/usr/share/de-omarchy") + "/registry.json" | |
24 | - registryProc.command = ["bash", "-c", "cat " + path + " 2>/dev/null || cat " + fallback + " 2>/dev/null || echo '{\"plugins\":[]}'"] | |
25 | - registryProc.running = true | |
30 | + property string installedSearch: "" | |
31 | + property string installedFilter: "all" // "all" | "active" | "inactive" | "bar" | "builtin" | "user" | |
32 | + | |
33 | + property string marketSearch: "" | |
34 | + property string marketCategory: "All" | |
35 | + property string marketSort: "stars" // "stars" | "name" | "date" | |
36 | + | |
37 | + property string installingId: "" | |
38 | + property string statusMessage: "" | |
39 | + property string statusType: "info" | |
40 | + property bool updatingAll: false | |
41 | + | |
42 | + property bool urlDialogOpen: false | |
43 | + property string urlInput: "" | |
44 | + | |
45 | + property bool confirmUninstallOpen: false | |
46 | + property var pendingUninstallPlugin: null | |
47 | + | |
48 | + property bool shortcutDialogOpen: false | |
49 | + property var shortcutTargetPlugin: null | |
50 | + property string shortcutInput: "" | |
51 | + | |
52 | + property bool barSectionDialogOpen: false | |
53 | + property var barTargetPlugin: null | |
54 | + | |
55 | + readonly property var registry: root.bar && root.bar.shell | |
56 | + ? root.bar.shell.pluginRegistry | |
57 | + : (root.bar ? root.bar.pluginRegistry : null) | |
58 | + | |
59 | + Component.onCompleted: { | |
60 | + loadInstalled() | |
61 | + loadMarketplace() | |
62 | + loadShortcuts() | |
63 | + loadBarLayout() | |
64 | + } | |
65 | + | |
66 | + onOpenedChanged: { | |
67 | + if (opened) { | |
68 | + loadInstalled() | |
69 | + loadMarketplace() | |
70 | + loadShortcuts() | |
71 | + loadBarLayout() | |
72 | + } | |
73 | + } | |
74 | + | |
75 | + // ---------------------------------------------------------------- Loading | |
76 | + function loadInstalled() { | |
77 | + var om = Quickshell.env("OMARCHY_PATH") || "/usr/share/de-omarchy" | |
78 | + installedProc.command = ["bash", "-c", "omarchy-shell shell listPlugins 2>/dev/null || " + om + "/bin/omarchy-plugins list --json 2>/dev/null || omarchy plugins list --json 2>/dev/null || echo '[]'"] | |
79 | + installedProc.running = true | |
80 | + } | |
81 | + | |
82 | + function loadMarketplace() { | |
83 | + var home = Quickshell.env("HOME") | |
84 | + var om = Quickshell.env("OMARCHY_PATH") || "/usr/share/de-omarchy" | |
85 | + marketProc.command = ["bash", "-c", "cat " + home + "/.cache/omarchy/marketplace_catalog.json 2>/dev/null | jq -c '.plugins // []' 2>/dev/null || " + om + "/bin/omarchy-plugins marketplace --json 2>/dev/null || cat " + om + "/registry.json 2>/dev/null | jq -c '.plugins // []' 2>/dev/null || echo '[]'"] | |
86 | + marketProc.running = true | |
87 | + } | |
88 | + | |
89 | + function loadShortcuts() { | |
90 | + shortcutsProc.command = ["bash", "-c", "omarchy plugins shortcut list --json 2>/dev/null || echo '[]'"] | |
91 | + shortcutsProc.running = true | |
92 | + } | |
93 | + | |
94 | + function loadBarLayout() { | |
95 | + barProc.command = ["bash", "-c", "omarchy plugins bar list 2>/dev/null || echo '{\"inBar\":[]}'"] | |
96 | + barProc.running = true | |
26 | 97 | } |
27 | 98 | |
28 | 99 | Process { |
29 | - id: registryProc | |
100 | + id: installedProc | |
30 | 101 | running: false |
31 | - stdout: SplitParser { | |
32 | - onRead: function(line) { | |
102 | + stdout: StdioCollector { | |
103 | + waitForEnd: true | |
104 | + onStreamFinished: { | |
33 | 105 | try { |
34 | - var data = JSON.parse(String(line).trim()) | |
35 | - if (data && data.plugins) root.plugins = data.plugins | |
36 | - } catch(e) {} | |
106 | + var arr = JSON.parse(String(text).trim()) | |
107 | + if (Array.isArray(arr)) { | |
108 | + root.installedPlugins = arr | |
109 | + root.applyInstalledFilter() | |
110 | + } | |
111 | + } catch (e) { | |
112 | + console.warn("Failed to parse installed plugins: " + e) | |
113 | + } | |
114 | + } | |
115 | + } | |
116 | + } | |
117 | + | |
118 | + Process { | |
119 | + id: marketProc | |
120 | + running: false | |
121 | + stdout: StdioCollector { | |
122 | + waitForEnd: true | |
123 | + onStreamFinished: { | |
124 | + try { | |
125 | + var data = JSON.parse(String(text).trim()) | |
126 | + var arr = Array.isArray(data) ? data : (data && data.plugins ? data.plugins : []) | |
127 | + if (Array.isArray(arr)) { | |
128 | + root.marketplacePlugins = arr | |
129 | + root.applyMarketFilter() | |
130 | + } | |
131 | + } catch (e) { | |
132 | + console.warn("Failed to parse marketplace plugins: " + e) | |
133 | + } | |
134 | + } | |
135 | + } | |
136 | + } | |
137 | + | |
138 | + Process { | |
139 | + id: shortcutsProc | |
140 | + running: false | |
141 | + stdout: StdioCollector { | |
142 | + waitForEnd: true | |
143 | + onStreamFinished: { | |
144 | + try { | |
145 | + var arr = JSON.parse(String(text).trim()) | |
146 | + var map = {} | |
147 | + if (Array.isArray(arr)) { | |
148 | + for (var i = 0; i < arr.length; i++) { | |
149 | + if (arr[i] && arr[i].id) { | |
150 | + map[arr[i].id] = arr[i].keys || "" | |
151 | + } | |
152 | + } | |
153 | + } | |
154 | + root.shortcutsMap = map | |
155 | + } catch (e) { | |
156 | + console.warn("Failed to parse shortcuts: " + e) | |
157 | + } | |
37 | 158 | } |
38 | 159 | } |
39 | 160 | } |
40 | 161 | |
41 | 162 | Process { |
42 | - id: installProc | |
163 | + id: barProc | |
43 | 164 | running: false |
44 | - onRunningChanged: { | |
45 | - if (!running) { | |
46 | - root.installing = false | |
47 | - root.statusText = root.installExitCode === 0 ? "Installed!" : "Install failed" | |
48 | - timer.restart() | |
165 | + stdout: StdioCollector { | |
166 | + waitForEnd: true | |
167 | + onStreamFinished: { | |
168 | + try { | |
169 | + var data = JSON.parse(String(text).trim()) | |
170 | + if (data && data.inBar) { | |
171 | + root.barLayoutData = data | |
172 | + root.applyInstalledFilter() | |
173 | + } | |
174 | + } catch (e) { | |
175 | + console.warn("Failed to parse bar layout: " + e) | |
176 | + } | |
177 | + } | |
178 | + } | |
179 | + } | |
180 | + | |
181 | + // ---------------------------------------------------------------- Filtering | |
182 | + function isPluginInBar(id) { | |
183 | + if (!id || !root.barLayoutData || !root.barLayoutData.inBar) return false | |
184 | + return root.barLayoutData.inBar.indexOf(id) !== -1 | |
185 | + } | |
186 | + | |
187 | + function applyInstalledFilter() { | |
188 | + var list = root.installedPlugins || [] | |
189 | + var q = root.installedSearch.toLowerCase().trim() | |
190 | + var filter = root.installedFilter | |
191 | + | |
192 | + var res = [] | |
193 | + for (var i = 0; i < list.length; i++) { | |
194 | + var p = list[i] | |
195 | + if (filter === "active" && !p.enabled) continue | |
196 | + if (filter === "inactive" && p.enabled) continue | |
197 | + if (filter === "bar" && !isPluginInBar(p.id)) continue | |
198 | + if (filter === "builtin" && !p.firstParty) continue | |
199 | + if (filter === "user" && p.firstParty) continue | |
200 | + | |
201 | + if (q.length > 0) { | |
202 | + var name = (p.name || "").toLowerCase() | |
203 | + var id = (p.id || "").toLowerCase() | |
204 | + var kinds = (p.kinds || []).join(" ").toLowerCase() | |
205 | + if (name.indexOf(q) === -1 && id.indexOf(q) === -1 && kinds.indexOf(q) === -1) continue | |
206 | + } | |
207 | + res.push(p) | |
208 | + } | |
209 | + root.filteredInstalled = res | |
210 | + } | |
211 | + | |
212 | + function applyMarketFilter() { | |
213 | + var list = root.marketplacePlugins || [] | |
214 | + var q = root.marketSearch.toLowerCase().trim() | |
215 | + var cat = root.marketCategory | |
216 | + | |
217 | + var res = [] | |
218 | + for (var i = 0; i < list.length; i++) { | |
219 | + var p = list[i] | |
220 | + if (cat !== "All" && (p.category || "").toLowerCase() !== cat.toLowerCase()) continue | |
221 | + | |
222 | + if (q.length > 0) { | |
223 | + var name = (p.name || "").toLowerCase() | |
224 | + var id = (p.id || "").toLowerCase() | |
225 | + var desc = (p.description || "").toLowerCase() | |
226 | + var author = (p.author || "").toLowerCase() | |
227 | + var tags = (p.tags || []).join(" ").toLowerCase() | |
228 | + if (name.indexOf(q) === -1 && id.indexOf(q) === -1 && desc.indexOf(q) === -1 && author.indexOf(q) === -1 && tags.indexOf(q) === -1) continue | |
229 | + } | |
230 | + res.push(p) | |
231 | + } | |
232 | + | |
233 | + if (root.marketSort === "stars") { | |
234 | + res.sort(function(a, b) { return (b.stars || 0) - (a.stars || 0) }) | |
235 | + } else if (root.marketSort === "name") { | |
236 | + res.sort(function(a, b) { return (a.name || a.id).localeCompare(b.name || b.id) }) | |
237 | + } else if (root.marketSort === "date") { | |
238 | + res.sort(function(a, b) { return String(b.addedAt || "").localeCompare(String(a.addedAt || "")) }) | |
239 | + } | |
240 | + | |
241 | + root.filteredMarketplace = res | |
242 | + } | |
243 | + | |
244 | + onInstalledSearchChanged: applyInstalledFilter() | |
245 | + onInstalledFilterChanged: applyInstalledFilter() | |
246 | + onMarketSearchChanged: applyMarketFilter() | |
247 | + onMarketCategoryChanged: applyMarketFilter() | |
248 | + onMarketSortChanged: applyMarketFilter() | |
249 | + | |
250 | + // ---------------------------------------------------------------- Helpers | |
251 | + function isInstalled(id) { | |
252 | + if (!id) return false | |
253 | + var cleanId = id.replace(/^[a-z0-9_-]+\./, "") | |
254 | + for (var i = 0; i < root.installedPlugins.length; i++) { | |
255 | + var instId = root.installedPlugins[i].id | |
256 | + if (instId === id || instId === cleanId || instId.endsWith("." + id) || id.endsWith("." + instId)) { | |
257 | + return true | |
49 | 258 | } |
50 | 259 | } |
51 | - property int installExitCode: 0 | |
52 | - onExited: function(code) { installExitCode = code } | |
260 | + return false | |
261 | + } | |
262 | + | |
263 | + function showToast(msg, type) { | |
264 | + root.statusMessage = msg | |
265 | + root.statusType = type || "info" | |
266 | + toastTimer.restart() | |
53 | 267 | } |
54 | 268 | |
55 | 269 | Timer { |
56 | - id: timer | |
57 | - interval: 3000 | |
58 | - onTriggered: root.statusText = "" | |
270 | + id: toastTimer | |
271 | + interval: 3500 | |
272 | + onTriggered: root.statusMessage = "" | |
273 | + } | |
274 | + | |
275 | + function togglePlugin(plugin) { | |
276 | + if (!plugin || !plugin.id) return | |
277 | + var targetState = !plugin.enabled | |
278 | + plugin.enabled = targetState | |
279 | + applyInstalledFilter() | |
280 | + | |
281 | + if (root.registry && typeof root.registry.setEnabled === "function") { | |
282 | + root.registry.setEnabled(plugin.id, targetState) | |
283 | + } | |
284 | + | |
285 | + actionProc.command = ["bash", "-c", "omarchy plugins " + (targetState ? "enable" : "disable") + " '" + plugin.id + "'"] | |
286 | + actionProc.targetAction = "toggle" | |
287 | + actionProc.targetName = plugin.name || plugin.id | |
288 | + actionProc.targetState = targetState | |
289 | + actionProc.running = true | |
290 | + } | |
291 | + | |
292 | + function toggleBarPlacement(plugin) { | |
293 | + if (!plugin || !plugin.id) return | |
294 | + if (isPluginInBar(plugin.id)) { | |
295 | + actionProc.command = ["bash", "-c", "omarchy plugins bar remove '" + plugin.id + "'"] | |
296 | + actionProc.targetAction = "bar_remove" | |
297 | + actionProc.targetName = plugin.name || plugin.id | |
298 | + actionProc.running = true | |
299 | + } else { | |
300 | + root.barTargetPlugin = plugin | |
301 | + root.barSectionDialogOpen = true | |
302 | + } | |
59 | 303 | } |
60 | 304 | |
61 | - function installPlugin(plugin) { | |
62 | - if (root.installing) return | |
63 | - root.installing = true | |
64 | - root.statusText = "Installing " + plugin.name + "..." | |
65 | - var pluginDir = Quickshell.env("HOME") + "/.config/omarchy/plugins" | |
66 | - installProc.command = ["bash", "-c", "mkdir -p " + pluginDir + " && " + plugin.installCommand] | |
67 | - installProc.running = true | |
305 | + function addPluginToBar(plugin, section) { | |
306 | + root.barSectionDialogOpen = false | |
307 | + root.barTargetPlugin = null | |
308 | + if (!plugin || !plugin.id) return | |
309 | + | |
310 | + showToast("Adding " + (plugin.name || plugin.id) + " to bar (" + section + ")...", "info") | |
311 | + actionProc.command = ["bash", "-c", "omarchy plugins bar add '" + plugin.id + "' " + section] | |
312 | + actionProc.targetAction = "bar_add" | |
313 | + actionProc.targetName = plugin.name || plugin.id | |
314 | + actionProc.running = true | |
315 | + } | |
316 | + | |
317 | + function openShortcutDialog(plugin) { | |
318 | + root.shortcutTargetPlugin = plugin | |
319 | + root.shortcutInput = root.shortcutsMap[plugin.id] || "" | |
320 | + root.shortcutDialogOpen = true | |
321 | + } | |
322 | + | |
323 | + function saveShortcut() { | |
324 | + var p = root.shortcutTargetPlugin | |
325 | + var keys = root.shortcutInput.trim() | |
326 | + root.shortcutDialogOpen = false | |
327 | + root.shortcutTargetPlugin = null | |
328 | + if (!p) return | |
329 | + | |
330 | + if (keys === "") { | |
331 | + actionProc.command = ["bash", "-c", "omarchy plugins shortcut remove '" + p.id + "'"] | |
332 | + actionProc.targetAction = "shortcut_remove" | |
333 | + actionProc.targetName = p.name || p.id | |
334 | + actionProc.running = true | |
335 | + } else { | |
336 | + actionProc.command = ["bash", "-c", "omarchy plugins shortcut set '" + p.id + "' '" + keys + "'"] | |
337 | + actionProc.targetAction = "shortcut_set" | |
338 | + actionProc.targetName = p.name || p.id | |
339 | + actionProc.running = true | |
340 | + } | |
341 | + } | |
342 | + | |
343 | + function clearShortcut() { | |
344 | + root.shortcutInput = "" | |
345 | + saveShortcut() | |
346 | + } | |
347 | + | |
348 | + function installPlugin(p) { | |
349 | + if (!p || root.installingId !== "") return | |
350 | + root.installingId = p.id | |
351 | + showToast("Installing " + (p.name || p.id) + "...", "info") | |
352 | + | |
353 | + actionProc.command = ["bash", "-c", "omarchy plugins install '" + p.id + "' --enable"] | |
354 | + actionProc.targetAction = "install" | |
355 | + actionProc.targetName = p.name || p.id | |
356 | + actionProc.running = true | |
357 | + } | |
358 | + | |
359 | + function installFromUrl(url) { | |
360 | + if (!url || url.trim() === "") return | |
361 | + url = url.trim() | |
362 | + root.urlDialogOpen = false | |
363 | + root.urlInput = "" | |
364 | + showToast("Installing: " + url + "...", "info") | |
365 | + | |
366 | + actionProc.command = ["bash", "-c", "omarchy plugins install '" + url + "' --enable"] | |
367 | + actionProc.targetAction = "install" | |
368 | + actionProc.targetName = url | |
369 | + actionProc.running = true | |
370 | + } | |
371 | + | |
372 | + function confirmUninstall(p) { | |
373 | + root.pendingUninstallPlugin = p | |
374 | + root.confirmUninstallOpen = true | |
375 | + } | |
376 | + | |
377 | + function executeUninstall() { | |
378 | + var p = root.pendingUninstallPlugin | |
379 | + root.confirmUninstallOpen = false | |
380 | + root.pendingUninstallPlugin = null | |
381 | + if (!p) return | |
382 | + | |
383 | + showToast("Removing " + (p.name || p.id) + "...", "info") | |
384 | + actionProc.command = ["bash", "-c", "omarchy plugins remove '" + p.id + "'"] | |
385 | + actionProc.targetAction = "remove" | |
386 | + actionProc.targetName = p.name || p.id | |
387 | + actionProc.running = true | |
388 | + } | |
389 | + | |
390 | + Process { | |
391 | + id: actionProc | |
392 | + running: false | |
393 | + property string targetAction: "" | |
394 | + property string targetName: "" | |
395 | + property bool targetState: false | |
396 | + | |
397 | + onExited: function(code) { | |
398 | + root.installingId = "" | |
399 | + root.updatingAll = false | |
400 | + if (code === 0) { | |
401 | + if (targetAction === "install") { | |
402 | + root.showToast("✓ Installed " + targetName + "!", "success") | |
403 | + } else if (targetAction === "remove") { | |
404 | + root.showToast("✓ Removed " + targetName + ".", "success") | |
405 | + } else if (targetAction === "toggle") { | |
406 | + root.showToast("✓ " + (targetState ? "Enabled" : "Disabled") + " " + targetName, "success") | |
407 | + } else if (targetAction === "bar_add") { | |
408 | + root.showToast("✓ Added " + targetName + " to top bar!", "success") | |
409 | + } else if (targetAction === "bar_remove") { | |
410 | + root.showToast("✓ Removed " + targetName + " from bar.", "success") | |
411 | + } else if (targetAction === "shortcut_set") { | |
412 | + root.showToast("✓ Shortcut saved for " + targetName + "!", "success") | |
413 | + } else if (targetAction === "shortcut_remove") { | |
414 | + root.showToast("✓ Shortcut cleared for " + targetName, "success") | |
415 | + } | |
416 | + } else { | |
417 | + root.showToast("✕ Action failed for " + targetName, "error") | |
418 | + } | |
419 | + root.loadInstalled() | |
420 | + root.loadShortcuts() | |
421 | + root.loadBarLayout() | |
422 | + } | |
68 | 423 | } |
69 | 424 | |
70 | - Column { | |
425 | + function getInitials(name) { | |
426 | + if (!name) return "?" | |
427 | + var parts = name.split(/[\s.-]+/) | |
428 | + if (parts.length >= 2 && parts[0].length > 0 && parts[1].length > 0) { | |
429 | + return (parts[0][0] + parts[1][0]).toUpperCase() | |
430 | + } | |
431 | + return name.substring(0, Math.min(2, name.length)).toUpperCase() | |
432 | + } | |
433 | + | |
434 | + // ---------------------------------------------------------------- Content Layout | |
435 | + ColumnLayout { | |
71 | 436 | anchors.fill: parent |
72 | - anchors.margins: 16 | |
437 | + anchors.margins: 14 | |
73 | 438 | spacing: 12 |
74 | 439 | |
75 | - Row { | |
76 | - width: parent.width | |
77 | - spacing: 12 | |
440 | + // Header | |
441 | + RowLayout { | |
442 | + Layout.fillWidth: true | |
443 | + spacing: 10 | |
78 | 444 | |
79 | 445 | Text { |
80 | - text: "Plugin Library" | |
446 | + text: "🧩 Plugin Manager" | |
81 | 447 | color: Color.foreground |
82 | 448 | font.family: root.bar ? root.bar.fontFamily : Style.font.family |
83 | - font.pixelSize: 20 | |
449 | + font.pixelSize: 18 | |
84 | 450 | font.bold: true |
85 | - anchors.verticalCenter: parent.verticalCenter | |
86 | 451 | } |
87 | 452 | |
88 | - Item { width: 1; height: 1 } | |
453 | + Item { Layout.fillWidth: true } | |
454 | + | |
455 | + // Tab switcher | |
456 | + Rectangle { | |
457 | + Layout.preferredWidth: 260 | |
458 | + Layout.preferredHeight: 32 | |
459 | + radius: 16 | |
460 | + color: Qt.rgba(0, 0, 0, 0.25) | |
461 | + border.color: Qt.rgba(255, 255, 255, 0.08) | |
462 | + border.width: 1 | |
463 | + | |
464 | + RowLayout { | |
465 | + anchors.fill: parent | |
466 | + spacing: 0 | |
467 | + | |
468 | + Rectangle { | |
469 | + Layout.fillWidth: true | |
470 | + Layout.fillHeight: true | |
471 | + radius: 14 | |
472 | + color: root.currentTab === "installed" ? Color.accent : "transparent" | |
473 | + | |
474 | + Text { | |
475 | + anchors.centerIn: parent | |
476 | + text: "📦 Installed (" + root.installedPlugins.length + ")" | |
477 | + color: root.currentTab === "installed" ? Color.background : Color.foreground | |
478 | + font.pixelSize: 11 | |
479 | + font.bold: root.currentTab === "installed" | |
480 | + } | |
481 | + | |
482 | + MouseArea { | |
483 | + anchors.fill: parent | |
484 | + cursorShape: Qt.PointingHandCursor | |
485 | + onClicked: root.currentTab = "installed" | |
486 | + } | |
487 | + } | |
488 | + | |
489 | + Rectangle { | |
490 | + Layout.fillWidth: true | |
491 | + Layout.fillHeight: true | |
492 | + radius: 14 | |
493 | + color: root.currentTab === "marketplace" ? Color.accent : "transparent" | |
494 | + | |
495 | + Text { | |
496 | + anchors.centerIn: parent | |
497 | + text: "🌐 Marketplace" | |
498 | + color: root.currentTab === "marketplace" ? Color.background : Color.foreground | |
499 | + font.pixelSize: 11 | |
500 | + font.bold: root.currentTab === "marketplace" | |
501 | + } | |
89 | 502 | |
503 | + MouseArea { | |
504 | + anchors.fill: parent | |
505 | + cursorShape: Qt.PointingHandCursor | |
506 | + onClicked: { | |
507 | + root.currentTab = "marketplace" | |
508 | + if (root.marketplacePlugins.length === 0) root.loadMarketplace() | |
509 | + } | |
510 | + } | |
511 | + } | |
512 | + } | |
513 | + } | |
514 | + | |
515 | + // Add Git URL Button | |
90 | 516 | Rectangle { |
91 | - width: statusText.length > 0 ? statusLabel.implicitWidth + 20 : 0 | |
92 | - height: 24 | |
93 | - radius: 12 | |
94 | - color: installing ? Qt.rgba(0.2, 0.5, 0.9, 0.3) : Qt.rgba(0.2, 0.8, 0.3, 0.3) | |
95 | - visible: statusText.length > 0 | |
96 | - anchors.verticalCenter: parent.verticalCenter | |
517 | + Layout.preferredHeight: 30 | |
518 | + Layout.preferredWidth: 32 | |
519 | + radius: 6 | |
520 | + color: Qt.rgba(255, 255, 255, 0.08) | |
521 | + border.color: Qt.rgba(255, 255, 255, 0.12) | |
522 | + border.width: 1 | |
97 | 523 | |
98 | 524 | Text { |
99 | - id: statusLabel | |
100 | 525 | anchors.centerIn: parent |
101 | - text: root.statusText | |
526 | + text: "+" | |
102 | 527 | color: Color.foreground |
103 | - font.family: root.bar ? root.bar.fontFamily : Style.font.family | |
104 | - font.pixelSize: 11 | |
528 | + font.pixelSize: 16 | |
529 | + font.bold: true | |
530 | + } | |
531 | + | |
532 | + MouseArea { | |
533 | + anchors.fill: parent | |
534 | + cursorShape: Qt.PointingHandCursor | |
535 | + onClicked: root.urlDialogOpen = true | |
105 | 536 | } |
106 | 537 | } |
107 | 538 | } |
108 | 539 | |
109 | - Text { | |
110 | - text: plugins.length + " plugins available" | |
111 | - color: Color.subtext | |
112 | - font.family: root.bar ? root.bar.fontFamily : Style.font.family | |
113 | - font.pixelSize: 12 | |
540 | + // Status Banner Toast | |
541 | + Rectangle { | |
542 | + Layout.fillWidth: true | |
543 | + Layout.preferredHeight: root.statusMessage !== "" ? 30 : 0 | |
544 | + visible: root.statusMessage !== "" | |
545 | + radius: 6 | |
546 | + color: root.statusType === "success" ? Qt.rgba(0.2, 0.8, 0.3, 0.2) : Qt.rgba(0.3, 0.6, 0.9, 0.2) | |
547 | + border.color: root.statusType === "success" ? "#a6e3a1" : "#89b4fa" | |
548 | + border.width: 1 | |
549 | + clip: true | |
550 | + | |
551 | + Text { | |
552 | + anchors.centerIn: parent | |
553 | + text: root.statusMessage | |
554 | + color: Color.foreground | |
555 | + font.pixelSize: 11 | |
556 | + font.bold: true | |
557 | + } | |
114 | 558 | } |
115 | 559 | |
116 | - ListView { | |
117 | - id: listView | |
118 | - width: parent.width | |
119 | - height: parent.height - 80 | |
120 | - clip: true | |
121 | - spacing: 8 | |
122 | - model: root.plugins | |
123 | - | |
124 | - delegate: Rectangle { | |
125 | - required property var modelData | |
126 | - required property int index | |
127 | - width: listView.width | |
128 | - height: 90 | |
129 | - radius: 8 | |
130 | - color: index === root.selected ? Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, 0.15) : Qt.rgba(1, 1, 1, 0.05) | |
131 | - border.color: index === root.selected ? Color.accent : Qt.rgba(1, 1, 1, 0.1) | |
132 | - border.width: index === root.selected ? 1 : 0 | |
133 | - | |
134 | - Column { | |
560 | + // Stack Views | |
561 | + StackLayout { | |
562 | + Layout.fillWidth: true | |
563 | + Layout.fillHeight: true | |
564 | + currentIndex: root.currentTab === "installed" ? 0 : 1 | |
565 | + | |
566 | + // TAB 1: INSTALLED | |
567 | + Item { | |
568 | + ColumnLayout { | |
135 | 569 | anchors.fill: parent |
136 | - anchors.margins: 10 | |
137 | - spacing: 4 | |
138 | - | |
139 | - Row { | |
140 | - width: parent.width | |
141 | - spacing: 8 | |
142 | - | |
143 | - Rectangle { | |
144 | - width: 36; height: 36; radius: 8 | |
145 | - color: Qt.rgba(0.4, 0.5, 0.9, 0.2) | |
146 | - Text { | |
147 | - anchors.centerIn: parent | |
148 | - text: modelData.name ? modelData.name.charAt(0) : "?" | |
570 | + spacing: 8 | |
571 | + | |
572 | + // Search Bar | |
573 | + Rectangle { | |
574 | + Layout.fillWidth: true | |
575 | + Layout.preferredHeight: 32 | |
576 | + radius: 6 | |
577 | + color: Qt.rgba(0, 0, 0, 0.2) | |
578 | + border.color: Qt.rgba(255, 255, 255, 0.1) | |
579 | + border.width: 1 | |
580 | + | |
581 | + RowLayout { | |
582 | + anchors.fill: parent | |
583 | + anchors.margins: 6 | |
584 | + spacing: 6 | |
585 | + Text { text: "🔍"; font.pixelSize: 10; color: Color.subtext } | |
586 | + TextInput { | |
587 | + id: installedSearchField | |
588 | + Layout.fillWidth: true | |
589 | + text: root.installedSearch | |
149 | 590 | color: Color.foreground |
150 | - font.family: root.bar ? root.bar.fontFamily : Style.font.family | |
151 | - font.pixelSize: 16 | |
152 | - font.bold: true | |
591 | + font.pixelSize: 11 | |
592 | + onTextChanged: root.installedSearch = text | |
593 | + Text { | |
594 | + text: "Filter installed..." | |
595 | + color: Color.subtext | |
596 | + font.pixelSize: 11 | |
597 | + visible: !installedSearchField.text | |
598 | + } | |
153 | 599 | } |
154 | 600 | } |
601 | + } | |
155 | 602 | |
156 | - Column { | |
157 | - width: parent.width - 120 | |
158 | - spacing: 2 | |
603 | + // Filter Chips | |
604 | + RowLayout { | |
605 | + spacing: 6 | |
606 | + Repeater { | |
607 | + model: [ | |
608 | + { id: "all", label: "All" }, | |
609 | + { id: "active", label: "Active" }, | |
610 | + { id: "inactive", label: "Inactive" }, | |
611 | + { id: "bar", label: "📌 In Bar" }, | |
612 | + { id: "user", label: "User" } | |
613 | + ] | |
614 | + delegate: Rectangle { | |
615 | + required property var modelData | |
616 | + Layout.preferredHeight: 24 | |
617 | + Layout.preferredWidth: chipText.implicitWidth + 14 | |
618 | + radius: 12 | |
619 | + color: root.installedFilter === modelData.id ? Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, 0.25) : Qt.rgba(255, 255, 255, 0.05) | |
620 | + border.color: root.installedFilter === modelData.id ? Color.accent : "transparent" | |
621 | + border.width: 1 | |
159 | 622 | |
160 | - Text { | |
161 | - text: modelData.name || "Unknown" | |
162 | - color: Color.foreground | |
163 | - font.family: root.bar ? root.bar.fontFamily : Style.font.family | |
164 | - font.pixelSize: 13 | |
165 | - font.bold: true | |
166 | - elide: Text.ElideRight | |
623 | + Text { | |
624 | + id: chipText | |
625 | + anchors.centerIn: parent | |
626 | + text: modelData.label | |
627 | + color: Color.foreground | |
628 | + font.pixelSize: 10 | |
629 | + } | |
630 | + | |
631 | + MouseArea { | |
632 | + anchors.fill: parent | |
633 | + cursorShape: Qt.PointingHandCursor | |
634 | + onClicked: root.installedFilter = modelData.id | |
635 | + } | |
167 | 636 | } |
637 | + } | |
638 | + } | |
168 | 639 | |
169 | - Text { | |
170 | - text: modelData.description || "" | |
171 | - color: Color.subtext | |
172 | - font.family: root.bar ? root.bar.fontFamily : Style.font.family | |
173 | - font.pixelSize: 11 | |
174 | - elide: Text.ElideRight | |
175 | - width: parent.width | |
640 | + // List | |
641 | + ListView { | |
642 | + Layout.fillWidth: true | |
643 | + Layout.fillHeight: true | |
644 | + clip: true | |
645 | + spacing: 6 | |
646 | + model: root.filteredInstalled | |
647 | + | |
648 | + delegate: Rectangle { | |
649 | + required property var modelData | |
650 | + property bool inBar: root.isPluginInBar(modelData.id) | |
651 | + property string shortcut: root.shortcutsMap[modelData.id] || "" | |
652 | + property bool isBarCompatible: { | |
653 | + var kinds = modelData.kinds || [] | |
654 | + return kinds.indexOf("bar-widget") !== -1 || kinds.indexOf("bar") !== -1 || kinds.indexOf("panel") !== -1 | |
176 | 655 | } |
177 | 656 | |
178 | - Row { | |
179 | - spacing: 6 | |
180 | - Text { | |
181 | - text: modelData.author || "" | |
182 | - color: Color.subtext | |
183 | - font.family: root.bar ? root.bar.fontFamily : Style.font.family | |
184 | - font.pixelSize: 10 | |
657 | + width: parent.width | |
658 | + height: 66 | |
659 | + radius: 8 | |
660 | + color: Qt.rgba(255, 255, 255, modelData.enabled ? 0.08 : 0.03) | |
661 | + border.color: Qt.rgba(255, 255, 255, 0.08) | |
662 | + border.width: 1 | |
663 | + | |
664 | + RowLayout { | |
665 | + anchors.fill: parent | |
666 | + anchors.margins: 8 | |
667 | + spacing: 8 | |
668 | + | |
669 | + Rectangle { | |
670 | + Layout.preferredWidth: 36 | |
671 | + Layout.preferredHeight: 36 | |
672 | + radius: 8 | |
673 | + color: Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, 0.2) | |
674 | + Text { | |
675 | + anchors.centerIn: parent | |
676 | + text: root.getInitials(modelData.name || modelData.id) | |
677 | + color: Color.foreground | |
678 | + font.pixelSize: 12 | |
679 | + font.bold: true | |
680 | + } | |
681 | + } | |
682 | + | |
683 | + ColumnLayout { | |
684 | + Layout.fillWidth: true | |
685 | + spacing: 2 | |
686 | + Text { | |
687 | + text: modelData.name || modelData.id | |
688 | + color: Color.foreground | |
689 | + font.pixelSize: 12 | |
690 | + font.bold: true | |
691 | + } | |
692 | + Text { | |
693 | + text: modelData.id + (modelData.firstParty ? " (Built-in)" : " (User)") | |
694 | + color: Color.subtext | |
695 | + font.pixelSize: 10 | |
696 | + } | |
697 | + } | |
698 | + | |
699 | + // Bar placement toggle | |
700 | + Rectangle { | |
701 | + visible: isBarCompatible | |
702 | + Layout.preferredHeight: 26 | |
703 | + Layout.preferredWidth: pBarText.implicitWidth + 12 | |
704 | + radius: 4 | |
705 | + color: inBar ? Qt.rgba(0.2, 0.8, 0.3, 0.18) : Qt.rgba(255, 255, 255, 0.06) | |
706 | + border.color: inBar ? "#a6e3a1" : Qt.rgba(255, 255, 255, 0.1) | |
707 | + border.width: 1 | |
708 | + | |
709 | + Text { | |
710 | + id: pBarText | |
711 | + anchors.centerIn: parent | |
712 | + text: inBar ? "📌 In Bar" : "➕ Bar" | |
713 | + color: inBar ? "#a6e3a1" : Color.subtext | |
714 | + font.pixelSize: 9 | |
715 | + font.bold: inBar | |
716 | + } | |
717 | + | |
718 | + MouseArea { | |
719 | + anchors.fill: parent | |
720 | + cursorShape: Qt.PointingHandCursor | |
721 | + onClicked: root.toggleBarPlacement(modelData) | |
722 | + } | |
723 | + } | |
724 | + | |
725 | + // Shortcut button | |
726 | + Rectangle { | |
727 | + Layout.preferredHeight: 26 | |
728 | + Layout.preferredWidth: pScText.implicitWidth + 12 | |
729 | + radius: 4 | |
730 | + color: shortcut !== "" ? Qt.rgba(1, 0.7, 0.2, 0.18) : Qt.rgba(255, 255, 255, 0.06) | |
731 | + border.color: shortcut !== "" ? "#ff9e64" : Qt.rgba(255, 255, 255, 0.1) | |
732 | + border.width: 1 | |
733 | + | |
734 | + Text { | |
735 | + id: pScText | |
736 | + anchors.centerIn: parent | |
737 | + text: shortcut !== "" ? "⌨ " + shortcut : "⌨ Key" | |
738 | + color: shortcut !== "" ? "#ff9e64" : Color.subtext | |
739 | + font.pixelSize: 9 | |
740 | + font.bold: shortcut !== "" | |
741 | + } | |
742 | + | |
743 | + MouseArea { | |
744 | + anchors.fill: parent | |
745 | + cursorShape: Qt.PointingHandCursor | |
746 | + onClicked: root.openShortcutDialog(modelData) | |
747 | + } | |
748 | + } | |
749 | + | |
750 | + // Delete button for user plugins | |
751 | + Rectangle { | |
752 | + visible: !modelData.firstParty | |
753 | + Layout.preferredWidth: 26 | |
754 | + Layout.preferredHeight: 26 | |
755 | + radius: 4 | |
756 | + color: Qt.rgba(1, 0, 0, 0.15) | |
757 | + Text { anchors.centerIn: parent; text: "🗑"; font.pixelSize: 10 } | |
758 | + MouseArea { | |
759 | + anchors.fill: parent | |
760 | + cursorShape: Qt.PointingHandCursor | |
761 | + onClicked: root.confirmUninstall(modelData) | |
762 | + } | |
763 | + } | |
764 | + | |
765 | + // Switch | |
766 | + Rectangle { | |
767 | + Layout.preferredWidth: 44 | |
768 | + Layout.preferredHeight: 24 | |
769 | + radius: 12 | |
770 | + color: modelData.enabled ? Color.accent : Qt.rgba(255, 255, 255, 0.15) | |
771 | + border.color: Qt.rgba(255, 255, 255, 0.1) | |
772 | + border.width: 1 | |
773 | + | |
774 | + Rectangle { | |
775 | + width: 18 | |
776 | + height: 18 | |
777 | + radius: 9 | |
778 | + color: "#ffffff" | |
779 | + anchors.verticalCenter: parent.verticalCenter | |
780 | + x: modelData.enabled ? 22 : 4 | |
781 | + Behavior on x { NumberAnimation { duration: 120 } } | |
782 | + } | |
783 | + | |
784 | + MouseArea { | |
785 | + anchors.fill: parent | |
786 | + cursorShape: Qt.PointingHandCursor | |
787 | + onClicked: root.togglePlugin(modelData) | |
788 | + } | |
185 | 789 | } |
790 | + } | |
791 | + } | |
792 | + } | |
793 | + } | |
794 | + } | |
795 | + | |
796 | + // TAB 2: MARKETPLACE | |
797 | + Item { | |
798 | + ColumnLayout { | |
799 | + anchors.fill: parent | |
800 | + spacing: 8 | |
801 | + | |
802 | + // Search Bar | |
803 | + Rectangle { | |
804 | + Layout.fillWidth: true | |
805 | + Layout.preferredHeight: 32 | |
806 | + radius: 6 | |
807 | + color: Qt.rgba(0, 0, 0, 0.2) | |
808 | + border.color: Qt.rgba(255, 255, 255, 0.1) | |
809 | + border.width: 1 | |
810 | + | |
811 | + RowLayout { | |
812 | + anchors.fill: parent | |
813 | + anchors.margins: 6 | |
814 | + spacing: 6 | |
815 | + Text { text: "🌐"; font.pixelSize: 10; color: Color.subtext } | |
816 | + TextInput { | |
817 | + id: marketSearchField | |
818 | + Layout.fillWidth: true | |
819 | + text: root.marketSearch | |
820 | + color: Color.foreground | |
821 | + font.pixelSize: 11 | |
822 | + onTextChanged: root.marketSearch = text | |
186 | 823 | Text { |
187 | - text: (modelData.tags || []).join(", ") | |
824 | + text: "Search 1,000+ plugins..." | |
188 | 825 | color: Color.subtext |
189 | - font.family: root.bar ? root.bar.fontFamily : Style.font.family | |
190 | - font.pixelSize: 10 | |
826 | + font.pixelSize: 11 | |
827 | + visible: !marketSearchField.text | |
191 | 828 | } |
192 | 829 | } |
193 | 830 | } |
831 | + } | |
194 | 832 | |
195 | - Rectangle { | |
196 | - width: 60; height: 28; radius: 6 | |
197 | - color: root.installing ? Qt.rgba(1,1,1,0.05) : Qt.rgba(0.3, 0.7, 0.3, 0.3) | |
198 | - anchors.verticalCenter: parent.verticalCenter | |
833 | + // Category Chips | |
834 | + ScrollView { | |
835 | + Layout.fillWidth: true | |
836 | + Layout.preferredHeight: 28 | |
837 | + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff | |
838 | + ScrollBar.vertical.policy: ScrollBar.AlwaysOff | |
199 | 839 | |
200 | - Text { | |
201 | - anchors.centerIn: parent | |
202 | - text: root.installing ? "..." : "Install" | |
203 | - color: root.installing ? Color.subtext : Color.foreground | |
204 | - font.family: root.bar ? root.bar.fontFamily : Style.font.family | |
205 | - font.pixelSize: 11 | |
206 | - font.bold: true | |
840 | + RowLayout { | |
841 | + spacing: 6 | |
842 | + Repeater { | |
843 | + model: ["All", "Appearance", "Desktop", "Productivity", "System", "Utilities", "Widgets"] | |
844 | + delegate: Rectangle { | |
845 | + required property var modelData | |
846 | + Layout.preferredHeight: 24 | |
847 | + Layout.preferredWidth: mCatText.implicitWidth + 14 | |
848 | + radius: 12 | |
849 | + color: root.marketCategory === modelData ? Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, 0.25) : Qt.rgba(255, 255, 255, 0.05) | |
850 | + border.color: root.marketCategory === modelData ? Color.accent : "transparent" | |
851 | + border.width: 1 | |
852 | + | |
853 | + Text { | |
854 | + id: mCatText | |
855 | + anchors.centerIn: parent | |
856 | + text: modelData | |
857 | + color: Color.foreground | |
858 | + font.pixelSize: 10 | |
859 | + } | |
860 | + | |
861 | + MouseArea { | |
862 | + anchors.fill: parent | |
863 | + cursorShape: Qt.PointingHandCursor | |
864 | + onClicked: root.marketCategory = modelData | |
865 | + } | |
866 | + } | |
207 | 867 | } |
868 | + } | |
869 | + } | |
870 | + | |
871 | + // List | |
872 | + ListView { | |
873 | + Layout.fillWidth: true | |
874 | + Layout.fillHeight: true | |
875 | + clip: true | |
876 | + spacing: 6 | |
877 | + model: root.filteredMarketplace | |
208 | 878 | |
209 | - MouseArea { | |
879 | + delegate: Rectangle { | |
880 | + required property var modelData | |
881 | + property bool installed: root.isInstalled(modelData.id) | |
882 | + width: parent.width | |
883 | + height: 74 | |
884 | + radius: 8 | |
885 | + color: Qt.rgba(255, 255, 255, 0.05) | |
886 | + border.color: Qt.rgba(255, 255, 255, 0.08) | |
887 | + border.width: 1 | |
888 | + | |
889 | + RowLayout { | |
210 | 890 | anchors.fill: parent |
211 | - cursorShape: Qt.PointingHandCursor | |
212 | - enabled: !root.installing | |
213 | - onClicked: root.installPlugin(modelData) | |
891 | + anchors.margins: 10 | |
892 | + spacing: 10 | |
893 | + | |
894 | + Rectangle { | |
895 | + Layout.preferredWidth: 38 | |
896 | + Layout.preferredHeight: 38 | |
897 | + radius: 8 | |
898 | + color: Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, 0.2) | |
899 | + Text { | |
900 | + anchors.centerIn: parent | |
901 | + text: modelData.initials || root.getInitials(modelData.name || modelData.id) | |
902 | + color: Color.foreground | |
903 | + font.pixelSize: 13 | |
904 | + font.bold: true | |
905 | + } | |
906 | + } | |
907 | + | |
908 | + ColumnLayout { | |
909 | + Layout.fillWidth: true | |
910 | + spacing: 2 | |
911 | + RowLayout { | |
912 | + spacing: 6 | |
913 | + Text { | |
914 | + text: modelData.name || modelData.id | |
915 | + color: Color.foreground | |
916 | + font.pixelSize: 12 | |
917 | + font.bold: true | |
918 | + } | |
919 | + Text { | |
920 | + visible: (modelData.stars || 0) > 0 | |
921 | + text: "★ " + modelData.stars | |
922 | + color: "#e0af68" | |
923 | + font.pixelSize: 10 | |
924 | + } | |
925 | + } | |
926 | + Text { | |
927 | + Layout.fillWidth: true | |
928 | + text: modelData.description || "" | |
929 | + color: Color.subtext | |
930 | + font.pixelSize: 10 | |
931 | + elide: Text.ElideRight | |
932 | + } | |
933 | + } | |
934 | + | |
935 | + // Install button | |
936 | + Rectangle { | |
937 | + Layout.preferredHeight: 28 | |
938 | + Layout.preferredWidth: installed ? 78 : 66 | |
939 | + radius: 6 | |
940 | + color: installed ? Qt.rgba(0.2, 0.8, 0.3, 0.18) : (root.installingId === modelData.id ? Qt.rgba(255, 255, 255, 0.08) : Color.accent) | |
941 | + border.color: installed ? "#a6e3a1" : "transparent" | |
942 | + border.width: installed ? 1 : 0 | |
943 | + | |
944 | + Text { | |
945 | + anchors.centerIn: parent | |
946 | + text: installed ? "✓ Installed" : (root.installingId === modelData.id ? "..." : "Install") | |
947 | + color: installed ? "#a6e3a1" : (root.installingId === modelData.id ? Color.subtext : Color.background) | |
948 | + font.pixelSize: 10 | |
949 | + font.bold: true | |
950 | + } | |
951 | + | |
952 | + MouseArea { | |
953 | + anchors.fill: parent | |
954 | + cursorShape: Qt.PointingHandCursor | |
955 | + enabled: !installed && root.installingId === "" | |
956 | + onClicked: root.installPlugin(modelData) | |
957 | + } | |
958 | + } | |
214 | 959 | } |
215 | 960 | } |
216 | 961 | } |
217 | 962 | } |
963 | + } | |
964 | + } | |
965 | + } | |
966 | + | |
967 | + // Modals | |
968 | + // 1. Shortcut Dialog | |
969 | + Rectangle { | |
970 | + anchors.fill: parent | |
971 | + color: Qt.rgba(0, 0, 0, 0.75) | |
972 | + visible: root.shortcutDialogOpen | |
973 | + | |
974 | + MouseArea { anchors.fill: parent; onClicked: root.shortcutDialogOpen = false } | |
975 | + | |
976 | + Rectangle { | |
977 | + anchors.centerIn: parent | |
978 | + width: 440 | |
979 | + height: 240 | |
980 | + radius: 10 | |
981 | + color: Color.popups ? Color.popups.background : "#1f2335" | |
982 | + border.color: "#ff9e64" | |
983 | + border.width: 1 | |
984 | + | |
985 | + MouseArea { anchors.fill: parent } | |
986 | + | |
987 | + ColumnLayout { | |
988 | + anchors.fill: parent | |
989 | + anchors.margins: 16 | |
990 | + spacing: 10 | |
991 | + | |
992 | + Text { | |
993 | + text: "⌨ Set Keyboard Shortcut" | |
994 | + color: Color.foreground | |
995 | + font.pixelSize: 14 | |
996 | + font.bold: true | |
997 | + } | |
218 | 998 | |
219 | - MouseArea { | |
220 | - anchors.fill: parent | |
221 | - onClicked: root.selectedIndex = index | |
999 | + Text { | |
1000 | + text: "Set Hyprland keybind for '" + (root.shortcutTargetPlugin ? (root.shortcutTargetPlugin.name || root.shortcutTargetPlugin.id) : "") + "':" | |
1001 | + color: Color.subtext | |
1002 | + font.pixelSize: 11 | |
1003 | + } | |
1004 | + | |
1005 | + Rectangle { | |
1006 | + Layout.fillWidth: true | |
1007 | + Layout.preferredHeight: 34 | |
1008 | + radius: 6 | |
1009 | + color: Qt.rgba(0, 0, 0, 0.25) | |
1010 | + border.color: "#ff9e64" | |
1011 | + border.width: 1 | |
1012 | + | |
1013 | + TextInput { | |
1014 | + anchors.fill: parent | |
1015 | + anchors.margins: 6 | |
1016 | + text: root.shortcutInput | |
1017 | + color: Color.foreground | |
1018 | + font.pixelSize: 12 | |
1019 | + font.bold: true | |
1020 | + onTextChanged: root.shortcutInput = text | |
1021 | + Text { | |
1022 | + text: "e.g. SUPER + ALT + D" | |
1023 | + color: Color.subtext | |
1024 | + font.pixelSize: 11 | |
1025 | + visible: !parent.text | |
1026 | + } | |
1027 | + } | |
1028 | + } | |
1029 | + | |
1030 | + RowLayout { | |
1031 | + Layout.fillWidth: true | |
1032 | + Button { | |
1033 | + text: "Clear" | |
1034 | + onClicked: root.clearShortcut() | |
1035 | + } | |
1036 | + Item { Layout.fillWidth: true } | |
1037 | + Button { | |
1038 | + text: "Cancel" | |
1039 | + onClicked: root.shortcutDialogOpen = false | |
1040 | + } | |
1041 | + Button { | |
1042 | + text: "Save & Apply" | |
1043 | + onClicked: root.saveShortcut() | |
1044 | + } | |
1045 | + } | |
1046 | + } | |
1047 | + } | |
1048 | + } | |
1049 | + | |
1050 | + // 2. Bar Section Selector | |
1051 | + Rectangle { | |
1052 | + anchors.fill: parent | |
1053 | + color: Qt.rgba(0, 0, 0, 0.75) | |
1054 | + visible: root.barSectionDialogOpen | |
1055 | + | |
1056 | + MouseArea { anchors.fill: parent; onClicked: root.barSectionDialogOpen = false } | |
1057 | + | |
1058 | + Rectangle { | |
1059 | + anchors.centerIn: parent | |
1060 | + width: 380 | |
1061 | + height: 180 | |
1062 | + radius: 10 | |
1063 | + color: Color.popups ? Color.popups.background : "#1f2335" | |
1064 | + border.color: "#a6e3a1" | |
1065 | + border.width: 1 | |
1066 | + | |
1067 | + MouseArea { anchors.fill: parent } | |
1068 | + | |
1069 | + ColumnLayout { | |
1070 | + anchors.fill: parent | |
1071 | + anchors.margins: 16 | |
1072 | + spacing: 12 | |
1073 | + | |
1074 | + Text { | |
1075 | + text: "📌 Add to Omarchy Top Bar" | |
1076 | + color: Color.foreground | |
1077 | + font.pixelSize: 14 | |
1078 | + font.bold: true | |
1079 | + } | |
1080 | + | |
1081 | + Text { | |
1082 | + text: "Select bar section:" | |
1083 | + color: Color.subtext | |
1084 | + font.pixelSize: 11 | |
1085 | + } | |
1086 | + | |
1087 | + RowLayout { | |
1088 | + Layout.fillWidth: true | |
1089 | + spacing: 8 | |
1090 | + | |
1091 | + Button { | |
1092 | + Layout.fillWidth: true | |
1093 | + text: "Left" | |
1094 | + onClicked: root.addPluginToBar(root.barTargetPlugin, "left") | |
1095 | + } | |
1096 | + Button { | |
1097 | + Layout.fillWidth: true | |
1098 | + text: "Center" | |
1099 | + onClicked: root.addPluginToBar(root.barTargetPlugin, "center") | |
1100 | + } | |
1101 | + Button { | |
1102 | + Layout.fillWidth: true | |
1103 | + text: "Right" | |
1104 | + onClicked: root.addPluginToBar(root.barTargetPlugin, "right") | |
1105 | + } | |
1106 | + } | |
1107 | + | |
1108 | + RowLayout { | |
1109 | + Layout.fillWidth: true | |
1110 | + Item { Layout.fillWidth: true } | |
1111 | + Button { | |
1112 | + text: "Cancel" | |
1113 | + onClicked: root.barSectionDialogOpen = false | |
1114 | + } | |
1115 | + } | |
1116 | + } | |
1117 | + } | |
1118 | + } | |
1119 | + | |
1120 | + // 3. Git URL Dialog Modal | |
1121 | + Rectangle { | |
1122 | + anchors.fill: parent | |
1123 | + color: Qt.rgba(0, 0, 0, 0.7) | |
1124 | + visible: root.urlDialogOpen | |
1125 | + | |
1126 | + MouseArea { anchors.fill: parent; onClicked: root.urlDialogOpen = false } | |
1127 | + | |
1128 | + Rectangle { | |
1129 | + anchors.centerIn: parent | |
1130 | + width: 420 | |
1131 | + height: 180 | |
1132 | + radius: 10 | |
1133 | + color: Color.popups ? Color.popups.background : "#1f2335" | |
1134 | + border.color: Qt.rgba(255, 255, 255, 0.15) | |
1135 | + border.width: 1 | |
1136 | + | |
1137 | + MouseArea { anchors.fill: parent } | |
1138 | + | |
1139 | + ColumnLayout { | |
1140 | + anchors.fill: parent | |
1141 | + anchors.margins: 16 | |
1142 | + spacing: 10 | |
1143 | + | |
1144 | + Text { | |
1145 | + text: "Install from Git Repository" | |
1146 | + color: Color.foreground | |
1147 | + font.pixelSize: 14 | |
1148 | + font.bold: true | |
1149 | + } | |
1150 | + | |
1151 | + Rectangle { | |
1152 | + Layout.fillWidth: true | |
1153 | + Layout.preferredHeight: 34 | |
1154 | + radius: 6 | |
1155 | + color: Qt.rgba(0, 0, 0, 0.25) | |
1156 | + border.color: Color.accent | |
1157 | + border.width: 1 | |
1158 | + | |
1159 | + TextInput { | |
1160 | + anchors.fill: parent | |
1161 | + anchors.margins: 6 | |
1162 | + text: root.urlInput | |
1163 | + color: Color.foreground | |
1164 | + font.pixelSize: 11 | |
1165 | + onTextChanged: root.urlInput = text | |
1166 | + Text { | |
1167 | + text: "https://github.com/..." | |
1168 | + color: Color.subtext | |
1169 | + font.pixelSize: 11 | |
1170 | + visible: !parent.text | |
1171 | + } | |
1172 | + } | |
1173 | + } | |
1174 | + | |
1175 | + RowLayout { | |
1176 | + Layout.fillWidth: true | |
1177 | + Item { Layout.fillWidth: true } | |
1178 | + Button { | |
1179 | + text: "Cancel" | |
1180 | + onClicked: root.urlDialogOpen = false | |
1181 | + } | |
1182 | + Button { | |
1183 | + text: "Install" | |
1184 | + onClicked: root.installFromUrl(root.urlInput) | |
1185 | + } | |
1186 | + } | |
1187 | + } | |
1188 | + } | |
1189 | + } | |
1190 | + | |
1191 | + // 4. Confirm Uninstall Dialog | |
1192 | + Rectangle { | |
1193 | + anchors.fill: parent | |
1194 | + color: Qt.rgba(0, 0, 0, 0.7) | |
1195 | + visible: root.confirmUninstallOpen | |
1196 | + | |
1197 | + MouseArea { anchors.fill: parent; onClicked: root.confirmUninstallOpen = false } | |
1198 | + | |
1199 | + Rectangle { | |
1200 | + anchors.centerIn: parent | |
1201 | + width: 380 | |
1202 | + height: 150 | |
1203 | + radius: 10 | |
1204 | + color: Color.popups ? Color.popups.background : "#1f2335" | |
1205 | + border.color: Qt.rgba(255, 255, 255, 0.15) | |
1206 | + border.width: 1 | |
1207 | + | |
1208 | + MouseArea { anchors.fill: parent } | |
1209 | + | |
1210 | + ColumnLayout { | |
1211 | + anchors.fill: parent | |
1212 | + anchors.margins: 16 | |
1213 | + spacing: 10 | |
1214 | + | |
1215 | + Text { | |
1216 | + text: "Uninstall Plugin" | |
1217 | + color: "#f7768e" | |
1218 | + font.pixelSize: 14 | |
1219 | + font.bold: true | |
1220 | + } | |
1221 | + | |
1222 | + Text { | |
1223 | + text: "Remove '" + (root.pendingUninstallPlugin ? (root.pendingUninstallPlugin.name || root.pendingUninstallPlugin.id) : "") + "'?" | |
1224 | + color: Color.foreground | |
1225 | + font.pixelSize: 11 | |
1226 | + } | |
1227 | + | |
1228 | + RowLayout { | |
1229 | + Layout.fillWidth: true | |
1230 | + Item { Layout.fillWidth: true } | |
1231 | + Button { | |
1232 | + text: "Cancel" | |
1233 | + onClicked: root.confirmUninstallOpen = false | |
1234 | + } | |
1235 | + Button { | |
1236 | + text: "Remove" | |
1237 | + onClicked: root.executeUninstall() | |
1238 | + } | |
222 | 1239 | } |
223 | 1240 | } |
224 | 1241 | } |
225 | 1242 | |
▼shell/plugins/plugin-manager/BarWidget.qml
+70-0
| @@ -0,0 +1,70 @@ | ||
1 | +import QtQuick | |
2 | +import Quickshell | |
3 | +import Quickshell.Io | |
4 | +import qs.Commons | |
5 | +import qs.Ui | |
6 | + | |
7 | +BarWidget { | |
8 | + id: root | |
9 | + moduleName: "deomarchy.plugin-manager" | |
10 | + | |
11 | + readonly property bool opened: panelItem ? panelItem.opened === true : false | |
12 | + | |
13 | + function open() { if (panelItem) panelItem.open() } | |
14 | + function close() { if (panelItem) panelItem.close() } | |
15 | + function togglePanel() { if (panelItem) panelItem.toggle() } | |
16 | + | |
17 | + readonly property bool popoutSwitchClosing: panelItem ? panelItem.popoutSwitchClosing === true : false | |
18 | + function closeForPopoutSwitch() { if (panelItem) panelItem.closeForPopoutSwitch() } | |
19 | + | |
20 | + property var panelItem: null | |
21 | + | |
22 | + function injectPanel() { | |
23 | + var target = panelLoader.item | |
24 | + if (!target) return | |
25 | + panelItem = target | |
26 | + if ("bar" in target) target.bar = root.bar | |
27 | + if ("settings" in target) target.settings = root.settings | |
28 | + if ("anchorItem" in target) target.anchorItem = button | |
29 | + if ("hostWidget" in target) target.hostWidget = root | |
30 | + } | |
31 | + | |
32 | + implicitWidth: button.implicitWidth | |
33 | + implicitHeight: button.implicitHeight | |
34 | + | |
35 | + onBarChanged: injectPanel() | |
36 | + onSettingsChanged: injectPanel() | |
37 | + | |
38 | + Loader { | |
39 | + id: panelLoader | |
40 | + active: true | |
41 | + source: Qt.resolvedUrl("Panel.qml") | |
42 | + visible: false | |
43 | + onLoaded: { | |
44 | + root.injectPanel() | |
45 | + Qt.callLater(root.injectPanel) | |
46 | + } | |
47 | + } | |
48 | + | |
49 | + IpcHandler { | |
50 | + target: "deomarchy.plugin-manager" | |
51 | + | |
52 | + function open(): void { root.open() } | |
53 | + function close(): void { root.close() } | |
54 | + function toggle(): void { root.togglePanel() } | |
55 | + } | |
56 | + | |
57 | + BarIconButton { | |
58 | + id: button | |
59 | + anchors.fill: parent | |
60 | + bar: root.bar | |
61 | + // md-puzzle from JetBrainsMono Nerd Font — the family the bar already | |
62 | + // renders with — so this matches the other monochrome widget icons. | |
63 | + text: String.fromCodePoint(0xF0327) | |
64 | + tooltipText: root.opened ? "Close Plugin Manager" : "Plugin Manager" | |
65 | + | |
66 | + onPressed: function(b) { | |
67 | + if (b === Qt.LeftButton) root.togglePanel() | |
68 | + } | |
69 | + } | |
70 | +} | |
0 | 71 | |
▼shell/plugins/plugin-manager/Panel.qml
+1323-0
| @@ -0,0 +1,1323 @@ | ||
1 | +import QtQuick | |
2 | +import QtQuick.Controls | |
3 | +import QtQuick.Layouts | |
4 | +import Quickshell | |
5 | +import Quickshell.Io | |
6 | +import qs.Commons | |
7 | +import qs.Ui | |
8 | + | |
9 | +Panel { | |
10 | + id: root | |
11 | + moduleName: "deomarchy.plugin-manager" | |
12 | + ipcTarget: "deomarchy.plugin-manager" | |
13 | + | |
14 | + implicitWidth: 700 | |
15 | + implicitHeight: 720 | |
16 | + | |
17 | + property var anchorItem: null | |
18 | + property var hostWidget: null | |
19 | + readonly property var barIdentity: hostWidget || root | |
20 | + | |
21 | + // ---------------------------------------------------------------- State | |
22 | + property string currentTab: "installed" // "installed" | "marketplace" | |
23 | + property var installedPlugins: [] | |
24 | + property var marketplacePlugins: [] | |
25 | + property var filteredInstalled: [] | |
26 | + property var filteredMarketplace: [] | |
27 | + property var shortcutsMap: ({}) | |
28 | + property var barLayoutData: ({ inBar: [] }) | |
29 | + | |
30 | + property string installedSearch: "" | |
31 | + property string installedFilter: "all" // "all" | "active" | "inactive" | "bar" | "builtin" | "user" | |
32 | + | |
33 | + property string marketSearch: "" | |
34 | + property string marketCategory: "All" | |
35 | + property string marketSort: "stars" // "stars" | "name" | "date" | |
36 | + | |
37 | + property string installingId: "" | |
38 | + property string statusMessage: "" | |
39 | + property string statusType: "info" | |
40 | + property bool updatingAll: false | |
41 | + | |
42 | + property bool urlDialogOpen: false | |
43 | + property string urlInput: "" | |
44 | + | |
45 | + property bool confirmUninstallOpen: false | |
46 | + property var pendingUninstallPlugin: null | |
47 | + | |
48 | + property bool shortcutDialogOpen: false | |
49 | + property var shortcutTargetPlugin: null | |
50 | + property string shortcutInput: "" | |
51 | + | |
52 | + property bool barSectionDialogOpen: false | |
53 | + property var barTargetPlugin: null | |
54 | + | |
55 | + readonly property var registry: root.bar && root.bar.shell | |
56 | + ? root.bar.shell.pluginRegistry | |
57 | + : (root.bar ? root.bar.pluginRegistry : null) | |
58 | + | |
59 | + // ---------------------------------------------------------------- Iconography | |
60 | + // | |
61 | + // Nerd Font glyph codepoints (same family the bar renders with) instead of | |
62 | + // colored emoji. Most glyphs live beyond the BMP, so they are stored as | |
63 | + // ints and resolved with String.fromCodePoint. | |
64 | + readonly property string nfFontFamily: "JetBrainsMono Nerd Font" | |
65 | + function nf(code) { return String.fromCodePoint(code) } | |
66 | + readonly property var ic: ({ | |
67 | + puzzle: 0xF0327, // md-puzzle | |
68 | + package: 0xF03D3, // md-package | |
69 | + globe: 0xF00AC, // fa-globe | |
70 | + magnify: 0xF0349, // md-magnify | |
71 | + close: 0xF0156, // md-close | |
72 | + check: 0xF012C, // md-check | |
73 | + closeCircle: 0xF0159, // md-close_circle | |
74 | + checkCircle: 0xF05E0, // md-check_circle | |
75 | + pin: 0xF0403, // md-pin | |
76 | + plus: 0xF0415, // md-plus | |
77 | + keyboard: 0xF097B, // md-keyboard_outline | |
78 | + trash: 0xF1F8, // fa-trash | |
79 | + star: 0xF04CE // md-star | |
80 | + }) | |
81 | + | |
82 | + // Per-plugin glyph, mirroring the Plugin Library window's map. | |
83 | + function getPluginIcon(id) { | |
84 | + var map = { | |
85 | + "omarchy.menu": 0xF0463, // md-rocket | |
86 | + "omarchy.active-window": 0xF05B1, // md-window_open | |
87 | + "omarchy.workspaces": 0xF0570, // md-view_grid | |
88 | + "omarchy.bar": 0xF056E, // md-view_dashboard | |
89 | + "omarchy.agents": 0xF06A9, // md-robot | |
90 | + "omarchy.audio": 0xF057E, // md-volume_high | |
91 | + "omarchy.bluetooth": 0xF00AF, // md-bluetooth | |
92 | + "omarchy.network": 0xF05A9, // md-wifi | |
93 | + "omarchy.clock": 0xF0150, // md-clock_outline | |
94 | + "omarchy.battery": 0xF0079, // md-battery | |
95 | + "omarchy.power": 0xF0425, // md-power | |
96 | + "omarchy.sysmon": 0xF029A, // md-gauge | |
97 | + "omarchy.weather": 0xF0599, // md-weather_sunny | |
98 | + "omarchy.media": 0xF075A, // md-music | |
99 | + "omarchy.notifications": 0xF009A, // md-bell | |
100 | + "omarchy.clipboard": 0xF014D, // md-clipboard_text | |
101 | + "omarchy.emojis": 0xF01F2, // md-emoticon_outline | |
102 | + "omarchy.indicators": 0xF0335, // md-lightbulb | |
103 | + "omarchy.keyboard-layout": 0xF097B, // md-keyboard_outline | |
104 | + "omarchy.lock": 0xF033E, // md-lock | |
105 | + "omarchy.nightlight": 0xF0594, // md-weather_night | |
106 | + "omarchy.microphone": 0xF036C, // md-microphone | |
107 | + "omarchy.tray": 0xF1294, // md-tray | |
108 | + "omarchy.background": 0xF02E9, // md-image | |
109 | + "omarchy.idle": 0xF051F, // md-timer_sand | |
110 | + "omarchy.polkit": 0xF0BC5, // md-shield_key_outline | |
111 | + "omarchy.reminders": 0xF13AB, // md-timer | |
112 | + "omarchy.spacer": 0xF084E, // md-arrow_expand_horizontal | |
113 | + "omarchy.speedtest": 0xF04C5, // md-speedometer | |
114 | + "omarchy.system-update": 0xF06B0, // md-update | |
115 | + "omarchy.tailscale": 0xF0582, // md-vpn | |
116 | + "omarchy.wifiqr": 0xF0432, // md-qrcode | |
117 | + "deomarchy.docker-status": 0xF0868, // md-docker | |
118 | + "deomarchy.wallpaper-manager": 0xF0E09, // md-wallpaper | |
119 | + "deomarchy.power-profile": 0xF0241, // md-flash | |
120 | + "deomarchy.system-overview": 0xF154D, // md-chart_box | |
121 | + "deomarchy.monitor-manager": 0xF0379, // md-monitor | |
122 | + "deomarchy.plugin-manager": 0xF0327, // md-puzzle | |
123 | + "deomarchy.plugin-library": 0xF0327, // md-puzzle | |
124 | + "nosignal.motion-wallpaper": 0xF022F, // md-film | |
125 | + "omaplug": 0xF06A5 // md-power_plug | |
126 | + } | |
127 | + if (map[id] !== undefined) return String.fromCodePoint(map[id]) | |
128 | + return String.fromCodePoint(0xF0068) // md-auto_fix | |
129 | + } | |
130 | + | |
131 | + Component.onCompleted: { | |
132 | + loadInstalled() | |
133 | + loadMarketplace() | |
134 | + loadShortcuts() | |
135 | + loadBarLayout() | |
136 | + } | |
137 | + | |
138 | + onOpenedChanged: { | |
139 | + if (opened) { | |
140 | + loadInstalled() | |
141 | + loadMarketplace() | |
142 | + loadShortcuts() | |
143 | + loadBarLayout() | |
144 | + } | |
145 | + } | |
146 | + | |
147 | + // ---------------------------------------------------------------- Loading | |
148 | + function loadInstalled() { | |
149 | + var om = Quickshell.env("OMARCHY_PATH") || "/usr/share/de-omarchy" | |
150 | + installedProc.command = ["bash", "-c", "omarchy-shell shell listPlugins 2>/dev/null || " + om + "/bin/omarchy-plugins list --json 2>/dev/null || omarchy plugins list --json 2>/dev/null || echo '[]'"] | |
151 | + installedProc.running = true | |
152 | + } | |
153 | + | |
154 | + function loadMarketplace() { | |
155 | + var home = Quickshell.env("HOME") | |
156 | + var om = Quickshell.env("OMARCHY_PATH") || "/usr/share/de-omarchy" | |
157 | + marketProc.command = ["bash", "-c", "cat " + home + "/.cache/omarchy/marketplace_catalog.json 2>/dev/null | jq -c '.plugins // []' 2>/dev/null || " + om + "/bin/omarchy-plugins marketplace --json 2>/dev/null || cat " + om + "/registry.json 2>/dev/null | jq -c '.plugins // []' 2>/dev/null || echo '[]'"] | |
158 | + marketProc.running = true | |
159 | + } | |
160 | + | |
161 | + function loadShortcuts() { | |
162 | + shortcutsProc.command = ["bash", "-c", "omarchy plugins shortcut list --json 2>/dev/null || echo '[]'"] | |
163 | + shortcutsProc.running = true | |
164 | + } | |
165 | + | |
166 | + function loadBarLayout() { | |
167 | + barProc.command = ["bash", "-c", "omarchy plugins bar list 2>/dev/null || echo '{\"inBar\":[]}'"] | |
168 | + barProc.running = true | |
169 | + } | |
170 | + | |
171 | + Process { | |
172 | + id: installedProc | |
173 | + running: false | |
174 | + stdout: StdioCollector { | |
175 | + waitForEnd: true | |
176 | + onStreamFinished: { | |
177 | + try { | |
178 | + var arr = JSON.parse(String(text).trim()) | |
179 | + if (Array.isArray(arr)) { | |
180 | + root.installedPlugins = arr | |
181 | + root.applyInstalledFilter() | |
182 | + } | |
183 | + } catch (e) { | |
184 | + console.warn("Failed to parse installed plugins: " + e) | |
185 | + } | |
186 | + } | |
187 | + } | |
188 | + } | |
189 | + | |
190 | + Process { | |
191 | + id: marketProc | |
192 | + running: false | |
193 | + stdout: StdioCollector { | |
194 | + waitForEnd: true | |
195 | + onStreamFinished: { | |
196 | + try { | |
197 | + var data = JSON.parse(String(text).trim()) | |
198 | + var arr = Array.isArray(data) ? data : (data && data.plugins ? data.plugins : []) | |
199 | + if (Array.isArray(arr)) { | |
200 | + root.marketplacePlugins = arr | |
201 | + root.applyMarketFilter() | |
202 | + } | |
203 | + } catch (e) { | |
204 | + console.warn("Failed to parse marketplace plugins: " + e) | |
205 | + } | |
206 | + } | |
207 | + } | |
208 | + } | |
209 | + | |
210 | + Process { | |
211 | + id: shortcutsProc | |
212 | + running: false | |
213 | + stdout: StdioCollector { | |
214 | + waitForEnd: true | |
215 | + onStreamFinished: { | |
216 | + try { | |
217 | + var arr = JSON.parse(String(text).trim()) | |
218 | + var map = {} | |
219 | + if (Array.isArray(arr)) { | |
220 | + for (var i = 0; i < arr.length; i++) { | |
221 | + if (arr[i] && arr[i].id) { | |
222 | + map[arr[i].id] = arr[i].keys || "" | |
223 | + } | |
224 | + } | |
225 | + } | |
226 | + root.shortcutsMap = map | |
227 | + } catch (e) { | |
228 | + console.warn("Failed to parse shortcuts: " + e) | |
229 | + } | |
230 | + } | |
231 | + } | |
232 | + } | |
233 | + | |
234 | + Process { | |
235 | + id: barProc | |
236 | + running: false | |
237 | + stdout: StdioCollector { | |
238 | + waitForEnd: true | |
239 | + onStreamFinished: { | |
240 | + try { | |
241 | + var data = JSON.parse(String(text).trim()) | |
242 | + if (data && data.inBar) { | |
243 | + root.barLayoutData = data | |
244 | + root.applyInstalledFilter() | |
245 | + } | |
246 | + } catch (e) { | |
247 | + console.warn("Failed to parse bar layout: " + e) | |
248 | + } | |
249 | + } | |
250 | + } | |
251 | + } | |
252 | + | |
253 | + // ---------------------------------------------------------------- Filtering | |
254 | + function isPluginInBar(id) { | |
255 | + if (!id || !root.barLayoutData || !root.barLayoutData.inBar) return false | |
256 | + return root.barLayoutData.inBar.indexOf(id) !== -1 | |
257 | + } | |
258 | + | |
259 | + function applyInstalledFilter() { | |
260 | + var list = root.installedPlugins || [] | |
261 | + var q = root.installedSearch.toLowerCase().trim() | |
262 | + var filter = root.installedFilter | |
263 | + | |
264 | + var res = [] | |
265 | + for (var i = 0; i < list.length; i++) { | |
266 | + var p = list[i] | |
267 | + if (filter === "active" && !p.enabled) continue | |
268 | + if (filter === "inactive" && p.enabled) continue | |
269 | + if (filter === "bar" && !isPluginInBar(p.id)) continue | |
270 | + if (filter === "builtin" && !p.firstParty) continue | |
271 | + if (filter === "user" && p.firstParty) continue | |
272 | + | |
273 | + if (q.length > 0) { | |
274 | + var name = (p.name || "").toLowerCase() | |
275 | + var id = (p.id || "").toLowerCase() | |
276 | + var kinds = (p.kinds || []).join(" ").toLowerCase() | |
277 | + if (name.indexOf(q) === -1 && id.indexOf(q) === -1 && kinds.indexOf(q) === -1) continue | |
278 | + } | |
279 | + res.push(p) | |
280 | + } | |
281 | + root.filteredInstalled = res | |
282 | + } | |
283 | + | |
284 | + function applyMarketFilter() { | |
285 | + var list = root.marketplacePlugins || [] | |
286 | + var q = root.marketSearch.toLowerCase().trim() | |
287 | + var cat = root.marketCategory | |
288 | + | |
289 | + var res = [] | |
290 | + for (var i = 0; i < list.length; i++) { | |
291 | + var p = list[i] | |
292 | + if (cat !== "All" && (p.category || "").toLowerCase() !== cat.toLowerCase()) continue | |
293 | + | |
294 | + if (q.length > 0) { | |
295 | + var name = (p.name || "").toLowerCase() | |
296 | + var id = (p.id || "").toLowerCase() | |
297 | + var desc = (p.description || "").toLowerCase() | |
298 | + var author = (p.author || "").toLowerCase() | |
299 | + var tags = (p.tags || []).join(" ").toLowerCase() | |
300 | + if (name.indexOf(q) === -1 && id.indexOf(q) === -1 && desc.indexOf(q) === -1 && author.indexOf(q) === -1 && tags.indexOf(q) === -1) continue | |
301 | + } | |
302 | + res.push(p) | |
303 | + } | |
304 | + | |
305 | + if (root.marketSort === "stars") { | |
306 | + res.sort(function(a, b) { return (b.stars || 0) - (a.stars || 0) }) | |
307 | + } else if (root.marketSort === "name") { | |
308 | + res.sort(function(a, b) { return (a.name || a.id).localeCompare(b.name || b.id) }) | |
309 | + } else if (root.marketSort === "date") { | |
310 | + res.sort(function(a, b) { return String(b.addedAt || "").localeCompare(String(a.addedAt || "")) }) | |
311 | + } | |
312 | + | |
313 | + root.filteredMarketplace = res | |
314 | + } | |
315 | + | |
316 | + onInstalledSearchChanged: applyInstalledFilter() | |
317 | + onInstalledFilterChanged: applyInstalledFilter() | |
318 | + onMarketSearchChanged: applyMarketFilter() | |
319 | + onMarketCategoryChanged: applyMarketFilter() | |
320 | + onMarketSortChanged: applyMarketFilter() | |
321 | + | |
322 | + // ---------------------------------------------------------------- Helpers | |
323 | + function isInstalled(id) { | |
324 | + if (!id) return false | |
325 | + var cleanId = id.replace(/^[a-z0-9_-]+\./, "") | |
326 | + for (var i = 0; i < root.installedPlugins.length; i++) { | |
327 | + var instId = root.installedPlugins[i].id | |
328 | + if (instId === id || instId === cleanId || instId.endsWith("." + id) || id.endsWith("." + instId)) { | |
329 | + return true | |
330 | + } | |
331 | + } | |
332 | + return false | |
333 | + } | |
334 | + | |
335 | + function showToast(msg, type) { | |
336 | + root.statusMessage = msg | |
337 | + root.statusType = type || "info" | |
338 | + toastTimer.restart() | |
339 | + } | |
340 | + | |
341 | + Timer { | |
342 | + id: toastTimer | |
343 | + interval: 3500 | |
344 | + onTriggered: root.statusMessage = "" | |
345 | + } | |
346 | + | |
347 | + function togglePlugin(plugin) { | |
348 | + if (!plugin || !plugin.id) return | |
349 | + var targetState = !plugin.enabled | |
350 | + plugin.enabled = targetState | |
351 | + applyInstalledFilter() | |
352 | + | |
353 | + if (root.registry && typeof root.registry.setEnabled === "function") { | |
354 | + root.registry.setEnabled(plugin.id, targetState) | |
355 | + } | |
356 | + | |
357 | + actionProc.command = ["bash", "-c", "omarchy plugins " + (targetState ? "enable" : "disable") + " '" + plugin.id + "'"] | |
358 | + actionProc.targetAction = "toggle" | |
359 | + actionProc.targetName = plugin.name || plugin.id | |
360 | + actionProc.targetState = targetState | |
361 | + actionProc.running = true | |
362 | + } | |
363 | + | |
364 | + function toggleBarPlacement(plugin) { | |
365 | + if (!plugin || !plugin.id) return | |
366 | + if (isPluginInBar(plugin.id)) { | |
367 | + actionProc.command = ["bash", "-c", "omarchy plugins bar remove '" + plugin.id + "'"] | |
368 | + actionProc.targetAction = "bar_remove" | |
369 | + actionProc.targetName = plugin.name || plugin.id | |
370 | + actionProc.running = true | |
371 | + } else { | |
372 | + root.barTargetPlugin = plugin | |
373 | + root.barSectionDialogOpen = true | |
374 | + } | |
375 | + } | |
376 | + | |
377 | + function addPluginToBar(plugin, section) { | |
378 | + root.barSectionDialogOpen = false | |
379 | + root.barTargetPlugin = null | |
380 | + if (!plugin || !plugin.id) return | |
381 | + | |
382 | + showToast("Adding " + (plugin.name || plugin.id) + " to bar (" + section + ")...", "info") | |
383 | + actionProc.command = ["bash", "-c", "omarchy plugins bar add '" + plugin.id + "' " + section] | |
384 | + actionProc.targetAction = "bar_add" | |
385 | + actionProc.targetName = plugin.name || plugin.id | |
386 | + actionProc.running = true | |
387 | + } | |
388 | + | |
389 | + function openShortcutDialog(plugin) { | |
390 | + root.shortcutTargetPlugin = plugin | |
391 | + root.shortcutInput = root.shortcutsMap[plugin.id] || "" | |
392 | + root.shortcutDialogOpen = true | |
393 | + } | |
394 | + | |
395 | + function saveShortcut() { | |
396 | + var p = root.shortcutTargetPlugin | |
397 | + var keys = root.shortcutInput.trim() | |
398 | + root.shortcutDialogOpen = false | |
399 | + root.shortcutTargetPlugin = null | |
400 | + if (!p) return | |
401 | + | |
402 | + if (keys === "") { | |
403 | + actionProc.command = ["bash", "-c", "omarchy plugins shortcut remove '" + p.id + "'"] | |
404 | + actionProc.targetAction = "shortcut_remove" | |
405 | + actionProc.targetName = p.name || p.id | |
406 | + actionProc.running = true | |
407 | + } else { | |
408 | + actionProc.command = ["bash", "-c", "omarchy plugins shortcut set '" + p.id + "' '" + keys + "'"] | |
409 | + actionProc.targetAction = "shortcut_set" | |
410 | + actionProc.targetName = p.name || p.id | |
411 | + actionProc.running = true | |
412 | + } | |
413 | + } | |
414 | + | |
415 | + function clearShortcut() { | |
416 | + root.shortcutInput = "" | |
417 | + saveShortcut() | |
418 | + } | |
419 | + | |
420 | + function installPlugin(p) { | |
421 | + if (!p || root.installingId !== "") return | |
422 | + root.installingId = p.id | |
423 | + showToast("Installing " + (p.name || p.id) + "...", "info") | |
424 | + | |
425 | + actionProc.command = ["bash", "-c", "omarchy plugins install '" + p.id + "' --enable"] | |
426 | + actionProc.targetAction = "install" | |
427 | + actionProc.targetName = p.name || p.id | |
428 | + actionProc.running = true | |
429 | + } | |
430 | + | |
431 | + function installFromUrl(url) { | |
432 | + if (!url || url.trim() === "") return | |
433 | + url = url.trim() | |
434 | + root.urlDialogOpen = false | |
435 | + root.urlInput = "" | |
436 | + showToast("Installing: " + url + "...", "info") | |
437 | + | |
438 | + actionProc.command = ["bash", "-c", "omarchy plugins install '" + url + "' --enable"] | |
439 | + actionProc.targetAction = "install" | |
440 | + actionProc.targetName = url | |
441 | + actionProc.running = true | |
442 | + } | |
443 | + | |
444 | + function confirmUninstall(p) { | |
445 | + root.pendingUninstallPlugin = p | |
446 | + root.confirmUninstallOpen = true | |
447 | + } | |
448 | + | |
449 | + function executeUninstall() { | |
450 | + var p = root.pendingUninstallPlugin | |
451 | + root.confirmUninstallOpen = false | |
452 | + root.pendingUninstallPlugin = null | |
453 | + if (!p) return | |
454 | + | |
455 | + showToast("Removing " + (p.name || p.id) + "...", "info") | |
456 | + actionProc.command = ["bash", "-c", "omarchy plugins remove '" + p.id + "'"] | |
457 | + actionProc.targetAction = "remove" | |
458 | + actionProc.targetName = p.name || p.id | |
459 | + actionProc.running = true | |
460 | + } | |
461 | + | |
462 | + Process { | |
463 | + id: actionProc | |
464 | + running: false | |
465 | + property string targetAction: "" | |
466 | + property string targetName: "" | |
467 | + property bool targetState: false | |
468 | + | |
469 | + onExited: function(code) { | |
470 | + root.installingId = "" | |
471 | + root.updatingAll = false | |
472 | + if (code === 0) { | |
473 | + var ok = root.nf(root.ic.checkCircle) | |
474 | + if (targetAction === "install") { | |
475 | + root.showToast(ok + " Installed " + targetName + "!", "success") | |
476 | + } else if (targetAction === "remove") { | |
477 | + root.showToast(ok + " Removed " + targetName + "!", "success") | |
478 | + } else if (targetAction === "toggle") { | |
479 | + root.showToast(ok + " " + (targetState ? "Enabled" : "Disabled") + " " + targetName, "success") | |
480 | + } else if (targetAction === "bar_add") { | |
481 | + root.showToast(ok + " Added " + targetName + " to top bar!", "success") | |
482 | + } else if (targetAction === "bar_remove") { | |
483 | + root.showToast(ok + " Removed " + targetName + " from bar.", "success") | |
484 | + } else if (targetAction === "shortcut_set") { | |
485 | + root.showToast(ok + " Shortcut saved for " + targetName + "!", "success") | |
486 | + } else if (targetAction === "shortcut_remove") { | |
487 | + root.showToast(ok + " Shortcut cleared for " + targetName, "success") | |
488 | + } | |
489 | + } else { | |
490 | + root.showToast(root.nf(root.ic.closeCircle) + " Action failed for " + targetName, "error") | |
491 | + } | |
492 | + root.loadInstalled() | |
493 | + root.loadShortcuts() | |
494 | + root.loadBarLayout() | |
495 | + } | |
496 | + } | |
497 | + | |
498 | + function getInitials(name) { | |
499 | + if (!name) return "?" | |
500 | + var parts = name.split(/[\s.-]+/) | |
501 | + if (parts.length >= 2 && parts[0].length > 0 && parts[1].length > 0) { | |
502 | + return (parts[0][0] + parts[1][0]).toUpperCase() | |
503 | + } | |
504 | + return name.substring(0, Math.min(2, name.length)).toUpperCase() | |
505 | + } | |
506 | + | |
507 | + // ---------------------------------------------------------------- Content Layout | |
508 | + ColumnLayout { | |
509 | + anchors.fill: parent | |
510 | + anchors.margins: 14 | |
511 | + spacing: 12 | |
512 | + | |
513 | + // Header | |
514 | + RowLayout { | |
515 | + Layout.fillWidth: true | |
516 | + spacing: 10 | |
517 | + | |
518 | + Text { | |
519 | + text: root.nf(root.ic.puzzle) + " Plugin Manager" | |
520 | + color: Color.foreground | |
521 | + font.family: root.nfFontFamily | |
522 | + font.pixelSize: 18 | |
523 | + font.bold: true | |
524 | + } | |
525 | + | |
526 | + Item { Layout.fillWidth: true } | |
527 | + | |
528 | + // Tab switcher | |
529 | + Rectangle { | |
530 | + Layout.preferredWidth: 260 | |
531 | + Layout.preferredHeight: 32 | |
532 | + radius: 16 | |
533 | + color: Qt.rgba(0, 0, 0, 0.25) | |
534 | + border.color: Qt.rgba(255, 255, 255, 0.08) | |
535 | + border.width: 1 | |
536 | + | |
537 | + RowLayout { | |
538 | + anchors.fill: parent | |
539 | + spacing: 0 | |
540 | + | |
541 | + Rectangle { | |
542 | + Layout.fillWidth: true | |
543 | + Layout.fillHeight: true | |
544 | + radius: 14 | |
545 | + color: root.currentTab === "installed" ? Color.accent : "transparent" | |
546 | + | |
547 | + Text { | |
548 | + anchors.centerIn: parent | |
549 | + text: root.nf(root.ic.package) + " Installed (" + root.installedPlugins.length + ")" | |
550 | + color: root.currentTab === "installed" ? Color.background : Color.foreground | |
551 | + font.family: root.nfFontFamily | |
552 | + font.pixelSize: 11 | |
553 | + font.bold: root.currentTab === "installed" | |
554 | + } | |
555 | + | |
556 | + MouseArea { | |
557 | + anchors.fill: parent | |
558 | + cursorShape: Qt.PointingHandCursor | |
559 | + onClicked: root.currentTab = "installed" | |
560 | + } | |
561 | + } | |
562 | + | |
563 | + Rectangle { | |
564 | + Layout.fillWidth: true | |
565 | + Layout.fillHeight: true | |
566 | + radius: 14 | |
567 | + color: root.currentTab === "marketplace" ? Color.accent : "transparent" | |
568 | + | |
569 | + Text { | |
570 | + anchors.centerIn: parent | |
571 | + text: root.nf(root.ic.globe) + " Marketplace" | |
572 | + color: root.currentTab === "marketplace" ? Color.background : Color.foreground | |
573 | + font.family: root.nfFontFamily | |
574 | + font.pixelSize: 11 | |
575 | + font.bold: root.currentTab === "marketplace" | |
576 | + } | |
577 | + | |
578 | + MouseArea { | |
579 | + anchors.fill: parent | |
580 | + cursorShape: Qt.PointingHandCursor | |
581 | + onClicked: { | |
582 | + root.currentTab = "marketplace" | |
583 | + if (root.marketplacePlugins.length === 0) root.loadMarketplace() | |
584 | + } | |
585 | + } | |
586 | + } | |
587 | + } | |
588 | + } | |
589 | + | |
590 | + // Add Git URL Button | |
591 | + Rectangle { | |
592 | + Layout.preferredHeight: 30 | |
593 | + Layout.preferredWidth: 32 | |
594 | + radius: 6 | |
595 | + color: Qt.rgba(255, 255, 255, 0.08) | |
596 | + border.color: Qt.rgba(255, 255, 255, 0.12) | |
597 | + border.width: 1 | |
598 | + | |
599 | + Text { | |
600 | + anchors.centerIn: parent | |
601 | + text: "+" | |
602 | + color: Color.foreground | |
603 | + font.pixelSize: 16 | |
604 | + font.bold: true | |
605 | + } | |
606 | + | |
607 | + MouseArea { | |
608 | + anchors.fill: parent | |
609 | + cursorShape: Qt.PointingHandCursor | |
610 | + onClicked: root.urlDialogOpen = true | |
611 | + } | |
612 | + } | |
613 | + } | |
614 | + | |
615 | + // Status Banner Toast | |
616 | + Rectangle { | |
617 | + Layout.fillWidth: true | |
618 | + Layout.preferredHeight: root.statusMessage !== "" ? 30 : 0 | |
619 | + visible: root.statusMessage !== "" | |
620 | + radius: 6 | |
621 | + color: root.statusType === "success" ? Qt.rgba(0.2, 0.8, 0.3, 0.2) : Qt.rgba(0.3, 0.6, 0.9, 0.2) | |
622 | + border.color: root.statusType === "success" ? "#a6e3a1" : "#89b4fa" | |
623 | + border.width: 1 | |
624 | + clip: true | |
625 | + | |
626 | + Text { | |
627 | + anchors.centerIn: parent | |
628 | + text: root.statusMessage | |
629 | + color: Color.foreground | |
630 | + font.pixelSize: 11 | |
631 | + font.bold: true | |
632 | + } | |
633 | + } | |
634 | + | |
635 | + // Stack Views | |
636 | + StackLayout { | |
637 | + Layout.fillWidth: true | |
638 | + Layout.fillHeight: true | |
639 | + currentIndex: root.currentTab === "installed" ? 0 : 1 | |
640 | + | |
641 | + // TAB 1: INSTALLED | |
642 | + Item { | |
643 | + ColumnLayout { | |
644 | + anchors.fill: parent | |
645 | + spacing: 8 | |
646 | + | |
647 | + // Search Bar | |
648 | + Rectangle { | |
649 | + Layout.fillWidth: true | |
650 | + Layout.preferredHeight: 32 | |
651 | + radius: 6 | |
652 | + color: Qt.rgba(0, 0, 0, 0.2) | |
653 | + border.color: Qt.rgba(255, 255, 255, 0.1) | |
654 | + border.width: 1 | |
655 | + | |
656 | + RowLayout { | |
657 | + anchors.fill: parent | |
658 | + anchors.margins: 6 | |
659 | + spacing: 6 | |
660 | + Text { text: root.nf(root.ic.magnify); font.family: root.nfFontFamily; font.pixelSize: 11; color: Color.subtext } | |
661 | + TextInput { | |
662 | + id: installedSearchField | |
663 | + Layout.fillWidth: true | |
664 | + text: root.installedSearch | |
665 | + color: Color.foreground | |
666 | + font.pixelSize: 11 | |
667 | + onTextChanged: root.installedSearch = text | |
668 | + Text { | |
669 | + text: "Filter installed..." | |
670 | + color: Color.subtext | |
671 | + font.pixelSize: 11 | |
672 | + visible: !installedSearchField.text | |
673 | + } | |
674 | + } | |
675 | + } | |
676 | + } | |
677 | + | |
678 | + // Filter Chips | |
679 | + RowLayout { | |
680 | + spacing: 6 | |
681 | + Repeater { | |
682 | + model: [ | |
683 | + { id: "all", label: "All" }, | |
684 | + { id: "active", label: "Active" }, | |
685 | + { id: "inactive", label: "Inactive" }, | |
686 | + { id: "bar", label: "In Bar" }, | |
687 | + { id: "user", label: "User" } | |
688 | + ] | |
689 | + delegate: Rectangle { | |
690 | + required property var modelData | |
691 | + Layout.preferredHeight: 24 | |
692 | + Layout.preferredWidth: chipText.implicitWidth + 14 | |
693 | + radius: 12 | |
694 | + color: root.installedFilter === modelData.id ? Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, 0.25) : Qt.rgba(255, 255, 255, 0.05) | |
695 | + border.color: root.installedFilter === modelData.id ? Color.accent : "transparent" | |
696 | + border.width: 1 | |
697 | + | |
698 | + Text { | |
699 | + id: chipText | |
700 | + anchors.centerIn: parent | |
701 | + text: modelData.label | |
702 | + color: Color.foreground | |
703 | + font.pixelSize: 10 | |
704 | + } | |
705 | + | |
706 | + MouseArea { | |
707 | + anchors.fill: parent | |
708 | + cursorShape: Qt.PointingHandCursor | |
709 | + onClicked: root.installedFilter = modelData.id | |
710 | + } | |
711 | + } | |
712 | + } | |
713 | + } | |
714 | + | |
715 | + // List | |
716 | + ListView { | |
717 | + Layout.fillWidth: true | |
718 | + Layout.fillHeight: true | |
719 | + clip: true | |
720 | + spacing: 6 | |
721 | + model: root.filteredInstalled | |
722 | + | |
723 | + delegate: Rectangle { | |
724 | + required property var modelData | |
725 | + property bool inBar: root.isPluginInBar(modelData.id) | |
726 | + property string shortcut: root.shortcutsMap[modelData.id] || "" | |
727 | + property bool isBarCompatible: { | |
728 | + var kinds = modelData.kinds || [] | |
729 | + return kinds.indexOf("bar-widget") !== -1 || kinds.indexOf("bar") !== -1 || kinds.indexOf("panel") !== -1 | |
730 | + } | |
731 | + | |
732 | + width: parent.width | |
733 | + height: 66 | |
734 | + radius: 8 | |
735 | + color: Qt.rgba(255, 255, 255, modelData.enabled ? 0.08 : 0.03) | |
736 | + border.color: Qt.rgba(255, 255, 255, 0.08) | |
737 | + border.width: 1 | |
738 | + | |
739 | + RowLayout { | |
740 | + anchors.fill: parent | |
741 | + anchors.margins: 8 | |
742 | + spacing: 8 | |
743 | + | |
744 | + Rectangle { | |
745 | + Layout.preferredWidth: 36 | |
746 | + Layout.preferredHeight: 36 | |
747 | + radius: 8 | |
748 | + color: Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, 0.2) | |
749 | + Text { | |
750 | + anchors.centerIn: parent | |
751 | + text: root.getPluginIcon(modelData.id) | |
752 | + color: Color.accent | |
753 | + font.family: root.nfFontFamily | |
754 | + font.pixelSize: 18 | |
755 | + } | |
756 | + } | |
757 | + | |
758 | + ColumnLayout { | |
759 | + Layout.fillWidth: true | |
760 | + spacing: 2 | |
761 | + Text { | |
762 | + text: modelData.name || modelData.id | |
763 | + color: Color.foreground | |
764 | + font.pixelSize: 12 | |
765 | + font.bold: true | |
766 | + } | |
767 | + Text { | |
768 | + text: modelData.id + (modelData.firstParty ? " (Built-in)" : " (User)") | |
769 | + color: Color.subtext | |
770 | + font.pixelSize: 10 | |
771 | + } | |
772 | + } | |
773 | + | |
774 | + // Bar placement toggle | |
775 | + Rectangle { | |
776 | + visible: isBarCompatible | |
777 | + Layout.preferredHeight: 26 | |
778 | + Layout.preferredWidth: pBarText.implicitWidth + 12 | |
779 | + radius: 4 | |
780 | + color: inBar ? Qt.rgba(0.2, 0.8, 0.3, 0.18) : Qt.rgba(255, 255, 255, 0.06) | |
781 | + border.color: inBar ? "#a6e3a1" : Qt.rgba(255, 255, 255, 0.1) | |
782 | + border.width: 1 | |
783 | + | |
784 | + Text { | |
785 | + id: pBarText | |
786 | + anchors.centerIn: parent | |
787 | + text: inBar ? root.nf(root.ic.pin) + " In Bar" : root.nf(root.ic.plus) + " Bar" | |
788 | + color: inBar ? "#a6e3a1" : Color.subtext | |
789 | + font.family: root.nfFontFamily | |
790 | + font.pixelSize: 9 | |
791 | + font.bold: inBar | |
792 | + } | |
793 | + | |
794 | + MouseArea { | |
795 | + anchors.fill: parent | |
796 | + cursorShape: Qt.PointingHandCursor | |
797 | + onClicked: root.toggleBarPlacement(modelData) | |
798 | + } | |
799 | + } | |
800 | + | |
801 | + // Shortcut button | |
802 | + Rectangle { | |
803 | + Layout.preferredHeight: 26 | |
804 | + Layout.preferredWidth: pScText.implicitWidth + 12 | |
805 | + radius: 4 | |
806 | + color: shortcut !== "" ? Qt.rgba(1, 0.7, 0.2, 0.18) : Qt.rgba(255, 255, 255, 0.06) | |
807 | + border.color: shortcut !== "" ? "#ff9e64" : Qt.rgba(255, 255, 255, 0.1) | |
808 | + border.width: 1 | |
809 | + | |
810 | + Text { | |
811 | + id: pScText | |
812 | + anchors.centerIn: parent | |
813 | + text: root.nf(root.ic.keyboard) + (shortcut !== "" ? " " + shortcut : " Key") | |
814 | + color: shortcut !== "" ? "#ff9e64" : Color.subtext | |
815 | + font.family: root.nfFontFamily | |
816 | + font.pixelSize: 9 | |
817 | + font.bold: shortcut !== "" | |
818 | + } | |
819 | + | |
820 | + MouseArea { | |
821 | + anchors.fill: parent | |
822 | + cursorShape: Qt.PointingHandCursor | |
823 | + onClicked: root.openShortcutDialog(modelData) | |
824 | + } | |
825 | + } | |
826 | + | |
827 | + // Delete button for user plugins | |
828 | + Rectangle { | |
829 | + visible: !modelData.firstParty | |
830 | + Layout.preferredWidth: 26 | |
831 | + Layout.preferredHeight: 26 | |
832 | + radius: 4 | |
833 | + color: Qt.rgba(1, 0, 0, 0.15) | |
834 | + Text { anchors.centerIn: parent; text: root.nf(root.ic.trash); font.family: root.nfFontFamily; font.pixelSize: 12; color: "#f7768e" } | |
835 | + MouseArea { | |
836 | + anchors.fill: parent | |
837 | + cursorShape: Qt.PointingHandCursor | |
838 | + onClicked: root.confirmUninstall(modelData) | |
839 | + } | |
840 | + } | |
841 | + | |
842 | + // Switch | |
843 | + Rectangle { | |
844 | + Layout.preferredWidth: 44 | |
845 | + Layout.preferredHeight: 24 | |
846 | + radius: 12 | |
847 | + color: modelData.enabled ? Color.accent : Qt.rgba(255, 255, 255, 0.15) | |
848 | + border.color: Qt.rgba(255, 255, 255, 0.1) | |
849 | + border.width: 1 | |
850 | + | |
851 | + Rectangle { | |
852 | + width: 18 | |
853 | + height: 18 | |
854 | + radius: 9 | |
855 | + color: "#ffffff" | |
856 | + anchors.verticalCenter: parent.verticalCenter | |
857 | + x: modelData.enabled ? 22 : 4 | |
858 | + Behavior on x { NumberAnimation { duration: 120 } } | |
859 | + } | |
860 | + | |
861 | + MouseArea { | |
862 | + anchors.fill: parent | |
863 | + cursorShape: Qt.PointingHandCursor | |
864 | + onClicked: root.togglePlugin(modelData) | |
865 | + } | |
866 | + } | |
867 | + } | |
868 | + } | |
869 | + } | |
870 | + } | |
871 | + } | |
872 | + | |
873 | + // TAB 2: MARKETPLACE | |
874 | + Item { | |
875 | + ColumnLayout { | |
876 | + anchors.fill: parent | |
877 | + spacing: 8 | |
878 | + | |
879 | + // Search Bar | |
880 | + Rectangle { | |
881 | + Layout.fillWidth: true | |
882 | + Layout.preferredHeight: 32 | |
883 | + radius: 6 | |
884 | + color: Qt.rgba(0, 0, 0, 0.2) | |
885 | + border.color: Qt.rgba(255, 255, 255, 0.1) | |
886 | + border.width: 1 | |
887 | + | |
888 | + RowLayout { | |
889 | + anchors.fill: parent | |
890 | + anchors.margins: 6 | |
891 | + spacing: 6 | |
892 | + Text { text: root.nf(root.ic.globe); font.family: root.nfFontFamily; font.pixelSize: 11; color: Color.subtext } | |
893 | + TextInput { | |
894 | + id: marketSearchField | |
895 | + Layout.fillWidth: true | |
896 | + text: root.marketSearch | |
897 | + color: Color.foreground | |
898 | + font.pixelSize: 11 | |
899 | + onTextChanged: root.marketSearch = text | |
900 | + Text { | |
901 | + text: "Search 1,000+ plugins..." | |
902 | + color: Color.subtext | |
903 | + font.pixelSize: 11 | |
904 | + visible: !marketSearchField.text | |
905 | + } | |
906 | + } | |
907 | + } | |
908 | + } | |
909 | + | |
910 | + // Category Chips | |
911 | + ScrollView { | |
912 | + Layout.fillWidth: true | |
913 | + Layout.preferredHeight: 28 | |
914 | + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff | |
915 | + ScrollBar.vertical.policy: ScrollBar.AlwaysOff | |
916 | + | |
917 | + RowLayout { | |
918 | + spacing: 6 | |
919 | + Repeater { | |
920 | + model: ["All", "Appearance", "Desktop", "Productivity", "System", "Utilities", "Widgets"] | |
921 | + delegate: Rectangle { | |
922 | + required property var modelData | |
923 | + Layout.preferredHeight: 24 | |
924 | + Layout.preferredWidth: mCatText.implicitWidth + 14 | |
925 | + radius: 12 | |
926 | + color: root.marketCategory === modelData ? Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, 0.25) : Qt.rgba(255, 255, 255, 0.05) | |
927 | + border.color: root.marketCategory === modelData ? Color.accent : "transparent" | |
928 | + border.width: 1 | |
929 | + | |
930 | + Text { | |
931 | + id: mCatText | |
932 | + anchors.centerIn: parent | |
933 | + text: modelData | |
934 | + color: Color.foreground | |
935 | + font.pixelSize: 10 | |
936 | + } | |
937 | + | |
938 | + MouseArea { | |
939 | + anchors.fill: parent | |
940 | + cursorShape: Qt.PointingHandCursor | |
941 | + onClicked: root.marketCategory = modelData | |
942 | + } | |
943 | + } | |
944 | + } | |
945 | + } | |
946 | + } | |
947 | + | |
948 | + // List | |
949 | + ListView { | |
950 | + Layout.fillWidth: true | |
951 | + Layout.fillHeight: true | |
952 | + clip: true | |
953 | + spacing: 6 | |
954 | + model: root.filteredMarketplace | |
955 | + | |
956 | + delegate: Rectangle { | |
957 | + required property var modelData | |
958 | + property bool installed: root.isInstalled(modelData.id) | |
959 | + width: parent.width | |
960 | + height: 74 | |
961 | + radius: 8 | |
962 | + color: Qt.rgba(255, 255, 255, 0.05) | |
963 | + border.color: Qt.rgba(255, 255, 255, 0.08) | |
964 | + border.width: 1 | |
965 | + | |
966 | + RowLayout { | |
967 | + anchors.fill: parent | |
968 | + anchors.margins: 10 | |
969 | + spacing: 10 | |
970 | + | |
971 | + Rectangle { | |
972 | + Layout.preferredWidth: 38 | |
973 | + Layout.preferredHeight: 38 | |
974 | + radius: 8 | |
975 | + color: Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, 0.2) | |
976 | + Text { | |
977 | + anchors.centerIn: parent | |
978 | + text: modelData.initials || root.getInitials(modelData.name || modelData.id) | |
979 | + color: Color.foreground | |
980 | + font.pixelSize: 13 | |
981 | + font.bold: true | |
982 | + } | |
983 | + } | |
984 | + | |
985 | + ColumnLayout { | |
986 | + Layout.fillWidth: true | |
987 | + spacing: 2 | |
988 | + RowLayout { | |
989 | + spacing: 6 | |
990 | + Text { | |
991 | + text: modelData.name || modelData.id | |
992 | + color: Color.foreground | |
993 | + font.pixelSize: 12 | |
994 | + font.bold: true | |
995 | + } | |
996 | + Text { | |
997 | + visible: (modelData.stars || 0) > 0 | |
998 | + text: root.nf(root.ic.star) + " " + modelData.stars | |
999 | + color: "#e0af68" | |
1000 | + font.family: root.nfFontFamily | |
1001 | + font.pixelSize: 10 | |
1002 | + } | |
1003 | + } | |
1004 | + Text { | |
1005 | + Layout.fillWidth: true | |
1006 | + text: modelData.description || "" | |
1007 | + color: Color.subtext | |
1008 | + font.pixelSize: 10 | |
1009 | + elide: Text.ElideRight | |
1010 | + } | |
1011 | + } | |
1012 | + | |
1013 | + // Install button | |
1014 | + Rectangle { | |
1015 | + Layout.preferredHeight: 28 | |
1016 | + Layout.preferredWidth: installed ? 78 : 66 | |
1017 | + radius: 6 | |
1018 | + color: installed ? Qt.rgba(0.2, 0.8, 0.3, 0.18) : (root.installingId === modelData.id ? Qt.rgba(255, 255, 255, 0.08) : Color.accent) | |
1019 | + border.color: installed ? "#a6e3a1" : "transparent" | |
1020 | + border.width: installed ? 1 : 0 | |
1021 | + | |
1022 | + Text { | |
1023 | + anchors.centerIn: parent | |
1024 | + text: installed ? root.nf(root.ic.check) + " Installed" : (root.installingId === modelData.id ? "..." : "Install") | |
1025 | + color: installed ? "#a6e3a1" : (root.installingId === modelData.id ? Color.subtext : Color.background) | |
1026 | + font.family: root.nfFontFamily | |
1027 | + font.pixelSize: 10 | |
1028 | + font.bold: true | |
1029 | + } | |
1030 | + | |
1031 | + MouseArea { | |
1032 | + anchors.fill: parent | |
1033 | + cursorShape: Qt.PointingHandCursor | |
1034 | + enabled: !installed && root.installingId === "" | |
1035 | + onClicked: root.installPlugin(modelData) | |
1036 | + } | |
1037 | + } | |
1038 | + } | |
1039 | + } | |
1040 | + } | |
1041 | + } | |
1042 | + } | |
1043 | + } | |
1044 | + } | |
1045 | + | |
1046 | + // Modals | |
1047 | + // 1. Shortcut Dialog | |
1048 | + Rectangle { | |
1049 | + anchors.fill: parent | |
1050 | + color: Qt.rgba(0, 0, 0, 0.75) | |
1051 | + visible: root.shortcutDialogOpen | |
1052 | + | |
1053 | + MouseArea { anchors.fill: parent; onClicked: root.shortcutDialogOpen = false } | |
1054 | + | |
1055 | + Rectangle { | |
1056 | + anchors.centerIn: parent | |
1057 | + width: 440 | |
1058 | + height: 240 | |
1059 | + radius: 10 | |
1060 | + color: Color.popups ? Color.popups.background : "#1f2335" | |
1061 | + border.color: "#ff9e64" | |
1062 | + border.width: 1 | |
1063 | + | |
1064 | + MouseArea { anchors.fill: parent } | |
1065 | + | |
1066 | + ColumnLayout { | |
1067 | + anchors.fill: parent | |
1068 | + anchors.margins: 16 | |
1069 | + spacing: 10 | |
1070 | + | |
1071 | + Text { | |
1072 | + text: root.nf(root.ic.keyboard) + " Set Keyboard Shortcut" | |
1073 | + color: Color.foreground | |
1074 | + font.family: root.nfFontFamily | |
1075 | + font.pixelSize: 14 | |
1076 | + font.bold: true | |
1077 | + } | |
1078 | + | |
1079 | + Text { | |
1080 | + text: "Set Hyprland keybind for '" + (root.shortcutTargetPlugin ? (root.shortcutTargetPlugin.name || root.shortcutTargetPlugin.id) : "") + "':" | |
1081 | + color: Color.subtext | |
1082 | + font.pixelSize: 11 | |
1083 | + } | |
1084 | + | |
1085 | + Rectangle { | |
1086 | + Layout.fillWidth: true | |
1087 | + Layout.preferredHeight: 34 | |
1088 | + radius: 6 | |
1089 | + color: Qt.rgba(0, 0, 0, 0.25) | |
1090 | + border.color: "#ff9e64" | |
1091 | + border.width: 1 | |
1092 | + | |
1093 | + TextInput { | |
1094 | + anchors.fill: parent | |
1095 | + anchors.margins: 6 | |
1096 | + text: root.shortcutInput | |
1097 | + color: Color.foreground | |
1098 | + font.pixelSize: 12 | |
1099 | + font.bold: true | |
1100 | + onTextChanged: root.shortcutInput = text | |
1101 | + Text { | |
1102 | + text: "e.g. SUPER + ALT + D" | |
1103 | + color: Color.subtext | |
1104 | + font.pixelSize: 11 | |
1105 | + visible: !parent.text | |
1106 | + } | |
1107 | + } | |
1108 | + } | |
1109 | + | |
1110 | + RowLayout { | |
1111 | + Layout.fillWidth: true | |
1112 | + Button { | |
1113 | + text: "Clear" | |
1114 | + onClicked: root.clearShortcut() | |
1115 | + } | |
1116 | + Item { Layout.fillWidth: true } | |
1117 | + Button { | |
1118 | + text: "Cancel" | |
1119 | + onClicked: root.shortcutDialogOpen = false | |
1120 | + } | |
1121 | + Button { | |
1122 | + text: "Save & Apply" | |
1123 | + onClicked: root.saveShortcut() | |
1124 | + } | |
1125 | + } | |
1126 | + } | |
1127 | + } | |
1128 | + } | |
1129 | + | |
1130 | + // 2. Bar Section Selector | |
1131 | + Rectangle { | |
1132 | + anchors.fill: parent | |
1133 | + color: Qt.rgba(0, 0, 0, 0.75) | |
1134 | + visible: root.barSectionDialogOpen | |
1135 | + | |
1136 | + MouseArea { anchors.fill: parent; onClicked: root.barSectionDialogOpen = false } | |
1137 | + | |
1138 | + Rectangle { | |
1139 | + anchors.centerIn: parent | |
1140 | + width: 380 | |
1141 | + height: 180 | |
1142 | + radius: 10 | |
1143 | + color: Color.popups ? Color.popups.background : "#1f2335" | |
1144 | + border.color: "#a6e3a1" | |
1145 | + border.width: 1 | |
1146 | + | |
1147 | + MouseArea { anchors.fill: parent } | |
1148 | + | |
1149 | + ColumnLayout { | |
1150 | + anchors.fill: parent | |
1151 | + anchors.margins: 16 | |
1152 | + spacing: 12 | |
1153 | + | |
1154 | + Text { | |
1155 | + text: root.nf(root.ic.pin) + " Add to Omarchy Top Bar" | |
1156 | + color: Color.foreground | |
1157 | + font.pixelSize: 14 | |
1158 | + font.bold: true | |
1159 | + } | |
1160 | + | |
1161 | + Text { | |
1162 | + text: "Select bar section:" | |
1163 | + color: Color.subtext | |
1164 | + font.pixelSize: 11 | |
1165 | + } | |
1166 | + | |
1167 | + RowLayout { | |
1168 | + Layout.fillWidth: true | |
1169 | + spacing: 8 | |
1170 | + | |
1171 | + Button { | |
1172 | + Layout.fillWidth: true | |
1173 | + text: "Left" | |
1174 | + onClicked: root.addPluginToBar(root.barTargetPlugin, "left") | |
1175 | + } | |
1176 | + Button { | |
1177 | + Layout.fillWidth: true | |
1178 | + text: "Center" | |
1179 | + onClicked: root.addPluginToBar(root.barTargetPlugin, "center") | |
1180 | + } | |
1181 | + Button { | |
1182 | + Layout.fillWidth: true | |
1183 | + text: "Right" | |
1184 | + onClicked: root.addPluginToBar(root.barTargetPlugin, "right") | |
1185 | + } | |
1186 | + } | |
1187 | + | |
1188 | + RowLayout { | |
1189 | + Layout.fillWidth: true | |
1190 | + Item { Layout.fillWidth: true } | |
1191 | + Button { | |
1192 | + text: "Cancel" | |
1193 | + onClicked: root.barSectionDialogOpen = false | |
1194 | + } | |
1195 | + } | |
1196 | + } | |
1197 | + } | |
1198 | + } | |
1199 | + | |
1200 | + // 3. Git URL Dialog Modal | |
1201 | + Rectangle { | |
1202 | + anchors.fill: parent | |
1203 | + color: Qt.rgba(0, 0, 0, 0.7) | |
1204 | + visible: root.urlDialogOpen | |
1205 | + | |
1206 | + MouseArea { anchors.fill: parent; onClicked: root.urlDialogOpen = false } | |
1207 | + | |
1208 | + Rectangle { | |
1209 | + anchors.centerIn: parent | |
1210 | + width: 420 | |
1211 | + height: 180 | |
1212 | + radius: 10 | |
1213 | + color: Color.popups ? Color.popups.background : "#1f2335" | |
1214 | + border.color: Qt.rgba(255, 255, 255, 0.15) | |
1215 | + border.width: 1 | |
1216 | + | |
1217 | + MouseArea { anchors.fill: parent } | |
1218 | + | |
1219 | + ColumnLayout { | |
1220 | + anchors.fill: parent | |
1221 | + anchors.margins: 16 | |
1222 | + spacing: 10 | |
1223 | + | |
1224 | + Text { | |
1225 | + text: "Install from Git Repository" | |
1226 | + color: Color.foreground | |
1227 | + font.pixelSize: 14 | |
1228 | + font.bold: true | |
1229 | + } | |
1230 | + | |
1231 | + Rectangle { | |
1232 | + Layout.fillWidth: true | |
1233 | + Layout.preferredHeight: 34 | |
1234 | + radius: 6 | |
1235 | + color: Qt.rgba(0, 0, 0, 0.25) | |
1236 | + border.color: Color.accent | |
1237 | + border.width: 1 | |
1238 | + | |
1239 | + TextInput { | |
1240 | + anchors.fill: parent | |
1241 | + anchors.margins: 6 | |
1242 | + text: root.urlInput | |
1243 | + color: Color.foreground | |
1244 | + font.pixelSize: 11 | |
1245 | + onTextChanged: root.urlInput = text | |
1246 | + Text { | |
1247 | + text: "https://github.com/..." | |
1248 | + color: Color.subtext | |
1249 | + font.pixelSize: 11 | |
1250 | + visible: !parent.text | |
1251 | + } | |
1252 | + } | |
1253 | + } | |
1254 | + | |
1255 | + RowLayout { | |
1256 | + Layout.fillWidth: true | |
1257 | + Item { Layout.fillWidth: true } | |
1258 | + Button { | |
1259 | + text: "Cancel" | |
1260 | + onClicked: root.urlDialogOpen = false | |
1261 | + } | |
1262 | + Button { | |
1263 | + text: "Install" | |
1264 | + onClicked: root.installFromUrl(root.urlInput) | |
1265 | + } | |
1266 | + } | |
1267 | + } | |
1268 | + } | |
1269 | + } | |
1270 | + | |
1271 | + // 4. Confirm Uninstall Dialog | |
1272 | + Rectangle { | |
1273 | + anchors.fill: parent | |
1274 | + color: Qt.rgba(0, 0, 0, 0.7) | |
1275 | + visible: root.confirmUninstallOpen | |
1276 | + | |
1277 | + MouseArea { anchors.fill: parent; onClicked: root.confirmUninstallOpen = false } | |
1278 | + | |
1279 | + Rectangle { | |
1280 | + anchors.centerIn: parent | |
1281 | + width: 380 | |
1282 | + height: 150 | |
1283 | + radius: 10 | |
1284 | + color: Color.popups ? Color.popups.background : "#1f2335" | |
1285 | + border.color: Qt.rgba(255, 255, 255, 0.15) | |
1286 | + border.width: 1 | |
1287 | + | |
1288 | + MouseArea { anchors.fill: parent } | |
1289 | + | |
1290 | + ColumnLayout { | |
1291 | + anchors.fill: parent | |
1292 | + anchors.margins: 16 | |
1293 | + spacing: 10 | |
1294 | + | |
1295 | + Text { | |
1296 | + text: "Uninstall Plugin" | |
1297 | + color: "#f7768e" | |
1298 | + font.pixelSize: 14 | |
1299 | + font.bold: true | |
1300 | + } | |
1301 | + | |
1302 | + Text { | |
1303 | + text: "Remove '" + (root.pendingUninstallPlugin ? (root.pendingUninstallPlugin.name || root.pendingUninstallPlugin.id) : "") + "'?" | |
1304 | + color: Color.foreground | |
1305 | + font.pixelSize: 11 | |
1306 | + } | |
1307 | + | |
1308 | + RowLayout { | |
1309 | + Layout.fillWidth: true | |
1310 | + Item { Layout.fillWidth: true } | |
1311 | + Button { | |
1312 | + text: "Cancel" | |
1313 | + onClicked: root.confirmUninstallOpen = false | |
1314 | + } | |
1315 | + Button { | |
1316 | + text: "Remove" | |
1317 | + onClicked: root.executeUninstall() | |
1318 | + } | |
1319 | + } | |
1320 | + } | |
1321 | + } | |
1322 | + } | |
1323 | +} | |
0 | 1324 | |
▼shell/plugins/plugin-manager/manifest.json
+23-0
| @@ -0,0 +1,23 @@ | ||
1 | +{ | |
2 | + "schemaVersion": 1, | |
3 | + "id": "deomarchy.plugin-manager", | |
4 | + "name": "Plugin Manager", | |
5 | + "version": "1.0.0", | |
6 | + "author": "de-omarchy", | |
7 | + "description": "Manage installed plugins and discover 1,000+ community extensions from OmarchyPlugins.com", | |
8 | + "kinds": [ | |
9 | + "bar-widget", | |
10 | + "panel" | |
11 | + ], | |
12 | + "entryPoints": { | |
13 | + "barWidget": "BarWidget.qml", | |
14 | + "panel": "Panel.qml" | |
15 | + }, | |
16 | + "barWidget": { | |
17 | + "displayName": "Plugin Manager", | |
18 | + "description": "Enable/disable, update, install, and remove plugins", | |
19 | + "category": "System", | |
20 | + "allowMultiple": false, | |
21 | + "defaultSection": "right" | |
22 | + } | |
23 | +} | |
0 | 24 | |