import React from 'react'; import superagent from 'superagent'; import _ from 'lodash'; import { fromUnixTime, formatDistanceToNow, format } from "date-fns"; import { formatInTimeZone } from 'date-fns-tz' import ReactPlayer from 'react-player' import {formatPrice} from '../Util/formatters'; import constants, { DEFAULT_TIMEZONE } from '../CloudApi/Constants.js'; import MoviesTVWrapper from './MoviesTVWrapper.jsx'; import MediaItemInfoPanel from './MediaItemInfoPanel.jsx'; import { Container, Segment, Grid, Header, Image, Icon, Button, Table, Form, Label, Message, Menu, Tab, Dropdown, Radio, Divider } from 'semantic-ui-react' export default class MediaProductEditor extends React.Component { constructor(props) { super(props); this.state = { product: { title: "", description: "", mediaItems: [], mediaItemIds: [], roomSettings: {}, entitlementClass: "", featuredMediaItemId: "", is3D: false, locationCodes: null, pricingProfileFilter: 0 }, locationSKUs: {}, allMediaItems: [], mediaItemsOptions: [], mediaProductTrailers: [], trailerOptions: [], messages: null, entitlementClasses: [], loadingShowings: false, showings: [], loadingMediaSessionEvents: false, mediaSessionEvents: [], }; } async componentDidMount() { this.setState({ loading: true }); const responses = await Promise.all([ this.reloadProduct(), superagent.get(`/api/admin/media/items`).accept('json'), superagent.get(`/api/admin/media/products?entitlementClass=FREE_FOR_ALL`).accept('json'), superagent.get("/api/admin/media/schemas") ]); const allMediaItems = responses[1].body; const mediaItemsOptions = allMediaItems.map(nv => { return { "key": nv.id, "value": nv.id, "text": `${nv.title} (${nv.cdn})` } }); this.setState({ allMediaItems, mediaItemsOptions }); const mediaProductTrailers = responses[2].body; const trailerOptions = mediaProductTrailers.map(mediaProduct => { return { "key": mediaProduct.bigMediaId, "value": mediaProduct.bigMediaId, "text": `${mediaProduct.title}`, "image": { src: mediaProduct.marketingData["en-US"].unityPosterTexture && mediaProduct.marketingData["en-US"].unityPosterTexture["src"] } } }); this.setState({ mediaProductTrailers, trailerOptions }); const schemas = responses[3].body; const entitlementClasses = Object.keys(schemas.EntitlementClass).map(k => { return { "key": k, "value": k, "text": k } }); const mediaTypes = Object.keys(schemas.MediaType).map(k => { return { "key": k, "value": k, "text": k } }); this.setState({ schemas, entitlementClasses, mediaTypes, loading: false }); } async reloadProduct() { if (this.props.match.params.productId) { this.setState({ loading: true, messages: null }); const res = await superagent.get(`/api/admin/media/product/${this.props.match.params.productId}`).accept('json'); console.log(res.body); this.setState({ product: res.body }); await Promise.all([ this.generateShowings(), this.generateMediaSessionEvents() ]); } } async onSaveProduct() { this.setState({ loading: true }); try { if (this.state.product.id) { const res = await superagent.put(`/api/admin/media/product`).send(this.state.product); console.log(res); //this.setState({ loading: false }); } else { const res = await superagent.post(`/api/admin/media/product`).send(this.state.product); this.props.history.push({pathname: `/cinema/product/${res.body.id}`}); await this.reloadProduct(); } this.setState({ messages: null, loading: false }) } catch (e) { console.log(e.response.body); this.setState({ messages: e.response.body, loading: false }); } } async onActivate(e) { e.preventDefault(); const product = this.state.product; const ACTIVATED = "ACTIVATED"; product.status = this.state.schemas.ProductStatus[ACTIVATED]; await this.onSaveProduct(); await this.reloadProduct(); this.setState({ loading: false }); } async generateShowings() { this.setState({ loadingShowings: true }); const res = await superagent.get(`/api/admin/media/showings/${this.state.product.synchronizationInterval}`).accept('json'); this.setState({ loadingShowings: false, showings: res.body }); } async generateMediaSessionEvents() { this.setState({ loadingMediaSessionEvents: true }); const res = await superagent.get(`/api/admin/media/events/${this.state.product.id}`).accept('json'); this.setState({ loadingMediaSessionEvents: false, mediaSessionEvents: res.body }); } async onChange(e, { name, value }) { const product = this.state.product; product[name] = value; this.setState({ product }); if (name === "synchronizationInterval") { await this.generateShowings(); } if (name === "featuredMediaItemId") { await this.generateMediaSessionEvents(); } } onChangeCurrentChannelRoomSettings(e, { name, value }) { if (this.state.product) { const product = this.state.product; if (!product.roomSettings) { product.roomSettings = {}; } product.roomSettings[name] = value; console.log(product); this.setState({ product }); } } onAddMediaItem(e) { e.preventDefault(); let product = this.state.product; product.mediaItems.push({ id: "" }); this.setState({ product }); } render() { if (!this.state.product) { return (
); } const existingProductInfo = (this.state.product.id) && (
Product Info

ID: {this.state.product.id}

BigMediaId: {this.state.product.bigMediaId}

Created At: {(this.state.product.createdAt && this.state.product.createdAt._seconds) ? formatInTimeZone(fromUnixTime(this.state.product.createdAt._seconds), DEFAULT_TIMEZONE, 'PPPp z') : "Error"}

Status: {this.state.product.status}

Duration: {this.state.product.duration ? (this.state.product.duration / 1000.0) : 0} seconds

); // Error messages. let messages = null; if (this.state.messages) { if (_.isArray(this.state.messages)) { messages = ( Errors: {this.state.messages.map(message => (

{message.msg}

))}
); } else { messages = ( {this.state.messages} ); } } const basicProductInfo = (
NOTE: This title and description is for internal use only!

See the marketing tab for customer facing information.

{(this.state.product.url) && }
); const mediaItems = (
Media Items Playlist
{(this.state.product.mediaItems.length > 0) && ( Item Edit )} {this.state.product.mediaItems.map((mediaItem, mediaItemIndex) => { let onMediaItemChanged = function (e, { name, value }) { e.preventDefault(); let product = this.state.product; product.mediaItemIds[mediaItemIndex] = value; product.mediaItems[mediaItemIndex] = _.find(this.state.allMediaItems, { id: value }); this.setState({ product }); } let onRemoveMediaItem = function (e) { e.preventDefault(); let product = this.state.product; product.mediaItemIds.splice(mediaItemIndex, 1); product.mediaItems.splice(mediaItemIndex, 1); this.setState({ product }); } return ( #{mediaItemIndex + 1} {mediaItem.cdn && } ) })}
) const roomSettings = (
Room Settings
); let featuredMediaItemOptions = [ { "key": "empty", "value": "", "text": "No default selected" } ]; featuredMediaItemOptions = featuredMediaItemOptions.concat(this.state.product.mediaItems.map((mediaItem, mediaItemIndex) => { return { "key": mediaItem.id, "value": mediaItem.id, "text": mediaItem.title } })); const movieNightsInfo = (
Movie Nights Info
Showings Interval - show the movie every X minutes

Use this value to generate "showings" for the movie. A showing is a social movie watching session, where the movie is synchronized for everyone in the session.

{constants.SynchronizationIntervals.map(synchronizationInterval => { return ( ); })}

Generated Showings (sample)

{this.state.showings.map(showing => { return ( ) })}
Events
Experimental!

Starting media item - the media item that starts on time.

Featured media item - the main media item that people paid for.

{featuredMediaItemOptions.map(mediaItemOption => { return ( ); })} {featuredMediaItemOptions.map(mediaItemOption => { return ( ); })}

Generated Events

{this.state.mediaSessionEvents.map(mediaSessionEvent => { return (

) })}
); let marketingInfo =
; if (this.state.product.marketingData) { const locale = "en-US"; const selectedMarketingData = this.state.product.marketingData[locale]; const onChangeMarketingData = (e, { name, value }) => { e.preventDefault(); if (this.state.product) { const product = this.state.product; product.marketingData[locale][name] = value; this.setState({ product }); } } const onChangeMarketingDataArray = (e, { name, value }) => { e.preventDefault(); if (this.state.product) { const arrayValue = _.split(value, ", "); const product = this.state.product; product.marketingData[locale][name] = arrayValue; this.setState({ product }); } } const onChangeImage = (e, { name, value }) => { e.preventDefault(); if (this.state.product) { const product = this.state.product; product.marketingData[locale][name].src = value; this.setState({ product }); } } const on3DChanged = function(e) { e.preventDefault(); if (this.state.product) { const product = this.state.product; product.is3D = !product.is3D; this.setState({ product }); } } marketingInfo = (
Marketing Info
General Marketing Info
Is the customer buying 3D content?

This should only be checked if the "main feature" is 3D. If the main feature is 2D, then set this to false.

Locale Specific Marketing Info: {locale}
Unity Texture Images

Unity poster dimensions (width x height): 170x253

Unity background image dimensions (width x height): 907x679 on a 1024x1024 image (example)

Unity screenshot image (width x height): 1920x800

{selectedMarketingData.unityPosterTexture && } {selectedMarketingData.unityPosterTexture && }
Website Product Images
Youtube Clip Info
Movie Info
); } // We have a different sku per location code. const pricing = (this.state.product.stripeProductData && this.state.product.stripeProductData.skus) && (
Pricing
Stripe Product Info

Product ID: {this.state.product.stripeProductData.id}

Active?: {this.state.product.stripeProductData.active ? "yes" : "no"}

{!this.state.product.stripeProductData.active && ( )}
Adding new Countries & Prices

To add new countries, first select a pricing profile.

Then add one of the allowed countries. It will pre-fill the default currency and default price for that country, based on the profile.

{constants.PricingProfileOptions.map(profile => { return ( ); })}
Countries
Country Active? Amount {this.state.product.locationCodes.map((locationCode, locationCodeIndex) => { const country = _.find(constants.CountryOptions, ["key", locationCode]); const skuIndex = _.findIndex(this.state.product.stripeProductData.skus.data, (sku) => { return (sku.attributes && sku.attributes.LocationCode && sku.attributes.LocationCode === locationCode); }); const sku = this.state.product.stripeProductData.skus.data[skuIndex]; const currencyInfo = sku ? constants.Currencies[_.toUpper(sku.currency)] : null; let pricingProfileCurrency, pricingProfilePrice = null; // if the currency info hasn't been filled in, let's retreive the defaults from the Pricing Profiles if(currencyInfo == null){ const pricingProfile = _.find(constants.PricingProfiles[this.state.product.pricingProfileFilter], ["key", locationCode]); if(pricingProfile){ pricingProfileCurrency = pricingProfile.currency != null ? pricingProfile.currency : null; pricingProfilePrice = pricingProfile.price != null ? pricingProfile.price : null; } } const onChangeCurrency = async (e, { name, value }) => { e.preventDefault(); const locationSKUs = this.state.locationSKUs; if (!locationSKUs[locationCode]) { locationSKUs[locationCode] = {}; } locationSKUs[locationCode].selectedCurrency = value; this.setState({ locationSKUs }); } const onChangeCurrencyAmount = async (e, { name, value }) => { e.preventDefault(); const locationSKUs = this.state.locationSKUs; if (!locationSKUs[locationCode]) { locationSKUs[locationCode] = {}; } locationSKUs[locationCode].selectedCurrencyAmount = value; this.setState({ locationSKUs }); } const onAddPricing = async (e, { name, value }) => { e.preventDefault(); const locationSKUs = this.state.locationSKUs; if (locationSKUs[locationCode]) { const payload = { locationCode, currency: locationSKUs[locationCode].selectedCurrency, price: locationSKUs[locationCode].selectedCurrencyAmount, } try { // HACK HACK HACK // // Save the product first, to update any location changes, then post the sku, then reload it! await this.onSaveProduct(); this.setState({ loading: true }); const res = await superagent.post(`/api/admin/media/product/${this.state.product.id}/sku`).send(payload).accept('json'); await this.reloadProduct(); } catch (e) { this.setState({ messages: e.response.body, loading: false }); } this.setState({ loading: false }); } } const onRemovePricing = async (e) => { e.preventDefault(); let product = this.state.product; product.stripeProductData.skus.data.splice(skuIndex, 1); this.setState({ loading: true, product }); try { const res = await superagent.delete(`/api/admin/media/product/${this.state.product.id}/sku/${sku.id}`); } catch (e) { this.setState({ messages: e.response.body, loading: false }); } this.setState({ loading: false }); } if (sku) { return ( {country && country.text} ); } else { const locationSKU = this.state.locationSKUs[locationCode]; return ( {country && country.text} Add Pricing ) } })}
); const panes = [ { menuItem: 'Details', render: () => {existingProductInfo}{basicProductInfo} }, { menuItem: 'Media Items', render: () => {mediaItems} }, { menuItem: 'Room Settings', render: () => {roomSettings} }, { menuItem: 'Movie Nights', render: () => {movieNightsInfo} }, { menuItem: 'Marketing', render: () => {marketingInfo} } ]; if (pricing) { panes.push({ menuItem: 'Pricing', render: () => {pricing} }); } const tabs = ( ); let activateButton = null; if (this.state.product.id) { if (this.state.product.status === "DRAFT") { if (this.state.product.stripeProductData) { if (!this.state.product.stripeProductData.active) { activateButton = ; } } else { activateButton = ; } } } let activationStatus =
if (this.state.product.id) { if (this.state.product.mediaItemIds.length === 0) { activationStatus = ( This product does not have any media items! ); } else if (this.state.product.status === "DRAFT") { activationStatus = ( This is a DRAFT. {activateButton} ); } else if (this.state.product.status === "ACTIVATED") { activationStatus = ( Activated! You should be able to see this in Bigscreen! ); } else if (this.state.product.status === "DISABLED") { activationStatus = ( This product is disabled ); } } let stripeActivationStatus =
if (this.state.product.id && this.state.product.stripeProductData && this.state.product.stripeProductData.skus) { if (this.state.product.stripeProductData.active) { stripeActivationStatus = ( Active on Stripe ); } else { stripeActivationStatus = ( Not active on Stripe! See "Pricing" tab to activated on Stripe. ); } } return ( {(!this.state.product.id) &&
Create Media Product
} {(this.state.product.id) &&
Edit Media Product
}
{this.state.product.title}
{messages && messages}
{(!this.state.product.id) && basicProductInfo} {activationStatus} {stripeActivationStatus} {(this.state.product.id) && tabs} {(!this.state.product.id) &&