Browse Source

Version Info and modal titles

pull/21/head
Sacha Weatherstone 4 years ago
parent
commit
d8fedb56bb
  1. 3
      package.json
  2. 99
      pnpm-lock.yaml
  3. 111
      src/components/Connection.tsx
  4. 25
      src/components/generic/Modal.tsx
  5. 19
      src/components/menu/BottomNav.tsx
  6. 100
      src/components/modals/VersionInfo.tsx
  7. 6
      src/core/slices/appSlice.ts
  8. 6
      src/core/utils/fetcher.ts
  9. 9
      src/core/utils/gqlFetcher.ts
  10. 2
      src/pages/Extensions/FileBrowser.tsx
  11. 18
      todo.txt
  12. 39
      types/github.ts

3
package.json

@ -14,13 +14,12 @@
},
"dependencies": {
"@emeraldpay/hashicon-react": "^0.5.2",
"@meshtastic/meshtasticjs": "^0.6.45",
"@meshtastic/meshtasticjs": "^0.6.46",
"@reduxjs/toolkit": "^1.7.2",
"@tippyjs/react": "^4.2.6",
"base64-js": "^1.5.1",
"cuid": "^2.1.8",
"framer-motion": "^6.2.6",
"graphql-request": "^4.0.0",
"mapbox-gl": "^2.7.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",

99
pnpm-lock.yaml

@ -3,7 +3,7 @@ lockfileVersion: 5.3
specifiers:
'@emeraldpay/hashicon-react': ^0.5.2
'@hookform/devtools': ^4.0.2
'@meshtastic/meshtasticjs': ^0.6.45
'@meshtastic/meshtasticjs': ^0.6.46
'@reduxjs/toolkit': ^1.7.2
'@tippyjs/react': ^4.2.6
'@types/mapbox-gl': ^2.6.1
@ -28,7 +28,6 @@ specifiers:
eslint-plugin-react: ^7.28.0
eslint-plugin-react-hooks: ^4.3.0
framer-motion: ^6.2.6
graphql-request: ^4.0.0
gzipper: ^7.0.0
mapbox-gl: ^2.7.0
postcss: ^8.4.6
@ -62,13 +61,12 @@ specifiers:
dependencies:
'@emeraldpay/hashicon-react': 0.5.2
'@meshtastic/meshtasticjs': 0.6.45
'@meshtastic/meshtasticjs': 0.6.46
'@reduxjs/toolkit': 1.7[email protected][email protected]
'@tippyjs/react': 4.2[email protected][email protected]
base64-js: 1.5.1
cuid: 2.1.8
framer-motion: 6.2[email protected][email protected]
graphql-request: 4.0.0
mapbox-gl: 2.7.0
react: 17.0.2
react-dom: 17.0[email protected]
@ -1638,8 +1636,8 @@ packages:
engines: {node: '>=6.0.0'}
dev: false
/@meshtastic/meshtasticjs/0.6.45:
resolution: {integrity: sha512-icAGMofpQ3hYqWhjYMLqMyhb0Xtk3GozEgOXScFTKVOaecDY0XIJmuvoBDfgD2lxNxDDPr0Dj77js+3JGxTcRg==}
/@meshtastic/meshtasticjs/0.6.46:
resolution: {integrity: sha512-XOaQz75kYDNvv1zK9KXy5te8V9AEyVXWK0JvK3v1v+WqAJ9UNvnNcaQTRsJBPG2elmE3cQkfEz+WfBus2wve8g==}
dependencies:
'@protobuf-ts/runtime': 2.2.2
sub-events: 1.8.9
@ -2312,10 +2310,6 @@ packages:
resolution: {integrity: sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=}
dev: true
/asynckit/0.4.0:
resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=}
dev: false
/at-least-node/1.0.0:
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
engines: {node: '>= 4.0.0'}
@ -2578,13 +2572,6 @@ packages:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
/combined-stream/1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: false
/commander/2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
dev: true
@ -2648,14 +2635,6 @@ packages:
yaml: 1.10.2
dev: true
/cross-fetch/3.1.5:
resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==}
dependencies:
node-fetch: 2.6.7
transitivePeerDependencies:
- encoding
dev: false
/cross-spawn/7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
@ -2827,11 +2806,6 @@ packages:
resolution: {integrity: sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=}
dev: true
/delayed-stream/1.0.0:
resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=}
engines: {node: '>=0.4.0'}
dev: false
/detective/5.2.0:
resolution: {integrity: sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==}
engines: {node: '>=0.8.0'}
@ -3594,11 +3568,6 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/extract-files/9.0.0:
resolution: {integrity: sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==}
engines: {node: ^10.17.0 || ^12.0.0 || >= 13.7.0}
dev: false
/fast-deep-equal/3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
dev: true
@ -3713,15 +3682,6 @@ packages:
resolution: {integrity: sha1-C+4AUBiusmDQo6865ljdATbsG5k=}
dev: true
/form-data/3.0.1:
resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.34
dev: false
/fraction.js/4.1.3:
resolution: {integrity: sha512-pUHWWt6vHzZZiQJcM6S/0PXfS+g6FM4BF5rj9wZyreivhQPdsh5PpE25VtSNxq80wHS5RfY51Ii+8Z0Zl/pmzg==}
dev: true
@ -3882,18 +3842,6 @@ packages:
resolution: {integrity: sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==}
dev: true
/graphql-request/4.0.0:
resolution: {integrity: sha512-cdqQLCXlBGkaLdkLYRl4LtkwaZU6TfpE7/tnUQFl3wXfUPWN74Ov+Q61VuIh+AltS789YfGB6whghmCmeXLvTw==}
peerDependencies:
graphql: 14 - 16
dependencies:
cross-fetch: 3.1.5
extract-files: 9.0.0
form-data: 3.0.1
transitivePeerDependencies:
- encoding
dev: false
/grid-index/1.1.0:
resolution: {integrity: sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==}
dev: false
@ -4489,18 +4437,6 @@ packages:
picomatch: 2.3.1
dev: true
/mime-db/1.51.0:
resolution: {integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==}
engines: {node: '>= 0.6'}
dev: false
/mime-types/2.1.34:
resolution: {integrity: sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.51.0
dev: false
/mimic-fn/2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
@ -4562,18 +4498,6 @@ packages:
resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=}
dev: true
/node-fetch/2.6.7:
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
dependencies:
whatwg-url: 5.0.0
dev: false
/node-modules-regexp/1.0.0:
resolution: {integrity: sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=}
engines: {node: '>=0.10.0'}
@ -5858,10 +5782,6 @@ packages:
resolution: {integrity: sha1-bkWxJj8gF/oKzH2J14sVuL932jI=}
dev: false
/tr46/0.0.3:
resolution: {integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=}
dev: false
/tr46/1.0.1:
resolution: {integrity: sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=}
dependencies:
@ -6114,21 +6034,10 @@ packages:
defaults: 1.0.3
dev: true
/webidl-conversions/3.0.1:
resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=}
dev: false
/webidl-conversions/4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
dev: true
/whatwg-url/5.0.0:
resolution: {integrity: sha1-lmRU6HZUYuN2RNNib2dCzotwll0=}
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
dev: false
/whatwg-url/7.1.0:
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
dependencies:

111
src/components/Connection.tsx

@ -2,7 +2,6 @@ import React from 'react';
import { AnimatePresence } from 'framer-motion';
import { Card } from '@app/components/generic/Card';
import { BLE } from '@components/connection/BLE';
import { HTTP } from '@components/connection/HTTP';
import { Serial } from '@components/connection/Serial';
@ -52,74 +51,72 @@ export const Connection = (): JSX.Element => {
<AnimatePresence>
{appState.connectionModalOpen && (
<Modal
className="w-full max-w-3xl"
title="Connect to a device"
onClose={(): void => {
dispatch(closeConnectionModal());
}}
>
<Card className="relative">
<div className="flex max-w-3xl flex-grow gap-4 p-2">
<div className="w-1/2">
<div className="space-y-2">
<Select
label="Connection Method"
optionsEnum={connType}
value={appState.connType}
onChange={(e): void => {
dispatch(setConnType(parseInt(e.target.value)));
}}
disabled={
<div className="flex max-w-3xl flex-grow gap-4">
<div className="w-1/2">
<div className="space-y-2">
<Select
label="Connection Method"
optionsEnum={connType}
value={appState.connType}
onChange={(e): void => {
dispatch(setConnType(parseInt(e.target.value)));
}}
disabled={
state.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
{appState.connType === connType.HTTP && (
<HTTP
connecting={
state.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
{appState.connType === connType.HTTP && (
<HTTP
connecting={
state.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
)}
{appState.connType === connType.BLE && (
<BLE
connecting={
state.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
)}
{appState.connType === connType.SERIAL && (
<Serial
connecting={
state.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
)}
</div>
)}
{appState.connType === connType.BLE && (
<BLE
connecting={
state.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
)}
{appState.connType === connType.SERIAL && (
<Serial
connecting={
state.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
)}
</div>
<div className="w-1/2">
<div className="h-96 overflow-y-auto rounded-md bg-gray-200 p-2 dark:bg-secondaryDark dark:text-gray-400">
{state.logs
.filter((log) => {
return ![
Types.Emitter.handleFromRadio,
Types.Emitter.handleMeshPacket,
Types.Emitter.sendPacket,
].includes(log.emitter);
})
.map((log, index) => (
<div key={index} className="flex">
<div className="truncate font-mono text-sm">
{log.message}
</div>
</div>
<div className="w-1/2">
<div className="h-96 overflow-y-auto rounded-md bg-gray-200 dark:bg-secondaryDark dark:text-gray-400">
{state.logs
.filter((log) => {
return ![
Types.Emitter.handleFromRadio,
Types.Emitter.handleMeshPacket,
Types.Emitter.sendPacket,
].includes(log.emitter);
})
.map((log, index) => (
<div key={index} className="flex">
<div className="truncate font-mono text-sm">
{log.message}
</div>
))}
</div>
</div>
))}
</div>
</div>
</Card>
</div>
</Modal>
)}
</AnimatePresence>

25
src/components/generic/Modal.tsx

@ -1,21 +1,23 @@
import type React from 'react';
import { m } from 'framer-motion';
import { FiX } from 'react-icons/fi';
import { useAppSelector } from '@hooks/useAppSelector';
type DefaultDivProps = JSX.IntrinsicElements['div'];
import { IconButton } from './button/IconButton';
import { Card } from './Card';
export interface ModalProps extends DefaultDivProps {
export interface ModalProps {
title: string;
onClose: () => void;
children: React.ReactNode;
}
export const Modal = ({
title,
onClose,
children,
className,
...props
}: ModalProps): JSX.Element => {
const darkMode = useAppSelector((state) => state.app.darkMode);
@ -32,8 +34,19 @@ export const Modal = ({
>
&#8203;
</span>
<div className={`inline-block align-middle ${className}`} {...props}>
{children}
<div className="inline-block w-full max-w-3xl align-middle">
<Card
className="relative flex-col gap-4
"
>
<div className="flex justify-between">
<div className="text-2xl font-medium dark:text-white">
{title}
</div>
<IconButton icon={<FiX />} onClick={onClose} />
</div>
{children}
</Card>
</div>
</m.div>
</m.div>

19
src/components/menu/BottomNav.tsx

@ -10,6 +10,7 @@ import {
FiWifi,
FiX,
} from 'react-icons/fi';
import { MdUpgrade } from 'react-icons/md';
import {
RiArrowDownLine,
RiArrowUpDownLine,
@ -124,19 +125,29 @@ export const BottomNav = (): JSX.Element => {
<div className="flex">
<VersionInfo
visible={showVersionInfo}
onclose={(): void => {
onClose={(): void => {
setShowVersionInfo(false);
}}
/>
<Tooltip content={`Current Commit`}>
<Tooltip
content={
appState.updateAvaliable ? 'Update Avaliable' : 'Current Commit'
}
>
<div
onClick={(): void => {
setShowVersionInfo(true);
}}
className="group flex cursor-pointer select-none border-l border-gray-300 p-1 hover:bg-gray-200 dark:border-gray-600 dark:text-white dark:hover:bg-primaryDark"
className={`group flex cursor-pointer select-none border-l border-gray-300 p-1 hover:bg-gray-200 dark:border-gray-600 dark:text-white dark:hover:bg-primaryDark ${
appState.updateAvaliable ? 'animate-pulse' : ''
}`}
>
<FiGitBranch className="mr-1 p-0.5 group-active:scale-90" />
{appState.updateAvaliable ? (
<MdUpgrade className="mr-1 p-0.5 group-active:scale-90" />
) : (
<FiGitBranch className="mr-1 p-0.5 group-active:scale-90" />
)}
<p className="text-xs opacity-60">{process.env.COMMIT_HASH}</p>
</div>
</Tooltip>

100
src/components/modals/VersionInfo.tsx

@ -1,59 +1,91 @@
import React from 'react';
import { AnimatePresence } from 'framer-motion';
import useSWR from 'swr';
import { setUpdateAvaliable } from '@app/core/slices/appSlice.js';
import { fetcher } from '@app/core/utils/fetcher.js';
import { useAppDispatch } from '@app/hooks/useAppDispatch.js';
import { Modal } from '@components/generic/Modal';
import { Card } from '../generic/Card';
export interface Commit {
sha: string;
node_id: string;
commit: {
author: string;
committer: string;
message: string;
tree: {
sha: string;
url: string;
};
url: string;
comment_count: number;
};
url: string;
html_url: string;
comments_url: string;
}
export interface VersionInfoProps {
visible: boolean;
onclose: () => void;
onClose: () => void;
}
export const VersionInfo = ({
visible,
onclose,
onClose,
}: VersionInfoProps): JSX.Element => {
// const { data } = useSWR<CommitHistory>(
// `query {
// repository(owner: "meshtastic", name: "meshtastic-web") {
// ref(qualifiedName: "master") {
// name
// target {
// ... on Commit {
// history(first: 4) {
// edges {
// node {
// abbreviatedOid
// message
// author {
// avatarUrl
// name
// }
// }
// }
// }
// }
// }
// }
// }
// }`,
// fetcher,
// );
const dispatch = useAppDispatch();
const { data } = useSWR<Commit[]>(
'https://api.github.com/repos/meshtastic/meshtastic-web/commits?per_page=10',
fetcher,
{
revalidateOnFocus: false,
},
);
React.useEffect(() => {
if (data) {
const index = data.findIndex(
(commit) => commit.sha.substring(0, 7) === process.env.COMMIT_HASH,
);
console.log(index);
if (index === -1 || index > 0) {
dispatch(setUpdateAvaliable(true));
}
}
}, [data]);
return (
<AnimatePresence>
{visible && (
<Modal
title="Version Info"
onClose={(): void => {
onclose();
onClose();
}}
>
<Card className="relative">
<div className="w-full max-w-3xl p-10">Version Info</div>
{/* {data?.sha} */}
</Card>
<div className="flex flex-col gap-1 dark:text-white">
{data &&
data.map((commit) => (
<div
key={commit.sha}
className={`flex gap-2 rounded-md p-1 ${
commit.sha.substring(0, 7) === process.env.COMMIT_HASH
? 'bg-primary'
: 'dark:bg-secondaryDark'
}`}
>
<div className="my-auto font-mono text-sm">
{commit.sha.substring(0, 7)}
</div>
<div className="truncate">{commit.commit.message}</div>
</div>
))}
</div>
</Modal>
)}
</AnimatePresence>

6
src/core/slices/appSlice.ts

@ -22,6 +22,7 @@ interface AppState {
HTTP: Types.HTTPConnectionParameters;
SERIAL: Types.SerialConnectionParameters;
};
updateAvaliable: boolean;
}
const initialState: AppState = {
@ -41,6 +42,7 @@ const initialState: AppState = {
},
SERIAL: {},
},
updateAvaliable: false,
};
export const appSlice = createSlice({
@ -77,6 +79,9 @@ export const appSlice = createSlice({
state.connectionParams[connType[action.payload.type]] =
action.payload.params;
},
setUpdateAvaliable(state, action: PayloadAction<boolean>) {
state.updateAvaliable = action.payload;
},
},
});
@ -88,6 +93,7 @@ export const {
setCurrentPage,
setConnType,
setConnectionParams,
setUpdateAvaliable,
} = appSlice.actions;
export default appSlice.reducer;

6
src/core/utils/fetcher.ts

@ -1,7 +1,7 @@
export default async function fetcher<JSON>(
export const fetcher = async <JSON>(
input: RequestInfo,
init?: RequestInit,
): Promise<JSON> {
): Promise<JSON> => {
const res = await fetch(input, init);
return res.json() as Promise<JSON>;
}
};

9
src/core/utils/gqlFetcher.ts

@ -1,9 +0,0 @@
import { request } from 'graphql-request';
export default async function gqlFetcher<JSON>(
url: string,
query?: string,
): Promise<JSON> {
// const res = await fetch(input, init);
return await request<JSON>(url, query);
}

2
src/pages/Extensions/FileBrowser.tsx

@ -4,7 +4,7 @@ import { AnimatePresence, m } from 'framer-motion';
import useSWR from 'swr';
import { Card } from '@app/components/generic/Card';
import fetcher from '@core/utils/fetcher';
import { fetcher } from '@core/utils/fetcher';
import { useAppSelector } from '@hooks/useAppSelector';
export interface File {

18
todo.txt

@ -1,18 +0,0 @@
Add desctiptions to form elements (below on mobile, to the right on desktop)
add default value to undefined protobufs, (omit if default to keep them small (only for ota packets))
add input validation min,max etc
maybe make channel editor acordion?
add url routing for settings tabs
add loading blur to card (prop)
form still considered dirty after save
form prefix should be located in the input (absolute?)
form suffix should focus input
reset store on new connection
redux actions seem to be dispatched twice
add qr generator in channel editor
no save button for channel config (bw,sf,cr,tx etc)
should reset store beased on disconnect state, as it can be set by the lirary, not just user interactions
way to set time for nodes, ntp?
meshtastic.js
- fix entering device-reconnecting state and not re-connecting despite packets being received

39
types/github.ts

@ -1,39 +0,0 @@
export interface CommitHistory {
data: Data;
}
export interface Data {
repository: Repository;
}
export interface Repository {
ref: Ref;
}
export interface Ref {
name: string;
target: Target;
}
export interface Target {
history: History;
}
export interface History {
edges: Edge[];
}
export interface Edge {
node: Node;
}
export interface Node {
abbreviatedOid: string;
message: string;
author: Author;
}
export interface Author {
avatarUrl: string;
name: string;
}
Loading…
Cancel
Save