Browse Source

Merge 'joystream' and fix conflicts

Leszek Wiesner 4 years ago
parent
commit
920de55c5e

+ 4 - 2
README.md

@@ -1,8 +1,10 @@
-# Joystream webapp
+<p align="center"><img src="img/pioneer_new.svg"></p>
+
+![Content Directory](https://user-images.githubusercontent.com/4144334/67765742-bbfab280-fa44-11e9-8b13-494b1bfb6014.jpeg)
 
 A Portal into the Joystream network. Provides a view and interaction layer from a browser.
 
-This can be accessed as a hosted application via [https://sparta.joystream.org/apps/](https://sparta.joystream.org/apps/).
+This can be accessed as a hosted application via [https://testnet.joystream.org/acropolis/pioneer](https://testnet.joystream.org/acropolis/pioneer).
 
 ## overview
 

File diff suppressed because it is too large
+ 72 - 0
img/pioneer_new.svg


+ 1 - 1
packages/apps/src/SideBar/index.tsx

@@ -176,7 +176,7 @@ function SideBar ({ className, collapse, handleResize, isCollapsed, toggleMenu,
                 )
             ))}
             <Menu.Divider hidden />
-            <OuterLink url='https://testnet.joystream.org/faucet' title='Free Tokens' />
+            <OuterLink url='https://faucet.joystream.org/' title='Free Tokens' />
             <OuterLink url='https://blog.joystream.org/acropolis-incentives/' title='Earn Monero' />
             <Menu.Divider hidden />
             {

+ 61 - 0
packages/joy-media/src/IterableFile.ts

@@ -0,0 +1,61 @@
+// 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:any)=>{
+                e.target.result && resolve(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)
+//  }

+ 65 - 29
packages/joy-media/src/Upload.tsx

@@ -4,7 +4,7 @@ import axios, { CancelTokenSource } from 'axios';
 import { History } from 'history';
 import { Progress, Message } from 'semantic-ui-react';
 
-import { InputFile } from '@polkadot/react-components/index';
+import { InputFileAsync } from '@polkadot/react-components/index';
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { SubmittableResult } from '@polkadot/api';
@@ -23,6 +23,7 @@ import IpfsHash from 'ipfs-only-hash';
 import { ChannelId } from '@joystream/types/content-working-group';
 import { EditVideoView } from './upload/EditVideo.view';
 import { JoyInfo } from '@polkadot/joy-utils/JoyStatus';
+import { IterableFile } from './IterableFile';
 
 const MAX_FILE_SIZE_MB = 500;
 const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;
@@ -39,8 +40,8 @@ type Props = ApiProps & I18nProps & DiscoveryProviderProps & {
 
 type State = {
   error?: any,
-  file_name?: string,
-  file_data?: Uint8Array,
+  file?: File,
+  computingHash: boolean,
   ipfs_cid?: string,
   newContentId: ContentId,
   discovering: boolean,
@@ -51,8 +52,9 @@ type State = {
 
 const defaultState = (): State => ({
   error: undefined,
-  file_name: undefined,
-  file_data: undefined,
+  file: undefined,
+  computingHash: false,
+  ipfs_cid: undefined,
   newContentId: ContentId.generate(),
   discovering: false,
   uploading: false,
@@ -83,11 +85,12 @@ class Component extends React.PureComponent<Props, State> {
   }
 
   private renderContent () {
-    const { error, uploading, discovering } = this.state;
+    const { error, uploading, discovering, computingHash } = this.state;
 
     if (error) return this.renderError();
     else if (discovering) return this.renderDiscovering();
     else if (uploading) return this.renderUploading();
+    else if (computingHash) return this.renderComputingHash();
     else return this.renderFileInput();
   }
 
@@ -103,15 +106,16 @@ class Component extends React.PureComponent<Props, State> {
   }
 
   private resetForm = () => {
-    let newDefaultState = defaultState();
     const { cancelSource } = this.state;
-    newDefaultState.cancelSource = cancelSource;
-    this.setState(newDefaultState);
+    this.setState({
+      cancelSource,
+      ...defaultState()
+    });
   }
 
   private renderUploading () {
-    const { file_name, newContentId, progress, error } = this.state;
-    if (!file_name) return <JoyInfo title='Loading...' />
+    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 } } } = this.props
@@ -122,7 +126,7 @@ class Component extends React.PureComponent<Props, State> {
         <EditVideoView
           channelId={new ChannelId(channelId)}
           contentId={newContentId}
-          fileName={fileNameWoExt(file_name)}
+          fileName={fileNameWoExt(file.name)}
           history={history}
         />
       }
@@ -156,11 +160,12 @@ class Component extends React.PureComponent<Props, State> {
   }
 
   private renderFileInput () {
-    const { file_name, file_data } = this.state;
-    const file_size = file_data ? file_data.length : 0;
+    const { file } = this.state;
+    const file_size = file ? file.size : 0;
+    const file_name = file ? file.name : '';
 
     return <div className='UploadSelectForm'>
-      <InputFile
+      <InputFileAsync
         label=""
         withLabel={false}
         className={`UploadInputFile ${file_name ? 'FileSelected' : ''}`}
@@ -191,29 +196,60 @@ class Component extends React.PureComponent<Props, State> {
     </div>;
   }
 
-  private onFileSelected = async (data: Uint8Array, file_name: string) => {
-    if (!data || data.length === 0) {
+  private onFileSelected = async (file: File) => {
+    if (!file.size) {
       this.setState({ error: `You cannot upload an empty file.` });
-    } else if (data.length > MAX_FILE_SIZE_BYTES) {
+    } else if (file.size > MAX_FILE_SIZE_BYTES) {
       this.setState({ error:
-        `You cannot upload a file that is more than ${MAX_FILE_SIZE_MB} MB.`
+        `You can't upload files larger than ${MAX_FILE_SIZE_MB} MBytes in size.`
       });
     } else {
-      const ipfs_cid = await IpfsHash.of(Buffer.from(data));
-      console.log('computed IPFS hash:', ipfs_cid)
-      // File size is valid and can be uploaded:
-      this.setState({ file_name, file_data: data, ipfs_cid });
+      this.setState({ file, computingHash: true })
+      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 });
+      const ipfs_cid = await IpfsHash.of(iterableFile);
+
+      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 <em>Processing your file. Please wait...</em>
   }
 
   private buildTxParams = () => {
-    const { file_name, file_data, newContentId, ipfs_cid } = this.state;
-    if (!file_name || !file_data || !ipfs_cid) return [];
+    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);
 
-    return [ newContentId, dataObjectTypeId, new BN(file_data.length), ipfs_cid];
+    return [ newContentId, dataObjectTypeId, new BN(file.size), ipfs_cid];
   }
 
   private onDataObjectCreated = async (_txResult: SubmittableResult) => {
@@ -249,8 +285,8 @@ class Component extends React.PureComponent<Props, State> {
   }
 
   private uploadFileTo = async (storageProvider: AccountId) => {
-    const { file_data, newContentId, cancelSource } = this.state;
-    if (!file_data || !file_data.length) {
+    const { file, newContentId, cancelSource } = this.state;
+    if (!file || !file.size) {
       this.setState({
         error: new Error('No file to upload!'),
         discovering: false,
@@ -297,7 +333,7 @@ class Component extends React.PureComponent<Props, State> {
     this.setState({ discovering: false, uploading: true, progress: 0 });
 
     try {
-      await axios.put<{ message: string }>(url, file_data, config);
+      await axios.put<{ message: string }>(url, file, config);
     } catch(err) {
       this.setState({ progress: 0, error: err, uploading: false });
       if (axios.isCancel(err)) {

+ 3 - 3
packages/joy-pages/src/md/Privacy.md

@@ -1,7 +1,7 @@
 # Privacy and Cookies
 **Last updated on the 17th of April 2019**
 
-Jsgenesis values your privacy. 
+Jsgenesis values your privacy.
 
 This Privacy Policy ("Privacy Policy") and Cookie Policy ("Cookie Policy") explains how Jsgenesis AS ("Jsgenesis", "Company", "We", "Us", "Our") collect and use data and information when you ("User) use on or any of the Joystream products, developed in the GitHub organization [Joystream](https://github.com/JoyStream). These products (collectively "Software") include, but are not limited to, [all pages under the joystream.org domain](https://www.joystream.org/) ("Website"), the [Joyful node](https://github.com/Joystream/substrate-node-joystream) ("Full Node"), the [Colossus Storage Node](https://github.com/Joystream/storage-node-joystream) ("Storage node"), and the Pioneer User Interface, either [self hosted](https://github.com/Joystream/apps) or [hosted by Us](http://testnet.joystream.org/) ("App").
 
@@ -14,7 +14,7 @@ Relevant to the Privacy Policy and Cookie Policy are the following terms:
 
 
 # Privacy Policy
-**Last updated on the 17th of April 2019**
+**Last updated on the 17th of March 2020**
 
 ## 1. Agreement to the Policy
 By using any of Our Software, the User are accepting this Privacy Policy. If you are acting on behalf of another company or an employer, you must have the rights to act on their behalf. The Privacy Policy is not extended to any of our newsletters, where Users are bound by the [privacy policy](https://mailchimp.com/legal/privacy/) of [Mailchimp](https://mailchimp.com/).
@@ -28,7 +28,7 @@ This Privacy Policy may be changed at the sole discretion of Company. If any mat
 All data written to the Blockchain, is implicitly collected not only by Company, but also anyone else in the world that is running the Full Node locally, or accessed via the App or a third party.
 This includes, but is not limited to, Content hashes, Membership profile, Memo field, and any other way a User can record data on the Blockchain.
 
-When using the [faucet](https://testnet.joystream.org/faucet) ("Faucet") subpage of the Website, Company will record the IP address behind every new request for tokens. This data will be deleted every 14 days.
+When using the [faucet](https://faucet.joystream.org/) ("Faucet") subpage of the Website, Company will record the IP address behind every new request for tokens. This data will be deleted every 14 days.
 
 Company uses [Google Analytics](https://marketingplatform.google.com/about/analytics/), with IP anonymization, to collect statistics on Website and the version of App hosted by us. All customizable data sharing settings are turned off to improve the privacy of Users.
 

+ 8 - 1
packages/joy-settings/src/Settings.ts

@@ -38,7 +38,14 @@ export class Settings implements SettingsStruct {
 
     this._emitter = new EventEmitter();
 
-    this._apiUrl = settings.apiUrl || process.env.WS_URL || ENDPOINT_DEFAULT;
+    // Transition from acropolis - since visitors are coming to the same domain they most likely
+    // have the endpoint url saved in local storage. Replace it with new rome default endpoint.
+    if (settings.apiUrl == 'wss://testnet.joystream.org/acropolis/rpc/') {
+      this._apiUrl = process.env.WS_URL || ENDPOINT_DEFAULT || 'wss://rome-rpc-endpoint.joystream.org:9944/';
+    } else {
+      this._apiUrl = settings.apiUrl || process.env.WS_URL || ENDPOINT_DEFAULT;
+    }
+
     this._ledgerConn = settings.ledgerConn || LEDGER_CONN_DEFAULT;
     this._i18nLang = settings.i18nLang || LANGUAGE_DEFAULT;
     this._icon = settings.icon || ICON_DEFAULT;

+ 6 - 12
packages/joy-settings/src/defaults/endpoints.ts

@@ -5,7 +5,7 @@
 import { Option } from '../types';
 
 // type ChainName = 'alexander' | 'edgeware' | 'edgewareTest' | 'flamingFir' | 'kusama';
-type ChainName = 'joystream_rome_experimental' | 'joystream_rome_final_staging';
+type ChainName = 'rome';
 
 interface ChainData {
   chainDisplay: string;
@@ -22,23 +22,18 @@ interface PoviderData {
 }
 
 // we use this to give an ordering to the chains available
-const ORDER_CHAINS: ChainName[] = ['joystream_rome_experimental', 'joystream_rome_final_staging'];
+const ORDER_CHAINS: ChainName[] = ['rome'];
 
 // we use this to order the providers inside the chains
 const ORDER_PROVIDERS: ProviderName[] = ['joystream_org'];
 
 // some suplementary info on a per-chain basis
 const CHAIN_INFO: Record<ChainName, ChainData> = {
-  joystream_rome_experimental: {
+  rome: {
     chainDisplay: 'Joystream',
     logo: 'joystream',
-    type: 'Rome Reckless Testnet'
+    type: 'Rome Testnet'
   },
-  joystream_rome_final_staging: {
-    chainDisplay: 'Joystream',
-    logo: 'joystream',
-    type: 'Rome Final Staging Testnet'
-  }
 };
 
 // the actual providers with all  the nodes they provide
@@ -46,13 +41,12 @@ const PROVIDERS: Record<ProviderName, PoviderData> = {
   'joystream_org': {
     providerDisplay: 'Joystream.org',
     nodes: {
-      'joystream_rome_experimental': 'wss://rome-staging-2.joystream.org/staging/rpc/',
-      'joystream_rome_final_staging': 'wss://rome-staging-4.joystream.org/rpc/',
+      'rome' : 'wss://rome-rpc-endpoint.joystream.org:9944/'
     }
   }
 };
 
-export const ENDPOINT_DEFAULT = PROVIDERS.joystream_org.nodes.joystream_rome_final_staging;
+export const ENDPOINT_DEFAULT = PROVIDERS.joystream_org.nodes.rome;
 
 export const ENDPOINTS: Option[] = ORDER_CHAINS.reduce((endpoints: Option[], chainName): Option[] => {
   const { chainDisplay, logo, type } = CHAIN_INFO[chainName];

+ 117 - 0
packages/react-components/src/InputFileAsync.tsx

@@ -0,0 +1,117 @@
+// Copyright 2017-2019 @polkadot/react-components authors & contributors
+// This software may be modified and distributed under the terms
+// of the Apache-2.0 license. See the LICENSE file for details.
+
+import { WithTranslation } from 'react-i18next';
+import { BareProps } from './types';
+
+import React, { useState, createRef } from 'react';
+import Dropzone, { DropzoneRef } from 'react-dropzone';
+import styled from 'styled-components';
+import { formatNumber } from '@polkadot/util';
+
+import { classes } from './util';
+import Labelled from './Labelled';
+import translate from './translate';
+
+interface Props extends BareProps, WithTranslation {
+  // Reference Example Usage: https://github.com/react-dropzone/react-dropzone/tree/master/examples/Accept
+  // i.e. MIME types: 'application/json, text/plain', or '.json, .txt'
+  accept?: string;
+  clearContent?: boolean;
+  help?: React.ReactNode;
+  isDisabled?: boolean;
+  isError?: boolean;
+  label: React.ReactNode;
+  onChange?: (blob: File) => void;
+  placeholder?: React.ReactNode | null;
+  withEllipsis?: boolean;
+  withLabel?: boolean;
+}
+
+interface FileState {
+  name: string;
+  size: number;
+  type: string;
+}
+
+function InputFileAsync ({ accept, className, clearContent, help, isDisabled, isError = false, label, onChange, placeholder, t, withEllipsis, withLabel }: Props): React.ReactElement<Props> {
+  const dropRef = createRef<DropzoneRef>();
+  const [file, setFile] = useState<FileState | undefined>();
+
+  const _onDrop = (files: File[]): void => {
+    if (!files.length) return;
+    const blob = files[0];
+    onChange && onChange(blob);
+    dropRef && setFile({
+      ...blob
+    });
+  };
+
+  const dropZone = (
+    <Dropzone
+      accept={accept}
+      disabled={isDisabled}
+      multiple={false}
+      ref={dropRef}
+      onDrop={_onDrop}
+    >
+      {({ getRootProps, getInputProps }): JSX.Element => (
+        <div {...getRootProps({ className: classes('ui--InputFile', isError ? 'error' : '', className) })} >
+          <input {...getInputProps()} />
+          <em className='label' >
+            {
+              !file || clearContent
+                ? placeholder || t('click to select or drag and drop the file here')
+                : placeholder || t('{{name}} ({{size}} bytes)', {
+                  replace: {
+                    name: file.name,
+                    size: formatNumber(file.size)
+                  }
+                })
+            }
+          </em>
+        </div>
+      )}
+    </Dropzone>
+  );
+
+  return label
+    ? (
+      <Labelled
+        help={help}
+        label={label}
+        withEllipsis={withEllipsis}
+        withLabel={withLabel}
+      >
+        {dropZone}
+      </Labelled>
+    )
+    : dropZone;
+}
+
+export default translate(
+  styled(InputFileAsync)`
+    background: #fff;
+    border: 1px solid rgba(34, 36, 38, 0.15);
+    border-radius: 0.28571429rem;
+    font-size: 1rem;
+    margin: 0.25rem 0;
+    padding: 1rem;
+    width: 100% !important;
+
+    &.error {
+      background: #fff6f6;
+      border-color: #e0b4b4;
+    }
+
+    &:hover {
+      background: #fefefe;
+      cursor: pointer;
+    }
+
+    .label {
+      color: rgba(0, 0, 0, .6);
+    }
+  `
+);

+ 1 - 0
packages/react-components/src/index.tsx

@@ -46,6 +46,7 @@ export { default as InputConsts } from './InputConsts';
 export { default as InputError } from './InputError';
 export { default as InputExtrinsic } from './InputExtrinsic';
 export { default as InputFile } from './InputFile';
+export { default as InputFileAsync } from './InputFileAsync';
 export { default as InputNumber } from './InputNumber';
 export { default as InputRpc } from './InputRpc';
 export { default as InputStorage } from './InputStorage';

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