· 6 years ago · Apr 03, 2019, 09:36 AM
1"use strict";
2
3import React, { Component } from "react";
4import { intlShape, injectIntl, FormattedHTMLMessage } from "react-intl";
5import { Rnd } from "react-rnd";
6import DefaultLayout from "../../layouts/DefaultLayout";
7import BranchService from "../../services/BranchService";
8import classNames from "classnames";
9import ReactTooltip from "react-tooltip";
10import Dropzone from "react-dropzone";
11import Loader from "halogen/ClipLoader";
12import Constants from "../../utils/Constants";
13import ReactDOM from "react-dom";
14import { SketchPicker } from "react-color";
15
16import { Modal, OverlayTrigger, Tooltip, Popover } from "react-bootstrap";
17import UploadService from "../../services/UploadService";
18import FroalaEditor from "react-froala-wysiwyg";
19import InputRange from "react-input-range";
20import ReactSortable from "react-sortablejs";
21
22import {
23 findObjectItemByPath,
24 rgba2hex,
25 darkenColor,
26 lightenColor,
27 isBrightColor,
28 removeDublicatesInArray,
29 getFieldValueLength,
30 insertAtCaret
31} from "../../utils/Utils";
32
33import "./playlistview.scss";
34import "react-input-range/lib/css/index.css";
35
36const colorBox = color => {
37 const borderColor = "#999";
38 return (
39 <div className="clr-pckr-btn-wrapper">
40 <div style={{ borderRadius: 2, margin: "0" }}>
41 <div
42 title={color}
43 style={{
44 backgroundColor: color,
45 height: 22,
46 width: 22,
47 cursor: "pointer",
48 border: "1px solid " + borderColor,
49 borderRadius: 2,
50 margin: "0"
51 }}
52 />
53 </div>
54 </div>
55 );
56};
57
58const imageFileTypes = ["jpg", "png", "jpeg", "JPG", "PNG", "JPEG"];
59const videoFileTypes = ["mp4", "MP4", "MOV", "mov"];
60
61class PlaylistView extends Component {
62 items = [];
63
64 constructor(props) {
65 super(props);
66 this.alertOptionsOnEntityDelete = $.extend(
67 true,
68 {},
69 Constants.metaData.alertOptionsOnEntityDelete
70 );
71 this.alertOptionsOnEntityDelete.title = this.props.intl.formatMessage({
72 id: this.alertOptionsOnEntityDelete.title
73 });
74 this.alertOptionsOnEntityDelete.text = this.props.intl.formatMessage({
75 id: this.alertOptionsOnEntityDelete.text
76 });
77 this.alertOptionsOnEntityDelete.confirmButtonText = this.props.intl.formatMessage(
78 { id: this.alertOptionsOnEntityDelete.confirmButtonText }
79 );
80 this.alertOptionsOnEntityDelete.cancelButtonText = this.props.intl.formatMessage(
81 { id: this.alertOptionsOnEntityDelete.cancelButtonText }
82 );
83 }
84
85 state = {
86 playlist: {
87 name: "Playlist name",
88 description: "something to explain this playlist",
89 slides: []
90 },
91 FETCHING_PLAYLIST: false,
92 slides: [],
93 displayTypes: [],
94 files: [],
95 selectedFile: { path: "" },
96 selectedFiles: [],
97 selectedDisplayType: null,
98 showFilesModal: false,
99 UPLOADING_FILE: false,
100 LOADING_FILES: false,
101 selectedSlideIndex: 0,
102 showSlideModal: false,
103 selectedScale: 100,
104 contentHeight: 200,
105 resizeTimer: 0,
106 selectedItem: null,
107 contentWidth: 0
108 };
109
110 componentWillMount() {
111 this.fetchDisplayTypes();
112 }
113
114 componentDidMount() {
115 let t = this;
116 window.addEventListener("resize", this.handleResize);
117 this.handleResize(null);
118 Utility.collapse_leftbar();
119 }
120
121 componentWillUnmount() {
122 window.removeEventListener("resize", this.handleResize);
123 Utility.expand_leftbar();
124 }
125 handleResize = e => {
126 clearTimeout(this.state.resizeTimer);
127 this.state.resizeTimer = setTimeout(() => {
128 this.setState({
129 contentHeight: $(window).height() - 150,
130 contentWidth: $(window).width() - 200
131 });
132 }, 250);
133 };
134
135 fetchPlaylist = displayTypeID => {
136 this.setState({ FETCHING_PLAYLIST: true });
137 BranchService.getPlaylist(
138 { id: this.props.params.id, displayTypeID: displayTypeID },
139 success => {
140 success.slides = success.slides.map(s => {
141 s.videoDurations = [];
142 return s;
143 });
144
145 if (success.slides.length == 0) {
146 success.slides.push({
147 name: "Slide 1",
148 description: "",
149 items: [],
150 stringID: guidGenerator(),
151 videoDurations: [],
152 orderID: 1,
153 duration: 10,
154 bgColor: "#ffffff"
155 });
156 }
157
158 let sortedSlides = success.slides.sort(function(a, b) {
159 return a.orderID - b.orderID;
160 });
161
162 this.setState(
163 {
164 playlist: success,
165 FETCHING_PLAYLIST: false,
166 slides: sortedSlides
167 },
168 () => {
169 var selectedSlide;
170
171 if (
172 this.state.selectedSlideIndex >= this.state.slides.length ||
173 this.state.selectedSlideIndex == null
174 ) {
175 selectedSlide = this.state.slides[0];
176 this.setState({ selectedSlideIndex: 0, selectedItem: null });
177 } else {
178 selectedSlide = this.state.slides[this.state.selectedSlideIndex];
179 }
180 selectedSlide.videoDurations = [];
181 this.setState({ selectedSlide: null }, () => {
182 this.setState({ selectedSlide });
183 });
184 }
185 );
186 },
187 error => {
188 this.setState({ FETCHING_PLAYLIST: false });
189 toastr.error("Could not get playlist.");
190 }
191 );
192 };
193
194 fetchDisplayTypes = () => {
195 this.setState({ FETCHING_DISPLAY_TYPES: true });
196 BranchService.getDisplayTypes(
197 success => {
198 if (success.length > 6) {
199 this.fetchPlaylist(success[6].id);
200 this.setState({
201 selectedDisplayType: success[6],
202 FETCHING_DISPLAY_TYPES: false,
203 displayTypes: success
204 });
205 } else if (success.length > 0) {
206 this.fetchPlaylist(success[0].id);
207 this.setState({
208 selectedDisplayType: success[0],
209 FETCHING_DISPLAY_TYPES: false,
210 displayTypes: success
211 });
212 }
213 },
214 error => {
215 toastr.error("Something went wrong");
216 this.setState({ FETCHING_DISPLAY_TYPES: false });
217 }
218 );
219 };
220
221 fetchFiles = () => {
222 this.setState({ LOADING_FILES: true });
223 BranchService.getFiles(
224 success => {
225 this.setState({ LOADING_FILES: false, files: success });
226 },
227 error => {
228 toastr.error("Something went wrong");
229 this.setState({ LOADING_FILES: false });
230 }
231 );
232 };
233
234 handleVideoMetadata = (event, file) => {
235 let selectedSlide = this.state.slides.filter(
236 s => s.stringID != "globalSlide"
237 )[this.state.selectedSlideIndex];
238 if (
239 this.state.slides.filter(s => s.stringID != "globalSlide").length == 1
240 ) {
241 selectedSlide = this.state.slides.filter(
242 s => s.stringID != "globalSlide"
243 )[0];
244 }
245 let duration = event.currentTarget.duration;
246 console.log("this file metadata loaded", file);
247 console.log("selected slide duration: ", selectedSlide.duration);
248 console.log("file duration: ", duration);
249 if (file.id == 0 && selectedSlide.duration < duration) {
250 selectedSlide.duration = parseInt(duration);
251 }
252 selectedSlide.videoDurations.push({ file: file, duration: duration });
253 this.setState({ selectedSlide: selectedSlide });
254 console.log(selectedSlide.videoDurations);
255 };
256
257 handleGalleryVideoMetadata = (event, file) => {
258 let t = this;
259 let duration = event.currentTarget.duration;
260 let files = [...t.state.files];
261 files.forEach(f => {
262 if(f.id == file.id){
263 f.duration = parseInt(duration);
264 }
265 });
266
267 let selectedFiles = [...t.state.selectedFiles]
268 selectedFiles.forEach(f => {
269 if(f.id == file.id){
270 f.duration = parseInt(duration);
271 }
272 });
273 this.setState({ files, selectedFiles });
274 };
275
276 _handleDeletePlaylistButton = () => {
277 let t = this;
278
279 swal(t.alertOptionsOnEntityDelete).then(() => {
280 return BranchService.deletePlaylist({ id: t.props.params.id })
281 .promise.then(response => {
282 this.props.history.replace("/playlists");
283 toastr.success(
284 t.props.intl.formatMessage({ id: "Display successfully deleted!" })
285 );
286 })
287 .catch(errorResponse => {
288 toastr.error("Something bad happened.");
289 });
290 });
291 };
292
293 handleAddSlide = global => {
294 let slides = this.state.slides;
295 let stringID = guidGenerator();
296 let bgColor = "#ffffff";
297
298 if (global) {
299 stringID = "globalSlide";
300 bgColor = "transparent";
301 }
302
303 slides.push({
304 name: "Slide " + (slides.length + 1),
305 description: "",
306 items: [],
307 stringID: stringID,
308 orderID: slides.length,
309 duration: 10,
310 bgColor: bgColor,
311 videoDurations: []
312 });
313
314 if (global) {
315 this.setState({ slides });
316 } else {
317 let selectedSlideIndex = 0;
318 let selectedSlide = slides[0];
319 this.setState({ slides, selectedSlide, selectedSlideIndex });
320 }
321 };
322
323 slidesListOnSort = sortedSlides => {
324 let newSlides = [];
325
326 sortedSlides.forEach((s, i) => {
327 let sortingSlide = this.state.slides.find(_ => _.stringID == s);
328 if (sortingSlide != null) {
329 sortingSlide.orderID = i;
330 sortingSlide.videoDurations = [];
331 newSlides.push(sortingSlide);
332 }
333 });
334
335 this.setState({ slides: newSlides }, () => {
336 this.setState({
337 selectedSlide: this.state.slides[this.state.selectedSlideIndex]
338 });
339 });
340 };
341
342 selectedFilesListOnSort = sortedFiles => {
343 let newFiles = [];
344
345 sortedFiles.forEach((s, i) => {
346 let sortingFile = this.state.selectedFiles.find(_ => _.id == s);
347 if (sortingFile != null) {
348 newFiles.push(sortingFile);
349 }
350 });
351
352 this.setState({ selectedFiles: newFiles });
353 };
354
355 deleteFile = item => {
356 BranchService.deleteFile(
357 { id: item.id },
358 success => {
359 toastr.success("File successfully deleted.");
360 this.setState({ selectedFile: null });
361 this.fetchFiles();
362 },
363 error => {
364 toastr.error("Could not delete file.");
365 }
366 );
367 };
368
369 removeSlide = index => {
370 let t = this;
371 swal(t.alertOptionsOnEntityDelete).then(() => {
372 let slides = this.state.slides;
373 slides.splice(index, 1);
374 slides.map((slide, index) => {
375 slide.orderID = index;
376 });
377 var selectedSlide;
378 var selectedSlideIndex;
379 if (this.selectedSlideIndex < slides.length) {
380 selectedSlide = slides[this.selectedSlideIndex];
381 selectedSlideIndex = this.selectedSlideIndex;
382 } else if (slides.length == 0) {
383 selectedSlide = null;
384 selectedSlideIndex = null;
385 } else {
386 selectedSlide = slides[0];
387 selectedSlideIndex = 0;
388 }
389 if (selectedSlide) {
390 selectedSlide.videoDurations = [];
391 }
392 this.setState({
393 slides: slides,
394 selectedSlide: selectedSlide,
395 selectedSlideIndex: selectedSlideIndex,
396 selectedItem: null
397 });
398 });
399 };
400
401 removeItem = (index, isGlobal) => {
402 let t = this;
403 swal(t.alertOptionsOnEntityDelete).then(() => {
404 let selectedSlide = this.state.selectedSlide;
405 if (isGlobal) {
406 selectedSlide = this.state.slides.filter(
407 s => s.stringID == "globalSlide"
408 )[0];
409 selectedSlide.items.splice(index, 1);
410 let slides = [...t.state.slides];
411 slides.forEach(s => {
412 if (s.stringID == "globalSlide") {
413 s = selectedSlide;
414 }
415 });
416
417 this.setState({ selectedItem: null, slides: slides });
418 } else {
419 selectedSlide.items.splice(index, 1);
420 this.setState({ selectedItem: null, selectedSlide: selectedSlide });
421 }
422 });
423 };
424
425 setSelectedDisplayType = dipslayType => {
426 let playlist = { ...this.state.playlist };
427 playlist.defaultDisplayTypeID = { ...dipslayType }.id;
428 this._handleSavePlaylistButton(false, false);
429 this.setState({
430 playlist,
431 selectedDisplayType: dipslayType,
432 selectedSlide: null,
433 selectedSlideIndex: null
434 });
435 this.fetchPlaylist(dipslayType.id);
436 };
437
438 setSelectedItem = item => {
439 let t = this;
440 t.setState({ selectedItem: item });
441 };
442
443 setSelectedItemIndex = (index, global) => {
444 let t = this;
445 if (global) {
446 let selectedItem = t.state.slides.filter(
447 s => s.stringID == "globalSlide"
448 )[0].items[index];
449 t.setState({ selectedItem });
450 } else {
451 t.setState({ selectedItem: t.state.selectedSlide.items[index] });
452 }
453 };
454
455 updateSelectedItemSize = (item, ref, position) => {
456 item.width = parseInt(ref.offsetWidth);
457 item.height = parseInt(ref.offsetHeight);
458 item.left = parseInt(position.x);
459 item.top = parseInt(position.y);
460 };
461
462 updateSelectedItemPosition = (item, position) => {
463 console.log("updatedLeft: " + position.x + " <-> " + parseInt(position.x));
464 console.log("updatedTop: " + position.y + " <-> " + parseInt(position.y));
465 item.left = parseInt(position.x);
466 item.top = parseInt(position.y);
467 };
468
469 openFilesModal = activeTab => {
470 this.setState({
471 files: [],
472 selectedFiles: [],
473 showFilesModal: true,
474 filesModalActiveTab: activeTab
475 }, () => {
476 this.fetchFiles();
477 });
478 };
479
480 hideFilesModal = () => {
481 this.setState({ showFilesModal: false });
482 };
483
484 addTextItem = () => {
485 let slide = { ...this.state.selectedSlide };
486 let maxIndex =
487 Math.max.apply(
488 Math,
489 slide.items.map(i => {
490 return i.index;
491 })
492 ) + 1;
493
494 if (slide.items.length == 0) {
495 maxIndex = 1;
496 }
497
498 let textItem = {
499 id: 0,
500 index: maxIndex,
501 name: "",
502 contentType: "text",
503 width: this.state.selectedDisplayType.width,
504 height: 120,
505 stringID: guidGenerator(),
506 left: 0,
507 top: 0,
508 src: "null",
509 itemText: '<div style="font-size: 100px">Your text goes here</div>',
510 fontSize: 16,
511 scrollingText: false,
512 bgColor: "#ffffff",
513 color: "#444a59",
514 property: "{}"
515 };
516
517 slide.items.push(textItem);
518 this.setState({
519 selectedSlide: slide,
520 selectedItem: slide.items[slide.items.length - 1]
521 });
522 };
523
524 addGlobalTextItem = () => {
525 if (
526 this.state.slides.filter(s => s.stringID == "globalSlide").length == 0
527 ) {
528 this.handleAddSlide(true);
529 }
530
531 let slide = this.state.slides.filter(a => a.stringID == "globalSlide")[0];
532
533 let maxIndex =
534 Math.max.apply(
535 Math,
536 slide.items.map(i => {
537 return i.index;
538 })
539 ) + 1;
540
541 if (slide.items.length == 0) {
542 maxIndex = 1;
543 }
544
545 let textItem = {
546 id: 0,
547 index: maxIndex,
548 name: "",
549 contentType: "text",
550 width: this.state.selectedDisplayType.width,
551 height: 120,
552 stringID: guidGenerator(),
553 left: 0,
554 top: 0,
555 src: "null",
556 itemText: '<div style="font-size: 100px">Your text goes here</div>',
557 fontSize: 16,
558 scrollingText: false,
559 bgColor: "#ffffff",
560 color: "#444a59",
561 property: "{}"
562 };
563
564 slide.items.push(textItem);
565 this.setState({
566 selectedItem: slide.items[slide.items.length - 1]
567 });
568 };
569
570 openEditSlideModal = index => {
571 this.setState({ showSlideModal: true });
572 };
573
574 _handleSlideFormInputChange = e => {
575 let model = this.state.slides[this.state.selectedSlideIndex];
576 model[e.target.name] = e.target.value;
577 this.setState({
578 selectedSlide: model
579 });
580 };
581
582 _handleItemFormChange = e => {
583 let model = this.state.selectedItem;
584 model[e.target.name] =
585 e.target.type == "checkbox" ? e.target.checked : e.target.value;
586 if (e.target.type == "number") {
587 model[e.target.name] = parseInt(e.target.value);
588 }
589
590 this.items[this.state.selectedItem.stringID].updateSize({
591 width: model.width,
592 height: model.height
593 });
594 this.items[this.state.selectedItem.stringID].updatePosition({
595 x: model.left,
596 y: model.top
597 });
598 this.setState({
599 selectedItem: model
600 });
601 };
602
603 handleItemTextChange = text => {
604 let item = this.state.selectedItem;
605 item.itemText = text;
606 this.setState({ selectedItem: item });
607 };
608
609 _handleSlideFormSubmit = e => {
610 e.preventDefault();
611 this.hideSlideModal();
612 };
613
614 hideSlideModal = () => {
615 this.setState({ showSlideModal: false });
616 };
617
618 openPreviewButtonClicked = () => {
619 this._handleSavePlaylistButton(true);
620 };
621
622 openPreview = () => {
623 let url =
624 "/playlists/preview/" +
625 this.props.params.id +
626 "/" +
627 this.state.selectedDisplayType.id;
628 var win = window.open(url, "_blank");
629 win.focus();
630 };
631
632 setSelectedScale = scale => {
633 this.setState({ selectedScale: scale });
634 };
635
636 getScaleToSizes = () => {
637 let scale = this.state.selectedScale;
638 return 100 / scale;
639 };
640
641 _handleSavePlaylistButton = (openPreview, showSuccess) => {
642 let t = this;
643 let playlist = { ...t.state.playlist };
644 playlist.slides = [...t.state.slides];
645
646 playlist.slides.map((slide, index) => {
647 slide.orderID = index;
648 });
649
650 playlist["displayTypeID"] = t.state.selectedDisplayType.id;
651
652 BranchService.updateDraftPlaylist(
653 playlist,
654 success => {
655 if (showSuccess) {
656 toastr.success("Playlist successfully updated.");
657 }
658 t.fetchPlaylist(t.state.selectedDisplayType.id);
659 t.setState({ selectedItem: null });
660 if (openPreview) {
661 this.openPreview();
662 }
663 },
664 error => {
665 toastr.error("could not update playlist.");
666 }
667 );
668 };
669
670 _handleUpload = (file, cb) => {
671 let t = this;
672 if (file) {
673 var data = new FormData();
674 data.append("file", file);
675
676 t.setState({
677 UPLOADING_FILE: true
678 });
679
680 UploadService.uploadPlaylistFile(
681 data,
682 response => {
683 let files = this.state.files;
684 files.unshift(response);
685 this.setState({
686 files: files,
687 selectedFile: response
688 });
689 cb(response);
690 },
691 erroResponse => {
692 console.log("error", erroResponse);
693 cb();
694 }
695 );
696 }
697 };
698
699 _handleFileDrop = (acceptedFiles, rejectedFiles) => {
700 let t = this;
701 console.log("accepted files", acceptedFiles, rejectedFiles);
702
703 t.setState({ UPLOADING_FILE: true });
704 if (acceptedFiles.length > 0) {
705 t._handleUpload(acceptedFiles[0], uploadedFile => {
706 acceptedFiles.splice(0, 1);
707 t._handleFileDrop(acceptedFiles);
708 });
709 } else {
710 t.setState({ UPLOADING_FILE: false });
711 }
712 };
713
714 _handleColorPickerButtonClick = (
715 e,
716 name,
717 color,
718 onColorPickerChange,
719 model
720 ) => {
721 e.preventDefault();
722 let t = this;
723
724 var _t = $(e.currentTarget);
725 var cpWrapper = $("<div class='color-picker-wrapper'/>");
726
727 var rect = e.currentTarget.getBoundingClientRect
728 ? e.currentTarget.getBoundingClientRect()
729 : { top: 0 };
730 var cpHeight = 305;
731
732 var position = $(_t).offset();
733 position.left += $(_t).width() - 250;
734
735 if (window.windowHeight < rect.top + cpHeight / 1.5) {
736 position.top -= cpHeight - $(_t).height();
737 }
738
739 var cover = $("<div class='color-picker-cover' />");
740 cover.css({
741 position: "fixed",
742 top: "0px",
743 right: "0px",
744 bottom: "0px",
745 left: "0px",
746 zIndex: 200
747 });
748 cover.on("click", function() {
749 ReactDOM.unmountComponentAtNode(cpWrapper[0]);
750 $(".color-picker-cover").remove();
751 cpWrapper.remove();
752 });
753
754 $("body")
755 .append(cover)
756 .append(cpWrapper);
757 cpWrapper.css({ ...position, display: "block" });
758
759 const onChangefn =
760 onColorPickerChange ||
761 function(c) {
762 t._handleColorPickerChange(name, c, null, model);
763 };
764
765 let props = {
766 color: color,
767 onChange: onChangefn,
768 onChangeComplete: color => {},
769 presetColors: []
770 };
771
772 ReactDOM.render(<SketchPicker {...props} />, cpWrapper[0]);
773 };
774
775 _handleColorPickerChange = (name, color, event, modelToChange) => {
776 console.log(name);
777 console.log(color);
778
779 var model;
780
781 if (modelToChange == "selectedItem") {
782 console.log("selectedItem color changing");
783 model = this.state.selectedItem;
784 } else if (modelToChange == "selectedSlide") {
785 console.log("selectedSlide color changing");
786 model = this.state.selectedSlide;
787 } else {
788 model = this.state.selectedSlide;
789 }
790
791 model[name] = color.hex;
792
793 this.setState({
794 modelToChange: model
795 });
796 };
797
798 renderGalleryItem = (item, controls) => {
799 if (imageFileTypes.indexOf(item.fileType) > -1) {
800 return (
801 <img
802 key={item.id}
803 id={item.id}
804 src={item.path}
805 alt=""
806 className="img-responsive"
807 style={{ maxHeight: "100%", margin: "auto" }}
808
809 />
810 );
811 } else if (videoFileTypes.indexOf(item.fileType) > -1) {
812 return (
813 <video style={{ width: "100%", height: "100%" }} controls={controls} onLoadedMetadata={e => this.handleGalleryVideoMetadata(e, item)}>
814 <source src={item.path} type={"video/mp4"} />
815 Your browser does not support HTML5 video.
816 </video>
817 );
818 } else {
819 return <span>{item.path}</span>;
820 }
821 };
822
823 selectFile = file => {
824 this.setState(
825 {
826 selectedFile: null
827 },
828 () => {
829 this.setState({ selectedFile: file });
830 }
831 );
832 };
833
834 toggleFile = file => {
835 let t = this;
836 let selectedFiles = [...t.state.selectedFiles];
837
838 let exists = selectedFiles.filter(f => f.id == file.id).length > 0;
839 if (exists) {
840 let indexToRemove = 0;
841 selectedFiles.forEach((f, index) => {
842 if (f.id == file.id) {
843 indexToRemove = index;
844 }
845 });
846 selectedFiles.splice(indexToRemove, 1);
847 } else {
848 selectedFiles.push(file);
849 }
850 t.setState({ selectedFiles });
851 };
852
853 addSelectedFiles = createSlides => {
854 let t = this;
855 let files = [...t.state.selectedFiles];
856 let selectedSlide = { ...t.state.selectedSlide };
857
858 if(createSlides && t.state.slides.filter(s => s.stringID != "globalSlide").length == 1 && t.state.slides.filter(s => s.stringID != "globalSlide")[0].items.length == 0){
859 t.state.slides.splice(0, 1);
860 }
861
862 files.forEach(f => {
863 if (createSlides) {
864 t.handleAddSlide();
865 selectedSlide = t.state.slides[t.state.slides.length - 1];
866 }
867 if(f.duration){
868 selectedSlide.duration = parseInt(
869 f.duration
870 );
871 }
872 t.addFile(selectedSlide, f);
873 });
874
875 this.hideFilesModal();
876 };
877
878 addFile = (slide, file) => {
879 let t = this;
880 let selectedSlide = slide;
881 let response = file;
882
883 let maxIndex =
884 Math.max.apply(
885 Math,
886 selectedSlide.items.map(i => {
887 return i.index;
888 })
889 ) + 1;
890
891 if (selectedSlide.items.length == 0) {
892 maxIndex = 1;
893 }
894
895 if (imageFileTypes.indexOf(response.fileType) > -1) {
896 let itemToAdd = {
897 id: 0,
898 index: maxIndex,
899 name: response.name,
900 contentType: "image",
901 width: this.state.selectedDisplayType.width,
902 height: this.state.selectedDisplayType.height,
903 stringID: guidGenerator(),
904 left: 0,
905 top: 0,
906 src: response.path,
907 property: "{}"
908 };
909 selectedSlide.items.push(itemToAdd);
910 } else if (videoFileTypes.indexOf(response.fileType) > -1) {
911 let itemToAdd = {
912 id: 0,
913 index: maxIndex,
914 name: response.name,
915 contentType: "video",
916 width: this.state.selectedDisplayType.width,
917 height: this.state.selectedDisplayType.height,
918 stringID: guidGenerator(),
919 left: 0,
920 top: 0,
921 src: response.path,
922 property: "{}",
923 mute: false
924 };
925 selectedSlide.items.push(itemToAdd);
926 }
927
928 t.setState({
929 selectedSlide: selectedSlide
930 });
931 };
932
933 _changeFilesModalTab = tabName => {
934 this.setState({ filesModalActiveTab: tabName });
935 };
936
937 renderFilesModal = () => {
938 let t = this;
939 return (
940 <Modal
941 autoFocus={true}
942 show={this.state.showFilesModal}
943 onHide={this.hideFilesModal}
944 dialogClassName="modal-colored-header flex-80"
945 >
946 <Modal.Header>
947 <button
948 type="button"
949 className="close"
950 onClick={this.hideFilesModal}
951 data-dismiss="modal"
952 aria-hidden="true"
953 >
954 x
955 </button>
956 <h3 className="modal-title">
957 <FormattedHTMLMessage id="Select or upload a file" />
958 </h3>
959 </Modal.Header>
960 <Modal.Body>
961 <div className="row">
962 <div className="col-md-6">
963 <div
964 className="available-images row"
965 style={{
966 height: "350px",
967 overflow: "hidden"
968 }}
969 >
970 {!t.state.LOADING_FILES && t.state.files.length == 0 && (
971 <div
972 style={{ height: "100%" }}
973 className="flex layout-column layout-align-center-center"
974 >
975 <p>
976 <FormattedHTMLMessage id="No file available. Please upload file" />
977 </p>
978 </div>
979 )}
980
981 {t.state.LOADING_FILES && (
982 <div
983 style={{ height: "100%" }}
984 className="flex layout-column layout-align-center-center"
985 >
986 <Loader color="#26A65B" size="22px" />
987 </div>
988 )}
989
990 <div className="tab-container">
991 <ul className="nav nav-tabs">
992 <li
993 className={classNames({
994 active: this.state.filesModalActiveTab == "filterImages"
995 })}
996 >
997 <a
998 href="#filterImages"
999 data-toggle="tab"
1000 onClick={() =>
1001 this._changeFilesModalTab("filterImages")
1002 }
1003 >
1004 Images
1005 </a>
1006 </li>
1007
1008 <li
1009 className={classNames({
1010 active: this.state.filesModalActiveTab == "filterVideos"
1011 })}
1012 >
1013 <a
1014 href="#filterVideos"
1015 data-toggle="tab"
1016 onClick={() =>
1017 this._changeFilesModalTab("filterVideos")
1018 }
1019 >
1020 Videos
1021 </a>
1022 </li>
1023 </ul>
1024 <div className="tab-content">
1025 <div
1026 id="filterImages"
1027 className={classNames("tab-pane cont", {
1028 active: this.state.filesModalActiveTab == "filterImages"
1029 })}
1030 >
1031 <div
1032 className="row"
1033 style={{
1034 height: "276px",
1035 overflowX: "hidden",
1036 overflowY: "scroll"
1037 }}
1038 >
1039 {t.state.files
1040 .filter(file => {
1041 return imageFileTypes.indexOf(file.fileType) > -1;
1042 })
1043 .map((item, i, a) => {
1044 return (
1045 <div
1046 key={item.id}
1047 className={classNames("gallery-file col-md-4", {
1048 selected:
1049 t.state.selectedFile &&
1050 t.state.selectedFiles.filter(
1051 f => f.id == item.id
1052 ).length > 0
1053 })}
1054 >
1055 <div className="img-div-outer relative">
1056 <div
1057 className="delete-img full-absolute"
1058 onClick={() => this.deleteFile(item, i, a)}
1059 >
1060 x
1061 </div>
1062 <div
1063 className="img-div layout-column layout-align-center-center"
1064 style={{ height: "95px" }}
1065 onClick={() => this.toggleFile(item)}
1066 >
1067 <div className="filename">{item.name}</div>
1068 {this.renderGalleryItem(item)}
1069 <div
1070 className={classNames("checkmark", {
1071 hidden: t.state.selectedFiles.filter(
1072 f => f.id == item.id
1073 ).length == 0
1074 })}
1075 style={{ left: '0' }}
1076 >
1077 <i className="fa fa-check"></i>
1078 </div>
1079 </div>
1080 </div>
1081 </div>
1082 );
1083 })}
1084 </div>
1085 </div>
1086 <div
1087 id="filterVideos"
1088 className={classNames("tab-pane cont", {
1089 active: this.state.filesModalActiveTab == "filterVideos"
1090 })}
1091 >
1092 <div
1093 className="row"
1094 style={{
1095 height: "276px",
1096 overflowX: "hidden",
1097 overflowY: "scroll"
1098 }}
1099 >
1100 {t.state.files
1101 .filter(file => {
1102 return videoFileTypes.indexOf(file.fileType) > -1;
1103 })
1104 .map((item, i, a) => {
1105 return (
1106 <div
1107 key={item.id}
1108 className={classNames("gallery-file col-md-4", {
1109 selected:
1110 t.state.selectedFile &&
1111 t.state.selectedFiles.filter(
1112 f => f.id == item.id
1113 ).length > 0
1114 })}
1115 >
1116 <div className="img-div-outer relative">
1117 <div
1118 className="delete-img full-absolute"
1119 onClick={() => this.deleteFile(item, i, a)}
1120 >
1121 x
1122 </div>
1123 <div
1124 className="img-div layout-column layout-align-center-center"
1125 style={{ height: "95px" }}
1126 onClick={() => this.toggleFile(item)}
1127 >
1128 <div className="filename">{item.name}</div>
1129 {this.renderGalleryItem(item)}
1130 <div
1131 className={classNames("checkmark", {
1132 hidden: t.state.selectedFiles.filter(
1133 f => f.id == item.id
1134 ).length == 0
1135 })}
1136 style={{ left: '0' }}
1137 >
1138 <i className="fa fa-check"></i>
1139 </div>
1140 </div>
1141 </div>
1142 </div>
1143 );
1144 })}
1145 </div>
1146 </div>
1147 </div>
1148 </div>
1149 </div>
1150 </div>
1151 <div className="col-md-6" style={{ height: "350px" }}>
1152 <div
1153 className="flex layout-column layout-align-center-center"
1154 style={{ height: "350px" }}
1155 >
1156 {this.state.selectedFiles &&
1157 this.state.selectedFiles.length == 1 &&
1158 this.state.selectedFiles[0].id &&
1159 this.renderGalleryItem(this.state.selectedFiles[0], true)}
1160
1161 {this.state.selectedFiles &&
1162 this.state.selectedFiles.length > 1 && (
1163 <div
1164 style={{
1165 height: "350px",
1166 width: "100%",
1167 overflowY: "scroll",
1168 padding: "20px"
1169 }}
1170 >
1171 <h4>Selected files: </h4>
1172 <table className={"table table-responsive"}>
1173 <ReactSortable
1174 options={{
1175 animation: 150
1176 }}
1177 onChange={items => t.selectedFilesListOnSort(items)}
1178 tag="tbody"
1179 className="slides-container"
1180 >
1181 {this.state.selectedFiles.map((f, index) => {
1182 return (
1183 <tr key={f.id} data-id={f.id}>
1184 <td>
1185 <i className="fa fa-sort"></i> {f.name} { (f.fileType == "mp4" || f.fileType == "mov") && (f.duration == 0 ? "(calculating ..." : ("(" + f.duration + " sec")) })
1186 <i className="fa fa-trash pull-right" style={{ cursor: "pointer" }} onClick={e => this.toggleFile(f)} ></i>
1187 </td>
1188 </tr>
1189 );
1190 })}
1191 </ReactSortable>
1192 </table>
1193 </div>
1194 )}
1195 </div>
1196 </div>
1197 </div>
1198 <div className="row mt10">
1199 <Dropzone
1200 style={{
1201 width: "auto",
1202 height: "100px",
1203 border: "3px dashed #607d8b",
1204 fontSize: "14px",
1205 cursor: "pointer"
1206 }}
1207 className={"layout-row layout-align-center-center"}
1208 multiple={true}
1209 maxSize={1024 * 1024 * 2000}
1210 accept="image/*, video/*"
1211 onDrop={this._handleFileDrop}
1212 >
1213 <div>
1214 {this.state.UPLOADING_FILE && (
1215 <div
1216 style={{ height: "100%" }}
1217 className="flex layout-column layout-align-center-center"
1218 >
1219 <Loader color="#26A65B" size="22px" />
1220 </div>
1221 )}
1222 {!this.state.UPLOADING_FILE && (
1223 <FormattedHTMLMessage id="Try dropping file here, or click to select file to upload" />
1224 )}
1225 </div>
1226 </Dropzone>
1227 </div>
1228 </Modal.Body>
1229 <Modal.Footer>
1230 <button className="btn btn-default" onClick={t.hideFilesModal}>
1231 <FormattedHTMLMessage id="Cancel" />
1232 </button>
1233
1234 <button
1235 className="btn btn-success"
1236 onClick={() => t.addSelectedFiles(true)}
1237 >
1238 <FormattedHTMLMessage id="Add and create slides" />
1239 </button>
1240 <button
1241 className="btn btn-success"
1242 onClick={() => t.addSelectedFiles()}
1243 >
1244 <FormattedHTMLMessage id="Add" />
1245 </button>
1246 </Modal.Footer>
1247 </Modal>
1248 );
1249 };
1250
1251 renderItem = item => {
1252 switch (item.contentType) {
1253 case "image":
1254 return (
1255 <div
1256 style={{
1257 width: "100%",
1258 height: "100%",
1259 backgroundImage: "url('" + item.src + "')",
1260 backgroundSize: "contain",
1261 backgroundRepeat: "no-repeat",
1262 backgroundPosition: "center"
1263 }}
1264 />
1265 );
1266 case "video":
1267 return (
1268 <video
1269 id={item.stringID}
1270 style={{ width: "100%", height: "100%" }}
1271 controls
1272 onLoadedMetadata={e => this.handleVideoMetadata(e, item)}
1273 >
1274 <source src={item.src} type="video/mp4" />
1275 Your browser does not support HTML5 video.
1276 </video>
1277 );
1278 case "text":
1279 if (item.scrollingText) {
1280 return (
1281 <div
1282 style={{
1283 width: "100%",
1284 height: "100%",
1285 backgroundColor: item.bgColor,
1286 overflow: "hidden"
1287 }}
1288 dangerouslySetInnerHTML={{
1289 __html:
1290 '<marquee scrollamount="' +
1291 item.scrollingTextSpeed +
1292 '"> ' +
1293 item.itemText +
1294 "</marquee>"
1295 }}
1296 />
1297 );
1298 } else {
1299 return (
1300 <div
1301 style={{
1302 width: "100%",
1303 height: "100%",
1304 backgroundColor: item.bgColor,
1305 overflow: "hidden"
1306 }}
1307 dangerouslySetInnerHTML={{ __html: item.itemText }}
1308 />
1309 );
1310 }
1311 default:
1312 return (
1313 <div style={{ width: "100%", height: "100%", overflow: "hidden" }}>
1314 Undefined item
1315 </div>
1316 );
1317 }
1318 };
1319
1320 hideEditorModal = () => {
1321 this.setState({ showTextEditor: false });
1322 };
1323
1324 showTextEditor = () => {
1325 this.setState({ showTextEditor: true }, () => {
1326 window.setTimeout(() => {
1327 let t = this;
1328 const emailEditorInstance = $(t.emailEditor);
1329 var foo = [];
1330
1331 for (var i = 1; i <= 150; i++) {
1332 foo.push(i);
1333 }
1334
1335 const editorConfig = {
1336 placeholderText: t.props.intl.formatMessage({
1337 id: "Type your content here"
1338 }),
1339 inlineStyles: {
1340 "Small Blue": "font-size: 14px; color: blue;"
1341 },
1342
1343 heightMin: 300,
1344 heightMax: 600,
1345 charCounterCount: false,
1346 colorsHEXInput: false,
1347 fontSize: foo,
1348 fontSizeSelection: true,
1349 toolbarButtons: [
1350 "fullscreen",
1351 "bold",
1352 "underline",
1353 "|",
1354 "fontSize",
1355 "color",
1356 "align",
1357 "|",
1358 "undo",
1359 "redo"
1360 ],
1361 quickInsertButtons: []
1362 };
1363
1364 const getEmailEditorInstance = () => {
1365 return emailEditorInstance.data("froala.editor");
1366 };
1367
1368 const initEmailEditor = config => {
1369 console.log("emailEditorInstance", emailEditorInstance);
1370 emailEditorInstance
1371 .on("froalaEditor.destroy", function(e, editor) {
1372 console.log("Alert destroy");
1373 window.setTimeout(function() {
1374 initEmailEditor(editorConfig);
1375 }, 250);
1376 })
1377 .on("froalaEditor.initialized", function(e, editor) {
1378 var editorInstance = getEmailEditorInstance();
1379 editorInstance.events.focus();
1380 editorInstance.html.set(t.state.selectedItem.itemText);
1381 })
1382 .on("froalaEditor.contentChanged", function(e, editor) {
1383 var editorInstance = getEmailEditorInstance();
1384 const html = editorInstance.html.get(true);
1385 t.handleItemTextChange(html);
1386 })
1387 .froalaEditor(config);
1388 };
1389
1390 initEmailEditor(editorConfig);
1391
1392 this.editorConfig = editorConfig;
1393 this.initEmailEditor = initEmailEditor;
1394
1395 window.emailEditorInstance = emailEditorInstance;
1396 window.editorConfig = editorConfig;
1397 window.initEmailEditor = initEmailEditor;
1398 }, 250);
1399 });
1400 };
1401
1402 renderTextEditorModal = () => {
1403 return (
1404 <Modal
1405 autoFocus={true}
1406 show={this.state.showTextEditor}
1407 onHide={this.hideEditorModal}
1408 dialogClassName="modal-colored-header flex-70"
1409 >
1410 <Modal.Header>
1411 <button
1412 type="button"
1413 className="close"
1414 onClick={this.hideEditorModal}
1415 data-dismiss="modal"
1416 aria-hidden="true"
1417 >
1418 x
1419 </button>
1420 <h3 className="modal-title">Edit text content</h3>
1421 </Modal.Header>
1422 <Modal.Body>
1423 <div>
1424 {this.state.selectedItem &&
1425 this.state.selectedItem.contentType == "text" && (
1426 <div>
1427 <div ref={node => (this.emailEditor = node)} />
1428 <textarea
1429 rows="5"
1430 required={true}
1431 name="itemText"
1432 onChange={() => {}}
1433 value={this.state.selectedItem.itemText}
1434 className="form-control hide"
1435 />
1436 {/* <FroalaEditor
1437 model={this.state.selectedItem.itemText}
1438 name="itemText"
1439 config={{
1440 charCounterCount: false,
1441 toolbarButtons: [
1442 "fullscreen",
1443 "bold",
1444 "italic",
1445 "underline",
1446 "strikeThrough",
1447 "|",
1448 "fontSize",
1449 "color",
1450 "align",
1451 "|",
1452 "specialCharacters",
1453 "insertHR",
1454 "clearFormatting",
1455 "|",
1456 "undo",
1457 "redo"
1458 ],
1459 heightMin: "400px",
1460 autoFocus: true,
1461 toolbarSticky: false,
1462 toolbarInline: false
1463 }}
1464 autoFocus={true}
1465 onModelChange={this.handleItemTextChange}
1466 /> */}
1467 </div>
1468 )}
1469 <div className="form-footer">
1470 <button
1471 type="submit"
1472 onClick={this.hideEditorModal}
1473 className={classNames("btn btn-success")}
1474 disabled={this.state.SUBMITTING_FORM}
1475 >
1476 Done
1477 </button>
1478 </div>
1479 </div>
1480 </Modal.Body>
1481 </Modal>
1482 );
1483 };
1484
1485 renderSlideItemsList = (item, index, global) => {
1486 let t = this;
1487 switch (item.contentType) {
1488 case "text":
1489 return (
1490 <td
1491 style={{
1492 whiteSpace: "nowrap",
1493 overflow: "hidden",
1494 textOverflow: "ellipsis"
1495 }}
1496 >
1497 <i
1498 className="fa fa-trash mr5"
1499 onClick={e => t.removeItem(index, global)}
1500 />
1501 <i className="fa fa-font" /> Text
1502 </td>
1503 );
1504 case "image":
1505 return (
1506 <td
1507 style={{
1508 whiteSpace: "nowrap",
1509 overflow: "hidden",
1510 textOverflow: "ellipsis"
1511 }}
1512 >
1513 <i
1514 className="fa fa-trash mr5"
1515 onClick={e => t.removeItem(index, global)}
1516 />
1517 <i className="fa fa-image" /> {item.name}
1518 </td>
1519 );
1520 case "video":
1521 return (
1522 <td
1523 style={{
1524 whiteSpace: "nowrap",
1525 overflow: "hidden",
1526 textOverflow: "ellipsis"
1527 }}
1528 >
1529 <i
1530 className="fa fa-trash mr5"
1531 onClick={e => t.removeItem(index, global)}
1532 />
1533 <i className="fa fa-play" /> {item.name}
1534 </td>
1535 );
1536 default:
1537 return <td> Undefined </td>;
1538 }
1539 };
1540
1541 render() {
1542 let t = this;
1543
1544 return (
1545 <DefaultLayout>
1546 <div>
1547 {this.renderFilesModal()}
1548 {this.renderTextEditorModal()}
1549 <div className="page-heading layout-row layout-align-start-center">
1550 <div>
1551 <h1>
1552 {!t.state.FETCHING_PLAYLIST && t.state.playlist.name}
1553 {t.state.FETCHING_PLAYLIST && (
1554 <div>
1555 <Loader size="20px" color="#444a59" />
1556 </div>
1557 )}
1558 </h1>
1559 <div>
1560 <em>{t.state.playlist.description}</em>
1561 </div>
1562 </div>
1563 <div className="flex" />
1564 <div className="options">
1565 <div className="btn-toolbar">
1566 <button
1567 className="btn btn-default btn-shade4 btn-space"
1568 onClick={this.openPreview}
1569 >
1570 <i className="fa fa-play pr5" />
1571 <FormattedHTMLMessage id="Preview" />
1572 </button>
1573 <div className={classNames("dropdown inline-block")}>
1574 <button
1575 className="btn btn-info btn-space"
1576 data-toggle="dropdown"
1577 >
1578 <i className="fa fa-desktop pr5" />
1579 {this.state.selectedDisplayType
1580 ? this.state.selectedDisplayType.name +
1581 "(" +
1582 this.state.selectedDisplayType.width +
1583 "x" +
1584 this.state.selectedDisplayType.height +
1585 ")"
1586 : "Please select display type "}
1587 </button>
1588
1589 <ul className="dropdown-menu dtdropdown" role="menu">
1590 <li className="dropdown-header">
1591 <FormattedHTMLMessage id="Available display types" />
1592 </li>
1593 {this.state.displayTypes.map((s, k) => {
1594 return (
1595 <li key={k}>
1596 <a
1597 onClick={e => {
1598 this.setSelectedDisplayType(s);
1599 }}
1600 >
1601 {s.name}
1602 </a>
1603 </li>
1604 );
1605 })}
1606 </ul>
1607 </div>
1608 <button
1609 className="btn btn-success btn-shade4 btn-space"
1610 onClick={() => {
1611 this._handleSavePlaylistButton(false);
1612 }}
1613 >
1614 <FormattedHTMLMessage id="Save" />
1615 </button>
1616 <button
1617 className="btn btn-danger btn-shade4 btn-space"
1618 onClick={this._handleDeletePlaylistButton}
1619 >
1620 <FormattedHTMLMessage id="Delete" />
1621 </button>
1622 <button className="btn btn-info btn-shade4 btn-space hide">
1623 <i className="fa fa-question-circle" />
1624 </button>
1625 </div>
1626 </div>
1627 </div>
1628
1629 <div
1630 className="playlist-editor-container"
1631 style={{ height: this.state.contentHeight + "px" }}
1632 >
1633 <div
1634 className="canvas-container"
1635 style={{
1636 width: this.state.contentWidth - 170 + "px",
1637 height: this.state.contentHeight - 100 + "px"
1638 }}
1639 >
1640 {t.state.selectedSlide && t.state.selectedDisplayType && (
1641 <div
1642 style={{
1643 width: t.state.selectedDisplayType.width + "px",
1644 height: t.state.selectedDisplayType.height + "px",
1645 background: "#fff",
1646 margin: "5px",
1647 border: "1px dashed #444a59",
1648 transform: "scale(" + this.state.selectedScale / 100 + ")",
1649 transformOrigin: "left top",
1650 backgroundColor: this.state.selectedSlide.bgColor
1651 }}
1652 onClick={() => {
1653 this.setSelectedItem(null);
1654 }}
1655 >
1656 {t.state.selectedSlide.items.map((item, index) => {
1657 return (
1658 <Rnd
1659 className={classNames({
1660 "slide-item":
1661 this.state.selectedItem &&
1662 item.stringID == this.state.selectedItem.stringID
1663 })}
1664 ref={c => {
1665 this.items[item.stringID] = c;
1666 }}
1667 key={item.stringID}
1668 default={{
1669 x: item.left,
1670 y: item.top,
1671 width: item.width + "px",
1672 height: item.height + "px"
1673 }}
1674 onClick={e => {
1675 e.stopPropagation();
1676 this.setSelectedItemIndex(index);
1677 }}
1678 onDragStop={(e, d) => {
1679 item.left = parseInt(d.x);
1680 item.top = parseInt(d.y);
1681 }}
1682 onResize={(e, d, ref, delta, position) => {
1683 item.width = parseInt(ref.offsetWidth);
1684 item.height = parseInt(ref.offsetHeight);
1685 item.left = parseInt(position.x);
1686 item.top = parseInt(position.y);
1687 }}
1688 bounds="parent"
1689 style={{
1690 zIndex:
1691 this.state.selectedItem &&
1692 item.stringID == this.state.selectedItem.stringID
1693 ? 99999
1694 : item.index
1695 }}
1696 >
1697 <div
1698 style={{
1699 position: "absolute",
1700 width: "100%",
1701 height: "100%",
1702 zIndex: 999999
1703 }}
1704 >
1705 {this.state.selectedItem &&
1706 item.stringID ==
1707 this.state.selectedItem.stringID && (
1708 <i
1709 className="fa fa-remove"
1710 style={{
1711 position: "absolute",
1712 right: "5px",
1713 top: "5px",
1714 background: "#fff",
1715 borderRadius: "3px",
1716 padding: "3px"
1717 }}
1718 onClick={() => t.removeItem(index)}
1719 role="button"
1720 />
1721 )}
1722 {(item.contentType == "video" ||
1723 item.contentType == "image") && (
1724 <div
1725 className="item-name"
1726 style={{
1727 position: "absolute",
1728 left: "0px",
1729 top: "0px",
1730 overflow: "hidden",
1731
1732 widht: "100%",
1733 height: "20px",
1734 fontSize: "18px",
1735 lineHeight: "20px",
1736 color: "#ffffff",
1737 background: "rgba(0,0,0,0.6)",
1738 padding: "3px"
1739 }}
1740 >
1741 {" "}
1742 {item.name}{" "}
1743 </div>
1744 )}
1745 </div>
1746 {t.renderItem(item)}
1747 </Rnd>
1748 );
1749 })}
1750 {t.state.slides.filter(s => s.stringID == "globalSlide")
1751 .length > 0 &&
1752 t.state.slides
1753 .filter(s => s.stringID == "globalSlide")[0]
1754 .items.map((item, index) => {
1755 return (
1756 <Rnd
1757 className={classNames({
1758 "slide-item":
1759 this.state.selectedItem &&
1760 item.stringID ==
1761 this.state.selectedItem.stringID
1762 })}
1763 ref={c => {
1764 this.items[item.stringID] = c;
1765 }}
1766 key={item.stringID}
1767 default={{
1768 x: item.left,
1769 y: item.top,
1770 width: item.width + "px",
1771 height: item.height + "px"
1772 }}
1773 onClick={e => {
1774 e.stopPropagation();
1775 this.setSelectedItemIndex(index, true);
1776 }}
1777 onDragStop={(e, d) => {
1778 item.left = parseInt(d.x);
1779 item.top = parseInt(d.y);
1780 }}
1781 onResize={(e, d, ref, delta, position) => {
1782 item.width = parseInt(ref.offsetWidth);
1783 item.height = parseInt(ref.offsetHeight);
1784 item.left = parseInt(position.x);
1785 item.top = parseInt(position.y);
1786 }}
1787 bounds="parent"
1788 style={{
1789 zIndex:
1790 this.state.selectedItem &&
1791 item.stringID ==
1792 this.state.selectedItem.stringID
1793 ? 99999
1794 : item.index
1795 }}
1796 >
1797 <div
1798 style={{
1799 position: "absolute",
1800 width: "100%",
1801 height: "100%",
1802 zIndex: 999999
1803 }}
1804 >
1805 {this.state.selectedItem &&
1806 item.stringID ==
1807 this.state.selectedItem.stringID && (
1808 <i
1809 className="fa fa-remove"
1810 style={{
1811 position: "absolute",
1812 right: "5px",
1813 top: "5px",
1814 background: "#fff",
1815 borderRadius: "3px",
1816 padding: "3px"
1817 }}
1818 onClick={() => t.removeItem(index, true)}
1819 role="button"
1820 />
1821 )}
1822 </div>
1823 {t.renderItem(item)}
1824 </Rnd>
1825 );
1826 })}
1827 </div>
1828 )}
1829 </div>
1830
1831 <div
1832 className="item-properties flex layout-column"
1833 style={{
1834 height: this.state.contentHeight - 100 + "px",
1835 background: "#444a59",
1836 paddingLeft: "10px",
1837 paddingRight: "10px",
1838 paddingTop: "10px"
1839 }}
1840 >
1841 <div
1842 className="flex"
1843 style={{ overflowX: "hidden", overflowY: "auto" }}
1844 >
1845 {this.state.selectedItem && (
1846 <div className="panel panel-default alt mb0 flex layout-column">
1847 <div className="panel-heading">
1848 <div
1849 className="mt5 mb5 text-center"
1850 style={{ textTransform: "capitalize" }}
1851 >
1852 {this.state.selectedItem.contentType} properties
1853 </div>
1854 </div>
1855 <div className="panel-body flex layout-column pb0 pt0">
1856 {/* text specific properties */}
1857 {t.state.selectedItem.contentType == "text" && (
1858 <table className="table table-xs mt10">
1859 <tbody>
1860 <tr>
1861 <td colSpan={2}>
1862 <textarea
1863 role="button"
1864 value={$(
1865 "<div>" +
1866 this.state.selectedItem.itemText +
1867 "</div>"
1868 ).text()}
1869 style={{ width: "100%", height: "62px" }}
1870 onClick={this.showTextEditor}
1871 />
1872 </td>
1873 </tr>
1874 <tr>
1875 <td style={{ borderRight: "1px solid #ccc" }}>
1876 <span className="checkbox block">
1877 <input
1878 type="checkbox"
1879 className="tectonic"
1880 id="videoLoop"
1881 name="scrollingText"
1882 onChange={this._handleItemFormChange}
1883 checked={
1884 this.state.selectedItem.scrollingText
1885 }
1886 />
1887 <label
1888 className="text-in"
1889 htmlFor="videoLoop"
1890 >
1891 {this.state.selectedItem.scrollingText
1892 ? "Disable"
1893 : "Enable"}{" "}
1894 scrolling
1895 </label>
1896 </span>
1897 </td>
1898 <td width="80">
1899 <input
1900 type="number"
1901 className="form-control"
1902 id="scrollingTextSpeed"
1903 name="scrollingTextSpeed"
1904 onChange={this._handleItemFormChange}
1905 disabled={
1906 !this.state.selectedItem.scrollingText
1907 }
1908 placeholder="speed"
1909 value={
1910 this.state.selectedItem.scrollingTextSpeed
1911 }
1912 />
1913 </td>
1914 </tr>
1915 <tr>
1916 <td>
1917 <FormattedHTMLMessage
1918 id={"Frame background color "}
1919 />{" "}
1920 <br />
1921 {this.state.selectedItem.bgColor != "" && (
1922 <a
1923 role="button"
1924 href="#"
1925 onClick={() => {
1926 let selectedItem = this.state
1927 .selectedItem;
1928 selectedItem.bgColor = "";
1929 this.setState({ selectedItem });
1930 }}
1931 >
1932 <i className="fa fa-remove" /> Clear color
1933 </a>
1934 )}
1935 </td>
1936 <td className="text-right">
1937 <div className="pull-right">
1938 <span className="dit">
1939 <div
1940 className="color-picker-btn"
1941 onClick={e =>
1942 this._handleColorPickerButtonClick(
1943 e,
1944 "bgColor",
1945 this.state.selectedItem.bgColor,
1946 null,
1947 "selectedItem"
1948 )
1949 }
1950 >
1951 {colorBox(
1952 this.state.selectedItem.bgColor
1953 )}
1954 </div>
1955 <div className="color-picker-wrapper" />
1956 </span>
1957 </div>
1958 </td>
1959 </tr>
1960 </tbody>
1961 </table>
1962 )}
1963
1964 {/* video specific properties */}
1965 {t.state.selectedItem.contentType == "video" && (
1966 <table className="table table-xs mt10">
1967 <tbody>
1968 <tr style={{ borderBottom: "1px solid #ccc" }}>
1969 <td style={{ borderRight: "1px solid #ccc" }}>
1970 <span className="checkbox block">
1971 <input
1972 type="checkbox"
1973 className="tectonic"
1974 id="videoLoop"
1975 name="loop"
1976 onChange={this._handleItemFormChange}
1977 checked={this.state.selectedItem.loop}
1978 />
1979 <label
1980 className="text-in"
1981 htmlFor="videoLoop"
1982 >
1983 <FormattedHTMLMessage id="Loop" />
1984 </label>
1985 </span>
1986 </td>
1987 <td>
1988 <span className="checkbox block ml5">
1989 <input
1990 type="checkbox"
1991 className="tectonic"
1992 id="videoMute"
1993 name="mute"
1994 onChange={this._handleItemFormChange}
1995 checked={this.state.selectedItem.mute}
1996 />
1997 <label
1998 className="text-in"
1999 htmlFor="videoMute"
2000 >
2001 <FormattedHTMLMessage id="Mute" />
2002 </label>
2003 </span>
2004 </td>
2005 </tr>
2006 </tbody>
2007 </table>
2008 )}
2009
2010 <table className="table table-xs">
2011 <tbody>
2012 <tr>
2013 <td width={40}>Top:</td>
2014 <td>
2015 <div className="pull-right">
2016 <input
2017 type="number"
2018 className="form-control"
2019 name="top"
2020 value={this.state.selectedItem.top}
2021 onChange={this._handleItemFormChange}
2022 />
2023 </div>
2024 </td>
2025 <td width={40}>Left:</td>
2026 <td>
2027 <div className="pull-right">
2028 <input
2029 type="number"
2030 className="form-control"
2031 name="left"
2032 value={this.state.selectedItem.left}
2033 onChange={this._handleItemFormChange}
2034 />
2035 </div>
2036 </td>
2037 </tr>
2038 <tr>
2039 <td width={40}>Width: </td>
2040 <td>
2041 <div className="pull-right">
2042 <input
2043 type="number"
2044 className="form-control"
2045 name="width"
2046 value={this.state.selectedItem.width}
2047 onChange={this._handleItemFormChange}
2048 />
2049 </div>
2050 </td>
2051 <td width={40}>Height:</td>
2052 <td>
2053 <input
2054 type="number"
2055 className="form-control"
2056 name="height"
2057 value={this.state.selectedItem.height}
2058 onChange={this._handleItemFormChange}
2059 />
2060 </td>
2061 </tr>
2062 <tr>
2063 <td width={40} colSpan={3}>
2064 Index:{" "}
2065 </td>
2066 <td>
2067 <div className="pull-right">
2068 <input
2069 type="number"
2070 className="form-control"
2071 name="index"
2072 value={this.state.selectedItem.index}
2073 onChange={this._handleItemFormChange}
2074 />
2075 </div>
2076 </td>
2077 </tr>
2078 </tbody>
2079 </table>
2080 </div>
2081 </div>
2082 )}
2083
2084 {this.state.selectedItem == null && this.state.selectedSlide && (
2085 <div className="panel panel-default alt mb0 flex layout-column">
2086 <div className="panel-heading">
2087 <div className="mt5 mb5 text-center">
2088 Slide properties
2089 </div>
2090 </div>
2091 <div className="panel-body flex layout-column pb0 pt0">
2092 <table className="table table-xs">
2093 <tbody>
2094 {/* <tr>
2095 <td>
2096 <FormattedHTMLMessage id="Name" />
2097 </td>
2098 <td className="text-right">
2099 <div className="pull-right">
2100 <input
2101 type="text"
2102 className="form-control"
2103 name="name"
2104 value={this.state.selectedSlide.name}
2105 onChange={this._handleSlideFormInputChange}
2106 />
2107 </div>
2108 </td>
2109 </tr> */}
2110 <tr>
2111 <td>
2112 <FormattedHTMLMessage id="Duration" />
2113 </td>
2114 <td className="text-right">
2115 <div className="pull-right">
2116 <div className="input-group">
2117 <input
2118 type="text"
2119 className="form-control"
2120 name="duration"
2121 value={this.state.selectedSlide.duration}
2122 onChange={this._handleSlideFormInputChange}
2123 />
2124 </div>
2125 </div>
2126 </td>
2127 </tr>
2128 {this.state.selectedSlide &&
2129 this.state.selectedSlide.videoDurations.length >
2130 0 && (
2131 <tr>
2132 <td colSpan={2}>
2133 <div
2134 className={classNames(
2135 "dropdown inline-block full-width"
2136 )}
2137 >
2138 <a href="#" data-toggle="dropdown">
2139 Set from file
2140 </a>
2141
2142 <ul className="dropdown-menu" role="menu">
2143 <li className="dropdown-header">
2144 <FormattedHTMLMessage id="File durations" />
2145 </li>
2146 {this.state.selectedSlide &&
2147 this.state.selectedSlide.videoDurations.map(
2148 (s, k) => {
2149 return (
2150 <li key={k}>
2151 <a
2152 onClick={e => {
2153 let slide = this.state
2154 .selectedSlide;
2155 slide.duration = parseInt(
2156 s.duration
2157 );
2158 this.setState({
2159 selectedSlide: slide
2160 });
2161 }}
2162 >
2163 {s.file.name &&
2164 s.file.name.substring(
2165 0,
2166 17
2167 )}
2168 {s.file.name &&
2169 s.file.name.length > 16 &&
2170 "..."}{" "}
2171 - {parseInt(s.duration)} sec
2172 </a>
2173 </li>
2174 );
2175 }
2176 )}
2177 </ul>
2178 </div>
2179 </td>
2180 </tr>
2181 )}
2182 <tr>
2183 <td>
2184 <FormattedHTMLMessage id={"Bg color "} />
2185 </td>
2186 <td className="text-right">
2187 <div className="pull-right">
2188 <span className="dit">
2189 <div
2190 className="color-picker-btn"
2191 onClick={e =>
2192 this._handleColorPickerButtonClick(
2193 e,
2194 "bgColor",
2195 this.state.selectedSlide.bgColor,
2196 null,
2197 "selectedSlide"
2198 )
2199 }
2200 >
2201 {colorBox(this.state.selectedSlide.bgColor)}
2202 </div>
2203 <div className="color-picker-wrapper" />
2204 </span>
2205 </div>
2206 </td>
2207 </tr>
2208 </tbody>
2209 </table>
2210 </div>
2211 </div>
2212 )}
2213
2214 {this.state.selectedSlide && (
2215 <div className="panel panel-default alt mt10 flex layout-column">
2216 <div className="panel-heading">
2217 <div className="mt5 mb5 text-center">Add item</div>
2218 </div>
2219 <div className="panel-body flex layout-column pb0 pt0">
2220 <div
2221 className="row"
2222 style={{ borderBottom: "1px solid #ddd" }}
2223 >
2224 <div className="col-md-6">
2225 <button
2226 className="btn btn-sm btn-default rounded-button mt5"
2227 onClick={e => this.openFilesModal("filterImages")}
2228 >
2229 Image
2230 </button>
2231 </div>
2232 <div className="col-md-6">
2233 <button
2234 className="btn btn-sm btn-default rounded-button mt5"
2235 onClick={e => this.openFilesModal("filterVideos")}
2236 >
2237 Video
2238 </button>
2239 </div>
2240 <div className="col-md-6">
2241 <button
2242 className="btn btn-sm btn-default rounded-button mt5 mb15"
2243 onClick={this.addTextItem}
2244 >
2245 Text
2246 </button>
2247 </div>
2248 <div className="col-md-6">
2249 <button
2250 className="btn btn-sm btn-default rounded-button mt5 mb15"
2251 onClick={this.addGlobalTextItem}
2252 >
2253 Global Text
2254 </button>
2255 </div>
2256 </div>
2257 </div>
2258
2259 {this.state.selectedSlide.items.length > 0 && (
2260 <div>
2261 <div className="panel-heading mt10">
2262 <div className="mt5 mb5 text-center">
2263 Current slide items
2264 </div>
2265 </div>
2266 <div className="panel-body flex layout-column pb0 pt0">
2267 <table className="table table-xs table-hover">
2268 <tbody>
2269 {this.state.selectedSlide.items.map(
2270 (item, index) => {
2271 let itemRow = this.renderSlideItemsList(
2272 item,
2273 index
2274 );
2275 return (
2276 <tr
2277 key={item.stringID}
2278 onClick={() => this.setSelectedItem(item)}
2279 style={{
2280 cursor: "pointer",
2281 backgroundColor:
2282 this.state.selectedItem &&
2283 item.stringID ==
2284 this.state.selectedItem.stringID
2285 ? "#eee"
2286 : "#fff"
2287 }}
2288 >
2289 {itemRow}
2290 </tr>
2291 );
2292 }
2293 )}
2294 </tbody>
2295 </table>
2296 </div>
2297 {this.state.slides.filter(
2298 s => s.stringID == "globalSlide"
2299 ).length > 0 &&
2300 this.state.slides.filter(
2301 s => s.stringID == "globalSlide"
2302 )[0].items.length > 0 && (
2303 <div className="panel-heading mt10">
2304 <div className="mt5 mb5 text-center">
2305 Global Items
2306 </div>
2307 </div>
2308 )}
2309
2310 {this.state.slides.filter(
2311 s => s.stringID == "globalSlide"
2312 ).length > 0 &&
2313 this.state.slides.filter(
2314 s => s.stringID == "globalSlide"
2315 )[0].items.length > 0 && (
2316 <div className="panel-body flex layout-column pb0 pt0">
2317 <table className="table table-xs table-hover">
2318 <tbody>
2319 {this.state.slides
2320 .filter(s => s.stringID == "globalSlide")[0]
2321 .items.map((item, index) => {
2322 let itemRow = this.renderSlideItemsList(
2323 item,
2324 index,
2325 true
2326 );
2327 return (
2328 <tr
2329 key={item.stringID}
2330 onClick={() =>
2331 this.setSelectedItem(item)
2332 }
2333 style={{
2334 cursor: "pointer",
2335 backgroundColor:
2336 this.state.selectedItem &&
2337 item.stringID ==
2338 this.state.selectedItem.stringID
2339 ? "#eee"
2340 : "#fff"
2341 }}
2342 >
2343 {itemRow}
2344 </tr>
2345 );
2346 })}
2347 </tbody>
2348 </table>
2349 </div>
2350 )}
2351 </div>
2352 )}
2353 </div>
2354 )}
2355 </div>
2356 <div style={{ height: "10px", widht: "100%" }} />
2357 {this.state.selectedSlide && (
2358 <div className="properties-footer">
2359 <div className="panel panel-default alt flex layout-column">
2360 <div className="panel-heading">
2361 <div className="mt5 mb5 text-center">Zoom</div>
2362 </div>
2363 <div className="panel-body flex layout-column pb0 pt0">
2364 <div className="pb20 pt20">
2365 <InputRange
2366 maxValue={100}
2367 minValue={0}
2368 value={this.state.selectedScale}
2369 onChange={value =>
2370 this.setState({ selectedScale: value })
2371 }
2372 />
2373 </div>
2374 </div>
2375 </div>
2376 </div>
2377 )}
2378 </div>
2379 <ReactSortable
2380 options={{
2381 animation: 150
2382 }}
2383 onChange={items => t.slidesListOnSort(items)}
2384 tag="div"
2385 className="slides-container"
2386 >
2387 {t.state.slides
2388 .filter(a => a.stringID != "globalSlide")
2389 .sort(function(a, b) {
2390 return a.orderID - b.orderID;
2391 })
2392 .map((slide, index) => {
2393 return (
2394 <div
2395 key={slide.stringID}
2396 data-id={slide.stringID}
2397 role="button"
2398 className="slide-item"
2399 style={{
2400 backgroundColor:
2401 this.state.selectedSlide &&
2402 slide.stringID == this.state.selectedSlide.stringID
2403 ? "#dddddd"
2404 : slide.bgColor
2405 }}
2406 onClick={() => {
2407 slide.videoDurations = [];
2408
2409 var selectedItem = null;
2410
2411 console.log("selecting slide", slide);
2412
2413 this.setState({
2414 selectedSlide: slide,
2415 selectedSlideIndex: index,
2416 selectedItem: selectedItem
2417 });
2418 }}
2419 >
2420 <b>Slide {index + 1}</b>
2421 <br />
2422 {slide.duration} sec <br/>
2423 <div className={"slide-video-file-sample-name"}>
2424 {slide.items.filter(slideItem => slideItem.contentType == "video").length > 0 && (
2425 slide.items.filter(s => s.contentType == "video")[0].name
2426 )}
2427 </div>
2428 {this.state.selectedSlide &&
2429 slide.stringID == this.state.selectedSlide.stringID && (
2430 <i
2431 className="fa fa-trash"
2432 role="button"
2433 style={{
2434 position: "absolute",
2435 right: "3px",
2436 top: "3px",
2437 padding: "4px",
2438 background: "#fff",
2439 color: "#444a59",
2440 borderRadius: "4px",
2441 fontSize: "12px"
2442 }}
2443 onClick={e => this.removeSlide(index)}
2444 />
2445 )}
2446 </div>
2447 );
2448 })}
2449 <div
2450 role="button"
2451 onClick={e => this.handleAddSlide()}
2452 style={{
2453 background: "transparent",
2454 color: "#fff",
2455 width: "120px",
2456 lineHeight: "90px"
2457 }}
2458 className="slide-item"
2459 >
2460 <i
2461 className="fa fa-plus"
2462 style={{
2463 marginTop: "18px",
2464 borderRadius: "7px",
2465 fontSize: "15px",
2466 color: "#444a59",
2467 padding: "12px",
2468 float: "left",
2469 background: "#ffffff"
2470 }}
2471 />
2472 <span
2473 style={{
2474 marginTop: "-7px",
2475 marginLeft: "7px",
2476 float: "left"
2477 }}
2478 >
2479 <b>Add slide</b>
2480 </span>
2481 </div>
2482 </ReactSortable>
2483 </div>
2484 </div>
2485 </DefaultLayout>
2486 );
2487 }
2488}
2489
2490function guidGenerator() {
2491 var S4 = function() {
2492 return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
2493 };
2494 return (
2495 S4() +
2496 S4() +
2497 "-" +
2498 S4() +
2499 "-" +
2500 S4() +
2501 "-" +
2502 S4() +
2503 "-" +
2504 S4() +
2505 S4() +
2506 S4()
2507 );
2508}
2509
2510export default injectIntl(PlaylistView);