Basic dialog to start/pause/stop and clear track recording.

This commit is contained in:
Eric van der Vlist 2022-09-27 21:53:52 +02:00
parent d71881dc02
commit 3235d9ae70
4 changed files with 264 additions and 25 deletions

View File

@ -1,15 +1,30 @@
import React, { useState } from 'react'; import React, { Fragment, useRef, useState } from 'react';
import { useDB } from 'react-pouchdb';
import { useDB, useFind } from 'react-pouchdb';
import '../../theme/get-location.css'; import '../../theme/get-location.css';
import { IonButton, IonIcon } from '@ionic/react'; import {
import { recordingOutline, recording } from 'ionicons/icons'; createAnimation,
IonButton,
IonButtons,
IonContent,
IonIcon,
IonModal,
IonTitle,
IonToolbar,
} from '@ionic/react';
import {
recordingOutline,
recording,
closeCircle,
pauseCircle,
stop,
} from 'ionicons/icons';
import { import {
startBackgroundGeolocation, startBackgroundGeolocation,
stopBackgroundGeolocation, stopBackgroundGeolocation,
} from '../../lib/background-geolocation'; } from '../../lib/background-geolocation';
import { deleteCurrent, saveCurrent } from '../../db/gpx';
const GpxRecord: React.FC<{}> = () => { const GpxRecord: React.FC<{}> = () => {
const db = useDB(); const db = useDB();
@ -18,24 +33,152 @@ const GpxRecord: React.FC<{}> = () => {
const [watcher_id, setWatcher_id] = useState(); const [watcher_id, setWatcher_id] = useState();
const toggleIsRecording = () => { const gpxes = useFind({
if (isRecording) { selector: {
stopBackgroundGeolocation(watcher_id); type: 'gpx',
} else { subtype: 'current',
},
});
const hasCurrentTrack = gpxes.length > 0;
console.log(
`GpxRecord, hasCurrentTrack:${hasCurrentTrack}, gpxes: ${JSON.stringify(
gpxes
)}`
);
const modal = useRef<HTMLIonModalElement>(null);
const dismiss = () => {
modal.current?.dismiss();
};
const startRecording = () => {
startBackgroundGeolocation(db).then((result) => { startBackgroundGeolocation(db).then((result) => {
setWatcher_id(result); setWatcher_id(result);
}); });
setIsRecording(true);
dismiss();
};
const pauseRecording = () => {
if (isRecording) {
stopBackgroundGeolocation(watcher_id);
} }
setIsRecording(!isRecording); setIsRecording(false);
dismiss();
};
const stopRecording = () => {
saveCurrent(db);
pauseRecording();
};
const deleteRecording = () => {
deleteCurrent(db);
pauseRecording();
};
const enterAnimation = (baseEl: HTMLElement) => {
const root = baseEl.shadowRoot;
const backdropAnimation = createAnimation()
.addElement(root?.querySelector('ion-backdrop')!)
.fromTo('opacity', '0.01', 'var(--backdrop-opacity)');
const wrapperAnimation = createAnimation()
.addElement(root?.querySelector('.modal-wrapper')!)
.keyframes([
{ offset: 0, opacity: '0', transform: 'scale(0)' },
{ offset: 1, opacity: '0.99', transform: 'scale(1)' },
]);
return createAnimation()
.addElement(baseEl)
.easing('ease-out')
.duration(500)
.addAnimation([backdropAnimation, wrapperAnimation]);
};
const leaveAnimation = (baseEl: HTMLElement) => {
return enterAnimation(baseEl).direction('reverse');
}; };
return ( return (
<IonButton onClick={toggleIsRecording}> <Fragment>
<IonButton id='open-RecordDialog'>
{isRecording && ( {isRecording && (
<IonIcon slot='icon-only' icon={recording} style={{ color: 'red' }} /> <IonIcon slot='icon-only' icon={recording} style={{ color: 'red' }} />
)} )}
{!isRecording && <IonIcon slot='icon-only' icon={recordingOutline} />} {!isRecording && <IonIcon slot='icon-only' icon={recordingOutline} />}
</IonButton> </IonButton>
<IonModal
ref={modal}
trigger='open-RecordDialog'
enterAnimation={enterAnimation}
leaveAnimation={leaveAnimation}
>
<IonToolbar>
<IonTitle>Track recording</IonTitle>
<IonButtons slot='end'>
<IonButton onClick={() => dismiss()}>Close</IonButton>
</IonButtons>
</IonToolbar>
<IonContent>
{!isRecording && (
<IonButton
expand='block'
color='primary'
size='large'
onClick={startRecording}
>
<IonIcon slot='start' icon={recording}></IonIcon>
{hasCurrentTrack ? (
<span>Resume recording</span>
) : (
<span>Start recording</span>
)}
</IonButton>
)}
{isRecording && (
<IonButton
expand='block'
color='primary'
size='large'
onClick={pauseRecording}
>
<IonIcon slot='start' icon={pauseCircle}></IonIcon>Pause
</IonButton>
)}
{hasCurrentTrack && (
<>
<IonButton
expand='block'
color='danger'
size='large'
onClick={stopRecording}
>
<IonIcon slot='start' icon={stop}></IonIcon>Stop recording
<br />
(and save track)
</IonButton>
<IonButton
expand='block'
color='danger'
size='large'
onClick={deleteRecording}
>
<IonIcon slot='start' icon={closeCircle}></IonIcon>Cancel
recording
<br />
(and clear track)
</IonButton>
</>
)}
</IonContent>
</IonModal>
</Fragment>
); );
}; };

View File

@ -1,6 +1,6 @@
import { initDb } from '.'; import { initDb } from '.';
import PouchDB from 'pouchdb'; import PouchDB from 'pouchdb';
import { appendTrkpt } from './gpx'; import { appendTrkpt, deleteCurrent, saveCurrent } from './gpx';
import PouchDBFind from 'pouchdb-find'; import PouchDBFind from 'pouchdb-find';
import { mkdtempSync, rmdirSync } from 'fs'; import { mkdtempSync, rmdirSync } from 'fs';
@ -48,3 +48,54 @@ describe('Checking that trkpts are beeing inserted', () => {
expect(docs.length).toBe(3); expect(docs.length).toBe(3);
}); });
}); });
describe('Checking that saveCurrent() is working as expected', () => {
test(', that we still have two documents after saving.', async () => {
await appendTrkpt(db, {});
await saveCurrent(db);
const results = await db.find({
selector: {},
});
const docs = results.docs;
// console.log(`docs: ${JSON.stringify(docs)}`);
expect(docs.length).toBe(2);
});
test("and that we don't have current tracks after saving.", async () => {
await appendTrkpt(db, {});
await saveCurrent(db);
const results = await db.find({
selector: {
type: 'gpx',
subtype: 'current',
},
});
const docs = results.docs;
// console.log(`docs: ${JSON.stringify(docs)}`);
expect(docs.length).toBe(0);
});
});
describe('Checking that deleteCurrent() is working as expected', () => {
test(', that we have no more documents after deleting.', async () => {
await appendTrkpt(db, { time: '' });
await deleteCurrent(db);
const results = await db.find({
selector: {},
});
const docs = results.docs;
// console.log(`docs: ${JSON.stringify(docs)}`);
expect(docs.length).toBe(0);
});
test(', that we have two documents from a saved track after deleting the current one.', async () => {
await appendTrkpt(db, { time: '' });
await saveCurrent(db);
await appendTrkpt(db, { time: '' });
await deleteCurrent(db);
const results = await db.find({
selector: {},
});
const docs = results.docs;
// console.log(`docs: ${JSON.stringify(docs)}`);
expect(docs.length).toBe(2);
});
});

View File

@ -93,8 +93,6 @@ const initialGpx = {
}; };
export const appendTrkpt = async (db: any, trkpt: any) => { export const appendTrkpt = async (db: any, trkpt: any) => {
const currents = await db.find({ const currents = await db.find({
selector: { selector: {
type: 'gpx', type: 'gpx',
@ -118,3 +116,49 @@ export const appendTrkpt = async (db: any, trkpt: any) => {
await db.post(docPoint); await db.post(docPoint);
console.log(JSON.stringify(docPoint)); console.log(JSON.stringify(docPoint));
}; };
export const saveCurrent = async (db: any) => {
const currents = await db.find({
selector: {
type: 'gpx',
subtype: 'current',
},
});
console.log(`saveCurrent - db.find() : ${JSON.stringify(currents)}`);
if (currents.docs.length > 0) {
const doc = currents.docs[0];
doc.subtype = 'other';
await db.put(doc);
}
};
export const deleteCurrent = async (db: any) => {
const currents = await db.find({
selector: {
type: 'gpx',
subtype: 'current',
},
fields: ['_id', '_rev'],
});
console.log(`deleteCurrent - db.find(gpx) : ${JSON.stringify(currents)}`);
const docs: { _id: string; _rev: string }[] = currents.docs;
for (let i = 0; i < docs.length; i++) {
console.log(`Deleting document ${JSON.stringify(docs[i])}`);
await db.put({ _deleted: true, ...docs[i] });
console.log(`done, id: ${docs[i]._id}`);
const currentTrkpts = await db.find({
selector: {
type: 'trkpt',
gpx: docs[i]._id,
},
fields: ['_id', '_rev', 'type'],
});
console.log(
`deleteCurrent - db.find(trkpts) : ${JSON.stringify(currentTrkpts)}`
);
const trkpts: { _id: string; _rev: string }[] = currentTrkpts.docs;
for (let j = 0; j < trkpts.length; j++) {
await db.put({ _deleted: true, ...trkpts[j] });
}
}
};

View File

@ -53,9 +53,9 @@ export const initDb = async (db: any, setDbReady: any) => {
for (var index of existingIndexes) { for (var index of existingIndexes) {
if (index.type === 'json') { if (index.type === 'json') {
// Non system indexes // Non system indexes
console.log(`Checking existing index :${JSON.stringify(index)}`); // console.log(`Checking existing index :${JSON.stringify(index)}`);
if (!findIndex(requiredIndexes, index)) { if (!findIndex(requiredIndexes, index)) {
console.log(`db.deleteIndex(${JSON.stringify(index)})`); // console.log(`db.deleteIndex(${JSON.stringify(index)})`);
await db.deleteIndex(index); await db.deleteIndex(index);
} }
} }
@ -63,14 +63,14 @@ export const initDb = async (db: any, setDbReady: any) => {
for (var index of requiredIndexes) { for (var index of requiredIndexes) {
if (!findIndex(existingIndexes, index)) { if (!findIndex(existingIndexes, index)) {
console.log(`db.createIndex(${JSON.stringify(index)})`); // console.log(`db.createIndex(${JSON.stringify(index)})`);
await db.createIndex({ name: index.name, ...index.def }); await db.createIndex({ name: index.name, ...index.def });
} }
} }
setDbReady(true); setDbReady(true);
const indexes = await db.getIndexes(); /* const indexes = await db.getIndexes();
console.log(`indexes: ${JSON.stringify(indexes)}`); console.log(`indexes: ${JSON.stringify(indexes)}`);
const explain1 = await db.explain({ const explain1 = await db.explain({
@ -90,4 +90,5 @@ export const initDb = async (db: any, setDbReady: any) => {
// use_index: 'type-trkpt-gpx-time', // use_index: 'type-trkpt-gpx-time',
}); });
console.log(`explain2: ${JSON.stringify(explain2)}`); console.log(`explain2: ${JSON.stringify(explain2)}`);
*/
}; };