Browse Source

Merge pull request #5 from Joystream/babylon

Babylon
Arsen Kondratiev 4 years ago
parent
commit
a4aeb0bc0c
100 changed files with 582 additions and 5894 deletions
  1. 2 2
      cli/src/commands/media/updateChannel.ts
  2. 3 3
      cli/src/commands/media/updateVideo.ts
  3. 128 1
      content-directory-schemas/README.md
  4. 52 0
      content-directory-schemas/examples/createChannel.ts
  5. 76 0
      content-directory-schemas/examples/createVideo.ts
  6. 47 0
      content-directory-schemas/examples/updateChannelTitle.ts
  7. 15 2
      content-directory-schemas/inputs/entityBatches/ContentCategoryBatch.json
  8. 36 1
      content-directory-schemas/inputs/entityBatches/LanguageBatch.json
  9. 4 4
      content-directory-schemas/inputs/entityBatches/VideoBatch.json
  10. 24 1
      content-directory-schemas/inputs/entityBatches/VideoMediaEncodingBatch.json
  11. 4 1
      content-directory-schemas/package.json
  12. 1 1
      content-directory-schemas/scripts/initializeContentDir.ts
  13. 188 61
      content-directory-schemas/src/helpers/InputParser.ts
  14. 0 1
      pioneer/.eslintignore
  15. 1 1
      pioneer/packages/apps-routing/src/joy-roles.ts
  16. 0 1
      pioneer/packages/apps/public/locales/en/index.json
  17. 0 5
      pioneer/packages/apps/public/locales/en/joy-media.json
  18. 1 1
      pioneer/packages/apps/src/Content/NotFound.tsx
  19. 0 0
      pioneer/packages/joy-media/.skip-build
  20. 0 3
      pioneer/packages/joy-media/README.md
  21. 0 2
      pioneer/packages/joy-media/aplayer.d.ts
  22. 0 2
      pioneer/packages/joy-media/dplayer.d.ts
  23. 0 26
      pioneer/packages/joy-media/package.json
  24. 0 204
      pioneer/packages/joy-media/src/DiscoveryProvider.tsx
  25. 0 62
      pioneer/packages/joy-media/src/IterableFile.ts
  26. 0 98
      pioneer/packages/joy-media/src/MediaView.tsx
  27. 0 40
      pioneer/packages/joy-media/src/TransportContext.tsx
  28. 0 400
      pioneer/packages/joy-media/src/Upload.tsx
  29. 0 37
      pioneer/packages/joy-media/src/channels/ChannelAvatar.tsx
  30. 0 23
      pioneer/packages/joy-media/src/channels/ChannelAvatarAndName.tsx
  31. 0 20
      pioneer/packages/joy-media/src/channels/ChannelHeader.tsx
  32. 0 34
      pioneer/packages/joy-media/src/channels/ChannelHelpers.ts
  33. 0 19
      pioneer/packages/joy-media/src/channels/ChannelNameAsLink.tsx
  34. 0 114
      pioneer/packages/joy-media/src/channels/ChannelPreview.tsx
  35. 0 43
      pioneer/packages/joy-media/src/channels/ChannelPreviewStats.tsx
  36. 0 79
      pioneer/packages/joy-media/src/channels/ChannelsByOwner.tsx
  37. 0 34
      pioneer/packages/joy-media/src/channels/ChannelsByOwner.view.tsx
  38. 0 94
      pioneer/packages/joy-media/src/channels/CurationPanel.tsx
  39. 0 219
      pioneer/packages/joy-media/src/channels/EditChannel.tsx
  40. 0 38
      pioneer/packages/joy-media/src/channels/EditChannel.view.tsx
  41. 0 41
      pioneer/packages/joy-media/src/channels/ViewChannel.tsx
  42. 0 35
      pioneer/packages/joy-media/src/channels/ViewChannel.view.tsx
  43. 0 47
      pioneer/packages/joy-media/src/channels/ViewMusicChannel.tsx
  44. 0 34
      pioneer/packages/joy-media/src/channels/ViewVideoChannel.tsx
  45. 0 51
      pioneer/packages/joy-media/src/channels/YouHaveNoChannels.tsx
  46. 0 43
      pioneer/packages/joy-media/src/common/BgImg.tsx
  47. 0 59
      pioneer/packages/joy-media/src/common/FormTabs.tsx
  48. 0 44
      pioneer/packages/joy-media/src/common/MediaDropdownOptions.tsx
  49. 0 202
      pioneer/packages/joy-media/src/common/MediaForms.tsx
  50. 0 145
      pioneer/packages/joy-media/src/common/MediaPlayerView.tsx
  51. 0 159
      pioneer/packages/joy-media/src/common/MediaPlayerWithResolver.tsx
  52. 0 7
      pioneer/packages/joy-media/src/common/NoContentYet.tsx
  53. 0 44
      pioneer/packages/joy-media/src/common/TypeHelpers.ts
  54. 0 17
      pioneer/packages/joy-media/src/common/images.tsx
  55. 0 405
      pioneer/packages/joy-media/src/common/index.scss
  56. 0 10
      pioneer/packages/joy-media/src/entities/ChannelEntity.ts
  57. 0 15
      pioneer/packages/joy-media/src/entities/EntityHelpers.ts
  58. 0 26
      pioneer/packages/joy-media/src/entities/MusicAlbumEntity.ts
  59. 0 18
      pioneer/packages/joy-media/src/entities/MusicTrackEntity.ts
  60. 0 28
      pioneer/packages/joy-media/src/explore/AllChannels.tsx
  61. 0 27
      pioneer/packages/joy-media/src/explore/AllVideos.tsx
  62. 0 54
      pioneer/packages/joy-media/src/explore/ExploreContent.tsx
  63. 0 23
      pioneer/packages/joy-media/src/explore/ExploreContent.view.tsx
  64. 0 92
      pioneer/packages/joy-media/src/explore/PlayContent.tsx
  65. 0 133
      pioneer/packages/joy-media/src/index.scss
  66. 0 82
      pioneer/packages/joy-media/src/index.tsx
  67. 0 14
      pioneer/packages/joy-media/src/mocks/ContentLicense.mock.ts
  68. 0 18
      pioneer/packages/joy-media/src/mocks/CurationStatus.mock.ts
  69. 0 9
      pioneer/packages/joy-media/src/mocks/EntityId.mock.ts
  70. 0 10
      pioneer/packages/joy-media/src/mocks/FeaturedContent.mock.ts
  71. 0 11
      pioneer/packages/joy-media/src/mocks/Language.mock.ts
  72. 0 17
      pioneer/packages/joy-media/src/mocks/MediaObject.mock.ts
  73. 0 34
      pioneer/packages/joy-media/src/mocks/MusicAlbum.mock.ts
  74. 0 31
      pioneer/packages/joy-media/src/mocks/MusicGenre.mock.ts
  75. 0 299
      pioneer/packages/joy-media/src/mocks/MusicMood.mock.ts
  76. 0 192
      pioneer/packages/joy-media/src/mocks/MusicTheme.mock.ts
  77. 0 33
      pioneer/packages/joy-media/src/mocks/MusicTrack.mock.ts
  78. 0 16
      pioneer/packages/joy-media/src/mocks/PublicationStatus.mock.ts
  79. 0 53
      pioneer/packages/joy-media/src/mocks/Video.mock.ts
  80. 0 25
      pioneer/packages/joy-media/src/mocks/VideoCategory.mock.ts
  81. 0 14
      pioneer/packages/joy-media/src/mocks/index.ts
  82. 0 14
      pioneer/packages/joy-media/src/music/EditAlbumModal.tsx
  83. 0 180
      pioneer/packages/joy-media/src/music/EditMusicAlbum.tsx
  84. 0 16
      pioneer/packages/joy-media/src/music/EditMusicAlbum.view.tsx
  85. 0 40
      pioneer/packages/joy-media/src/music/MusicAlbumPreview.tsx
  86. 0 59
      pioneer/packages/joy-media/src/music/MusicAlbumTracks.tsx
  87. 0 59
      pioneer/packages/joy-media/src/music/MusicTrackPreview.tsx
  88. 0 31
      pioneer/packages/joy-media/src/music/MusicTrackReaderPreview.tsx
  89. 0 27
      pioneer/packages/joy-media/src/music/MyMusicAlbums.tsx
  90. 0 164
      pioneer/packages/joy-media/src/music/MyMusicTracks.tsx
  91. 0 91
      pioneer/packages/joy-media/src/music/ReorderableTracks.tsx
  92. 0 168
      pioneer/packages/joy-media/src/schemas/channel/Channel.ts
  93. 0 60
      pioneer/packages/joy-media/src/schemas/general/ContentLicense.ts
  94. 0 60
      pioneer/packages/joy-media/src/schemas/general/CurationStatus.ts
  95. 0 83
      pioneer/packages/joy-media/src/schemas/general/FeaturedContent.ts
  96. 0 60
      pioneer/packages/joy-media/src/schemas/general/Language.ts
  97. 0 60
      pioneer/packages/joy-media/src/schemas/general/MediaObject.ts
  98. 0 60
      pioneer/packages/joy-media/src/schemas/general/PublicationStatus.ts
  99. 0 308
      pioneer/packages/joy-media/src/schemas/music/MusicAlbum.ts
  100. 0 60
      pioneer/packages/joy-media/src/schemas/music/MusicGenre.ts

+ 2 - 2
cli/src/commands/media/updateChannel.ts

@@ -69,9 +69,9 @@ export default class UpdateChannelCommand extends ContentDirectoryCommandBase {
     if (confirmed) {
       saveOutputJson(output, `${inputJson.title}Channel.json`, inputJson)
       const inputParser = InputParser.createWithKnownSchemas(this.getOriginalApi())
-      const updateOperation = await inputParser.createEntityUpdateOperation(inputJson, 'Channel', channelId)
+      const updateOperations = await inputParser.getEntityUpdateOperations(inputJson, 'Channel', channelId)
       this.log('Sending the extrinsic...')
-      await this.sendAndFollowNamedTx(account, 'contentDirectory', 'transaction', [actor, [updateOperation]])
+      await this.sendAndFollowNamedTx(account, 'contentDirectory', 'transaction', [actor, updateOperations])
     }
   }
 }

+ 3 - 3
cli/src/commands/media/updateVideo.ts

@@ -84,13 +84,13 @@ export default class UpdateVideoCommand extends ContentDirectoryCommandBase {
 
     // Parse inputs into operations and send final extrinsic
     const inputParser = InputParser.createWithKnownSchemas(this.getOriginalApi())
-    const videoUpdateOperation = await inputParser.createEntityUpdateOperation(updatedProps, 'Video', videoId)
-    const licenseUpdateOperation = await inputParser.createEntityUpdateOperation(
+    const videoUpdateOperations = await inputParser.getEntityUpdateOperations(updatedProps, 'Video', videoId)
+    const licenseUpdateOperations = await inputParser.getEntityUpdateOperations(
       updatedLicense,
       'License',
       currentValues.license
     )
-    const operations = [videoUpdateOperation, licenseUpdateOperation]
+    const operations = [...videoUpdateOperations, ...licenseUpdateOperations]
     await this.sendAndFollowNamedTx(account, 'contentDirectory', 'transaction', [actor, operations], true)
   }
 }

+ 128 - 1
content-directory-schemas/README.md

@@ -1,4 +1,4 @@
-# Content directory json schemas and inputs
+# Content directory tooling
 
 ## Definitions
 
@@ -143,6 +143,133 @@ Besides that, a Typescript code can be written to generate some inputs (ie. usin
 
 There are a lot of other potential use-cases, but for the purpose of this documentation it should be enough to mention there exists this very easy way of converting `.schema.json` files into Typescript interfaces.
 
+## Using as library
+
+The `content-directory-schemas` directory of the monorepo is constructed in such a way, that it should be possible to use it as library and import from it json schemas, types (mentioned in `Typescript support` section) and tools to, for example, convert entity input like this described in the `Entity batches` section into `CreateEntity`, `AddSchemaSupportToEntity` and/or `UpdateEntityPropertyValues` operations.
+
+### Examples
+
+The best way to ilustrate this would be by providing some examples:
+
+#### Creating a channel
+```
+  import { InputParser } from 'cd-schemas'
+  import { ChannelEntity } from 'cd-schemas/types/entities/ChannelEntity'
+  // Other imports...
+
+  async main() {
+    // Initialize the api, SENDER_KEYPAIR and SENDER_MEMBER_ID...
+
+    const channel: ChannelEntity = {
+      title: 'Example channel',
+      description: 'This is an example channel',
+      language: { existing: { code: 'EN' } },
+      coverPhotoUrl: '',
+      avatarPhotoURL: '',
+      isPublic: true,
+    }
+
+    const parser = InputParser.createWithKnownSchemas(api, [
+      {
+        className: 'Channel',
+        entries: [channel],
+      },
+    ])
+
+    const operations = await parser.getEntityBatchOperations()
+    await api.tx.contentDirectory
+      .transaction({ Member: SENDER_MEMBER_ID }, operations)
+      .signAndSend(SENDER_KEYPAIR)
+  }
+```
+_Full example with comments can be found in `content-directory-schemas/examples/createChannel.ts` and ran with `yarn workspace cd-schemas example:createChannel`_
+
+#### Creating a video
+```
+import { InputParser } from 'cd-schemas'
+import { VideoEntity } from 'cd-schemas/types/entities/VideoEntity'
+// ...
+
+async main() {
+  // ...
+
+  const video: VideoEntity = {
+    title: 'Example video',
+    description: 'This is an example video',
+    language: { existing: { code: 'EN' } },
+    category: { existing: { name: 'Education' } },
+    channel: { existing: { title: 'Example channel' } },
+    media: {
+      new: {
+        encoding: { existing: { name: 'H.263_MP4' } },
+        pixelHeight: 600,
+        pixelWidth: 800,
+        location: {
+          new: {
+            httpMediaLocation: {
+              new: { url: 'https://testnet.joystream.org/' },
+            },
+          },
+        },
+      },
+    },
+    license: {
+      new: {
+        knownLicense: {
+          existing: { code: 'CC_BY' },
+        },
+      },
+    },
+    duration: 3600,
+    thumbnailURL: '',
+    isExplicit: false,
+    isPublic: true,
+  }
+
+  const parser = InputParser.createWithKnownSchemas(api, [
+    {
+      className: 'Video',
+      entries: [video],
+    },
+  ])
+
+  const operations = await parser.getEntityBatchOperations()
+  await api.tx.contentDirectory
+    .transaction({ Member: SENDER_MEMBER_ID }, operations)
+    .signAndSend(SENDER_KEYPAIR)
+}
+```
+_Full example with comments can be found in `content-directory-schemas/examples/createVideo.ts` and ran with `yarn workspace cd-schemas example:createChannel`_
+
+#### Update channel title
+
+```
+import { InputParser } from 'cd-schemas'
+import { ChannelEntity } from 'cd-schemas/types/entities/ChannelEntity'
+// ...
+
+async function main() {
+  // ...
+
+  const channelUpdateInput: Partial<ChannelEntity> = {
+    title: 'Updated channel title',
+  }
+
+  const parser = InputParser.createWithKnownSchemas(api)
+
+  const CHANNEL_ID = await parser.findEntityIdByUniqueQuery({ title: 'Example channel' }, 'Channel')
+
+  const updateOperations = await parser.getEntityUpdateOperations(channelUpdateInput, 'Channel', CHANNEL_ID)
+
+  await api.tx.contentDirectory
+    .transaction({ Member: SENDER_MEMBER_ID }, [updateOperation])
+    .signAndSend(SENDER_KEYPAIR)
+}
+```
+_Full example with comments can be found in `content-directory-schemas/examples/updateChannelTitle.ts` and ran with `yarn workspace cd-schemas example:updateChannelTitle`_
+
+Note: Updates can also inlucde `new` and `existing` keywords. In case `new` is specified inside the update - `CreateEntity` and `AddSchemaSupportToEntity` operations will be included as part of the operations returned by `InputParser.getEntityUpdateOperations`.
+
 ## Current limitations
 
 Some limitations that should be dealt with in the nearest future:

+ 52 - 0
content-directory-schemas/examples/createChannel.ts

@@ -0,0 +1,52 @@
+import { ApiPromise, WsProvider } from '@polkadot/api'
+import { types as joyTypes } from '@joystream/types'
+import { Keyring } from '@polkadot/keyring'
+// Import input parser and channel entity from cd-schemas (we use it as library here)
+import { InputParser } from 'cd-schemas'
+import { ChannelEntity } from 'cd-schemas/types/entities/ChannelEntity'
+
+async function main() {
+  // Initialize the api
+  const provider = new WsProvider('ws://127.0.0.1:9944')
+  const api = await ApiPromise.create({ provider, types: joyTypes })
+
+  // Get Alice keypair
+  const keyring = new Keyring()
+  keyring.addFromUri('//Alice', undefined, 'sr25519')
+  const [ALICE] = keyring.getPairs()
+
+  const channel: ChannelEntity = {
+    title: 'Example channel',
+    description: 'This is an example channel',
+    // We can use "existing" syntax to reference either an on-chain entity or other entity that's part of the same batch.
+    // Here we reference language that we assume was added by initialization script (initialize:dev), as it is part of
+    // input/entityBatches/LanguageBatch.json
+    language: { existing: { code: 'EN' } },
+    coverPhotoUrl: '',
+    avatarPhotoURL: '',
+    isPublic: true,
+  }
+  // Create the parser with known entity schemas (the ones in content-directory-schemas/inputs)
+  const parser = InputParser.createWithKnownSchemas(
+    api,
+    // The second argument is an array of entity batches, following standard entity batch syntax ({ className, entries }):
+    [
+      {
+        className: 'Channel',
+        entries: [channel], // We could specify multiple entries here, but in this case we only need one
+      },
+    ]
+  )
+  // We parse the input into CreateEntity and AddSchemaSupportToEntity operations
+  const operations = await parser.getEntityBatchOperations()
+  await api.tx.contentDirectory
+    .transaction(
+      { Member: 0 }, // We use member with id 0 as actor (in this case we assume this is Alice)
+      operations // We provide parsed operations as second argument
+    )
+    .signAndSend(ALICE)
+}
+
+main()
+  .then(() => process.exit())
+  .catch(console.error)

+ 76 - 0
content-directory-schemas/examples/createVideo.ts

@@ -0,0 +1,76 @@
+import { ApiPromise, WsProvider } from '@polkadot/api'
+import { types as joyTypes } from '@joystream/types'
+import { Keyring } from '@polkadot/keyring'
+// Import input parser and video entity from cd-schemas (we use it as library here)
+import { InputParser } from 'cd-schemas'
+import { VideoEntity } from 'cd-schemas/types/entities/VideoEntity'
+
+async function main() {
+  // Initialize the api
+  const provider = new WsProvider('ws://127.0.0.1:9944')
+  const api = await ApiPromise.create({ provider, types: joyTypes })
+
+  // Get Alice keypair
+  const keyring = new Keyring()
+  keyring.addFromUri('//Alice', undefined, 'sr25519')
+  const [ALICE] = keyring.getPairs()
+
+  const video: VideoEntity = {
+    title: 'Example video',
+    description: 'This is an example video',
+    // We reference existing language and category by their unique properties with "existing" syntax
+    // (those referenced here are part of inputs/entityBatches)
+    language: { existing: { code: 'EN' } },
+    category: { existing: { name: 'Education' } },
+    // We use the same "existing" syntax to reference a channel by unique property (title)
+    // In this case it's a channel that we created in createChannel example
+    channel: { existing: { title: 'Example channel' } },
+    media: {
+      // We use "new" syntax to sygnalize we want to create a new VideoMedia entity that will be related to this Video entity
+      new: {
+        // We use "exisiting" enconding from inputs/entityBatches/VideoMediaEncodingBatch.json
+        encoding: { existing: { name: 'H.263_MP4' } },
+        pixelHeight: 600,
+        pixelWidth: 800,
+        // We create nested VideoMedia->MediaLocation->HttpMediaLocation relations using the "new" syntax
+        location: { new: { httpMediaLocation: { new: { url: 'https://testnet.joystream.org/' } } } },
+      },
+    },
+    // Here we use combined "new" and "existing" syntaxes to create Video->License->KnownLicense relations
+    license: {
+      new: {
+        knownLicense: {
+          // This license can be found in inputs/entityBatches/KnownLicenseBatch.json
+          existing: { code: 'CC_BY' },
+        },
+      },
+    },
+    duration: 3600,
+    thumbnailURL: '',
+    isExplicit: false,
+    isPublic: true,
+  }
+  // Create the parser with known entity schemas (the ones in content-directory-schemas/inputs)
+  const parser = InputParser.createWithKnownSchemas(
+    api,
+    // The second argument is an array of entity batches, following standard entity batch syntax ({ className, entries }):
+    [
+      {
+        className: 'Video',
+        entries: [video], // We could specify multiple entries here, but in this case we only need one
+      },
+    ]
+  )
+  // We parse the input into CreateEntity and AddSchemaSupportToEntity operations
+  const operations = await parser.getEntityBatchOperations()
+  await api.tx.contentDirectory
+    .transaction(
+      { Member: 0 }, // We use member with id 0 as actor (in this case we assume this is Alice)
+      operations // We provide parsed operations as second argument
+    )
+    .signAndSend(ALICE)
+}
+
+main()
+  .then(() => process.exit())
+  .catch(console.error)

+ 47 - 0
content-directory-schemas/examples/updateChannelTitle.ts

@@ -0,0 +1,47 @@
+import { ApiPromise, WsProvider } from '@polkadot/api'
+import { types as joyTypes } from '@joystream/types'
+import { Keyring } from '@polkadot/keyring'
+// Import input parser and channel entity from cd-schemas (we use it as library here)
+import { InputParser } from 'cd-schemas'
+import { ChannelEntity } from 'cd-schemas/types/entities/ChannelEntity'
+
+async function main() {
+  // Initialize the api
+  const provider = new WsProvider('ws://127.0.0.1:9944')
+  const api = await ApiPromise.create({ provider, types: joyTypes })
+
+  // Get Alice keypair
+  const keyring = new Keyring()
+  keyring.addFromUri('//Alice', undefined, 'sr25519')
+  const [ALICE] = keyring.getPairs()
+
+  // Create partial channel entity, only containing the fields we wish to update
+  const channelUpdateInput: Partial<ChannelEntity> = {
+    title: 'Updated channel title',
+  }
+
+  // Create the parser with known entity schemas (the ones in content-directory-schemas/inputs)
+  const parser = InputParser.createWithKnownSchemas(api)
+
+  // We can reuse InputParser's `findEntityIdByUniqueQuery` method to find entityId of the channel we
+  // created in ./createChannel.ts example (normally we would probably use some other way to do it, ie.: query node)
+  const CHANNEL_ID = await parser.findEntityIdByUniqueQuery({ title: 'Example channel' }, 'Channel')
+
+  // Use getEntityUpdateOperations to parse the update input
+  const updateOperations = await parser.getEntityUpdateOperations(
+    channelUpdateInput,
+    'Channel', // Class name
+    CHANNEL_ID // Id of the entity we want to update
+  )
+
+  await api.tx.contentDirectory
+    .transaction(
+      { Member: 0 }, // We use member with id 0 as actor (in this case we assume this is Alice)
+      updateOperations // In this case this will be just a single UpdateEntityPropertyValues operation
+    )
+    .signAndSend(ALICE)
+}
+
+main()
+  .then(() => process.exit())
+  .catch(console.error)

+ 15 - 2
content-directory-schemas/inputs/entityBatches/ContentCategoryBatch.json

@@ -1,7 +1,20 @@
 {
   "className": "ContentCategory",
   "entries": [
-    { "name": "Cartoon", "description": "Content which is a cartoon or related to cartoons" },
-    { "name": "Sports", "description": "Content related to sports" }
+    { "name": "Film & Animation" },
+    { "name": "Autos & Vehicles" },
+    { "name": "Music" },
+    { "name": "Pets & Animals" },
+    { "name": "Sports" },
+    { "name": "Travel & Events" },
+    { "name": "Gaming" },
+    { "name": "People & Blogs" },
+    { "name": "Comedy" },
+    { "name": "Entertainment" },
+    { "name": "News & Politics" },
+    { "name": "Howto & Style" },
+    { "name": "Education" },
+    { "name": "Science & Technology" },
+    { "name": "Nonprofits & Activism" }
   ]
 }

+ 36 - 1
content-directory-schemas/inputs/entityBatches/LanguageBatch.json

@@ -3,6 +3,41 @@
   "entries": [
     { "code": "EN", "name": "English" },
     { "code": "RU", "name": "Russian" },
-    { "code": "DE", "name": "German" }
+    { "code": "DE", "name": "German" },
+    { "code": "IT", "name": "Italian" },
+    { "code": "ES", "name": "Spanish" },
+    { "code": "UK", "name": "Ukrainian" },
+    { "code": "CZ", "name": "Czech" },
+    { "code": "PL", "name": "Polish" },
+    { "code": "RO", "name": "Romanian" },
+    { "code": "NO", "name": "Norwegian" },
+    { "code": "AR", "name": "Arabic" },
+    { "code": "BG", "name": "Bulgarian" },
+    { "code": "ZH", "name": "Chinese" },
+    { "code": "HR", "name": "Croatian" },
+    { "code": "DA", "name": "Danish" },
+    { "code": "NL", "name": "Dutch" },
+    { "code": "FI", "name": "Finnish" },
+    { "code": "FR", "name": "French" },
+    { "code": "EL", "name": "Greek" },
+    { "code": "HI", "name": "Hindi" },
+    { "code": "HU", "name": "Hungarian" },
+    { "code": "ID", "name": "Indonesian" },
+    { "code": "GA", "name": "Irish" },
+    { "code": "IS", "name": "Icelandic" },
+    { "code": "JA", "name": "Japanese" },
+    { "code": "KO", "name": "Korean" },
+    { "code": "LT", "name": "Lithuanian" },
+    { "code": "MK", "name": "Macedonian" },
+    { "code": "PT", "name": "Portuguese" },
+    { "code": "SR", "name": "Serbian" },
+    { "code": "SK", "name": "Slovak" },
+    { "code": "SL", "name": "Slovenian" },
+    { "code": "SV", "name": "Swedish" },
+    { "code": "TH", "name": "Thai" },
+    { "code": "BO", "name": "Tibetan" },
+    { "code": "TR", "name": "Turkish" },
+    { "code": "VI", "name": "Vietnamese" },
+    { "code": "CY", "name": "Welsh" }
   ]
 }

+ 4 - 4
content-directory-schemas/inputs/entityBatches/VideoBatch.json

@@ -5,14 +5,14 @@
       "title": "Caminades 2",
       "description": "Caminandes 2: Gran Dillama",
       "language": { "existing": { "code": "EN" } },
-      "category": { "existing": { "name": "Cartoon" } },
+      "category": { "existing": { "name": "Film & Animation" } },
       "channel": { "existing": { "title": "Joystream Cartoons" } },
       "duration": 146,
       "hasMarketing": false,
       "isPublic": true,
       "media": {
         "new": {
-          "encoding": { "existing": { "name": "MPEG4" } },
+          "encoding": { "existing": { "name": "H.264_MP4" } },
           "location": {
             "new": {
               "httpMediaLocation": {
@@ -34,14 +34,14 @@
       "title": "Caminades 3",
       "description": "Caminandes 3: Llamigos",
       "language": { "existing": { "code": "EN" } },
-      "category": { "existing": { "name": "Cartoon" } },
+      "category": { "existing": { "name": "Film & Animation" } },
       "channel": { "existing": { "title": "Joystream Cartoons" } },
       "duration": 150,
       "hasMarketing": false,
       "isPublic": true,
       "media": {
         "new": {
-          "encoding": { "existing": { "name": "MPEG4" } },
+          "encoding": { "existing": { "name": "H.264_MP4" } },
           "location": {
             "new": {
               "httpMediaLocation": {

+ 24 - 1
content-directory-schemas/inputs/entityBatches/VideoMediaEncodingBatch.json

@@ -1,4 +1,27 @@
 {
   "className": "VideoMediaEncoding",
-  "entries": [{ "name": "MPEG4" }]
+  "entries": [
+    { "name": "H.263_MP4" },
+    { "name": "H.263_3GP" },
+    { "name": "H.263_AVI" },
+    { "name": "H.264_MP4" },
+    { "name": "H.264_3GP" },
+    { "name": "H.264_AVI" },
+    { "name": "H.264_MKV" },
+    { "name": "H.265_MP4" },
+    { "name": "H.265_3GP" },
+    { "name": "H.265_AVI" },
+    { "name": "VP8_WEBM" },
+    { "name": "VP8_MP4" },
+    { "name": "VP8_AVI" },
+    { "name": "VP8_MKV" },
+    { "name": "VP9_WEBM" },
+    { "name": "VP9_MP4" },
+    { "name": "VP9_AVI" },
+    { "name": "VP9_MKV" },
+    { "name": "AV1_MP4" },
+    { "name": "MVC_MP4" },
+    { "name": "MVC_3GP" },
+    { "name": "MVC_MKV" }
+  ]
 }

+ 4 - 1
content-directory-schemas/package.json

@@ -16,7 +16,10 @@
     "generate:all": "yarn generate:entity-schemas && yarn generate:types",
     "initialize:alice-as-lead": "ts-node ./scripts/devInitAliceLead.ts",
     "initialize:content-dir": "ts-node ./scripts/initializeContentDir.ts",
-    "initialize:dev": "yarn initialize:alice-as-lead && yarn initialize:content-dir"
+    "initialize:dev": "yarn initialize:alice-as-lead && yarn initialize:content-dir",
+    "example:createChannel": "ts-node ./examples/createChannel.ts",
+    "example:createVideo": "ts-node ./examples/createVideo.ts",
+    "example:updateChannelTitle": "ts-node ./examples/updateChannelTitle.ts"
   },
   "dependencies": {
     "ajv": "6.12.5",

+ 1 - 1
content-directory-schemas/scripts/initializeContentDir.ts

@@ -54,7 +54,7 @@ async function main() {
       4
     )
   )
-  console.log('Sending Transaction extrinsic...')
+  console.log(`Sending Transaction extrinsic (${entityOperations.length} operations)...`)
   await txHelper.sendAndCheck(
     ALICE,
     [api.tx.contentDirectory.transaction({ Lead: null }, entityOperations)],

+ 188 - 61
content-directory-schemas/src/helpers/InputParser.ts

@@ -7,6 +7,9 @@ import {
   ParametrizedPropertyValue,
   PropertyId,
   PropertyType,
+  EntityId,
+  Entity,
+  ParametrizedClassPropertyValue,
 } from '@joystream/types/content-directory'
 import { isSingle, isReference } from './propertyType'
 import { ApiPromise } from '@polkadot/api'
@@ -22,12 +25,15 @@ export class InputParser {
   private batchInputs: EntityBatch[]
   private createEntityOperations: OperationType[] = []
   private addSchemaToEntityOprations: OperationType[] = []
+  private updateEntityPropertyValuesOperations: OperationType[] = []
   private entityIndexByUniqueQueryMap = new Map<string, number>()
+  private entityIdByUniqueQueryMap = new Map<string, number>()
   private entityByUniqueQueryCurrentIndex = 0
   private classIdByNameMap = new Map<string, number>()
   private classMapInitialized = false
+  private entityIdByUniqueQueryMapInitialized = false
 
-  static createWithKnownSchemas(api: ApiPromise, entityBatches?: EntityBatch[]) {
+  static createWithKnownSchemas(api: ApiPromise, entityBatches?: EntityBatch[]): InputParser {
     return new InputParser(
       api,
       [],
@@ -59,6 +65,56 @@ export class InputParser {
     this.classMapInitialized = true
   }
 
+  // Initialize entityIdByUniqueQueryMap with entities fetched from the chain
+  private async initializeEntityIdByUniqueQueryMap() {
+    if (this.entityIdByUniqueQueryMapInitialized) {
+      return
+    }
+
+    await this.initializeClassMap() // Initialize if not yet initialized
+
+    // Get entity entries
+    const entityEntries: [EntityId, Entity][] = (
+      await this.api.query.contentDirectory.entityById.entries()
+    ).map(([storageKey, entity]) => [storageKey.args[0] as EntityId, entity])
+
+    entityEntries.forEach(([entityId, entity]) => {
+      const classId = entity.class_id.toNumber()
+      const className = Array.from(this.classIdByNameMap.entries()).find(([, id]) => id === classId)?.[0]
+      if (!className) {
+        // Class not found - skip
+        return
+      }
+      let schema: AddClassSchema
+      try {
+        schema = this.schemaByClassName(className)
+      } catch (e) {
+        // Input schema not found - skip
+        return
+      }
+      const valuesEntries = Array.from(entity.getField('values').entries())
+      schema.newProperties.forEach(({ name, unique }, index) => {
+        if (!unique) {
+          return // Skip non-unique properties
+        }
+        const storedValue = valuesEntries.find(([propertyId]) => propertyId.toNumber() === index)?.[1]
+        if (
+          storedValue === undefined ||
+          // If unique value is Bool, it's almost definitely empty, so we skip it
+          (storedValue.isOfType('Single') && storedValue.asType('Single').isOfType('Bool'))
+        ) {
+          // Skip empty values (not all unique properties are required)
+          return
+        }
+        const simpleValue = storedValue.getValue().toJSON()
+        const hash = this.getUniqueQueryHash({ [name]: simpleValue }, schema.className)
+        this.entityIdByUniqueQueryMap.set(hash, entityId.toNumber())
+      })
+    })
+
+    this.entityIdByUniqueQueryMapInitialized = true
+  }
+
   private schemaByClassName(className: string) {
     const foundSchema = this.schemaInputs.find((data) => data.className === className)
     if (!foundSchema) {
@@ -84,6 +140,20 @@ export class InputParser {
     return foundIndex
   }
 
+  // Seatch for entity by { [uniquePropName]: [uniquePropVal] } on chain
+  async findEntityIdByUniqueQuery(uniquePropVal: Record<string, any>, className: string): Promise<number> {
+    await this.initializeEntityIdByUniqueQueryMap()
+    const hash = this.getUniqueQueryHash(uniquePropVal, className)
+    const foundId = this.entityIdByUniqueQueryMap.get(hash)
+    if (foundId === undefined) {
+      throw new Error(
+        `findEntityIdByUniqueQuery failed for class ${className} and query: ${JSON.stringify(uniquePropVal)}`
+      )
+    }
+
+    return foundId
+  }
+
   private getClassIdByName(className: string): number {
     const classId = this.classIdByNameMap.get(className)
     if (classId === undefined) {
@@ -129,70 +199,124 @@ export class InputParser {
     ++this.entityByUniqueQueryCurrentIndex
   }
 
-  private createParametrizedPropertyValues(
+  private async createParametrizedPropertyValues(
     entityInput: Record<string, any>,
     schema: AddClassSchema,
-    customHandler?: (property: Property, value: any) => ParametrizedPropertyValue | undefined
-  ) {
-    return Object.entries(entityInput)
-      .filter(([, pValue]) => pValue !== undefined)
-      .map(([propertyName, propertyValue]) => {
-        const schemaPropertyIndex = schema.newProperties.findIndex((p) => p.name === propertyName)
-        const schemaProperty = schema.newProperties[schemaPropertyIndex]
-
-        let value = customHandler && customHandler(schemaProperty, propertyValue)
-        if (value === undefined) {
-          value = createType('ParametrizedPropertyValue', {
-            InputPropertyValue: this.parsePropertyType(schemaProperty.property_type).toInputPropertyValue(
-              propertyValue
-            ),
-          })
-        }
+    customHandler?: (property: Property, value: any) => Promise<ParametrizedPropertyValue | undefined>
+  ): Promise<ParametrizedClassPropertyValue[]> {
+    const filteredInput = Object.entries(entityInput).filter(([, pValue]) => pValue !== undefined)
+    const parametrizedClassPropValues: ParametrizedClassPropertyValue[] = []
+
+    for (const [propertyName, propertyValue] of filteredInput) {
+      const schemaPropertyIndex = schema.newProperties.findIndex((p) => p.name === propertyName)
+      const schemaProperty = schema.newProperties[schemaPropertyIndex]
+
+      let value = customHandler && (await customHandler(schemaProperty, propertyValue))
+      if (value === undefined) {
+        value = createType('ParametrizedPropertyValue', {
+          InputPropertyValue: this.parsePropertyType(schemaProperty.property_type).toInputPropertyValue(propertyValue),
+        })
+      }
 
-        return { in_class_index: schemaPropertyIndex, value }
+      parametrizedClassPropValues.push(
+        createType('ParametrizedClassPropertyValue', {
+          in_class_index: schemaPropertyIndex,
+          value,
+        })
+      )
+    }
+
+    return parametrizedClassPropValues
+  }
+
+  private async existingEntityQueryToParametrizedPropertyValue(className: string, uniquePropVal: Record<string, any>) {
+    try {
+      // First - try to find in existing batches
+      const entityIndex = this.findEntityIndexByUniqueQuery(uniquePropVal, className)
+      return createType('ParametrizedPropertyValue', { InternalEntityJustAdded: entityIndex })
+    } catch (e) {
+      // If not found - fallback to chain search
+      const entityId = await this.findEntityIdByUniqueQuery(uniquePropVal, className)
+      return createType('ParametrizedPropertyValue', {
+        InputPropertyValue: { Single: { Reference: entityId } },
       })
+    }
   }
 
-  private parseEntityInput(entityInput: Record<string, any>, schema: AddClassSchema) {
-    const parametrizedPropertyValues = this.createParametrizedPropertyValues(entityInput, schema, (property, value) => {
-      // Custom handler for references
-      const { property_type: propertyType } = property
-      if (isSingle(propertyType) && isReference(propertyType.Single)) {
-        const refEntitySchema = this.schemaByClassName(propertyType.Single.Reference.className)
-        if (Object.keys(value).includes('new')) {
-          const entityIndex = this.parseEntityInput(value.new, refEntitySchema)
-          return createType('ParametrizedPropertyValue', { InternalEntityJustAdded: entityIndex })
-        } else if (Object.keys(value).includes('existing')) {
-          const entityIndex = this.findEntityIndexByUniqueQuery(value.existing, refEntitySchema.className)
-          return createType('ParametrizedPropertyValue', { InternalEntityJustAdded: entityIndex })
+  // parseEntityInput Overloads
+  private parseEntityInput(entityInput: Record<string, any>, schema: AddClassSchema): Promise<number>
+  private parseEntityInput(
+    entityInput: Record<string, any>,
+    schema: AddClassSchema,
+    updatedEntityId: number
+  ): Promise<void>
+
+  // Parse entity input. Speficy "updatedEntityId" only if want to parse into update operation!
+  private async parseEntityInput(
+    entityInput: Record<string, any>,
+    schema: AddClassSchema,
+    updatedEntityId?: number
+  ): Promise<void | number> {
+    const parametrizedPropertyValues = await this.createParametrizedPropertyValues(
+      entityInput,
+      schema,
+      async (property, value) => {
+        // Custom handler for references
+        const { property_type: propertyType } = property
+        if (isSingle(propertyType) && isReference(propertyType.Single)) {
+          const refEntitySchema = this.schemaByClassName(propertyType.Single.Reference.className)
+          if (Object.keys(value).includes('new')) {
+            const entityIndex = await this.parseEntityInput(value.new, refEntitySchema)
+            return createType('ParametrizedPropertyValue', { InternalEntityJustAdded: entityIndex })
+          } else if (Object.keys(value).includes('existing')) {
+            return this.existingEntityQueryToParametrizedPropertyValue(refEntitySchema.className, value.existing)
+          }
         }
+        return undefined
       }
-      return undefined
-    })
-
-    // Add operations
-    const createEntityOperationIndex = this.createEntityOperations.length
-    const classId = this.classIdByNameMap.get(schema.className)
-    this.createEntityOperations.push(createType('OperationType', { CreateEntity: { class_id: classId } }))
-    this.addSchemaToEntityOprations.push(
-      createType('OperationType', {
-        AddSchemaSupportToEntity: {
-          schema_id: 0,
-          entity_id: { InternalEntityJustAdded: createEntityOperationIndex },
-          parametrized_property_values: parametrizedPropertyValues,
-        },
-      })
     )
 
-    // Return CreateEntity operation index
-    return createEntityOperationIndex
+    if (updatedEntityId) {
+      // Update operation
+      this.updateEntityPropertyValuesOperations.push(
+        createType('OperationType', {
+          UpdatePropertyValues: {
+            entity_id: { ExistingEntity: updatedEntityId },
+            new_parametrized_property_values: parametrizedPropertyValues,
+          },
+        })
+      )
+    } else {
+      // Add operations (createEntity, AddSchemaSupportToEntity)
+      const createEntityOperationIndex = this.createEntityOperations.length
+      const classId = this.getClassIdByName(schema.className)
+      this.createEntityOperations.push(createType('OperationType', { CreateEntity: { class_id: classId } }))
+      this.addSchemaToEntityOprations.push(
+        createType('OperationType', {
+          AddSchemaSupportToEntity: {
+            schema_id: 0,
+            entity_id: { InternalEntityJustAdded: createEntityOperationIndex },
+            parametrized_property_values: parametrizedPropertyValues,
+          },
+        })
+      )
+
+      // Return CreateEntity operation index
+      return createEntityOperationIndex
+    }
   }
 
   private reset() {
     this.entityIndexByUniqueQueryMap = new Map<string, number>()
+    this.entityIdByUniqueQueryMapInitialized = false
+
     this.classIdByNameMap = new Map<string, number>()
+    this.classMapInitialized = false
+
     this.createEntityOperations = []
     this.addSchemaToEntityOprations = []
+    this.updateEntityPropertyValuesOperations = []
+
     this.entityByUniqueQueryCurrentIndex = 0
   }
 
@@ -204,10 +328,12 @@ export class InputParser {
       batch.entries.forEach((entityInput) => this.includeEntityInputInUniqueQueryMap(entityInput, entitySchema))
     })
     // Then - parse into actual operations
-    this.batchInputs.forEach((batch) => {
+    for (const batch of this.batchInputs) {
       const entitySchema = this.schemaByClassName(batch.className)
-      batch.entries.forEach((entityInput) => this.parseEntityInput(entityInput, entitySchema))
-    })
+      for (const entityInput of batch.entries) {
+        await this.parseEntityInput(entityInput, entitySchema)
+      }
+    }
 
     const operations = [...this.createEntityOperations, ...this.addSchemaToEntityOprations]
     this.reset()
@@ -215,21 +341,22 @@ export class InputParser {
     return operations
   }
 
-  public async createEntityUpdateOperation(
-    entityInput: Record<string, any>,
+  public async getEntityUpdateOperations(
+    input: Record<string, any>,
     className: string,
     entityId: number
-  ): Promise<OperationType> {
+  ): Promise<OperationType[]> {
     await this.initializeClassMap()
     const schema = this.schemaByClassName(className)
-    const parametrizedPropertyValues = this.createParametrizedPropertyValues(entityInput, schema)
+    await this.parseEntityInput(input, schema, entityId)
+    const operations = [
+      ...this.createEntityOperations,
+      ...this.addSchemaToEntityOprations,
+      ...this.updateEntityPropertyValuesOperations,
+    ]
+    this.reset()
 
-    return createType('OperationType', {
-      UpdatePropertyValues: {
-        entity_id: { ExistingEntity: entityId },
-        new_parametrized_property_values: parametrizedPropertyValues,
-      },
-    })
+    return operations
   }
 
   public async parseAddClassSchemaExtrinsic(inputData: AddClassSchema) {

+ 0 - 1
pioneer/.eslintignore

@@ -1,6 +1,5 @@
 **/build/*
 **/coverage/*
 **/node_modules/*
-packages/joy-media/**
 .eslintrc.js
 i18next-scanner.config.js

+ 1 - 1
pioneer/packages/apps-routing/src/joy-roles.ts

@@ -7,7 +7,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
     Component: Roles,
     display: {
       needsApi: [
-        'query.contentWorkingGroup.mint',
+        'query.contentDirectoryWorkingGroup.mint',
         'query.storageWorkingGroup.mint'
       ]
     },

+ 0 - 1
pioneer/packages/apps/public/locales/en/index.json

@@ -22,7 +22,6 @@
   "apps-routing.json",
   "apps.json",
   "joy-election.json",
-  "joy-media.json",
   "joy-members.json",
   "joy-proposals.json",
   "joy-roles.json",

+ 0 - 5
pioneer/packages/apps/public/locales/en/joy-media.json

@@ -1,5 +0,0 @@
-{
-  "Explore": "Explore",
-  "My channels": "My channels",
-  "My videos": "My videos"
-}

+ 1 - 1
pioneer/packages/apps/src/Content/NotFound.tsx

@@ -7,7 +7,7 @@ import { Redirect } from 'react-router';
 
 function NotFound (): React.ReactElement {
   return (
-    <Redirect to='/media' />
+    <Redirect to='/tokenomics' />
   );
 }
 

+ 0 - 0
pioneer/packages/joy-media/.skip-build


+ 0 - 3
pioneer/packages/joy-media/README.md

@@ -1,3 +0,0 @@
-# Media content module for Joystream node
-
-This module works with multi-media content such as audio and video.

+ 0 - 2
pioneer/packages/joy-media/aplayer.d.ts

@@ -1,2 +0,0 @@
-// No offical definitions available
-declare module 'react-aplayer';

+ 0 - 2
pioneer/packages/joy-media/dplayer.d.ts

@@ -1,2 +0,0 @@
-// No offical definitions available
-declare module 'react-dplayer';

+ 0 - 26
pioneer/packages/joy-media/package.json

@@ -1,26 +0,0 @@
-{
-  "name": "@polkadot/joy-media",
-  "version": "0.1.1",
-  "description": "Media content module for Joystream node",
-  "main": "index.js",
-  "scripts": {},
-  "author": "Joystream contributors",
-  "maintainers": [],
-  "dependencies": {
-    "@babel/runtime": "^7.10.5",
-    "@polkadot/joy-utils": "^0.1.1",
-    "@polkadot/react-components": "0.51.1",
-    "@polkadot/react-query": "0.51.1",
-    "@types/mime-types": "^2.1.0",
-    "@types/react-beautiful-dnd": "^11.0.3",
-    "aplayer": "^1.10.1",
-    "dplayer": "1.25.0",
-    "ipfs-only-hash": "^1.0.2",
-    "iso-639-1": "^2.1.0",
-    "lodash": "^4.17.11",
-    "mime-types": "^2.1.22",
-    "react-aplayer": "^1.0.0",
-    "react-beautiful-dnd": "^12.0.0",
-    "react-dplayer": "^0.2.3"
-  }
-}

+ 0 - 204
pioneer/packages/joy-media/src/DiscoveryProvider.tsx

@@ -1,204 +0,0 @@
-import React, { useState, useEffect, useContext, createContext } from 'react';
-import { Message } from 'semantic-ui-react';
-import axios, { CancelToken } from 'axios';
-
-import { StorageProviderId } from '@joystream/types/working-group';
-import { Vec } from '@polkadot/types';
-import { Url } from '@joystream/types/discovery';
-import ApiContext from '@polkadot/react-api/ApiContext';
-import { ApiProps } from '@polkadot/react-api/types';
-import { JoyInfo } from '@polkadot/joy-utils/react/components';
-import { componentName } from '@polkadot/joy-utils/react/helpers';
-import { isObjectWithProperties } from '@polkadot/joy-utils/functions/misc';
-
-export type BootstrapNodes = {
-  bootstrapNodes?: Url[];
-};
-
-export type DiscoveryProvider = {
-  resolveAssetEndpoint: (provider: StorageProviderId, contentId?: string, cancelToken?: CancelToken) => Promise<string>;
-  reportUnreachable: (provider: StorageProviderId) => void;
-};
-
-export type DiscoveryProviderProps = {
-  discoveryProvider: DiscoveryProvider;
-};
-
-// return string Url with last `/` removed
-function normalizeUrl (url: string | Url): string {
-  const st: string = url.toString();
-
-  if (st.endsWith('/')) {
-    return st.substring(0, st.length - 1);
-  }
-
-  return st.toString();
-}
-
-type ProviderStats = {
-  assetApiEndpoint: string;
-  unreachableReports: number;
-  resolvedAt: number;
-}
-
-function newDiscoveryProvider ({ bootstrapNodes }: BootstrapNodes): DiscoveryProvider {
-  const stats = new Map<string, ProviderStats>();
-
-  const resolveAssetEndpoint = async (
-    storageProvider: StorageProviderId,
-    contentId?: string,
-    cancelToken?: CancelToken
-  ) => {
-    const providerKey = storageProvider.toString();
-
-    let stat = stats.get(providerKey);
-
-    if (
-      (!stat || (stat && (Date.now() > (stat.resolvedAt + (10 * 60 * 1000))))) &&
-      bootstrapNodes
-    ) {
-      for (let n = 0; n < bootstrapNodes.length; n++) {
-        const discoveryUrl = normalizeUrl(bootstrapNodes[n]);
-
-        try {
-          // eslint-disable-next-line no-new
-          new URL(discoveryUrl);
-        } catch (err) {
-          continue;
-        }
-
-        const serviceInfoQuery = `${discoveryUrl}/discover/v0/${storageProvider.toString()}`;
-
-        try {
-          console.log(`Resolving ${providerKey} using ${discoveryUrl}`);
-
-          const serviceInfo = await axios.get<unknown>(serviceInfoQuery, { cancelToken });
-
-          if (!serviceInfo) {
-            continue;
-          }
-
-          const { data } = serviceInfo;
-
-          if (!isObjectWithProperties(data, 'serialized') || typeof data.serialized !== 'string') {
-            continue;
-          }
-
-          const dataParsed = JSON.parse(data.serialized) as unknown;
-
-          if (
-            !isObjectWithProperties(dataParsed, 'asset') ||
-            !isObjectWithProperties(dataParsed.asset, 'endpoint') ||
-            typeof dataParsed.asset.endpoint !== 'string'
-          ) {
-            continue;
-          }
-
-          stats.set(providerKey, {
-            assetApiEndpoint: normalizeUrl(dataParsed.asset.endpoint),
-            unreachableReports: 0,
-            resolvedAt: Date.now()
-          });
-          break;
-        } catch (err) {
-          console.log(err);
-
-          if (axios.isCancel(err)) {
-            throw err;
-          }
-
-          continue;
-        }
-      }
-    }
-
-    stat = stats.get(providerKey);
-
-    console.log(stat);
-
-    if (stat) {
-      return `${stat.assetApiEndpoint}/asset/v0/${contentId || ''}`;
-    }
-
-    throw new Error('Resolving failed.');
-  };
-
-  const reportUnreachable = (provider: StorageProviderId) => {
-    const key = provider.toString();
-    const stat = stats.get(key);
-
-    if (stat) {
-      stat.unreachableReports = stat.unreachableReports + 1;
-    }
-  };
-
-  return { resolveAssetEndpoint, reportUnreachable };
-}
-
-const DiscoveryProviderContext = createContext<DiscoveryProvider>(undefined as unknown as DiscoveryProvider);
-
-export const DiscoveryProviderProvider = (props: React.PropsWithChildren<Record<any, unknown>>) => {
-  const api: ApiProps = useContext(ApiContext);
-  const [provider, setProvider] = useState<DiscoveryProvider | undefined>();
-  const [loaded, setLoaded] = useState<boolean | undefined>();
-
-  useEffect(() => {
-    const load = async () => {
-      if (loaded || !api) return;
-
-      console.log('Discovery Provider: Loading bootstrap node from Substrate...');
-      const bootstrapNodes = await api.api.query.discovery.bootstrapEndpoints() as Vec<Url>;
-
-      setProvider(newDiscoveryProvider({ bootstrapNodes }));
-      setLoaded(true);
-      console.log('Discovery Provider: Initialized');
-    };
-
-    void load();
-  }, [loaded]);
-
-  if (!api || !api.isApiReady) {
-    // Substrate API is not ready yet.
-    return null;
-  }
-
-  if (!provider) {
-    return (
-      <Message info className='JoyMainStatus'>
-        <Message.Header>Initializing Content Discovery Provider</Message.Header>
-        <div style={{ marginTop: '1rem' }}>
-          Loading bootstrap nodes... Please wait.
-        </div>
-      </Message>
-    );
-  }
-
-  return (
-    <DiscoveryProviderContext.Provider value={provider}>
-      {props.children}
-    </DiscoveryProviderContext.Provider>
-  );
-};
-
-export const useDiscoveryProvider = () =>
-  useContext(DiscoveryProviderContext);
-
-export function withDiscoveryProvider (Component: React.ComponentType<DiscoveryProviderProps>) {
-  const ResultComponent: React.FunctionComponent<Record<any, unknown>> = (props: React.PropsWithChildren<Record<any, unknown>>) => {
-    const discoveryProvider = useDiscoveryProvider();
-
-    if (!discoveryProvider) {
-      return <JoyInfo title={'Please wait...'}>Loading discovery provider.</JoyInfo>;
-    }
-
-    return (
-      <Component {...props} discoveryProvider={discoveryProvider}>
-        {props.children}
-      </Component>
-    );
-  };
-
-  ResultComponent.displayName = `withDiscoveryProvider(${componentName(Component)})`;
-
-  return ResultComponent;
-}

+ 0 - 62
pioneer/packages/joy-media/src/IterableFile.ts

@@ -1,62 +0,0 @@
-// Based on
-// https://gist.github.com/grishgrigoryan/bf6222d16d72cb28620399d27e83eb22
-
-interface IConfig{
-  chunkSize: number;
-}
-
-const DEFAULT_CHUNK_SIZE: number = 64 * 1024; // 64K
-
-export class IterableFile implements AsyncIterable<Buffer> {
-  private reader: FileReader;
-  private file: File
-  private config: IConfig = { chunkSize: DEFAULT_CHUNK_SIZE }
-
-  constructor (file: File, config: Partial<IConfig> = {}) {
-    this.file = file;
-    this.reader = new FileReader();
-    Object.assign(this.config, config);
-  }
-
-  [Symbol.asyncIterator] () {
-    return this.readFile();
-  }
-
-  get chunkSize () {
-    return this.config.chunkSize;
-  }
-
-  get fileSize () {
-    return this.file.size;
-  }
-
-  readBlobAsBuffer (blob: Blob): Promise<Buffer> {
-    return new Promise((resolve, reject) => {
-      this.reader.onload = (e) => {
-        e.target?.result && resolve(typeof e.target.result === 'string' ? Buffer.from(e.target.result) : Buffer.from(e.target.result));
-        e.target?.error && reject(e.target.error);
-      };
-
-      this.reader.readAsArrayBuffer(blob);
-    });
-  }
-
-  async * readFile () {
-    let offset = 0;
-    let blob;
-    let result;
-
-    while (offset < this.fileSize) {
-      blob = this.file.slice(offset, this.chunkSize + offset);
-      result = await this.readBlobAsBuffer(blob);
-      offset += result.length;
-      yield result;
-    }
-  }
-}
-
-// Usage:
-//  let iterableFile = new IterableFile(file)
-//  for await (const chunk: Buffer of iterableFile) {
-//      doSomethingWithBuffer(chunk)
-//  }

+ 0 - 98
pioneer/packages/joy-media/src/MediaView.tsx

@@ -1,98 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { MediaTransport } from './transport';
-import { MemberId } from '@joystream/types/members';
-import { useMyMembership } from '@polkadot/joy-utils/react/hooks';
-import { useTransportContext } from './TransportContext';
-import { withMembershipRequired } from '@polkadot/joy-utils/react/hocs/guards';
-import { useApi } from '@polkadot/react-hooks';
-import { ApiPromise } from '@polkadot/api';
-import { isObjectWithProperties } from '@polkadot/joy-utils/functions/misc';
-
-type InitialPropsWithMembership<A> = A & {
-  myAddress?: string;
-  myMemberId?: MemberId;
-}
-
-type ResolverProps<A> = InitialPropsWithMembership<A> & {
-  transport: MediaTransport;
-  api: ApiPromise;
-}
-
-type BaseProps<A, B> = {
-  component: React.ComponentType<A & B>;
-  unresolvedView?: React.ReactElement;
-  resolveProps?: (props: ResolverProps<A>) => Promise<B>;
-
-  /**
-   * Array of property names that can trigger re-render of the view,
-   * if values of such properties changed.
-   */
-  triggers?: (keyof A)[];
-
-  /** Set `true` if only members should have access to this component. `false` by default. */
-  membersOnly?: boolean;
-}
-
-function serializeTrigger (val: unknown): number | boolean | string | undefined {
-  if (['number', 'boolean', 'string'].includes(typeof val)) {
-    return val as number | boolean | string;
-  } else if (isObjectWithProperties(val, 'toString') && typeof val.toString === 'function') {
-    return val.toString() as string;
-  } else {
-    return undefined;
-  }
-}
-
-export function MediaView<A extends Record<string, unknown> = Record<string, unknown>, B extends Record<string, unknown> = Record<string, unknown>> (baseProps: BaseProps<A, B>) {
-  function InnerView (initialProps: A & B) {
-    const { component: Component, resolveProps, triggers = [], unresolvedView = null } = baseProps;
-
-    const transport = useTransportContext();
-    const { myAddress, myMemberId } = useMyMembership();
-    const { api } = useApi();
-    const resolverProps = { ...initialProps, transport, api, myAddress, myMemberId };
-
-    const [resolvedProps, setResolvedProps] = useState({} as B);
-    const [propsResolved, setPropsResolved] = useState(false);
-
-    const initialDeps = triggers.map((propName) => serializeTrigger(initialProps[propName]));
-    const rerenderDeps = [...initialDeps, myAddress];
-
-    useEffect(() => {
-      async function doResolveProps () {
-        if (typeof resolveProps !== 'function') return;
-
-        console.log('Resolving props of media view');
-
-        // Transport session allows us to cache loaded channels, entites and classes
-        // during the render of this view:
-        transport.openSession();
-        setResolvedProps(await resolveProps(resolverProps));
-        transport.closeSession();
-        setPropsResolved(true);
-      }
-
-      if (!transport) {
-        console.error('Transport is not defined');
-      } else {
-        void doResolveProps();
-      }
-    }, rerenderDeps);
-
-    console.log('Rerender deps of Media View:', rerenderDeps);
-
-    const renderResolving = () => {
-      return unresolvedView || <div className='ui active centered inline loader' />;
-    };
-
-    return propsResolved
-      ? <Component {...initialProps} {...resolvedProps} />
-      : renderResolving();
-  }
-
-  const { membersOnly = false } = baseProps;
-
-  return membersOnly
-    ? withMembershipRequired(InnerView)
-    : InnerView;
-}

+ 0 - 40
pioneer/packages/joy-media/src/TransportContext.tsx

@@ -1,40 +0,0 @@
-import React, { useEffect, useState, useContext, createContext } from 'react';
-import { MediaTransport } from './transport';
-import { MockTransport } from './transport.mock';
-import { SubstrateTransport } from './transport.substrate';
-import ApiContext from '@polkadot/react-api/ApiContext';
-import { ApiProps } from '@polkadot/react-api/types';
-
-export const TransportContext = createContext<MediaTransport>(undefined as unknown as MediaTransport);
-
-export const useTransportContext = () =>
-  useContext(TransportContext);
-
-export const MockTransportProvider = (props: React.PropsWithChildren<Record<any, unknown>>) =>
-  <TransportContext.Provider value={new MockTransport()}>
-    {props.children}
-  </TransportContext.Provider>;
-
-export const SubstrateTransportProvider = (props: React.PropsWithChildren<Record<any, unknown>>) => {
-  const api: ApiProps = useContext(ApiContext);
-  const [transport, setTransport] = useState<SubstrateTransport>();
-  const [loaded, setLoaded] = useState<boolean>();
-
-  useEffect(() => {
-    if (!loaded && api && api.isApiReady) {
-      setTransport(new SubstrateTransport(api));
-      setLoaded(true);
-    }
-  }, [loaded]);
-
-  if (!transport) {
-    // Substrate API is not ready yet.
-    return null;
-  }
-
-  return (
-    <TransportContext.Provider value={transport}>
-      {props.children}
-    </TransportContext.Provider>
-  );
-};

+ 0 - 400
pioneer/packages/joy-media/src/Upload.tsx

@@ -1,400 +0,0 @@
-import React from 'react';
-import BN from 'bn.js';
-import axios, { CancelTokenSource, AxiosError, AxiosRequestConfig } from 'axios';
-import { History } from 'history';
-import { Progress, Message } from 'semantic-ui-react';
-
-import { registry } from '@joystream/types';
-import { InputFileAsync, TxButton, JoyInfo, Loading } from '@polkadot/joy-utils/react/components';
-import { ApiProps } from '@polkadot/react-api/types';
-import { I18nProps } from '@polkadot/react-components/types';
-import { SubmittableResult } from '@polkadot/api';
-import { Option } from '@polkadot/types/codec';
-import { withMulti, withApi } from '@polkadot/react-api';
-import { formatNumber } from '@polkadot/util';
-
-import translate from './translate';
-import { fileNameWoExt } from './utils';
-import { ContentId, DataObject } from '@joystream/types/media';
-import { MyAccountProps } from '@polkadot/joy-utils/react/hocs/accounts';
-import { withOnlyMembers } from '@polkadot/joy-utils/react/hocs/guards';
-import { DiscoveryProviderProps, withDiscoveryProvider } from './DiscoveryProvider';
-
-import IpfsHash from 'ipfs-only-hash';
-import { ChannelId } from '@joystream/types/content-working-group';
-import { EditVideoView } from './upload/EditVideo.view';
-
-import { IterableFile } from './IterableFile';
-import { StorageProviderId } from '@joystream/types/working-group';
-import { normalizeError, isObjectWithProperties } from '@polkadot/joy-utils/functions/misc';
-
-const MAX_FILE_SIZE_MB = 500;
-const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;
-
-type Props = ApiProps & I18nProps & DiscoveryProviderProps & MyAccountProps & {
-  channelId: ChannelId;
-  history?: History;
-  match: {
-    params: {
-      channelId: string;
-    };
-  };
-};
-
-type State = {
-  error?: string;
-  file?: File;
-  computingHash: boolean;
-  ipfs_cid?: string;
-  newContentId: ContentId;
-  discovering: boolean;
-  uploading: boolean;
-  sendingTx: boolean;
-  progress: number;
-  cancelSource: CancelTokenSource;
-};
-
-const defaultState = (): State => ({
-  error: undefined,
-  file: undefined,
-  computingHash: false,
-  ipfs_cid: undefined,
-  newContentId: ContentId.generate(registry),
-  discovering: false,
-  uploading: false,
-  sendingTx: false,
-  progress: 0,
-  cancelSource: axios.CancelToken.source()
-});
-
-class Upload extends React.PureComponent<Props, State> {
-  state = defaultState();
-
-  componentWillUnmount () {
-    this.setState({
-      discovering: false,
-      uploading: false
-    });
-
-    const { cancelSource } = this.state;
-
-    cancelSource.cancel('unmounting');
-  }
-
-  render () {
-    return (
-      <div className='UploadBox'>
-        {this.renderContent()}
-      </div>
-    );
-  }
-
-  private renderContent () {
-    const { error, uploading, discovering, computingHash, sendingTx } = this.state;
-
-    if (error) return this.renderError(error);
-    else if (discovering) return this.renderDiscovering();
-    else if (uploading) return this.renderUploading();
-    else if (computingHash) return this.renderComputingHash();
-    else if (sendingTx) return this.renderSendingTx();
-    else return this.renderFileInput();
-  }
-
-  private renderError (error: string) {
-    return (
-      <Message error className='JoyMainStatus'>
-        <Message.Header>Failed to upload your file</Message.Header>
-        <p>{error}</p>
-        <button className='ui button' onClick={this.resetForm}>Start over</button>
-      </Message>
-    );
-  }
-
-  private resetForm = () => {
-    const { cancelSource } = this.state;
-
-    this.setState({
-      ...defaultState(),
-      cancelSource
-    });
-  }
-
-  private renderUploading () {
-    const { file, newContentId, progress, error } = this.state;
-
-    if (!file || !file.name) return <JoyInfo title='Loading...' />;
-
-    const success = !error && progress >= 100;
-    const { history, match: { params: { channelId } }, api } = this.props;
-
-    return <div style={{ width: '100%' }}>
-      {this.renderProgress()}
-      {success &&
-        <EditVideoView
-          channelId={api.createType('ChannelId', channelId)}
-          contentId={newContentId}
-          fileName={fileNameWoExt(file.name)}
-          history={history}
-        />
-      }
-    </div>;
-  }
-
-  private renderSendingTx () {
-    return <JoyInfo title='Please wait...'><Loading text='Waiting for the transaction confirmation...' /></JoyInfo>;
-  }
-
-  private renderDiscovering () {
-    return <JoyInfo title={'Please wait...'}>Contacting storage provider.</JoyInfo>;
-  }
-
-  private renderProgress () {
-    const { progress, error } = this.state;
-    const active = !error && progress < 100;
-    const success = !error && progress >= 100;
-
-    let label = '';
-
-    if (active) {
-      label = 'Your file is uploading. Please keep this page open until it\'s done.';
-    } else if (success) {
-      label = 'Uploaded! Click "Publish" button to make your file live.';
-    }
-
-    return <Progress
-      className='UploadProgress'
-      progress={success}
-      percent={progress}
-      active={active}
-      success={success}
-      label={label}
-    />;
-  }
-
-  private renderFileInput () {
-    const { file } = this.state;
-    const file_size = file ? file.size : 0;
-    const file_name = file ? file.name : '';
-
-    return <div className='UploadSelectForm'>
-      <InputFileAsync
-        label=''
-        withLabel={false}
-        className={`UploadInputFile ${file_name ? 'FileSelected' : ''}`}
-        placeholder={
-          <div>
-            <div><i className='cloud upload icon'></i></div>
-            <div>{file_name
-              ? `${file_name} (${formatNumber(file_size)} bytes)`
-              : <>
-                <div>Drag and drop either video or audio file here.</div>
-                <div>Your file should not be more than {MAX_FILE_SIZE_MB} MB.</div>
-              </>
-            }</div>
-          </div>
-        }
-        onChange={this.onFileSelected}
-      />
-      {file_name && <div className='UploadButtonBox'>
-        <TxButton
-          label={'Upload'}
-          isDisabled={!file_name}
-          tx={'dataDirectory.addContent'}
-          params={this.buildTxParams()}
-          onClick={(sendTx) => {
-            this.setState({ sendingTx: true });
-            sendTx();
-          }}
-          txSuccessCb={ this.onDataObjectCreated }
-          txFailedCb={() => { this.setState({ sendingTx: false }); }}
-        />
-      </div>}
-    </div>;
-  }
-
-  private onFileSelected = (file: File) => {
-    if (!file.size) {
-      this.setState({ error: 'You cannot upload an empty file.' });
-    } else if (file.size > MAX_FILE_SIZE_BYTES) {
-      this.setState({
-        error:
-        `You can't upload files larger than ${MAX_FILE_SIZE_MB} MBytes in size.`
-      });
-    } else {
-      this.setState({ file, computingHash: true });
-      void this.startComputingHash();
-    }
-  }
-
-  private async startComputingHash () {
-    const { file } = this.state;
-
-    if (!file) {
-      return this.hashComputationComplete(undefined, 'No file passed to hasher');
-    }
-
-    try {
-      const iterableFile = new IterableFile(file, { chunkSize: 65535 });
-      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
-      const ipfs_cid = (await IpfsHash.of(iterableFile)) as string;
-
-      this.hashComputationComplete(ipfs_cid);
-    } catch (err) {
-      return this.hashComputationComplete(undefined, err);
-    }
-  }
-
-  private hashComputationComplete (ipfs_cid: string | undefined, error?: string) {
-    if (!error) {
-      console.log('Computed IPFS hash:', ipfs_cid);
-    }
-
-    this.setState({
-      computingHash: false,
-      ipfs_cid,
-      error
-    });
-  }
-
-  private renderComputingHash () {
-    return <JoyInfo title='Processing your file. Please wait...' />;
-  }
-
-  private buildTxParams = () => {
-    const { file, newContentId, ipfs_cid } = this.state;
-
-    if (!file || !ipfs_cid) return [];
-
-    // TODO get corresponding data type id based on file content
-    const dataObjectTypeId = new BN(1);
-    const { myMemberId } = this.props;
-
-    return [myMemberId, newContentId, dataObjectTypeId, new BN(file.size), ipfs_cid];
-  }
-
-  private onDataObjectCreated = async (_txResult: SubmittableResult) => {
-    this.setState({ sendingTx: false, discovering: true });
-
-    const { api } = this.props;
-    const { newContentId } = this.state;
-    let dataObject: Option<DataObject>;
-
-    try {
-      dataObject = await api.query.dataDirectory.dataObjectByContentId(newContentId) as Option<DataObject>;
-    } catch (err) {
-      this.setState({
-        error: normalizeError(err),
-        discovering: false
-      });
-
-      return;
-    }
-
-    const { discovering } = this.state;
-
-    if (!discovering) {
-      return;
-    }
-
-    if (dataObject.isSome) {
-      const storageProvider = dataObject.unwrap().liaison;
-
-      void this.uploadFileTo(storageProvider);
-    } else {
-      this.setState({
-        error: 'No Storage Provider assigned to process upload',
-        discovering: false
-      });
-    }
-  }
-
-  private uploadFileTo = async (storageProvider: StorageProviderId) => {
-    const { file, newContentId, cancelSource } = this.state;
-
-    if (!file || !file.size) {
-      this.setState({
-        error: 'No file to upload!',
-        discovering: false
-      });
-
-      return;
-    }
-
-    const contentId = newContentId.encode();
-    const config: AxiosRequestConfig = {
-      headers: {
-        // TODO uncomment this once the issue fixed:
-        // https://github.com/Joystream/storage-node-joystream/issues/16
-        // 'Content-Type': file.type
-        'Content-Type': '' // <-- this is a temporary hack
-      },
-      cancelToken: cancelSource.token,
-      onUploadProgress: (progressEvent: unknown) => {
-        if (
-          !isObjectWithProperties(progressEvent, 'loaded', 'total') ||
-          typeof progressEvent.loaded !== 'number' ||
-          typeof progressEvent.total !== 'number'
-        ) {
-          return;
-        }
-
-        const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
-
-        this.setState({
-          progress: percentCompleted
-        });
-      }
-    };
-
-    const { discoveryProvider } = this.props;
-    let url: string;
-
-    try {
-      url = await discoveryProvider.resolveAssetEndpoint(storageProvider, contentId, cancelSource.token);
-    } catch (err) {
-      return this.setState({
-        error: `Failed to contact storage provider: ${normalizeError(err)}`,
-        discovering: false
-      });
-    }
-
-    const { discovering } = this.state;
-
-    if (!discovering) {
-      return;
-    }
-
-    // TODO: validate url .. must start with http
-
-    this.setState({ discovering: false, uploading: true, progress: 0 });
-
-    try {
-      await axios.put<{ message: string }>(url, file, config);
-    } catch (e) {
-      const err = e as unknown;
-
-      this.setState({ progress: 0, error: normalizeError(err), uploading: false });
-
-      if (axios.isCancel(err)) {
-        return;
-      }
-
-      const response = isObjectWithProperties(err, 'response')
-        ? (err as AxiosError).response
-        : undefined;
-
-      if (!response || (response.status >= 500 && response.status <= 504)) {
-        // network connection error
-        discoveryProvider.reportUnreachable(storageProvider);
-      }
-    }
-  }
-}
-
-export const UploadWithRouter = withMulti(
-  Upload,
-  translate,
-  withApi,
-  withOnlyMembers,
-  withDiscoveryProvider
-);

+ 0 - 37
pioneer/packages/joy-media/src/channels/ChannelAvatar.tsx

@@ -1,37 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { ChannelEntity } from '../entities/ChannelEntity';
-import { BgImg } from '../common/BgImg';
-import { DEFAULT_THUMBNAIL_URL } from '../common/images';
-
-const defaultSizePx = 75;
-
-export type ChannelAvatarSize = 'big' | 'default' | 'small';
-
-type Props = {
-  channel: ChannelEntity;
-  size?: ChannelAvatarSize;
-}
-
-function sizeToPx (size: ChannelAvatarSize): number {
-  switch (size) {
-    case 'big': return 100;
-    case 'small': return 35;
-    case 'default': return defaultSizePx;
-    default: return defaultSizePx;
-  }
-}
-
-export function ChannelAvatar (props: Props) {
-  const { channel, size = 'default' } = props;
-
-  return (
-    <Link to={`/media/channels/${channel.id}`}>
-      <BgImg
-        className={'ChannelAvatar ' + size}
-        url={channel.avatar || DEFAULT_THUMBNAIL_URL}
-        size={sizeToPx(size)}
-      />
-    </Link>
-  );
-}

+ 0 - 23
pioneer/packages/joy-media/src/channels/ChannelAvatarAndName.tsx

@@ -1,23 +0,0 @@
-import React from 'react';
-import { ChannelEntity } from '../entities/ChannelEntity';
-import { ChannelAvatar } from './ChannelAvatar';
-import { ChannelNameAsLink } from './ChannelNameAsLink';
-
-type Props = {
-  channel: ChannelEntity;
-}
-
-export const ChannelAvatarAndName = (props: Props) => {
-  const { channel } = props;
-
-  return (
-    <div className={'ChannelPreview small'}>
-      <ChannelAvatar channel={channel} size='small' />
-      <div className='ChannelDetails'>
-        <h3 className='ChannelTitle' style={{ display: 'block' }}>
-          <ChannelNameAsLink channel={channel} />
-        </h3>
-      </div>
-    </div>
-  );
-};

+ 0 - 20
pioneer/packages/joy-media/src/channels/ChannelHeader.tsx

@@ -1,20 +0,0 @@
-import React from 'react';
-import { BgImg } from '../common/BgImg';
-import { ChannelEntity } from '../entities/ChannelEntity';
-import { ChannelPreview } from './ChannelPreview';
-
-type Props = {
-  channel: ChannelEntity;
-}
-
-export function ChannelHeader (props: Props) {
-  const { channel } = props;
-  const { banner } = channel;
-
-  return (
-    <div className='ChannelHeader'>
-      {banner && <BgImg className='ChannelCover' url={banner} />}
-      <ChannelPreview channel={channel} size='big' withDescription />
-    </div>
-  );
-}

+ 0 - 34
pioneer/packages/joy-media/src/channels/ChannelHelpers.ts

@@ -1,34 +0,0 @@
-import { AccountId } from '@polkadot/types/interfaces';
-import { ChannelType } from '../schemas/channel/Channel';
-import { ChannelPublicationStatusAllValues } from '@joystream/types/content-working-group';
-
-export const ChannelPublicationStatusDropdownOptions =
-  ChannelPublicationStatusAllValues
-    .map((x) => ({ key: x, value: x, text: x }));
-
-export const isVideoChannel = (channel: ChannelType) => {
-  return channel.content === 'Video';
-};
-
-export const isMusicChannel = (channel: ChannelType) => {
-  return channel.content === 'Music';
-};
-
-export const isAccountAChannelOwner = (channel?: ChannelType, account?: AccountId | string): boolean => {
-  return (channel && account) ? channel.roleAccount.eq(account) : false;
-};
-
-export function isPublicChannel (channel: ChannelType): boolean {
-  return (
-    channel.publicationStatus === 'Public' &&
-    channel.curationStatus !== 'Censored'
-  );
-}
-
-export function isCensoredChannel (channel: ChannelType): boolean {
-  return channel.curationStatus === 'Censored';
-}
-
-export function isVerifiedChannel (channel: ChannelType): boolean {
-  return channel.verified;
-}

+ 0 - 19
pioneer/packages/joy-media/src/channels/ChannelNameAsLink.tsx

@@ -1,19 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { ChannelEntity } from '../entities/ChannelEntity';
-
-type Props = {
-  channel: ChannelEntity;
-  className?: string;
-  style?: React.CSSProperties;
-}
-
-export const ChannelNameAsLink = (props: Props) => {
-  const { channel, className, style } = props;
-
-  return (
-    <Link to={`/media/channels/${channel.id}`} className={className} style={style}>
-      {channel.title || channel.handle}
-    </Link>
-  );
-};

+ 0 - 114
pioneer/packages/joy-media/src/channels/ChannelPreview.tsx

@@ -1,114 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import ReactMarkdown from 'react-markdown';
-import { Icon, Label, SemanticICONS, SemanticCOLORS } from 'semantic-ui-react';
-import { ChannelEntity } from '../entities/ChannelEntity';
-import { ChannelAvatar, ChannelAvatarSize } from './ChannelAvatar';
-import { isPublicChannel, isMusicChannel, isVideoChannel, isAccountAChannelOwner, isVerifiedChannel } from './ChannelHelpers';
-
-import { useMyMembership } from '@polkadot/joy-utils/react/hooks';
-import { nonEmptyStr } from '@polkadot/joy-utils/functions/misc';
-import { CurationPanel } from './CurationPanel';
-import { ChannelNameAsLink } from './ChannelNameAsLink';
-
-type ChannelPreviewProps = {
-  channel: ChannelEntity;
-  size?: ChannelAvatarSize;
-  withSubtitle?: boolean;
-  withDescription?: boolean;
-};
-
-export const ChannelPreview = (props: ChannelPreviewProps) => {
-  const { myAccountId } = useMyMembership();
-  const { channel, size, withSubtitle = true, withDescription } = props;
-
-  let subtitle: string | undefined;
-  let icon: 'music' | 'film' | undefined;
-
-  if (isMusicChannel(channel)) {
-    subtitle = 'Music channel';
-    icon = 'music';
-  } else if (isVideoChannel(channel)) {
-    subtitle = 'Video channel';
-    icon = 'film';
-  }
-
-  let visibilityIcon: SemanticICONS = 'eye';
-  let visibilityColor: SemanticCOLORS = 'green';
-  let visibilityText = 'Public';
-
-  if (!isPublicChannel(channel)) {
-    visibilityIcon = 'eye slash';
-    visibilityColor = 'orange';
-    visibilityText = 'Unlisted';
-  }
-
-  return <>
-    <div className={`ChannelPreview ${size || ''}`}>
-
-      <ChannelAvatar channel={channel} size={size} />
-
-      <div className='ChannelDetails'>
-        <h3 className='ChannelTitle' style={{ display: 'block' }}>
-          <ChannelNameAsLink channel={channel} style={{ marginRight: '1rem' }} />
-
-          {isAccountAChannelOwner(channel, myAccountId) &&
-            <div style={{ float: 'right' }}>
-
-              <Link to={`/media/channels/${channel.id}/edit`} className='ui button basic' style={{ marginRight: '1rem' }}>
-                <i className='icon pencil' />
-                Edit
-              </Link>
-
-              <Link to={`/media/channels/${channel.id}/upload`} className='ui button basic primary'>
-                <i className='icon upload' />
-                Upload {channel.content}
-              </Link>
-
-            </div>
-          }
-        </h3>
-
-        <div className='ChannelSubtitle'>
-
-          {withSubtitle && subtitle &&
-            <span style={{ marginRight: '1rem' }}>
-              {icon && <i className={`icon ${icon}`} />}
-              {subtitle}
-            </span>
-          }
-
-          <Label basic color={visibilityColor} style={{ marginRight: '1rem' }}>
-            <Icon name={visibilityIcon} />
-            {visibilityText}
-          </Label>
-
-          {channel.curationStatus !== 'Normal' &&
-            <Label basic color='red'>
-              <Icon name='dont' />
-              Channel {channel.curationStatus}
-              {' '}<Icon name='question circle outline' size='small' />
-            </Label>
-          }
-
-          {isVerifiedChannel(channel) &&
-            <Label basic color='blue'>
-              <i className='icon checkmark'/>
-              Verified
-            </Label>
-          }
-        </div>
-
-        <CurationPanel channel={channel} />
-
-        {withDescription && nonEmptyStr(channel.description) &&
-          <ReactMarkdown className='JoyMemo--full ChannelDesc' source={channel.description} linkTarget='_blank' />
-        }
-      </div>
-
-      {/* // TODO uncomment when we calculate reward and count of videos in channel: */}
-      {/* <ChannelStats channel={channel} /> */}
-
-    </div>
-  </>;
-};

+ 0 - 43
pioneer/packages/joy-media/src/channels/ChannelPreviewStats.tsx

@@ -1,43 +0,0 @@
-import React from 'react';
-import { Statistic } from 'semantic-ui-react';
-
-import { ChannelEntity } from '../entities/ChannelEntity';
-import { formatNumber } from '@polkadot/util';
-
-type Props = {
-  channel: ChannelEntity;
-};
-
-export const ChannelPreviewStats = (props: Props) => {
-  const { channel } = props;
-  const statSize = 'tiny';
-
-  let itemsPublishedLabel = '';
-
-  if (channel.content === 'Video') {
-    itemsPublishedLabel = 'Videos';
-  } else if (channel.content === 'Music') {
-    itemsPublishedLabel = 'Music tracks';
-  }
-
-  return (
-    <div className='ChannelStats'>
-      <div>
-        <Statistic size={statSize}>
-          <Statistic.Label>Reward earned</Statistic.Label>
-          <Statistic.Value>
-            {formatNumber(channel.rewardEarned)}
-            &nbsp;<span style={{ fontSize: '1.5rem' }}>JOY</span>
-          </Statistic.Value>
-        </Statistic>
-      </div>
-
-      <div style={{ marginTop: '1rem' }}>
-        <Statistic size={statSize}>
-          <Statistic.Label>{itemsPublishedLabel}</Statistic.Label>
-          <Statistic.Value>{formatNumber(channel.contentItemsCount)}</Statistic.Value>
-        </Statistic>
-      </div>
-    </div>
-  );
-};

+ 0 - 79
pioneer/packages/joy-media/src/channels/ChannelsByOwner.tsx

@@ -1,79 +0,0 @@
-import React, { useState } from 'react';
-import { Link } from 'react-router-dom';
-import { Segment, Tab } from 'semantic-ui-react';
-import { AccountId } from '@polkadot/types/interfaces';
-import { ChannelEntity } from '../entities/ChannelEntity';
-import { YouHaveNoChannels } from './YouHaveNoChannels';
-import { ChannelContentTypeValue } from '@joystream/types/content-working-group';
-import { ChannelPreview } from './ChannelPreview';
-
-export type ChannelsByOwnerProps = {
-  accountId: AccountId;
-  suspended?: boolean;
-  channels?: ChannelEntity[];
-};
-
-const TabsAndChannels = (props: ChannelsByOwnerProps) => {
-  const { channels: allChannels = [] } = props;
-  const [channels, setChannels] = useState(allChannels);
-
-  let videoChannelsCount = 0;
-  let musicChannelsCount = 0;
-
-  allChannels.forEach((x) => {
-    if (x.content === 'Video') {
-      videoChannelsCount++;
-    } else if (x.content === 'Music') {
-      musicChannelsCount++;
-    }
-  });
-
-  const panes = [
-    { menuItem: `All channels (${allChannels.length})` },
-    { menuItem: `Video channels (${videoChannelsCount})` },
-    { menuItem: `Music channels (${musicChannelsCount})` }
-  ];
-
-  const contentTypeByTabIndex: Array<ChannelContentTypeValue | undefined> =
-    [undefined, 'Video', 'Music'];
-
-  const switchTab = (activeIndex: number) => {
-    const activeContentType = contentTypeByTabIndex[activeIndex];
-
-    if (activeContentType === undefined) {
-      setChannels(allChannels);
-    } else {
-      setChannels(allChannels.filter(
-        (x) => x.content === activeContentType)
-      );
-    }
-  };
-
-  return <>
-    <Tab
-      panes={panes}
-      menu={{ secondary: true }}
-      style={{ display: 'inline-flex', margin: '0 2rem 1rem 0' }}
-      onTabChange={(_e, data) => switchTab(data.activeIndex as number)}
-    />
-    <Link to={'/media/channels/new'} className='ui button'>
-      <i className='icon plus' />
-      Create Channel
-    </Link>
-    {channels.map((channel) =>
-      <Segment key={channel.id} padded style={{ backgroundColor: '#fff' }}>
-        <ChannelPreview channel={channel} withDescription />
-      </Segment>
-    )}
-  </>;
-};
-
-export function ChannelsByOwner (props: ChannelsByOwnerProps) {
-  const { suspended = false, channels = [] } = props;
-
-  return <div className='JoyChannels'>
-    {!channels.length
-      ? <YouHaveNoChannels suspended={suspended} />
-      : <TabsAndChannels {...props} />
-    }</div>;
-}

+ 0 - 34
pioneer/packages/joy-media/src/channels/ChannelsByOwner.view.tsx

@@ -1,34 +0,0 @@
-import React from 'react';
-import { RouteComponentProps } from 'react-router';
-
-import { MediaView } from '../MediaView';
-import { ChannelsByOwnerProps, ChannelsByOwner } from './ChannelsByOwner';
-import { JoyError } from '@polkadot/joy-utils/react/components';
-import { useApi } from '@polkadot/react-hooks';
-
-type Props = ChannelsByOwnerProps;
-
-export const ChannelsByOwnerView = MediaView<Props>({
-  component: ChannelsByOwner,
-  resolveProps: async (props) => {
-    const { transport, accountId } = props;
-    const channels = await transport.channelsByAccount(accountId);
-
-    return { channels };
-  }
-});
-
-export const ChannelsByOwnerWithRouter = (props: Props & RouteComponentProps<Record<string, string | undefined>>) => {
-  const { match: { params: { account } } } = props;
-  const { api } = useApi();
-
-  if (account) {
-    try {
-      return <ChannelsByOwnerView {...props} accountId={api.createType('AccountId', account)} />;
-    } catch (err) {
-      console.log('ChannelsByOwnerWithRouter failed:', err);
-    }
-  }
-
-  return <JoyError title={'Invalid account address in URL'}>{account}</JoyError>;
-};

+ 0 - 94
pioneer/packages/joy-media/src/channels/CurationPanel.tsx

@@ -1,94 +0,0 @@
-import React from 'react';
-import { ChannelEntity } from '../entities/ChannelEntity';
-import { isVerifiedChannel, isCensoredChannel } from './ChannelHelpers';
-import { useMyMembership } from '@polkadot/joy-utils/react/hooks';
-import { SemanticTxButton } from '@polkadot/joy-utils/react/components/TxButton';
-import { AccountId } from '@polkadot/types/interfaces';
-import { useApi } from '@polkadot/react-hooks';
-import { Icon } from 'semantic-ui-react';
-
-type ChannelCurationPanelProps = {
-  channel: ChannelEntity;
-};
-
-export const CurationPanel = (props: ChannelCurationPanelProps) => {
-  const { api } = useApi();
-  const { curationActor, allAccounts } = useMyMembership();
-  const { channel } = props;
-
-  const canUseAccount = (account: AccountId) => {
-    if (!allAccounts || !Object.keys(allAccounts).length) {
-      return false;
-    }
-
-    const ix = Object.keys(allAccounts).findIndex((key) => {
-      return account.eq(allAccounts[key].json.address);
-    });
-
-    return ix !== -1;
-  };
-
-  const renderToggleCensorshipButton = () => {
-    if (!curationActor) { return null; }
-
-    const [curation_actor, role_account] = curationActor;
-    const accountAvailable = canUseAccount(role_account);
-
-    const isCensored = isCensoredChannel(channel);
-
-    const new_curation_status = api.createType('ChannelCurationStatus',
-      isCensored ? 'Normal' : 'Censored'
-    );
-
-    return <SemanticTxButton
-      accountId={role_account.toString()}
-      type='submit'
-      size='small'
-      color={isCensored ? undefined : 'red'}
-      disabled={!accountAvailable}
-      params={[
-        curation_actor,
-        channel.id,
-        null, // not changing verified status
-        new_curation_status // toggled curation status
-      ]}
-      tx={'contentWorkingGroup.updateChannelAsCurationActor'}
-    >
-      <Icon name={isCensored ? 'x' : 'warning'}/>
-      { isCensored ? 'Un-Censor' : 'Censor' }
-    </SemanticTxButton>;
-  };
-
-  const renderToggleVerifiedButton = () => {
-    if (!curationActor) { return null; }
-
-    const [curation_actor, role_account] = curationActor;
-    const accountAvailable = canUseAccount(role_account);
-    const isVerified = isVerifiedChannel(channel);
-
-    return <SemanticTxButton
-      accountId={role_account.toString()}
-      type='submit'
-      size='small'
-      color={isVerified ? undefined : 'green'}
-      disabled={!accountAvailable}
-      params={[
-        curation_actor,
-        channel.id,
-        !isVerified, // toggle verified
-        null // not changing curation status
-      ]}
-      tx={'contentWorkingGroup.updateChannelAsCurationActor'}
-    >
-      <Icon name={isVerified ? 'x' : 'checkmark'}/>
-      { isVerified ? 'Remove Verification' : 'Verify' }
-    </SemanticTxButton>;
-  };
-
-  return <>
-    <div style={{ display: 'flex', float: 'right', margin: '0.5em', marginRight: 0 }}>
-      {renderToggleCensorshipButton()}
-      {renderToggleVerifiedButton()}
-    </div>
-  </>;
-};

+ 0 - 219
pioneer/packages/joy-media/src/channels/EditChannel.tsx

@@ -1,219 +0,0 @@
-import React from 'react';
-import { Button } from 'semantic-ui-react';
-import { Form, withFormik } from 'formik';
-import { History } from 'history';
-
-import { Option } from '@polkadot/types';
-import { TxButton, JoyError, Section } from '@polkadot/joy-utils/react/components';
-import { onImageError } from '../common/images';
-import { withMediaForm, MediaFormProps } from '../common/MediaForms';
-import { ChannelType, ChannelClass as Fields, buildChannelValidationSchema, ChannelFormValues, ChannelToFormValues, ChannelGenericProp } from '../schemas/channel/Channel';
-import { MediaDropdownOptions } from '../common/MediaDropdownOptions';
-import { ChannelId, OptionalText } from '@joystream/types/content-working-group';
-import { findFirstParamOfSubstrateEvent } from '@polkadot/joy-utils/functions/misc';
-import { useMyMembership } from '@polkadot/joy-utils/react/hooks';
-import { ChannelPublicationStatusDropdownOptions, isAccountAChannelOwner } from './ChannelHelpers';
-import { TxCallback } from '@polkadot/react-components/Status/types';
-import { SubmittableResult } from '@polkadot/api';
-import { ChannelValidationConstraints } from '../transport';
-
-import { useApi } from '@polkadot/react-hooks';
-
-export type OuterProps = {
-  history?: History;
-  id?: ChannelId;
-  entity?: ChannelType;
-  constraints?: ChannelValidationConstraints;
-  opts?: MediaDropdownOptions;
-};
-
-type FormValues = ChannelFormValues;
-
-const InnerForm = (props: MediaFormProps<OuterProps, FormValues>) => {
-  const {
-    // React components for form fields:
-    MediaText,
-    MediaDropdown,
-    LabelledField,
-
-    // Callbacks:
-    onSubmit,
-    // onTxSuccess,
-    onTxFailed,
-
-    history,
-    id: existingId,
-    entity,
-    isFieldChanged,
-
-    // Formik stuff:
-    values,
-    dirty,
-    isValid,
-    isSubmitting,
-    setSubmitting,
-    resetForm
-  } = props;
-
-  const { myAccountId, myMemberId } = useMyMembership();
-  const { api } = useApi();
-
-  if (entity && !isAccountAChannelOwner(entity, myAccountId)) {
-    return <JoyError title={'Only owner can edit channel'} />;
-  }
-
-  const { avatar } = values;
-  const isNew = !entity;
-
-  // if user is not the channel owner don't render the edit form
-  // return null
-
-  const onTxSuccess: TxCallback = (txResult: SubmittableResult) => {
-    setSubmitting(false);
-    if (!history) return;
-
-    const id = existingId || findFirstParamOfSubstrateEvent<ChannelId>(txResult, 'ChannelCreated');
-
-    console.log('Channel id:', id?.toString());
-
-    if (id) {
-      history.push('/media/channels/' + id.toString());
-    }
-  };
-
-  const buildTxParams = () => {
-    if (!isValid) return [];
-
-    if (!entity) {
-      // Create a new channel
-
-      const channelOwner = myMemberId;
-      const roleAccount = myAccountId;
-      const contentType = api.createType('ChannelContentType', values.content);
-
-      return [
-        channelOwner,
-        roleAccount,
-        contentType,
-        values.handle,
-        values.title || null,
-        values.description || null,
-        values.avatar || null,
-        values.banner || null,
-        values.publicationStatus
-      ];
-    } else {
-      // Update an existing channel
-
-      const updOptText = (field: ChannelGenericProp): Option<OptionalText> => {
-        return api.createType('Option<OptionalText>',
-          isFieldChanged(field)
-            ? api.createType('Option<Text>', values[field.id])
-            : null
-        );
-      };
-
-      const updHandle = api.createType('Option<Text>',
-        isFieldChanged(Fields.handle)
-          ? values[Fields.handle.id]
-          : null
-      );
-
-      const updPublicationStatus = api.createType('Option<ChannelPublicationStatus>',
-        isFieldChanged(Fields.publicationStatus)
-          ? api.createType('ChannelPublicationStatus', values[Fields.publicationStatus.id])
-          : null
-      );
-
-      return [
-        entity.id,
-        updHandle,
-        updOptText(Fields.title),
-        updOptText(Fields.description),
-        updOptText(Fields.avatar),
-        updOptText(Fields.banner),
-        updPublicationStatus
-      ];
-    }
-  };
-
-  const formFields = () => <>
-    <MediaText field={Fields.handle} {...props} />
-    <MediaText field={Fields.title} {...props} />
-    <MediaText field={Fields.avatar} {...props} />
-    <MediaText field={Fields.banner} {...props} />
-    <MediaText field={Fields.description} textarea {...props} />
-
-    <MediaDropdown
-      {...props}
-      field={Fields.publicationStatus}
-      options={ChannelPublicationStatusDropdownOptions}
-    />
-  </>;
-
-  const renderMainButton = () =>
-    <TxButton
-      type='submit'
-      isDisabled={!dirty || isSubmitting}
-      label={isNew
-        ? 'Create channel'
-        : 'Update channel'
-      }
-      params={buildTxParams()}
-      tx={isNew
-        ? 'contentWorkingGroup.createChannel'
-        : 'contentWorkingGroup.updateChannelAsOwner'
-      }
-      onClick={onSubmit}
-      txFailedCb={onTxFailed}
-      txSuccessCb={onTxSuccess}
-    />;
-
-  return <div className='EditMetaBox'>
-    <div className='EditMetaThumb'>
-      {avatar && <img src={avatar} onError={onImageError} />}
-    </div>
-
-    <Section title={isNew ? 'Create a channel' : 'Edit a channel'}>
-      <Form className='ui form JoyForm EditMetaForm'>
-
-        {formFields()}
-
-        <LabelledField style={{ marginTop: '1rem' }} {...props} flex>
-          {renderMainButton()}
-          <Button
-            type='button'
-            size='large'
-            disabled={!dirty || isSubmitting}
-            onClick={() => resetForm()}
-            content='Reset form'
-          />
-        </LabelledField>
-      </Form>
-    </Section>
-  </div>;
-};
-
-export const EditForm = withFormik<OuterProps, FormValues>({
-
-  // Transform outer props into form values
-  mapPropsToValues: (props): FormValues => {
-    const { entity } = props;
-
-    return ChannelToFormValues(entity);
-  },
-
-  validationSchema: (props: OuterProps): any => {
-    const { constraints } = props;
-
-    if (!constraints) return null;
-
-    return buildChannelValidationSchema(constraints);
-  },
-
-  handleSubmit: () => {
-    // do submitting things
-  }
-})(withMediaForm(InnerForm) as any);
-
-export default EditForm;

+ 0 - 38
pioneer/packages/joy-media/src/channels/EditChannel.view.tsx

@@ -1,38 +0,0 @@
-import React from 'react';
-import { RouteComponentProps } from 'react-router';
-import { MediaView } from '../MediaView';
-import { OuterProps, EditForm } from './EditChannel';
-import { JoyError } from '@polkadot/joy-utils/react/components';
-import { useApi } from '@polkadot/react-hooks';
-
-type Props = OuterProps;
-
-export const EditChannelView = MediaView<Props>({
-  component: EditForm,
-  membersOnly: true,
-  triggers: ['id'],
-  resolveProps: async (props) => {
-    const { transport, id } = props;
-    const entity = id && await transport.channelById(id);
-    const constraints = await transport.channelValidationConstraints();
-
-    return { entity, constraints };
-  }
-});
-
-type WithRouterProps = Props & RouteComponentProps<Record<string, string | undefined>>
-
-export const EditChannelWithRouter = (props: WithRouterProps) => {
-  const { match: { params: { id } } } = props;
-  const { api } = useApi();
-
-  if (id) {
-    try {
-      return <EditChannelView {...props} id={api.createType('ChannelId', id)} />;
-    } catch (err) {
-      console.log('EditChannelWithRouter failed:', err);
-    }
-  }
-
-  return <JoyError title={'Invalid channel id in URL'}>{id}</JoyError>;
-};

+ 0 - 41
pioneer/packages/joy-media/src/channels/ViewChannel.tsx

@@ -1,41 +0,0 @@
-import React from 'react';
-import { ChannelEntity } from '../entities/ChannelEntity';
-import { ChannelId } from '@joystream/types/content-working-group';
-import { VideoType } from '../schemas/video/Video';
-import { MusicAlbumPreviewProps } from '../music/MusicAlbumPreview';
-import { MusicTrackReaderPreviewProps } from '../music/MusicTrackReaderPreview';
-import { ViewVideoChannel } from './ViewVideoChannel';
-import { ViewMusicChannel } from './ViewMusicChannel';
-import { toVideoPreviews } from '../video/VideoPreview';
-import { isVideoChannel, isMusicChannel } from './ChannelHelpers';
-import { JoyError } from '@polkadot/joy-utils/react/components';
-
-export type ViewChannelProps = {
-  id: ChannelId;
-  channel?: ChannelEntity;
-
-  // Video channel specific:
-  videos?: VideoType[];
-
-  // Music channel specific:
-  albums?: MusicAlbumPreviewProps[];
-  tracks?: MusicTrackReaderPreviewProps[];
-}
-
-export function ViewChannel (props: ViewChannelProps) {
-  const { channel, videos = [], albums = [], tracks = [] } = props;
-
-  if (!channel) {
-    return <JoyError title={'Channel was not found'} />;
-  }
-
-  if (isVideoChannel(channel)) {
-    const previews = toVideoPreviews(videos);
-
-    return <ViewVideoChannel channel={channel} videos={previews} />;
-  } else if (isMusicChannel(channel)) {
-    return <ViewMusicChannel channel={channel} albums={albums} tracks={tracks} />;
-  } else {
-    return <JoyError title={'Unsupported channel type'}>{channel.content}</JoyError>;
-  }
-}

+ 0 - 35
pioneer/packages/joy-media/src/channels/ViewChannel.view.tsx

@@ -1,35 +0,0 @@
-import React from 'react';
-import { RouteComponentProps } from 'react-router';
-import { MediaView } from '../MediaView';
-import { ViewChannelProps, ViewChannel } from './ViewChannel';
-import { JoyError } from '@polkadot/joy-utils/react/components';
-import { useApi } from '@polkadot/react-hooks';
-
-type Props = ViewChannelProps;
-
-export const ViewChannelView = MediaView<Props>({
-  component: ViewChannel,
-  triggers: ['id'],
-  resolveProps: async (props) => {
-    const { transport, id } = props;
-    const channel = await transport.channelById(id);
-    const videos = await transport.videosByChannelId(id);
-
-    return { channel, videos };
-  }
-});
-
-export const ViewChannelWithRouter = (props: Props & RouteComponentProps<Record<string, string | undefined>>) => {
-  const { match: { params: { id } } } = props;
-  const { api } = useApi();
-
-  if (id) {
-    try {
-      return <ViewChannelView {...props} id={api.createType('ChannelId', id)} />;
-    } catch (err) {
-      console.log('ViewChannelWithRouter failed:', err);
-    }
-  }
-
-  return <JoyError title={'Invalid channel id in URL'}>{id}</JoyError>;
-};

+ 0 - 47
pioneer/packages/joy-media/src/channels/ViewMusicChannel.tsx

@@ -1,47 +0,0 @@
-import React from 'react';
-import { ChannelEntity } from '../entities/ChannelEntity';
-import { Section } from '@polkadot/joy-utils/react/components';
-import { ChannelHeader } from './ChannelHeader';
-import { MusicAlbumPreviewProps, MusicAlbumPreview } from '../music/MusicAlbumPreview';
-import { MusicTrackReaderPreview, MusicTrackReaderPreviewProps } from '../music/MusicTrackReaderPreview';
-import NoContentYet from '../common/NoContentYet';
-
-type Props = {
-  channel: ChannelEntity;
-  albums?: MusicAlbumPreviewProps[];
-  tracks?: MusicTrackReaderPreviewProps[];
-};
-
-function NoAlbums () {
-  return <NoContentYet>Channel has no music albums yet.</NoContentYet>;
-}
-
-function NoTracks () {
-  return <NoContentYet>Channel has no music tracks yet.</NoContentYet>;
-}
-
-export function ViewMusicChannel (props: Props) {
-  const { channel, albums = [], tracks = [] } = props;
-
-  const renderAlbumsSection = () => (
-    !albums.length
-      ? <NoAlbums />
-      : <Section title={'Music albums'}>
-        {albums.map((x) => <MusicAlbumPreview key={x.id} {...x} />)}
-      </Section>
-  );
-
-  const renderTracksSection = () => (
-    !tracks.length
-      ? <NoTracks />
-      : <Section title={'Music tracks'}>
-        {tracks.map((x) => <MusicTrackReaderPreview key={x.id} {...x} />)}
-      </Section>
-  );
-
-  return <div className='JoyViewChannel'>
-    <ChannelHeader channel={channel} />
-    {renderAlbumsSection()}
-    {renderTracksSection()}
-  </div>;
-}

+ 0 - 34
pioneer/packages/joy-media/src/channels/ViewVideoChannel.tsx

@@ -1,34 +0,0 @@
-import React from 'react';
-import { Section } from '@polkadot/joy-utils/react/components';
-import { ChannelEntity } from '../entities/ChannelEntity';
-import { ChannelHeader } from './ChannelHeader';
-import { VideoPreview, VideoPreviewProps } from '../video/VideoPreview';
-import NoContentYet from '../common/NoContentYet';
-
-type Props = {
-  channel: ChannelEntity;
-  videos?: VideoPreviewProps[];
-};
-
-function NoVideosYet () {
-  return <NoContentYet>Channel has no videos yet.</NoContentYet>;
-}
-
-export function ViewVideoChannel (props: Props) {
-  const { channel, videos = [] } = props;
-
-  const renderVideosSection = () => (
-    !videos.length
-      ? <NoVideosYet />
-      : <Section title={'Videos'}>
-        {videos.map((x) =>
-          <VideoPreview key={x.id.toString()} {...x} channel={channel} />
-        )}
-      </Section>
-  );
-
-  return <div className='JoyViewChannel'>
-    <ChannelHeader channel={channel} />
-    {renderVideosSection()}
-  </div>;
-}

+ 0 - 51
pioneer/packages/joy-media/src/channels/YouHaveNoChannels.tsx

@@ -1,51 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { Message } from 'semantic-ui-react';
-
-type Props = {
-  suspended?: boolean;
-};
-
-export function YouHaveNoChannels (props: Props) {
-  const { suspended = false } = props;
-
-  const renderSuspendedAlert = () => (
-    <Message
-      compact
-      error
-      icon='warning sign'
-      header='Channel Creation Suspended'
-      content='Please try again later'
-      className='JoyInlineMsg'
-    />
-  );
-
-  const renderCreateButton = () => (
-    <Link to={'/media/channels/new'}>
-      <Message
-        compact
-        success
-        icon='plus circle'
-        header='Create Channel'
-        content='and start publishing'
-        className='JoyInlineMsg CreateBtn'
-      />
-    </Link>
-  );
-
-  return <>
-    <h2 style={{ marginTop: '2rem', marginBottom: '.5rem' }}>
-      Build your following on Joystream
-    </h2>
-
-    <p style={{ marginBottom: '2rem' }}>
-      A channel is a way to organize related content for the benefit
-      of both the publisher and the audience.
-    </p>
-
-    {suspended
-      ? renderSuspendedAlert()
-      : renderCreateButton()
-    }
-  </>;
-}

+ 0 - 43
pioneer/packages/joy-media/src/common/BgImg.tsx

@@ -1,43 +0,0 @@
-import React, { CSSProperties } from 'react';
-
-type Props = {
-  url: string;
-  size?: number;
-  width?: number;
-  height?: number;
-  circle?: boolean;
-  className?: string;
-  style?: CSSProperties;
-};
-
-export function BgImg (props: Props) {
-  let { url, width, height, size, circle, className, style } = props;
-
-  const fullClass = `JoyBgImg ${className || ''}`;
-
-  let fullStyle: CSSProperties = {
-    backgroundImage: `url(${url})`
-  };
-
-  if (!width || !height) {
-    width = size;
-    height = size;
-  }
-
-  fullStyle = Object.assign(fullStyle, {
-    width,
-    height,
-    minWidth: width,
-    minHeight: height
-  });
-
-  if (circle) {
-    fullStyle = Object.assign(fullStyle, {
-      borderRadius: '50%'
-    });
-  }
-
-  fullStyle = Object.assign(fullStyle, style);
-
-  return <div className={fullClass} style={fullStyle} />;
-}

+ 0 - 59
pioneer/packages/joy-media/src/common/FormTabs.tsx

@@ -1,59 +0,0 @@
-import React from 'react';
-import { Menu, Label, Tab } from 'semantic-ui-react';
-import { FormikErrors } from 'formik';
-import { GenericMediaProp } from './MediaForms';
-
-type FormTab<FormValues> = {
-  id: string;
-  fields?: GenericMediaProp<FormValues>[];
-  renderTitle?: () => React.ReactNode;
-  render?: () => React.ReactNode;
-}
-
-type FormTabsProps<FormValues> = {
-  errors: FormikErrors<FormValues>;
-  panes: FormTab<FormValues>[];
-}
-
-export function FormTabs <FormValues> (props: FormTabsProps<FormValues>) {
-  const { panes, errors } = props;
-
-  return <Tab
-    menu={{ secondary: true, pointing: true, color: 'blue' }}
-    panes={panes.map((tab) => {
-      const {
-        id,
-        fields = [],
-        renderTitle = () => id,
-        render = () => null
-      } = tab;
-
-      const tabErrors: any[] = [];
-
-      fields.forEach((f) => {
-        const err = errors[f.id];
-
-        if (err) {
-          tabErrors.push(err);
-        }
-      });
-
-      // Currently we don't show error counter because it's markup is broken:
-      // a red circle with a number is shifted quite far from the right border of its tab.
-      const showErrorCounter = false;
-
-      const errCount = tabErrors.length;
-      const errTooltip = 'Number of errors on this tab';
-
-      const menuItem =
-        <Menu.Item key={id}>
-          {renderTitle()}
-          {showErrorCounter && errCount > 0 &&
-            <Label color='red' circular floating title={errTooltip}>{errCount}</Label>
-          }
-        </Menu.Item>;
-
-      return { menuItem, render };
-    })}
-  />;
-}

+ 0 - 44
pioneer/packages/joy-media/src/common/MediaDropdownOptions.tsx

@@ -1,44 +0,0 @@
-import ISO6391 from 'iso-639-1';
-import { DropdownItemProps } from 'semantic-ui-react';
-import { LanguageType } from '../schemas/general/Language';
-import { TextValueEntity } from '@joystream/types/versioned-store/EntityCodec';
-import { InternalEntities } from '../transport';
-
-const buildOptions = (entities: TextValueEntity[]): DropdownItemProps[] =>
-  entities.map((x) => ({ key: x.id, value: x.id, text: x.value }));
-
-const buildLanguageOptions = (entities: LanguageType[]): DropdownItemProps[] =>
-  entities.map((x) => ({ key: x.id, value: x.id, text: ISO6391.getName(x.value) }));
-
-export class MediaDropdownOptions {
-  public languageOptions: DropdownItemProps[]
-  public contentLicenseOptions: DropdownItemProps[]
-  public curationStatusOptions: DropdownItemProps[]
-  public musicGenreOptions: DropdownItemProps[]
-  public musicMoodOptions: DropdownItemProps[]
-  public musicThemeOptions: DropdownItemProps[]
-  public publicationStatusOptions: DropdownItemProps[]
-  public videoCategoryOptions: DropdownItemProps[]
-
-  constructor (props: InternalEntities) {
-    this.languageOptions = buildLanguageOptions(props.languages);
-    this.contentLicenseOptions = buildOptions(props.contentLicenses);
-    this.curationStatusOptions = buildOptions(props.curationStatuses);
-    this.musicGenreOptions = buildOptions(props.musicGenres);
-    this.musicMoodOptions = buildOptions(props.musicMoods);
-    this.musicThemeOptions = buildOptions(props.musicThemes);
-    this.publicationStatusOptions = buildOptions(props.publicationStatuses);
-    this.videoCategoryOptions = buildOptions(props.videoCategories);
-  }
-
-  static Empty = new MediaDropdownOptions({
-    languages: [],
-    contentLicenses: [],
-    curationStatuses: [],
-    musicGenres: [],
-    musicMoods: [],
-    musicThemes: [],
-    publicationStatuses: [],
-    videoCategories: []
-  });
-}

+ 0 - 202
pioneer/packages/joy-media/src/common/MediaForms.tsx

@@ -1,202 +0,0 @@
-import React from 'react';
-import { Dropdown, DropdownItemProps, DropdownProps } from 'semantic-ui-react';
-import { FormikProps, Field } from 'formik';
-import * as JoyForms from '@polkadot/joy-utils/react/components/forms';
-import { SubmittableResult } from '@polkadot/api';
-import { TxFailedCallback, TxCallback } from '@polkadot/react-components/Status/types';
-import { MediaDropdownOptions } from './MediaDropdownOptions';
-import { OnTxButtonClick } from '@polkadot/joy-utils/react/components/TxButton';
-import isEqual from 'lodash/isEqual';
-import { componentName } from '@polkadot/joy-utils/react/helpers';
-
-export const datePlaceholder = 'Date in format yyyy-mm-dd';
-
-export const boolOptions: DropdownItemProps[] = [
-  { value: 'true', text: 'Yes' },
-  { value: 'false', text: 'No' }
-];
-
-export type FormCallbacks = {
-  onSubmit: OnTxButtonClick;
-  onTxSuccess: TxCallback;
-  onTxFailed: TxFailedCallback;
-};
-
-export type GenericMediaProp<FormValues> = {
-  id: keyof FormValues;
-  type: string;
-  name: string;
-  description?: string;
-  required?: boolean;
-  minItems?: number;
-  maxItems?: number;
-  minTextLength?: number;
-  maxTextLength?: number;
-  classId?: any;
-};
-
-type BaseFieldProps<OuterProps, FormValues> = OuterProps & FormikProps<FormValues> & {
-  field: GenericMediaProp<FormValues>;
-};
-
-type MediaTextProps<OuterProps, FormValues> =
-  BaseFieldProps<OuterProps, FormValues> & JoyForms.LabelledProps<FormValues>;
-
-type MediaFieldProps<OuterProps, FormValues> =
-  BaseFieldProps<OuterProps, FormValues> &
-  JoyForms.LabelledProps<FormValues> & {
-    fieldProps: Record<string, unknown>;
-  }
-
-type MediaDropdownProps<OuterProps, FormValues> =
-  BaseFieldProps<OuterProps, FormValues> &
-  {
-    options: DropdownItemProps[];
-  };
-
-type FormFields<OuterProps, FormValues> = {
-  LabelledText: React.FunctionComponent<JoyForms.LabelledProps<FormValues>>;
-  LabelledField: React.FunctionComponent<JoyForms.LabelledProps<FormValues>>;
-  MediaText: React.FunctionComponent<MediaTextProps<OuterProps, FormValues>>;
-  MediaField: React.FunctionComponent<MediaFieldProps<OuterProps, FormValues>>;
-  MediaDropdown: React.FunctionComponent<MediaDropdownProps<OuterProps, FormValues>>;
-};
-
-export type MediaFormProps<OuterProps, FormValues> =
-  OuterProps &
-  FormikProps<FormValues> &
-  FormFields<OuterProps, FormValues> &
-  FormCallbacks & {
-    opts: MediaDropdownOptions;
-    isFieldChanged: (field: keyof FormValues | GenericMediaProp<FormValues>) => boolean;
-  };
-
-export function withMediaForm<OuterProps, FormValues>
-(Component: React.ComponentType<MediaFormProps<OuterProps, FormValues>>) {
-  type FieldName = keyof FormValues
-
-  type FieldObject = GenericMediaProp<FormValues>
-
-  const LabelledText = JoyForms.LabelledText<FormValues>();
-
-  const LabelledField = JoyForms.LabelledField<FormValues>();
-
-  function MediaText (props: MediaTextProps<OuterProps, FormValues>) {
-    const { field: f } = props;
-
-    return !f ? null : <LabelledText name={f.id} label={f.name} tooltip={f.description} required={f.required} {...props} />;
-  }
-
-  const MediaField = (props: MediaFieldProps<OuterProps, FormValues>) => {
-    const { field: f, fieldProps = {}, placeholder, className, style, ...otherProps } = props;
-
-    const { id } = f;
-
-    const allFieldProps = {
-      name: id,
-      id,
-      placeholder,
-      className,
-      style,
-      disabled: otherProps.isSubmitting,
-      ...fieldProps
-    };
-
-    return !f ? null : (
-      <LabelledField name={id} label={f.name} tooltip={f.description} required={f.required} {...props}>
-        <Field {...allFieldProps} />
-      </LabelledField>
-    );
-  };
-
-  const MediaDropdown = (props: MediaDropdownProps<OuterProps, FormValues>) => {
-    const { field: f, options = [] } = props;
-    const id = f.id;
-    const value = props.values[id] || '';
-
-    return <MediaField {...props} fieldProps={{
-      component: Dropdown,
-      selection: true,
-      search: true,
-      options,
-      value,
-      onBlur: (_event: any, _data: DropdownProps) => {
-        props.setFieldTouched(id as string, true);
-      },
-      onChange: (_event: any, data: DropdownProps) => {
-        props.setFieldValue(id as string, data.value);
-      }
-    }} />;
-  };
-
-  const ResultComponent: React.FunctionComponent<MediaFormProps<OuterProps, FormValues>> =
-    (props: MediaFormProps<OuterProps, FormValues>) => {
-      const {
-        initialValues,
-        values,
-        dirty,
-        touched,
-        errors,
-        isValid,
-        setSubmitting,
-        opts = MediaDropdownOptions.Empty
-      } = props;
-
-      const isFieldChanged = (field: FieldName | FieldObject): boolean => {
-        const fieldName = typeof field === 'string' ? field : (field as FieldObject).id;
-
-        return (
-          dirty &&
-          touched[fieldName] === true &&
-          !isEqual(values[fieldName], initialValues[fieldName])
-        );
-      };
-
-      const onSubmit = (sendTx: () => void) => {
-        if (isValid) {
-          sendTx();
-        } else {
-          console.log('Form is invalid. Errors:', errors);
-        }
-      };
-
-      const onTxSuccess: TxCallback = (_txResult: SubmittableResult) => {
-        setSubmitting(false);
-      };
-
-      const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | null) => {
-        setSubmitting(false);
-
-        if (txResult === null) {
-          // Tx cancelled
-
-        }
-      };
-
-      const allProps = {
-        ...props,
-
-        // Callbacks:
-        onSubmit,
-        onTxSuccess,
-        onTxFailed,
-
-        // Components:
-        LabelledText,
-        LabelledField,
-        MediaText,
-        MediaField,
-        MediaDropdown,
-
-        // Other
-        opts,
-        isFieldChanged
-      };
-
-      return <Component {...allProps} />;
-    };
-
-  ResultComponent.displayName = `withMediaForm(${componentName(Component)})`;
-
-  return ResultComponent;
-}

+ 0 - 145
pioneer/packages/joy-media/src/common/MediaPlayerView.tsx

@@ -1,145 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { Link } from 'react-router-dom';
-import DPlayer from 'react-dplayer';
-import APlayer from 'react-aplayer';
-
-import { ApiProps } from '@polkadot/react-api/types';
-import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls, withMulti } from '@polkadot/react-api/hoc';
-import { Option } from '@polkadot/types/codec';
-
-import translate from '../translate';
-import { DiscoveryProviderProps } from '../DiscoveryProvider';
-import { DataObject, ContentId } from '@joystream/types/media';
-import { VideoType } from '../schemas/video/Video';
-import { isAccountAChannelOwner } from '../channels/ChannelHelpers';
-import { ChannelEntity } from '../entities/ChannelEntity';
-import { useMyMembership } from '@polkadot/joy-utils/react/hooks';
-import { JoyError } from '@polkadot/joy-utils/react/components';
-
-const PLAYER_COMMON_PARAMS = {
-  lang: 'en',
-  autoplay: true,
-  theme: '#2185d0'
-};
-
-// This is just a part of Player's methods that are used in this component.
-// To see all the methods available on APlayer and DPlayer visit the next URLs:
-// http://aplayer.js.org/#/home?id=api
-// http://dplayer.js.org/#/home?id=api
-interface PartOfPlayer {
-  pause: () => void;
-  destroy: () => void;
-}
-
-export type RequiredMediaPlayerProps = {
-  channel: ChannelEntity;
-  video: VideoType;
-  contentId: ContentId;
-}
-
-type ContentProps = {
-  contentType?: string;
-  dataObjectOpt?: Option<DataObject>;
-  resolvedAssetUrl: string;
-}
-
-type MediaPlayerViewProps = ApiProps & I18nProps &
-DiscoveryProviderProps & RequiredMediaPlayerProps & ContentProps
-
-type PlayerProps = RequiredMediaPlayerProps & ContentProps
-
-function Player (props: PlayerProps) {
-  const { video, resolvedAssetUrl: url, contentType = 'video/video' } = props;
-  const { thumbnail: cover } = video;
-  const prefix = contentType.substring(0, contentType.indexOf('/'));
-
-  const [player, setPlayer] = useState<PartOfPlayer>();
-
-  const onPlayerCreated = (newPlayer: PartOfPlayer) => {
-    console.log('onPlayerCreated:', newPlayer);
-    setPlayer(newPlayer);
-  };
-
-  const destroyPlayer = () => {
-    if (!player) return;
-
-    console.log('Destroy the current player');
-    player.pause();
-    player.destroy();
-    setPlayer(undefined);
-  };
-
-  useEffect(() => {
-    return () => {
-      destroyPlayer();
-    };
-  }, [url]);
-
-  if (prefix === 'video') {
-    const video = { url, name, pic: cover };
-
-    return <DPlayer
-      video={video}
-      {...PLAYER_COMMON_PARAMS}
-      loop={false}
-      onLoad={onPlayerCreated} // Note that DPlayer has onLoad, but APlayer - onInit.
-    />;
-  } else if (prefix === 'audio') {
-    const audio = { url, name, cover };
-
-    return <APlayer
-      audio={audio}
-      {...PLAYER_COMMON_PARAMS}
-      loop='none'
-      onInit={onPlayerCreated} // Note that APlayer has onInit, but DPlayer - onLoad.
-    />;
-  }
-
-  return <JoyError title={'Unsupported type of content'}>{contentType}</JoyError>;
-}
-
-function InnerComponent (props: MediaPlayerViewProps) {
-  const { video, resolvedAssetUrl: url } = props;
-
-  const { dataObjectOpt, channel } = props;
-  const { myAccountId } = useMyMembership();
-
-  if (!dataObjectOpt || dataObjectOpt.isNone) {
-    return null;
-  }
-
-  // TODO extract and show the next info from dataObject:
-  // {"owner":"5GSMNn8Sy8k64mGUWPDafjMZu9bQNX26GujbBQ1LeJpNbrfg","added_at":{"block":2781,"time":1582750854000},"type_id":1,"size":3664485,"liaison":"5HN528fspu4Jg3KXWm7Pu7aUK64RSBz2ZSbwo1XKR9iz3hdY","liaison_judgement":1,"ipfs_content_id":"QmNk4QczoJyPTAKdfoQna6KhAz3FwfjpKyRBXAZHG5djYZ"}
-  const iAmOwner = isAccountAChannelOwner(channel, myAccountId);
-
-  return (
-    <div className='PlayBox'>
-
-      {/* Note that here we use a 'key' prop to force Player component to rerender */}
-      <Player {...props} key={url} />
-
-      <div className='ContentHeader'>
-        <a className='ui button outline DownloadBtn' href={`${url}?download`}><i className='cloud download icon'></i> Download</a>
-
-        {iAmOwner &&
-          <Link to={`/media/videos/${video.id}/edit`} className='ui button' style={{ float: 'right' }}>
-            <i className='pencil alternate icon'></i>
-            Edit
-          </Link>
-        }
-
-        <h1>{video.title}</h1>
-      </div>
-    </div>
-  );
-}
-
-export const MediaPlayerView = withMulti(
-  InnerComponent,
-  translate,
-  withCalls<MediaPlayerViewProps>(
-    ['query.dataDirectory.dataObjectByContentId',
-      { paramName: 'contentId', propName: 'dataObjectOpt' }]
-  )
-);

+ 0 - 159
pioneer/packages/joy-media/src/common/MediaPlayerWithResolver.tsx

@@ -1,159 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import axios, { CancelTokenSource, AxiosError } from 'axios';
-import _ from 'lodash';
-
-import { ApiProps } from '@polkadot/react-api/types';
-import { I18nProps } from '@polkadot/react-components/types';
-import { withMulti } from '@polkadot/react-api/hoc';
-import { Option, Vec } from '@polkadot/types/codec';
-
-import translate from '../translate';
-import { DiscoveryProviderProps, withDiscoveryProvider } from '../DiscoveryProvider';
-import { DataObjectStorageRelationshipId, DataObjectStorageRelationship } from '@joystream/types/media';
-import { Message } from 'semantic-ui-react';
-import { MediaPlayerView, RequiredMediaPlayerProps } from './MediaPlayerView';
-import { JoyInfo } from '@polkadot/joy-utils/react/components';
-import { useTransport } from '@polkadot/joy-utils/react/hooks';
-import { isObjectWithProperties } from '@polkadot/joy-utils/functions/misc';
-
-type Props = ApiProps & I18nProps & DiscoveryProviderProps & RequiredMediaPlayerProps;
-
-function newCancelSource (): CancelTokenSource {
-  return axios.CancelToken.source();
-}
-
-function InnerComponent (props: Props) {
-  const { contentId, api, discoveryProvider } = props;
-  const transport = useTransport();
-
-  const [error, setError] = useState<Error>();
-  const [resolvedAssetUrl, setResolvedAssetUrl] = useState<string>();
-  const [contentType, setContentType] = useState<string>();
-  const [cancelSource, setCancelSource] = useState<CancelTokenSource>(newCancelSource());
-
-  const resolveAsset = async () => {
-    setError(undefined);
-    setCancelSource(newCancelSource());
-
-    const rids = await api.query.dataObjectStorageRegistry.relationshipsByContentId<Vec<DataObjectStorageRelationshipId>>(contentId);
-
-    const allRelationships = await Promise.all(
-      rids.map((id) =>
-        api.query.dataObjectStorageRegistry.relationships<Option<DataObjectStorageRelationship>>(id)
-      )
-    );
-
-    // Providers that have signalled onchain that they have the asset
-    let readyProviders = allRelationships.filter((r) => r.isSome).map((r) => r.unwrap())
-      .filter((r) => r.ready)
-      .map((r) => r.storage_provider);
-
-    // runtime doesn't currently guarantee unique set
-    readyProviders = _.uniqBy(readyProviders, (provider) => provider.toString());
-
-    if (!readyProviders.length) {
-      setError(new Error('No Storage Providers found storing this content'));
-
-      return;
-    }
-
-    const activeProviders = (await transport.workingGroups.allWorkers('Storage')).map(([id]) => id);
-
-    // filter out providers no longer active - relationships of providers that have left
-    // are not pruned onchain.
-    readyProviders = _.intersectionBy(activeProviders, readyProviders, (provider) => provider.toString());
-
-    console.log(`Found ${readyProviders.length} providers ready to serve content: ${readyProviders.join(', ')}`);
-
-    // shuffle to spread the load
-    readyProviders = _.shuffle(readyProviders);
-
-    // TODO: prioritize already resolved providers, least reported unreachable, closest
-    // by geography etc..
-
-    // loop over providers until we find one that responds
-    while (readyProviders.length) {
-      const provider = readyProviders.shift();
-
-      if (!provider) continue;
-
-      let assetUrl: string | undefined;
-
-      try {
-        assetUrl = await discoveryProvider.resolveAssetEndpoint(provider, contentId.encode(), cancelSource.token);
-      } catch (err) {
-        if (axios.isCancel(err)) {
-          return;
-        } else {
-          continue;
-        }
-      }
-
-      try {
-        console.log('Check URL of resolved asset:', assetUrl);
-        const response = await axios.head(assetUrl, { cancelToken: cancelSource.token });
-        const headers = response.headers as Record<string, string | undefined>;
-
-        setContentType(headers['content-type'] || 'video/video');
-        setResolvedAssetUrl(assetUrl);
-
-        return;
-      } catch (e) {
-        const err = e as unknown;
-
-        if (axios.isCancel(err)) {
-          return;
-        } else {
-          const response = isObjectWithProperties(err, 'response')
-            ? (err as AxiosError).response
-            : undefined;
-
-          if (!response || (response.status >= 500 && response.status <= 504)) {
-            // network connection error
-            discoveryProvider.reportUnreachable(provider);
-          }
-
-          // try next provider
-          continue;
-        }
-      }
-    }
-
-    setError(new Error('Unable to reach any provider serving this content'));
-  };
-
-  useEffect(() => {
-    void resolveAsset();
-
-    return () => {
-      cancelSource.cancel();
-    };
-  }, [contentId.encode()]);
-
-  console.log('Content id:', contentId.encode());
-  console.log('Resolved asset URL:', resolvedAssetUrl);
-
-  if (error) {
-    return (
-      <Message error className='JoyMainStatus'>
-        <Message.Header>Error loading media content</Message.Header>
-        <p>{error.toString()}</p>
-        <button className='ui button' onClick={resolveAsset}>Try again</button>
-      </Message>
-    );
-  }
-
-  if (!resolvedAssetUrl) {
-    return <JoyInfo title={'Please wait...'}>Resolving media content.</JoyInfo>;
-  }
-
-  const playerProps = { ...props, contentType, resolvedAssetUrl };
-
-  return <MediaPlayerView {...playerProps} />;
-}
-
-export const MediaPlayerWithResolver = withMulti(
-  InnerComponent,
-  translate,
-  withDiscoveryProvider
-);

+ 0 - 7
pioneer/packages/joy-media/src/common/NoContentYet.tsx

@@ -1,7 +0,0 @@
-import React from 'react';
-
-const NoContentYet: React.FunctionComponent = (props) => {
-  return <div className='NoContentYet'>{props.children}</div>;
-};
-
-export default NoContentYet;

+ 0 - 44
pioneer/packages/joy-media/src/common/TypeHelpers.ts

@@ -1,44 +0,0 @@
-import BN from 'bn.js';
-import { createType } from '@joystream/types';
-import { ChannelId } from '@joystream/types/content-working-group';
-import { EntityId, ClassId } from '@joystream/types/versioned-store';
-
-export type AnyChannelId = ChannelId | BN | number | string
-
-export type AnyEntityId = EntityId | BN | number | string
-
-export type AnyClassId = ClassId | BN | number | string
-
-function canBeId (id: BN | number | string): boolean {
-  return id instanceof BN || typeof id === 'number' || typeof id === 'string';
-}
-
-export function asChannelId (id: AnyChannelId): ChannelId {
-  if (id instanceof ChannelId) {
-    return id;
-  } else if (canBeId(id)) {
-    return createType('ChannelId', id);
-  } else {
-    throw new Error(`Not supported format for Channel id: ${typeof id === 'object' ? id.constructor.name : id}`);
-  }
-}
-
-export function asEntityId (id: AnyEntityId): EntityId {
-  if (id instanceof EntityId) {
-    return id;
-  } else if (canBeId(id)) {
-    return createType('EntityId', id);
-  } else {
-    throw new Error(`Not supported format for Entity id: ${typeof id === 'object' ? id.constructor.name : id}`);
-  }
-}
-
-export function asClassId (id: AnyClassId): ClassId {
-  if (id instanceof ClassId) {
-    return id;
-  } else if (canBeId(id)) {
-    return createType('ClassId', id);
-  } else {
-    throw new Error(`Not supported format for Class id: ${typeof id === 'object' ? id.constructor.name : id}`);
-  }
-}

+ 0 - 17
pioneer/packages/joy-media/src/common/images.tsx

@@ -1,17 +0,0 @@
-import React from 'react';
-
-export const DEFAULT_THUMBNAIL_URL = 'images/default-thumbnail.png';
-
-// This is a hack to just satisfy TypeScript compiler.
-type ImageOnErrorEvent = EventTarget & {
-  src: string;
-  onerror?: (e: any) => void;
-};
-
-export function onImageError (event: React.SyntheticEvent<HTMLImageElement, Event>) {
-  const target = event.target as ImageOnErrorEvent;
-
-  // Set onerror callback to undefined to prevent infinite callbacks when image src path fails:
-  target.onerror = undefined;
-  target.src = DEFAULT_THUMBNAIL_URL;
-}

+ 0 - 405
pioneer/packages/joy-media/src/common/index.scss

@@ -1,405 +0,0 @@
-$blackFont: #222;
-$grayFont: #999;
-
-$bgHover: #f2f2f2;
-$bgSelected: #c7e7fa;
-
-$borderColor: #e4e4e4;
-
-.NoContentYet {
-  padding: 2rem 0;
-  color: $grayFont;
-}
-
-.JoyBgImg {
-  background-color: #ccc;
-  background-size: cover;
-  background-position: center;
-}
-
-.JoySection {
-  .ViewAllLink {
-    float: right;
-    font-size: 1rem;
-    font-weight: normal;
-    text-transform: uppercase;
-    margin-left: 1rem;
-  }
-}
-.Ellipsis {
-  text-overflow: ellipsis;
-  overflow: hidden;
-  white-space: nowrap;
-}
-
-.JoyTopActionBar {
-  margin: 1rem 0;
-
-  .button {
-    margin-right: .5rem;
-  }
-}
-
-.JoyListOfPreviews {
-  background-color: #ddd;
-
-  .CheckboxCell {
-    padding-left: 1rem;
-  }
-
-  .NoItems {
-    display: block;
-    background-color: #fafafa;
-    padding: 1rem 0;
-  }
-}
-
-.JoyMusicAlbumPreview,
-.JoyMusicTrackPreview {
-  display: flex;
-
-  &.vertical {
-    flex-direction: column;
-  }
-
-  &.horizontal {
-    flex-direction: row;
-
-    .AlbumCover {
-      margin-right: 1rem;
-    }
-  }
-
-  .JoyListOfPreviews & {
-    background-color: #fafafa;
-    margin-top: 1px;
-    padding: .5rem 0;
-
-    &:hover {
-      background-color: $bgHover;
-    }
-
-    &.DraggableItem,
-    &.SelectedItem {
-      background-color: $bgSelected;
-    }
-  }
-
-  .AlbumNumber {
-    color: $grayFont;
-    width: 3.5rem;
-    text-align: right;
-    padding-right: 1rem;
-  }
-
-  .AlbumCover {
-    text-align: right;
-    white-space: nowrap;
-
-    $size: 60px;
-    width: $size;
-
-    img {
-      max-width: $size;
-      max-height: $size;
-    }
-  }
-
-  .AlbumDescription {
-    padding: 0 1rem;
-    width: 100%;
-
-    .AlbumTitle {
-      font-size: 1rem;
-      font-weight: bold;
-      color: $blackFont;
-      margin-top: 0;
-      margin-bottom: 0.5rem;
-    }
-
-    .AlbumArtist,
-    .AlbumTracksCount {
-      color: $grayFont;
-      font-size: .9rem;
-    }
-  }
-
-  .AlbumActions {
-    white-space: nowrap;
-    opacity: 0;
-
-    .button {
-      margin-right: .5rem;
-    }
-  }
-  &:hover .AlbumActions {
-    opacity: 1;
-  }
-}
-
-/* Music Album */
-/* ------------------------------------------------------ */
-
-.JoyMusicAlbumPreview {
-  display: inline-flex;
-  flex-direction: column;
-  padding: 0 1rem 1rem 0;
-  $size: 200px;
-
-  .AlbumDescription {
-    text-align: left;
-    padding: 0;
-  }
-
-  .AlbumCover {
-    width: $size;
-    height: $size;
-    min-width: $size;
-    min-height: $size;
-  }
-
-  .AlbumTitle {
-    @extend .Ellipsis;
-
-    font-size: 1rem;
-    font-weight: bold;
-    color: $blackFont;
-    margin-top: 0;
-    margin-bottom: 0.5rem;
-  }
-  &.vertical {
-    .AlbumTitle {
-      margin-top: 0.5rem;
-    }
-  }
-
-  .AlbumArtist {
-    color: $grayFont;
-    font-size: 1rem;
-  }
-
-  .AlbumActions {
-    opacity: 1;
-  }
-}
-
-/* Channels */
-/* ------------------------------------------------------ */
-
-.ChannelTitle {
-  color: $blackFont;
-  display: flex;
-}
-
-.ChannelAvatar {
-  display: table;
-  border-radius: 50%;
-  margin-right: 1rem;
-
-  &.big {
-    margin-right: 2rem;
-  }
-}
-
-.JoyChannels {
-
-  .ui.message.JoyInlineMsg {
-    display: inline-flex;
-    width: auto;
-    margin-top: 0;
-
-    &.CreateBtn {
-      cursor: pointer;
-
-      /* Disable text selection by user: */
-      -webkit-touch-callout: none; /* iOS Safari */
-      -webkit-user-select: none; /* Safari */
-       -khtml-user-select: none; /* Konqueror HTML */
-         -moz-user-select: none; /* Old versions of Firefox */
-          -ms-user-select: none; /* Internet Explorer/Edge */
-              user-select: none; /* Non-prefixed version, currently
-                                    supported by Chrome, Opera and Firefox */
-
-      &:hover,
-      &:focus {
-        background-color: #f3fce0;
-      }
-      &:active {
-        background-color: #e0f4b7;
-      }
-    }
-  }
-
-  .ChannelPreview {
-    display: flex;
-
-    .ChannelStats {
-      margin-left: 3rem;
-      text-align: right;
-
-      .statistic {
-        .label,
-        .value {
-          text-align: right;
-          white-space: nowrap;
-        }
-        .label {
-          color: $grayFont;
-          font-weight: normal;
-          font-size: .9rem;
-        }
-      }
-    }
-  }
-}
-
-.JoyViewChannel {
-  .ChannelHeader {
-    margin-bottom: 1rem;
-  }
-  .ChannelCover {
-    background-size: cover;
-    margin-bottom: 2rem;
-    height: 200px;
-  }
-}
-
-.ChannelPreview {
-  display: flex;
-  align-items: normal;
-
-  .ListOfChannels & {
-    margin-bottom: 1rem;
-  }
-
-  .JoyPlayAlbum & {
-    margin: .5rem 0;
-  }
-
-  .ChannelDetails {
-    width: 100%;
-  }
-
-  .ChannelTitle {
-    font-size: 1.5rem;
-    font-weight: 500;
-    margin: 0;
-    margin-top: .75rem;
-
-    .ChannelHeader & {
-      margin-top: 1rem;
-    }
-  }
-  
-  &.small {
-    align-items: center;
-
-    .ChannelTitle {
-      margin-top: 0;
-      font-size: 1rem;
-      a {
-        color: $blackFont;
-      }
-    }
-    .ChannelAvatar {
-      margin-right: .5rem;
-    }
-  }
-
-  &.big {
-    .ChannelTitle {
-      font-size: 2rem;
-    }
-  }
-
-  .ChannelSubtitle {
-    color: $grayFont;
-    font-size: .9rem;
-    text-transform: uppercase;
-    margin-top: .5rem;
-
-    .icon {
-      margin-right: .5rem;
-    }
-  }
-
-  .ChannelDesc {
-    margin-top: .75rem;
-  }
-}
-
-/* Play Album, Video */
-/* ------------------------------------------------------ */
-
-.JoyPlayAlbum {
-  display: flex;
-  flex-direction: row;
-
-  .JoyPlayAlbum_Main {
-    display: flex;
-    flex-direction: row;
-    margin-right: 1rem;
-
-    .JoyPlayAlbum_CurrentTrack {
-      margin-right: 1rem;
-
-      .AlbumTitle {
-        font-size: 1.5rem;
-        margin-top: 1rem;
-      }
-      .AlbumArtist {
-        font-size: 1.15rem;
-      }
-    }
-
-    .JoyPlayAlbum_AlbumTracks {
-      .TrackRow {
-        cursor: pointer;
-
-        &.Current {
-          background-color: $bgSelected;
-        }
-
-        .JoyMusicAlbumPreview {
-          padding: 0;
-        }
-        .TrackNumber {
-          color: $grayFont;
-          width: 2rem;
-          padding-left: .5rem !important;
-        }
-        .TrackTitle {
-          padding-right: .5rem !important;
-        }
-        .AlbumDescription {
-          max-width: 300px;
-        }
-      }
-    }
-
-    .JoyPlayAlbum_AlbumTracks,
-    .JoyPlayAlbum_MetaInfo {
-      td:first-child {
-        font-weight: bold;
-        color: $grayFont;
-      }
-    }
-  }
-
-  .JoyPlayAlbum_RightSidePanel {
-    max-width: 450px;
-  }
-}
-
-.ContentHeader {
-  border-bottom: 1px solid $borderColor;
-  padding-bottom: 1rem;
-  margin-bottom: 1rem;
-}
-
-.PlayVideoDetails {
-  border-top: 1px solid $borderColor;
-  padding-top: 1rem;
-  margin-top: 1rem;
-  margin-bottom: 2rem;
-}

+ 0 - 10
pioneer/packages/joy-media/src/entities/ChannelEntity.ts

@@ -1,10 +0,0 @@
-import BN from 'bn.js';
-import { ChannelType } from '../schemas/channel/Channel';
-
-// TODO rename to EnrichedChannelType
-export type ChannelEntity = ChannelType & {
-
-  // Stats:
-  rewardEarned: BN;
-  contentItemsCount: number;
-};

+ 0 - 15
pioneer/packages/joy-media/src/entities/EntityHelpers.ts

@@ -1,15 +0,0 @@
-import moment from 'moment';
-import ISO6391 from 'iso-639-1';
-import { LanguageType } from '../schemas/general/Language';
-
-export function printExplicit (explicit?: boolean): string {
-  return explicit === true ? 'Yes' : 'No';
-}
-
-export function printReleaseDate (linuxTimestamp?: number): string {
-  return !linuxTimestamp ? '' : moment(linuxTimestamp * 1000).format('YYYY-MM-DD');
-}
-
-export function printLanguage (language?: LanguageType): string {
-  return !language ? '' : ISO6391.getName(language.value);
-}

+ 0 - 26
pioneer/packages/joy-media/src/entities/MusicAlbumEntity.ts

@@ -1,26 +0,0 @@
-export type MusicAlbumEntity = {
-  title: string;
-  artist: string;
-  thumbnail: string;
-  description: string;
-
-  explicit: boolean;
-  license: string;
-
-  year: number;
-  month?: number;
-  date?: number;
-
-  genre?: string;
-  mood?: string;
-  theme?: string;
-
-  language?: string;
-  links?: string[];
-  lyrics?: string;
-  composer?: string;
-  reviews?: string;
-
-  // publicationStatus: ...
-  // curationStatus: ...
-};

+ 0 - 18
pioneer/packages/joy-media/src/entities/MusicTrackEntity.ts

@@ -1,18 +0,0 @@
-export type MusicTrackEntity = {
-
-  // Basic:
-  title: string;
-  description?: string;
-  thumbnail?: string;
-  visibility?: string;
-  album?: string;
-
-  // Additional:
-  artist?: string;
-  composer?: string;
-  genre?: string;
-  mood?: string;
-  theme?: string;
-  explicit?: boolean;
-  license?: string;
-};

+ 0 - 28
pioneer/packages/joy-media/src/explore/AllChannels.tsx

@@ -1,28 +0,0 @@
-import React from 'react';
-import { Section } from '@polkadot/joy-utils/react/components';
-import { MediaView } from '../MediaView';
-import { ChannelEntity } from '../entities/ChannelEntity';
-import { ChannelPreview } from '../channels/ChannelPreview';
-
-export type Props = {
-  channels?: ChannelEntity[];
-}
-
-export function AllChannels (props: Props) {
-  const { channels = [] } = props;
-
-  return channels.length === 0
-    ? <em>No channels found</em>
-    : <Section title={`All channels (${channels.length})`} className='ListOfChannels'>
-      {channels.map((x) =>
-        <ChannelPreview key={x.id} channel={x} withSubtitle={false} />
-      )}
-    </Section>;
-}
-
-export const AllChannelsView = MediaView<Props>({
-  component: AllChannels,
-  resolveProps: async ({ transport }) => ({
-    channels: await transport.allPublicVideoChannels()
-  })
-});

+ 0 - 27
pioneer/packages/joy-media/src/explore/AllVideos.tsx

@@ -1,27 +0,0 @@
-import React from 'react';
-import { Section } from '@polkadot/joy-utils/react/components';
-import { VideoPreviewProps, VideoPreview } from '../video/VideoPreview';
-import { MediaView } from '../MediaView';
-
-export type Props = {
-  videos?: VideoPreviewProps[];
-}
-
-export function AllVideos (props: Props) {
-  const { videos = [] } = props;
-
-  return videos.length === 0
-    ? <em>No videos found</em>
-    : <Section title={`All videos (${videos.length})`} className='ListOfVideos'>
-      {videos.map((x) =>
-        <VideoPreview key={x.id} {...x} withChannel />
-      )}
-    </Section>;
-}
-
-export const AllVideosView = MediaView<Props>({
-  component: AllVideos,
-  resolveProps: async ({ transport }) => ({
-    videos: await transport.allPublicVideos()
-  })
-});

+ 0 - 54
pioneer/packages/joy-media/src/explore/ExploreContent.tsx

@@ -1,54 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { Section } from '@polkadot/joy-utils/react/components';
-import { VideoPreviewProps, VideoPreview } from '../video/VideoPreview';
-import { ChannelEntity } from '../entities/ChannelEntity';
-import { ChannelPreview } from '../channels/ChannelPreview';
-
-const LatestVideosTitle = () => (
-  <div>
-    Latest videos
-    <Link to={'/media/videos'} className='ViewAllLink'>All videos</Link>
-  </div>
-);
-
-const LatestChannelsTitle = () => (
-  <div>
-    Latest video channels
-    <Link to={'/media/channels'} className='ViewAllLink'>All channels</Link>
-  </div>
-);
-
-export type ExploreContentProps = {
-  featuredVideos?: VideoPreviewProps[];
-  latestVideos?: VideoPreviewProps[];
-  latestVideoChannels?: ChannelEntity[];
-}
-
-export function ExploreContent (props: ExploreContentProps) {
-  const { featuredVideos = [], latestVideos = [], latestVideoChannels = [] } = props;
-
-  return <div>
-    {featuredVideos.length > 0 &&
-      <Section title={'Featured videos'} className='ListOfVideos'>
-        {featuredVideos.map((x) =>
-          <VideoPreview key={x.id} {...x} withChannel />
-        )}
-      </Section>
-    }
-    {latestVideos.length > 0 &&
-      <Section className='ListOfVideos' title={<LatestVideosTitle />}>
-        {latestVideos.map((x) =>
-          <VideoPreview key={x.id} {...x} withChannel />
-        )}
-      </Section>
-    }
-    {latestVideoChannels.length > 0 &&
-      <Section className='ListOfChannels' title={<LatestChannelsTitle />}>
-        {latestVideoChannels.map((x) =>
-          <ChannelPreview key={x.id} channel={x} withSubtitle={false} />
-        )}
-      </Section>
-    }
-  </div>;
-}

+ 0 - 23
pioneer/packages/joy-media/src/explore/ExploreContent.view.tsx

@@ -1,23 +0,0 @@
-import { MediaView } from '../MediaView';
-import { ExploreContentProps, ExploreContent } from './ExploreContent';
-
-export const ExploreContentView = MediaView<ExploreContentProps>({
-  component: ExploreContent,
-  resolveProps: async (props) => {
-    const { transport } = props;
-
-    const [
-      latestVideoChannels,
-      latestVideos,
-      featuredVideos
-    ] = await Promise.all([
-      transport.latestPublicVideoChannels(),
-      transport.latestPublicVideos(),
-      transport.featuredVideos()
-    ]);
-
-    return { featuredVideos, latestVideos, latestVideoChannels };
-  }
-});
-
-export default ExploreContentView;

+ 0 - 92
pioneer/packages/joy-media/src/explore/PlayContent.tsx

@@ -1,92 +0,0 @@
-import React, { useState } from 'react';
-import { MusicAlbumPreviewProps, MusicAlbumPreview } from '../music/MusicAlbumPreview';
-import { MusicTrackReaderPreviewProps, MusicTrackReaderPreview } from '../music/MusicTrackReaderPreview';
-import { Pluralize } from '@polkadot/joy-utils/react/components';
-import { Table } from 'semantic-ui-react';
-import { ChannelEntity } from '../entities/ChannelEntity';
-import { ChannelPreview } from '../channels/ChannelPreview';
-
-type Props = {
-  channel: ChannelEntity;
-  tracks: MusicTrackReaderPreviewProps[];
-  currentTrackIndex?: number;
-  featuredAlbums?: MusicAlbumPreviewProps[];
-};
-
-// TODO get meta from track item
-const meta = {
-  artist: 'Berlin Philharmonic',
-  composer: 'Wolfgang Amadeus Mozart',
-  genre: 'Classical Music',
-  mood: 'Relaxing',
-  theme: 'Dark',
-  explicit: false,
-  license: 'Public Domain'
-};
-
-export function PlayContent (props: Props) {
-  const { channel, tracks = [], currentTrackIndex = 0, featuredAlbums = [] } = props;
-
-  const [currentTrack, setCurrentTrack] = useState(tracks[currentTrackIndex]);
-
-  const metaField = (label: React.ReactNode, value: React.ReactNode) =>
-    <Table.Row>
-      <Table.Cell width={2}>{label}</Table.Cell>
-      <Table.Cell>{value}</Table.Cell>
-    </Table.Row>;
-
-  const metaTable = <>
-    <h3>Track Info</h3>
-    <Table basic='very' compact className='JoyPlayAlbum_MetaInfo'>
-      <Table.Body>
-        {metaField('Artist', meta.artist)}
-        {metaField('Composer', meta.composer)}
-        {metaField('Genre', meta.genre)}
-        {metaField('Mood', meta.mood)}
-        {metaField('Theme', meta.theme)}
-        {metaField('Explicit', meta.explicit ? 'Yes' : 'No')}
-        {metaField('License', meta.license)}
-      </Table.Body>
-    </Table>
-  </>;
-
-  const albumTracks = (
-    <div className='JoyPlayAlbum_AlbumTracks'>
-      <h3><Pluralize count={tracks.length} singularText='Track' /></h3>
-      <Table basic='very' compact>
-        <Table.Body>
-          {tracks.map((x, i) => {
-            const isCurrent = x.id === currentTrack.id;
-            const className = 'TrackRow ' + (isCurrent ? 'Current' : '');
-
-            return (
-              <Table.Row key={x.id} className={className} onClick={() => setCurrentTrack(x)}>
-                <Table.Cell className='TrackNumber' width={1}>{i + 1}</Table.Cell>
-                <Table.Cell className='TrackTitle'>{x.title}</Table.Cell>
-              </Table.Row>
-            );
-          })}
-        </Table.Body>
-      </Table>
-    </div>
-  );
-
-  return <div className='JoyPlayAlbum'>
-    <div className='JoyPlayAlbum_Main'>
-      <div className='JoyPlayAlbum_CurrentTrack'>
-        <MusicTrackReaderPreview {...currentTrack} size={400} />
-        <ChannelPreview channel={channel} />
-      </div>
-      <div>
-        {albumTracks}
-        {metaTable}
-      </div>
-    </div>
-    {featuredAlbums.length > 0 &&
-      <div className='JoyPlayAlbum_RightSidePanel'>
-        <h3>Featured albums</h3>
-        {featuredAlbums.map((x) => <MusicAlbumPreview key={x.id} {...x} size={170} />)}
-      </div>
-    }
-  </div>;
-}

+ 0 - 133
pioneer/packages/joy-media/src/index.scss

@@ -1,133 +0,0 @@
-.JoyPaperWidth {
-  max-width: 900px;
-  margin: 0 auto;
-}
-
-.UploadBox {
-  display: flex;
-  flex-direction: row;
-  justify-content: center;
-
-  .UploadSelectForm {
-    max-width: 500px;
-    width: 100%;
-  }
-
-  .UploadInputFile {
-    padding: 2rem 3rem;
-    margin-bottom: 1rem;
-    text-align: center;
-    height: auto;
-
-    &:hover,
-    &.FileSelected {
-      border: 1px solid #2185d0 !important;
-      .label {
-        color: #2185d0 !important;
-      }
-    }
-
-    i.cloud.icon {
-      font-size: 3rem;
-    }
-  }
-
-  .UploadButtonBox {
-    text-align: center;
-  }
-
-  .UploadProgress {
-    margin-left: calc(210px + 1rem) !important;
-    width: 100%;
-    max-width: 600px;
-  }
-
-  .EditMetaForm {
-    flex-grow: 1;
-    max-width: 600px;
-  }
-}
-
-.PlayBox {
-  max-width: 700px;
-  margin-bottom: 1rem;
-  h1, h2 {
-    text-transform: none;
-    margin: 0;
-  }
-  .ContentHeader {
-    margin-top: 1.5rem;
-    margin-bottom: .5rem;
-  }
-  .DownloadBtn {
-    float: right;
-    margin-left: .5rem;
-  }
-  .ContentDesc {
-    margin-top: 1rem;
-  }
-}
-
-.MediaGrid {
-  display: flex;
-  flex-direction: row;
-  flex-wrap: wrap;
-  width: 880px;
-
-  .MediaCell {
-    width: 25%;
-
-    &:hover {
-      background-color: #deeffc;
-    }
-    .CellContent {
-      padding: 5px;
-      margin-bottom: 15px;
-      overflow: hidden;
-
-      h3 {
-        font-size: 1rem;
-        font-weight: bold;
-        margin: 1rem 0 .5rem 0;
-      }
-    }
-
-    .ThumbBox {
-      display: flex;
-      flex-direction: row;
-      justify-content: center;
-      margin-bottom: 5px;
-
-      .ThumbImg {
-        width: 210px;
-        max-height: 118px;
-        display: block;
-      }
-    }
-  }
-}
-
-.EditMetaBox {
-  display: flex;
-  flex-direction: row;
-  flex-wrap: wrap;
-  /* width: 880px; */
-
-  .EditMetaThumb {
-    width: 100%;
-    max-width: 210px;
-    max-height: 118px;
-    margin-right: 1rem;
-
-    img {
-      width: 100%;
-      max-width: 210px;
-      max-height: 118px;
-    }
-  }
-
-  .JoySection {
-    width: 100%;
-    max-width: 600px;
-  }
-}

+ 0 - 82
pioneer/packages/joy-media/src/index.tsx

@@ -1,82 +0,0 @@
-
-import React from 'react';
-import { Route, Switch } from 'react-router';
-
-import { AppProps, I18nProps } from '@polkadot/react-components/types';
-import Tabs from '@polkadot/react-components/Tabs';
-import { TabItem } from '@polkadot/react-components/Tabs/types';
-import { ApiProps } from '@polkadot/react-api/types';
-import { withMulti } from '@polkadot/react-api/hoc';
-
-import './index.scss';
-import './common/index.scss';
-
-import translate from './translate';
-import { useMyAccount } from '@polkadot/joy-utils/react/hooks';
-import { UploadWithRouter } from './Upload';
-import { DiscoveryProviderProps, DiscoveryProviderProvider } from './DiscoveryProvider';
-import { SubstrateTransportProvider } from './TransportContext';
-import { ChannelsByOwnerWithRouter } from './channels/ChannelsByOwner.view';
-import { EditChannelView, EditChannelWithRouter } from './channels/EditChannel.view';
-import { ExploreContentView } from './explore/ExploreContent.view';
-import { ViewChannelWithRouter } from './channels/ViewChannel.view';
-import { EditVideoWithRouter } from './upload/EditVideo.view';
-import { PlayVideoWithRouter } from './video/PlayVideo.view';
-import { AllVideosView } from './explore/AllVideos';
-import { AllChannelsView } from './explore/AllChannels';
-// import { VideosByOwner } from './video/VideosByOwner';
-
-type Props = AppProps & I18nProps & ApiProps & DiscoveryProviderProps;
-
-function App (props: Props) {
-  const { t, basePath } = props;
-  const { state: { address: myAddress } } = useMyAccount();
-
-  const tabs: TabItem[] = [
-    {
-      isRoot: true,
-      name: 'explore',
-      text: t('Explore')
-    },
-    !myAddress ? undefined : {
-      name: `account/${myAddress}/channels`,
-      text: t('My channels')
-    }
-    // !myAddress ? undefined : {
-    //   name: `account/${myAddress}/videos`,
-    //   text: t('My videos')
-    // }
-  ].filter((x) => x !== undefined) as TabItem[];
-
-  return (
-    <SubstrateTransportProvider>
-      <DiscoveryProviderProvider>
-        <main className='media--App'>
-          <header>
-            <Tabs basePath={basePath} items={tabs} />
-          </header>
-          <Switch>
-            <Route path={`${basePath}/account/:account/channels`} component={ChannelsByOwnerWithRouter} />
-            <Route path={`${basePath}/channels/new`} component={EditChannelView} />
-            <Route path={`${basePath}/channels/:id/edit`} component={EditChannelWithRouter} />
-            <Route path={`${basePath}/channels/:channelId/upload`} component={UploadWithRouter} />
-            <Route path={`${basePath}/channels/:id`} component={ViewChannelWithRouter} />
-            <Route path={`${basePath}/channels/:id`} component={ViewChannelWithRouter} />
-            <Route path={`${basePath}/channels`} component={AllChannelsView} />
-            {/* <Route path={`${basePath}/videos/my`} component={VideosByOwnerView} /> */}
-            <Route path={`${basePath}/videos/:id/edit`} component={EditVideoWithRouter} />
-            <Route path={`${basePath}/videos/:id`} component={PlayVideoWithRouter} />
-            <Route path={`${basePath}/videos`} component={AllVideosView} />
-            <Route path={`${basePath}/explore`} component={ExploreContentView} />
-            <Route component={ExploreContentView} />
-          </Switch>
-        </main>
-      </DiscoveryProviderProvider>
-    </SubstrateTransportProvider>
-  );
-}
-
-export default withMulti(
-  App,
-  translate
-);

+ 0 - 14
pioneer/packages/joy-media/src/mocks/ContentLicense.mock.ts

@@ -1,14 +0,0 @@
-import { newEntityId } from './EntityId.mock';
-import { ContentLicenseType } from '../schemas/general/ContentLicense';
-
-const values = [
-  'Public Domain',
-  'Share Alike',
-  'No Derivatives',
-  'No Commercial'
-];
-
-export const AllContentLicenses: ContentLicenseType[] =
-  values.map((value) => ({ id: newEntityId(), value })) as unknown as ContentLicenseType[]; // A hack to fix TS compilation.
-
-export const ContentLicense = AllContentLicenses[0];

+ 0 - 18
pioneer/packages/joy-media/src/mocks/CurationStatus.mock.ts

@@ -1,18 +0,0 @@
-import { newEntityId } from './EntityId.mock';
-import { CurationStatusType } from '../schemas/general/CurationStatus';
-
-function newEntity (value: string): CurationStatusType {
-  return { id: newEntityId(), value } as unknown as CurationStatusType; // A hack to fix TS compilation.
-}
-
-export const CurationStatus = {
-  Edited: newEntity('Edited'),
-  UpdatedSchema: newEntity('Updated schema'),
-  UnderReview: newEntity('Under review'),
-  Removed: newEntity('Removed')
-};
-
-export const AllCurationStatuses: CurationStatusType[] =
-  Object.values(CurationStatus);
-
-export const DefaultCurationStatus = CurationStatus.Edited;

+ 0 - 9
pioneer/packages/joy-media/src/mocks/EntityId.mock.ts

@@ -1,9 +0,0 @@
-let value = 1;
-
-export function nextEntityId (): number {
-  return value;
-}
-
-export function newEntityId (): number {
-  return value++;
-}

+ 0 - 10
pioneer/packages/joy-media/src/mocks/FeaturedContent.mock.ts

@@ -1,10 +0,0 @@
-import { newEntityId } from './EntityId.mock';
-import { FeaturedContentType } from '../schemas/general/FeaturedContent';
-import { AllVideos, AllMusicAlbums, Video } from '.';
-
-export const FeaturedContent: FeaturedContentType = {
-  id: newEntityId(),
-  topVideo: Video,
-  featuredVideos: AllVideos,
-  featuredAlbums: AllMusicAlbums
-} as unknown as FeaturedContentType; // A hack to fix TS compilation.

+ 0 - 11
pioneer/packages/joy-media/src/mocks/Language.mock.ts

@@ -1,11 +0,0 @@
-import { newEntityId } from './EntityId.mock';
-import { LanguageType } from '../schemas/general/Language';
-
-const values = [
-  'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az', 'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce', 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee', 'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr', 'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr', 'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is', 'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn', 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln', 'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mr', 'ms', 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr', 'nv', 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt', 'qu', 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw', 'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh', 'yi', 'yo', 'za', 'zh', 'zu'
-];
-
-export const AllLanguages: LanguageType[] =
-  values.map((value) => ({ id: newEntityId(), value })) as unknown as LanguageType[]; // A hack to fix TS compilation.
-
-export const Language = AllLanguages[0];

+ 0 - 17
pioneer/packages/joy-media/src/mocks/MediaObject.mock.ts

@@ -1,17 +0,0 @@
-import { newEntityId } from './EntityId.mock';
-import { MediaObjectType } from '../schemas/general/MediaObject';
-
-const values = [
-  '5Gm2XPvDm1RhYW2CEbVvrMbRFguL4TMmzP3tm72wvhCZdx5G',
-  '5GTGqmWTurJhYY5UoHzFrAqAxL5ry4Jegw9pmjKniQ3KWWww',
-  '5CbyRopmCNwLYyRCwHrmovoQ15MMCau9v9cmazbWuQjY9DG2',
-  '5GTXWLWgfCM6GpsBkeJQZvF6RFvZh3SjCsH8aGUY1WwV5YGU',
-  '5CSBeDZR5baBcnLYZsP839P1uqZKfz3D9Uip43uvhUd56XAq',
-  '5EXsnf4sS6wVsgjqQmT2jchP2LdGXLxZZSJijjTiUxcLm7Vg',
-  '5HRieqw8oRZfwc6paio4TrBeYvmdTstGB2KoKE9gL5qLnAQY'
-];
-
-export const AllMediaObjects: MediaObjectType[] =
-  values.map((value) => ({ id: newEntityId(), value })) as unknown as MediaObjectType[]; // A hack to fix TS compilation.
-
-export const MediaObject = AllMediaObjects[0];

+ 0 - 34
pioneer/packages/joy-media/src/mocks/MusicAlbum.mock.ts

@@ -1,34 +0,0 @@
-import { newEntityId } from './EntityId.mock';
-import { MusicAlbumType } from '../schemas/music/MusicAlbum';
-import { MusicGenre } from './MusicGenre.mock';
-import { MusicMood } from './MusicMood.mock';
-import { MusicTheme } from './MusicTheme.mock';
-import { DefaultPublicationStatus } from './PublicationStatus.mock';
-import { DefaultCurationStatus } from './CurationStatus.mock';
-import { ContentLicense } from './ContentLicense.mock';
-import { Language } from './Language.mock';
-
-export const MusicAlbum: MusicAlbumType = {
-  id: newEntityId(),
-  title: 'Riddle',
-  artist: 'Liquid Stone',
-  thumbnail: 'https://images.unsplash.com/photo-1484352491158-830ef5692bb3?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60',
-  description: 'Building material is any material which is used for construction purposes. Many naturally occurring substances, such as clay, rocks, sand, and wood, even twigs and leaves, have been used to construct buildings.\n\nApart from naturally occurring materials, many man-made products are in use, some more and some less synthetic.',
-  firstReleased: 567425123, // 1987 year.
-  genre: MusicGenre,
-  mood: MusicMood,
-  theme: MusicTheme,
-  tracks: [],
-  language: Language,
-  link: [],
-  lyrics: undefined,
-  composerOrSongwriter: 'Massive Sand',
-  reviews: [],
-  publicationStatus: DefaultPublicationStatus,
-  curationStatus: DefaultCurationStatus,
-  explicit: false,
-  license: ContentLicense,
-  attribution: undefined
-} as unknown as MusicAlbumType; // A hack to fix TS compilation.
-
-export const AllMusicAlbums: MusicAlbumType[] = [MusicAlbum];

+ 0 - 31
pioneer/packages/joy-media/src/mocks/MusicGenre.mock.ts

@@ -1,31 +0,0 @@
-import { newEntityId } from './EntityId.mock';
-import { MusicGenreType } from '../schemas/music/MusicGenre';
-
-const values = [
-  'Avant-Garde',
-  'Blues',
-  'Children\'s',
-  'Classical',
-  'Comedy/Spoken',
-  'Country',
-  'Easy Listening',
-  'Electronic',
-  'Folk',
-  'Holiday',
-  'International',
-  'Jazz',
-  'Latin',
-  'New Age',
-  'Pop/Rock',
-  'R&B',
-  'Rap',
-  'Reggae',
-  'Religious',
-  'Stage & Screen',
-  'Vocal'
-];
-
-export const AllMusicGenres: MusicGenreType[] =
-  values.map((value) => ({ id: newEntityId(), value })) as unknown as MusicGenreType[]; // A hack to fix TS compilation.
-
-export const MusicGenre = AllMusicGenres[0];

+ 0 - 299
pioneer/packages/joy-media/src/mocks/MusicMood.mock.ts

@@ -1,299 +0,0 @@
-import { newEntityId } from './EntityId.mock';
-import { MusicMoodType } from '../schemas/music/MusicMood';
-
-const values = [
-  'Acerbic',
-  'Aggressive',
-  'Agreeable',
-  'Airy',
-  'Ambitious',
-  'Amiable/Good-Natured',
-  'Angry',
-  'Angst-Ridden',
-  'Anguished/Distraught',
-  'Angular',
-  'Animated',
-  'Apocalyptic',
-  'Arid',
-  'Athletic',
-  'Atmospheric',
-  'Austere',
-  'Autumnal',
-  'Belligerent',
-  'Benevolent',
-  'Bitter',
-  'Bittersweet',
-  'Bleak',
-  'Boisterous',
-  'Bombastic',
-  'Brash',
-  'Brassy',
-  'Bravado',
-  'Bright',
-  'Brittle',
-  'Brooding',
-  'Calm/Peaceful',
-  'Campy',
-  'Capricious',
-  'Carefree',
-  'Cartoonish',
-  'Cathartic',
-  'Celebratory',
-  'Cerebral',
-  'Cheerful',
-  'Child-like',
-  'Circular',
-  'Clinical',
-  'Cold',
-  'Comic',
-  'Complex',
-  'Concise',
-  'Confident',
-  'Confrontational',
-  'Cosmopolitan',
-  'Crunchy',
-  'Cynical/Sarcastic',
-  'Dark',
-  'Declamatory',
-  'Defiant',
-  'Delicate',
-  'Demonic',
-  'Desperate',
-  'Detached',
-  'Devotional',
-  'Difficult',
-  'Dignified/Noble',
-  'Dramatic',
-  'Dreamy',
-  'Driving',
-  'Druggy',
-  'Earnest',
-  'Earthy',
-  'Ebullient',
-  'Eccentric',
-  'Ecstatic',
-  'Eerie',
-  'Effervescent',
-  'Elaborate',
-  'Elegant',
-  'Elegiac',
-  'Energetic',
-  'Enigmatic',
-  'Epic',
-  'Erotic',
-  'Ethereal',
-  'Euphoric',
-  'Exciting',
-  'Exotic',
-  'Explosive',
-  'Extroverted',
-  'Exuberant',
-  'Fantastic/Fantasy-like',
-  'Feral',
-  'Feverish',
-  'Fierce',
-  'Fiery',
-  'Flashy',
-  'Flowing',
-  'Fractured',
-  'Freewheeling',
-  'Fun',
-  'Funereal',
-  'Gentle',
-  'Giddy',
-  'Gleeful',
-  'Gloomy',
-  'Graceful',
-  'Greasy',
-  'Grim',
-  'Gritty',
-  'Gutsy',
-  'Happy',
-  'Harsh',
-  'Hedonistic',
-  'Heroic',
-  'Hostile',
-  'Humorous',
-  'Hungry',
-  'Hymn-like',
-  'Hyper',
-  'Hypnotic',
-  'Improvisatory',
-  'Indulgent',
-  'Innocent',
-  'Insular',
-  'Intense',
-  'Intimate',
-  'Introspective',
-  'Ironic',
-  'Irreverent',
-  'Jovial',
-  'Joyous',
-  'Kinetic',
-  'Knotty',
-  'Laid-Back/Mellow',
-  'Languid',
-  'Lazy',
-  'Light',
-  'Literate',
-  'Lively',
-  'Lonely',
-  'Lush',
-  'Lyrical',
-  'Macabre',
-  'Magical',
-  'Majestic',
-  'Malevolent',
-  'Manic',
-  'Marching',
-  'Martial',
-  'Meandering',
-  'Mechanical',
-  'Meditative',
-  'Melancholy',
-  'Menacing',
-  'Messy',
-  'Mighty',
-  'Monastic',
-  'Monumental',
-  'Motoric',
-  'Mysterious',
-  'Mystical',
-  'Naive',
-  'Narcotic',
-  'Narrative',
-  'Negative',
-  'Nervous/Jittery',
-  'Nihilistic',
-  'Nocturnal',
-  'Nostalgic',
-  'Ominous',
-  'Optimistic',
-  'Opulent',
-  'Organic',
-  'Ornate',
-  'Outraged',
-  'Outrageous',
-  'Paranoid',
-  'Passionate',
-  'Pastoral',
-  'Patriotic',
-  'Perky',
-  'Philosophical',
-  'Plain',
-  'Plaintive',
-  'Playful',
-  'Poignant',
-  'Positive',
-  'Powerful',
-  'Precious',
-  'Provocative',
-  'Pulsing',
-  'Pure',
-  'Quirky',
-  'Rambunctious',
-  'Ramshackle',
-  'Raucous',
-  'Reassuring/Consoling',
-  'Rebellious',
-  'Reckless',
-  'Refined',
-  'Reflective',
-  'Regretful',
-  'Relaxed',
-  'Reserved',
-  'Resolute',
-  'Restrained',
-  'Reverent',
-  'Rhapsodic',
-  'Rollicking',
-  'Romantic',
-  'Rousing',
-  'Rowdy',
-  'Rustic',
-  'Sacred',
-  'Sad',
-  'Sarcastic',
-  'Sardonic',
-  'Satirical',
-  'Savage',
-  'Scary',
-  'Scattered',
-  'Searching',
-  'Self-Conscious',
-  'Sensual',
-  'Sentimental',
-  'Serious',
-  'Severe',
-  'Sexual',
-  'Sexy',
-  'Shimmering',
-  'Silly',
-  'Sleazy',
-  'Slick',
-  'Smooth',
-  'Snide',
-  'Soft/Quiet',
-  'Somber',
-  'Soothing',
-  'Sophisticated',
-  'Spacey',
-  'Sparkling',
-  'Sparse',
-  'Spicy',
-  'Spiritual',
-  'Spontaneous',
-  'Spooky',
-  'Sprawling',
-  'Sprightly',
-  'Springlike',
-  'Stately',
-  'Street-Smart',
-  'Striding',
-  'Strong',
-  'Stylish',
-  'Suffocating',
-  'Sugary',
-  'Summery',
-  'Suspenseful',
-  'Swaggering',
-  'Sweet',
-  'Swinging',
-  'Technical',
-  'Tender',
-  'Tense/Anxious',
-  'Theatrical',
-  'Thoughtful',
-  'Threatening',
-  'Thrilling',
-  'Thuggish',
-  'Tragic',
-  'Transparent/Translucent',
-  'Trashy',
-  'Trippy',
-  'Triumphant',
-  'Tuneful',
-  'Turbulent',
-  'Uncompromising',
-  'Understated',
-  'Unsettling',
-  'Uplifting',
-  'Urgent',
-  'Virile',
-  'Visceral',
-  'Volatile',
-  'Vulgar',
-  'Warm',
-  'Weary',
-  'Whimsical',
-  'Wintry',
-  'Wistful',
-  'Witty',
-  'Wry',
-  'Yearning'
-];
-
-export const AllMusicMoods: MusicMoodType[] =
-  values.map((value) => ({ id: newEntityId(), value })) as unknown as MusicMoodType[]; // A hack to fix TS compilation.
-
-export const MusicMood = AllMusicMoods[0];

+ 0 - 192
pioneer/packages/joy-media/src/mocks/MusicTheme.mock.ts

@@ -1,192 +0,0 @@
-import { newEntityId } from './EntityId.mock';
-import { MusicThemeType } from '../schemas/music/MusicTheme';
-
-const values = [
-  'Adventure',
-  'Affection/Fondness',
-  'Affirmation',
-  'Anger/Hostility',
-  'Animals',
-  'Anniversary',
-  'Argument',
-  'At the Beach',
-  'At the Office',
-  'Autumn',
-  'Award Winners',
-  'Awareness',
-  'Background Music',
-  'Biographical',
-  'Birth',
-  'Birthday',
-  'Breakup',
-  'Cars',
-  'Celebration',
-  'Celebrities',
-  'Children',
-  'Christmas',
-  'Christmas Party',
-  'City Life',
-  'Classy Gatherings',
-  'Club',
-  'Comfort',
-  'Conflict',
-  'Cool & Cocky',
-  'Country Life',
-  'Crime',
-  'D-I-V-O-R-C-E',
-  'Dance Party',
-  'Day Driving',
-  'Daydreaming',
-  'Death',
-  'Despair',
-  'Destiny',
-  'Dinner Ambiance',
-  'Disappointment',
-  'Dreaming',
-  'Drinking',
-  'Drugs',
-  'Early Morning',
-  'Easter',
-  'Empowering',
-  'Everyday Life',
-  'Exercise/Workout',
-  'Family',
-  'Family Gatherings',
-  'Fantasy',
-  'Fear',
-  'Feeling Blue',
-  'Flying',
-  'Food/Eating',
-  'Forgiveness',
-  'Fourth of July',
-  'Freedom',
-  'Friendship',
-  'Funeral',
-  'Girls Night Out',
-  'Good Times',
-  'Goodbyes',
-  'Graduation',
-  'Guys Night Out',
-  'Halloween',
-  'Hanging Out',
-  'Happiness',
-  'Healing/Comfort',
-  'Heartache',
-  'Heartbreak',
-  'High School',
-  'Historical Events',
-  'Holidays',
-  'Home',
-  'Homecoming',
-  'Hope',
-  'Housework',
-  'Illness',
-  'In Love',
-  'Introspection',
-  'Jealousy',
-  'Joy',
-  'Late Night',
-  'Lifecycle',
-  'Loneliness',
-  'Long Walk',
-  'Loss/Grief',
-  'Lying',
-  'Magic',
-  'Maverick',
-  'Meditation',
-  'Memorial',
-  'Military',
-  'Mischievous',
-  'Monday Morning',
-  'Money',
-  'Moon',
-  'Morning',
-  'Motivation',
-  'Music',
-  'Myths & Legends',
-  'Nature',
-  'New Love',
-  'Night Driving',
-  'Nighttime',
-  'Open Road',
-  'Other Times & Places',
-  'Pain',
-  'Parenthood',
-  'Partying',
-  'Passion',
-  'Patriotism',
-  'Peace',
-  'Picnic',
-  'Playful',
-  'Poetry',
-  'Politics/Society',
-  'Pool Party',
-  'Prom',
-  'Promises',
-  'Protest',
-  'Rainy Day',
-  'Reflection',
-  'Regret',
-  'Relationships',
-  'Relaxation',
-  'Religion',
-  'Reminiscing',
-  'Reunion',
-  'Revolutionary',
-  'Road Trip',
-  'Romance',
-  'Romantic Evening',
-  'Scary Music',
-  'School',
-  'Science',
-  'SciFi',
-  'Seduction',
-  'Separation',
-  'Sex',
-  'Slow Dance',
-  'Small Gathering',
-  'Solitude',
-  'Sorrow',
-  'Sports',
-  'Spring',
-  'Starry Sky',
-  'Starting Out',
-  'Stay in Bed',
-  'Storms',
-  'Street Life',
-  'Summer',
-  'Sun',
-  'Sunday Afternoon',
-  'Sweet Dreams',
-  'Teenagers',
-  'Temptation',
-  'TGIF',
-  'Thanksgiving',
-  'The Creative Side',
-  'The Great Outdoors',
-  'Theme',
-  'Tragedy',
-  'Travel',
-  'Truth',
-  'Vacation',
-  'Victory',
-  'Violence',
-  'Visions',
-  'War',
-  'Water',
-  'Weather',
-  'Wedding',
-  'Winter',
-  'Wisdom',
-  'Word Play',
-  'Work',
-  'World View',
-  'Yearning',
-  'Youth',
-  'Zeitgeist'
-];
-
-export const AllMusicThemes: MusicThemeType[] =
-  values.map((value) => ({ id: newEntityId(), value })) as unknown as MusicThemeType[]; // A hack to fix TS compilation.
-
-export const MusicTheme = AllMusicThemes[0];

+ 0 - 33
pioneer/packages/joy-media/src/mocks/MusicTrack.mock.ts

@@ -1,33 +0,0 @@
-import { newEntityId } from './EntityId.mock';
-import { MusicTrackType } from '../schemas/music/MusicTrack';
-import { MusicGenre } from './MusicGenre.mock';
-import { MusicMood } from './MusicMood.mock';
-import { MusicTheme } from './MusicTheme.mock';
-import { DefaultPublicationStatus } from './PublicationStatus.mock';
-import { DefaultCurationStatus } from './CurationStatus.mock';
-import { ContentLicense } from './ContentLicense.mock';
-import { Language } from './Language.mock';
-
-export const MusicTrack: MusicTrackType = {
-  id: newEntityId(),
-  title: 'Requiem (Mozart)',
-  artist: 'Berlin Philharmonic',
-  thumbnail: 'https://assets.classicfm.com/2017/36/mozart-1504532179-list-handheld-0.jpg',
-  description: 'The Requiem in D minor, K. 626, is a requiem mass by Wolfgang Amadeus Mozart (1756–1791). Mozart composed part of the Requiem in Vienna in late 1791, but it was unfinished at his death on 5 December the same year.',
-  language: Language,
-  firstReleased: 567425967, // 1987 year.
-  genre: MusicGenre,
-  mood: MusicMood,
-  theme: MusicTheme,
-  link: [],
-  composerOrSongwriter: 'Mozart',
-  lyrics: undefined,
-  object: undefined,
-  publicationStatus: DefaultPublicationStatus,
-  curationStatus: DefaultCurationStatus,
-  explicit: false,
-  license: ContentLicense,
-  attribution: undefined
-} as unknown as MusicTrackType; // A hack to fix TS compilation.
-
-export const AllMusicTracks: MusicTrackType[] = [MusicTrack];

+ 0 - 16
pioneer/packages/joy-media/src/mocks/PublicationStatus.mock.ts

@@ -1,16 +0,0 @@
-import { newEntityId } from './EntityId.mock';
-import { PublicationStatusType } from '../schemas/general/PublicationStatus';
-
-function newEntity (value: string): PublicationStatusType {
-  return { id: newEntityId(), value } as unknown as PublicationStatusType; // A hack to fix TS compilation.
-}
-
-export const PublicationStatus = {
-  Publiс: newEntity('Public'),
-  Unlisted: newEntity('Unlisted')
-};
-
-export const AllPublicationStatuses: PublicationStatusType[] =
-  Object.values(PublicationStatus);
-
-export const DefaultPublicationStatus = PublicationStatus.Publiс;

+ 0 - 53
pioneer/packages/joy-media/src/mocks/Video.mock.ts

@@ -1,53 +0,0 @@
-import { newEntityId } from './EntityId.mock';
-import { VideoType } from '../schemas/video/Video';
-import { Language } from './Language.mock';
-import { VideoCategory } from './VideoCategory.mock';
-import { DefaultPublicationStatus } from './PublicationStatus.mock';
-import { DefaultCurationStatus } from './CurationStatus.mock';
-import { ContentLicense } from './ContentLicense.mock';
-
-const titles = [
-  'Arborvitae (Thuja occidentalis)',
-  'Black Ash (Fraxinus nigra)',
-  'White Ash (Fraxinus americana)',
-  'Bigtooth Aspen (Populus grandidentata)',
-  'Quaking Aspen (Populus tremuloides)',
-  'Basswood (Tilia americana)',
-  'American Beech (Fagus grandifolia)',
-  'Black Birch (Betula lenta)',
-  'Gray Birch (Betula populifolia)',
-  'Paper Birch (Betula papyrifera)',
-  'Yellow Birch (Betula alleghaniensis)',
-  'Butternut (Juglans cinerea)',
-  'Black Cherry (Prunus serotina)',
-  'Pin Cherry (Prunus pensylvanica)'
-];
-
-const thumbnails = [
-  'https://images.unsplash.com/photo-1477414348463-c0eb7f1359b6?ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=60',
-  'https://images.unsplash.com/photo-1484352491158-830ef5692bb3?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60',
-  'https://images.unsplash.com/photo-1543467091-5f0406620f8b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=200&q=60',
-  'https://images.unsplash.com/photo-1526749837599-b4eba9fd855e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=200&q=60',
-  'https://images.unsplash.com/photo-1504567961542-e24d9439a724?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=200&q=60',
-  'https://images.unsplash.com/photo-1543716091-a840c05249ec?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=200&q=60',
-  'https://images.unsplash.com/photo-1444465693019-aa0b6392460d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=200&q=60'
-];
-
-export const AllVideos: VideoType[] = thumbnails.map((thumbnail, i) => ({
-  id: newEntityId(),
-  title: titles[i],
-  thumbnail,
-  description: 'Nature, in the broadest sense, is the natural, physical, or material world or universe. "Nature" can refer to the phenomena of the physical world, and also to life in general. The study of nature is a large, if not the only, part of science. Although humans are part of nature, human activity is often understood as a separate category from other natural phenomena.',
-  language: Language,
-  firstReleased: 567425543, // 1987 year.
-  category: VideoCategory,
-  link: [],
-  object: undefined,
-  publicationStatus: DefaultPublicationStatus,
-  curationStatus: DefaultCurationStatus,
-  explicit: true,
-  license: ContentLicense,
-  attribution: undefined
-})) as unknown as VideoType[]; // A hack to fix TS compilation.
-
-export const Video = AllVideos[0];

+ 0 - 25
pioneer/packages/joy-media/src/mocks/VideoCategory.mock.ts

@@ -1,25 +0,0 @@
-import { newEntityId } from './EntityId.mock';
-import { VideoCategoryType } from '../schemas/video/VideoCategory';
-
-const values = [
-  'Film & Animation',
-  'Autos & Vehicles',
-  'Music',
-  'Pets & Animals',
-  'Sports',
-  'Travel & Events',
-  'Gaming',
-  'People & Blogs',
-  'Comedy',
-  'Entertainment',
-  'News & Politics',
-  'Howto & Style',
-  'Education',
-  'Science & Technology',
-  'Nonprofits & Activism'
-];
-
-export const AllVideoCategories: VideoCategoryType[] =
-  values.map((value) => ({ id: newEntityId(), value })) as unknown as VideoCategoryType[]; // A hack to fix TS compilation.
-
-export const VideoCategory = AllVideoCategories[0];

+ 0 - 14
pioneer/packages/joy-media/src/mocks/index.ts

@@ -1,14 +0,0 @@
-export * from './ContentLicense.mock';
-export * from './CurationStatus.mock';
-export * from './EntityId.mock';
-export * from './FeaturedContent.mock';
-export * from './Language.mock';
-export * from './MediaObject.mock';
-export * from './MusicAlbum.mock';
-export * from './MusicGenre.mock';
-export * from './MusicMood.mock';
-export * from './MusicTheme.mock';
-export * from './MusicTrack.mock';
-export * from './PublicationStatus.mock';
-export * from './Video.mock';
-export * from './VideoCategory.mock';

+ 0 - 14
pioneer/packages/joy-media/src/music/EditAlbumModal.tsx

@@ -1,14 +0,0 @@
-import React from 'react';
-import { Button, Modal } from 'semantic-ui-react';
-import { TracksOfMyMusicAlbumProps, TracksOfMyMusicAlbum } from './MusicAlbumTracks';
-
-export const EditAlbumModal = (props: TracksOfMyMusicAlbumProps) => {
-  return <Modal trigger={<Button icon='pencil'>Edit album</Button>} centered={false}>
-    <Modal.Header>Edit My Album</Modal.Header>
-    <Modal.Content image>
-      <Modal.Description>
-        <TracksOfMyMusicAlbum {...props} />
-      </Modal.Description>
-    </Modal.Content>
-  </Modal>;
-};

+ 0 - 180
pioneer/packages/joy-media/src/music/EditMusicAlbum.tsx

@@ -1,180 +0,0 @@
-import React from 'react';
-import { Button, Tab } from 'semantic-ui-react';
-import { Form, withFormik } from 'formik';
-import { History } from 'history';
-
-import { TxButton } from '@polkadot/joy-utils/react/components';
-import { onImageError } from '../common/images';
-import { ReorderableTracks } from './ReorderableTracks';
-import { MusicAlbumValidationSchema, MusicAlbumType, MusicAlbumClass as Fields, MusicAlbumFormValues, MusicAlbumToFormValues } from '../schemas/music/MusicAlbum';
-import { withMediaForm, MediaFormProps, datePlaceholder } from '../common/MediaForms';
-import EntityId from '@joystream/types/versioned-store/EntityId';
-import { MediaDropdownOptions } from '../common/MediaDropdownOptions';
-import { MusicTrackReaderPreviewProps } from './MusicTrackReaderPreview';
-import { FormTabs } from '../common/FormTabs';
-
-export type OuterProps = {
-  history?: History;
-  id?: EntityId;
-  entity?: MusicAlbumType;
-  tracks?: MusicTrackReaderPreviewProps[];
-  opts?: MediaDropdownOptions;
-};
-
-type FormValues = MusicAlbumFormValues;
-
-const InnerForm = (props: MediaFormProps<OuterProps, FormValues>) => {
-  const {
-    // React components for form fields:
-    MediaText,
-    MediaDropdown,
-    LabelledField,
-
-    // Callbacks:
-    onSubmit,
-    onTxSuccess,
-    onTxFailed,
-
-    // history,
-    entity,
-    tracks = [],
-    opts = MediaDropdownOptions.Empty,
-
-    values,
-    dirty,
-    errors,
-    isValid,
-    isSubmitting,
-    resetForm
-  } = props;
-
-  const { thumbnail } = values;
-
-  const isNew = !entity;
-
-  const buildTxParams = () => {
-    if (!isValid) return [];
-
-    return [];
-  };
-
-  const basicInfoTab = () => <Tab.Pane as='div'>
-    <MediaText field={Fields.title} {...props} />
-    <MediaText field={Fields.artist} {...props} />
-    <MediaText field={Fields.thumbnail} {...props} />
-    <MediaText field={Fields.description} textarea {...props} />
-    <MediaText field={Fields.firstReleased} placeholder={datePlaceholder} {...props} />
-    <MediaText field={Fields.explicit} {...props} />
-    <MediaDropdown field={Fields.license} options={opts.contentLicenseOptions} {...props} />
-    <MediaDropdown field={Fields.publicationStatus} options={opts.publicationStatusOptions} {...props} />
-  </Tab.Pane>;
-
-  const additionalTab = () => <Tab.Pane as='div'>
-    <MediaText field={Fields.composerOrSongwriter} {...props} />
-    <MediaDropdown field={Fields.genre} options={opts.musicGenreOptions} {...props} />
-    <MediaDropdown field={Fields.mood} options={opts.musicMoodOptions} {...props} />
-    <MediaDropdown field={Fields.theme} options={opts.musicThemeOptions} {...props} />
-    <MediaDropdown field={Fields.language} options={opts.languageOptions} {...props} />
-    <MediaText field={Fields.lyrics} {...props} />
-    <MediaText field={Fields.attribution} {...props} />
-  </Tab.Pane>;
-
-  const tracksTab = () => <Tab.Pane as='div'>
-    <ReorderableTracks
-      tracks={tracks}
-      noTracksView={<em style={{ padding: '1rem 0', display: 'block' }}>This album has no tracks yet.</em>}
-    />
-  </Tab.Pane>;
-
-  const tabs = <FormTabs errors={errors} panes={[
-    {
-      id: 'Basic info',
-      render: basicInfoTab,
-      fields: [
-        Fields.title,
-        Fields.artist,
-        Fields.thumbnail,
-        Fields.description,
-        Fields.firstReleased,
-        Fields.explicit,
-        Fields.license,
-        Fields.publicationStatus
-      ]
-    },
-    {
-      id: 'Additional',
-      render: additionalTab,
-      fields: [
-        Fields.composerOrSongwriter,
-        Fields.genre,
-        Fields.mood,
-        Fields.theme,
-        Fields.language,
-        Fields.lyrics,
-        Fields.attribution
-      ]
-    },
-    {
-      id: `Tracks (${tracks.length})`,
-      render: tracksTab
-    }
-  ]} />;
-
-  const renderMainButton = () =>
-    <TxButton
-      type='submit'
-      isDisabled={!dirty || isSubmitting}
-      label={isNew
-        ? 'Publish'
-        : 'Update'
-      }
-      params={buildTxParams()}
-      tx={isNew
-        ? 'dataDirectory.addMetadata'
-        : 'dataDirectory.updateMetadata'
-      }
-      onClick={onSubmit}
-      txFailedCb={onTxFailed}
-      txSuccessCb={onTxSuccess}
-    />;
-
-  return <div className='EditMetaBox'>
-    <div className='EditMetaThumb'>
-      {thumbnail && <img src={thumbnail} onError={onImageError} />}
-    </div>
-
-    <Form className='ui form JoyForm EditMetaForm'>
-
-      {tabs}
-
-      <LabelledField style={{ marginTop: '1rem' }} {...props} flex>
-        {renderMainButton()}
-        <Button
-          type='button'
-          size='large'
-          disabled={!dirty || isSubmitting}
-          onClick={() => resetForm()}
-          content='Reset form'
-        />
-      </LabelledField>
-    </Form>
-  </div>;
-};
-
-export const EditForm = withFormik<OuterProps, FormValues>({
-
-  // Transform outer props into form values
-  mapPropsToValues: (props): FormValues => {
-    const { entity } = props;
-
-    return MusicAlbumToFormValues(entity);
-  },
-
-  validationSchema: MusicAlbumValidationSchema,
-
-  handleSubmit: () => {
-    // do submitting things
-  }
-})(withMediaForm(InnerForm) as any);
-
-export default EditForm;

+ 0 - 16
pioneer/packages/joy-media/src/music/EditMusicAlbum.view.tsx

@@ -1,16 +0,0 @@
-import { MediaView } from '../MediaView';
-import { OuterProps, EditForm } from './EditMusicAlbum';
-
-export const EditMusicAlbumView = MediaView<OuterProps>({
-  component: EditForm,
-  triggers: ['id'],
-  resolveProps: async (props) => {
-    const { transport, id } = props;
-    const entity = id ? await transport.musicAlbumById(id) : undefined;
-    const opts = await transport.dropdownOptions();
-
-    return { entity, opts };
-  }
-});
-
-export default EditMusicAlbumView;

+ 0 - 40
pioneer/packages/joy-media/src/music/MusicAlbumPreview.tsx

@@ -1,40 +0,0 @@
-import React from 'react';
-import { Button } from 'semantic-ui-react';
-import { Pluralize } from '@polkadot/joy-utils/react/components';
-import { BgImg } from '../common/BgImg';
-import { ChannelEntity } from '../entities/ChannelEntity';
-
-export type MusicAlbumPreviewProps = {
-  id: string;
-  title: string;
-  artist: string;
-  cover: string;
-  tracksCount: number;
-
-  // Extra props:
-  channel?: ChannelEntity;
-  size?: number;
-  withActions?: boolean;
-};
-
-export function MusicAlbumPreview (props: MusicAlbumPreviewProps) {
-  const { size = 200 } = props;
-
-  // TODO show the channel this album belongs to.
-
-  return <div className='JoyMusicAlbumPreview'>
-
-    <BgImg className='AlbumCover' url={props.cover} size={size} />
-
-    <div className='AlbumDescription' style={{ maxWidth: size }}>
-      <h3 className='AlbumTitle'>{props.title}</h3>
-      <div className='AlbumArtist'>{props.artist}</div>
-      <div className='AlbumTracksCount'><Pluralize count={props.tracksCount} singularText='track' /></div>
-    </div>
-
-    {props.withActions && <div className='AlbumActions'>
-      <Button content='Edit' icon='pencil' />
-      <Button content='Add track' icon='plus' />
-    </div>}
-  </div>;
-}

+ 0 - 59
pioneer/packages/joy-media/src/music/MusicAlbumTracks.tsx

@@ -1,59 +0,0 @@
-import React, { useState } from 'react';
-import { Button, CheckboxProps } from 'semantic-ui-react';
-import { Pluralize } from '@polkadot/joy-utils/react/components';
-import { EditableMusicTrackPreviewProps, MusicTrackPreview } from './MusicTrackPreview';
-import { MusicAlbumPreviewProps, MusicAlbumPreview } from './MusicAlbumPreview';
-
-export type TracksOfMyMusicAlbumProps = {
-  album: MusicAlbumPreviewProps;
-  tracks?: EditableMusicTrackPreviewProps[];
-};
-
-export function TracksOfMyMusicAlbum (props: TracksOfMyMusicAlbumProps) {
-  const [idxsOfSelectedTracks, setIdxsOfSelectedTracks] = useState(new Set<number>());
-
-  const { album, tracks = [] } = props;
-  const tracksCount = (tracks && tracks.length) || 0;
-
-  const onTrackSelect = (
-    trackIdx: number,
-    _event: React.FormEvent<HTMLInputElement>,
-    data: CheckboxProps
-  ) => {
-    const set = new Set(idxsOfSelectedTracks);
-
-    data.checked
-      ? set.add(trackIdx)
-      : set.delete(trackIdx)
-    ;
-    setIdxsOfSelectedTracks(set);
-  };
-
-  const selectedCount = idxsOfSelectedTracks.size;
-
-  const removeButtonText = <span>Remove <Pluralize count={selectedCount} singularText='track' /> from album</span>;
-
-  return <>
-    <MusicAlbumPreview {...album} tracksCount={tracksCount} />
-
-    <div className='JoyTopActionBar'>
-      <Button content='Add track' icon='plus' />
-      {selectedCount > 0 && <Button content={removeButtonText} icon='trash' />}
-    </div>
-
-    <div className='JoyListOfPreviews'>
-      {tracksCount === 0
-        ? <em className='NoItems'>This album has no tracks yet</em>
-        : tracks.map((track, i) =>
-          <MusicTrackPreview
-            key={i}
-            {...track}
-            position={i + 1}
-            onSelect={(e, d) => onTrackSelect(i, e, d)}
-            withRemoveButton
-          />
-        )
-      }
-    </div>
-  </>;
-}

+ 0 - 59
pioneer/packages/joy-media/src/music/MusicTrackPreview.tsx

@@ -1,59 +0,0 @@
-import React, { useState } from 'react';
-import { Button, Checkbox, CheckboxProps } from 'semantic-ui-react';
-
-type OnCheckboxChange = (event: React.FormEvent<HTMLInputElement>, data: CheckboxProps) => void;
-
-export type EditableMusicTrackPreviewProps = {
-  id: string;
-  title: string;
-  artist: string;
-  thumbnail: string;
-  position?: number;
-  selected?: boolean;
-  onSelect?: OnCheckboxChange;
-  onEdit?: () => void;
-  onRemove?: () => void;
-  withEditButton?: boolean;
-  withRemoveButton?: boolean;
-  withActionLabels?: boolean;
-  isDraggable?: boolean;
-};
-
-export function MusicTrackPreview (props: EditableMusicTrackPreviewProps) {
-  const {
-    withActionLabels = false,
-    selected = false,
-    onEdit = () => { /* do nothing */ },
-    onRemove = () => { /* do nothing */ }
-  } = props;
-
-  const [checked, setChecked] = useState(selected);
-
-  const onChange: OnCheckboxChange = (e, d) => {
-    try {
-      props.onSelect && props.onSelect(e, d);
-    } catch (err) {
-      console.log('Error during checkbox change:', err);
-    }
-
-    setChecked(d.checked || false);
-  };
-
-  return <div className={`JoyMusicTrackPreview ${checked ? 'SelectedItem' : ''} ${props.isDraggable ? 'DraggableItem' : ''}`}>
-    {props.onSelect && <div className='CheckboxCell'>
-      <Checkbox checked={checked} onChange={onChange} />
-    </div>}
-    {props.position && <div className='AlbumNumber'>{props.position}</div>}
-    <div className='AlbumCover'>
-      <img src={props.thumbnail} />
-    </div>
-    <div className='AlbumDescription'>
-      <h3 className='AlbumTitle'>{props.title}</h3>
-      <div className='AlbumArtist'>{props.artist}</div>
-    </div>
-    <div className='AlbumActions'>
-      {props.withEditButton && <Button icon='pencil' content={withActionLabels ? 'Edit' : null} onClick={onEdit} />}
-      {props.withRemoveButton && <Button icon='trash' content={withActionLabels ? 'Remove' : null} onClick={onRemove} />}
-    </div>
-  </div>;
-}

+ 0 - 31
pioneer/packages/joy-media/src/music/MusicTrackReaderPreview.tsx

@@ -1,31 +0,0 @@
-import React, { CSSProperties } from 'react';
-import { BgImg } from '../common/BgImg';
-
-export type MusicTrackReaderPreviewProps = {
-  id: string;
-  title: string;
-  artist: string;
-  thumbnail: string;
-  size?: number;
-  orientation?: 'vertical' | 'horizontal';
-};
-
-export function MusicTrackReaderPreview (props: MusicTrackReaderPreviewProps) {
-  const { size = 200, orientation = 'vertical' } = props;
-
-  const descStyle: CSSProperties = {};
-
-  if (orientation === 'vertical') {
-    descStyle.maxWidth = size;
-  }
-
-  return <div className={'JoyMusicAlbumPreview ' + orientation}>
-
-    <BgImg className='AlbumCover' url={props.thumbnail} size={size} />
-
-    <div className='AlbumDescription' style={descStyle}>
-      <h3 className='AlbumTitle'>{props.title}</h3>
-      <div className='AlbumArtist'>{props.artist}</div>
-    </div>
-  </div>;
-}

+ 0 - 27
pioneer/packages/joy-media/src/music/MyMusicAlbums.tsx

@@ -1,27 +0,0 @@
-import React from 'react';
-import { Button } from 'semantic-ui-react';
-import { MusicAlbumPreviewProps, MusicAlbumPreview } from './MusicAlbumPreview';
-
-export type MyMusicAlbumsProps = {
-  albums?: MusicAlbumPreviewProps[];
-};
-
-export function MyMusicAlbums (props: MyMusicAlbumsProps) {
-  const { albums = [] } = props;
-  const albumCount = (albums && albums.length) || 0;
-
-  return <>
-    <h2>{`My music albums (${albumCount})`}</h2>
-    <div className='JoyTopActionBar'>
-      <Button content='New album' icon='plus' />
-    </div>
-    <div>
-      {albumCount === 0
-        ? <em className='NoItems'>{'You don\'t have music albums yet'}</em>
-        : albums.map((album, i) =>
-          <MusicAlbumPreview key={i} {...album} />
-        )
-      }
-    </div>
-  </>;
-}

+ 0 - 164
pioneer/packages/joy-media/src/music/MyMusicTracks.tsx

@@ -1,164 +0,0 @@
-import React, { useState } from 'react';
-import { Button, CheckboxProps, Dropdown, Message } from 'semantic-ui-react';
-
-import { Pluralize, Section } from '@polkadot/joy-utils/react/components';
-
-import { EditableMusicTrackPreviewProps, MusicTrackPreview } from './MusicTrackPreview';
-import { ReorderableTracks } from './ReorderableTracks';
-import { MusicAlbumPreviewProps } from './MusicAlbumPreview';
-
-export type MyMusicTracksProps = {
-  albums?: MusicAlbumPreviewProps[];
-  tracks?: EditableMusicTrackPreviewProps[];
-};
-
-export function MyMusicTracks (props: MyMusicTracksProps) {
-  const [idsOfSelectedTracks, setIdsOfSelectedTracks] = useState(new Set<string>());
-
-  const onTrackSelect = (
-    track: EditableMusicTrackPreviewProps,
-    _event: React.FormEvent<HTMLInputElement>,
-    data: CheckboxProps
-  ) => {
-    const { id } = track;
-    const set = new Set(idsOfSelectedTracks);
-
-    data.checked
-      ? set.add(id)
-      : set.delete(id)
-    ;
-    setIdsOfSelectedTracks(set);
-  };
-
-  const { albums = [], tracks = [] } = props;
-  const albumsCount = albums.length;
-  const tracksCount = tracks.length;
-  const selectedCount = idsOfSelectedTracks.size;
-
-  let longestAlbumName = '';
-
-  albums.forEach((x) => {
-    if (longestAlbumName.length < x.title.length) {
-      longestAlbumName = x.title;
-    }
-  });
-
-  const albumsDropdownOptions = albums.map((x) => {
-    const { id } = x;
-
-    return {
-      key: id,
-      value: id,
-      text: x.title,
-      image: x.cover
-    };
-  });
-
-  const [showSecondScreen, setShowSecondScreen] = useState(false);
-  const [albumName, setAlbumName] = useState<string | undefined>();
-
-  const AlbumDropdown = () => {
-    const style = {
-      display: 'inline-block',
-      opacity: selectedCount ? 1 : 0,
-
-      // This is a required hack to fit every dropdown items on a single line:
-      minWidth: `${longestAlbumName.length / 1.5}rem`
-    };
-
-    return <div style={style}>
-      <Dropdown
-        onChange={(_e, { value: id }) => {
-          const selectedAlbum = albums.find((x) => x.id === id);
-
-          if (selectedAlbum) {
-            setAlbumName(selectedAlbum.title);
-            setShowSecondScreen(true);
-          }
-        }}
-        options={albumsDropdownOptions}
-        placeholder='Select an album'
-        search
-        selection
-        value={albumName}
-      />
-    </div>;
-  };
-
-  const AddTracksText = () => albumsCount
-    ? <span style={{ marginRight: '1rem' }}>
-        Add <Pluralize count={selectedCount} singularText='track' /> to
-    </span>
-    : <em>
-        You have no albums.
-      <Button content='Create first album' icon='plus' />
-    </em>;
-
-  const goBack = () => {
-    setAlbumName('');
-    setShowSecondScreen(false);
-  };
-
-  const renderAllTracks = () => {
-    return <Section title={`My Music Tracks (${tracksCount})`}>
-
-      <div className='JoyTopActionBar'>
-        {selectedCount
-          ? <><AddTracksText /></>
-          : <span>Select tracks to add them to your album</span>
-        }
-        <AlbumDropdown />
-      </div>
-
-      <div className='JoyListOfPreviews'>
-        {tracksCount === 0
-          ? <em className='NoItems'>You have no music tracks yet</em>
-          : tracks.map((track, i) =>
-            <MusicTrackPreview
-              key={i}
-              {...track}
-              position={i + 1}
-              selected={idsOfSelectedTracks.has(track.id)}
-              onSelect={(e, d) => onTrackSelect(track, e, d)}
-              withEditButton
-            />
-          )
-        }
-      </div>
-    </Section>;
-  };
-
-  const selectedTracks = tracks.filter((track) => idsOfSelectedTracks.has(track.id));
-
-  const renderReorderTracks = () => {
-    return <Section title={`Add tracks to album "${albumName || ''}"`}>
-
-      <Message
-        info
-        icon='info'
-        content='You can reorder tracks before adding them to this album.'
-      />
-
-      <ReorderableTracks
-        tracks={selectedTracks}
-        onRemove={(track) => {
-          const set = new Set(idsOfSelectedTracks);
-
-          set.delete(track.id);
-          setIdsOfSelectedTracks(set);
-        }}
-      />
-
-      <div style={{ marginTop: '1rem' }}>
-        <Button size='large' onClick={goBack}>&lt; Back to my tracks</Button>
-        <Button size='large' primary style={{ float: 'right' }} onClick={() => alert('Not implemented yet')}>Add to album &gt;</Button>
-      </div>
-    </Section>;
-  };
-
-  return <div className='JoyPaperWidth'>{
-    !showSecondScreen
-      ? renderAllTracks()
-      : renderReorderTracks()
-  }</div>;
-}

+ 0 - 91
pioneer/packages/joy-media/src/music/ReorderableTracks.tsx

@@ -1,91 +0,0 @@
-import React, { useState } from 'react';
-import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
-import { EditableMusicTrackPreviewProps, MusicTrackPreview } from './MusicTrackPreview';
-
-// A little function to help us with reordering the result
-const reorder = (list: OrderableItem[], startIndex: number, endIndex: number) => {
-  const result = Array.from(list);
-  const [removed] = result.splice(startIndex, 1);
-
-  result.splice(endIndex, 0, removed);
-
-  return result;
-};
-
-type Props = {
-  tracks: EditableMusicTrackPreviewProps[];
-  onRemove?: (track: EditableMusicTrackPreviewProps) => void;
-  noTracksView?: React.ReactElement;
-}
-
-type OrderableItem = EditableMusicTrackPreviewProps;
-
-export const ReorderableTracks = (props: Props) => {
-  const { tracks = [], onRemove = () => { /* do nothing */ }, noTracksView = null } = props;
-
-  const [items, setItems] = useState(tracks);
-
-  if (!items.length) {
-    return noTracksView;
-  }
-
-  const onDragEnd = (result: DropResult) => {
-    // Dropped outside the list
-    if (!result.destination) {
-      return;
-    }
-
-    const reorderedItems = reorder(
-      items,
-      result.source.index,
-      result.destination.index
-    );
-
-    setItems(reorderedItems);
-  };
-
-  // Normally you would want to split things out into separate components.
-  // But in this example everything is just done in one place for simplicity
-  return (
-    <DragDropContext onDragEnd={onDragEnd}>
-      <Droppable droppableId='droppable'>
-        {(provided, _snapshot) => (
-          <div
-            {...provided.droppableProps}
-            // eslint-disable-next-line @typescript-eslint/unbound-method
-            ref={provided.innerRef}
-            className='JoyListOfPreviews'
-          >
-            {items.map((item, index) => (
-              <Draggable key={item.id} draggableId={item.id} index={index}>
-                {(provided, snapshot) => (
-                  <div
-                    // eslint-disable-next-line @typescript-eslint/unbound-method
-                    ref={provided.innerRef}
-                    {...provided.draggableProps}
-                    {...provided.dragHandleProps}
-                  >
-                    <MusicTrackPreview
-                      key={index}
-                      {...item}
-                      position={index + 1}
-                      isDraggable={snapshot.isDragging}
-                      withRemoveButton
-                      onRemove={() => {
-                        onRemove(item);
-                        const lessItems = items.filter((x) => x.id !== item.id);
-
-                        setItems(lessItems);
-                      }}
-                    />
-                  </div>
-                )}
-              </Draggable>
-            ))}
-            {provided.placeholder}
-          </div>
-        )}
-      </Droppable>
-    </DragDropContext>
-  );
-};

+ 0 - 168
pioneer/packages/joy-media/src/schemas/channel/Channel.ts

@@ -1,168 +0,0 @@
-
-import * as Yup from 'yup';
-import { BlockNumber, AccountId } from '@polkadot/types/interfaces';
-import { ChannelContentTypeValue, PrincipalId, Channel, ChannelId, ChannelPublicationStatusValue, ChannelCurationStatusValue } from '@joystream/types/content-working-group';
-import { MemberId } from '@joystream/types/members';
-import { ChannelValidationConstraints } from '@polkadot/joy-media/transport';
-import { ValidationConstraint } from '@polkadot/joy-utils/types/ValidationConstraint';
-
-function textValidation (constraint?: ValidationConstraint) {
-  if (!constraint) {
-    return Yup.string();
-  }
-
-  const { min, max } = constraint;
-
-  return Yup.string()
-    .min(min, `Text is too short. Minimum length is ${min} chars.`)
-    .max(max, `Text is too long. Maximum length is ${max} chars.`);
-}
-
-export const buildChannelValidationSchema = (constraints?: ChannelValidationConstraints) =>
-  Yup.object().shape({
-    handle: textValidation(constraints?.handle).required('This field is required'),
-    title: textValidation(constraints?.title),
-    description: textValidation(constraints?.description),
-    avatar: textValidation(constraints?.avatar),
-    banner: textValidation(constraints?.banner)
-  });
-
-export type ChannelFormValues = {
-  content: ChannelContentTypeValue;
-  handle: string;
-  title: string;
-  description: string;
-  avatar: string;
-  banner: string;
-  publicationStatus: ChannelPublicationStatusValue;
-};
-
-export type ChannelType = {
-  id: number;
-  verified: boolean;
-  handle: string;
-  title?: string;
-  description?: string;
-  avatar?: string;
-  banner?: string;
-  content: ChannelContentTypeValue;
-  owner: MemberId;
-  roleAccount: AccountId;
-  publicationStatus: ChannelPublicationStatusValue;
-  curationStatus: ChannelCurationStatusValue;
-  created: BlockNumber;
-  principalId: PrincipalId;
-};
-
-export class ChannelCodec {
-  static fromSubstrate (id: ChannelId, sub: Channel): ChannelType {
-    return {
-      id: id.toNumber(),
-      verified: sub.verified.valueOf(),
-      handle: sub.handle.toString(),
-      title: sub.title.unwrapOr(undefined)?.toString(),
-      description: sub.description.unwrapOr(undefined)?.toString(),
-      avatar: sub.avatar.unwrapOr(undefined)?.toString(),
-      banner: sub.banner.unwrapOr(undefined)?.toString(),
-      content: sub.content.type,
-      owner: sub.owner,
-      roleAccount: sub.role_account,
-      publicationStatus: sub.publication_status.type,
-      curationStatus: sub.curation_status.type,
-      created: sub.created,
-      principalId: sub.principal_id
-    };
-  }
-}
-
-export function ChannelToFormValues (entity?: ChannelType): ChannelFormValues {
-  return {
-    content: (entity && entity.content) || 'Video',
-    handle: (entity && entity.handle) || '',
-    title: (entity && entity.title) || '',
-    description: (entity && entity.description) || '',
-    avatar: (entity && entity.avatar) || '',
-    banner: (entity && entity.banner) || '',
-    publicationStatus: (entity && entity.publicationStatus) || 'Public'
-  };
-}
-
-export type ChannelPropId =
-  'content' |
-  'handle' |
-  'title' |
-  'description' |
-  'avatar' |
-  'banner' |
-  'publicationStatus'
-  ;
-
-export type ChannelGenericProp = {
-  id: ChannelPropId;
-  type: string;
-  name: string;
-  description?: string;
-  required?: boolean;
-  maxItems?: number;
-  maxTextLength?: number;
-  classId?: any;
-};
-
-type ChannelClassType = {
-  [id in ChannelPropId]: ChannelGenericProp
-};
-
-export const ChannelClass: ChannelClassType = {
-  content: {
-    id: 'content',
-    name: 'Content',
-    description: 'The type of channel.',
-    type: 'Text',
-    required: true,
-    maxTextLength: 100
-  },
-  handle: {
-    id: 'handle',
-    name: 'Handle',
-    description: 'Unique URL handle of channel.',
-    type: 'Text',
-    required: true,
-    maxTextLength: 40
-  },
-  title: {
-    id: 'title',
-    name: 'Title',
-    description: 'Human readable title of channel.',
-    type: 'Text',
-    maxTextLength: 100
-  },
-  description: {
-    id: 'description',
-    name: 'Description',
-    description: 'Human readable description of channel purpose and scope.',
-    type: 'Text',
-    maxTextLength: 4000
-  },
-  avatar: {
-    id: 'avatar',
-    name: 'Avatar',
-    description: 'URL to avatar (logo) iamge: NOTE: Should be an https link to a square image.',
-    type: 'Text',
-    maxTextLength: 1000
-  },
-  banner: {
-    id: 'banner',
-    name: 'Banner',
-    description: 'URL to banner image: NOTE: Should be an https link to a rectangular image, between 1400x1400 and 3000x3000 pixels, in JPEG or PNG format.',
-    type: 'Text',
-    maxTextLength: 1000
-  },
-  publicationStatus: {
-    id: 'publicationStatus',
-    name: 'Publication Status',
-    description: 'The publication status of the channel.',
-    required: true,
-    type: 'Internal',
-    classId: 'Publication Status'
-  }
-};

+ 0 - 60
pioneer/packages/joy-media/src/schemas/general/ContentLicense.ts

@@ -1,60 +0,0 @@
-
-/** This file is generated based on JSON schema. Do not modify. */
-
-import * as Yup from 'yup';
-import { EntityCodec } from '@joystream/types/versioned-store/EntityCodec';
-
-export const ContentLicenseValidationSchema = Yup.object().shape({
-  value: Yup.string()
-    .required('This field is required')
-    .max(200, 'Text is too long. Maximum length is 200 chars.')
-});
-
-export type ContentLicenseFormValues = {
-  value: string;
-};
-
-export type ContentLicenseType = {
-  classId: number;
-  inClassSchemaIndexes: number[];
-  id: number;
-  value: string;
-};
-
-export class ContentLicenseCodec extends EntityCodec<ContentLicenseType> { }
-
-export function ContentLicenseToFormValues (entity?: ContentLicenseType): ContentLicenseFormValues {
-  return {
-    value: (entity && entity.value) || ''
-  };
-}
-
-export type ContentLicensePropId =
-  'value'
-  ;
-
-export type ContentLicenseGenericProp = {
-  id: ContentLicensePropId;
-  type: string;
-  name: string;
-  description?: string;
-  required?: boolean;
-  maxItems?: number;
-  maxTextLength?: number;
-  classId?: any;
-};
-
-type ContentLicenseClassType = {
-  [id in ContentLicensePropId]: ContentLicenseGenericProp
-};
-
-export const ContentLicenseClass: ContentLicenseClassType = {
-  value: {
-    id: 'value',
-    name: 'Value',
-    description: 'The license of which the content is originally published under.',
-    type: 'Text',
-    required: true,
-    maxTextLength: 200
-  }
-};

+ 0 - 60
pioneer/packages/joy-media/src/schemas/general/CurationStatus.ts

@@ -1,60 +0,0 @@
-
-/** This file is generated based on JSON schema. Do not modify. */
-
-import * as Yup from 'yup';
-import { EntityCodec } from '@joystream/types/versioned-store/EntityCodec';
-
-export const CurationStatusValidationSchema = Yup.object().shape({
-  value: Yup.string()
-    .required('This field is required')
-    .max(255, 'Text is too long. Maximum length is 255 chars.')
-});
-
-export type CurationStatusFormValues = {
-  value: string;
-};
-
-export type CurationStatusType = {
-  classId: number;
-  inClassSchemaIndexes: number[];
-  id: number;
-  value: string;
-};
-
-export class CurationStatusCodec extends EntityCodec<CurationStatusType> { }
-
-export function CurationStatusToFormValues (entity?: CurationStatusType): CurationStatusFormValues {
-  return {
-    value: (entity && entity.value) || ''
-  };
-}
-
-export type CurationStatusPropId =
-  'value'
-  ;
-
-export type CurationStatusGenericProp = {
-  id: CurationStatusPropId;
-  type: string;
-  name: string;
-  description?: string;
-  required?: boolean;
-  maxItems?: number;
-  maxTextLength?: number;
-  classId?: any;
-};
-
-type CurationStatusClassType = {
-  [id in CurationStatusPropId]: CurationStatusGenericProp
-};
-
-export const CurationStatusClass: CurationStatusClassType = {
-  value: {
-    id: 'value',
-    name: 'Value',
-    description: 'The curator publication status of the content in the content directory.',
-    required: true,
-    type: 'Text',
-    maxTextLength: 255
-  }
-};

+ 0 - 83
pioneer/packages/joy-media/src/schemas/general/FeaturedContent.ts

@@ -1,83 +0,0 @@
-
-/** This file is generated based on JSON schema. Do not modify. */
-
-import * as Yup from 'yup';
-import { EntityCodec } from '@joystream/types/versioned-store/EntityCodec';
-import { VideoType } from '../video/Video';
-import { MusicAlbumType } from '../music/MusicAlbum';
-
-export const FeaturedContentValidationSchema = Yup.object().shape({
-  // No validation rules.
-});
-
-export type FeaturedContentFormValues = {
-  topVideo: number;
-  featuredVideos: number[];
-  featuredAlbums: number[];
-};
-
-export type FeaturedContentType = {
-  classId: number;
-  inClassSchemaIndexes: number[];
-  id: number;
-  topVideo?: VideoType;
-  featuredVideos?: VideoType[];
-  featuredAlbums?: MusicAlbumType[];
-};
-
-export class FeaturedContentCodec extends EntityCodec<FeaturedContentType> { }
-
-export function FeaturedContentToFormValues (entity?: FeaturedContentType): FeaturedContentFormValues {
-  return {
-    topVideo: (entity && entity.topVideo?.id) || 0,
-    featuredVideos: (entity && entity.featuredVideos?.map((x) => x.id)) || [],
-    featuredAlbums: (entity && entity.featuredAlbums?.map((x) => x.id)) || []
-  };
-}
-
-export type FeaturedContentPropId =
-  'topVideo' |
-  'featuredVideos' |
-  'featuredAlbums'
-  ;
-
-export type FeaturedContentGenericProp = {
-  id: FeaturedContentPropId;
-  type: string;
-  name: string;
-  description?: string;
-  required?: boolean;
-  maxItems?: number;
-  maxTextLength?: number;
-  classId?: any;
-};
-
-type FeaturedContentClassType = {
-  [id in FeaturedContentPropId]: FeaturedContentGenericProp
-};
-
-export const FeaturedContentClass: FeaturedContentClassType = {
-  topVideo: {
-    id: 'topVideo',
-    name: 'Top Video',
-    description: 'The video that has the most prominent position(s) on the platform.',
-    type: 'Internal',
-    classId: 'Video'
-  },
-  featuredVideos: {
-    id: 'featuredVideos',
-    name: 'Featured Videos',
-    description: 'Videos featured in the Video tab.',
-    type: 'InternalVec',
-    maxItems: 12,
-    classId: 'Video'
-  },
-  featuredAlbums: {
-    id: 'featuredAlbums',
-    name: 'Featured Albums',
-    description: 'Music albums featured in the Music tab.',
-    type: 'InternalVec',
-    maxItems: 12,
-    classId: 'Music Album'
-  }
-};

+ 0 - 60
pioneer/packages/joy-media/src/schemas/general/Language.ts

@@ -1,60 +0,0 @@
-
-/** This file is generated based on JSON schema. Do not modify. */
-
-import * as Yup from 'yup';
-import { EntityCodec } from '@joystream/types/versioned-store/EntityCodec';
-
-export const LanguageValidationSchema = Yup.object().shape({
-  value: Yup.string()
-    .required('This field is required')
-    .max(2, 'Text is too long. Maximum length is 2 chars.')
-});
-
-export type LanguageFormValues = {
-  value: string;
-};
-
-export type LanguageType = {
-  classId: number;
-  inClassSchemaIndexes: number[];
-  id: number;
-  value: string;
-};
-
-export class LanguageCodec extends EntityCodec<LanguageType> { }
-
-export function LanguageToFormValues (entity?: LanguageType): LanguageFormValues {
-  return {
-    value: (entity && entity.value) || ''
-  };
-}
-
-export type LanguagePropId =
-  'value'
-  ;
-
-export type LanguageGenericProp = {
-  id: LanguagePropId;
-  type: string;
-  name: string;
-  description?: string;
-  required?: boolean;
-  maxItems?: number;
-  maxTextLength?: number;
-  classId?: any;
-};
-
-type LanguageClassType = {
-  [id in LanguagePropId]: LanguageGenericProp
-};
-
-export const LanguageClass: LanguageClassType = {
-  value: {
-    id: 'value',
-    name: 'Value',
-    description: 'Language code following the ISO 639-1 two letter standard.',
-    type: 'Text',
-    required: true,
-    maxTextLength: 2
-  }
-};

+ 0 - 60
pioneer/packages/joy-media/src/schemas/general/MediaObject.ts

@@ -1,60 +0,0 @@
-
-/** This file is generated based on JSON schema. Do not modify. */
-
-import * as Yup from 'yup';
-import { EntityCodec } from '@joystream/types/versioned-store/EntityCodec';
-
-export const MediaObjectValidationSchema = Yup.object().shape({
-  value: Yup.string()
-    .required('This field is required')
-    .max(66, 'Text is too long. Maximum length is 66 chars.')
-});
-
-export type MediaObjectFormValues = {
-  value: string;
-};
-
-export type MediaObjectType = {
-  classId: number;
-  inClassSchemaIndexes: number[];
-  id: number;
-  value: string;
-};
-
-export class MediaObjectCodec extends EntityCodec<MediaObjectType> { }
-
-export function MediaObjectToFormValues (entity?: MediaObjectType): MediaObjectFormValues {
-  return {
-    value: (entity && entity.value) || ''
-  };
-}
-
-export type MediaObjectPropId =
-  'value'
-  ;
-
-export type MediaObjectGenericProp = {
-  id: MediaObjectPropId;
-  type: string;
-  name: string;
-  description?: string;
-  required?: boolean;
-  maxItems?: number;
-  maxTextLength?: number;
-  classId?: any;
-};
-
-type MediaObjectClassType = {
-  [id in MediaObjectPropId]: MediaObjectGenericProp
-};
-
-export const MediaObjectClass: MediaObjectClassType = {
-  value: {
-    id: 'value',
-    name: 'Value',
-    description: 'Content id of object in the data directory.',
-    type: 'Text',
-    required: true,
-    maxTextLength: 48
-  }
-};

+ 0 - 60
pioneer/packages/joy-media/src/schemas/general/PublicationStatus.ts

@@ -1,60 +0,0 @@
-
-/** This file is generated based on JSON schema. Do not modify. */
-
-import * as Yup from 'yup';
-import { EntityCodec } from '@joystream/types/versioned-store/EntityCodec';
-
-export const PublicationStatusValidationSchema = Yup.object().shape({
-  value: Yup.string()
-    .required('This field is required')
-    .max(50, 'Text is too long. Maximum length is 50 chars.')
-});
-
-export type PublicationStatusFormValues = {
-  value: string;
-};
-
-export type PublicationStatusType = {
-  classId: number;
-  inClassSchemaIndexes: number[];
-  id: number;
-  value: string;
-};
-
-export class PublicationStatusCodec extends EntityCodec<PublicationStatusType> { }
-
-export function PublicationStatusToFormValues (entity?: PublicationStatusType): PublicationStatusFormValues {
-  return {
-    value: (entity && entity.value) || ''
-  };
-}
-
-export type PublicationStatusPropId =
-  'value'
-  ;
-
-export type PublicationStatusGenericProp = {
-  id: PublicationStatusPropId;
-  type: string;
-  name: string;
-  description?: string;
-  required?: boolean;
-  maxItems?: number;
-  maxTextLength?: number;
-  classId?: any;
-};
-
-type PublicationStatusClassType = {
-  [id in PublicationStatusPropId]: PublicationStatusGenericProp
-};
-
-export const PublicationStatusClass: PublicationStatusClassType = {
-  value: {
-    id: 'value',
-    name: 'Value',
-    description: 'The publication status of the content in the content directory.',
-    required: true,
-    type: 'Text',
-    maxTextLength: 50
-  }
-};

+ 0 - 308
pioneer/packages/joy-media/src/schemas/music/MusicAlbum.ts

@@ -1,308 +0,0 @@
-
-/** This file is generated based on JSON schema. Do not modify. */
-
-import * as Yup from 'yup';
-import { EntityCodec } from '@joystream/types/versioned-store/EntityCodec';
-import moment from 'moment';
-import { MusicGenreType } from './MusicGenre';
-import { MusicMoodType } from './MusicMood';
-import { MusicThemeType } from './MusicTheme';
-import { MusicTrackType } from './MusicTrack';
-import { LanguageType } from '../general/Language';
-import { PublicationStatusType } from '../general/PublicationStatus';
-import { CurationStatusType } from '../general/CurationStatus';
-import { ContentLicenseType } from '../general/ContentLicense';
-import { ChannelEntity } from '@polkadot/joy-media/entities/ChannelEntity';
-
-export const MusicAlbumValidationSchema = Yup.object().shape({
-  title: Yup.string()
-    .required('This field is required')
-    .max(255, 'Text is too long. Maximum length is 255 chars.'),
-  artist: Yup.string()
-    .required('This field is required')
-    .max(255, 'Text is too long. Maximum length is 255 chars.'),
-  thumbnail: Yup.string()
-    .required('This field is required')
-    .max(255, 'Text is too long. Maximum length is 255 chars.'),
-  description: Yup.string()
-    .required('This field is required')
-    .max(4000, 'Text is too long. Maximum length is 4000 chars.'),
-  firstReleased: Yup.string()
-    .required('This field is required')
-    .test('valid-date', 'Invalid date. Valid date formats are yyyy-mm-dd or yyyy-mm or yyyy.', (val?: any) => {
-      return moment(val).isValid();
-    }),
-  lyrics: Yup.string()
-    .max(255, 'Text is too long. Maximum length is 255 chars.'),
-  composerOrSongwriter: Yup.string()
-    .max(255, 'Text is too long. Maximum length is 255 chars.'),
-  attribution: Yup.string()
-    .max(255, 'Text is too long. Maximum length is 255 chars.')
-});
-
-export type MusicAlbumFormValues = {
-  title: string;
-  artist: string;
-  thumbnail: string;
-  description: string;
-  firstReleased: string;
-  genre: number;
-  mood: number;
-  theme: number;
-  tracks: number[];
-  language: number;
-  links: string[];
-  lyrics: string;
-  composerOrSongwriter: string;
-  reviews: string[];
-  publicationStatus: number;
-  curationStatus: number;
-  explicit: boolean;
-  license: number;
-  attribution: string;
-  channelId: number;
-};
-
-export type MusicAlbumType = {
-  classId: number;
-  inClassSchemaIndexes: number[];
-  id: number;
-  title: string;
-  artist: string;
-  thumbnail: string;
-  description: string;
-  firstReleased: number;
-  genre?: MusicGenreType;
-  mood?: MusicMoodType;
-  theme?: MusicThemeType;
-  tracks?: MusicTrackType[];
-  language?: LanguageType;
-  links?: string[];
-  lyrics?: string;
-  composerOrSongwriter?: string;
-  reviews?: string[];
-  publicationStatus: PublicationStatusType;
-  curationStatus?: CurationStatusType;
-  explicit: boolean;
-  license: ContentLicenseType;
-  attribution?: string;
-  channelId?: number;
-  channel?: ChannelEntity;
-};
-
-export class MusicAlbumCodec extends EntityCodec<MusicAlbumType> { }
-
-export function MusicAlbumToFormValues (entity?: MusicAlbumType): MusicAlbumFormValues {
-  return {
-    title: (entity && entity.title) || '',
-    artist: (entity && entity.artist) || '',
-    thumbnail: (entity && entity.thumbnail) || '',
-    description: (entity && entity.description) || '',
-    firstReleased: (entity && moment(entity.firstReleased * 1000).format('YYYY-MM-DD')) || '',
-    genre: (entity && entity.genre?.id) || 0,
-    mood: (entity && entity.mood?.id) || 0,
-    theme: (entity && entity.theme?.id) || 0,
-    tracks: (entity && entity.tracks?.map((x) => x.id)) || [],
-    language: (entity && entity.language?.id) || 0,
-    links: (entity && entity.links) || [],
-    lyrics: (entity && entity.lyrics) || '',
-    composerOrSongwriter: (entity && entity.composerOrSongwriter) || '',
-    reviews: (entity && entity.reviews) || [],
-    publicationStatus: (entity && entity.publicationStatus.id) || 0,
-    curationStatus: (entity && entity.curationStatus?.id) || 0,
-    explicit: (entity && entity.explicit) || false,
-    license: (entity && entity.license?.id) || 0,
-    attribution: (entity && entity.attribution) || '',
-    channelId: (entity && entity.channelId) || 0
-  };
-}
-
-export type MusicAlbumPropId =
-  'title' |
-  'artist' |
-  'thumbnail' |
-  'description' |
-  'firstReleased' |
-  'genre' |
-  'mood' |
-  'theme' |
-  'tracks' |
-  'language' |
-  'links' |
-  'lyrics' |
-  'composerOrSongwriter' |
-  'reviews' |
-  'publicationStatus' |
-  'curationStatus' |
-  'explicit' |
-  'license' |
-  'attribution' |
-  'channelId'
-  ;
-
-export type MusicAlbumGenericProp = {
-  id: MusicAlbumPropId;
-  type: string;
-  name: string;
-  description?: string;
-  required?: boolean;
-  maxItems?: number;
-  maxTextLength?: number;
-  classId?: any;
-};
-
-type MusicAlbumClassType = {
-  [id in MusicAlbumPropId]: MusicAlbumGenericProp
-};
-
-export const MusicAlbumClass: MusicAlbumClassType = {
-  title: {
-    id: 'title',
-    name: 'Title',
-    description: 'The title of the album',
-    type: 'Text',
-    required: true,
-    maxTextLength: 255
-  },
-  artist: {
-    id: 'artist',
-    name: 'Artist',
-    description: 'The artist, composer, band or group that published the album.',
-    type: 'Text',
-    required: true,
-    maxTextLength: 255
-  },
-  thumbnail: {
-    id: 'thumbnail',
-    name: 'Thumbnail',
-    description: 'URL to album cover art thumbnail: NOTE: Should be an https link to a square image, between 1400x1400 and 3000x3000 pixels, in JPEG or PNG format.',
-    required: true,
-    type: 'Text',
-    maxTextLength: 255
-  },
-  description: {
-    id: 'description',
-    name: 'Description',
-    description: 'Information about the album and artist.',
-    required: true,
-    type: 'Text',
-    maxTextLength: 4000
-  },
-  firstReleased: {
-    id: 'firstReleased',
-    name: 'First Released',
-    description: 'When the album was first released',
-    required: true,
-    type: 'Int64'
-  },
-  genre: {
-    id: 'genre',
-    name: 'Genre',
-    description: 'The genre of the album.',
-    type: 'Internal',
-    classId: 'Music Genre'
-  },
-  mood: {
-    id: 'mood',
-    name: 'Mood',
-    description: 'The mood of the album.',
-    type: 'Internal',
-    classId: 'Music Mood'
-  },
-  theme: {
-    id: 'theme',
-    name: 'Theme',
-    description: 'The theme of the album.',
-    type: 'Internal',
-    classId: 'Music Theme'
-  },
-  tracks: {
-    id: 'tracks',
-    name: 'Tracks',
-    description: 'The tracks of the album.',
-    type: 'InternalVec',
-    maxItems: 100,
-    classId: 'Music Track'
-  },
-  language: {
-    id: 'language',
-    name: 'Language',
-    description: 'The language of the song lyrics in the album.',
-    required: false,
-    type: 'Internal',
-    classId: 'Language'
-  },
-  links: {
-    id: 'links',
-    name: 'Links',
-    description: 'Links to the artist or album site, or social media pages.',
-    type: 'TextVec',
-    maxItems: 5,
-    maxTextLength: 255
-  },
-  lyrics: {
-    id: 'lyrics',
-    name: 'Lyrics',
-    description: 'Link to the album tracks lyrics.',
-    type: 'Text',
-    maxTextLength: 255
-  },
-  composerOrSongwriter: {
-    id: 'composerOrSongwriter',
-    name: 'Composer or songwriter',
-    description: 'The composer(s) and/or songwriter(s) of the album.',
-    type: 'Text',
-    maxTextLength: 255
-  },
-  reviews: {
-    id: 'reviews',
-    name: 'Reviews',
-    description: 'Links to reviews of the album.',
-    type: 'TextVec',
-    maxItems: 5,
-    maxTextLength: 255
-  },
-  publicationStatus: {
-    id: 'publicationStatus',
-    name: 'Publication Status',
-    description: 'The publication status of the album.',
-    required: true,
-    type: 'Internal',
-    classId: 'Publication Status'
-  },
-  curationStatus: {
-    id: 'curationStatus',
-    name: 'Curation Status',
-    description: 'The publication status of the album set by the a content curator on the platform.',
-    type: 'Internal',
-    classId: 'Curation Status'
-  },
-  explicit: {
-    id: 'explicit',
-    name: 'Explicit',
-    description: 'Indicates whether the album contains explicit material.',
-    required: true,
-    type: 'Bool'
-  },
-  license: {
-    id: 'license',
-    name: 'License',
-    description: 'The license of which the album is released under.',
-    required: true,
-    type: 'Internal',
-    classId: 'Content License'
-  },
-  attribution: {
-    id: 'attribution',
-    name: 'Attribution',
-    description: 'If the License requires attribution, add this here.',
-    type: 'Text',
-    maxTextLength: 255
-  },
-  channelId: {
-    id: 'channelId',
-    name: 'Channel Id',
-    description: 'Id of the channel this album is published under.',
-    type: 'Uint64'
-  }
-};

+ 0 - 60
pioneer/packages/joy-media/src/schemas/music/MusicGenre.ts

@@ -1,60 +0,0 @@
-
-/** This file is generated based on JSON schema. Do not modify. */
-
-import * as Yup from 'yup';
-import { EntityCodec } from '@joystream/types/versioned-store/EntityCodec';
-
-export const MusicGenreValidationSchema = Yup.object().shape({
-  value: Yup.string()
-    .required('This field is required')
-    .max(100, 'Text is too long. Maximum length is 100 chars.')
-});
-
-export type MusicGenreFormValues = {
-  value: string;
-};
-
-export type MusicGenreType = {
-  classId: number;
-  inClassSchemaIndexes: number[];
-  id: number;
-  value: string;
-};
-
-export class MusicGenreCodec extends EntityCodec<MusicGenreType> { }
-
-export function MusicGenreToFormValues (entity?: MusicGenreType): MusicGenreFormValues {
-  return {
-    value: (entity && entity.value) || ''
-  };
-}
-
-export type MusicGenrePropId =
-  'value'
-  ;
-
-export type MusicGenreGenericProp = {
-  id: MusicGenrePropId;
-  type: string;
-  name: string;
-  description?: string;
-  required?: boolean;
-  maxItems?: number;
-  maxTextLength?: number;
-  classId?: any;
-};
-
-type MusicGenreClassType = {
-  [id in MusicGenrePropId]: MusicGenreGenericProp
-};
-
-export const MusicGenreClass: MusicGenreClassType = {
-  value: {
-    id: 'value',
-    name: 'Value',
-    description: 'Genres for music.',
-    required: true,
-    type: 'Text',
-    maxTextLength: 100
-  }
-};

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