import * as React from "react"; import * as Strings from "~/common/strings"; import * as Actions from "~/common/actions"; import * as Constants from "~/common/constants"; import * as System from "~/components/system"; import * as SVG from "~/common/svg"; import * as Window from "~/common/window"; import * as Messages from "~/common/messages"; import * as FileUtilities from "~/common/file-utilities"; import * as Events from "~/common/custom-events"; import { css } from "@emotion/react"; import { createState } from "~/scenes/SceneSettings"; import { LoaderSpinner } from "~/components/system/components/Loaders"; import { FilecoinNumber } from "@glif/filecoin-number"; import { Link } from "~/components/core/Link"; import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper"; import Section from "~/components/core/Section"; import ScenePage from "~/components/core/ScenePage"; import ScenePageHeader from "~/components/core/ScenePageHeader"; const STAGING_DEAL_BUCKET = "stage-deal"; const STYLES_SPINNER_CONTAINER = css` width: 100%; height: 40vh; display: flex; align-items: center; justify-content: center; `; const STYLES_FILE_HIDDEN = css` height: 1px; width: 1px; opacity: 0; visibility: hidden; position: fixed; top: -1px; left: -1px; `; const STYLES_ROW = css` display: flex; align-items: flex-start; justify-content: space-between; `; const STYLES_LEFT = css` color: ${Constants.system.black}; transition: 200ms ease all; min-width: 10%; width: 100%; :visited { color: ${Constants.system.black}; } `; const STYLES_RIGHT = css` flex-shrink: 0; transition: 200ms ease all; cursor: pointer; :hover { color: ${Constants.system.brand}; } `; const DEFAULT_ERROR_MESSAGE = "We could not make your deal. Please try again later."; let mounted = false; export default class SceneMakeFilecoinDeal extends React.Component { state = { encryption: false }; async componentDidMount() { if (mounted) { return; } mounted = true; let networkViewer; try { const response = await fetch("/api/network"); const json = await response.json(); networkViewer = json.data; } catch (e) {} if (networkViewer) { this.setState({ networkViewer, ...createState(networkViewer.settings), encryption: false, }); } } _handleUpload = async (e) => { e.persist(); if (!e.target.files) { return null; } if (!e.target.files.length) { return null; } this.setState({ loading: true }); for (let i = 0; i < e.target.files.length; i++) { const file = e.target.files[i]; const response = await FileUtilities.upload({ bucketName: STAGING_DEAL_BUCKET, routes: this.props.resources, file, }); } let networkViewer; try { const response = await fetch("/api/network"); const json = await response.json(); networkViewer = json.data; } catch (e) {} this.setState({ networkViewer, loading: false, }); }; _handleArchive = async (e) => { this.setState({ archiving: true }); const response = await Actions.archive({ bucketName: STAGING_DEAL_BUCKET, forceEncryption: this.state.encryption, settings: { /** * RepFactor indicates the desired amount of active deals * with different miners to store the data. While making deals * the other attributes of FilConfig are considered for miner selection. */ repFactor: Number(this.state.repFactor), /** * DealMinDuration indicates the duration to be used when making new deals. */ dealMinDuration: this.state.dealMinDuration, /** * ExcludedMiners is a set of miner addresses won't be ever be selected *when making new deals, even if they comply to other filters. */ excludedMiners: this.state.excludedMiners, /** * TrustedMiners is a set of miner addresses which will be forcibly used * when making new deals. An empty/nil list disables this feature. */ trustedMiners: this.state.trustedMiners, /** * Renew indicates deal-renewal configuration. */ renew: { enabled: this.state.renewEnabled, threshold: this.state.renewThreshold, }, /** * CountryCodes indicates that new deals should select miners on specific countries. */ countryCodes: [], /** * Addr is the wallet address used to store the data in filecoin */ addr: this.state.addr, /** * MaxPrice is the maximum price that will be spent to store the data, 0 is no max */ maxPrice: this.state.maxPrice, /** * * FastRetrieval indicates that created deals should enable the * fast retrieval feature. */ // fastRetrieval: boolean /** * DealStartOffset indicates how many epochs in the future impose a * deadline to new deals being active on-chain. This value might influence * if miners accept deals, since they should seal fast enough to satisfy * this constraint. */ // dealStartOffset: number }, }); if (Events.hasError(response)) { this.setState({ archiving: false }); } await Window.delay(5000); alert( "Your storage deal was put in the queue. This can take up to 36 hours, check back later." ); this.props.onAction({ type: "NAVIGATE", href: "/_/filecoin" }); }; _handleRemove = async (cid) => { this.setState({ loading: true }); await Actions.removeFromBucket({ bucketName: STAGING_DEAL_BUCKET, cid }); let networkViewer; try { const response = await fetch("/api/network"); const json = await response.json(); networkViewer = json.data; } catch (e) {} this.setState({ networkViewer, loading: false, }); }; _handleAddTrustedMiner = () => { const miner = prompt("Enter the Miner ID to trust."); if (Strings.isEmpty(miner)) { return Events.dispatchMessage({ message: "You must provide a miner ID." }); } if (this.state.trustedMiners.includes(miner)) { return Events.dispatchMessage({ message: `${miner} is already on your list of miners to try.`, }); } this.setState({ trustedMiners: [miner, ...this.state.trustedMiners], }); }; _handleAddExcludedMiner = () => { const miner = prompt("Enter the Miner ID to exclude."); if (Strings.isEmpty(miner)) { return Events.dispatchMessage({ message: "You must provide a miner ID." }); } if (this.state.excludedMiners.includes(miner)) { return Events.dispatchMessage({ message: `${miner} is already on your list of miners to exclude.`, }); } this.setState({ excludedMiners: [miner, ...this.state.excludedMiners], }); }; _handleRemoveTrustedMiner = (minerId) => { this.setState({ trustedMiners: this.state.trustedMiners.filter((m) => m !== minerId), }); }; _handleRemoveExcludedMiner = (minerId) => { this.setState({ excludedMiners: this.state.excludedMiners.filter((m) => m !== minerId), }); }; _handleChange = (e) => { this.setState({ [e.target.name]: e.target.value }); }; componentWillUnmount() { mounted = false; } render() { const { networkViewer } = this.state; const addressMap = {}; const addresses = []; let selected = null; let balance = 0; if (networkViewer) { // TODO(jim): restore this when the wallet function is back in Pow. } let inFil = 0; if (networkViewer) { const filecoinNumber = new FilecoinNumber(`${this.state.maxPrice}`, "attofil"); inFil = filecoinNumber.toFil(); } return ( Upload data and make one-off storage deals in the Filecoin network here. Files must be at least 100MB in size. {this.state.networkViewer ? (
{this.state.loading ? (
) : ( { return { cid: (
{file.cid} this._handleRemove(file.cid)}>
), }; }), }} /> )}
{ return { miner: (
{miner} this._handleRemoveTrustedMiner(miner)} >
), }; }), }} />
{ return { miner: (
Excluding: {miner} this._handleRemoveExcludedMiner(miner)} >
), }; }), }} />
Encrypt this storage deal. Accessing the contents will require decryption. Make storage deal
) : (
)}
); } }