Initial commit
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
|
||||
+15
-19
@@ -1,25 +1,21 @@
|
||||
import React from 'react';
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
import React from "react";
|
||||
import logo from "./logo.svg";
|
||||
//import "./App.css";
|
||||
import { Grommet, Box } from "grommet";
|
||||
|
||||
import client from "./apolloclient";
|
||||
import { ApolloProvider } from "@apollo/react-hooks";
|
||||
import Devices from "./devices";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.js</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
<ApolloProvider client={client}>
|
||||
<Grommet full>
|
||||
<Box fill>
|
||||
<Devices />
|
||||
</Box>
|
||||
</Grommet>
|
||||
</ApolloProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
import { ApolloClient } from "apollo-client";
|
||||
import {
|
||||
InMemoryCache,
|
||||
IntrospectionFragmentMatcher,
|
||||
} from "apollo-cache-inmemory";
|
||||
import { HttpLink } from "apollo-link-http";
|
||||
import { onError } from "apollo-link-error";
|
||||
import { ApolloLink, split } from "apollo-link";
|
||||
import { WebSocketLink } from "apollo-link-ws";
|
||||
import { getMainDefinition } from "apollo-utilities";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
import introspectionQueryResultData from "./fragmentTypes.json";
|
||||
|
||||
import {
|
||||
propertyVectorFragment,
|
||||
vectorFragment,
|
||||
deviceInfoFragment,
|
||||
} from "./graphql/fragment";
|
||||
import { isConnectedQuery } from "./graphql/query";
|
||||
|
||||
// Create an http link:
|
||||
const httpLink = new HttpLink({
|
||||
uri: "/graphql",
|
||||
});
|
||||
|
||||
// Create a WebSocket link:
|
||||
const wsLink = new WebSocketLink({
|
||||
uri: `ws://${window.location.host}/graphql`,
|
||||
options: {
|
||||
reconnect: true,
|
||||
},
|
||||
});
|
||||
|
||||
const link = split(
|
||||
// split based on operation type
|
||||
({ query }) => {
|
||||
const definition = getMainDefinition(query);
|
||||
return (
|
||||
definition.kind === "OperationDefinition" &&
|
||||
definition.operation === "subscription"
|
||||
);
|
||||
},
|
||||
wsLink,
|
||||
httpLink
|
||||
);
|
||||
|
||||
const client = new ApolloClient({
|
||||
link: ApolloLink.from([
|
||||
onError(({ graphQLErrors, networkError }) => {
|
||||
if (graphQLErrors)
|
||||
graphQLErrors.forEach(({ message, locations, path }) =>
|
||||
console.log(
|
||||
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
|
||||
)
|
||||
);
|
||||
if (networkError) console.log(`[Network error]: ${networkError}`);
|
||||
}),
|
||||
link,
|
||||
]),
|
||||
cache: new InMemoryCache({
|
||||
fragmentMatcher: new IntrospectionFragmentMatcher({
|
||||
introspectionQueryResultData,
|
||||
}),
|
||||
cacheRedirects: {
|
||||
Query: {
|
||||
device: (_, args, { getCacheKey }) =>
|
||||
getCacheKey({ __typename: "Device", id: args.id }),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const CONNECTED = gql`
|
||||
subscription connected {
|
||||
connected
|
||||
}
|
||||
`;
|
||||
|
||||
client
|
||||
.subscribe({
|
||||
query: CONNECTED,
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
client.writeQuery({
|
||||
query: isConnectedQuery,
|
||||
data: { connected: data.connected },
|
||||
});
|
||||
console.log(data);
|
||||
});
|
||||
|
||||
const DISCONNECTED = gql`
|
||||
subscription disconnected {
|
||||
disconnected
|
||||
}
|
||||
`;
|
||||
|
||||
client
|
||||
.subscribe({
|
||||
query: DISCONNECTED,
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
console.log(data);
|
||||
});
|
||||
|
||||
const NEW_DEVICE = gql`
|
||||
subscription newDevice {
|
||||
newDevice {
|
||||
id
|
||||
...DeviceInfo
|
||||
}
|
||||
}
|
||||
|
||||
${deviceInfoFragment}
|
||||
`;
|
||||
|
||||
const GETDEVICES = gql`
|
||||
query devices {
|
||||
devices {
|
||||
id
|
||||
...DeviceInfo
|
||||
properties {
|
||||
id
|
||||
name
|
||||
...PropertyVector
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${deviceInfoFragment}
|
||||
${propertyVectorFragment}
|
||||
`;
|
||||
|
||||
const GETDEVICE = gql`
|
||||
query device($id: String!) {
|
||||
device(id: $id) {
|
||||
id
|
||||
...DeviceInfo
|
||||
properties {
|
||||
id
|
||||
name
|
||||
...PropertyVector
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${deviceInfoFragment}
|
||||
${propertyVectorFragment}
|
||||
`;
|
||||
|
||||
client
|
||||
.subscribe({
|
||||
query: NEW_DEVICE,
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
const res = client.readQuery({ query: GETDEVICES });
|
||||
data.newDevice.properties = [];
|
||||
client.writeQuery({
|
||||
query: GETDEVICES,
|
||||
data: { devices: [...res.devices, data.newDevice] },
|
||||
});
|
||||
});
|
||||
|
||||
const NEW_PROPERTY = gql`
|
||||
subscription newProperty {
|
||||
newProperty {
|
||||
id
|
||||
name
|
||||
device
|
||||
...PropertyVector
|
||||
}
|
||||
}
|
||||
|
||||
${propertyVectorFragment}
|
||||
`;
|
||||
|
||||
client
|
||||
.subscribe({
|
||||
query: NEW_PROPERTY,
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
const res = client.readQuery({
|
||||
query: GETDEVICE,
|
||||
variables: { id: data.newProperty.device },
|
||||
});
|
||||
client.writeQuery({
|
||||
query: GETDEVICE,
|
||||
variables: { id: res.device.id },
|
||||
data: {
|
||||
device: {
|
||||
...res.device,
|
||||
drivers:
|
||||
data.newProperty.name === "DRIVER_INFO"
|
||||
? data.newProperty.vector.values[3].text
|
||||
: res.device.drivers,
|
||||
connected:
|
||||
data.newProperty.name === "CONNECTION"
|
||||
? data.newProperty.vector.values[0].switch
|
||||
: res.device.connected,
|
||||
properties: [...res.device.properties, data.newProperty],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const NEW_VALUE = gql`
|
||||
subscription newValue {
|
||||
newValue {
|
||||
id
|
||||
device
|
||||
...VectorData
|
||||
}
|
||||
}
|
||||
|
||||
${vectorFragment}
|
||||
`;
|
||||
|
||||
client
|
||||
.subscribe({
|
||||
query: NEW_VALUE,
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
if (data.newValue.name === "CONNECTION") {
|
||||
const res = client.readQuery({
|
||||
query: GETDEVICE,
|
||||
variables: { id: data.newValue.device },
|
||||
});
|
||||
client.writeQuery({
|
||||
query: GETDEVICE,
|
||||
variables: { id: res.device.id },
|
||||
data: {
|
||||
device: {
|
||||
...res.device,
|
||||
connected: data.newValue.values[0].switch,
|
||||
},
|
||||
},
|
||||
});
|
||||
console.log(res);
|
||||
}
|
||||
});
|
||||
|
||||
export default client;
|
||||
@@ -0,0 +1,64 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useMutation } from "@apollo/react-hooks";
|
||||
import { driversToInterfaces } from "../../utils/indi";
|
||||
import Property from "./property";
|
||||
import gql from "graphql-tag";
|
||||
import { Box, Button, Tabs, Tab } from "grommet";
|
||||
|
||||
const CONNECT_DEVICE = gql`
|
||||
mutation connectDevice($id: String!) {
|
||||
connectDevice(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Device = ({ device }) => {
|
||||
const [connect] = useMutation(CONNECT_DEVICE, {
|
||||
variables: { id: device.id },
|
||||
});
|
||||
const [groups, setGroups] = useState([]);
|
||||
const [selectedTabs, setSelectedTabs] = useState({});
|
||||
const [currentTab, setCurrentTab] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const groups = new Set();
|
||||
device.properties.forEach((v) => {
|
||||
groups.add(v.vector.group);
|
||||
});
|
||||
setGroups(Array.from(groups));
|
||||
setCurrentTab(selectedTabs[device.id] || 0);
|
||||
}, [device]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{!device.connected && (
|
||||
<Button
|
||||
label="Connect"
|
||||
onClick={() => {
|
||||
connect();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Tabs justify="start" activeIndex={currentTab} onActive={(nextTab) => {
|
||||
setCurrentTab(nextTab);
|
||||
const newValue = { ...selectedTabs };
|
||||
newValue[device.id] = nextTab;
|
||||
setSelectedTabs(newValue)
|
||||
}}>
|
||||
{groups.map((g) => (
|
||||
<Tab title={g} key={g}>
|
||||
<Box pad="small">
|
||||
{device.properties &&
|
||||
device.properties
|
||||
.filter((p) => p.vector.group === g)
|
||||
.map((p) => <Property property={p} key={p.id} />)}
|
||||
</Box>
|
||||
</Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Device;
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
|
||||
import { stateToEmoji } from "../../utils/indi";
|
||||
|
||||
const LightVector = ({ vector }) => {
|
||||
return (
|
||||
<div>
|
||||
{vector.values.map(v => (
|
||||
<div key={v.name}>{stateToEmoji(v.switch)} {v.label || v.name}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LightVector;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { Box, TextInput, Button } from "grommet";
|
||||
|
||||
const NumberVector = ({ vector }) => {
|
||||
return (
|
||||
<Box gap="xsmall">
|
||||
{vector.values.map((v) => (
|
||||
<Box key={v.name} direction="row" align="center">
|
||||
<Box width="small">{v.label || v.name} ({v.min},{v.max},{v.step})</Box>
|
||||
<Box width="small">{v.formated || v.number}</Box>
|
||||
{vector.permission !== 0 && (
|
||||
<Box direction="row" align="center" gap="xsmall">
|
||||
<TextInput></TextInput>
|
||||
<Button primary label="Set"></Button>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default NumberVector;
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { Box } from "grommet";
|
||||
|
||||
import { stateToEmoji } from "../../utils/indi";
|
||||
import LightVector from "./light";
|
||||
import NumberVector from "./number";
|
||||
import SwitchVector from "./switch";
|
||||
import TextVector from "./text";
|
||||
|
||||
const Property = ({ property }) => {
|
||||
return (
|
||||
<Box direction="row" margin={{bottom: "medium"}} align="center">
|
||||
<Box width="medium">
|
||||
<div>
|
||||
{stateToEmoji(property.vector.state)}{" "}
|
||||
<strong>{property.vector.label || property.name}</strong>
|
||||
</div>
|
||||
</Box>
|
||||
{property.vector.__typename === "NumberVector" && (
|
||||
<NumberVector vector={property.vector} />
|
||||
)}
|
||||
{property.vector.__typename === "SwitchVector" && (
|
||||
<SwitchVector vector={property.vector} />
|
||||
)}
|
||||
{property.vector.__typename === "TextVector" && (
|
||||
<TextVector vector={property.vector} />
|
||||
)}
|
||||
{property.vector.__typename === "LightVector" && (
|
||||
<LightVector vector={property.vector} />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Property;
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from "react";
|
||||
import { Button, Box } from "grommet";
|
||||
import gql from "graphql-tag";
|
||||
import { useMutation } from "@apollo/react-hooks";
|
||||
|
||||
const SEND = gql`
|
||||
mutation sendSwitch($input: SwitchInput) {
|
||||
sendSwitch(input: $input)
|
||||
}
|
||||
`;
|
||||
|
||||
const SwitchVector = ({ vector }) => {
|
||||
const [sendSwitch] = useMutation(SEND);
|
||||
|
||||
return (
|
||||
<Box direction="row" gap="xsmall">
|
||||
{vector.values.map((v) => (
|
||||
<Button
|
||||
primary={v.switch}
|
||||
key={v.name}
|
||||
label={v.label}
|
||||
onClick={() => {
|
||||
const newValues = vector.values.map((val) => {
|
||||
if (v.name === val.name) {
|
||||
return { name: val.name, value: true };
|
||||
} else {
|
||||
return {
|
||||
name: val.name,
|
||||
value: vector.rule === 0 ? false : val.switch,
|
||||
};
|
||||
}
|
||||
});
|
||||
const input = {
|
||||
device: vector.device,
|
||||
property: vector.name,
|
||||
values: newValues,
|
||||
};
|
||||
console.log(vector.rule);
|
||||
console.log(input);
|
||||
|
||||
sendSwitch({
|
||||
variables: { input },
|
||||
}).then((data) => {
|
||||
console.log(data);
|
||||
});
|
||||
}}
|
||||
></Button>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default SwitchVector;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { Box, Button, TextInput } from "grommet";
|
||||
|
||||
const TextVector = ({ vector }) => {
|
||||
return (
|
||||
<Box gap="xsmall">
|
||||
{vector.values.map((v) => (
|
||||
<Box key={v.name} direction="row" align="center">
|
||||
<Box width="small">{v.label || v.name}</Box>
|
||||
<Box width="small">{v.text}</Box>
|
||||
{vector.permission !== 0 && (
|
||||
<Box direction="row" align="center" gap="xsmall">
|
||||
<TextInput value={v.text}></TextInput>
|
||||
<Button primary label="Set"></Button>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default TextVector;
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { useQuery, useSubscription, useMutation } from "@apollo/react-hooks";
|
||||
import gql from "graphql-tag";
|
||||
import { deviceInfoFragment, propertyVectorFragment } from "./graphql/fragment";
|
||||
import Device from "./components/indi/device";
|
||||
import { Box, Tabs, Tab, Button, Header, Heading } from "grommet";
|
||||
import { isConnectedQuery } from "./graphql/query";
|
||||
|
||||
const GETDEVICES = gql`
|
||||
query devices {
|
||||
devices {
|
||||
id
|
||||
...DeviceInfo
|
||||
properties {
|
||||
id
|
||||
name
|
||||
...PropertyVector
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${deviceInfoFragment}
|
||||
${propertyVectorFragment}
|
||||
`;
|
||||
|
||||
const CONNECT = gql`
|
||||
mutation connect {
|
||||
connect
|
||||
}
|
||||
`;
|
||||
|
||||
const DISCONNECT = gql`
|
||||
mutation disconnect {
|
||||
disconnect
|
||||
}
|
||||
`;
|
||||
|
||||
const useIndiState = () => {
|
||||
const { data } = useQuery(isConnectedQuery);
|
||||
const [connected, setConnected] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
data && setConnected(data.connected);
|
||||
}, [data]);
|
||||
|
||||
return connected;
|
||||
};
|
||||
|
||||
const Devices = () => {
|
||||
const connected = useIndiState();
|
||||
const { data } = useQuery(GETDEVICES);
|
||||
const [connect] = useMutation(CONNECT);
|
||||
const [disconnect] = useMutation(DISCONNECT);
|
||||
|
||||
if (!data || !data.devices) {
|
||||
return <div>No data</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box fill>
|
||||
<Header background="brand" pad="small">
|
||||
<Box>
|
||||
<Heading margin="xsmall">ASTRAW</Heading>
|
||||
</Box>
|
||||
<Box direction="row" gap="medium">
|
||||
{!connected ? (
|
||||
<Button
|
||||
label="Connect"
|
||||
onClick={() => {
|
||||
connect();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
label="Disconnect"
|
||||
onClick={() => {
|
||||
disconnect();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Header>
|
||||
|
||||
<Box overflow="auto">
|
||||
{data ? (
|
||||
<Tabs justify="start" key="d.id">
|
||||
{data && data.devices ? (
|
||||
data.devices.map((d) => (
|
||||
<Tab title={d.name} key={d.id}>
|
||||
<Device device={d} />
|
||||
</Tab>
|
||||
))
|
||||
) : (
|
||||
<div>no devices</div>
|
||||
)}
|
||||
</Tabs>
|
||||
) : null}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Devices;
|
||||
@@ -0,0 +1 @@
|
||||
{"__schema":{"types":[{"kind":"INTERFACE","name":"Vector","possibleTypes":[{"name":"NumberVector"},{"name":"SwitchVector"},{"name":"TextVector"},{"name":"LightVector"}]}]}}
|
||||
@@ -0,0 +1,66 @@
|
||||
import gql from "graphql-tag";
|
||||
|
||||
const deviceInfoFragment = gql`
|
||||
fragment DeviceInfo on Device {
|
||||
name
|
||||
connected
|
||||
drivers
|
||||
}
|
||||
`;
|
||||
|
||||
const vectorFragment = gql`
|
||||
fragment VectorData on Vector {
|
||||
name
|
||||
label
|
||||
group
|
||||
device
|
||||
state
|
||||
permission
|
||||
... on NumberVector {
|
||||
values {
|
||||
name
|
||||
label
|
||||
number:value
|
||||
min
|
||||
max
|
||||
step
|
||||
formated
|
||||
}
|
||||
}
|
||||
... on SwitchVector {
|
||||
rule
|
||||
values {
|
||||
name
|
||||
label
|
||||
switch:value
|
||||
}
|
||||
}
|
||||
... on TextVector {
|
||||
values {
|
||||
name
|
||||
label
|
||||
text:value
|
||||
}
|
||||
}
|
||||
... on LightVector {
|
||||
values {
|
||||
name
|
||||
label
|
||||
light:value
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const propertyVectorFragment = gql`
|
||||
fragment PropertyVector on Property {
|
||||
vector {
|
||||
id
|
||||
...VectorData
|
||||
}
|
||||
}
|
||||
|
||||
${vectorFragment}
|
||||
`;
|
||||
|
||||
export { deviceInfoFragment, vectorFragment, propertyVectorFragment };
|
||||
@@ -0,0 +1,9 @@
|
||||
import gql from "graphql-tag";
|
||||
|
||||
const isConnectedQuery = gql`
|
||||
query connected {
|
||||
connected
|
||||
}
|
||||
`;
|
||||
|
||||
export { isConnectedQuery }
|
||||
+6
-6
@@ -1,14 +1,14 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
const proxy = require('http-proxy-middleware');
|
||||
|
||||
module.exports = function(app) {
|
||||
app.use(proxy('/graphql', { target: 'http://localhost:4000', ws: true }));
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
const INTERFACES = {
|
||||
GENERAL: 0,
|
||||
TELESCOPE: 1 << 0,
|
||||
CCD: 1 << 1,
|
||||
GUIDER: 1 << 2,
|
||||
FOCUSER: 1 << 3,
|
||||
};
|
||||
/*
|
||||
FILTER
|
||||
DOME
|
||||
GPS
|
||||
WEATHER
|
||||
AO
|
||||
DUSTCAP
|
||||
LIGHTBOX
|
||||
DETECTOR
|
||||
ROTATOR
|
||||
SPECTROGRAPH
|
||||
CORRELATOR
|
||||
AUX
|
||||
}
|
||||
*/
|
||||
export function driversToInterfaces(drivers) {
|
||||
const ret = [];
|
||||
for (const iface in INTERFACES) {
|
||||
if (INTERFACES[iface] & drivers) {
|
||||
ret.push(iface);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function stateToEmoji(state) {
|
||||
switch (state) {
|
||||
case 0:
|
||||
return "⚪";
|
||||
case 1:
|
||||
return "🟢";
|
||||
case 2:
|
||||
return "🟡";
|
||||
case 3:
|
||||
return "🔴";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user