new view, gnome extension
This commit is contained in:
@@ -20,8 +20,11 @@ def parse_line(line: str) -> tuple[Optional[str], Optional[str]]:
|
|||||||
parts = line.split("|")
|
parts = line.split("|")
|
||||||
if len(parts) > 1:
|
if len(parts) > 1:
|
||||||
task_name = parts[0].strip()
|
task_name = parts[0].strip()
|
||||||
task_id = parts[1].split()[0].strip()
|
id_parts = parts[1].split()
|
||||||
|
if id_parts:
|
||||||
|
task_id = id_parts[0].strip()
|
||||||
return task_name, task_id
|
return task_name, task_id
|
||||||
|
return task_name, None
|
||||||
|
|
||||||
return parts[0].strip(), None
|
return parts[0].strip(), None
|
||||||
|
|
||||||
@@ -32,7 +35,8 @@ def file_to_db(filepath: str):
|
|||||||
filepath = task_file
|
filepath = task_file
|
||||||
|
|
||||||
current_path = []
|
current_path = []
|
||||||
tasks.delete_many({})
|
# Only delete non-historic tasks
|
||||||
|
tasks.delete_many({"historic": {"$ne": True}})
|
||||||
seen_paths = set()
|
seen_paths = set()
|
||||||
|
|
||||||
with open(filepath, "r") as f:
|
with open(filepath, "r") as f:
|
||||||
@@ -54,8 +58,8 @@ def file_to_db(filepath: str):
|
|||||||
|
|
||||||
if task_id:
|
if task_id:
|
||||||
tasks.update_one(
|
tasks.update_one(
|
||||||
{"_id": task_id},
|
{"task_id": task_id},
|
||||||
{"$set": {"path": full_path, "task_id": task_id}},
|
{"$set": {"path": full_path, "task_id": task_id, "historic": False}},
|
||||||
upsert=True,
|
upsert=True,
|
||||||
)
|
)
|
||||||
elif full_path not in seen_paths:
|
elif full_path not in seen_paths:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
@@ -11,9 +12,99 @@ client = MongoClient()
|
|||||||
db = client.deskmeter
|
db = client.deskmeter
|
||||||
switches = db.switch
|
switches = db.switch
|
||||||
tasks = db.task
|
tasks = db.task
|
||||||
|
task_history = db.task_history
|
||||||
|
|
||||||
|
|
||||||
task_file = "/home/mariano/LETRAS/adm/task/main"
|
task_file = "/home/mariano/LETRAS/adm/task/main"
|
||||||
|
task_dir = Path(task_file).parent
|
||||||
|
|
||||||
|
|
||||||
|
def parse_task_line(line):
|
||||||
|
"""Parse a task line to extract task name and ID."""
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
parts = line.split("|")
|
||||||
|
if len(parts) > 1:
|
||||||
|
task_name = parts[0].strip()
|
||||||
|
id_parts = parts[1].split()
|
||||||
|
if id_parts:
|
||||||
|
task_id = id_parts[0].strip()
|
||||||
|
return task_name, task_id
|
||||||
|
return task_name, None
|
||||||
|
|
||||||
|
return parts[0].strip(), None
|
||||||
|
|
||||||
|
|
||||||
|
def load_task_from_files(task_id):
|
||||||
|
"""Search task directory files for a task ID and load it into task_history."""
|
||||||
|
for task_filepath in task_dir.glob("*"):
|
||||||
|
if not task_filepath.is_file():
|
||||||
|
continue
|
||||||
|
|
||||||
|
current_path = []
|
||||||
|
try:
|
||||||
|
with open(task_filepath, "r") as f:
|
||||||
|
for line in f:
|
||||||
|
if not line.strip():
|
||||||
|
continue
|
||||||
|
|
||||||
|
indent = len(line) - len(line.lstrip())
|
||||||
|
level = indent // 4
|
||||||
|
|
||||||
|
task_name, found_id = parse_task_line(line)
|
||||||
|
if task_name is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
current_path = current_path[:level]
|
||||||
|
current_path.append(task_name)
|
||||||
|
full_path = "/".join(current_path)
|
||||||
|
|
||||||
|
if found_id == task_id:
|
||||||
|
# Found it! Insert into task_history
|
||||||
|
task_history.update_one(
|
||||||
|
{"task_id": task_id},
|
||||||
|
{"$set": {
|
||||||
|
"path": full_path,
|
||||||
|
"task_id": task_id,
|
||||||
|
"source_file": task_filepath.name
|
||||||
|
}},
|
||||||
|
upsert=True
|
||||||
|
)
|
||||||
|
return full_path
|
||||||
|
except:
|
||||||
|
# Skip files that can't be read
|
||||||
|
continue
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_task_path(task_id):
|
||||||
|
"""
|
||||||
|
Get task path from tasks collection, falling back to task_history.
|
||||||
|
If not found, searches task directory files and populates task_history on-demand.
|
||||||
|
"""
|
||||||
|
if not task_id:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Try current tasks first
|
||||||
|
task_doc = tasks.find_one({"task_id": task_id})
|
||||||
|
if task_doc and "path" in task_doc:
|
||||||
|
return task_doc["path"]
|
||||||
|
|
||||||
|
# Try task history cache
|
||||||
|
task_doc = task_history.find_one({"task_id": task_id})
|
||||||
|
if task_doc and "path" in task_doc:
|
||||||
|
return task_doc["path"]
|
||||||
|
|
||||||
|
# Not in cache, search files and populate history
|
||||||
|
task_path = load_task_from_files(task_id)
|
||||||
|
if task_path:
|
||||||
|
return task_path
|
||||||
|
|
||||||
|
# Still not found, return ID as fallback
|
||||||
|
return task_id
|
||||||
|
|
||||||
|
|
||||||
def get_current_task_info():
|
def get_current_task_info():
|
||||||
@@ -134,9 +225,8 @@ def get_work_period_totals(start, end):
|
|||||||
total_seconds = result["total_seconds"]
|
total_seconds = result["total_seconds"]
|
||||||
|
|
||||||
if total_seconds > 0:
|
if total_seconds > 0:
|
||||||
# Get task path from tasks collection
|
# Get task path with history fallback
|
||||||
task_doc = tasks.find_one({"task_id": task_id})
|
task_path = get_task_path(task_id)
|
||||||
task_path = task_doc["path"] if task_doc and "path" in task_doc else task_id
|
|
||||||
|
|
||||||
combined_rows.append({
|
combined_rows.append({
|
||||||
"ws": task_path,
|
"ws": task_path,
|
||||||
@@ -194,15 +284,12 @@ def get_task_blocks_calendar(start, end, task=None, min_block_seconds=300):
|
|||||||
if current_block is not None:
|
if current_block is not None:
|
||||||
blocks.append(current_block)
|
blocks.append(current_block)
|
||||||
|
|
||||||
# Get task path
|
# Get task path with history fallback
|
||||||
task_path = None
|
task_path = get_task_path(task_id) or "No Task"
|
||||||
if task_id:
|
|
||||||
task_doc = tasks.find_one({"task_id": task_id})
|
|
||||||
task_path = task_doc["path"] if task_doc and "path" in task_doc else task_id
|
|
||||||
|
|
||||||
current_block = {
|
current_block = {
|
||||||
"task_id": task_id,
|
"task_id": task_id,
|
||||||
"task_path": task_path or "No Task",
|
"task_path": task_path,
|
||||||
"start": switch_start,
|
"start": switch_start,
|
||||||
"end": switch_end,
|
"end": switch_end,
|
||||||
"duration": switch_duration,
|
"duration": switch_duration,
|
||||||
@@ -256,15 +343,13 @@ def get_raw_switches(start, end, task=None):
|
|||||||
result = []
|
result = []
|
||||||
for switch in raw_switches:
|
for switch in raw_switches:
|
||||||
task_id = switch.get("task")
|
task_id = switch.get("task")
|
||||||
task_path = None
|
# Get task path with history fallback
|
||||||
if task_id:
|
task_path = get_task_path(task_id) or "No Task"
|
||||||
task_doc = tasks.find_one({"task_id": task_id})
|
|
||||||
task_path = task_doc["path"] if task_doc and "path" in task_doc else task_id
|
|
||||||
|
|
||||||
result.append({
|
result.append({
|
||||||
"workspace": switch["workspace"],
|
"workspace": switch["workspace"],
|
||||||
"task_id": task_id,
|
"task_id": task_id,
|
||||||
"task_path": task_path or "No Task",
|
"task_path": task_path,
|
||||||
"date": switch["date"].replace(tzinfo=utctz).astimezone(timezone),
|
"date": switch["date"].replace(tzinfo=utctz).astimezone(timezone),
|
||||||
"delta": switch["delta"]
|
"delta": switch["delta"]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -56,7 +56,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
background: #f9f9f9;
|
|
||||||
border-left: 4px solid;
|
border-left: 4px solid;
|
||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
}
|
}
|
||||||
@@ -83,12 +82,9 @@
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Workspace colors */
|
/* Active vs idle workspace indicator */
|
||||||
.ws-Work { border-color: #2563eb; background: #eff6ff; }
|
.ws-active { font-weight: bold; }
|
||||||
.ws-Think { border-color: #7c3aed; background: #f5f3ff; }
|
.ws-idle { opacity: 0.6; }
|
||||||
.ws-Plan { border-color: #059669; background: #ecfdf5; }
|
|
||||||
.ws-Other { border-color: #64748b; background: #f8fafc; }
|
|
||||||
.ws-Away { border-color: #94a3b8; background: #f1f5f9; }
|
|
||||||
|
|
||||||
.stats {
|
.stats {
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
@@ -161,7 +157,13 @@
|
|||||||
|
|
||||||
<div class="switches-container">
|
<div class="switches-container">
|
||||||
{% for switch in switches %}
|
{% for switch in switches %}
|
||||||
<div class="switch-row ws-{{ switch.workspace }}">
|
{% set is_active = switch.workspace in ['Plan', 'Think', 'Work'] %}
|
||||||
|
{% set task_hash = switch.task_path|hash if switch.task_path else 0 %}
|
||||||
|
{% set border_hue = task_hash % 360 %}
|
||||||
|
{% set border_color = 'hsl(%d, 70%%, 50%%)'|format(border_hue) %}
|
||||||
|
{% set bg_color = 'hsl(%d, 70%%, 95%%)'|format(border_hue) %}
|
||||||
|
<div class="switch-row {{ 'ws-active' if is_active else 'ws-idle' }}"
|
||||||
|
style="border-left-color: {{ border_color }}; background-color: {{ bg_color }};">
|
||||||
<div class="switch-time">{{ switch.date.strftime('%m/%d %H:%M:%S') }}</div>
|
<div class="switch-time">{{ switch.date.strftime('%m/%d %H:%M:%S') }}</div>
|
||||||
<div class="switch-workspace">{{ switch.workspace }}</div>
|
<div class="switch-workspace">{{ switch.workspace }}</div>
|
||||||
<div class="switch-task">{{ switch.task_path }}</div>
|
<div class="switch-task">{{ switch.task_path }}</div>
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ A GNOME Shell extension that displays your current deskmeter task in the top pan
|
|||||||
1. Copy the extension to your GNOME extensions directory:
|
1. Copy the extension to your GNOME extensions directory:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp -r /home/mariano/wdir/dm/gnome-extension/deskmeter-indicator@local \
|
cp -r /home/mariano/wdir/dm/gnome-extension/deskmeter-indicator@local ~/.local/share/gnome-shell/extensions/
|
||||||
~/.local/share/gnome-shell/extensions/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Restart GNOME Shell:
|
2. Restart GNOME Shell:
|
||||||
@@ -31,10 +30,12 @@ Or use GNOME Extensions app (install with `sudo apt install gnome-shell-extensio
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The extension polls the API every 3 seconds. You can adjust this in `extension.js`:
|
The extension updates automatically when you switch workspaces. It waits 2.2 seconds after a workspace switch to allow dmcore (which polls every 2 seconds) to detect the change and update MongoDB.
|
||||||
|
|
||||||
|
You can adjust the delay in `extension.js`:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const UPDATE_INTERVAL = 3000; // milliseconds
|
const DEBOUNCE_DELAY = 2200; // milliseconds
|
||||||
```
|
```
|
||||||
|
|
||||||
The API URL is set to:
|
The API URL is set to:
|
||||||
@@ -52,6 +53,28 @@ rm -rf ~/.local/share/gnome-shell/extensions/deskmeter-indicator@local
|
|||||||
|
|
||||||
Then restart GNOME Shell.
|
Then restart GNOME Shell.
|
||||||
|
|
||||||
|
## Updating the Extension
|
||||||
|
|
||||||
|
After making changes to the extension code:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use the update script
|
||||||
|
cd /home/mariano/wdir/dm/gnome-extension
|
||||||
|
./update.sh
|
||||||
|
|
||||||
|
# Then restart GNOME Shell (X11 only)
|
||||||
|
Alt+F2, type: r, press Enter
|
||||||
|
|
||||||
|
# On Wayland: log out and back in
|
||||||
|
```
|
||||||
|
|
||||||
|
Or manually:
|
||||||
|
```bash
|
||||||
|
cp -r /home/mariano/wdir/dm/gnome-extension/deskmeter-indicator@local \
|
||||||
|
~/.local/share/gnome-shell/extensions/
|
||||||
|
# Then restart GNOME Shell
|
||||||
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Extension not showing
|
### Extension not showing
|
||||||
@@ -66,6 +89,12 @@ Then restart GNOME Shell.
|
|||||||
journalctl -f -o cat /usr/bin/gnome-shell
|
journalctl -f -o cat /usr/bin/gnome-shell
|
||||||
```
|
```
|
||||||
|
|
||||||
|
3. Try disabling and re-enabling:
|
||||||
|
```bash
|
||||||
|
gnome-extensions disable deskmeter-indicator@local
|
||||||
|
gnome-extensions enable deskmeter-indicator@local
|
||||||
|
```
|
||||||
|
|
||||||
### Shows "offline" or "error"
|
### Shows "offline" or "error"
|
||||||
|
|
||||||
- Ensure dmweb Flask server is running on port 10000
|
- Ensure dmweb Flask server is running on port 10000
|
||||||
@@ -75,6 +104,16 @@ Then restart GNOME Shell.
|
|||||||
```
|
```
|
||||||
Should return JSON like: `{"task_id":"12345678","task_path":"work/default"}`
|
Should return JSON like: `{"task_id":"12345678","task_path":"work/default"}`
|
||||||
|
|
||||||
|
### Changes not appearing
|
||||||
|
|
||||||
|
- Make sure you copied files after editing
|
||||||
|
- GNOME Shell must be restarted (no way around this)
|
||||||
|
- Check logs for JavaScript errors: `journalctl -b -o cat /usr/bin/gnome-shell | grep deskmeter`
|
||||||
|
|
||||||
|
### Debug with Looking Glass
|
||||||
|
|
||||||
|
Press `Alt+F2`, type `lg`, press Enter. Go to Extensions tab to see if the extension loaded and check for errors.
|
||||||
|
|
||||||
### Task path too long
|
### Task path too long
|
||||||
|
|
||||||
The extension automatically truncates paths longer than 40 characters, showing only the last two segments with a `.../ ` prefix.
|
The extension automatically truncates paths longer than 40 characters, showing only the last two segments with a `.../ ` prefix.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
|||||||
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
|
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
|
||||||
|
|
||||||
const DESKMETER_API_URL = 'http://localhost:10000/api/current_task';
|
const DESKMETER_API_URL = 'http://localhost:10000/api/current_task';
|
||||||
const UPDATE_INTERVAL = 3000; // 3 seconds
|
const DEBOUNCE_DELAY = 2200; // Wait 2.2s after workspace switch (dmcore polls every 2s)
|
||||||
|
|
||||||
const TaskIndicator = GObject.registerClass(
|
const TaskIndicator = GObject.registerClass(
|
||||||
class TaskIndicator extends PanelMenu.Button {
|
class TaskIndicator extends PanelMenu.Button {
|
||||||
@@ -24,11 +24,36 @@ class TaskIndicator extends PanelMenu.Button {
|
|||||||
|
|
||||||
this.add_child(this._label);
|
this.add_child(this._label);
|
||||||
|
|
||||||
// Start periodic updates
|
this._debounceTimeout = null;
|
||||||
|
this._workspaceManager = global.workspace_manager;
|
||||||
|
|
||||||
|
// Connect to workspace switch signal
|
||||||
|
this._workspaceSwitchedId = this._workspaceManager.connect(
|
||||||
|
'workspace-switched',
|
||||||
|
this._onWorkspaceSwitched.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initial update
|
||||||
|
this._scheduleUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onWorkspaceSwitched() {
|
||||||
|
// Debounce updates - dmcore takes ~2 seconds to detect and update
|
||||||
|
// We wait a bit to ensure the task has been updated in MongoDB
|
||||||
|
this._scheduleUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
_scheduleUpdate() {
|
||||||
|
// Clear any pending update
|
||||||
|
if (this._debounceTimeout) {
|
||||||
|
GLib.source_remove(this._debounceTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule new update
|
||||||
|
this._debounceTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, DEBOUNCE_DELAY, () => {
|
||||||
this._updateTask();
|
this._updateTask();
|
||||||
this._timeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, UPDATE_INTERVAL, () => {
|
this._debounceTimeout = null;
|
||||||
this._updateTask();
|
return GLib.SOURCE_REMOVE;
|
||||||
return GLib.SOURCE_CONTINUE;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,10 +95,16 @@ class TaskIndicator extends PanelMenu.Button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this._timeout) {
|
if (this._debounceTimeout) {
|
||||||
GLib.source_remove(this._timeout);
|
GLib.source_remove(this._debounceTimeout);
|
||||||
this._timeout = null;
|
this._debounceTimeout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._workspaceSwitchedId) {
|
||||||
|
this._workspaceManager.disconnect(this._workspaceSwitchedId);
|
||||||
|
this._workspaceSwitchedId = null;
|
||||||
|
}
|
||||||
|
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
20
gnome-extension/update.sh
Normal file
20
gnome-extension/update.sh
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Quick update script for GNOME extension development
|
||||||
|
|
||||||
|
EXTENSION_DIR="$HOME/.local/share/gnome-shell/extensions"
|
||||||
|
EXTENSION_NAME="deskmeter-indicator@local"
|
||||||
|
SOURCE_DIR="$(cd "$(dirname "$0")" && pwd)/$EXTENSION_NAME"
|
||||||
|
|
||||||
|
echo "Updating Deskmeter Task Indicator..."
|
||||||
|
|
||||||
|
# Copy extension files
|
||||||
|
cp -r "$SOURCE_DIR" "$EXTENSION_DIR/"
|
||||||
|
|
||||||
|
echo "Files copied."
|
||||||
|
echo ""
|
||||||
|
echo "Next: Restart GNOME Shell"
|
||||||
|
echo " X11: Alt+F2, type 'r', press Enter"
|
||||||
|
echo " Wayland: Log out and back in"
|
||||||
|
echo ""
|
||||||
|
echo "Check logs: journalctl -f -o cat /usr/bin/gnome-shell"
|
||||||
Reference in New Issue
Block a user