Browse Source

refactored projects, started instruments.

Tomi Cvetic 5 years ago
parent
commit
cfe0bce706

+ 41 - 19
backend/database/generated/prisma-client/index.d.ts

@@ -1132,6 +1132,8 @@ export type InstrumentInstanceOrderByInput =
   | "id_DESC"
   | "identifier_ASC"
   | "identifier_DESC"
+  | "interface_ASC"
+  | "interface_DESC"
   | "label_ASC"
   | "label_DESC"
   | "location_ASC"
@@ -1974,6 +1976,20 @@ export interface InstrumentInstanceWhereInput {
   identifier_not_starts_with?: String;
   identifier_ends_with?: String;
   identifier_not_ends_with?: String;
+  interface?: String;
+  interface_not?: String;
+  interface_in?: String[] | String;
+  interface_not_in?: String[] | String;
+  interface_lt?: String;
+  interface_lte?: String;
+  interface_gt?: String;
+  interface_gte?: String;
+  interface_contains?: String;
+  interface_not_contains?: String;
+  interface_starts_with?: String;
+  interface_not_starts_with?: String;
+  interface_ends_with?: String;
+  interface_not_ends_with?: String;
   label?: String;
   label_not?: String;
   label_in?: String[] | String;
@@ -2684,7 +2700,7 @@ export interface InstrumentInstanceCreateManyInput {
 export interface InstrumentInstanceCreateInput {
   instrument: InstrumentCreateOneInput;
   identifier: String;
-  interface?: InstrumentInstanceCreateinterfaceInput;
+  interface: String;
   label?: String;
   location?: String;
 }
@@ -2787,10 +2803,6 @@ export interface InstrumentCreateWithoutCommandsInput {
   subsystems?: InstrumentSubsystemCreateManyInput;
 }
 
-export interface InstrumentInstanceCreateinterfaceInput {
-  set?: String[] | String;
-}
-
 export interface CharacterizationUpdateInput {
   name?: String;
   projectVersion?: ProjectVersionUpdateOneRequiredInput;
@@ -3609,7 +3621,7 @@ export interface InstrumentInstanceUpdateWithWhereUniqueNestedInput {
 export interface InstrumentInstanceUpdateDataInput {
   instrument?: InstrumentUpdateOneRequiredInput;
   identifier?: String;
-  interface?: InstrumentInstanceUpdateinterfaceInput;
+  interface?: String;
   label?: String;
   location?: String;
 }
@@ -4126,10 +4138,6 @@ export interface InstrumentUpsertNestedInput {
   create: InstrumentCreateInput;
 }
 
-export interface InstrumentInstanceUpdateinterfaceInput {
-  set?: String[] | String;
-}
-
 export interface InstrumentInstanceUpsertWithWhereUniqueNestedInput {
   where: InstrumentInstanceWhereUniqueInput;
   update: InstrumentInstanceUpdateDataInput;
@@ -4165,6 +4173,20 @@ export interface InstrumentInstanceScalarWhereInput {
   identifier_not_starts_with?: String;
   identifier_ends_with?: String;
   identifier_not_ends_with?: String;
+  interface?: String;
+  interface_not?: String;
+  interface_in?: String[] | String;
+  interface_not_in?: String[] | String;
+  interface_lt?: String;
+  interface_lte?: String;
+  interface_gt?: String;
+  interface_gte?: String;
+  interface_contains?: String;
+  interface_not_contains?: String;
+  interface_starts_with?: String;
+  interface_not_starts_with?: String;
+  interface_ends_with?: String;
+  interface_not_ends_with?: String;
   label?: String;
   label_not?: String;
   label_in?: String[] | String;
@@ -4211,7 +4233,7 @@ export interface InstrumentInstanceUpdateManyWithWhereNestedInput {
 
 export interface InstrumentInstanceUpdateManyDataInput {
   identifier?: String;
-  interface?: InstrumentInstanceUpdateinterfaceInput;
+  interface?: String;
   label?: String;
   location?: String;
 }
@@ -4418,14 +4440,14 @@ export interface InstrumentCommandUpdateManyMutationInput {
 export interface InstrumentInstanceUpdateInput {
   instrument?: InstrumentUpdateOneRequiredInput;
   identifier?: String;
-  interface?: InstrumentInstanceUpdateinterfaceInput;
+  interface?: String;
   label?: String;
   location?: String;
 }
 
 export interface InstrumentInstanceUpdateManyMutationInput {
   identifier?: String;
-  interface?: InstrumentInstanceUpdateinterfaceInput;
+  interface?: String;
   label?: String;
   location?: String;
 }
@@ -5572,7 +5594,7 @@ export interface SetupHardwareSubscription
 export interface InstrumentInstance {
   id: ID_Output;
   identifier: String;
-  interface: String[];
+  interface: String;
   label?: String;
   location?: String;
 }
@@ -5583,7 +5605,7 @@ export interface InstrumentInstancePromise
   id: () => Promise<ID_Output>;
   instrument: <T = InstrumentPromise>() => T;
   identifier: () => Promise<String>;
-  interface: () => Promise<String[]>;
+  interface: () => Promise<String>;
   label: () => Promise<String>;
   location: () => Promise<String>;
 }
@@ -5594,7 +5616,7 @@ export interface InstrumentInstanceSubscription
   id: () => Promise<AsyncIterator<ID_Output>>;
   instrument: <T = InstrumentSubscription>() => T;
   identifier: () => Promise<AsyncIterator<String>>;
-  interface: () => Promise<AsyncIterator<String[]>>;
+  interface: () => Promise<AsyncIterator<String>>;
   label: () => Promise<AsyncIterator<String>>;
   location: () => Promise<AsyncIterator<String>>;
 }
@@ -7405,7 +7427,7 @@ export interface InstrumentInstanceSubscriptionPayloadSubscription
 export interface InstrumentInstancePreviousValues {
   id: ID_Output;
   identifier: String;
-  interface: String[];
+  interface: String;
   label?: String;
   location?: String;
 }
@@ -7415,7 +7437,7 @@ export interface InstrumentInstancePreviousValuesPromise
     Fragmentable {
   id: () => Promise<ID_Output>;
   identifier: () => Promise<String>;
-  interface: () => Promise<String[]>;
+  interface: () => Promise<String>;
   label: () => Promise<String>;
   location: () => Promise<String>;
 }
@@ -7425,7 +7447,7 @@ export interface InstrumentInstancePreviousValuesSubscription
     Fragmentable {
   id: () => Promise<AsyncIterator<ID_Output>>;
   identifier: () => Promise<AsyncIterator<String>>;
-  interface: () => Promise<AsyncIterator<String[]>>;
+  interface: () => Promise<AsyncIterator<String>>;
   label: () => Promise<AsyncIterator<String>>;
   location: () => Promise<AsyncIterator<String>>;
 }

+ 37 - 15
backend/database/generated/prisma-client/prisma-schema.js

@@ -1538,7 +1538,7 @@ type InstrumentInstance {
   id: ID!
   instrument: Instrument!
   identifier: String!
-  interface: [String!]!
+  interface: String!
   label: String
   location: String
 }
@@ -1552,15 +1552,11 @@ type InstrumentInstanceConnection {
 input InstrumentInstanceCreateInput {
   instrument: InstrumentCreateOneInput!
   identifier: String!
-  interface: InstrumentInstanceCreateinterfaceInput
+  interface: String!
   label: String
   location: String
 }
 
-input InstrumentInstanceCreateinterfaceInput {
-  set: [String!]
-}
-
 input InstrumentInstanceCreateManyInput {
   create: [InstrumentInstanceCreateInput!]
   connect: [InstrumentInstanceWhereUniqueInput!]
@@ -1576,6 +1572,8 @@ enum InstrumentInstanceOrderByInput {
   id_DESC
   identifier_ASC
   identifier_DESC
+  interface_ASC
+  interface_DESC
   label_ASC
   label_DESC
   location_ASC
@@ -1589,7 +1587,7 @@ enum InstrumentInstanceOrderByInput {
 type InstrumentInstancePreviousValues {
   id: ID!
   identifier: String!
-  interface: [String!]!
+  interface: String!
   label: String
   location: String
 }
@@ -1623,6 +1621,20 @@ input InstrumentInstanceScalarWhereInput {
   identifier_not_starts_with: String
   identifier_ends_with: String
   identifier_not_ends_with: String
+  interface: String
+  interface_not: String
+  interface_in: [String!]
+  interface_not_in: [String!]
+  interface_lt: String
+  interface_lte: String
+  interface_gt: String
+  interface_gte: String
+  interface_contains: String
+  interface_not_contains: String
+  interface_starts_with: String
+  interface_not_starts_with: String
+  interface_ends_with: String
+  interface_not_ends_with: String
   label: String
   label_not: String
   label_in: [String!]
@@ -1677,7 +1689,7 @@ input InstrumentInstanceSubscriptionWhereInput {
 input InstrumentInstanceUpdateDataInput {
   instrument: InstrumentUpdateOneRequiredInput
   identifier: String
-  interface: InstrumentInstanceUpdateinterfaceInput
+  interface: String
   label: String
   location: String
 }
@@ -1685,18 +1697,14 @@ input InstrumentInstanceUpdateDataInput {
 input InstrumentInstanceUpdateInput {
   instrument: InstrumentUpdateOneRequiredInput
   identifier: String
-  interface: InstrumentInstanceUpdateinterfaceInput
+  interface: String
   label: String
   location: String
 }
 
-input InstrumentInstanceUpdateinterfaceInput {
-  set: [String!]
-}
-
 input InstrumentInstanceUpdateManyDataInput {
   identifier: String
-  interface: InstrumentInstanceUpdateinterfaceInput
+  interface: String
   label: String
   location: String
 }
@@ -1715,7 +1723,7 @@ input InstrumentInstanceUpdateManyInput {
 
 input InstrumentInstanceUpdateManyMutationInput {
   identifier: String
-  interface: InstrumentInstanceUpdateinterfaceInput
+  interface: String
   label: String
   location: String
 }
@@ -1766,6 +1774,20 @@ input InstrumentInstanceWhereInput {
   identifier_not_starts_with: String
   identifier_ends_with: String
   identifier_not_ends_with: String
+  interface: String
+  interface_not: String
+  interface_in: [String!]
+  interface_not_in: [String!]
+  interface_lt: String
+  interface_lte: String
+  interface_gt: String
+  interface_gte: String
+  interface_contains: String
+  interface_not_contains: String
+  interface_starts_with: String
+  interface_not_starts_with: String
+  interface_ends_with: String
+  interface_not_ends_with: String
   label: String
   label_not: String
   label_in: [String!]

+ 90 - 16
backend/database/generated/prisma.graphql

@@ -1,5 +1,5 @@
 # source: http://localhost:4466
-# timestamp: Mon Apr 22 2019 07:21:06 GMT+0200 (Central European Summer Time)
+# timestamp: Wed Apr 24 2019 17:07:32 GMT+0200 (Central European Summer Time)
 
 type AggregateCharacterization {
   count: Int!
@@ -2803,7 +2803,7 @@ type InstrumentInstance implements Node {
   id: ID!
   instrument: Instrument!
   identifier: String!
-  interface: [String!]!
+  interface: String!
   label: String
   location: String
 }
@@ -2820,16 +2820,12 @@ type InstrumentInstanceConnection {
 
 input InstrumentInstanceCreateInput {
   identifier: String!
+  interface: String!
   label: String
   location: String
-  interface: InstrumentInstanceCreateinterfaceInput
   instrument: InstrumentCreateOneInput!
 }
 
-input InstrumentInstanceCreateinterfaceInput {
-  set: [String!]
-}
-
 input InstrumentInstanceCreateManyInput {
   create: [InstrumentInstanceCreateInput!]
   connect: [InstrumentInstanceWhereUniqueInput!]
@@ -2849,6 +2845,8 @@ enum InstrumentInstanceOrderByInput {
   id_DESC
   identifier_ASC
   identifier_DESC
+  interface_ASC
+  interface_DESC
   label_ASC
   label_DESC
   location_ASC
@@ -2862,7 +2860,7 @@ enum InstrumentInstanceOrderByInput {
 type InstrumentInstancePreviousValues {
   id: ID!
   identifier: String!
-  interface: [String!]!
+  interface: String!
   label: String
   location: String
 }
@@ -2956,6 +2954,46 @@ input InstrumentInstanceScalarWhereInput {
 
   """All values not ending with the given string."""
   identifier_not_ends_with: String
+  interface: String
+
+  """All values that are not equal to given value."""
+  interface_not: String
+
+  """All values that are contained in given list."""
+  interface_in: [String!]
+
+  """All values that are not contained in given list."""
+  interface_not_in: [String!]
+
+  """All values less than the given value."""
+  interface_lt: String
+
+  """All values less than or equal the given value."""
+  interface_lte: String
+
+  """All values greater than the given value."""
+  interface_gt: String
+
+  """All values greater than or equal the given value."""
+  interface_gte: String
+
+  """All values containing the given string."""
+  interface_contains: String
+
+  """All values not containing the given string."""
+  interface_not_contains: String
+
+  """All values starting with the given string."""
+  interface_starts_with: String
+
+  """All values not starting with the given string."""
+  interface_not_starts_with: String
+
+  """All values ending with the given string."""
+  interface_ends_with: String
+
+  """All values not ending with the given string."""
+  interface_not_ends_with: String
   label: String
 
   """All values that are not equal to given value."""
@@ -3077,29 +3115,25 @@ input InstrumentInstanceSubscriptionWhereInput {
 
 input InstrumentInstanceUpdateDataInput {
   identifier: String
+  interface: String
   label: String
   location: String
-  interface: InstrumentInstanceUpdateinterfaceInput
   instrument: InstrumentUpdateOneRequiredInput
 }
 
 input InstrumentInstanceUpdateInput {
   identifier: String
+  interface: String
   label: String
   location: String
-  interface: InstrumentInstanceUpdateinterfaceInput
   instrument: InstrumentUpdateOneRequiredInput
 }
 
-input InstrumentInstanceUpdateinterfaceInput {
-  set: [String!]
-}
-
 input InstrumentInstanceUpdateManyDataInput {
   identifier: String
+  interface: String
   label: String
   location: String
-  interface: InstrumentInstanceUpdateinterfaceInput
 }
 
 input InstrumentInstanceUpdateManyInput {
@@ -3116,9 +3150,9 @@ input InstrumentInstanceUpdateManyInput {
 
 input InstrumentInstanceUpdateManyMutationInput {
   identifier: String
+  interface: String
   label: String
   location: String
-  interface: InstrumentInstanceUpdateinterfaceInput
 }
 
 input InstrumentInstanceUpdateManyWithWhereNestedInput {
@@ -3226,6 +3260,46 @@ input InstrumentInstanceWhereInput {
 
   """All values not ending with the given string."""
   identifier_not_ends_with: String
+  interface: String
+
+  """All values that are not equal to given value."""
+  interface_not: String
+
+  """All values that are contained in given list."""
+  interface_in: [String!]
+
+  """All values that are not contained in given list."""
+  interface_not_in: [String!]
+
+  """All values less than the given value."""
+  interface_lt: String
+
+  """All values less than or equal the given value."""
+  interface_lte: String
+
+  """All values greater than the given value."""
+  interface_gt: String
+
+  """All values greater than or equal the given value."""
+  interface_gte: String
+
+  """All values containing the given string."""
+  interface_contains: String
+
+  """All values not containing the given string."""
+  interface_not_contains: String
+
+  """All values starting with the given string."""
+  interface_starts_with: String
+
+  """All values not starting with the given string."""
+  interface_not_starts_with: String
+
+  """All values ending with the given string."""
+  interface_ends_with: String
+
+  """All values not ending with the given string."""
+  interface_not_ends_with: String
   label: String
 
   """All values that are not equal to given value."""

+ 1 - 1
backend/datamodel.prisma

@@ -105,7 +105,7 @@ type InstrumentInstance {
   id: ID! @unique
   instrument: Instrument!
   identifier: String!
-  interface: [String]!
+  interface: String!
   label: String
   location: String
 }

+ 81 - 0
backend/package-lock.json

@@ -2969,6 +2969,11 @@
       "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
       "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
     },
+    "eyespect": {
+      "version": "0.1.10",
+      "resolved": "https://registry.npmjs.org/eyespect/-/eyespect-0.1.10.tgz",
+      "integrity": "sha1-ma6EajAvzK95Dj7LRPR7XzsXaqA="
+    },
     "fast-deep-equal": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
@@ -3112,6 +3117,14 @@
       "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
       "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
     },
+    "forEachAsync": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/forEachAsync/-/forEachAsync-2.2.1.tgz",
+      "integrity": "sha1-43I/AJA5EOHrSx2zrVG1xkoxn+w=",
+      "requires": {
+        "sequence": "2.x"
+      }
+    },
     "forever-agent": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@@ -6305,6 +6318,45 @@
         "pify": "^3.0.0"
       }
     },
+    "pathhash": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/pathhash/-/pathhash-1.0.0.tgz",
+      "integrity": "sha1-904ZwRtXJNLOOQa/G8lnIJ7Ytyc="
+    },
+    "pdf-extract": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/pdf-extract/-/pdf-extract-1.0.11.tgz",
+      "integrity": "sha1-p2F6mkC6h+ulaRPO5bja3+wtSIE=",
+      "requires": {
+        "async": "~0.1.22",
+        "eyespect": "~0.1.8",
+        "pathhash": "~1.0.0",
+        "rimraf": "~2.0.2",
+        "temp": "~0.8.3",
+        "walk": "~2.2.1"
+      },
+      "dependencies": {
+        "async": {
+          "version": "0.1.22",
+          "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz",
+          "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE="
+        },
+        "graceful-fs": {
+          "version": "1.1.14",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.1.14.tgz",
+          "integrity": "sha1-BweNtfY3f2Mh/Oqu30l94STclGU=",
+          "optional": true
+        },
+        "rimraf": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.0.3.tgz",
+          "integrity": "sha1-9QopZecUTpr9mYmC8V33BnMPVqk=",
+          "requires": {
+            "graceful-fs": "~1.1"
+          }
+        }
+      }
+    },
     "performance-now": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@@ -7566,6 +7618,11 @@
         "upper-case-first": "^1.1.2"
       }
     },
+    "sequence": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/sequence/-/sequence-2.2.1.tgz",
+      "integrity": "sha1-f1YXiV1ENRwKBH52RGdpBJChawM="
+    },
     "serialize-error": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-3.0.0.tgz",
@@ -8134,6 +8191,22 @@
         "xtend": "^4.0.0"
       }
     },
+    "temp": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz",
+      "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=",
+      "requires": {
+        "os-tmpdir": "^1.0.0",
+        "rimraf": "~2.2.6"
+      },
+      "dependencies": {
+        "rimraf": {
+          "version": "2.2.8",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
+          "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI="
+        }
+      }
+    },
     "term-size": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz",
@@ -8608,6 +8681,14 @@
         "extsprintf": "^1.2.0"
       }
     },
+    "walk": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/walk/-/walk-2.2.1.tgz",
+      "integrity": "sha1-WtofjknkfUt0Rdi+ei4eYxq0MBY=",
+      "requires": {
+        "forEachAsync": "~2.2"
+      }
+    },
     "wcwidth": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",

+ 1 - 0
backend/package.json

@@ -16,6 +16,7 @@
     "lodash": "^4.17.11",
     "mkdirp": "^0.5.1",
     "multer": "^1.4.1",
+    "pdf-extract": "^1.0.11",
     "prisma": "^1.30.1",
     "prisma-binding": "^2.3.10",
     "prisma-client-lib": "^1.30.1",

+ 29 - 0
backend/pdfScan.js

@@ -0,0 +1,29 @@
+const pdfExtract = require('pdf-extract')
+const path = '/home/tomi/Downloads/2230G-900-01A_Jun_2018_User.pdf'
+const options = {
+  type: 'text' // or 'ocr'
+}
+
+function getSCPICommands (pages) {
+  const scpiCommon = /(\*\w+\??)/g
+  const scpi = /((?:\*\w+|(?:\[?\w+\]?)(?=:\w+)\]?)(?:\[?:\w+\]?)*\??)(?:\s+(<?\w+>?)(?:[,|]\s*(<?\w+>?))*)?/g
+
+  pages.map((page, pageIndex) => {
+    const lines = page.split('\n')
+    lines.map((line, lineIndex) => {
+      const matches = line.match(scpi)
+      if (matches) console.log(pageIndex, lineIndex, matches)
+    })
+  })
+}
+
+const processor = pdfExtract(path, options, error => {
+  if (error) return error
+})
+processor.on('complete', data => {
+  console.log(data.text_pages)
+  getSCPICommands(data.text_pages)
+})
+processor.on('error', error => {
+  console.error(error)
+})

+ 2 - 0
backend/schema.graphql

@@ -10,6 +10,8 @@ type Query {
 type Mutation {
   createCharacterization(name: String!, projectVersion: ID!): Characterization!
   createUser(name: String!, email: String!, abbreviation: String!, password: String!): User!
+  createProject(name: String!, abbreviation: String!, description: String): Project!
+  createProjectVersion(name: String!, date: String!, project: ID!, changes: [String]): ProjectVersion!
   userLogin(email: String!, password: String!): User!
   userLogout: String!
 }

+ 24 - 1
backend/src/resolvers.js

@@ -56,8 +56,31 @@ const Mutation = {
         }
       }
     }, info)
-    console.log(characterization)
     return characterization
+  },
+  createProject: async (parent, args, context, info) => {
+    const project = await context.db.mutation.createProject({
+      data: { ...args }
+    }, info)
+    return project
+  },
+  createProjectVersion: async (parent, args, context, info) => {
+    console.log(args)
+    const projectVersion = await context.db.mutation.createProjectVersion({
+      data: {
+        ...args,
+        date: new Date(args.date).toISOString(),
+        project: {
+          connect: {
+            id: args.project
+          }
+        },
+        changes: {
+          set: args.changes
+        }
+      }
+    }, info)
+    return projectVersion
   }
 }
 

+ 3 - 3
frontend/components/CharacterizationForm.js

@@ -61,10 +61,10 @@ class CharacterziationForm extends React.Component {
                 {({ data, error, loading }) => {
                   if (error) return (<p>Error loading project: ${error.message}</p>)
                   if (loading) return (<p>Loading data...</p>)
-                  if (!data) return (<p>No project found.</p>)
+                  if (!data || !data.projects.length) return (<p>No project found.</p>)
 
                   const { projects } = data
-                  if (!this.state.project) this.setState({ project: projects[0].id })
+                  if (!this.state.project && projects.length) this.setState({ project: projects[0].id })
                   return (
                     <>
                       <label htmlFor="project">Project</label>
@@ -75,7 +75,7 @@ class CharacterziationForm extends React.Component {
                         {({ data, error, loading }) => {
                           if (error) return (<p>Error loading project version: ${error.message}</p>)
                           if (loading) return (<p>Loading data...</p>)
-                          if (!data) return (<p>No project version found.</p>)
+                          if (!data || !data.projectVersions.length) return (<p>No project version found.</p>)
 
                           const { projectVersions } = data
                           if (!this.state.projectVersion) this.setState({ projectVersion: projectVersions[0].id })

+ 6 - 4
frontend/components/Gallery.js

@@ -1,18 +1,20 @@
 import styled from 'styled-components'
 
 const GalleryStyle = styled.div`
-  border: 1px solid grey;
+  border: 1px solid ${props => props.theme.lightblue};
+  margin-bottom: 1.5em;
 
   #header {
     padding: 0 0.3em;
-    background: lightgray;
+    background-color: ${props => props.theme.lighterblue};
+    color: ${props => props.theme.darkblue};
     cursor: pointer
   }
 `
 
 class Gallery extends React.Component {
   state = {
-    galleryOpen: false
+    galleryOpen: !!this.props.galleryOpen
   }
 
   openGallery = event => {
@@ -24,7 +26,7 @@ class Gallery extends React.Component {
     return (
       <GalleryStyle>
         <div onClick={this.openGallery} id="header">
-          &#8227; {items.length} {title} available (click to {this.state.galleryOpen ? 'hide' : 'see'})
+          {this.state.galleryOpen ? <span>&#8259;</span> : <span>&#8227;</span>} {items.length} {title} available (click to {this.state.galleryOpen ? 'hide' : 'see'})
         </div>
         <div style={{ display: this.state.galleryOpen ? 'block' : 'none' }} id="gallery">
           {items}

+ 78 - 0
frontend/components/Instrument.js

@@ -0,0 +1,78 @@
+import gql from 'graphql-tag'
+import { Query, Mutation } from 'react-apollo'
+import InstrumentSubsystem from './InstrumentSubsystem'
+
+const CREATE_INSTRUMENT = gql`
+  mutation CREATE_INSTRUMENT($name: String!, $description: String!, $interfaces: [String]!) {
+    createInstrument(name: $name, description: $description, interfaces: $interfaces) {
+      id
+    }
+  }
+`
+
+class Instrument extends React.Component {
+  state = {
+    id: this.props.instrument ? this.props.instrument.id : null,
+    name: '',
+    description: '',
+    documents: [],
+    interfaces: [],
+    subsystems: []
+  }
+
+  toState = event => {
+    this.setState({ [event.target.name]: event.target.value })
+  }
+
+  addComment = event => {
+    event.preventDefault()
+    const newState = { ...this.state }
+    newState.changes.push(this.state.change)
+    newState.change = ''
+    this.setState(newState)
+  }
+
+  render() {
+    return (
+      <Mutation mutation={CREATE_PROJECT_VERSION} variables={this.state}>
+        {(createProjectVersion, { data, error, loading }) => (
+          <form onSubmit={async event => {
+            event.preventDefault()
+            const { data } = await createProjectVersion()
+            this.state.id = data.createProjectVersion.id
+          }}>
+            {!this.props.title && <h1>Project Version</h1>}
+            <fieldset id='project-generic'>
+              <label htmlFor='name'>Project name</label>
+              <input type='text' name='name' id='name' placeholder='Project version name' value={this.state.name} onChange={this.toState} />
+              <label htmlFor='description'>Date</label>
+              <input type='text' name='description' id='description' placeholder='Project description' value={this.state.description} onChange={this.toState} />
+              {this.props.project || (
+                <Query query={QUERY_PROJECTS}>
+                  {({ data, error, loading }) => {
+                    if (loading) return <p>Loading projects...</p>
+                    if (error) return <p>Error: {error.message}</p>
+                    if (!data || !data.projects.length) return <p>No projects found.</p>
+                    if (!this.state.project) this.setState({ project: data.projects[0].id })
+                    return (
+                      <label htmlFor="version">
+                        <select name="version" id="version">onChange={this.toState}>
+                        {data.projects.map(project =>
+                          <option key={project.id} value={project.id}>{project.name}</option>)
+                          }
+                        </select>
+                      </label>
+                    )
+                  }}
+                </Query>
+              )}
+            </fieldset>
+            <button type='submit'>Save</button>
+          </form>
+        )}
+      </Mutation>
+    )
+  }
+}
+
+export default Instrument

+ 82 - 0
frontend/components/InstrumentCommand.js

@@ -0,0 +1,82 @@
+import gql from 'graphql-tag'
+import { Query, Mutation } from 'react-apollo'
+
+const CREATE_INSTRUMENT_SUBSYSTEM = gql`
+  mutation CREATE_INSTRUMENT_SUBSYSTEM($name: String!, $description: String!, $interfaces: [String]!) {
+    createInstrument(name: $name, description: $description, interfaces: $interfaces) {
+      id
+    }
+  }
+`
+
+class InstrumentCommand extends React.Component {
+  state = {
+    id: this.props.instrumentCommand ? this.props.instrumentCommand.id : null,
+    tag: '',
+    name: '',
+    description: '',
+    instrument: null,
+    readString: '',
+    writeString: '',
+    parameters: []
+  }
+
+  toState = event => {
+    this.setState({ [event.target.name]: event.target.value })
+  }
+
+  addComment = event => {
+    event.preventDefault()
+    const newState = { ...this.state }
+    newState.changes.push(this.state.change)
+    newState.change = ''
+    this.setState(newState)
+  }
+
+  render() {
+    return (
+      <Mutation mutation={CREATE_PROJECT_VERSION} variables={this.state}>
+        {(createProjectVersion, { data, error, loading }) => (
+          <form onSubmit={async event => {
+            event.preventDefault()
+            const { data } = await createProjectVersion()
+            this.state.id = data.createProjectVersion.id
+          }}>
+            {!this.props.title && <h1>Project Version</h1>}
+            <fieldset id='project-generic'>
+              <label htmlFor='name'>Project name</label>
+              <input type='text' name='name' id='name' placeholder='Project version name' value={this.state.name} onChange={this.toState} />
+              <label htmlFor='date'>Date</label>
+              <input type='date' name='date' id='date' placeholder='Project date' value={this.state.date} onChange={this.toState} />
+              <label htmlFor='change'>Comments</label>
+              <input type='text' name='change' id='change' placeholder='Project change' value={this.state.change} onChange={this.toState} /><button onClick={this.addComment}>Add</button>
+              {this.state.changes.map((change, index) => <p key={index}>{change}</p>)}
+              {this.props.project || (
+                <Query query={QUERY_PROJECTS}>
+                  {({ data, error, loading }) => {
+                    if (loading) return <p>Loading projects...</p>
+                    if (error) return <p>Error: {error.message}</p>
+                    if (!data || !data.projects.length) return <p>No projects found.</p>
+                    if (!this.state.project) this.setState({ project: data.projects[0].id })
+                    return (
+                      <label htmlFor="version">
+                        <select name="version" id="version">onChange={this.toState}>
+                        {data.projects.map(project =>
+                          <option key={project.id} value={project.id}>{project.name}</option>)
+                          }
+                        </select>
+                      </label>
+                    )
+                  }}
+                </Query>
+              )}
+            </fieldset>
+            <button type='submit'>Save</button>
+          </form>
+        )}
+      </Mutation>
+    )
+  }
+}
+
+export default InstrumentCommand

+ 80 - 0
frontend/components/InstrumentInstance.js

@@ -0,0 +1,80 @@
+import gql from 'graphql-tag'
+import { Query, Mutation } from 'react-apollo'
+
+const CREATE_INSTRUMENT_INSTANCE = gql`
+  mutation CREATE_INSTRUMENT_INSTANCE($name: String!, $description: String!, $interfaces: [String]!) {
+    createInstrument(name: $name, description: $description, interfaces: $interfaces) {
+      id
+    }
+  }
+`
+
+class InstrumentInstance extends React.Component {
+  state = {
+    id: this.props.instrumentInstance ? this.props.instrumentInstance.id : null,
+    instrument: null,
+    identifier: '',
+    interface: '',
+    label: '',
+    location: ''
+  }
+
+  toState = event => {
+    this.setState({ [event.target.name]: event.target.value })
+  }
+
+  addComment = event => {
+    event.preventDefault()
+    const newState = { ...this.state }
+    newState.changes.push(this.state.change)
+    newState.change = ''
+    this.setState(newState)
+  }
+
+  render() {
+    return (
+      <Mutation mutation={CREATE_PROJECT_VERSION} variables={this.state}>
+        {(createProjectVersion, { data, error, loading }) => (
+          <form onSubmit={async event => {
+            event.preventDefault()
+            const { data } = await createProjectVersion()
+            this.state.id = data.createProjectVersion.id
+          }}>
+            {!this.props.title && <h1>Project Version</h1>}
+            <fieldset id='project-generic'>
+              <label htmlFor='name'>Project name</label>
+              <input type='text' name='name' id='name' placeholder='Project version name' value={this.state.name} onChange={this.toState} />
+              <label htmlFor='date'>Date</label>
+              <input type='date' name='date' id='date' placeholder='Project date' value={this.state.date} onChange={this.toState} />
+              <label htmlFor='change'>Comments</label>
+              <input type='text' name='change' id='change' placeholder='Project change' value={this.state.change} onChange={this.toState} /><button onClick={this.addComment}>Add</button>
+              {this.state.changes.map((change, index) => <p key={index}>{change}</p>)}
+              {this.props.project || (
+                <Query query={QUERY_PROJECTS}>
+                  {({ data, error, loading }) => {
+                    if (loading) return <p>Loading projects...</p>
+                    if (error) return <p>Error: {error.message}</p>
+                    if (!data || !data.projects.length) return <p>No projects found.</p>
+                    if (!this.state.project) this.setState({ project: data.projects[0].id })
+                    return (
+                      <label htmlFor="version">
+                        <select name="version" id="version">onChange={this.toState}>
+                        {data.projects.map(project =>
+                          <option key={project.id} value={project.id}>{project.name}</option>)
+                          }
+                        </select>
+                      </label>
+                    )
+                  }}
+                </Query>
+              )}
+            </fieldset>
+            <button type='submit'>Save</button>
+          </form>
+        )}
+      </Mutation>
+    )
+  }
+}
+
+export default InstrumentInstance

+ 79 - 0
frontend/components/InstrumentParameter.js

@@ -0,0 +1,79 @@
+import gql from 'graphql-tag'
+import { Query, Mutation } from 'react-apollo'
+
+const CREATE_INSTRUMENT_PARAMETER = gql`
+  mutation CREATE_INSTRUMENT_PARAMETER($name: String!, $description: String!, $interfaces: [String]!) {
+    createInstrument(name: $name, description: $description, interfaces: $interfaces) {
+      id
+    }
+  }
+`
+
+class InstrumentParameter extends React.Component {
+  state = {
+    id: this.props.instrumentParameter ? this.props.instrumentParameter.id : null,
+    tag: '',
+    name: '',
+    description: '',
+    values: []
+  }
+
+  toState = event => {
+    this.setState({ [event.target.name]: event.target.value })
+  }
+
+  addComment = event => {
+    event.preventDefault()
+    const newState = { ...this.state }
+    newState.changes.push(this.state.change)
+    newState.change = ''
+    this.setState(newState)
+  }
+
+  render() {
+    return (
+      <Mutation mutation={CREATE_PROJECT_VERSION} variables={this.state}>
+        {(createProjectVersion, { data, error, loading }) => (
+          <form onSubmit={async event => {
+            event.preventDefault()
+            const { data } = await createProjectVersion()
+            this.state.id = data.createProjectVersion.id
+          }}>
+            {!this.props.title && <h1>Project Version</h1>}
+            <fieldset id='project-generic'>
+              <label htmlFor='name'>Project name</label>
+              <input type='text' name='name' id='name' placeholder='Project version name' value={this.state.name} onChange={this.toState} />
+              <label htmlFor='date'>Date</label>
+              <input type='date' name='date' id='date' placeholder='Project date' value={this.state.date} onChange={this.toState} />
+              <label htmlFor='change'>Comments</label>
+              <input type='text' name='change' id='change' placeholder='Project change' value={this.state.change} onChange={this.toState} /><button onClick={this.addComment}>Add</button>
+              {this.state.changes.map((change, index) => <p key={index}>{change}</p>)}
+              {this.props.project || (
+                <Query query={QUERY_PROJECTS}>
+                  {({ data, error, loading }) => {
+                    if (loading) return <p>Loading projects...</p>
+                    if (error) return <p>Error: {error.message}</p>
+                    if (!data || !data.projects.length) return <p>No projects found.</p>
+                    if (!this.state.project) this.setState({ project: data.projects[0].id })
+                    return (
+                      <label htmlFor="version">
+                        <select name="version" id="version">onChange={this.toState}>
+                        {data.projects.map(project =>
+                          <option key={project.id} value={project.id}>{project.name}</option>)
+                          }
+                        </select>
+                      </label>
+                    )
+                  }}
+                </Query>
+              )}
+            </fieldset>
+            <button type='submit'>Save</button>
+          </form>
+        )}
+      </Mutation>
+    )
+  }
+}
+
+export default InstrumentParameter

+ 80 - 0
frontend/components/InstrumentSubsystem.js

@@ -0,0 +1,80 @@
+import gql from 'graphql-tag'
+import { Query, Mutation } from 'react-apollo'
+
+const CREATE_INSTRUMENT_SUBSYSTEM = gql`
+  mutation CREATE_INSTRUMENT_SUBSYSTEM($name: String!, $description: String!, $interfaces: [String]!) {
+    createInstrument(name: $name, description: $description, interfaces: $interfaces) {
+      id
+    }
+  }
+`
+
+class InstrumentSubsystem extends React.Component {
+  state = {
+    id: this.props.instrumentSubsystem ? this.props.instrumentSubsystem.id : null,
+    name: '',
+    description: '',
+    commands: [],
+    parameters: [],
+    subsystems: []
+  }
+
+  toState = event => {
+    this.setState({ [event.target.name]: event.target.value })
+  }
+
+  addComment = event => {
+    event.preventDefault()
+    const newState = { ...this.state }
+    newState.changes.push(this.state.change)
+    newState.change = ''
+    this.setState(newState)
+  }
+
+  render() {
+    return (
+      <Mutation mutation={CREATE_PROJECT_VERSION} variables={this.state}>
+        {(createProjectVersion, { data, error, loading }) => (
+          <form onSubmit={async event => {
+            event.preventDefault()
+            const { data } = await createProjectVersion()
+            this.state.id = data.createProjectVersion.id
+          }}>
+            {!this.props.title && <h1>Project Version</h1>}
+            <fieldset id='project-generic'>
+              <label htmlFor='name'>Project name</label>
+              <input type='text' name='name' id='name' placeholder='Project version name' value={this.state.name} onChange={this.toState} />
+              <label htmlFor='date'>Date</label>
+              <input type='date' name='date' id='date' placeholder='Project date' value={this.state.date} onChange={this.toState} />
+              <label htmlFor='change'>Comments</label>
+              <input type='text' name='change' id='change' placeholder='Project change' value={this.state.change} onChange={this.toState} /><button onClick={this.addComment}>Add</button>
+              {this.state.changes.map((change, index) => <p key={index}>{change}</p>)}
+              {this.props.project || (
+                <Query query={QUERY_PROJECTS}>
+                  {({ data, error, loading }) => {
+                    if (loading) return <p>Loading projects...</p>
+                    if (error) return <p>Error: {error.message}</p>
+                    if (!data || !data.projects.length) return <p>No projects found.</p>
+                    if (!this.state.project) this.setState({ project: data.projects[0].id })
+                    return (
+                      <label htmlFor="version">
+                        <select name="version" id="version">onChange={this.toState}>
+                        {data.projects.map(project =>
+                          <option key={project.id} value={project.id}>{project.name}</option>)
+                          }
+                        </select>
+                      </label>
+                    )
+                  }}
+                </Query>
+              )}
+            </fieldset>
+            <button type='submit'>Save</button>
+          </form>
+        )}
+      </Mutation>
+    )
+  }
+}
+
+export default InstrumentSubsystem

+ 78 - 16
frontend/components/ProjectForm.js

@@ -1,9 +1,63 @@
 import styled from 'styled-components'
 import gql from 'graphql-tag'
-import { Query } from 'react-apollo'
+import { Mutation, Query } from 'react-apollo'
 
-class ProjectForm extends React.Component {
+const CREATE_PROJECT = gql`
+  mutation CREATE_PROJECT($name: String!, $abbreviation: String!, $description: String) {
+    createProject(name: $name, abbreviation: $abbreviation, description: $description) {
+      id
+    }
+  }
+`
+
+const QUERY_PROJECTS = gql`
+  query QUERY_PROJECTS {
+    projects {
+      id
+      name
+      abbreviation
+      description
+      files {
+        id
+        path
+        name
+        description
+        filename
+        mimetype
+        size
+      }
+      versions {
+        id
+        name
+        changes
+        date
+      }
+    }
+  }
+`
+
+const ProjectSelector = props => (
+  <Query query={QUERY_PROJECTS}>
+    {({ data, loading, error }) => {
+      if (error) return (<p>Error loading project: ${error.message}</p>)
+      if (loading) return (<p>Loading data...</p>)
+      if (!data || !data.projects.length) return (<p>No project found.</p>)
+      if (!props.value) props.onChange({ target: { name: 'project', value: data.projects[0].id } })
+      const selector = (
+        <select {...props}>
+          {data.projects.map(project =>
+            <option key={project.id} value={project.id}>{project.name}</option>
+          )}
+        </select>
+      )
+      return selector
+    }}
+  </Query >
+)
+
+class Project extends React.Component {
   state = {
+    id: null,
     name: '',
     abbreviation: '',
     description: '',
@@ -15,21 +69,29 @@ class ProjectForm extends React.Component {
 
   render() {
     return (
-      <form>
-        <h1>Project Setup</h1>
-        <p>Please fill in the form.</p>
-        <fieldset id='project-generic'>
-          <label htmlFor='project-name'>Project name</label>
-          <input type='text' id='project-name' placeholder='Project name' />
-          <label htmlFor='project-abbreviation'>Project abbreviation</label>
-          <input type='text' id='project-abbreviation' placeholder='Project abbreviation' />
-          <label htmlFor='project-description'>Project description</label>
-          <textarea id='project-description' placeholder='Project description' />
-        </fieldset>
-        <button type='submit' onClick={this.saveForm}>Save</button>
-      </form>
+      <Mutation mutation={CREATE_PROJECT} variables={this.state} refetchQueries={[{ query: QUERY_PROJECTS }]}>
+        {(createProject, { data, error, loading }) => (
+          <form onSubmit={async event => {
+            event.preventDefault()
+            const { data } = await createProject()
+            this.state.id = data.createProject.id
+          }}>
+            <h1>Project Setup</h1>
+            <fieldset id='project-generic'>
+              <label htmlFor='name'>Project name</label>
+              <input type='text' name='name' id='name' placeholder='Project name' value={this.state.name} onChange={this.toState} />
+              <label htmlFor='abbreviation'>Project abbreviation</label>
+              <input type='text' name='abbreviation' id='abbreviation' placeholder='Project abbreviation' value={this.state.abbreviation} onChange={this.toState} />
+              <label htmlFor='description'>Project description</label>
+              <textarea name='description' id='description' placeholder='Project description' value={this.state.description} onChange={this.toState} />
+            </fieldset>
+            <button type='submit'>Save</button>
+          </form>
+        )}
+      </Mutation>
     )
   }
 }
 
-export default ProjectForm
+export default Project
+export { ProjectSelector }

+ 63 - 0
frontend/components/ProjectVersionForm.js

@@ -0,0 +1,63 @@
+import gql from 'graphql-tag'
+import { Query, Mutation } from 'react-apollo'
+import { ProjectSelector } from './ProjectForm'
+
+const CREATE_PROJECT_VERSION = gql`
+  mutation CREATE_PROJECT_VERSION($name: String!, $date: String!, $project: ID!, $changes: [String]!) {
+    createProjectVersion(name: $name, date: $date, project: $project, changes: $changes) {
+      id
+    }
+  }
+`
+
+class ProjectVersionForm extends React.Component {
+  state = {
+    id: this.props.project ? this.props.project.id : null,
+    name: '',
+    date: '',
+    change: '',
+    changes: [],
+    project: null
+  }
+
+  toState = event => {
+    this.setState({ [event.target.name]: event.target.value })
+  }
+
+  addComment = event => {
+    event.preventDefault()
+    const newState = { ...this.state }
+    newState.changes.push(this.state.change)
+    newState.change = ''
+    this.setState(newState)
+  }
+
+  render() {
+    return (
+      <Mutation mutation={CREATE_PROJECT_VERSION} variables={this.state}>
+        {(createProjectVersion, { data, error, loading }) => (
+          <form onSubmit={async event => {
+            event.preventDefault()
+            const { data } = await createProjectVersion()
+            this.state.id = data.createProjectVersion.id
+          }}>
+            {!this.props.title && <h1>Project Version</h1>}
+            <fieldset id='project-generic'>
+              <label htmlFor='name'>Project name</label>
+              <input type='text' name='name' id='name' placeholder='Project version name' value={this.state.name} onChange={this.toState} />
+              <label htmlFor='date'>Date</label>
+              <input type='date' name='date' id='date' placeholder='Project date' value={this.state.date} onChange={this.toState} />
+              <label htmlFor='change'>Comments</label>
+              <input type='text' name='change' id='change' placeholder='Project change' value={this.state.change} onChange={this.toState} /><button onClick={this.addComment}>Add</button>
+              {this.state.changes.map((change, index) => <p key={index}>{change}</p>)}
+              {this.props.project || <ProjectSelector name='project' id='project' value={this.state.project} onChange={this.toState} />}
+            </fieldset>
+            <button type='submit'>Save</button>
+          </form>
+        )}
+      </Mutation>
+    )
+  }
+}
+
+export default ProjectVersionForm

+ 4 - 0
frontend/pages/index.js

@@ -4,10 +4,14 @@
  */
 
 import DemoForm from '../components/Form'
+import Project from '../components/ProjectForm'
+import ProjectVersionForm from '../components/ProjectVersionForm'
 
 const Index = props => (
   <div>
     <DemoForm />
+    <Project />
+    <ProjectVersionForm />
   </div>
 )