mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-05-11 03:06:55 +00:00
Merge branch 'dev'
This commit is contained in:
@@ -27,6 +27,8 @@ All notable changes to this project will be documented in this file.
|
||||
- **NomadNet file downloads (cancel)**: Fixed `AttributeError` when cancelling a download — `RequestReceipt` has no `.cancel()`; we now cancel the underlying `Resource` if present, or mark the receipt `FAILED` and remove it from the link queue.
|
||||
- **NomadNet browser (links)**: Relative `/page/` and `/file/` URLs from the Micron parser (which include backtick parameters) are now parsed correctly so they no longer show "Unsupported URL".
|
||||
- **NomadNet browser (hover)**: Links with `data-destination` now show the full URL including backtick parameters in the browser hover title.
|
||||
- **Docker build**: `build-frontend` stage now installs `python3` so docs generation succeeds in `node:24-alpine`.
|
||||
- **Docs manager**: Markdown tables in generated documentation render with proper borders and padding.
|
||||
|
||||
### Added
|
||||
|
||||
@@ -47,6 +49,9 @@ All notable changes to this project will be documented in this file.
|
||||
- **NomadNet query tests**: Frontend and backend tests for `parseNomadnetworkUrl` with query strings and `downloadNomadNetFile` data payload handling.
|
||||
- **Android RNode protection**: On Android, `RNodeInterface`, `RNodeIPInterface`, and `RNodeMultiInterface` entries in the Reticulum config are automatically disabled before startup to prevent crashes from missing serial/BLE support in Chaquopy.
|
||||
- **Android external storage**: On Android, MeshChatX now defaults to `getExternalFilesDir()` (user-accessible via file managers) instead of private internal storage.
|
||||
- **CI (Linux packages)**: AppImage, deb, and rpm release assets are now built and tested on every push to `dev` for both x64 and arm64.
|
||||
- **Docker**: Added a hardened image variant (`-hardened` suffix) with non-root user, read-only rootfs, and restricted capabilities.
|
||||
- **Map**: Drag-and-drop import of GeoJSON, KML, and KMZ files directly onto the map window, with localized drop hint overlays.
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -61,6 +66,7 @@ All notable changes to this project will be documented in this file.
|
||||
- **Sidebar order**: Reordered sidebar so **Telephone** appears directly below **Messages** for faster access.
|
||||
- **Telephone announce**: Disabled by default in `config_manager`.
|
||||
- **CONTRIBUTING.md**: Updated generative AI policy to emphasize local/offline models and reference the Reticulum Zen and License.
|
||||
- **Dependencies**: Migrated Python dependency management from **Poetry** to **UV** (0.11.12) across all CI scripts, Dockerfiles, and dev tooling. `poetry.lock` replaced with `uv.lock`.
|
||||
|
||||
## [4.6.1] - 2026-05-04
|
||||
|
||||
|
||||
+6
-3
@@ -16,17 +16,19 @@ ARG PYTHON_HASH=sha256:dd4d2bd5b53d9b25a51da13addf2be586beebd5387e289e798e4083d9
|
||||
# ---- STAGE 1: Frontend Build ----
|
||||
FROM --platform=linux/amd64 ${NODE_IMAGE}@${NODE_HASH} AS build-frontend
|
||||
WORKDIR /src
|
||||
RUN apk add --no-cache git
|
||||
RUN apk add --no-cache git python3
|
||||
COPY package.json pnpm-lock.yaml vite.config.js ./
|
||||
COPY patches ./patches
|
||||
COPY scripts/fetch-micron-wasm.mjs scripts/fetch-micron-wasm.mjs
|
||||
COPY scripts/micron-wasm-resolve-bundled.mjs scripts/micron-wasm-resolve-bundled.mjs
|
||||
COPY scripts/micron-parser-go-version.mjs scripts/micron-parser-go-version.mjs
|
||||
COPY scripts/build/fetch_reticulum_manual.py scripts/build/fetch_reticulum_manual.py
|
||||
COPY meshchatx/src/frontend ./meshchatx/src/frontend
|
||||
RUN npm install -g pnpm@10.33.0 && \
|
||||
pnpm config set verify-store-integrity true && \
|
||||
pnpm install --frozen-lockfile && \
|
||||
pnpm run build-frontend
|
||||
pnpm run build-frontend && \
|
||||
pnpm run build-docs
|
||||
|
||||
# ---- STAGE 2: Python Builder ----
|
||||
|
||||
@@ -45,7 +47,8 @@ ENV PATH="/opt/venv/bin:$PATH"
|
||||
# Install essential runtime tools in the venv (cffi verify needs setuptools on Python 3.12+)
|
||||
RUN pip install --no-cache-dir --upgrade "pip>=26.0" "setuptools" "jaraco.context>=6.1.0"
|
||||
|
||||
COPY pyproject.toml uv.lock README.md ./
|
||||
COPY pyproject.toml uv.lock README.md CHANGELOG.md ./
|
||||
COPY logo ./logo
|
||||
COPY vendor ./vendor
|
||||
RUN uv sync --no-group dev --no-install-project && \
|
||||
rm -rf /root/.cache/pip /root/.cache/uv
|
||||
|
||||
+6
-3
@@ -13,17 +13,19 @@ ARG PYTHON_RUNTIME_IMAGE=cgr.dev/chainguard/python:latest-dev
|
||||
FROM --platform=linux/amd64 ${NODE_IMAGE} AS build-frontend
|
||||
USER root
|
||||
WORKDIR /src
|
||||
RUN apk add --no-cache git
|
||||
RUN apk add --no-cache git python3
|
||||
COPY package.json pnpm-lock.yaml vite.config.js ./
|
||||
COPY patches ./patches
|
||||
COPY scripts/fetch-micron-wasm.mjs scripts/fetch-micron-wasm.mjs
|
||||
COPY scripts/micron-wasm-resolve-bundled.mjs scripts/micron-wasm-resolve-bundled.mjs
|
||||
COPY scripts/micron-parser-go-version.mjs scripts/micron-parser-go-version.mjs
|
||||
COPY scripts/build/fetch_reticulum_manual.py scripts/build/fetch_reticulum_manual.py
|
||||
COPY meshchatx/src/frontend ./meshchatx/src/frontend
|
||||
RUN npm install -g pnpm@10.33.0 && \
|
||||
pnpm config set verify-store-integrity true && \
|
||||
pnpm install --frozen-lockfile && \
|
||||
pnpm run build-frontend
|
||||
pnpm run build-frontend && \
|
||||
pnpm run build-docs
|
||||
|
||||
FROM ${PYTHON_BUILD_IMAGE} AS builder
|
||||
USER root
|
||||
@@ -37,7 +39,8 @@ ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade "pip>=26.0" "setuptools" "jaraco.context>=6.1.0"
|
||||
|
||||
COPY pyproject.toml uv.lock README.md ./
|
||||
COPY pyproject.toml uv.lock README.md CHANGELOG.md ./
|
||||
COPY logo ./logo
|
||||
COPY vendor ./vendor
|
||||
RUN uv sync --no-group dev --no-install-project && \
|
||||
rm -rf /root/.cache/pip /root/.cache/uv
|
||||
|
||||
@@ -856,4 +856,41 @@ iframe {
|
||||
.dark :deep(.max-w-none) h4 {
|
||||
color: #f4f4f5; /* zinc-100 */
|
||||
}
|
||||
|
||||
/* Markdown table styling */
|
||||
:deep(.max-w-none) table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1rem 0;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
:deep(.max-w-none) th,
|
||||
:deep(.max-w-none) td {
|
||||
border: 1px solid #d1d5db;
|
||||
padding: 0.5rem 0.75rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
:deep(.max-w-none) th {
|
||||
background-color: #f3f4f6;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
:deep(.max-w-none) tr:nth-child(even) {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.dark :deep(.max-w-none) th,
|
||||
.dark :deep(.max-w-none) td {
|
||||
border-color: #3f3f46;
|
||||
}
|
||||
|
||||
.dark :deep(.max-w-none) th {
|
||||
background-color: #27272a;
|
||||
}
|
||||
|
||||
.dark :deep(.max-w-none) tr:nth-child(even) {
|
||||
background-color: #18181b;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -149,7 +149,24 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div ref="mapContainer" class="absolute inset-0" :class="{ 'cursor-crosshair': isExportMode }"></div>
|
||||
<div
|
||||
ref="mapContainer"
|
||||
class="absolute inset-0"
|
||||
:class="{ 'cursor-crosshair': isExportMode }"
|
||||
@dragover.prevent="onMapDragOver"
|
||||
@dragleave="onMapDragLeave"
|
||||
@drop.prevent="onMapDrop"
|
||||
></div>
|
||||
|
||||
<!-- Drag-and-drop file indicator -->
|
||||
<div
|
||||
v-if="isMapDropTarget"
|
||||
class="absolute inset-0 z-40 flex flex-col items-center justify-center bg-blue-500/20 backdrop-blur-sm border-4 border-blue-500 border-dashed m-4 rounded-2xl pointer-events-none transition-opacity"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="map-plus" class="w-16 h-16 text-blue-600 dark:text-blue-400 mb-4" />
|
||||
<h3 class="text-lg font-bold text-blue-700 dark:text-blue-300">{{ $t("map.drop_geo_files") }}</h3>
|
||||
<p class="text-sm text-blue-600 dark:text-blue-400 mt-1">GeoJSON, KML, or KMZ</p>
|
||||
</div>
|
||||
|
||||
<!-- note hover tooltip -->
|
||||
<div
|
||||
@@ -1147,9 +1164,9 @@ import MapExportProgressPanel from "./internal/MapExportProgressPanel.vue";
|
||||
import MapLoadingOverlay from "./internal/MapLoadingOverlay.vue";
|
||||
import MapVectorExchangePanel from "./internal/MapVectorExchangePanel.vue";
|
||||
import { buildMeshchatMapUri, buildWebHashMapUrl } from "../../js/mapLinkUtils.js";
|
||||
import { writeFeaturesToGeoJson } from "../../js/mapExchange/geoJsonCodec.js";
|
||||
import { writeFeaturesToKml } from "../../js/mapExchange/kmlCodec.js";
|
||||
import { writeFeaturesToKmzBlob } from "../../js/mapExchange/kmzCodec.js";
|
||||
import { readGeoJsonToFeatures, writeFeaturesToGeoJson } from "../../js/mapExchange/geoJsonCodec.js";
|
||||
import { readKmlToFeatures, writeFeaturesToKml } from "../../js/mapExchange/kmlCodec.js";
|
||||
import { readKmzToFeatures, writeFeaturesToKmzBlob } from "../../js/mapExchange/kmzCodec.js";
|
||||
import { getDrawFeatureMetadataPayload, getFeatureAnchorCoordinate } from "../../js/mapExchange/metadataUtils.js";
|
||||
import { styleFromMcxProperties } from "../../js/mapExchange/styleFromProperties.js";
|
||||
import { computeSegmentMetrics, buildBearingOverlayHtml, buildBearingLiveTooltipHtml } from "../../js/mapGeodesy.js";
|
||||
@@ -1191,6 +1208,7 @@ export default {
|
||||
hasOfflineMap: false,
|
||||
metadata: null,
|
||||
isUploading: false,
|
||||
isMapDropTarget: false,
|
||||
isSettingsOpen: false,
|
||||
settingsPanelPos: null,
|
||||
settingsPanelDrag: null,
|
||||
@@ -4308,6 +4326,71 @@ export default {
|
||||
ToastUtils.error(this.$t("map.vector_import_failed"));
|
||||
},
|
||||
|
||||
onMapDragOver(ev) {
|
||||
if (ev.dataTransfer && ev.dataTransfer.types.includes("Files")) {
|
||||
this.isMapDropTarget = true;
|
||||
}
|
||||
},
|
||||
onMapDragLeave() {
|
||||
this.isMapDropTarget = false;
|
||||
},
|
||||
async onMapDrop(ev) {
|
||||
this.isMapDropTarget = false;
|
||||
const files = Array.from(ev.dataTransfer?.files || []);
|
||||
if (!files.length) return;
|
||||
|
||||
const geoFiles = files.filter((f) => {
|
||||
const name = f.name.toLowerCase();
|
||||
return (
|
||||
name.endsWith(".geojson") ||
|
||||
name.endsWith(".json") ||
|
||||
name.endsWith(".kml") ||
|
||||
name.endsWith(".kmz")
|
||||
);
|
||||
});
|
||||
if (!geoFiles.length) {
|
||||
ToastUtils.warning(this.$t("map.drop_no_geo_files"));
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of geoFiles) {
|
||||
try {
|
||||
const name = file.name.toLowerCase();
|
||||
let features = [];
|
||||
if (name.endsWith(".kmz")) {
|
||||
const buf = await this.readFileArrayBuffer(file);
|
||||
features = await readKmzToFeatures(buf, "EPSG:3857");
|
||||
} else if (name.endsWith(".kml")) {
|
||||
const text = await this.readFileText(file);
|
||||
features = readKmlToFeatures(text, "EPSG:3857");
|
||||
} else {
|
||||
const text = await this.readFileText(file);
|
||||
features = readGeoJsonToFeatures(text, "EPSG:3857");
|
||||
}
|
||||
this.onVectorExchangeImport({ features, merge: true });
|
||||
} catch (e) {
|
||||
console.error("Map drop import failed:", e);
|
||||
ToastUtils.error(this.$t("map.vector_import_failed") + ` — ${file.name}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
readFileText(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const r = new FileReader();
|
||||
r.onload = () => resolve(String(r.result || ""));
|
||||
r.onerror = () => reject(new Error("read failed"));
|
||||
r.readAsText(file);
|
||||
});
|
||||
},
|
||||
readFileArrayBuffer(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const r = new FileReader();
|
||||
r.onload = () => resolve(/** @type {ArrayBuffer} */ (r.result));
|
||||
r.onerror = () => reject(new Error("read failed"));
|
||||
r.readAsArrayBuffer(file);
|
||||
});
|
||||
},
|
||||
|
||||
exportVectorGeoJson() {
|
||||
if (!this.drawSource || !this.hasVectorDrawFeatures) {
|
||||
return;
|
||||
|
||||
@@ -1131,7 +1131,9 @@
|
||||
"vector_import_ok": "Imported {count} feature(s).",
|
||||
"vector_import_empty": "No features found in file.",
|
||||
"vector_import_failed": "Could not read vector file.",
|
||||
"vector_export_ok": "Export started."
|
||||
"vector_export_ok": "Export started.",
|
||||
"drop_geo_files": "Kartendatei hier ablegen",
|
||||
"drop_no_geo_files": "Keine GeoJSON-, KML- oder KMZ-Dateien erkannt."
|
||||
},
|
||||
"interface": {
|
||||
"disable": "Deaktivieren",
|
||||
|
||||
@@ -1079,7 +1079,9 @@
|
||||
"vector_import_ok": "Imported {count} feature(s).",
|
||||
"vector_import_empty": "No features found in file.",
|
||||
"vector_import_failed": "Could not read vector file.",
|
||||
"vector_export_ok": "Export started."
|
||||
"vector_export_ok": "Export started.",
|
||||
"drop_geo_files": "Drop map file here",
|
||||
"drop_no_geo_files": "No GeoJSON, KML, or KMZ files detected."
|
||||
},
|
||||
"interface": {
|
||||
"disable": "Disable",
|
||||
|
||||
@@ -1079,7 +1079,9 @@
|
||||
"vector_import_ok": "Imported {count} feature(s).",
|
||||
"vector_import_empty": "No features found in file.",
|
||||
"vector_import_failed": "Could not read vector file.",
|
||||
"vector_export_ok": "Export started."
|
||||
"vector_export_ok": "Export started.",
|
||||
"drop_geo_files": "Suelta el archivo del mapa aquí",
|
||||
"drop_no_geo_files": "No se detectaron archivos GeoJSON, KML o KMZ."
|
||||
},
|
||||
"interface": {
|
||||
"disable": "Inhabilitación",
|
||||
|
||||
@@ -1079,7 +1079,9 @@
|
||||
"vector_import_ok": "Imported {count} feature(s).",
|
||||
"vector_import_empty": "No features found in file.",
|
||||
"vector_import_failed": "Could not read vector file.",
|
||||
"vector_export_ok": "Export started."
|
||||
"vector_export_ok": "Export started.",
|
||||
"drop_geo_files": "Déposez le fichier carte ici",
|
||||
"drop_no_geo_files": "Aucun fichier GeoJSON, KML ou KMZ détecté."
|
||||
},
|
||||
"interface": {
|
||||
"disable": "Désactiver",
|
||||
|
||||
@@ -1131,7 +1131,9 @@
|
||||
"vector_import_ok": "Imported {count} feature(s).",
|
||||
"vector_import_empty": "No features found in file.",
|
||||
"vector_import_failed": "Could not read vector file.",
|
||||
"vector_export_ok": "Export started."
|
||||
"vector_export_ok": "Export started.",
|
||||
"drop_geo_files": "Rilascia il file della mappa qui",
|
||||
"drop_no_geo_files": "Nessun file GeoJSON, KML o KMZ rilevato."
|
||||
},
|
||||
"interface": {
|
||||
"disable": "Disabilita",
|
||||
|
||||
@@ -1079,7 +1079,9 @@
|
||||
"vector_import_ok": "Imported {count} feature(s).",
|
||||
"vector_import_empty": "No features found in file.",
|
||||
"vector_import_failed": "Could not read vector file.",
|
||||
"vector_export_ok": "Export started."
|
||||
"vector_export_ok": "Export started.",
|
||||
"drop_geo_files": "Sleep kaartbestand hierheen",
|
||||
"drop_no_geo_files": "Geen GeoJSON-, KML- of KMZ-bestanden gedetecteerd."
|
||||
},
|
||||
"interface": {
|
||||
"disable": "Uitschakelen",
|
||||
|
||||
@@ -1131,7 +1131,9 @@
|
||||
"vector_import_ok": "Imported {count} feature(s).",
|
||||
"vector_import_empty": "No features found in file.",
|
||||
"vector_import_failed": "Could not read vector file.",
|
||||
"vector_export_ok": "Export started."
|
||||
"vector_export_ok": "Export started.",
|
||||
"drop_geo_files": "Перетащите файл карты сюда",
|
||||
"drop_no_geo_files": "Не обнаружено файлов GeoJSON, KML или KMZ."
|
||||
},
|
||||
"interface": {
|
||||
"disable": "Выключить",
|
||||
|
||||
@@ -1079,7 +1079,9 @@
|
||||
"vector_import_ok": "Imported {count} feature(s).",
|
||||
"vector_import_empty": "No features found in file.",
|
||||
"vector_import_failed": "Could not read vector file.",
|
||||
"vector_export_ok": "Export started."
|
||||
"vector_export_ok": "Export started.",
|
||||
"drop_geo_files": "将地图文件拖放到此处",
|
||||
"drop_no_geo_files": "未检测到 GeoJSON、KML 或 KMZ 文件。"
|
||||
},
|
||||
"interface": {
|
||||
"disable": "禁用",
|
||||
|
||||
Reference in New Issue
Block a user