Browse Source

split style into separate file for modal.

Tomi Cvetic 4 years ago
parent
commit
dbaf42b4b5
58 changed files with 829 additions and 3066 deletions
  1. 0 276
      backend/database/generated/prisma-client/prisma-schema.ts
  2. 11 543
      backend/database/generated/prisma.graphql
  3. 0 14
      backend/datamodel.prisma
  4. 6 4
      backend/index.ts
  5. 9 361
      backend/package-lock.json
  6. 0 2
      backend/package.json
  7. 0 17
      backend/schema.graphql
  8. 0 3
      backend/src/file/constants.ts
  9. 0 3
      backend/src/file/index.ts
  10. 0 118
      backend/src/file/resolvers.ts
  11. 2 12
      backend/src/training/resolvers.ts
  12. 3 3
      frontend/initial-data.ts
  13. 0 10
      frontend/jest.config.js
  14. 264 309
      frontend/package-lock.json
  15. 20 23
      frontend/package.json
  16. 0 11
      frontend/pages/admin/index.tsx
  17. 1 0
      frontend/pages/admin/training/[id].tsx
  18. 4 10
      frontend/pages/index.tsx
  19. 8 0
      frontend/pages/timer.tsx
  20. 0 19
      frontend/pages/timer/[id].tsx
  21. 2 10
      frontend/pages/training/[id].tsx
  22. 1 1
      frontend/src/app/components/Footer.tsx
  23. 1 24
      frontend/src/app/components/Header.tsx
  24. 30 49
      frontend/src/app/components/Logo.tsx
  25. 51 171
      frontend/src/app/components/Nav.tsx
  26. 2 0
      frontend/src/app/components/Page.tsx
  27. 5 14
      frontend/src/form/__tests__/useFormHandler.test.tsx
  28. 1 1
      frontend/src/form/components/TextInput.tsx
  29. 21 312
      frontend/src/gql/index.tsx
  30. 71 4
      frontend/src/sortable/components/SortableList.tsx
  31. 0 50
      frontend/src/sortable/styles/index.ts
  32. 8 5
      frontend/src/styles/global.ts
  33. 1 2
      frontend/src/styles/theme.ts
  34. 2 2
      frontend/src/timer/components/Timer.tsx
  35. 13 17
      frontend/src/timer/utils.ts
  36. 0 63
      frontend/src/training/__tests__/utils.test.ts
  37. 6 23
      frontend/src/training/components/BlockInputs.tsx
  38. 0 64
      frontend/src/training/components/BlockSelector.tsx
  39. 3 7
      frontend/src/training/components/ExerciseComposition.tsx
  40. 0 8
      frontend/src/training/components/ExerciseInputs.tsx
  41. 2 2
      frontend/src/training/components/ExerciseInstanceInputs.tsx
  42. 0 66
      frontend/src/training/components/ExerciseSelector.tsx
  43. 2 2
      frontend/src/training/components/FormatSelector.tsx
  44. 9 11
      frontend/src/training/components/Training.tsx
  45. 17 30
      frontend/src/training/components/TrainingBlock.tsx
  46. 20 38
      frontend/src/training/components/TrainingMeta.tsx
  47. 1 1
      frontend/src/training/components/TrainingTypeSelector.tsx
  48. 15 125
      frontend/src/training/training.graphql
  49. 63 41
      frontend/src/training/types.ts
  50. 53 64
      frontend/src/training/utils.ts
  51. 17 33
      frontend/src/user/components/LoginForm.tsx
  52. 13 9
      frontend/src/user/components/LogoutButton.tsx
  53. 0 45
      frontend/src/user/components/UserNav.tsx
  54. 1 2
      frontend/src/user/components/__tests__/DeleteUserButton.test.tsx
  55. 6 23
      frontend/src/user/hooks.tsx
  56. 62 0
      frontend/src/user/user.js
  57. 0 6
      frontend/tsconfig.jest.json
  58. 2 3
      frontend/tsconfig.json

+ 0 - 276
backend/database/generated/prisma-client/prisma-schema.ts

@@ -22,10 +22,6 @@ type AggregateExerciseInstance {
   count: Int!
 }
 
-type AggregateFile {
-  count: Int!
-}
-
 type AggregateFormat {
   count: Int!
 }
@@ -1307,251 +1303,6 @@ input ExerciseWhereUniqueInput {
   id: ID
 }
 
-type File {
-  id: ID!
-  createdAt: DateTime!
-  updatedAt: DateTime!
-  path: String!
-  mimetype: String!
-  user: User!
-  thumbnail: String
-  filename: String!
-  encoding: String!
-  size: Int!
-  comment: String
-}
-
-type FileConnection {
-  pageInfo: PageInfo!
-  edges: [FileEdge]!
-  aggregate: AggregateFile!
-}
-
-input FileCreateInput {
-  id: ID
-  path: String!
-  mimetype: String!
-  user: UserCreateOneInput!
-  thumbnail: String
-  filename: String!
-  encoding: String!
-  size: Int!
-  comment: String
-}
-
-type FileEdge {
-  node: File!
-  cursor: String!
-}
-
-enum FileOrderByInput {
-  id_ASC
-  id_DESC
-  createdAt_ASC
-  createdAt_DESC
-  updatedAt_ASC
-  updatedAt_DESC
-  path_ASC
-  path_DESC
-  mimetype_ASC
-  mimetype_DESC
-  thumbnail_ASC
-  thumbnail_DESC
-  filename_ASC
-  filename_DESC
-  encoding_ASC
-  encoding_DESC
-  size_ASC
-  size_DESC
-  comment_ASC
-  comment_DESC
-}
-
-type FilePreviousValues {
-  id: ID!
-  createdAt: DateTime!
-  updatedAt: DateTime!
-  path: String!
-  mimetype: String!
-  thumbnail: String
-  filename: String!
-  encoding: String!
-  size: Int!
-  comment: String
-}
-
-type FileSubscriptionPayload {
-  mutation: MutationType!
-  node: File
-  updatedFields: [String!]
-  previousValues: FilePreviousValues
-}
-
-input FileSubscriptionWhereInput {
-  mutation_in: [MutationType!]
-  updatedFields_contains: String
-  updatedFields_contains_every: [String!]
-  updatedFields_contains_some: [String!]
-  node: FileWhereInput
-  AND: [FileSubscriptionWhereInput!]
-  OR: [FileSubscriptionWhereInput!]
-  NOT: [FileSubscriptionWhereInput!]
-}
-
-input FileUpdateInput {
-  path: String
-  mimetype: String
-  user: UserUpdateOneRequiredInput
-  thumbnail: String
-  filename: String
-  encoding: String
-  size: Int
-  comment: String
-}
-
-input FileUpdateManyMutationInput {
-  path: String
-  mimetype: String
-  thumbnail: String
-  filename: String
-  encoding: String
-  size: Int
-  comment: String
-}
-
-input FileWhereInput {
-  id: ID
-  id_not: ID
-  id_in: [ID!]
-  id_not_in: [ID!]
-  id_lt: ID
-  id_lte: ID
-  id_gt: ID
-  id_gte: ID
-  id_contains: ID
-  id_not_contains: ID
-  id_starts_with: ID
-  id_not_starts_with: ID
-  id_ends_with: ID
-  id_not_ends_with: ID
-  createdAt: DateTime
-  createdAt_not: DateTime
-  createdAt_in: [DateTime!]
-  createdAt_not_in: [DateTime!]
-  createdAt_lt: DateTime
-  createdAt_lte: DateTime
-  createdAt_gt: DateTime
-  createdAt_gte: DateTime
-  updatedAt: DateTime
-  updatedAt_not: DateTime
-  updatedAt_in: [DateTime!]
-  updatedAt_not_in: [DateTime!]
-  updatedAt_lt: DateTime
-  updatedAt_lte: DateTime
-  updatedAt_gt: DateTime
-  updatedAt_gte: DateTime
-  path: String
-  path_not: String
-  path_in: [String!]
-  path_not_in: [String!]
-  path_lt: String
-  path_lte: String
-  path_gt: String
-  path_gte: String
-  path_contains: String
-  path_not_contains: String
-  path_starts_with: String
-  path_not_starts_with: String
-  path_ends_with: String
-  path_not_ends_with: String
-  mimetype: String
-  mimetype_not: String
-  mimetype_in: [String!]
-  mimetype_not_in: [String!]
-  mimetype_lt: String
-  mimetype_lte: String
-  mimetype_gt: String
-  mimetype_gte: String
-  mimetype_contains: String
-  mimetype_not_contains: String
-  mimetype_starts_with: String
-  mimetype_not_starts_with: String
-  mimetype_ends_with: String
-  mimetype_not_ends_with: String
-  user: UserWhereInput
-  thumbnail: String
-  thumbnail_not: String
-  thumbnail_in: [String!]
-  thumbnail_not_in: [String!]
-  thumbnail_lt: String
-  thumbnail_lte: String
-  thumbnail_gt: String
-  thumbnail_gte: String
-  thumbnail_contains: String
-  thumbnail_not_contains: String
-  thumbnail_starts_with: String
-  thumbnail_not_starts_with: String
-  thumbnail_ends_with: String
-  thumbnail_not_ends_with: String
-  filename: String
-  filename_not: String
-  filename_in: [String!]
-  filename_not_in: [String!]
-  filename_lt: String
-  filename_lte: String
-  filename_gt: String
-  filename_gte: String
-  filename_contains: String
-  filename_not_contains: String
-  filename_starts_with: String
-  filename_not_starts_with: String
-  filename_ends_with: String
-  filename_not_ends_with: String
-  encoding: String
-  encoding_not: String
-  encoding_in: [String!]
-  encoding_not_in: [String!]
-  encoding_lt: String
-  encoding_lte: String
-  encoding_gt: String
-  encoding_gte: String
-  encoding_contains: String
-  encoding_not_contains: String
-  encoding_starts_with: String
-  encoding_not_starts_with: String
-  encoding_ends_with: String
-  encoding_not_ends_with: String
-  size: Int
-  size_not: Int
-  size_in: [Int!]
-  size_not_in: [Int!]
-  size_lt: Int
-  size_lte: Int
-  size_gt: Int
-  size_gte: Int
-  comment: String
-  comment_not: String
-  comment_in: [String!]
-  comment_not_in: [String!]
-  comment_lt: String
-  comment_lte: String
-  comment_gt: String
-  comment_gte: String
-  comment_contains: String
-  comment_not_contains: String
-  comment_starts_with: String
-  comment_not_starts_with: String
-  comment_ends_with: String
-  comment_not_ends_with: String
-  AND: [FileWhereInput!]
-  OR: [FileWhereInput!]
-  NOT: [FileWhereInput!]
-}
-
-input FileWhereUniqueInput {
-  id: ID
-}
-
 type Format {
   id: ID!
   name: String!
@@ -1725,12 +1476,6 @@ type Mutation {
   upsertExerciseInstance(where: ExerciseInstanceWhereUniqueInput!, create: ExerciseInstanceCreateInput!, update: ExerciseInstanceUpdateInput!): ExerciseInstance!
   deleteExerciseInstance(where: ExerciseInstanceWhereUniqueInput!): ExerciseInstance
   deleteManyExerciseInstances(where: ExerciseInstanceWhereInput): BatchPayload!
-  createFile(data: FileCreateInput!): File!
-  updateFile(data: FileUpdateInput!, where: FileWhereUniqueInput!): File
-  updateManyFiles(data: FileUpdateManyMutationInput!, where: FileWhereInput): BatchPayload!
-  upsertFile(where: FileWhereUniqueInput!, create: FileCreateInput!, update: FileUpdateInput!): File!
-  deleteFile(where: FileWhereUniqueInput!): File
-  deleteManyFiles(where: FileWhereInput): BatchPayload!
   createFormat(data: FormatCreateInput!): Format!
   updateFormat(data: FormatUpdateInput!, where: FormatWhereUniqueInput!): Format
   updateManyFormats(data: FormatUpdateManyMutationInput!, where: FormatWhereInput): BatchPayload!
@@ -1807,9 +1552,6 @@ type Query {
   exerciseInstance(where: ExerciseInstanceWhereUniqueInput!): ExerciseInstance
   exerciseInstances(where: ExerciseInstanceWhereInput, orderBy: ExerciseInstanceOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [ExerciseInstance]!
   exerciseInstancesConnection(where: ExerciseInstanceWhereInput, orderBy: ExerciseInstanceOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): ExerciseInstanceConnection!
-  file(where: FileWhereUniqueInput!): File
-  files(where: FileWhereInput, orderBy: FileOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [File]!
-  filesConnection(where: FileWhereInput, orderBy: FileOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): FileConnection!
   format(where: FormatWhereUniqueInput!): Format
   formats(where: FormatWhereInput, orderBy: FormatOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Format]!
   formatsConnection(where: FormatWhereInput, orderBy: FormatOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): FormatConnection!
@@ -2098,7 +1840,6 @@ type Subscription {
   comment(where: CommentSubscriptionWhereInput): CommentSubscriptionPayload
   exercise(where: ExerciseSubscriptionWhereInput): ExerciseSubscriptionPayload
   exerciseInstance(where: ExerciseInstanceSubscriptionWhereInput): ExerciseInstanceSubscriptionPayload
-  file(where: FileSubscriptionWhereInput): FileSubscriptionPayload
   format(where: FormatSubscriptionWhereInput): FormatSubscriptionPayload
   rating(where: RatingSubscriptionWhereInput): RatingSubscriptionPayload
   track(where: TrackSubscriptionWhereInput): TrackSubscriptionPayload
@@ -2794,11 +2535,6 @@ input UserCreateManyInput {
   connect: [UserWhereUniqueInput!]
 }
 
-input UserCreateOneInput {
-  create: UserCreateInput
-  connect: UserWhereUniqueInput
-}
-
 input UserCreateOneWithoutCommentsInput {
   create: UserCreateWithoutCommentsInput
   connect: UserWhereUniqueInput
@@ -3046,13 +2782,6 @@ input UserUpdateManyWithWhereNestedInput {
   data: UserUpdateManyDataInput!
 }
 
-input UserUpdateOneRequiredInput {
-  create: UserCreateInput
-  update: UserUpdateDataInput
-  upsert: UserUpsertNestedInput
-  connect: UserWhereUniqueInput
-}
-
 input UserUpdateOneRequiredWithoutCommentsInput {
   create: UserCreateWithoutCommentsInput
   update: UserUpdateWithoutCommentsDataInput
@@ -3098,11 +2827,6 @@ input UserUpdateWithWhereUniqueNestedInput {
   data: UserUpdateDataInput!
 }
 
-input UserUpsertNestedInput {
-  update: UserUpdateDataInput!
-  create: UserCreateInput!
-}
-
 input UserUpsertWithoutCommentsInput {
   update: UserUpdateWithoutCommentsDataInput!
   create: UserCreateWithoutCommentsInput!

+ 11 - 543
backend/database/generated/prisma.graphql

@@ -1,5 +1,5 @@
 # source: http://prisma:4466
-# timestamp: Fri Apr 10 2020 18:04:15 GMT+0000 (Coordinated Universal Time)
+# timestamp: Wed Apr 08 2020 18:07:43 GMT+0000 (Coordinated Universal Time)
 
 type AggregateBlock {
   count: Int!
@@ -21,10 +21,6 @@ type AggregateExerciseInstance {
   count: Int!
 }
 
-type AggregateFile {
-  count: Int!
-}
-
 type AggregateFormat {
   count: Int!
 }
@@ -2118,507 +2114,6 @@ input ExerciseWhereUniqueInput {
   id: ID
 }
 
-type File implements Node {
-  id: ID!
-  createdAt: DateTime!
-  updatedAt: DateTime!
-  path: String!
-  mimetype: String!
-  user: User!
-  thumbnail: String
-  filename: String!
-  encoding: String!
-  size: Int!
-  comment: String
-}
-
-"""A connection to a list of items."""
-type FileConnection {
-  """Information to aid in pagination."""
-  pageInfo: PageInfo!
-
-  """A list of edges."""
-  edges: [FileEdge]!
-  aggregate: AggregateFile!
-}
-
-input FileCreateInput {
-  id: ID
-  path: String!
-  mimetype: String!
-  thumbnail: String
-  filename: String!
-  encoding: String!
-  size: Int!
-  comment: String
-  user: UserCreateOneInput!
-}
-
-"""An edge in a connection."""
-type FileEdge {
-  """The item at the end of the edge."""
-  node: File!
-
-  """A cursor for use in pagination."""
-  cursor: String!
-}
-
-enum FileOrderByInput {
-  id_ASC
-  id_DESC
-  createdAt_ASC
-  createdAt_DESC
-  updatedAt_ASC
-  updatedAt_DESC
-  path_ASC
-  path_DESC
-  mimetype_ASC
-  mimetype_DESC
-  thumbnail_ASC
-  thumbnail_DESC
-  filename_ASC
-  filename_DESC
-  encoding_ASC
-  encoding_DESC
-  size_ASC
-  size_DESC
-  comment_ASC
-  comment_DESC
-}
-
-type FilePreviousValues {
-  id: ID!
-  createdAt: DateTime!
-  updatedAt: DateTime!
-  path: String!
-  mimetype: String!
-  thumbnail: String
-  filename: String!
-  encoding: String!
-  size: Int!
-  comment: String
-}
-
-type FileSubscriptionPayload {
-  mutation: MutationType!
-  node: File
-  updatedFields: [String!]
-  previousValues: FilePreviousValues
-}
-
-input FileSubscriptionWhereInput {
-  """Logical AND on all given filters."""
-  AND: [FileSubscriptionWhereInput!]
-
-  """Logical OR on all given filters."""
-  OR: [FileSubscriptionWhereInput!]
-
-  """Logical NOT on all given filters combined by AND."""
-  NOT: [FileSubscriptionWhereInput!]
-
-  """The subscription event gets dispatched when it's listed in mutation_in"""
-  mutation_in: [MutationType!]
-
-  """
-  The subscription event gets only dispatched when one of the updated fields names is included in this list
-  """
-  updatedFields_contains: String
-
-  """
-  The subscription event gets only dispatched when all of the field names included in this list have been updated
-  """
-  updatedFields_contains_every: [String!]
-
-  """
-  The subscription event gets only dispatched when some of the field names included in this list have been updated
-  """
-  updatedFields_contains_some: [String!]
-  node: FileWhereInput
-}
-
-input FileUpdateInput {
-  path: String
-  mimetype: String
-  thumbnail: String
-  filename: String
-  encoding: String
-  size: Int
-  comment: String
-  user: UserUpdateOneRequiredInput
-}
-
-input FileUpdateManyMutationInput {
-  path: String
-  mimetype: String
-  thumbnail: String
-  filename: String
-  encoding: String
-  size: Int
-  comment: String
-}
-
-input FileWhereInput {
-  """Logical AND on all given filters."""
-  AND: [FileWhereInput!]
-
-  """Logical OR on all given filters."""
-  OR: [FileWhereInput!]
-
-  """Logical NOT on all given filters combined by AND."""
-  NOT: [FileWhereInput!]
-  id: ID
-
-  """All values that are not equal to given value."""
-  id_not: ID
-
-  """All values that are contained in given list."""
-  id_in: [ID!]
-
-  """All values that are not contained in given list."""
-  id_not_in: [ID!]
-
-  """All values less than the given value."""
-  id_lt: ID
-
-  """All values less than or equal the given value."""
-  id_lte: ID
-
-  """All values greater than the given value."""
-  id_gt: ID
-
-  """All values greater than or equal the given value."""
-  id_gte: ID
-
-  """All values containing the given string."""
-  id_contains: ID
-
-  """All values not containing the given string."""
-  id_not_contains: ID
-
-  """All values starting with the given string."""
-  id_starts_with: ID
-
-  """All values not starting with the given string."""
-  id_not_starts_with: ID
-
-  """All values ending with the given string."""
-  id_ends_with: ID
-
-  """All values not ending with the given string."""
-  id_not_ends_with: ID
-  createdAt: DateTime
-
-  """All values that are not equal to given value."""
-  createdAt_not: DateTime
-
-  """All values that are contained in given list."""
-  createdAt_in: [DateTime!]
-
-  """All values that are not contained in given list."""
-  createdAt_not_in: [DateTime!]
-
-  """All values less than the given value."""
-  createdAt_lt: DateTime
-
-  """All values less than or equal the given value."""
-  createdAt_lte: DateTime
-
-  """All values greater than the given value."""
-  createdAt_gt: DateTime
-
-  """All values greater than or equal the given value."""
-  createdAt_gte: DateTime
-  updatedAt: DateTime
-
-  """All values that are not equal to given value."""
-  updatedAt_not: DateTime
-
-  """All values that are contained in given list."""
-  updatedAt_in: [DateTime!]
-
-  """All values that are not contained in given list."""
-  updatedAt_not_in: [DateTime!]
-
-  """All values less than the given value."""
-  updatedAt_lt: DateTime
-
-  """All values less than or equal the given value."""
-  updatedAt_lte: DateTime
-
-  """All values greater than the given value."""
-  updatedAt_gt: DateTime
-
-  """All values greater than or equal the given value."""
-  updatedAt_gte: DateTime
-  path: String
-
-  """All values that are not equal to given value."""
-  path_not: String
-
-  """All values that are contained in given list."""
-  path_in: [String!]
-
-  """All values that are not contained in given list."""
-  path_not_in: [String!]
-
-  """All values less than the given value."""
-  path_lt: String
-
-  """All values less than or equal the given value."""
-  path_lte: String
-
-  """All values greater than the given value."""
-  path_gt: String
-
-  """All values greater than or equal the given value."""
-  path_gte: String
-
-  """All values containing the given string."""
-  path_contains: String
-
-  """All values not containing the given string."""
-  path_not_contains: String
-
-  """All values starting with the given string."""
-  path_starts_with: String
-
-  """All values not starting with the given string."""
-  path_not_starts_with: String
-
-  """All values ending with the given string."""
-  path_ends_with: String
-
-  """All values not ending with the given string."""
-  path_not_ends_with: String
-  mimetype: String
-
-  """All values that are not equal to given value."""
-  mimetype_not: String
-
-  """All values that are contained in given list."""
-  mimetype_in: [String!]
-
-  """All values that are not contained in given list."""
-  mimetype_not_in: [String!]
-
-  """All values less than the given value."""
-  mimetype_lt: String
-
-  """All values less than or equal the given value."""
-  mimetype_lte: String
-
-  """All values greater than the given value."""
-  mimetype_gt: String
-
-  """All values greater than or equal the given value."""
-  mimetype_gte: String
-
-  """All values containing the given string."""
-  mimetype_contains: String
-
-  """All values not containing the given string."""
-  mimetype_not_contains: String
-
-  """All values starting with the given string."""
-  mimetype_starts_with: String
-
-  """All values not starting with the given string."""
-  mimetype_not_starts_with: String
-
-  """All values ending with the given string."""
-  mimetype_ends_with: String
-
-  """All values not ending with the given string."""
-  mimetype_not_ends_with: String
-  thumbnail: String
-
-  """All values that are not equal to given value."""
-  thumbnail_not: String
-
-  """All values that are contained in given list."""
-  thumbnail_in: [String!]
-
-  """All values that are not contained in given list."""
-  thumbnail_not_in: [String!]
-
-  """All values less than the given value."""
-  thumbnail_lt: String
-
-  """All values less than or equal the given value."""
-  thumbnail_lte: String
-
-  """All values greater than the given value."""
-  thumbnail_gt: String
-
-  """All values greater than or equal the given value."""
-  thumbnail_gte: String
-
-  """All values containing the given string."""
-  thumbnail_contains: String
-
-  """All values not containing the given string."""
-  thumbnail_not_contains: String
-
-  """All values starting with the given string."""
-  thumbnail_starts_with: String
-
-  """All values not starting with the given string."""
-  thumbnail_not_starts_with: String
-
-  """All values ending with the given string."""
-  thumbnail_ends_with: String
-
-  """All values not ending with the given string."""
-  thumbnail_not_ends_with: String
-  filename: String
-
-  """All values that are not equal to given value."""
-  filename_not: String
-
-  """All values that are contained in given list."""
-  filename_in: [String!]
-
-  """All values that are not contained in given list."""
-  filename_not_in: [String!]
-
-  """All values less than the given value."""
-  filename_lt: String
-
-  """All values less than or equal the given value."""
-  filename_lte: String
-
-  """All values greater than the given value."""
-  filename_gt: String
-
-  """All values greater than or equal the given value."""
-  filename_gte: String
-
-  """All values containing the given string."""
-  filename_contains: String
-
-  """All values not containing the given string."""
-  filename_not_contains: String
-
-  """All values starting with the given string."""
-  filename_starts_with: String
-
-  """All values not starting with the given string."""
-  filename_not_starts_with: String
-
-  """All values ending with the given string."""
-  filename_ends_with: String
-
-  """All values not ending with the given string."""
-  filename_not_ends_with: String
-  encoding: String
-
-  """All values that are not equal to given value."""
-  encoding_not: String
-
-  """All values that are contained in given list."""
-  encoding_in: [String!]
-
-  """All values that are not contained in given list."""
-  encoding_not_in: [String!]
-
-  """All values less than the given value."""
-  encoding_lt: String
-
-  """All values less than or equal the given value."""
-  encoding_lte: String
-
-  """All values greater than the given value."""
-  encoding_gt: String
-
-  """All values greater than or equal the given value."""
-  encoding_gte: String
-
-  """All values containing the given string."""
-  encoding_contains: String
-
-  """All values not containing the given string."""
-  encoding_not_contains: String
-
-  """All values starting with the given string."""
-  encoding_starts_with: String
-
-  """All values not starting with the given string."""
-  encoding_not_starts_with: String
-
-  """All values ending with the given string."""
-  encoding_ends_with: String
-
-  """All values not ending with the given string."""
-  encoding_not_ends_with: String
-  size: Int
-
-  """All values that are not equal to given value."""
-  size_not: Int
-
-  """All values that are contained in given list."""
-  size_in: [Int!]
-
-  """All values that are not contained in given list."""
-  size_not_in: [Int!]
-
-  """All values less than the given value."""
-  size_lt: Int
-
-  """All values less than or equal the given value."""
-  size_lte: Int
-
-  """All values greater than the given value."""
-  size_gt: Int
-
-  """All values greater than or equal the given value."""
-  size_gte: Int
-  comment: String
-
-  """All values that are not equal to given value."""
-  comment_not: String
-
-  """All values that are contained in given list."""
-  comment_in: [String!]
-
-  """All values that are not contained in given list."""
-  comment_not_in: [String!]
-
-  """All values less than the given value."""
-  comment_lt: String
-
-  """All values less than or equal the given value."""
-  comment_lte: String
-
-  """All values greater than the given value."""
-  comment_gt: String
-
-  """All values greater than or equal the given value."""
-  comment_gte: String
-
-  """All values containing the given string."""
-  comment_contains: String
-
-  """All values not containing the given string."""
-  comment_not_contains: String
-
-  """All values starting with the given string."""
-  comment_starts_with: String
-
-  """All values not starting with the given string."""
-  comment_not_starts_with: String
-
-  """All values ending with the given string."""
-  comment_ends_with: String
-
-  """All values not ending with the given string."""
-  comment_not_ends_with: String
-  user: UserWhereInput
-}
-
-input FileWhereUniqueInput {
-  id: ID
-}
-
 type Format implements Node {
   id: ID!
   name: String!
@@ -2876,73 +2371,67 @@ Long can represent values between -(2^63) and 2^63 - 1.
 scalar Long
 
 type Mutation {
-  createFile(data: FileCreateInput!): File!
   createTraining(data: TrainingCreateInput!): Training!
   createBlock(data: BlockCreateInput!): Block!
   createBlockInstance(data: BlockInstanceCreateInput!): BlockInstance!
   createComment(data: CommentCreateInput!): Comment!
-  createTrainingType(data: TrainingTypeCreateInput!): TrainingType!
   createUser(data: UserCreateInput!): User!
+  createTrainingType(data: TrainingTypeCreateInput!): TrainingType!
   createTrack(data: TrackCreateInput!): Track!
   createExercise(data: ExerciseCreateInput!): Exercise!
   createFormat(data: FormatCreateInput!): Format!
   createExerciseInstance(data: ExerciseInstanceCreateInput!): ExerciseInstance!
   createRating(data: RatingCreateInput!): Rating!
-  updateFile(data: FileUpdateInput!, where: FileWhereUniqueInput!): File
   updateTraining(data: TrainingUpdateInput!, where: TrainingWhereUniqueInput!): Training
   updateBlock(data: BlockUpdateInput!, where: BlockWhereUniqueInput!): Block
   updateBlockInstance(data: BlockInstanceUpdateInput!, where: BlockInstanceWhereUniqueInput!): BlockInstance
   updateComment(data: CommentUpdateInput!, where: CommentWhereUniqueInput!): Comment
-  updateTrainingType(data: TrainingTypeUpdateInput!, where: TrainingTypeWhereUniqueInput!): TrainingType
   updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User
+  updateTrainingType(data: TrainingTypeUpdateInput!, where: TrainingTypeWhereUniqueInput!): TrainingType
   updateTrack(data: TrackUpdateInput!, where: TrackWhereUniqueInput!): Track
   updateExercise(data: ExerciseUpdateInput!, where: ExerciseWhereUniqueInput!): Exercise
   updateFormat(data: FormatUpdateInput!, where: FormatWhereUniqueInput!): Format
   updateExerciseInstance(data: ExerciseInstanceUpdateInput!, where: ExerciseInstanceWhereUniqueInput!): ExerciseInstance
   updateRating(data: RatingUpdateInput!, where: RatingWhereUniqueInput!): Rating
-  deleteFile(where: FileWhereUniqueInput!): File
   deleteTraining(where: TrainingWhereUniqueInput!): Training
   deleteBlock(where: BlockWhereUniqueInput!): Block
   deleteBlockInstance(where: BlockInstanceWhereUniqueInput!): BlockInstance
   deleteComment(where: CommentWhereUniqueInput!): Comment
-  deleteTrainingType(where: TrainingTypeWhereUniqueInput!): TrainingType
   deleteUser(where: UserWhereUniqueInput!): User
+  deleteTrainingType(where: TrainingTypeWhereUniqueInput!): TrainingType
   deleteTrack(where: TrackWhereUniqueInput!): Track
   deleteExercise(where: ExerciseWhereUniqueInput!): Exercise
   deleteFormat(where: FormatWhereUniqueInput!): Format
   deleteExerciseInstance(where: ExerciseInstanceWhereUniqueInput!): ExerciseInstance
   deleteRating(where: RatingWhereUniqueInput!): Rating
-  upsertFile(where: FileWhereUniqueInput!, create: FileCreateInput!, update: FileUpdateInput!): File!
   upsertTraining(where: TrainingWhereUniqueInput!, create: TrainingCreateInput!, update: TrainingUpdateInput!): Training!
   upsertBlock(where: BlockWhereUniqueInput!, create: BlockCreateInput!, update: BlockUpdateInput!): Block!
   upsertBlockInstance(where: BlockInstanceWhereUniqueInput!, create: BlockInstanceCreateInput!, update: BlockInstanceUpdateInput!): BlockInstance!
   upsertComment(where: CommentWhereUniqueInput!, create: CommentCreateInput!, update: CommentUpdateInput!): Comment!
-  upsertTrainingType(where: TrainingTypeWhereUniqueInput!, create: TrainingTypeCreateInput!, update: TrainingTypeUpdateInput!): TrainingType!
   upsertUser(where: UserWhereUniqueInput!, create: UserCreateInput!, update: UserUpdateInput!): User!
+  upsertTrainingType(where: TrainingTypeWhereUniqueInput!, create: TrainingTypeCreateInput!, update: TrainingTypeUpdateInput!): TrainingType!
   upsertTrack(where: TrackWhereUniqueInput!, create: TrackCreateInput!, update: TrackUpdateInput!): Track!
   upsertExercise(where: ExerciseWhereUniqueInput!, create: ExerciseCreateInput!, update: ExerciseUpdateInput!): Exercise!
   upsertFormat(where: FormatWhereUniqueInput!, create: FormatCreateInput!, update: FormatUpdateInput!): Format!
   upsertExerciseInstance(where: ExerciseInstanceWhereUniqueInput!, create: ExerciseInstanceCreateInput!, update: ExerciseInstanceUpdateInput!): ExerciseInstance!
   upsertRating(where: RatingWhereUniqueInput!, create: RatingCreateInput!, update: RatingUpdateInput!): Rating!
-  updateManyFiles(data: FileUpdateManyMutationInput!, where: FileWhereInput): BatchPayload!
   updateManyTrainings(data: TrainingUpdateManyMutationInput!, where: TrainingWhereInput): BatchPayload!
   updateManyBlocks(data: BlockUpdateManyMutationInput!, where: BlockWhereInput): BatchPayload!
   updateManyBlockInstances(data: BlockInstanceUpdateManyMutationInput!, where: BlockInstanceWhereInput): BatchPayload!
   updateManyComments(data: CommentUpdateManyMutationInput!, where: CommentWhereInput): BatchPayload!
-  updateManyTrainingTypes(data: TrainingTypeUpdateManyMutationInput!, where: TrainingTypeWhereInput): BatchPayload!
   updateManyUsers(data: UserUpdateManyMutationInput!, where: UserWhereInput): BatchPayload!
+  updateManyTrainingTypes(data: TrainingTypeUpdateManyMutationInput!, where: TrainingTypeWhereInput): BatchPayload!
   updateManyTracks(data: TrackUpdateManyMutationInput!, where: TrackWhereInput): BatchPayload!
   updateManyExercises(data: ExerciseUpdateManyMutationInput!, where: ExerciseWhereInput): BatchPayload!
   updateManyFormats(data: FormatUpdateManyMutationInput!, where: FormatWhereInput): BatchPayload!
   updateManyExerciseInstances(data: ExerciseInstanceUpdateManyMutationInput!, where: ExerciseInstanceWhereInput): BatchPayload!
   updateManyRatings(data: RatingUpdateManyMutationInput!, where: RatingWhereInput): BatchPayload!
-  deleteManyFiles(where: FileWhereInput): BatchPayload!
   deleteManyTrainings(where: TrainingWhereInput): BatchPayload!
   deleteManyBlocks(where: BlockWhereInput): BatchPayload!
   deleteManyBlockInstances(where: BlockInstanceWhereInput): BatchPayload!
   deleteManyComments(where: CommentWhereInput): BatchPayload!
-  deleteManyTrainingTypes(where: TrainingTypeWhereInput): BatchPayload!
   deleteManyUsers(where: UserWhereInput): BatchPayload!
+  deleteManyTrainingTypes(where: TrainingTypeWhereInput): BatchPayload!
   deleteManyTracks(where: TrackWhereInput): BatchPayload!
   deleteManyExercises(where: ExerciseWhereInput): BatchPayload!
   deleteManyFormats(where: FormatWhereInput): BatchPayload!
@@ -2983,37 +2472,34 @@ enum Permission {
 }
 
 type Query {
-  files(where: FileWhereInput, orderBy: FileOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [File]!
   trainings(where: TrainingWhereInput, orderBy: TrainingOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Training]!
   blocks(where: BlockWhereInput, orderBy: BlockOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Block]!
   blockInstances(where: BlockInstanceWhereInput, orderBy: BlockInstanceOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [BlockInstance]!
   comments(where: CommentWhereInput, orderBy: CommentOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Comment]!
-  trainingTypes(where: TrainingTypeWhereInput, orderBy: TrainingTypeOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [TrainingType]!
   users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]!
+  trainingTypes(where: TrainingTypeWhereInput, orderBy: TrainingTypeOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [TrainingType]!
   tracks(where: TrackWhereInput, orderBy: TrackOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Track]!
   exercises(where: ExerciseWhereInput, orderBy: ExerciseOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Exercise]!
   formats(where: FormatWhereInput, orderBy: FormatOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Format]!
   exerciseInstances(where: ExerciseInstanceWhereInput, orderBy: ExerciseInstanceOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [ExerciseInstance]!
   ratings(where: RatingWhereInput, orderBy: RatingOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Rating]!
-  file(where: FileWhereUniqueInput!): File
   training(where: TrainingWhereUniqueInput!): Training
   block(where: BlockWhereUniqueInput!): Block
   blockInstance(where: BlockInstanceWhereUniqueInput!): BlockInstance
   comment(where: CommentWhereUniqueInput!): Comment
-  trainingType(where: TrainingTypeWhereUniqueInput!): TrainingType
   user(where: UserWhereUniqueInput!): User
+  trainingType(where: TrainingTypeWhereUniqueInput!): TrainingType
   track(where: TrackWhereUniqueInput!): Track
   exercise(where: ExerciseWhereUniqueInput!): Exercise
   format(where: FormatWhereUniqueInput!): Format
   exerciseInstance(where: ExerciseInstanceWhereUniqueInput!): ExerciseInstance
   rating(where: RatingWhereUniqueInput!): Rating
-  filesConnection(where: FileWhereInput, orderBy: FileOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): FileConnection!
   trainingsConnection(where: TrainingWhereInput, orderBy: TrainingOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): TrainingConnection!
   blocksConnection(where: BlockWhereInput, orderBy: BlockOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): BlockConnection!
   blockInstancesConnection(where: BlockInstanceWhereInput, orderBy: BlockInstanceOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): BlockInstanceConnection!
   commentsConnection(where: CommentWhereInput, orderBy: CommentOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): CommentConnection!
-  trainingTypesConnection(where: TrainingTypeWhereInput, orderBy: TrainingTypeOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): TrainingTypeConnection!
   usersConnection(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): UserConnection!
+  trainingTypesConnection(where: TrainingTypeWhereInput, orderBy: TrainingTypeOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): TrainingTypeConnection!
   tracksConnection(where: TrackWhereInput, orderBy: TrackOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): TrackConnection!
   exercisesConnection(where: ExerciseWhereInput, orderBy: ExerciseOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): ExerciseConnection!
   formatsConnection(where: FormatWhereInput, orderBy: FormatOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): FormatConnection!
@@ -3486,13 +2972,12 @@ input RatingWhereUniqueInput {
 }
 
 type Subscription {
-  file(where: FileSubscriptionWhereInput): FileSubscriptionPayload
   training(where: TrainingSubscriptionWhereInput): TrainingSubscriptionPayload
   block(where: BlockSubscriptionWhereInput): BlockSubscriptionPayload
   blockInstance(where: BlockInstanceSubscriptionWhereInput): BlockInstanceSubscriptionPayload
   comment(where: CommentSubscriptionWhereInput): CommentSubscriptionPayload
-  trainingType(where: TrainingTypeSubscriptionWhereInput): TrainingTypeSubscriptionPayload
   user(where: UserSubscriptionWhereInput): UserSubscriptionPayload
+  trainingType(where: TrainingTypeSubscriptionWhereInput): TrainingTypeSubscriptionPayload
   track(where: TrackSubscriptionWhereInput): TrackSubscriptionPayload
   exercise(where: ExerciseSubscriptionWhereInput): ExerciseSubscriptionPayload
   format(where: FormatSubscriptionWhereInput): FormatSubscriptionPayload
@@ -4728,11 +4213,6 @@ input UserCreateManyInput {
   connect: [UserWhereUniqueInput!]
 }
 
-input UserCreateOneInput {
-  create: UserCreateInput
-  connect: UserWhereUniqueInput
-}
-
 input UserCreateOneWithoutCommentsInput {
   create: UserCreateWithoutCommentsInput
   connect: UserWhereUniqueInput
@@ -5166,13 +4646,6 @@ input UserUpdateManyWithWhereNestedInput {
   data: UserUpdateManyDataInput!
 }
 
-input UserUpdateOneRequiredInput {
-  create: UserCreateInput
-  connect: UserWhereUniqueInput
-  update: UserUpdateDataInput
-  upsert: UserUpsertNestedInput
-}
-
 input UserUpdateOneRequiredWithoutCommentsInput {
   create: UserCreateWithoutCommentsInput
   connect: UserWhereUniqueInput
@@ -5218,11 +4691,6 @@ input UserUpdateWithWhereUniqueNestedInput {
   data: UserUpdateDataInput!
 }
 
-input UserUpsertNestedInput {
-  update: UserUpdateDataInput!
-  create: UserCreateInput!
-}
-
 input UserUpsertWithoutCommentsInput {
   update: UserUpdateWithoutCommentsDataInput!
   create: UserCreateWithoutCommentsInput!

+ 0 - 14
backend/datamodel.prisma

@@ -17,20 +17,6 @@ enum Permission {
     INSTRUCTOR
 }
 
-type File {
-    id: ID! @id
-    createdAt: DateTime! @createdAt
-    updatedAt: DateTime! @updatedAt
-    path: String!
-    mimetype: String!
-    user: User!
-    thumbnail: String
-    filename: String!
-    encoding: String!
-    size: Int!
-    comment: String
-}
-
 type Training {
     id: ID! @id
     title: String!

+ 6 - 4
backend/index.ts

@@ -7,14 +7,14 @@ import { merge } from 'lodash'
 import { importSchema } from 'graphql-import'
 import { db, populateUser } from './src/db'
 import { authenticate } from './src/user/authenticate'
-import file from './src/file'
+//import file from './src/file'
 import user from './src/user'
 import history from './src/history'
 //import google from './src/google'
 import training from './src/training'
 
 const resolvers = merge(
-  file.resolvers,
+  //file.resolvers,
   training.resolvers,
   user.resolvers,
   history.resolvers
@@ -48,10 +48,12 @@ const server = new ApolloServer({
   debug: false,
   engine: { debugPrintReports: false }
 } as ApolloServerExpressConfig)
-server.applyMiddleware({ app, cors: corsOptions, path: '/graphql' })
+server.applyMiddleware({ app, cors: corsOptions })
 
 app.listen({ port: process.env.PORT }, () => {
-  console.log(`Server ready 🚀!`)
+  console.log(
+    `Server ready at http://localhost:${process.env.PORT}/${server.graphqlPath} 🚀!`
+  )
 })
 
 export default app

+ 9 - 361
backend/package-lock.json

@@ -1941,14 +1941,6 @@
         "@types/mime": "*"
       }
     },
-    "@types/sharp": {
-      "version": "0.24.0",
-      "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.24.0.tgz",
-      "integrity": "sha512-+0WeyJajTSoIacBzonsq856whNJC+cN9FNEs0yZ6hFq/V1CZmlqM8vBRy7TKZunH+gIO7SwDCzgXYWRRbzqfDA==",
-      "requires": {
-        "@types/node": "*"
-      }
-    },
     "@types/stack-utils": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
@@ -2743,11 +2735,6 @@
         "tslib": "^1.9.3"
       }
     },
-    "aproba": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
-      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
-    },
     "arch": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz",
@@ -2791,15 +2778,6 @@
         }
       }
     },
-    "are-we-there-yet": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
-      "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
-      "requires": {
-        "delegates": "^1.0.0",
-        "readable-stream": "^2.0.6"
-      }
-    },
     "argparse": {
       "version": "1.0.10",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -3642,7 +3620,8 @@
     "chownr": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz",
-      "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw=="
+      "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==",
+      "dev": true
     },
     "ci-info": {
       "version": "2.0.0",
@@ -3790,15 +3769,6 @@
         "object-visit": "^1.0.0"
       }
     },
-    "color": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz",
-      "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==",
-      "requires": {
-        "color-convert": "^1.9.1",
-        "color-string": "^1.5.2"
-      }
-    },
     "color-convert": {
       "version": "1.9.3",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -3812,15 +3782,6 @@
       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
       "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
     },
-    "color-string": {
-      "version": "1.5.3",
-      "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz",
-      "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==",
-      "requires": {
-        "color-name": "^1.0.0",
-        "simple-swizzle": "^0.2.2"
-      }
-    },
     "colors": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
@@ -4122,11 +4083,6 @@
         "xdg-basedir": "^3.0.0"
       }
     },
-    "console-control-strings": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
-      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
-    },
     "constant-case": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-2.0.0.tgz",
@@ -4632,11 +4588,6 @@
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
       "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
     },
-    "delegates": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
-      "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
-    },
     "depd": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -4652,11 +4603,6 @@
       "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
       "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
     },
-    "detect-libc": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
-      "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
-    },
     "detect-newline": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -5402,11 +5348,6 @@
         }
       }
     },
-    "expand-template": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
-      "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
-    },
     "expand-tilde": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
@@ -5875,6 +5816,7 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.0.0.tgz",
       "integrity": "sha512-40Qz+LFXmd9tzYVnnBmZvFfvAADfUA14TXPK1s7IfElJTIZ97rA8w4Kin7Wt5JBrC3ShnnFJO/5vPjPEeJIq9A==",
+      "dev": true,
       "requires": {
         "minipass": "^3.0.0"
       }
@@ -6442,54 +6384,6 @@
       "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
       "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc="
     },
-    "gauge": {
-      "version": "2.7.4",
-      "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
-      "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
-      "requires": {
-        "aproba": "^1.0.3",
-        "console-control-strings": "^1.0.0",
-        "has-unicode": "^2.0.0",
-        "object-assign": "^4.1.0",
-        "signal-exit": "^3.0.0",
-        "string-width": "^1.0.1",
-        "strip-ansi": "^3.0.1",
-        "wide-align": "^1.1.0"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
-        },
-        "is-fullwidth-code-point": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
-          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
-          "requires": {
-            "number-is-nan": "^1.0.0"
-          }
-        },
-        "string-width": {
-          "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
-          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
-          "requires": {
-            "code-point-at": "^1.0.0",
-            "is-fullwidth-code-point": "^1.0.0",
-            "strip-ansi": "^3.0.0"
-          }
-        },
-        "strip-ansi": {
-          "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
-          "requires": {
-            "ansi-regex": "^2.0.0"
-          }
-        }
-      }
-    },
     "gensync": {
       "version": "1.0.0-beta.1",
       "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
@@ -6524,11 +6418,6 @@
         "assert-plus": "^1.0.0"
       }
     },
-    "github-from-package": {
-      "version": "0.0.0",
-      "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
-      "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
-    },
     "glob": {
       "version": "7.1.5",
       "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz",
@@ -7141,11 +7030,6 @@
       "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
       "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q="
     },
-    "has-unicode": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
-      "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
-    },
     "has-value": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
@@ -10512,6 +10396,7 @@
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz",
       "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==",
+      "dev": true,
       "requires": {
         "yallist": "^4.0.0"
       },
@@ -10519,7 +10404,8 @@
         "yallist": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+          "dev": true
         }
       }
     },
@@ -10527,6 +10413,7 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz",
       "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==",
+      "dev": true,
       "requires": {
         "minipass": "^3.0.0",
         "yallist": "^4.0.0"
@@ -10535,7 +10422,8 @@
         "yallist": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+          "dev": true
         }
       }
     },
@@ -10566,11 +10454,6 @@
         "minimist": "0.0.8"
       }
     },
-    "mkdirp-classic": {
-      "version": "0.5.2",
-      "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz",
-      "integrity": "sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g=="
-    },
     "mongodb": {
       "version": "3.3.3",
       "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.3.3.tgz",
@@ -10647,11 +10530,6 @@
         "to-regex": "^3.0.1"
       }
     },
-    "napi-build-utils": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
-      "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
-    },
     "natural-compare": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -10675,26 +10553,6 @@
         "lower-case": "^1.1.1"
       }
     },
-    "node-abi": {
-      "version": "2.15.0",
-      "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.15.0.tgz",
-      "integrity": "sha512-FeLpTS0F39U7hHZU1srAK4Vx+5AHNVOTP+hxBNQknR/54laTHSFIJkDWDqiquY1LeLUgTfPN7sLPhMubx0PLAg==",
-      "requires": {
-        "semver": "^5.4.1"
-      },
-      "dependencies": {
-        "semver": {
-          "version": "5.7.1",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
-          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
-        }
-      }
-    },
-    "node-addon-api": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz",
-      "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA=="
-    },
     "node-emoji": {
       "version": "1.10.0",
       "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz",
@@ -10800,11 +10658,6 @@
         }
       }
     },
-    "noop-logger": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
-      "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI="
-    },
     "nopt": {
       "version": "1.0.10",
       "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
@@ -10910,17 +10763,6 @@
         "which": "^1.2.10"
       }
     },
-    "npmlog": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
-      "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
-      "requires": {
-        "are-we-there-yet": "~1.1.2",
-        "console-control-strings": "~1.1.0",
-        "gauge": "~2.7.3",
-        "set-blocking": "~2.0.0"
-      }
-    },
     "number-is-nan": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
@@ -11776,35 +11618,6 @@
         "xtend": "^4.0.0"
       }
     },
-    "prebuild-install": {
-      "version": "5.3.3",
-      "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz",
-      "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==",
-      "requires": {
-        "detect-libc": "^1.0.3",
-        "expand-template": "^2.0.3",
-        "github-from-package": "0.0.0",
-        "minimist": "^1.2.0",
-        "mkdirp": "^0.5.1",
-        "napi-build-utils": "^1.0.1",
-        "node-abi": "^2.7.0",
-        "noop-logger": "^0.1.1",
-        "npmlog": "^4.0.1",
-        "pump": "^3.0.0",
-        "rc": "^1.2.7",
-        "simple-get": "^3.0.3",
-        "tar-fs": "^2.0.0",
-        "tunnel-agent": "^0.6.0",
-        "which-pm-runs": "^1.0.0"
-      },
-      "dependencies": {
-        "minimist": {
-          "version": "1.2.5",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
-          "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
-        }
-      }
-    },
     "prelude-ls": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@@ -13336,52 +13149,6 @@
         "safe-buffer": "^5.0.1"
       }
     },
-    "sharp": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.25.2.tgz",
-      "integrity": "sha512-l1GN0kFNtJr3U9i9pt7a+vo2Ij0xv4tTKDIPx8W6G9WELhPwrMyZZJKAAQNBSI785XB4uZfS5Wpz8C9jWV4AFQ==",
-      "requires": {
-        "color": "^3.1.2",
-        "detect-libc": "^1.0.3",
-        "node-addon-api": "^2.0.0",
-        "npmlog": "^4.1.2",
-        "prebuild-install": "^5.3.3",
-        "semver": "^7.1.3",
-        "simple-get": "^3.1.0",
-        "tar": "^6.0.1",
-        "tunnel-agent": "^0.6.0"
-      },
-      "dependencies": {
-        "mkdirp": {
-          "version": "1.0.4",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
-          "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
-        },
-        "semver": {
-          "version": "7.2.2",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.2.2.tgz",
-          "integrity": "sha512-Zo84u6o2PebMSK3zjJ6Zp5wi8VnQZnEaCP13Ul/lt1ANsLACxnJxq4EEm1PY94/por1Hm9+7xpIswdS5AkieMA=="
-        },
-        "tar": {
-          "version": "6.0.1",
-          "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.1.tgz",
-          "integrity": "sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q==",
-          "requires": {
-            "chownr": "^1.1.3",
-            "fs-minipass": "^2.0.0",
-            "minipass": "^3.0.0",
-            "minizlib": "^2.1.0",
-            "mkdirp": "^1.0.3",
-            "yallist": "^4.0.0"
-          }
-        },
-        "yallist": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-          "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
-        }
-      }
-    },
     "shebang-command": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
@@ -13412,11 +13179,6 @@
       "resolved": "https://registry.npmjs.org/sillyname/-/sillyname-0.1.0.tgz",
       "integrity": "sha1-z9mIWOJJhnE0d3Xv47tRQfRsh9Y="
     },
-    "simple-concat": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
-      "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
-    },
     "simple-errors": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/simple-errors/-/simple-errors-1.0.1.tgz",
@@ -13425,46 +13187,6 @@
         "errno": "^0.1.1"
       }
     },
-    "simple-get": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
-      "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
-      "requires": {
-        "decompress-response": "^4.2.0",
-        "once": "^1.3.1",
-        "simple-concat": "^1.0.0"
-      },
-      "dependencies": {
-        "decompress-response": {
-          "version": "4.2.1",
-          "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
-          "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
-          "requires": {
-            "mimic-response": "^2.0.0"
-          }
-        },
-        "mimic-response": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
-          "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
-        }
-      }
-    },
-    "simple-swizzle": {
-      "version": "0.2.2",
-      "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
-      "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
-      "requires": {
-        "is-arrayish": "^0.3.1"
-      },
-      "dependencies": {
-        "is-arrayish": {
-          "version": "0.3.2",
-          "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
-          "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
-        }
-      }
-    },
     "sisteransi": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -14164,67 +13886,6 @@
         }
       }
     },
-    "tar-fs": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz",
-      "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==",
-      "requires": {
-        "chownr": "^1.1.1",
-        "mkdirp-classic": "^0.5.2",
-        "pump": "^3.0.0",
-        "tar-stream": "^2.0.0"
-      },
-      "dependencies": {
-        "bl": {
-          "version": "4.0.2",
-          "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
-          "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
-          "requires": {
-            "buffer": "^5.5.0",
-            "inherits": "^2.0.4",
-            "readable-stream": "^3.4.0"
-          },
-          "dependencies": {
-            "inherits": {
-              "version": "2.0.4",
-              "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-              "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
-            }
-          }
-        },
-        "buffer": {
-          "version": "5.5.0",
-          "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz",
-          "integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==",
-          "requires": {
-            "base64-js": "^1.0.2",
-            "ieee754": "^1.1.4"
-          }
-        },
-        "readable-stream": {
-          "version": "3.6.0",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
-          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
-          "requires": {
-            "inherits": "^2.0.3",
-            "string_decoder": "^1.1.1",
-            "util-deprecate": "^1.0.1"
-          }
-        },
-        "tar-stream": {
-          "version": "2.1.2",
-          "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz",
-          "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==",
-          "requires": {
-            "bl": "^4.0.1",
-            "end-of-stream": "^1.4.1",
-            "fs-constants": "^1.0.0",
-            "inherits": "^2.0.3",
-            "readable-stream": "^3.1.1"
-          }
-        }
-      }
-    },
     "tar-stream": {
       "version": "1.6.2",
       "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz",
@@ -15198,19 +14859,6 @@
       "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
       "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
     },
-    "which-pm-runs": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
-      "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
-    },
-    "wide-align": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
-      "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
-      "requires": {
-        "string-width": "^1.0.2 || 2"
-      }
-    },
     "widest-line": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",

+ 0 - 2
backend/package.json

@@ -19,7 +19,6 @@
     "@types/jsonwebtoken": "^8.3.8",
     "@types/lodash": "^4.14.149",
     "@types/randombytes": "^2.0.0",
-    "@types/sharp": "^0.24.0",
     "apollo-server-express": "2.11.0",
     "bcryptjs": "^2.4.3",
     "body-parser": "1.19.0",
@@ -35,7 +34,6 @@
     "prisma-binding": "2.3.16",
     "promisify": "0.0.3",
     "randombytes": "^2.1.0",
-    "sharp": "^0.25.2",
     "standard": "14.3.1"
   },
   "devDependencies": {

+ 0 - 17
backend/schema.graphql

@@ -1,19 +1,6 @@
 # import * from './database/generated/prisma.graphql'
 
-scalar Upload
-
-type FsFile {
-  filename: String!
-  path: String!
-  size: Int!
-  ctime: DateTime!
-  mtime: DateTime!
-}
-
 type Query {
-  # File module
-  fsFiles(directory: String!): [FsFile!]!
-  files: [File!]!
   currentUser: User!
   user(where: UserWhereUniqueInput!): User
   users(
@@ -35,7 +22,6 @@ type Query {
     first: Int
     last: Int
   ): [Training!]!
-  publishedTrainings: [Training!]!
   trainingType(where: TrainingTypeWhereUniqueInput!): TrainingType
   trainingTypes(
     where: TrainingTypeWhereInput
@@ -79,9 +65,6 @@ type Query {
 }
 
 type Mutation {
-  # File module
-  uploadFile(file: Upload!, comment: String): File!
-
   createUser(data: UserCreateInput!): User!
   updateUser(email: String!, data: UserUpdateInput!): User
   deleteUser(email: String!): User

+ 0 - 3
backend/src/file/constants.ts

@@ -1,3 +0,0 @@
-export const tmpDir = 'tmp'
-export const uploadDir = 'upload_files'
-export const thumbnails = 'thumbnails'

+ 0 - 3
backend/src/file/index.ts

@@ -1,3 +0,0 @@
-import { resolvers } from './resolvers'
-
-export default { resolvers }

+ 0 - 118
backend/src/file/resolvers.ts

@@ -1,118 +0,0 @@
-import { IResolvers } from 'apollo-server-express'
-import fs from 'fs'
-import randombytes from 'randombytes'
-import sharp from 'sharp'
-import { tmpDir, uploadDir, thumbnails } from './constants'
-import user from '../user'
-
-export const resolvers: IResolvers = {
-  Query: {
-    fsFiles: async (parent, { directory }, context, info) => {
-      user.checkPermission(context, 'ADMIN')
-      const data = await fsFiles(directory)
-      return data
-    },
-    files: (parent, args, context, info) => {
-      user.checkPermission(context, 'ADMIN')
-      return context.db.query.files(args, info)
-    }
-  },
-  Mutation: {
-    uploadFile: async (parent, { comment, file }, context, info) => {
-      user.checkPermission(context, 'ADMIN')
-      const fileInfo = await uploadFile(file)
-
-      return context.db.mutation.createFile(
-        {
-          data: {
-            ...fileInfo,
-            comment,
-            user: { connect: { id: context.req.userId } }
-          }
-        },
-        info
-      )
-    }
-  }
-}
-
-async function fsFiles(directory: string) {
-  const fileList = await fs.promises.readdir(directory)
-  return Promise.all(
-    fileList.map(async filename => {
-      const path = `${directory}/${filename}`
-      const { size, ctime, mtime } = await fs.promises.stat(path)
-      return {
-        filename,
-        path,
-        size,
-        ctime,
-        mtime
-      }
-    })
-  )
-}
-
-async function uploadFile(file: any) {
-  const { createReadStream, filename, mimetype, encoding } = await file
-  const stream = createReadStream()
-
-  const fsFilename = randombytes(16).toString('hex')
-  const tmpPath = `${tmpDir}/${fsFilename}`
-  const path = `${uploadDir}/${fsFilename}`
-  const thumbnailPath = `${thumbnails}/${fsFilename}`
-  await new Promise((resolve, reject) => {
-    const file = fs.createWriteStream(tmpPath)
-    file.on('finish', resolve)
-    file.on('error', error => {
-      fs.unlink(tmpPath, () => {
-        reject(error)
-      })
-    })
-    stream.on('error', (error: any) => file.destroy(error))
-    stream.pipe(file)
-  })
-
-  if (mimetype.startsWith('image/')) {
-    try {
-      await processImage(tmpPath, path)
-      await createThumbnail(tmpPath, thumbnailPath)
-      await fs.promises.unlink(tmpPath)
-    } catch (error) {
-      try {
-        await fs.promises.unlink(tmpPath)
-        await fs.promises.unlink(path)
-      } catch (ignore) {}
-      throw error
-    }
-  }
-
-  const { size } = await fs.promises.stat(path)
-
-  return {
-    filename,
-    path,
-    mimetype,
-    encoding,
-    size
-  }
-}
-
-function processImage(tmpFile: string, outputFile: string) {
-  return sharp(tmpFile)
-    .resize(1600, 1600, {
-      fit: sharp.fit.inside,
-      withoutEnlargement: true
-    })
-    .jpeg()
-    .toFile(outputFile)
-}
-
-function createThumbnail(tmpFile: string, thumbnail: string) {
-  return sharp(tmpFile)
-    .resize(200, 200, {
-      fit: sharp.fit.inside
-    })
-    .jpeg()
-    .toFile(thumbnail)
-}

+ 2 - 12
backend/src/training/resolvers.ts

@@ -12,15 +12,9 @@ export const resolvers: IResolvers = {
       return context.db.query.training({ where: args }, info)
     },
     trainings: async (parent, args, context, info) => {
-      checkPermission(context, ['INSTRUCTOR', 'ADMIN'])
-      return context.db.query.trainings({}, info)
-    },
-    publishedTrainings: async (parent, args, context, info) => {
       checkPermission(context)
-      return context.db.query.trainings(
-        { where: { published: true }, orderBy: 'trainingDate_DESC', first: 20 },
-        info
-      )
+      console.log(info)
+      return context.db.query.trainings({}, info)
     },
     trainingTypes: async (parent, args, context, info) => {
       checkPermission(context)
@@ -33,10 +27,6 @@ export const resolvers: IResolvers = {
     formats: async (parent, args, context, info) => {
       checkPermission(context)
       return context.db.query.formats({}, info)
-    },
-    exercises: async (parent, args, context, info) => {
-      checkPermission(context)
-      return context.db.query.exercises({}, info)
     }
   },
 

+ 3 - 3
frontend/initial-data.ts

@@ -1,6 +1,6 @@
-import { TTraining } from './src/training/types'
+import { ITraining } from './src/training/types'
 
-const data: { trainings: any[]; polls: any } = {
+const data: { trainings: ITraining[]; polls: any } = {
   trainings: [
     {
       id: 'training0',
@@ -20,7 +20,7 @@ const data: { trainings: any[]; polls: any } = {
       blocks: [
         {
           id: 'block0',
-          order: 0,
+          sequence: 0,
           title: 'Drop Sets',
           repetitions: 1,
           rest: 25,

+ 0 - 10
frontend/jest.config.js

@@ -1,10 +0,0 @@
-module.exports = {
-  testEnvironment: 'node',
-  preset: 'ts-jest',
-  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
-  transform: { '^.+\\.(t|j)sx?$': 'ts-jest' },
-  testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
-  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
-  testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/'],
-  globals: { 'ts-jest': { 'ts-config': '<rootDir>/tsconfig.jest.json' } }
-}

File diff suppressed because it is too large
+ 264 - 309
frontend/package-lock.json


+ 20 - 23
frontend/package.json

@@ -8,29 +8,26 @@
     "start": "next start",
     "test": "jest",
     "test:watch": "npm run test -- --watchAll",
-    "test:coverage": "npm run test -- --coverage",
     "type-check": "tsc"
   },
   "dependencies": {
-    "@apollo/client": "3.0.0-beta.43",
-    "@apollo/react-ssr": "^3.1.4",
-    "@fortawesome/fontawesome-svg-core": "^1.2.28",
-    "@fortawesome/free-solid-svg-icons": "^5.13.0",
-    "@fortawesome/react-fontawesome": "^0.1.9",
+    "@apollo/client": "3.0.0-beta.41",
+    "@apollo/react-ssr": "^3.1.3",
     "@types/howler": "^2.1.2",
+    "@types/jest": "24.9.1",
     "@types/lodash": "^4.14.149",
     "@types/react-onclickoutside": "^6.7.3",
     "@types/styled-jsx": "^2.2.8",
-    "@types/video.js": "7.3.7",
+    "@types/video.js": "7.3.6",
     "apollo-boost": "0.4.7",
-    "apollo-link": "^1.2.14",
-    "apollo-link-error": "^1.1.13",
+    "apollo-link": "^1.2.13",
+    "apollo-link-error": "^1.1.12",
     "array-move": "^2.2.1",
-    "date-fns": "^2.12.0",
+    "date-fns": "^2.11.1",
     "dotenv": "^8.2.0",
     "formik": "2.1.4",
-    "fuse.js": "5.1.0",
-    "graphql": "15.0.0",
+    "fuse.js": "3.4.5",
+    "graphql": "14.6.0",
     "howler": "^2.1.3",
     "isomorphic-unfetch": "^3.0.0",
     "lodash": "^4.17.15",
@@ -44,29 +41,26 @@
     "react-sortable-hoc": "^1.11.0",
     "standard": "14.3.3",
     "video.js": "^7.7.5",
-    "yup": "^0.28.3"
+    "yup": "^0.27.0"
   },
   "devDependencies": {
-    "@apollo/react-testing": "^3.1.4",
+    "@apollo/react-testing": "^3.1.3",
     "@babel/core": "7.9.0",
-    "@babel/preset-env": "7.9.5",
+    "@babel/preset-env": "7.9.0",
     "@babel/preset-react": "7.9.4",
-    "@testing-library/react": "10.0.2",
+    "@testing-library/react": "9.5.0",
     "@testing-library/react-hooks": "^3.2.1",
     "@types/enzyme": "^3.10.5",
-    "@types/jest": "^25.2.1",
-    "@types/react": "16.9.34",
-    "@types/yup": "0.26.35",
+    "@types/react": "16.9.31",
+    "@types/yup": "0.26.34",
     "@zeit/next-typescript": "^1.1.1",
     "babel-eslint": "10.1.0",
-    "babel-jest": "^25.3.0",
+    "babel-jest": "^24.9.0",
     "enzyme": "^3.11.0",
     "enzyme-adapter-react-16": "^1.15.2",
-    "jest": "^25.3.0",
+    "jest": "^24.9.0",
     "jest-transform-graphql": "^2.1.0",
-    "npm-check-updates": "^4.1.2",
     "react-test-renderer": "16.13.1",
-    "ts-jest": "^25.3.1",
     "typescript": "3.8.3"
   },
   "jest": {
@@ -82,5 +76,8 @@
       ".*": "babel-jest",
       "^.+\\.js?$": "babel-jest"
     }
+  },
+  "standard": {
+    "parser": "babel-eslint"
   }
 }

+ 0 - 11
frontend/pages/admin/index.tsx

@@ -1,11 +0,0 @@
-import Link from 'next/link'
-
-const AdminPage = () => {
-  return (
-    <Link href='training'>
-      <a>Training</a>
-    </Link>
-  )
-}
-
-export default AdminPage

+ 1 - 0
frontend/pages/admin/training/[id].tsx

@@ -11,6 +11,7 @@ const EditTrainingPage = () => {
 
   if (loading) return <p>Loading data...</p>
   if (error) return <p>Error loading data.</p>
+  console.log(data?.training)
   if (data?.training) return <EditTraining training={data.training} />
   else return <p>Training {id} not found.</p>
 }

+ 4 - 10
frontend/pages/index.tsx

@@ -1,11 +1,11 @@
 import Link from 'next/link'
 
-//import initialData from '../initial-data'
-import { usePublishedTrainingsQuery } from '../src/gql'
+import initialData from '../initial-data'
+import { useTrainingsQuery } from '../src/gql'
 import { Training } from '../src/training'
 
 const Home = () => {
-  const { data, error, loading } = usePublishedTrainingsQuery()
+  //const { data, error, loading } = useTrainingsQuery();
 
   return (
     <>
@@ -25,13 +25,7 @@ const Home = () => {
       </section>
 
       <section id='nextTraining'>
-        {loading && <p>Loading trainings...</p>}
-        {error && <p>Error loading trainings: {error.message}</p>}
-        {data?.publishedTrainings && data.publishedTrainings.length > 0 ? (
-          <Training training={data.publishedTrainings[0]} />
-        ) : (
-          <p>No trainings found.</p>
-        )}
+        <Training training={initialData.trainings[0]} />
       </section>
 
       <style jsx>

+ 8 - 0
frontend/pages/timer.tsx

@@ -0,0 +1,8 @@
+import initialData from '../initial-data'
+import { Timer } from '../src/timer'
+
+const TimerPage = () => {
+  return <Timer training={initialData.trainings[0]} />
+}
+
+export default TimerPage

+ 0 - 19
frontend/pages/timer/[id].tsx

@@ -1,19 +0,0 @@
-//import initialData from '../../initial-data'
-import { Timer } from '../../src/timer'
-import { useRouter } from 'next/router'
-import { useTrainingQuery } from '../../src/gql'
-
-const TimerPage = () => {
-  const router = useRouter()
-  const { id } = router.query
-
-  const { data, error, loading } = useTrainingQuery({
-    variables: { id: typeof id === 'string' ? id : id[0] }
-  })
-
-  if (loading) return <p>Loading data...</p>
-  if (error) return <p>Error loading data.</p>
-  if (data?.training) return <Timer training={data.training} />
-}
-
-export default TimerPage

+ 2 - 10
frontend/pages/training/[id].tsx

@@ -1,18 +1,10 @@
+import EditTraining from '../../src/training/components/EditTraining'
 import { useRouter } from 'next/router'
-import { Training } from '../../src/training'
-import { useTrainingQuery } from '../../src/gql'
 
 const TrainingPage = () => {
   const router = useRouter()
-  const { id } = router.query
-  const { data, error, loading } = useTrainingQuery({
-    variables: { id: typeof id === 'string' ? id : id[0] }
-  })
 
-  if (loading) return <p>Loading data...</p>
-  if (error) return <p>Error loading data.</p>
-  if (data?.training) return <Training training={data.training} />
-  else return <p>Training {id} not found.</p>
+  return <p>Nothing here yet...</p>
 }
 
 export default TrainingPage

+ 1 - 1
frontend/src/app/components/Footer.tsx

@@ -6,7 +6,7 @@ const Footer = () => (
     <style jsx>{`
       footer {
         text-align: center;
-        background-color: ${theme.colors.lightgrey};
+        background-color: ${theme.colors.darkgrey};
         color: ${theme.colors.offWhite};
       }
     `}</style>

+ 1 - 24
frontend/src/app/components/Header.tsx

@@ -1,36 +1,13 @@
 import Logo from './Logo'
 import Link from 'next/link'
-import Nav from './Nav'
-import theme from '../../styles/theme'
 
 const Header = () => (
   <header>
     <Logo />
-    <Nav />
 
     <style jsx>{`
       header {
-        position: relative;
-        text-align: center;
-        background-color: #efefef;
-        width: 100%;
-        z-index: 999;
-        box-shadow: ${theme.bs};
-      }
-
-      @media screen and (min-width: 768px) {
-        header {
-          display: grid;
-          grid-template-columns: 1fr auto minmax(670px, 1fr) 1fr;
-        }
-
-        :global(.logo) {
-          grid-column: 2;
-        }
-
-        :global(nav) {
-          grid-column: 3;
-        }
+        padding: 20px;
       }
     `}</style>
   </header>

+ 30 - 49
frontend/src/app/components/Logo.tsx

@@ -1,57 +1,38 @@
 import Link from 'next/link'
 
 const Logo = () => (
-  <h1 className='logo'>
-    <Link href='/'>
-      <a>
-        <span className='logo-circle'>u</span>
-        <span className='logo-text'>fit</span>
-      </a>
-    </Link>
-    <style jsx>
-      {`
-        .logo {
-          margin: 10px auto;
-        }
-        .logo span {
-          padding-top: 0.3em;
-          font-size: 40px;
-        }
-        .logo-circle {
-          position: relative;
-          display: inline-block;
-          text-align: right;
-          width: 1.55em;
-          height: 1.55em;
-          background-color: red;
-          border-radius: 50%;
-          font-weight: 900;
-          padding-right: 0.2em;
-        }
-        .logo-circle::before {
-          position: absolute;
-          content: '';
-          display: block;
-          width: 0.2em;
-          height: 0.2em;
-          border-radius: 50%;
-          background-color: white;
-          right: 55%;
-          bottom: 55%;
-        }
+  <Link href='/'>
+    <a>
+      <div id='logo'>
+        <span id='circle'>˙u</span>
+        <span id='text'>fit</span>
+      </div>
+      <style jsx>
+        {`
+          #logo {
+            position: relative;
+            height: 60px;
+            width: 60px;
+            color: white;
+            background-color: red;
+            border-radius: 30px;
+            font-size: 40px;
+            font-weight: 900;
+            padding: 10px 0 0 15px;
+          }
 
-        a {
-          text-decoration: none;
-          color: white;
-        }
+          a {
+            text-decoration: none;
+          }
 
-        .logo .logo-text {
-          color: black;
-          margin-left: 0em;
-        }
-      `}
-    </style>
-  </h1>
+          #text {
+            color: black;
+            margin-left: 3px;
+          }
+        `}
+      </style>
+    </a>
+  </Link>
 )
 
 export default Logo

+ 51 - 171
frontend/src/app/components/Nav.tsx

@@ -1,188 +1,68 @@
 import Link from 'next/link'
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { faCalendarAlt, faStopwatch20 } from '@fortawesome/free-solid-svg-icons'
 
+import { UserNav } from '../../../src/user/user'
 import theme from '../../styles/theme'
-import UserNav from '../../user/components/UserNav'
 
-const Nav = () => {
-  return (
-    <>
-      <input type='checkbox' id='nav-toggle' className='nav-toggle' />
-      <label htmlFor='nav-toggle' className='nav-toggle-label'>
-        <span></span>
-      </label>
-      <nav>
-        <ul>
-          <li>
-            <Link href='/trainings'>
-              <a>
-                <FontAwesomeIcon icon={faCalendarAlt} />
-                Archive
-              </a>
-            </Link>
-          </li>
-          <li>
-            <Link href='/timer'>
-              <a>
-                <FontAwesomeIcon icon={faStopwatch20} />
-                Timer
-              </a>
-            </Link>
-          </li>
-          <UserNav />
-        </ul>
-      </nav>
-
-      <style jsx>{`
-        nav {
-          max-height: 0;
-          overflow: hidden;
-          transition: max-height 400ms ease-in-out;
-          position: absolute;
-          text-align: left;
-          top: 100%;
-          left: 0;
-          width: 100%;
-          background-color: ${theme.colors.nav};
-          align-content: end;
-        }
-
-        .nav-toggle:checked ~ nav {
-          max-height: 100vh;
-        }
-
-        nav ul {
-          margin: 0;
-          padding: 0;
-          list-style: none;
-        }
-
-        :global(nav li) {
-          margin: 0 0.2em;
-          padding: 0.6em 1.5em;
-          border-bottom: 1px solid #696969e7;
-        }
-
-        :global(nav svg) {
-          width: 0.8em;
-          margin-right: 0.7em;
-        }
-
-        :global(nav a) {
-          color: ${theme.colors.offWhite};
-          text-decoration: none;
-          font-size: 1.2rem;
-          text-transform: uppercase;
-        }
-
-        .nav-toggle {
-          display: none;
-        }
-
-        .nav-toggle-label {
-          position: absolute;
-          top: 0;
-          left: 0;
-          margin-left: 1em;
-          height: 100%;
-          display: flex;
-          align-items: center;
-        }
-
-        .nav-toggle-label span {
+const Nav = () => (
+  <nav>
+    <ul>
+      <li>
+        <Link href='/'>
+          <a>Home</a>
+        </Link>
+      </li>
+      <li>
+        <Link href='/training'>
+          <a>Training</a>
+        </Link>
+      </li>
+      <li>
+        <Link href='/login'>
+          <a>Login</a>
+        </Link>
+      </li>
+      <li>
+        <Link href='/signup'>
+          <a>Sign up</a>
+        </Link>
+      </li>
+      <li id='user'>
+        <UserNav />
+      </li>
+    </ul>
+
+    <style jsx>
+      {`
+        ul {
           display: grid;
-          align-items: center;
-          border: 2px solid #5d6a6bff;
-          transition: all 400ms ease-in-out;
-          height: 4px;
-          width: 2em;
-          border-radius: 2px;
-          margin: 0;
-          padding: 0;
-        }
-        .nav-toggle-label span::before,
-        .nav-toggle-label span::after {
-          content: '';
-          height: 4px;
-          background-color: #5d6a6bff;
-          border-radius: 2px;
-          position: absolute;
-          left: 0.2em;
-          right: 0.2em;
-          transform: rotate(0deg);
-          transition: all 200ms ease-in-out 200ms;
-        }
-        .nav-toggle-label span::before {
-          transform: translate(0, -8px);
-        }
-        .nav-toggle-label span::after {
-          transform: translate(0, 8px);
-        }
-
-        .nav-toggle:checked ~ .nav-toggle-label span {
-          height: 2em;
-          width: 2em;
-          background-color: #5d6a6b00;
-          border: 2px solid #5d6a6b55;
-          border-radius: 0px;
         }
 
-        .nav-toggle:checked ~ .nav-toggle-label span::before {
-          transform: rotate(45deg) translate(0);
+        li {
+          padding: 0 0.5em;
+          border-bottom: 1px solid ${theme.colors.lightgrey};
         }
 
-        .nav-toggle:checked ~ .nav-toggle-label span::after {
-          transform: rotate(-45deg) translate(0);
-        }
-
-        @media screen and (min-width: 768px) {
-          .nav-toggle-label {
-            display: none;
+        @media (min-width: 500px) {
+          ul {
+            grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+            border-bottom: 1px solid ${theme.colors.lightgrey};
           }
-          nav {
-            all: unset;
-            display: flex;
-            align-items: center;
-            justify-content: flex-end;
-          }
-
-          :global(nav li) {
-            display: inline-block;
+          li {
+            display: inline;
             border-bottom: none;
-            position: relative;
-            text-align: center;
-          }
-
-          :global(nav li::after) {
-            content: '';
-            display: block;
-            position: absolute;
-            height: 1px;
-            left: 10%;
-            right: 10%;
-            transform: scale(0, 1);
-            transition: all 300ms ease-in-out;
-            bottom: 0;
-            background-color: ${theme.colors.darkerblue};
-          }
-
-          :global(nav li:hover::after) {
-            background-color: ${theme.colors.blue};
-            transform: scale(1, 1);
           }
+        }
 
-          :global(nav a) {
-            color: ${theme.colors.darkerblue};
-          }
+        #search {
+          grid-column-end: -2;
+        }
 
-          :global(nav a:hover) {
-            color: ${theme.colors.blue};
-          }
+        #user {
+          grid-column-end: -1;
         }
-      `}</style>
-    </>
-  )
-}
+      `}
+    </style>
+  </nav>
+)
 
 export default Nav

+ 2 - 0
frontend/src/app/components/Page.tsx

@@ -1,5 +1,7 @@
+import Head from 'next/head'
 import Header from './Header'
 import Meta from './Meta'
+import Nav from './Nav'
 import Footer from './Footer'
 import GlobalStyle from '../../styles/global'
 

+ 5 - 14
frontend/src/form/__tests__/useFormHandler.test.tsx

@@ -3,26 +3,17 @@ import { renderHook } from '@testing-library/react-hooks'
 import { useFormHandler } from '../useFormHandler'
 
 describe('form hook return values', () => {
+
   const values = {
     text: 'sample-text',
     number: 42,
     boolean: true,
     textArray: ['element1', 'element2'],
-    objectArray: [
-      { text: 'sample1', boolean: true },
-      { text: 'sample2', boolean: false }
-    ],
-    object: {
-      text: 'sample',
-      array: [12, 13],
-      nestedObject: { text: 'nested-sample', number: 18 }
-    }
+    objectArray: [{ text: 'sample1', boolean: true }, { text: 'sample2', boolean: false }],
+    object: { text: 'sample', array: [12, 13], nestedObject: { text: 'nested-sample', number: 18 } }
   }
 
-  const Component = () =>
-    useFormHandler(values, values => {
-      return {}
-    })
+  const Component = () => useFormHandler(values, values => { return {} })
   const { result } = renderHook(Component)
 
   it('returns correct initial states.', () => {
@@ -48,4 +39,4 @@ describe('form hook return values', () => {
   })
 })
 
-export default true
+export default true

+ 1 - 1
frontend/src/form/components/TextInput.tsx

@@ -22,7 +22,7 @@ const TextInput = ({
       const newValue = {
         target: {
           type: 'custom',
-          value: parseInt(event.target.value) ?? undefined,
+          value: parseInt(event.target.value),
           name: event.target.name
         }
       }

+ 21 - 312
frontend/src/gql/index.tsx

@@ -12,7 +12,6 @@ export type Scalars = {
   Int: number,
   Float: number,
   DateTime: any,
-  Upload: any,
 };
 
 export type Block = Node & {
@@ -1359,20 +1358,6 @@ export type ExerciseWhereUniqueInput = {
   id?: Maybe<Scalars['ID']>,
 };
 
-export type File = Node & {
-  id: Scalars['ID'],
-  createdAt: Scalars['DateTime'],
-  updatedAt: Scalars['DateTime'],
-  path: Scalars['String'],
-  mimetype: Scalars['String'],
-  user: User,
-  thumbnail?: Maybe<Scalars['String']>,
-  filename: Scalars['String'],
-  encoding: Scalars['String'],
-  size: Scalars['Int'],
-  comment?: Maybe<Scalars['String']>,
-};
-
 export type Format = Node & {
   id: Scalars['ID'],
   name: Scalars['String'],
@@ -1510,16 +1495,7 @@ export type FormatWhereUniqueInput = {
   id?: Maybe<Scalars['ID']>,
 };
 
-export type FsFile = {
-  filename: Scalars['String'],
-  path: Scalars['String'],
-  size: Scalars['Int'],
-  ctime: Scalars['DateTime'],
-  mtime: Scalars['DateTime'],
-};
-
 export type Mutation = {
-  uploadFile: File,
   createUser: User,
   updateUser?: Maybe<User>,
   deleteUser?: Maybe<User>,
@@ -1537,12 +1513,6 @@ export type Mutation = {
 };
 
 
-export type MutationUploadFileArgs = {
-  file: Scalars['Upload'],
-  comment?: Maybe<Scalars['String']>
-};
-
-
 export type MutationCreateUserArgs = {
   data: UserCreateInput
 };
@@ -1633,14 +1603,11 @@ export enum Permission {
 }
 
 export type Query = {
-  fsFiles: Array<FsFile>,
-  files: Array<File>,
   currentUser: User,
   user?: Maybe<User>,
   users: Array<User>,
   training?: Maybe<Training>,
   trainings: Array<Training>,
-  publishedTrainings: Array<Training>,
   trainingType?: Maybe<TrainingType>,
   trainingTypes: Array<TrainingType>,
   block?: Maybe<Block>,
@@ -1652,11 +1619,6 @@ export type Query = {
 };
 
 
-export type QueryFsFilesArgs = {
-  directory: Scalars['String']
-};
-
-
 export type QueryUserArgs = {
   where: UserWhereUniqueInput
 };
@@ -2804,7 +2766,6 @@ export type TrainingWhereUniqueInput = {
   id?: Maybe<Scalars['ID']>,
 };
 
-
 export type User = Node & {
   id: Scalars['ID'],
   email: Scalars['String'],
@@ -3353,25 +3314,22 @@ export type UserWhereUniqueInput = {
   email?: Maybe<Scalars['String']>,
 };
 
-export type ExerciseContentFragment = Pick<Exercise, 'id' | 'name' | 'description' | 'videos' | 'pictures' | 'targets' | 'baseExercise'>;
+export type ExerciseContentFragment = (
+  Pick<ExerciseInstance, 'id' | 'order' | 'repetitions' | 'variation'>
+  & { exercise: Pick<Exercise, 'id' | 'name' | 'description' | 'videos' | 'pictures' | 'targets' | 'baseExercise'> }
+);
 
 export type BlockContentFragment = (
   Pick<Block, 'id' | 'title' | 'description' | 'videos' | 'pictures' | 'duration' | 'rest'>
   & { format: Pick<Format, 'id' | 'name' | 'description'>, blocks: Maybe<Array<(
     Pick<BlockInstance, 'id' | 'order' | 'rounds' | 'variation'>
     & { block: BlockHintFragment }
-  )>>, exercises: Maybe<Array<(
-    Pick<ExerciseInstance, 'id' | 'order' | 'repetitions' | 'variation'>
-    & { exercise: ExerciseContentFragment }
-  )>> }
+  )>>, exercises: Maybe<Array<ExerciseContentFragment>> }
 );
 
 export type BlockHintFragment = (
   Pick<Block, 'id' | 'title' | 'description' | 'videos' | 'pictures' | 'duration' | 'rest'>
-  & { format: Pick<Format, 'id' | 'name' | 'description'>, blocks: Maybe<Array<Pick<BlockInstance, 'id'>>>, exercises: Maybe<Array<(
-    Pick<ExerciseInstance, 'id' | 'order' | 'repetitions' | 'variation'>
-    & { exercise: ExerciseContentFragment }
-  )>> }
+  & { format: Pick<Format, 'id' | 'name' | 'description'>, blocks: Maybe<Array<Pick<BlockInstance, 'id'>>>, exercises: Maybe<Array<ExerciseContentFragment>> }
 );
 
 export type SubBlockFragment = (
@@ -3394,51 +3352,6 @@ export type TrainingQuery = { training: Maybe<(
     & { type: Pick<TrainingType, 'id' | 'name' | 'description'>, blocks: Maybe<Array<SubBlockFragment>>, registrations: Maybe<Array<Pick<User, 'id' | 'name'>>> }
   )> };
 
-export type DisplayTrainingFragment = (
-  Pick<Training, 'id' | 'title' | 'trainingDate' | 'location' | 'attendance'>
-  & { type: Pick<TrainingType, 'id' | 'name' | 'description'>, blocks: Maybe<Array<(
-    { block: (
-      { blocks: Maybe<Array<(
-        { block: (
-          { blocks: Maybe<Array<(
-            { block: (
-              { blocks: Maybe<Array<Pick<BlockInstance, 'id'>>> }
-              & DisplayBlockFragment
-            ) }
-            & DisplayBlockInstanceFragment
-          )>> }
-          & DisplayBlockFragment
-        ) }
-        & DisplayBlockInstanceFragment
-      )>> }
-      & DisplayBlockFragment
-    ) }
-    & DisplayBlockInstanceFragment
-  )>>, registrations: Maybe<Array<Pick<User, 'id'>>> }
-);
-
-export type DisplayBlockInstanceFragment = Pick<BlockInstance, 'id' | 'order' | 'rounds' | 'variation'>;
-
-export type DisplayBlockFragment = (
-  Pick<Block, 'id' | 'title' | 'description' | 'videos' | 'pictures' | 'duration' | 'rest'>
-  & { format: Pick<Format, 'name' | 'description'>, exercises: Maybe<Array<(
-    Pick<ExerciseInstance, 'order' | 'repetitions' | 'variation'>
-    & { exercise: DisplayExerciseFragment }
-  )>> }
-);
-
-export type DisplayExerciseInstanceFragment = (
-  Pick<ExerciseInstance, 'id' | 'order' | 'repetitions' | 'variation'>
-  & { exercise: DisplayExerciseFragment }
-);
-
-export type DisplayExerciseFragment = Pick<Exercise, 'id' | 'name' | 'description' | 'videos' | 'pictures' | 'targets' | 'baseExercise'>;
-
-export type PublishedTrainingsQueryVariables = {};
-
-
-export type PublishedTrainingsQuery = { publishedTrainings: Array<DisplayTrainingFragment> };
-
 export type TrainingsQueryVariables = {};
 
 
@@ -3457,16 +3370,6 @@ export type FormatsQueryVariables = {};
 
 export type FormatsQuery = { formats: Array<Pick<Format, 'id' | 'name' | 'description'>> };
 
-export type BlocksQueryVariables = {};
-
-
-export type BlocksQuery = { blocks: Array<BlockContentFragment> };
-
-export type ExercisesQueryVariables = {};
-
-
-export type ExercisesQuery = { exercises: Array<ExerciseContentFragment> };
-
 export type CreateTrainingMutationVariables = {
   title: Scalars['String'],
   type: TrainingTypeCreateOneInput,
@@ -3574,14 +3477,20 @@ export type UserUpdateMutationVariables = {
 export type UserUpdateMutation = { updateUser: Maybe<Pick<User, 'id' | 'name' | 'email' | 'permissions' | 'interests'>> };
 
 export const ExerciseContentFragmentDoc = gql`
-    fragment exerciseContent on Exercise {
+    fragment exerciseContent on ExerciseInstance {
   id
-  name
-  description
-  videos
-  pictures
-  targets
-  baseExercise
+  exercise {
+    id
+    name
+    description
+    videos
+    pictures
+    targets
+    baseExercise
+  }
+  order
+  repetitions
+  variation
 }
     `;
 export const BlockHintFragmentDoc = gql`
@@ -3602,13 +3511,7 @@ export const BlockHintFragmentDoc = gql`
     id
   }
   exercises {
-    id
-    exercise {
-      ...exerciseContent
-    }
-    order
-    repetitions
-    variation
+    ...exerciseContent
   }
 }
     ${ExerciseContentFragmentDoc}`;
@@ -3636,13 +3539,7 @@ export const BlockContentFragmentDoc = gql`
     variation
   }
   exercises {
-    id
-    exercise {
-      ...exerciseContent
-    }
-    order
-    repetitions
-    variation
+    ...exerciseContent
   }
 }
     ${BlockHintFragmentDoc}
@@ -3669,98 +3566,6 @@ export const SubBlockHintFragmentDoc = gql`
   variation
 }
     ${BlockHintFragmentDoc}`;
-export const DisplayBlockInstanceFragmentDoc = gql`
-    fragment displayBlockInstance on BlockInstance {
-  id
-  order
-  rounds
-  variation
-}
-    `;
-export const DisplayExerciseFragmentDoc = gql`
-    fragment displayExercise on Exercise {
-  id
-  name
-  description
-  videos
-  pictures
-  targets
-  baseExercise
-}
-    `;
-export const DisplayBlockFragmentDoc = gql`
-    fragment displayBlock on Block {
-  id
-  title
-  description
-  videos
-  pictures
-  duration
-  format {
-    name
-    description
-  }
-  rest
-  exercises {
-    order
-    repetitions
-    variation
-    exercise {
-      ...displayExercise
-    }
-  }
-}
-    ${DisplayExerciseFragmentDoc}`;
-export const DisplayTrainingFragmentDoc = gql`
-    fragment displayTraining on Training {
-  id
-  title
-  type {
-    id
-    name
-    description
-  }
-  trainingDate
-  location
-  attendance
-  blocks {
-    ...displayBlockInstance
-    block {
-      ...displayBlock
-      blocks {
-        ...displayBlockInstance
-        block {
-          ...displayBlock
-          blocks {
-            ...displayBlockInstance
-            block {
-              ...displayBlock
-              blocks {
-                id
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-  registrations {
-    id
-  }
-}
-    ${DisplayBlockInstanceFragmentDoc}
-${DisplayBlockFragmentDoc}`;
-export const DisplayExerciseInstanceFragmentDoc = gql`
-    fragment displayExerciseInstance on ExerciseInstance {
-  id
-  order
-  repetitions
-  variation
-  exercise {
-    ...displayExercise
-  }
-}
-    ${DisplayExerciseFragmentDoc}`;
 export const TrainingDocument = gql`
     query training($id: ID!) {
   training(id: $id) {
@@ -3812,38 +3617,6 @@ export function useTrainingLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHoo
 export type TrainingQueryHookResult = ReturnType<typeof useTrainingQuery>;
 export type TrainingLazyQueryHookResult = ReturnType<typeof useTrainingLazyQuery>;
 export type TrainingQueryResult = ApolloReactCommon.QueryResult<TrainingQuery, TrainingQueryVariables>;
-export const PublishedTrainingsDocument = gql`
-    query publishedTrainings {
-  publishedTrainings {
-    ...displayTraining
-  }
-}
-    ${DisplayTrainingFragmentDoc}`;
-
-/**
- * __usePublishedTrainingsQuery__
- *
- * To run a query within a React component, call `usePublishedTrainingsQuery` and pass it any options that fit your needs.
- * When your component renders, `usePublishedTrainingsQuery` returns an object from Apollo Client that contains loading, error, and data properties 
- * you can use to render your UI.
- *
- * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
- *
- * @example
- * const { data, loading, error } = usePublishedTrainingsQuery({
- *   variables: {
- *   },
- * });
- */
-export function usePublishedTrainingsQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<PublishedTrainingsQuery, PublishedTrainingsQueryVariables>) {
-        return ApolloReactHooks.useQuery<PublishedTrainingsQuery, PublishedTrainingsQueryVariables>(PublishedTrainingsDocument, baseOptions);
-      }
-export function usePublishedTrainingsLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<PublishedTrainingsQuery, PublishedTrainingsQueryVariables>) {
-          return ApolloReactHooks.useLazyQuery<PublishedTrainingsQuery, PublishedTrainingsQueryVariables>(PublishedTrainingsDocument, baseOptions);
-        }
-export type PublishedTrainingsQueryHookResult = ReturnType<typeof usePublishedTrainingsQuery>;
-export type PublishedTrainingsLazyQueryHookResult = ReturnType<typeof usePublishedTrainingsLazyQuery>;
-export type PublishedTrainingsQueryResult = ApolloReactCommon.QueryResult<PublishedTrainingsQuery, PublishedTrainingsQueryVariables>;
 export const TrainingsDocument = gql`
     query trainings {
   trainings {
@@ -3961,70 +3734,6 @@ export function useFormatsLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHook
 export type FormatsQueryHookResult = ReturnType<typeof useFormatsQuery>;
 export type FormatsLazyQueryHookResult = ReturnType<typeof useFormatsLazyQuery>;
 export type FormatsQueryResult = ApolloReactCommon.QueryResult<FormatsQuery, FormatsQueryVariables>;
-export const BlocksDocument = gql`
-    query blocks {
-  blocks {
-    ...blockContent
-  }
-}
-    ${BlockContentFragmentDoc}`;
-
-/**
- * __useBlocksQuery__
- *
- * To run a query within a React component, call `useBlocksQuery` and pass it any options that fit your needs.
- * When your component renders, `useBlocksQuery` returns an object from Apollo Client that contains loading, error, and data properties 
- * you can use to render your UI.
- *
- * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
- *
- * @example
- * const { data, loading, error } = useBlocksQuery({
- *   variables: {
- *   },
- * });
- */
-export function useBlocksQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<BlocksQuery, BlocksQueryVariables>) {
-        return ApolloReactHooks.useQuery<BlocksQuery, BlocksQueryVariables>(BlocksDocument, baseOptions);
-      }
-export function useBlocksLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<BlocksQuery, BlocksQueryVariables>) {
-          return ApolloReactHooks.useLazyQuery<BlocksQuery, BlocksQueryVariables>(BlocksDocument, baseOptions);
-        }
-export type BlocksQueryHookResult = ReturnType<typeof useBlocksQuery>;
-export type BlocksLazyQueryHookResult = ReturnType<typeof useBlocksLazyQuery>;
-export type BlocksQueryResult = ApolloReactCommon.QueryResult<BlocksQuery, BlocksQueryVariables>;
-export const ExercisesDocument = gql`
-    query exercises {
-  exercises {
-    ...exerciseContent
-  }
-}
-    ${ExerciseContentFragmentDoc}`;
-
-/**
- * __useExercisesQuery__
- *
- * To run a query within a React component, call `useExercisesQuery` and pass it any options that fit your needs.
- * When your component renders, `useExercisesQuery` returns an object from Apollo Client that contains loading, error, and data properties 
- * you can use to render your UI.
- *
- * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
- *
- * @example
- * const { data, loading, error } = useExercisesQuery({
- *   variables: {
- *   },
- * });
- */
-export function useExercisesQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<ExercisesQuery, ExercisesQueryVariables>) {
-        return ApolloReactHooks.useQuery<ExercisesQuery, ExercisesQueryVariables>(ExercisesDocument, baseOptions);
-      }
-export function useExercisesLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<ExercisesQuery, ExercisesQueryVariables>) {
-          return ApolloReactHooks.useLazyQuery<ExercisesQuery, ExercisesQueryVariables>(ExercisesDocument, baseOptions);
-        }
-export type ExercisesQueryHookResult = ReturnType<typeof useExercisesQuery>;
-export type ExercisesLazyQueryHookResult = ReturnType<typeof useExercisesLazyQuery>;
-export type ExercisesQueryResult = ApolloReactCommon.QueryResult<ExercisesQuery, ExercisesQueryVariables>;
 export const CreateTrainingDocument = gql`
     mutation createTraining($title: String!, $type: TrainingTypeCreateOneInput!, $trainingDate: DateTime!, $location: String!, $attendance: Int!, $published: Boolean!, $blocks: BlockInstanceCreateManyWithoutParentTrainingInput) {
   createTraining(title: $title, type: $type, trainingDate: $trainingDate, location: $location, attendance: $attendance, published: $published, blocks: $blocks) {

+ 71 - 4
frontend/src/sortable/components/SortableList.tsx

@@ -1,13 +1,48 @@
+import React, { useState } from 'react'
 import {
   SortableContainer,
   SortableElement,
   SortableHandle
 } from 'react-sortable-hoc'
-import { dragHandle, sortableItem, sortableList } from '../styles'
+import arrayMove from 'array-move'
 
 const DragHandle = SortableHandle(() => (
   <div className='listitem-drag-handle'>
-    <style jsx>{dragHandle}</style>
+    <style jsx>{`
+      div {
+        width: 20px;
+        height: 15px;
+        user-select: none;
+        cursor: row-resize;
+        align-items: center;
+        background: linear-gradient(
+          top,
+          #0006,
+          #0006 20%,
+          #fff0 0,
+          #fff0 40%,
+          #0006 0,
+          #0006 60%,
+          #fff0 0,
+          #fff0 80%,
+          #0006 0,
+          #0006
+        );
+        background: -webkit-linear-gradient(
+          top,
+          #0006,
+          #0006 20%,
+          #fff0 0,
+          #fff0 40%,
+          #0006 0,
+          #0006 60%,
+          #fff0 0,
+          #fff0 80%,
+          #0006 0,
+          #0006
+        );
+      }
+    `}</style>
   </div>
 ))
 
@@ -16,7 +51,12 @@ const SortableItem = SortableElement(({ item }: any) => (
     <DragHandle />
     <div className='listitem-content'>{item}</div>
 
-    <style jsx>{sortableItem}</style>
+    <style jsx>{`
+      li {
+        display: grid;
+        grid-template-columns: 30px 1fr;
+      }
+    `}</style>
   </li>
 ))
 
@@ -26,9 +66,36 @@ const SortableList = SortableContainer(({ items }: { items: any[] }) => {
       {items.map((item: any, index: number) => {
         return <SortableItem key={item.key} index={index} item={item} />
       })}
-      <style jsx>{sortableList}</style>
+      <style jsx>{`
+        ul {
+          padding: 0;
+        }
+      `}</style>
     </ul>
   )
 })
 
+const SortableComponent = ({ items }: { items: any[] }) => {
+  const [state, setState] = useState(items)
+
+  function onSortEnd({
+    oldIndex,
+    newIndex
+  }: {
+    oldIndex: number
+    newIndex: number
+  }) {
+    const a = arrayMove(state, oldIndex, newIndex)
+    setState(a)
+  }
+  return (
+    <SortableList
+      onSortEnd={onSortEnd}
+      items={state}
+      lockAxis='y'
+      useDragHandle={true}
+    />
+  )
+}
+
 export default SortableList

+ 0 - 50
frontend/src/sortable/styles/index.ts

@@ -1,50 +0,0 @@
-import css from 'styled-jsx/css'
-
-export const dragHandle = css`
-  div {
-    width: 20px;
-    height: 15px;
-    user-select: none;
-    cursor: row-resize;
-    align-items: center;
-    background: linear-gradient(
-      top,
-      #0006,
-      #0006 20%,
-      #fff0 0,
-      #fff0 40%,
-      #0006 0,
-      #0006 60%,
-      #fff0 0,
-      #fff0 80%,
-      #0006 0,
-      #0006
-    );
-    background: -webkit-linear-gradient(
-      top,
-      #0006,
-      #0006 20%,
-      #fff0 0,
-      #fff0 40%,
-      #0006 0,
-      #0006 60%,
-      #fff0 0,
-      #fff0 80%,
-      #0006 0,
-      #0006
-    );
-  }
-`
-
-export const sortableItem = css`
-  li {
-    display: grid;
-    grid-template-columns: 30px 1fr;
-  }
-`
-
-export const sortableList = css`
-  ul {
-    padding: 0;
-  }
-`

+ 8 - 5
frontend/src/styles/global.ts

@@ -27,11 +27,14 @@ const GlobalStyle = css.global`
     display: grid;
     grid-template-areas:
       'header'
+      'nav'
       'main'
       'footer';
-    grid-template-rows: auto auto 1fr minmax(140px, auto);
+    grid-template-rows: auto auto 1fr minmax(180px, auto);
 
+    max-width: ${theme.maxWidth};
     min-height: 100vh;
+    margin: 0 auto;
 
     background: ${theme.colors.offWhite};
     color: ${theme.colors.black};
@@ -41,7 +44,7 @@ const GlobalStyle = css.global`
   @media (min-width: 500px) {
     body #__next {
       grid-template-areas:
-        'header header'
+        'header nav'
         'main main'
         'footer footer';
       grid-template-columns: auto 1fr;
@@ -52,10 +55,11 @@ const GlobalStyle = css.global`
   header {
     grid-area: header;
   }
+  nav {
+    grid-area: nav;
+  }
   main {
     grid-area: main;
-    max-width: ${theme.maxWidth};
-    margin: 0 auto;
   }
   footer {
     grid-area: footer;
@@ -69,7 +73,6 @@ const GlobalStyle = css.global`
   h5,
   h6 {
     font-weight: 900;
-    color: ${theme.colors.darkerblue};
   }
 
   /* Use monospace font for pre */

+ 1 - 2
frontend/src/styles/theme.ts

@@ -11,8 +11,7 @@ const theme = {
     blue: '#4482c3',
     darkblue: '#285680',
     darkerblue: '#204567',
-    offWhite: '#EDEDED',
-    nav: '#393939e7'
+    offWhite: '#EDEDED'
   },
   maxWidth: '1000px',
   bs: '0 12px 24px 0 rgba(0,0,0,0.09)',

+ 2 - 2
frontend/src/timer/components/Timer.tsx

@@ -1,6 +1,6 @@
 import { useState, useEffect, useRef } from 'react'
 
-import { TTraining } from '../../training/types'
+import { ITraining } from '../../training/types'
 import { getExerciseList, getTrainingTime, getPosition } from '../utils'
 
 import Countdown from './Countdown'
@@ -11,7 +11,7 @@ import { useVoice } from '../hooks/useVoice'
 import { useVideo } from '../hooks/useVideo'
 import theme from '../../styles/theme'
 
-const Timer = ({ training }: { training: TTraining }) => {
+const Timer = ({ training }: { training: ITraining }) => {
   const [time, timer] = useTimer({ tickPeriod: 100 })
   const voice = useVoice('rosie')
 

+ 13 - 17
frontend/src/timer/utils.ts

@@ -1,6 +1,6 @@
+import { IBlock } from '../training/types'
 import { calculateDuration } from '../training/utils'
 import { IExerciseItem } from './types'
-import { TBlock, TBlockInstance } from '../training/types'
 
 /**
  * Find the right exercise given a certain time.
@@ -36,18 +36,16 @@ export function getPosition(exerciseList: IExerciseItem[], time: number) {
  * @param initialOffset - used for recursive application
  */
 export function getExerciseList(
-  blockInstances?: TBlockInstance[],
+  blocks: IBlock[],
   initialOffset = 0,
   toplevelBlock: undefined | string = undefined
 ): IExerciseItem[] {
-  if (!blockInstances) return []
   let offset = initialOffset
-  return blockInstances
-    .map(blockInstance => {
-      const { block, rounds = 1 } = blockInstance
+  return blocks
+    .map(block => {
       if (block.blocks) {
         const blockArray = []
-        for (let i = 0; i < rounds; i++) {
+        for (let i = 0; i < (block.repetitions || 1); i++) {
           const subBlocks = getExerciseList(
             block.blocks,
             offset,
@@ -76,21 +74,19 @@ export function getExerciseList(
         const blockArray: IExerciseItem[] = []
         const newItem = {
           exercise: block.exercises
-            .map(exerciseInstance => {
-              const {
-                exercise: { name },
-                repetitions = 1
-              } = exerciseInstance
-              return repetitions > 1 ? `${repetitions}x ${name}` : name
-            })
+            .map(exercise =>
+              exercise.repetitions > 1
+                ? `${exercise.repetitions}x ${exercise.name}`
+                : exercise.name
+            )
             .join(' - '),
-          duration: calculateDuration(blockInstance),
-          videos: block.videos,
+          duration: calculateDuration(block),
+          video: block.video,
           description: block.description,
           toplevelBlock: toplevelBlock || block.title,
           offset
         }
-        for (let i = 0; i < rounds; i++) {
+        for (let i = 0; i < (block.repetitions || 1); i++) {
           blockArray.push({ ...newItem, offset })
           offset += newItem.duration
         }

+ 0 - 63
frontend/src/training/__tests__/utils.test.ts

@@ -1,63 +0,0 @@
-import { diffDB } from '../utils'
-const testDate1 = new Date(2020, 1, 1)
-const testDate2 = new Date(2020, 2, 2)
-console.log(typeof testDate2)
-const typesFalsy = {
-  number: 0,
-  boolean: false,
-  string: '',
-  //date: testDate1,
-  array: [],
-  object: {},
-  undefined: undefined,
-  null: null
-}
-const expectedFalsy = {
-  number: 0,
-  boolean: false,
-  string: ''
-  //date: testDate1
-}
-const typesTruthy = {
-  number: 12,
-  boolean: true,
-  string: 'hello!',
-  //date: testDate2,
-  array: [1, 2, 3],
-  object: { a: 1, b: 2 },
-  undefined: 'not undefined',
-  null: 45
-}
-const dbId = 'dbid'
-const createId = '++createId'
-const updateId = '@@updateId'
-const deleteId = '--deleteId'
-
-const dbItem = {
-  id: dbId,
-  ...typesTruthy,
-  child: {
-    id: dbId,
-    ...typesTruthy
-  }
-}
-
-const updateItem = {
-  id: dbId,
-  ...typesTruthy
-}
-
-describe('diffDB: Find differences of current state to database', () => {
-  it('returns undefined if there are no differences', () => {
-    const truthyNoDiffs = diffDB(typesTruthy, typesTruthy)
-    expect(truthyNoDiffs).toBe(undefined)
-    const falsyNoDiffs = diffDB(typesFalsy, typesFalsy)
-    expect(falsyNoDiffs).toBe(undefined)
-  })
-  it('diffs types correctly', () => {
-    const truthyDiff = diffDB(typesTruthy, typesFalsy)
-    expect(truthyDiff).toEqual(typesTruthy)
-    const falsyDiff = diffDB(typesFalsy, typesTruthy)
-    expect(falsyDiff).toEqual(expectedFalsy)
-  })
-})

+ 6 - 23
frontend/src/training/components/BlockInputs.tsx

@@ -1,15 +1,9 @@
 import FormatSelector from './FormatSelector'
 import { TextInput } from '../../form'
 import BlockInstanceInputs from './BlockInstanceInputs'
-import {
-  emptyBlockInstance,
-  emptyExercise,
-  emptyExerciseInstance
-} from '../utils'
+import { emptyBlockInstance } from '../utils'
 import ExerciseInstanceInputs from './ExerciseInstanceInputs'
 import { TBlock } from '../types'
-import { useState, ChangeEvent } from 'react'
-import BlockSelector from './BlockSelector'
 
 interface IBlockInputs {
   onChange: GenericEventHandler
@@ -20,15 +14,6 @@ interface IBlockInputs {
 const BlockInputs = ({ onChange, value, name }: IBlockInputs) => {
   return (
     <>
-      <p>
-        {value.id} {value.title}
-      </p>
-      <BlockSelector
-        name={name}
-        value={value}
-        label='Existing block'
-        onChange={onChange}
-      />
       <TextInput
         name={`${name}.title`}
         label='Title'
@@ -89,7 +74,7 @@ const BlockInputs = ({ onChange, value, name }: IBlockInputs) => {
       <label>Exercises</label>
       {value.exercises && value.exercises.length > 0 && (
         <ExerciseInstanceInputs
-          name={`${name}.exercises`}
+          name={`${name}.blocks`}
           value={value.exercises}
           onChange={onChange}
         />
@@ -97,16 +82,14 @@ const BlockInputs = ({ onChange, value, name }: IBlockInputs) => {
       <button
         onClick={event => {
           event.preventDefault()
-          const newExercise = emptyExerciseInstance({
-            order: value.exercises ? value.exercises.length : 0
+          const newExercise = empty({
+            order: value.blocks ? value.blocks.length : 0
           })
           onChange({
             target: {
               type: 'custom',
-              name: `${name}.exercises`,
-              value: value.exercises
-                ? [...value.exercises, newExercise]
-                : [newExercise]
+              name: `${name}.blocks`,
+              value: value.blocks ? [...value.blocks, newBlock] : [newBlock]
             }
           })
         }}

+ 0 - 64
frontend/src/training/components/BlockSelector.tsx

@@ -1,64 +0,0 @@
-import { useBlocksQuery } from '../../gql'
-import { useState, useEffect } from 'react'
-import { TBlock } from '../types'
-
-interface IBlockSelector {
-  value?: TBlock
-  onChange: GenericEventHandler
-  name?: string
-  label?: string
-}
-
-const BlockSelector = ({
-  value,
-  onChange,
-  name = 'block',
-  label = 'Block'
-}: IBlockSelector) => {
-  const [state, setState] = useState(value?.id ?? '')
-  const blocks = useBlocksQuery()
-
-  useEffect(() => {
-    setState(value?.id || '')
-  }, [value])
-
-  return (
-    <>
-      <label>{label}</label>
-      <select
-        id={name}
-        name={name}
-        value={state}
-        onChange={ev => setState(ev.target.value)}
-      >
-        {blocks.loading && 'loading blocks...'}
-        {blocks.error && 'error loading blocks'}
-        {blocks.data &&
-          blocks.data.blocks.map(block => (
-            <option key={block.id} value={block.id}>
-              {[block.title, block.description?.slice(0, 60)]
-                .filter(block => !!block)
-                .join(' - ')}
-            </option>
-          ))}
-      </select>
-      <button
-        type='button'
-        onClick={event => {
-          const changeEvent: CustomChangeEvent = {
-            target: {
-              type: 'custom',
-              value: { id: state },
-              name
-            }
-          }
-          onChange(changeEvent)
-        }}
-      >
-        Use
-      </button>
-    </>
-  )
-}
-
-export default BlockSelector

+ 3 - 7
frontend/src/training/components/ExerciseComposition.tsx

@@ -1,8 +1,8 @@
 import { formatTime, printExercises } from '../utils'
-import { TExerciseInstance } from '../types'
+import { IExercise } from '../types'
 
 export interface IExerciseComposition {
-  exercises: TExerciseInstance[]
+  exercises: IExercise[]
   duration: number
 }
 
@@ -11,7 +11,7 @@ const ExerciseComposition = ({ exercises, duration }: IExerciseComposition) => {
 
   return (
     <div className='exercise-composition'>
-      <span className='exercise-name'>{exerciseString}</span>
+      <span>{exerciseString}</span>
       <span className='exercise-time'>{formatTime(duration)}</span>
 
       <style jsx>
@@ -23,10 +23,6 @@ const ExerciseComposition = ({ exercises, duration }: IExerciseComposition) => {
           .exercise-composition .exercise-time {
             text-align: right;
           }
-          .exercise-time {
-            color: gray;
-            font-size: 80%;
-          }
         `}
       </style>
     </div>

+ 0 - 8
frontend/src/training/components/ExerciseInputs.tsx

@@ -1,6 +1,5 @@
 import { TextInput } from '../../form'
 import { TExercise } from '../types'
-import ExerciseSelector from './ExerciseSelector'
 
 interface IExerciseInputs {
   onChange: GenericEventHandler
@@ -11,13 +10,6 @@ interface IExerciseInputs {
 const ExerciseInputs = ({ onChange, value, name }: IExerciseInputs) => {
   return (
     <>
-      <p>ex: {value.id}</p>
-      <ExerciseSelector
-        name={name}
-        value={value}
-        label='Existing exercise'
-        onChange={onChange}
-      />
       <TextInput
         name={`${name}.name`}
         label='Name'

+ 2 - 2
frontend/src/training/components/ExerciseInstanceInputs.tsx

@@ -80,7 +80,7 @@ const ExerciseInstanceInputs = ({
           />
           {item.exercise && (
             <ExerciseInputs
-              name={`${name}.${itemIndex}.exercise`}
+              name={`${name}.${itemIndex}.block`}
               value={item.exercise}
               onChange={onChange}
             />
@@ -100,7 +100,7 @@ const ExerciseInstanceInputs = ({
               updateOrderProperty(newValues, newOrder)
             }}
           >
-            Delete Exercise
+            Delete block
           </button>
         </div>
       )

+ 0 - 66
frontend/src/training/components/ExerciseSelector.tsx

@@ -1,66 +0,0 @@
-import { useExercisesQuery } from '../../gql'
-import { useState, useEffect } from 'react'
-import { TExercise } from '../types'
-
-interface IExerciseSelector {
-  value?: TExercise
-  onChange: GenericEventHandler
-  name?: string
-  label?: string
-}
-
-const ExerciseSelector = ({
-  value,
-  onChange,
-  name = 'exercise',
-  label = 'Exercise'
-}: IExerciseSelector) => {
-  const [state, setState] = useState(value?.id ?? '')
-  const exercises = useExercisesQuery()
-
-  useEffect(() => {
-    setState(value?.id || '')
-  }, [value])
-
-  return (
-    <>
-      <label>{label}</label>
-      <select
-        id={name}
-        name={name}
-        value={state}
-        onChange={ev => setState(ev.target.value)}
-      >
-        {exercises.loading && 'loading exercises...'}
-        {exercises.error && 'error loading exercises'}
-        {exercises.data &&
-          exercises.data.exercises.map(exercise => (
-            <option key={exercise.id} value={exercise.id}>
-              {[exercise.name, exercise.description?.slice(0, 60)]
-                .filter(exercise => !!exercise)
-                .join(' - ')}
-            </option>
-          ))}
-      </select>
-      <button
-        type='button'
-        onClick={event => {
-          const changeEvent: CustomChangeEvent = {
-            target: {
-              type: 'custom',
-              value: exercises.data?.exercises.find(
-                exercise => exercise.id === state
-              ),
-              name
-            }
-          }
-          onChange(changeEvent)
-        }}
-      >
-        Use
-      </button>
-    </>
-  )
-}
-
-export default ExerciseSelector

+ 2 - 2
frontend/src/training/components/FormatSelector.tsx

@@ -43,7 +43,7 @@ const FormatSelector = ({
           const changeEvent: CustomChangeEvent = {
             target: {
               type: 'custom',
-              value: { id: event.target.value },
+              value: { connect: { id: event.target.value } },
               name
             }
           }
@@ -74,7 +74,7 @@ const FormatSelector = ({
               const changeEvent: CustomChangeEvent = {
                 target: {
                   type: 'custom',
-                  value: { id: result.data.createFormat.id },
+                  value: { connect: { id: result.data.createFormat.id } },
                   name
                 }
               }

+ 9 - 11
frontend/src/training/components/Training.tsx

@@ -2,10 +2,12 @@ import theme from '../../styles/theme'
 
 import TrainingBlock from './TrainingBlock'
 import Link from 'next/link'
-import { TTraining } from '../types'
+import { ITraining } from '../types'
 import TrainingMeta from './TrainingMeta'
+import { useRouter } from 'next/router'
+import { useTrainingLazyQuery } from '../../gql'
 
-const Training = ({ training }: { training: TTraining }) => {
+const Training = ({ training }: { training: ITraining }) => {
   return (
     <article>
       <h2>{training.title}</h2>
@@ -14,16 +16,13 @@ const Training = ({ training }: { training: TTraining }) => {
 
       <section>
         <h2>Program</h2>
-        <Link href='/timer/[id]' as={`/timer/${training.id}`}>
-          <button type='button'>Start Timer</button>
+        <Link href='/timer'>
+          <button>Start Timer</button>
         </Link>
         {training.blocks &&
           training.blocks
-            .slice()
-            .sort((a, b) => a.order - b.order)
-            .map(block => (
-              <TrainingBlock key={block.id} blockInstance={block} />
-            ))}
+            .sort(block => block.sequence || 0)
+            .map(block => <TrainingBlock key={block.id} block={block} />)}
       </section>
 
       <style jsx>
@@ -35,10 +34,9 @@ const Training = ({ training }: { training: TTraining }) => {
               'information placeholder'
               'content content';
             grid-template-columns: 1fr 2fr;
-            background-image: url('/media/man_working_out.jpg');
+            background-image: url('media/man_working_out.jpg');
             background-size: auto 400px;
             background-repeat: no-repeat;
-            background-position: center 0;
             margin: 2em 0;
           }
 

+ 17 - 30
frontend/src/training/components/TrainingBlock.tsx

@@ -1,23 +1,20 @@
-import ExerciseComposition from './ExerciseComposition'
-import { calculateDuration, formatTime } from '../utils'
-import { TBlockInstance } from '../types'
+import ExerciseComposition from "./ExerciseComposition";
+import { calculateDuration, formatTime } from "../utils";
+import { IBlock } from "../types";
 
-const TrainingBlock = ({
-  blockInstance
-}: {
-  blockInstance: TBlockInstance
-}) => {
-  const duration = calculateDuration(blockInstance)
-  const { title, blocks, exercises } = blockInstance.block
+const TrainingBlock = ({ block }: { block: IBlock }) => {
+  const duration = calculateDuration(block);
   return (
     <div>
-      <h3>
-        <span className='block-title'>{title}</span>{' '}
-        <span className='block-time'>{formatTime(duration)}</span>
-      </h3>
-      {blocks && blocks.map(block => <TrainingBlock blockInstance={block} />)}
-      {exercises && (
-        <ExerciseComposition exercises={exercises} duration={duration} />
+      {block.title && (
+        <h3>
+          {block.title} ({formatTime(duration)})
+        </h3>
+      )}
+      {block.blocks &&
+        block.blocks.map(block => <TrainingBlock block={block} />)}
+      {block.exercises && (
+        <ExerciseComposition exercises={block.exercises} duration={duration} />
       )}
 
       <style jsx>
@@ -25,19 +22,9 @@ const TrainingBlock = ({
           section {
             display: grid;
           }
-          .block-time {
-            color: gray;
-            font-size: 90%;
-          }
-          .block-time::before {
-            content: '(';
-          }
-          .block-time::after {
-            content: ')';
-          }
         `}
       </style>
     </div>
-  )
-}
-export default TrainingBlock
+  );
+};
+export default TrainingBlock;

+ 20 - 38
frontend/src/training/components/TrainingMeta.tsx

@@ -1,46 +1,28 @@
-import { TTraining } from '../types'
-import { calculateRating } from '../utils'
-import { useContext } from 'react'
-import { UserContext } from '../../user/hooks'
-import { useRegisterMutation } from '../../gql'
-
-const TrainingMeta = ({ training }: { training: TTraining }) => {
-  const { user } = useContext(UserContext)
-  const [register, registerData] = useRegisterMutation({
-    variables: { training: training.id }
-  })
+import { ITraining } from "../types";
+import { calculateRating } from "../utils";
 
+const TrainingMeta = ({ training }: { training: ITraining }) => {
   return (
     <aside>
-      <div className='info'>
-        <span className='caption'>Type: </span>
-        <span className='data'>{training.type.name}</span>
+      <div className="info">
+        <span className="caption">Type: </span>
+        <span className="data">{training.type.name}</span>
       </div>
-      <div className='info'>
-        <span className='caption'>Date: </span>
-        <span className='data'>
+      <div className="info">
+        <span className="caption">Date: </span>
+        <span className="data">
           {new Date(training.trainingDate).toLocaleString()}
         </span>
       </div>
-      <div className='info'>
-        <span className='caption'>Location: </span>
-        <span className='data'>{training.location}</span>
-      </div>
-      <div className='info'>
-        <span className='caption'>Registrations: </span>
-        <span className='data'>{training.registrations?.length ?? 0}</span>
-        {training.registrations &&
-        training.registrations.find(
-          registeredUser =>
-            user?.data?.currentUser &&
-            registeredUser.id === user.data.currentUser.id
-        ) ? (
-          <button onClick={() => register()}>Deregister</button>
-        ) : (
-          <button onClick={() => register()}>Register now!</button>
-        )}
+      <div className="info">
+        <span className="caption">Location: </span>
+        <span className="data">{training.location}</span>
       </div>
       {/*<div className="info">
+        <span className="caption">Registrations: </span>
+        <span className="data">{training.registrations.length} </span>
+      </div>
+      <div className="info">
         <span className="caption">Attendance: </span>
         <span className="data">{training.attendance}</span>
       </div>
@@ -56,7 +38,7 @@ const TrainingMeta = ({ training }: { training: TTraining }) => {
           <a href="">*</a>
         </span>
       </div>
-          */}
+          <button>Register now!</button>*/}
 
       <style jsx>{`
         aside {
@@ -73,7 +55,7 @@ const TrainingMeta = ({ training }: { training: TTraining }) => {
         }
       `}</style>
     </aside>
-  )
-}
+  );
+};
 
-export default TrainingMeta
+export default TrainingMeta;

+ 1 - 1
frontend/src/training/components/TrainingTypeSelector.tsx

@@ -1,7 +1,7 @@
 import { useTrainingTypesQuery, TrainingType } from '../../gql'
+import { Modal } from '../../modal'
 import AddTrainingType from './AddTrainingType'
 import { useState, useEffect } from 'react'
-import { Modal } from '../../modal'
 
 interface ITrainingTypeSelector {
   value?: TrainingType

+ 15 - 125
frontend/src/training/training.graphql

@@ -1,13 +1,19 @@
 # import * from '../../../backend/database/generated/prisma.graphql'
 
-fragment exerciseContent on Exercise {
+fragment exerciseContent on ExerciseInstance {
   id
-  name
-  description
-  videos
-  pictures
-  targets
-  baseExercise
+  exercise {
+    id
+    name
+    description
+    videos
+    pictures
+    targets
+    baseExercise
+  }
+  order
+  repetitions
+  variation
 }
 
 fragment blockContent on Block {
@@ -33,13 +39,7 @@ fragment blockContent on Block {
     variation
   }
   exercises {
-    id
-    exercise {
-      ...exerciseContent
-    }
-    order
-    repetitions
-    variation
+    ...exerciseContent
   }
 }
 
@@ -60,13 +60,7 @@ fragment blockHint on Block {
     id
   }
   exercises {
-    id
-    exercise {
-      ...exerciseContent
-    }
-    order
-    repetitions
-    variation
+    ...exerciseContent
   }
 }
 
@@ -114,98 +108,6 @@ query training($id: ID!) {
   }
 }
 
-fragment displayTraining on Training {
-  id
-  title
-  type {
-    id
-    name
-    description
-  }
-  trainingDate
-  location
-  attendance
-  blocks {
-    ...displayBlockInstance
-    block {
-      ...displayBlock
-      blocks {
-        ...displayBlockInstance
-        block {
-          ...displayBlock
-          blocks {
-            ...displayBlockInstance
-            block {
-              ...displayBlock
-              blocks {
-                id
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-  registrations {
-    id
-  }
-}
-
-fragment displayBlockInstance on BlockInstance {
-  id
-  order
-  rounds
-  variation
-}
-
-fragment displayBlock on Block {
-  id
-  title
-  description
-  videos
-  pictures
-  duration
-  format {
-    name
-    description
-  }
-  rest
-  exercises {
-    order
-    repetitions
-    variation
-    exercise {
-      ...displayExercise
-    }
-  }
-}
-
-fragment displayExerciseInstance on ExerciseInstance {
-  id
-  order
-  repetitions
-  variation
-  exercise {
-    ...displayExercise
-  }
-}
-
-fragment displayExercise on Exercise {
-  id
-  name
-  description
-  videos
-  pictures
-  targets
-  baseExercise
-}
-
-query publishedTrainings {
-  publishedTrainings {
-    ...displayTraining
-  }
-}
-
 query trainings {
   trainings {
     id
@@ -245,18 +147,6 @@ query formats {
   }
 }
 
-query blocks {
-  blocks {
-    ...blockContent
-  }
-}
-
-query exercises {
-  exercises {
-    ...exerciseContent
-  }
-}
-
 mutation createTraining(
   $title: String!
   $type: TrainingTypeCreateOneInput!

+ 63 - 41
frontend/src/training/types.ts

@@ -6,44 +6,66 @@ import {
   Exercise
 } from '../gql'
 
-export type TTraining = Pick<Training, 'id' | 'type'> &
-  Partial<Omit<Training, 'id' | 'type'>>
-export type TBlockInstance = Pick<BlockInstance, 'id' | 'block' | 'order'> &
-  Partial<Omit<BlockInstance, 'id' | 'block' | 'order'>>
-export type TBlock = Pick<
-  Block,
-  | 'id'
-  | 'title'
-  | 'exercises'
-  | 'videos'
-  | 'blocks'
-  | 'tracks'
-  | 'pictures'
-  | 'format'
-> &
-  Partial<
-    Omit<
-      Block,
-      | 'id'
-      | 'title'
-      | 'exercises'
-      | 'videos'
-      | 'blocks'
-      | 'tracks'
-      | 'pictures'
-      | 'format'
-    >
-  >
-export type TExerciseInstance = Pick<ExerciseInstance, 'id' | 'exercise'> &
-  Partial<Omit<ExerciseInstance, 'id' | 'exercise'>>
-export type TExercise = Pick<
-  Exercise,
-  'id' | 'name' | 'pictures' | 'videos' | 'targets' | 'baseExercise'
-> &
-  Partial<
-    Omit<
-      Exercise,
-      'id' | 'name' | 'pictures' | 'videos' | 'targets' | 'baseExercise'
-    >
-  >
-export type TRating = { value: number }
+export interface ITraining {
+  id: string
+  title: string
+  type: {
+    id: string
+    name: string
+    description: string
+  }
+  createdAt: string
+  trainingDate: string
+  location: string
+  registrations: string[]
+  attendance: number
+  ratings: IRating[]
+  published: boolean
+  blocks: IBlock[]
+}
+
+export type TTraining = Pick<Training, 'id'> & Partial<Omit<Training, 'id'>>
+export type TBlockInstance = Pick<BlockInstance, 'id'> &
+  Partial<Omit<BlockInstance, 'id'>>
+export type TBlock = Pick<Block, 'id'> & Partial<Omit<Block, 'id'>>
+export type TExerciseInstance = Pick<ExerciseInstance, 'id'> &
+  Partial<Omit<ExerciseInstance, 'id'>>
+export type TExercise = Pick<Exercise, 'id'> & Partial<Omit<Exercise, 'id'>>
+
+export interface IBlock {
+  id: string
+  sequence?: number
+  title?: string
+  description?: string
+  video?: string
+  duration?: number
+  repetitions?: number
+  rest?: number
+  format?: IFormat
+  blocks?: IBlock[]
+  exercises?: IExercise[]
+}
+
+export interface IFormat {
+  id: string
+  name: string
+  description: string
+}
+
+export interface IExercise {
+  id: string
+  name: string
+  description: string
+  repetitions: number
+  videos: string[]
+  pictures: string[]
+  targets: string[]
+  baseExercise: {
+    id: string
+    name: string
+  }
+}
+
+export interface IRating {
+  value: number
+}

+ 53 - 64
frontend/src/training/utils.ts

@@ -1,19 +1,20 @@
 import { parse } from 'date-fns'
 import {
+  IBlock,
+  IExercise,
+  IRating,
   TTraining,
   TExerciseInstance,
   TBlockInstance,
   TBlock,
-  TExercise,
-  TRating
+  TExercise
 } from './types'
 import {
   TrainingQuery,
   SubBlockFragment,
-  Exercise,
-  Block,
-  ExerciseInstance,
-  BlockInstance
+  BlockContentFragment,
+  Training,
+  ExerciseContentFragment
 } from '../gql'
 import { isArray, transform, isEqual, isObject } from 'lodash'
 
@@ -21,21 +22,20 @@ import { isArray, transform, isEqual, isObject } from 'lodash'
  * Takes a block of exercises and calculates the duration in seconds.
  * @param block
  */
-export function calculateDuration(
-  blocks?: TBlockInstance | TBlockInstance[]
-): number {
-  if (!blocks) return 0
-  const blocksArray = isArray(blocks) ? blocks : [blocks]
-  const duration = blocksArray.map(blockInstance => {
-    const blockRounds = blockInstance.rounds ?? 1
-    const blockDuration =
-      blockInstance.block.duration ??
-      calculateDuration(blockInstance.block.blocks) ??
+export function calculateDuration(block: IBlock): number {
+  if (block.duration) return block.duration
+  const repetitions = block.repetitions || 1
+  const rest = block.rest || 0
+  if (block.blocks) {
+    const subblockDuration = block.blocks.reduce(
+      (accumulator, block) =>
+        accumulator + (block.duration || calculateDuration(block)),
       0
-    const blockRest = blockInstance.block.rest ?? 0
-    return blockRounds * (blockDuration + blockRest)
-  })
-  return duration.reduce((a, b) => a + b, 0)
+    )
+    return repetitions * (subblockDuration + rest)
+  } else {
+    return 0
+  }
 }
 
 /**
@@ -53,12 +53,12 @@ export function formatTime(seconds: number) {
  * 4x Exercise 1 - Exercise 2 - 2x Exercise 3
  * @param exercises
  */
-export function printExercises(exercises: TExerciseInstance[]) {
+export function printExercises(exercises: IExercise[]) {
   return exercises
-    .map(exerciseInstance =>
-      exerciseInstance.repetitions && exerciseInstance.repetitions > 1
-        ? `${exerciseInstance.repetitions}x ${exerciseInstance.exercise.name}`
-        : exerciseInstance.exercise.name
+    .map(exercise =>
+      exercise.repetitions > 1
+        ? `${exercise.repetitions}x ${exercise.name}`
+        : exercise.name
     )
     .join(' - ')
 }
@@ -67,7 +67,7 @@ export function printExercises(exercises: TExerciseInstance[]) {
  * Takes an array of rating and calculates the average rating
  * @param ratings
  */
-export function calculateRating(ratings: TRating[]) {
+export function calculateRating(ratings: IRating[]) {
   const numberOfRatings = ratings.length
   const sumOfRatings = ratings.reduce(
     (accumulator, rating) => accumulator + rating.value,
@@ -83,6 +83,8 @@ export function trainingDBToArray(DBTraining: TrainingQuery['training']) {
   return { ...data, blocks: blockDBToArray(blocks) }
 }
 
+export function trainingArrayToDB() {}
+
 export function blockDBToArray(DBSubBlock?: SubBlockFragment[]) {
   console.log({ DBSubBlock })
   if (!DBSubBlock) return undefined
@@ -121,8 +123,8 @@ function randomID() {
     .substr(0, 10)}`
 }
 
-export function emptyExercise(input?: Partial<Exercise>) {
-  const emptyExercise: TExercise = {
+export function emptyExercise(input?: TExercise) {
+  const emptyExercise = {
     id: randomID(),
     name: '',
     description: '',
@@ -134,8 +136,8 @@ export function emptyExercise(input?: Partial<Exercise>) {
   return { ...emptyExercise, ...input }
 }
 
-export function emptyExerciseInstance(input?: Partial<ExerciseInstance>) {
-  const emptyExerciseInstance: TExerciseInstance = {
+export function emptyExerciseInstance(input?: TExerciseInstance) {
+  const emptyExerciseInstance = {
     id: randomID(),
     order: 0,
     exercise: emptyExercise()
@@ -143,8 +145,8 @@ export function emptyExerciseInstance(input?: Partial<ExerciseInstance>) {
   return { ...emptyExerciseInstance, ...input }
 }
 
-export function emptyBlock(input?: Partial<Block>) {
-  const emptyBlock: TBlock = {
+export function emptyBlock(input?: TBlock) {
+  const emptyBlock = {
     id: randomID(),
     title: '',
     format: { id: '', name: '', description: '' },
@@ -156,8 +158,8 @@ export function emptyBlock(input?: Partial<Block>) {
   return { ...emptyBlock, ...input }
 }
 
-export function emptyBlockInstance(input?: Partial<BlockInstance>) {
-  const emptyBlockInstance: TBlockInstance = {
+export function emptyBlockInstance(input?: TBlockInstance) {
+  const emptyBlockInstance = {
     id: randomID(),
     block: emptyBlock(),
     order: 0
@@ -166,7 +168,7 @@ export function emptyBlockInstance(input?: Partial<BlockInstance>) {
 }
 
 export function emptyTraining(input?: TTraining) {
-  const emptyTraining: TTraining = {
+  const emptyTraining = {
     id: randomID(),
     title: '',
     type: { id: '', name: '', description: '' },
@@ -178,12 +180,11 @@ export function emptyTraining(input?: TTraining) {
   return { ...emptyTraining, ...input }
 }
 
-export function collectMutations(arr: any[]) {
+export function collectMutationCreateConnect(arr: any[]) {
   const create: any[] = []
   const connect: any[] = []
   const del: any[] = []
   const update: any[] = []
-  console.log('collect', arr)
   arr.forEach(val => {
     if (typeof val === 'object' && val['connect']) connect.push(val['connect'])
     if (typeof val === 'object' && val['create']) create.push(val['create'])
@@ -203,19 +204,17 @@ export function collectMutations(arr: any[]) {
   return returnObject
 }
 
-function isNotInDB(key: any, val: any) {
-  return (
-    key === '__typename' ||
-    (key === 'id' && typeof val === 'string' && val.startsWith('++'))
-  )
-}
-
 export function transformArrayToDB(acc: any, val: any, key: any, object: any) {
-  if (isNotInDB(key, val)) return
-  if (isArray(val)) {
-    acc[key] = collectMutations(transform(val, transformArrayToDB))
+  if (key === '__typename') {
+    // remove the typename from the database
+    return
+  } else if (key === 'id' && typeof val === 'string' && val.startsWith('++')) {
+    // remove placeholder IDs
+    return
+  } else if (isArray(val)) {
+    // collect 'create' and 'connect' statements
+    acc[key] = collectMutationCreateConnect(transform(val, transformArrayToDB))
   } else if (typeof val === 'object' && !!val) {
-    console.log('object', key, val)
     // we found an object!
     if (!!val['id'] && val['id'].startsWith('++')) {
       // values with placeholder IDs are preserved
@@ -224,6 +223,7 @@ export function transformArrayToDB(acc: any, val: any, key: any, object: any) {
       // IDs starting with -- are for deletion
       acc[key] = { delete: { id: val['id'].substr(2) } }
     } else {
+      // values with real IDs are just connected
       const { id, ...data } = val
       if (id?.startsWith('@@')) {
         if (typeof key === 'string') {
@@ -236,6 +236,7 @@ export function transformArrayToDB(acc: any, val: any, key: any, object: any) {
             }
           }
         }
+        console.log('update candidate', key, id, data, acc[key].update.data)
       } else {
         acc[key] = { connect: { id } }
       }
@@ -250,27 +251,15 @@ export function diffDB(newObject: any = {}, oldObject: any = {}) {
   const transformResult = transform(
     newObject,
     (result: any, value: any, key: string) => {
-      /*if (key === 'id') {
-        result[key] = value
-      } else*/ if (
-        !isEqual(value, oldObject[key])
-      ) {
+      if (key === 'id') {
+        if (isEqual(value, oldObject[key])) result[key] = `@@${value}`
+      } else if (!isEqual(value, oldObject[key])) {
         const newValue =
           isObject(value) && isObject(oldObject[key])
             ? diffDB(value, oldObject[key])
             : value
         if (newValue !== undefined && newValue !== null) {
-          if (isEqual(key, 'id')) {
-            if (result[key] !== undefined) return
-          } else {
-            result[key] = newValue
-            if (
-              oldObject['id'] !== undefined &&
-              oldObject['id'] === newObject['id']
-            ) {
-              result['id'] = `@@${oldObject['id']}`
-            }
-          }
+          result[key] = newValue
         }
       }
     }

+ 17 - 33
frontend/src/user/components/LoginForm.tsx

@@ -1,41 +1,25 @@
-import { TextInput, useForm } from '../../form'
-import { useContext } from 'react'
-import { UserContext } from '../hooks'
+import { useUserLoginMutation, CurrentUserDocument } from "../../gql";
+import { TextInput } from "../../form";
 
-const LoginForm = () => {
-  const { values, onChange } = useForm({ email: '', password: '' })
-  const { login } = useContext(UserContext)
+const initialValues = {
+  email: "tomislav.cvetic@u-blox.com",
+  password: "1234"
+};
 
-  if (!login) return <p>Loading context.</p>
-  const [userLogin, { error, loading }] = login
+const LoginForm = () => {
+  const [login, { loading, error, data }] = useUserLoginMutation();
+  console.log("LoginForm", loading, error, data);
 
   return (
-    <form
-      onSubmit={async ev => {
-        ev.preventDefault()
-        const result = await userLogin({ variables: values })
-        console.log('login', result)
-      }}
-    >
-      <TextInput
-        label='Email'
-        name='email'
-        value={values.email}
-        onChange={onChange}
-      />
-      <TextInput
-        label='Password'
-        name='password'
-        type='password'
-        value={values.password}
-        onChange={onChange}
-      />
-      <button type='submit' disabled={loading}>
+    <form>
+      <TextInput label="Email" name="email" type="text" placeholder="Email" />
+      <TextInput label="Password" name="password" type="password" />
+      <button type="submit" disabled={loading}>
         Login!
       </button>
-      {error && <div className='error'>{error.message}</div>}
+      {error && <div className="error">{error.message}</div>}
     </form>
-  )
-}
+  );
+};
 
-export default LoginForm
+export default LoginForm;

+ 13 - 9
frontend/src/user/components/LogoutButton.tsx

@@ -1,20 +1,24 @@
-import { useContext } from 'react'
-import { UserContext } from '../hooks'
+import { useUserLogoutMutation, CurrentUserDocument } from '../../gql'
 
 interface LogoutButtonProps {
   title?: string
 }
 
-const LogoutButton = ({ title = 'Logout' }: LogoutButtonProps) => {
-  const { logout } = useContext(UserContext)
+const LogoutButton = ({ title }: LogoutButtonProps) => {
 
-  if (!logout) return <p>Loading context.</p>
-  const [userLogout, { error, loading }] = logout
+  const [logout, { loading, error }] = useUserLogoutMutation(
+    { refetchQueries: [{ query: CurrentUserDocument }] }
+  )
 
   return (
-    <button disabled={loading} onClick={event => userLogout()}>
-      {title}
-    </button>
+    <button disabled={loading} onClick={async (event: React.SyntheticEvent) => {
+      try {
+        const data = await logout()
+        console.log('LogoutButton', data)
+      } catch (error) {
+        console.log('LogoutButton', error)
+      }
+    }}>{title || 'Log out'}</button>
   )
 }
 

+ 0 - 45
frontend/src/user/components/UserNav.tsx

@@ -1,45 +0,0 @@
-import { useState, useContext } from 'react'
-import Link from 'next/link'
-import LogoutButton from './LogoutButton'
-import LoginForm from './LoginForm'
-import { UserContext } from '../hooks'
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { faUser } from '@fortawesome/free-solid-svg-icons'
-
-const UserNav = () => {
-  const [menu, setMenu] = useState(false)
-  const { user } = useContext(UserContext)
-
-  return (
-    <li>
-      <a
-        href=''
-        onClick={ev => {
-          ev.preventDefault()
-          setMenu(!menu)
-        }}
-      >
-        <FontAwesomeIcon icon={faUser} />
-        {user?.data ? user.data.currentUser.name : 'Login'}
-      </a>
-
-      {menu ? (
-        <section className='usermenu'>
-          {user?.data ? (
-            <>
-              <h2>Welcome, {user.data.currentUser.name}</h2>
-              <Link href={{ pathname: 'user' }}>
-                <a>Edit user data</a>
-              </Link>
-              <LogoutButton />
-            </>
-          ) : (
-            <LoginForm />
-          )}
-        </section>
-      ) : null}
-    </li>
-  )
-}
-
-export default UserNav

+ 1 - 2
frontend/src/user/components/__tests__/DeleteUserButton.test.tsx

@@ -1,4 +1,3 @@
-import React from 'react'
 import { shallow } from 'enzyme'
 import { MockedProvider } from '@apollo/client/testing'
 
@@ -29,4 +28,4 @@ describe('testing delete user button', () => {
       </MockedProvider>
     )
   })
-})
+})

+ 6 - 23
frontend/src/user/hooks.tsx

@@ -1,31 +1,14 @@
 import { createContext, FunctionComponent } from 'react'
-import {
-  CurrentUserQuery,
-  useCurrentUserQuery,
-  useUserLogoutMutation,
-  useUserLoginMutation,
-  CurrentUserDocument
-} from '../gql'
+import { CurrentUserQuery, useCurrentUserQuery } from '../gql'
 
-interface IUserContext {
-  user?: ReturnType<typeof useCurrentUserQuery>
-  login?: ReturnType<typeof useUserLoginMutation>
-  logout?: ReturnType<typeof useUserLogoutMutation>
-}
-
-export const UserContext = createContext<IUserContext>({})
+export const UserContext = createContext<
+  CurrentUserQuery['currentUser'] | undefined
+>(undefined)
 
 export const UserProvider: FunctionComponent = ({ children }) => {
-  const user = useCurrentUserQuery({ fetchPolicy: 'network-only' })
-  const logout = useUserLogoutMutation({
-    refetchQueries: [{ query: CurrentUserDocument }]
-  })
-  const login = useUserLoginMutation({
-    refetchQueries: [{ query: CurrentUserDocument }]
-  })
-  console.log('current user', user.data)
+  const user = useCurrentUserQuery()
   return (
-    <UserContext.Provider value={{ user, login, logout }}>
+    <UserContext.Provider value={user.data?.currentUser}>
       {children}
     </UserContext.Provider>
   )

+ 62 - 0
frontend/src/user/user.js

@@ -0,0 +1,62 @@
+import { useState, useEffect } from 'react'
+import Link from 'next/link'
+import { useQuery } from '@apollo/client'
+import { CURRENT_USER } from './graphql'
+import LogoutButton from './components/LogoutButton'
+import LoginForm from './components/LoginForm'
+
+const UserNav = props => {
+  const [menu, setMenu] = useState(false)
+
+  return (
+    <>
+      <a
+        href='' onClick={ev => {
+          ev.preventDefault()
+          setMenu(!menu)
+        }}
+      >sali
+      </a>
+      {menu ? (
+        <UserNavMenu />
+      ) : null}
+    </>
+  )
+}
+
+const UserNavMenu = props => {
+  const { data, loading, error } = useQuery(CURRENT_USER, { fetchPolicy: 'cache-and-network' })
+  console.log('UserNav', data, loading, error && error.message)
+  const user = data && data.me
+
+  if (loading) return <p>Loading user data...</p>
+  if (error) return <p>Error loading user data.</p>
+
+  return (
+    <section className='usermenu'>
+      {user ? (
+        <>
+          <h2>Welcome, {user.name}</h2>
+          <Link href={{ pathname: 'user' }}><a>Edit user data</a></Link>
+          <LogoutButton />
+        </>
+      ) : (
+          <LoginForm />
+        )}
+
+      <style jsx>
+        {`
+          section.usermenu {
+          position: absolute;
+          background: rgba(127,0,0,0.5);
+          }
+        `}
+      </style>
+    </section>
+  )
+}
+
+const User = props => <a />
+
+export { UserNav }
+export default User

+ 0 - 6
frontend/tsconfig.jest.json

@@ -1,6 +0,0 @@
-{
-  "extends": "./tsconfig.json",
-  "compilerOptions": {
-    "jsx": "react"
-  }
-}

+ 2 - 3
frontend/tsconfig.json

@@ -1,6 +1,6 @@
 {
   "compilerOptions": {
-    "target": "es5",
+    "target": "ESNext",
     "module": "ESNext",
     "jsx": "preserve",
     "moduleResolution": "Node",
@@ -10,8 +10,7 @@
     "allowJs": true,
     "skipLibCheck": true,
     "forceConsistentCasingInFileNames": true,
-    "noEmit": false,
-    "outDir": "./tsout",
+    "noEmit": true,
     "esModuleInterop": true,
     "resolveJsonModule": true,
     "isolatedModules": true

Some files were not shown because too many files changed in this diff