Jelajahi Sumber

Curation frontend

Joystream Stats 3 tahun lalu
induk
melakukan
81f81c4128

+ 27 - 0
src/components/Curation/Buttons.tsx

@@ -0,0 +1,27 @@
+import React from 'react'
+import { Button } from 'react-bootstrap'
+
+const Buttons = (props) => {
+  const { show, addVideoVote, id } = props
+  if (!show) return <div />
+  return (
+    <div>
+      <Button
+        className="p-1 btn btn-small"
+        variant="danger"
+        onClick={() => addVideoVote(id, 'censor')}
+      >
+        CENSOR
+      </Button>
+      <Button
+        className="p-1 mx-1 btn btn-small"
+        variant="success"
+        onClick={() => addVideoVote(id, 'approve')}
+      >
+        Approve
+      </Button>
+    </div>
+  )
+}
+
+export default Buttons

+ 21 - 0
src/components/Curation/Curated.jsx

@@ -0,0 +1,21 @@
+import React from 'react'
+import { Badge } from 'react-bootstrap'
+
+const Curated = (props: { curations: any[], videoId: number }) => {
+  const { curations, videoId } = props
+  const existing = curations.filter((c) => c.videoId === videoId)
+  if (!existing.length) return <div />
+  const variants = { approve: 'success', censor: 'danger' }
+  const strings = { approve: `approved`, censer: `censored` }
+  return (
+    <>
+      {existing.map(({ id, vote, memberId }) => (
+        <Badge key={id} variant={variants[vote]} className="ml-2">
+          {strings[vote]} by {memberId}
+        </Badge>
+      ))}
+    </>
+  )
+}
+
+export default Curated

+ 16 - 0
src/components/Curation/ToggleCurated.jsx

@@ -0,0 +1,16 @@
+import React from 'react'
+import { Button } from 'react-bootstrap'
+
+const ToggleCurated = (props: { curations: number, toggle: () => void }) => {
+  if (!props.curations) return <div />
+  return (
+    <Button
+      variant="secondary"
+      className="btn-sm float-right"
+      onClick={() => props.toggle()}
+    >
+      Toggle curated
+    </Button>
+  )
+}
+export default ToggleCurated

+ 274 - 0
src/components/Curation/index.jsx

@@ -0,0 +1,274 @@
+import React from "react";
+import { Button } from "react-bootstrap";
+import { ChevronLeft, ChevronRight } from "react-feather";
+import Buttons from "./Buttons";
+import ToggleCurated from "./ToggleCurated";
+import Curated from "./Curated";
+import Loading from "../Loading";
+import moment from "moment";
+import axios from "axios";
+
+import { Player } from "video-react";
+import "video-react/dist/video-react.css";
+
+import { domain } from "../../config";
+import { fetchPending } from "../../lib/hydra";
+
+class Curation extends React.Component<> {
+  constructor(props) {
+    super(props);
+    this.state = {
+      loading: true,
+      page: 1,
+      pending: [],
+      hideWithoutThumb: true,
+      ipfsLocations: {},
+    };
+    this.setPage = this.setPage.bind(this);
+    this.findProvider = this.findProvider.bind(this);
+    this.toggleHideWithoutThumb = this.toggleHideWithoutThumb.bind(this);
+  }
+
+  componentDidMount() {
+    this.fetchPending();
+  }
+
+  async fetchPending(offset: number = 0) {
+    const { page } = this.state;
+    console.log(`Fetching assets (offset: ${offset})`);
+    const pending = await fetchPending(offset);
+    this.setState({ pending, loading: false });
+  }
+
+  async findProvider(hash) {
+    console.debug(`looking for provider for ${hash}`);
+    const { providers } = this.props;
+
+    providers.forEach(async ({ url }) => {
+      try {
+        const request = `${url}asset/v0/${hash}`;
+        console.debug(`testing ${request}`);
+        const { status, data } = await axios.head(request);
+        if (!status === 200) return;
+        console.log(`found: `, request, status, data);
+
+        let { ipfsLocations } = this.state;
+        ipfsLocations[hash] = request;
+        this.setState({ ipfsLocations });
+      } catch (e) {
+        //console.log(e);
+      }
+    });
+  }
+
+  setPage(page: number) {
+    this.setState({ page, loading: true, pending: [] });
+    this.fetchPending((page - 1) * 50);
+  }
+  toggleHideWithoutThumb() {
+    this.setState({ hideWithoutThumb: !this.state.hideWithoutThumb });
+  }
+
+  render() {
+    const {
+      toggleShowCurated,
+      handleChange,
+      addVideoVote,
+      selectVideo,
+      videos = [],
+      curations = [],
+      search,
+      providers,
+    } = this.props;
+    const {
+      loading,
+      pending,
+      page,
+      hideWithoutThumb,
+      ipfsLocations,
+    } = this.state;
+    if (!pending) return <Loading target="pending videos" />;
+
+    const showCurated = this.props.showCurated ? "" : "un";
+    const noThumb = pending.filter((v) => !v.thumbnailPhotoDataObject);
+
+    return (
+      <div className="h-100 bg-warning m-1 overflow-auto">
+        <div className="d-flex flex-row">
+          <Button
+            variant="secondary"
+            disabled={page <= 1}
+            onClick={() => this.setPage(page - 1)}
+          >
+            <ChevronLeft />
+          </Button>
+
+          <input
+            className="flex-fill p-1"
+            name="search"
+            value={search}
+            placeholder={
+              loading
+                ? `Loading page ${page} ..`
+                : `Search in ${pending.length} uploads`
+            }
+            disabled={!pending.length}
+            onChange={handleChange}
+          />
+          {curations.length ? (
+            <ToggleCurated
+              curations={curations.length}
+              toggle={toggleShowCurated}
+            />
+          ) : null}
+          <Button variant="secondary" onClick={() => this.setPage(page + 1)}>
+            <ChevronRight />
+          </Button>
+        </div>
+
+        <div className="p-3">
+          {noThumb.length ? (
+            <div
+              className="text-right mb-1"
+              title={noThumb.map((v) => v.id).join(" ")}
+              onClick={this.toggleHideWithoutThumb}
+            >
+              No thumbnail: {noThumb.length}
+            </div>
+          ) : (
+            ``
+          )}
+
+          {pending
+            .filter((v) => hideWithoutThumb || v.thumbnailPhotoDataObject)
+            .map((v: Video) => {
+              const {
+                id,
+                ipfsContentId,
+                mediaDataObject,
+                createdAt,
+                createdInBlock,
+                updatedAt,
+                size,
+              } = v;
+              const ipfs = v.mediaDataObject.joystreamContentId;
+              const {
+                categoryId,
+                channelId,
+                title,
+                description,
+                duration,
+                languageId,
+                licenseId,
+                hasMarketing,
+                isCensored,
+                isExplicit,
+                isFeatured,
+                mediaDataObjectId,
+                mediaMetadataId,
+                mediaUrls,
+                thumbnailPhotoDataObjectId,
+              } = hideWithoutThumb
+                ? v.mediaDataObject
+                  ? v.mediaDataObject
+                  : {}
+                : v.thumbnailPhotoDataObject;
+
+              return (
+                <div key={id}>
+                  <h4>
+                    <a href={`https://play.joystream.org/video/${id}`}>
+                      {title || `Video ${id}`}
+                    </a>
+                  </h4>
+
+                  <div className="d-flex flex-row mb-3">
+                    <div className="col-3">
+                      <div>
+                        <div>
+                          created: {moment(createdAt).fromNow()} (
+                          <a
+                            href={`${domain}/#/explorer/query/${createdInBlock}`}
+                          >
+                            {createdInBlock}
+                          </a>
+                          )
+                        </div>
+                        <div>updated: {moment(updatedAt).fromNow()}</div>
+                        <div>
+                          Duration:{" "}
+                          {duration > 60
+                            ? `${(duration / 60).toFixed()} m, ${
+                                duration % 60
+                              } s`
+                            : `${duration || 0} s`}
+                        </div>
+                        <div>Size: {(size / 1000000).toFixed(1)} mb</div>
+                        <div>
+                          Channel:{" "}
+                          <a
+                            href={`https://play.joystream.org/video/${channelId}`}
+                          >
+                            {channelId}
+                          </a>
+                        </div>
+                        <div>License: {licenseId}</div>
+                        <div>Language: {languageId}</div>
+                      </div>
+
+                      {ipfsLocations[ipfs] ? (
+                        ``
+                      ) : (
+                        <div>
+                          <div
+                            className="px-3 display-none"
+                            style={{ width: "130px", minWidth: "130px" }}
+                          >
+                            <img
+                              onClick={() => selectVideo(id)}
+                              src={thumbnailPhotoDataObjectId}
+                              alt={thumbnailPhotoDataObjectId}
+                              width="100"
+                            />
+                          </div>
+                          <Button
+                            onClick={() => this.findProvider(ipfs)}
+                            variant="light"
+                            className="btn btn-sm"
+                          >
+                            Load
+                          </Button>
+                        </div>
+                      )}
+                    </div>
+                    {ipfsLocations[ipfs] ? (
+                      <div className="col-5">
+                        <Player
+                          autoPlay
+                          playsInline
+                          src={ipfsLocations[ipfs]}
+                        />
+                      </div>
+                    ) : (
+                      ``
+                    )}
+
+                    <div className={ipfsLocations[ipfs] ? "col-4" : "col-9"}>
+                      <Buttons addVideoVote={addVideoVote} id={id} />
+                      <span onClick={() => selectVideo(id)}>
+                        <Curated curations={curations} videoId={id} />
+                      </span>
+                      <p>{description}</p>
+                      <div title={JSON.stringify(v)}>Details</div>
+                    </div>
+                  </div>
+                </div>
+              );
+            })}
+        </div>
+      </div>
+    );
+  }
+}
+
+export default Curation;