1
0
mirror of git://f0xx.org/android_cast synced 2026-07-29 05:17:39 +03:00

new stuff

This commit is contained in:
Anton Afanasyeu
2026-05-23 00:12:44 +02:00
parent 7900441ff6
commit 91e9b1d9ed
5 changed files with 300 additions and 30 deletions

View File

@@ -479,7 +479,30 @@ a:hover { text-decoration: underline; }
}
.th-drag:active { cursor: grabbing; }
.th-label { flex: 1; min-width: 0; }
.th-draggable.th-dragging { opacity: .55; }
.th-draggable.th-dragging { opacity: .45; }
.th-draggable.th-dragging .th-label {
color: var(--accent);
font-weight: 600;
}
.data-table th.th-drop-target {
box-shadow: inset 0 -3px 0 var(--accent);
background: var(--row-hover);
}
.col-drag-ghost {
position: fixed;
top: -1000px;
left: -1000px;
z-index: 9999;
padding: 6px 12px;
border-radius: 8px;
background: var(--accent);
color: #fff;
font-size: 13px;
font-weight: 600;
box-shadow: 0 6px 18px rgba(0, 0, 0, .35);
pointer-events: none;
white-space: nowrap;
}
.col-resizer {
position: absolute;
top: 0;
@@ -548,20 +571,33 @@ a:hover { text-decoration: underline; }
.reports-search-row {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
gap: 10px 14px;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-end;
gap: 10px 12px;
width: 100%;
margin: 12px 0 8px;
box-sizing: border-box;
}
.reports-search-label {
font-size: 13px;
color: var(--muted);
flex: 0 0 auto;
margin: 0;
line-height: 1.2;
align-self: center;
}
.reports-search-field {
position: relative;
flex: 1 1 280px;
min-width: 200px;
flex: 0 0 40%;
width: 40%;
max-width: 40%;
align-self: center;
}
.reports-search-row .btn {
flex: 0 0 auto;
align-self: center;
margin-left: 0;
}
.reports-search-input {
width: 100%;

View File

@@ -331,34 +331,79 @@
cg.innerHTML = html;
}
function columnLabelByKey(key) {
const c = LIST_COLUMNS.find((col) => col.key === key);
return c ? c.label : key;
}
function clearColumnDropMarkers(thead) {
thead.querySelectorAll('.th-drop-target').forEach((el) => {
el.classList.remove('th-drop-target');
});
thead.querySelectorAll('.th-dragging').forEach((el) => {
el.classList.remove('th-dragging');
});
}
function bindReportsTableLayout(table, thead, ctx) {
if (!table || !thead || table.dataset.layoutBound === '1') return;
table.dataset.layoutBound = '1';
let dragKey = null;
thead.addEventListener('dragstart', (e) => {
const handle = e.target.closest('.th-drag');
if (!handle || ctx.getGrouped()) return;
dragKey = handle.getAttribute('data-col-key');
const sourceKey = handle.getAttribute('data-col-key');
if (!sourceKey) return;
table.dataset.dragColKey = sourceKey;
const th = handle.closest('th');
if (th) th.classList.add('th-dragging');
const label = columnLabelByKey(sourceKey);
const ghost = document.createElement('div');
ghost.className = 'col-drag-ghost';
ghost.textContent = label;
ghost.setAttribute('aria-hidden', 'true');
document.body.appendChild(ghost);
table._dragGhost = ghost;
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', dragKey || '');
e.dataTransfer.setData('text/plain', sourceKey);
try {
e.dataTransfer.setDragImage(ghost, 16, 14);
} catch { /* ignore */ }
}
});
thead.addEventListener('dragend', (e) => {
const th = e.target.closest('th');
if (th) th.classList.remove('th-dragging');
dragKey = null;
thead.addEventListener('dragend', () => {
clearColumnDropMarkers(thead);
delete table.dataset.dragColKey;
if (table._dragGhost) {
table._dragGhost.remove();
table._dragGhost = null;
}
});
thead.addEventListener('dragenter', (e) => {
if (ctx.getGrouped() || !table.dataset.dragColKey) return;
e.preventDefault();
});
thead.addEventListener('dragover', (e) => {
if (ctx.getGrouped()) return;
if (!e.target.closest('th[data-col-key]')) return;
if (ctx.getGrouped() || !table.dataset.dragColKey) return;
e.preventDefault();
if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';
const th = e.target.closest('th[data-col-key]');
if (!th) return;
const active = thead.querySelector('.th-drop-target');
if (active && active !== th) active.classList.remove('th-drop-target');
th.classList.add('th-drop-target');
});
thead.addEventListener('dragleave', (e) => {
const th = e.target.closest('th[data-col-key]');
if (!th) return;
const rel = e.relatedTarget;
if (rel && th.contains(rel)) return;
th.classList.remove('th-drop-target');
});
thead.addEventListener('drop', (e) => {
@@ -366,9 +411,18 @@
const th = e.target.closest('th[data-col-key]');
if (!th) return;
e.preventDefault();
e.stopPropagation();
const sourceKey =
(e.dataTransfer && e.dataTransfer.getData('text/plain')) || dragKey;
table.dataset.dragColKey ||
(e.dataTransfer && e.dataTransfer.getData('text/plain')) ||
'';
const targetKey = th.getAttribute('data-col-key');
clearColumnDropMarkers(thead);
delete table.dataset.dragColKey;
if (table._dragGhost) {
table._dragGhost.remove();
table._dragGhost = null;
}
if (!sourceKey || !targetKey || sourceKey === targetKey) return;
const order = getOrderedColumnKeys();
const from = order.indexOf(sourceKey);
@@ -377,7 +431,6 @@
order.splice(from, 1);
order.splice(to, 0, sourceKey);
saveColumnOrder(order);
dragKey = null;
ctx.refreshLayout();
});
@@ -907,7 +960,6 @@
const searchSuggest = document.getElementById('reports-search-suggest');
const searchClear = document.getElementById('reports-search-clear');
let suggestTimer = null;
let searchRunTimer = null;
function hideSuggest() {
if (searchSuggest) {
@@ -988,22 +1040,18 @@
if (searchClear) searchClear.hidden = false;
}
searchInput.addEventListener('input', () => {
const q = searchInput.value.trim();
if (searchClear) searchClear.hidden = !q;
const q = searchInput.value;
if (searchClear) searchClear.hidden = !q.trim();
hideSuggest();
clearTimeout(suggestTimer);
clearTimeout(searchRunTimer);
if (q.length < 2) {
hideSuggest();
return;
}
suggestTimer = setTimeout(() => fetchSuggest(q), 1000);
searchRunTimer = setTimeout(() => applySearchQuery(q, true), 1600);
const trimmed = q.trim();
if (trimmed.length < 2) return;
suggestTimer = setTimeout(() => fetchSuggest(trimmed), 1000);
});
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
clearTimeout(suggestTimer);
clearTimeout(searchRunTimer);
hideSuggest();
applySearchQuery(searchInput.value.trim(), true);
}
@@ -1012,14 +1060,26 @@
}
});
}
function appendSearchTerm(term) {
if (!searchInput) return;
const t = (term || '').trim();
if (!t) return;
const cur = searchInput.value.trim();
if (!cur) {
searchInput.value = t;
} else if (!cur.toLowerCase().includes(t.toLowerCase())) {
searchInput.value = cur + ' ' + t;
}
if (searchClear) searchClear.hidden = !searchInput.value.trim();
searchInput.focus();
}
if (searchSuggest) {
searchSuggest.addEventListener('click', (e) => {
const btn = e.target.closest('[data-suggest]');
if (!btn) return;
const term = btn.getAttribute('data-suggest') || '';
if (searchInput) searchInput.value = term;
appendSearchTerm(term);
hideSuggest();
applySearchQuery(term, true);
});
}
if (searchClear) {

View File

@@ -0,0 +1,101 @@
#!/usr/bin/env bash
# Batch-upload fake crash reports (java + native) from test_reports/.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
REPORTS_DIR="${REPORTS_DIR:-$ROOT/test_reports}"
BASE="${BASE:-https://apps.f0xx.org/app/androidcast_project/crashes}"
UPLOAD_URL="$BASE/api/upload.php"
JAR="${JAR:-/tmp/ac_crash_cookies.txt}"
LOGIN="${LOGIN:-0}"
DELAY_MS="${DELAY_MS:-50}"
ONLY="${ONLY:-all}" # all | java | native
if [[ ! -d "$REPORTS_DIR" ]]; then
echo "Missing $REPORTS_DIR — run: python3 $ROOT/generate_test_reports.py" >&2
exit 1
fi
shopt -s nullglob
FILES=()
case "$ONLY" in
java)
FILES=("$REPORTS_DIR"/crash_java_*.json)
;;
native)
FILES=("$REPORTS_DIR"/crash_native_*.json)
;;
all|*)
FILES=("$REPORTS_DIR"/crash_java_*.json "$REPORTS_DIR"/crash_native_*.json)
# legacy single-prefix files from older generator
LEGACY=("$REPORTS_DIR"/crash_[0-9][0-9][0-9].json)
if [[ ${#FILES[@]} -eq 0 && ${#LEGACY[@]} -gt 0 ]]; then
FILES=("${LEGACY[@]}")
fi
;;
esac
shopt -u nullglob
if [[ ${#FILES[@]} -eq 0 ]]; then
echo "No reports in $REPORTS_DIR (expected crash_java_*.json / crash_native_*.json)" >&2
exit 1
fi
IFS=$'\n' FILES=($(printf '%s\n' "${FILES[@]}" | sort))
unset IFS
java_n=0
native_n=0
for f in "${FILES[@]}"; do
case "$(basename "$f")" in
crash_java_*) ((java_n++)) || true ;;
crash_native_*) ((native_n++)) || true ;;
esac
done
if [[ "$LOGIN" == "1" ]]; then
curl -sk -c "$JAR" -b "$JAR" -L -X POST "$BASE/login" \
-d 'username=admin&password=admin' -o /dev/null
echo "Logged in (cookie jar: $JAR)"
fi
ok=0
fail=0
dup=0
ok_java=0
ok_native=0
echo "Uploading ${#FILES[@]} reports to $UPLOAD_URL (java=$java_n native=$native_n only=$ONLY)"
for f in "${FILES[@]}"; do
name="$(basename "$f")"
body="$(curl -sk -w '\n%{http_code}' -H 'Content-Type: application/json' --data-binary "@$f" "$UPLOAD_URL")"
code="$(echo "$body" | tail -n1)"
json="$(echo "$body" | sed '$d')"
case "$code" in
200)
((ok++)) || true
case "$name" in
crash_java_*) ((ok_java++)) || true; echo " OK [java] $name" ;;
crash_native_*) ((ok_native++)) || true; echo " OK [native] $name" ;;
*) echo " OK $name" ;;
esac
;;
409)
((dup++)) || true
echo " DUP $name (report_id already stored)"
;;
*)
((fail++)) || true
echo " FAIL $name HTTP $code$json" >&2
;;
esac
if [[ "$DELAY_MS" -gt 0 ]]; then
sleep "$(awk "BEGIN { print $DELAY_MS / 1000 }")"
fi
done
echo "Done: ok=$ok (java=$ok_java native=$ok_native) duplicate=$dup failed=$fail (total=${#FILES[@]})"
[[ "$fail" -eq 0 ]]