You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
flyweb/flymaster-ui.html

349 lines
13 KiB

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flymaster GPS Client</title>
<!-- Vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<!-- Flymaster Client -->
<script src="flymaster-client.js"></script>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.container {
border: 1px solid #ddd;
border-radius: 5px;
padding: 20px;
margin-bottom: 20px;
}
h1, h2 {
color: #333;
}
button {
background-color: #4CAF50;
border: none;
color: white;
padding: 10px 15px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.status {
margin: 10px 0;
padding: 10px;
border-radius: 4px;
}
.status.success {
background-color: #dff0d8;
color: #3c763d;
}
.status.error {
background-color: #f2dede;
color: #a94442;
}
.status.info {
background-color: #d9edf7;
color: #31708f;
}
table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
table, th, td {
border: 1px solid #ddd;
}
th, td {
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
.progress-container {
width: 100%;
background-color: #f1f1f1;
border-radius: 4px;
margin: 10px 0;
}
.progress-bar {
height: 20px;
background-color: #4CAF50;
border-radius: 4px;
text-align: center;
color: white;
line-height: 20px;
}
.track-list {
max-height: 300px;
overflow-y: auto;
}
</style>
</head>
<body>
<div id="app">
<h1>Flymaster GPS Client</h1>
<div class="container">
<h2>Connection</h2>
<button @click="connect" :disabled="isConnected">Connect</button>
<button @click="disconnect" :disabled="!isConnected">Disconnect</button>
<div v-if="statusMessage" :class="['status', statusType]">
{{ statusMessage }}
</div>
<div v-if="deviceInfo">
<h3>Device Information</h3>
<p><strong>Name:</strong> {{ deviceInfo.name }}</p>
<p><strong>Unit ID:</strong> {{ deviceInfo.unitId }}</p>
</div>
</div>
<div class="container" v-if="isConnected">
<h2>Tracks</h2>
<button @click="refreshTracks" :disabled="isLoading">Refresh Track List</button>
<div v-if="isLoading" class="status info">
Loading tracks...
</div>
<div v-if="tracks.length > 0" class="track-list">
<table>
<thead>
<tr>
<th>Select</th>
<th>#</th>
<th>Start Date</th>
<th>End Date</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr v-for="track in tracks" :key="track.index">
<td><input type="checkbox" v-model="selectedTracks" :value="track.index"></td>
<td>{{ track.index + 1 }}</td>
<td>{{ formatDate(track.startDate) }}</td>
<td>{{ formatDate(track.endDate) }}</td>
<td>{{ formatDuration(track.duration) }}</td>
</tr>
</tbody>
</table>
<button @click="downloadTracks" :disabled="selectedTracks.length === 0 || isDownloading">
Download Selected Tracks
</button>
</div>
<div v-if="isDownloading">
<h3>Download Progress</h3>
<div class="progress-container">
<div class="progress-bar" :style="{ width: downloadProgress + '%' }">
{{ downloadProgress }}%
</div>
</div>
</div>
</div>
<div class="container" v-if="trackPoints.length > 0">
<h2>Downloaded Track Data</h2>
<p>Total points: {{ trackPoints.length }}</p>
<div v-if="trackPoints.length > 0">
<h3>Sample Points</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Latitude</th>
<th>Longitude</th>
<th>GPS Alt</th>
<th>Baro Alt</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<tr v-for="(point, index) in samplePoints" :key="index">
<td>{{ index + 1 }}</td>
<td>{{ point.lat.toFixed(6) }}</td>
<td>{{ point.lon.toFixed(6) }}</td>
<td>{{ point.gpsalt }}m</td>
<td>{{ point.baroalt.toFixed(1) }}m</td>
<td>{{ formatDate(new Date(point.time * 1000)) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
client: null,
isConnected: false,
isLoading: false,
isDownloading: false,
statusMessage: '',
statusType: 'info',
deviceInfo: null,
tracks: [],
selectedTracks: [],
trackPoints: [],
downloadProgress: 0
},
computed: {
samplePoints() {
// Return a sample of points (first 10)
return this.trackPoints.slice(0, 10);
}
},
methods: {
async connect() {
try {
this.statusMessage = 'Connecting to device...';
this.statusType = 'info';
this.client = new FlymasterClient();
const connected = await this.client.connect();
if (connected) {
this.isConnected = true;
this.statusMessage = 'Connected to device successfully!';
this.statusType = 'success';
// Initialize GPS
await this.initializeGps();
} else {
this.statusMessage = 'Failed to connect to device.';
this.statusType = 'error';
}
} catch (error) {
console.error('Connection error:', error);
this.statusMessage = 'Error connecting to device: ' + error.message;
this.statusType = 'error';
}
},
async disconnect() {
try {
if (this.client) {
await this.client.disconnect();
this.isConnected = false;
this.statusMessage = 'Disconnected from device.';
this.statusType = 'info';
this.deviceInfo = null;
}
} catch (error) {
console.error('Disconnection error:', error);
this.statusMessage = 'Error disconnecting from device: ' + error.message;
this.statusType = 'error';
}
},
async initializeGps() {
try {
this.isLoading = true;
this.statusMessage = 'Initializing GPS...';
this.statusType = 'info';
await this.client.initGps();
this.deviceInfo = {
name: this.client.gpsname,
unitId: this.client.gpsunitid
};
this.refreshTracks();
this.statusMessage = 'GPS initialized successfully!';
this.statusType = 'success';
} catch (error) {
console.error('GPS initialization error:', error);
this.statusMessage = 'Error initializing GPS: ' + error.message;
this.statusType = 'error';
} finally {
this.isLoading = false;
}
},
async refreshTracks() {
try {
this.isLoading = true;
this.statusMessage = 'Loading tracks...';
this.statusType = 'info';
this.tracks = this.client.getTrackList();
this.selectedTracks = [];
this.statusMessage = `Loaded ${this.tracks.length} tracks.`;
this.statusType = 'success';
} catch (error) {
console.error('Track loading error:', error);
this.statusMessage = 'Error loading tracks: ' + error.message;
this.statusType = 'error';
} finally {
this.isLoading = false;
}
},
async downloadTracks() {
if (this.selectedTracks.length === 0) return;
try {
this.isDownloading = true;
this.downloadProgress = 0;
this.statusMessage = 'Downloading selected tracks...';
this.statusType = 'info';
this.client.selectTracks(this.selectedTracks);
this.trackPoints = await this.client.downloadSelectedTracks((current, total) => {
this.downloadProgress = Math.floor((current / total) * 100);
return true; // Continue download
});
this.statusMessage = `Downloaded ${this.trackPoints.length} points successfully!`;
this.statusType = 'success';
} catch (error) {
console.error('Download error:', error);
this.statusMessage = 'Error downloading tracks: ' + error.message;
this.statusType = 'error';
} finally {
this.isDownloading = false;
}
},
formatDate(date) {
return new Date(date).toLocaleString();
},
formatDuration(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
}
});
</script>
</body>
</html>