import React, {ReactElement} from 'react'; import axios from "axios" import ModalFeedback from "./ModalFeedback"; import ModalError from "./ModalError"; import ModalSuccess from './ModalSuccess'; import Screenshot from "./Screenshot"; import Recorder from "./Recorder"; import * as tus from 'tus-js-client' import {ErrorBoundary} from "react-error-boundary"; import RecorderWhammy from "./RecorderWhammy"; import {withCookies} from 'react-cookie'; import {Feedback} from "./types/feedback"; import {openDB} from 'idb'; import {LoadingOverlay, Modal} from "@mantine/core"; class App extends React.Component { constructor(props: Feedback.Props) { super(props); if (!('url' in props.config) || typeof props.config.url !== 'string') { console.error('Feedback URL config param must be specified!') } if (!('tus' in props.config) || typeof props.config.tus !== 'string') { console.error('Feedback TUS config param must be specified!') } this.state = { show: 'feedback', screenshot: undefined, screenshotError: undefined, record: undefined, tusUniqIdRecord: undefined, tusUploadedRecord: false, tusUniqIdScreenshot: undefined, tusUploadedScreenshot: false, uploadProgress: 0, uploadProgressTusScreenshot: 0, newTaskUrl: undefined }; if (FeedbackSupport) { FeedbackSupport.opened = true } } componentDidMount() { axios.get(`${this.props.config.url}/tasklists`, { params: { project: this.props.config.project }, headers: this.props.config.headers || {} }).then((res) => { const tasklists: any = [] res.data.data.tasklists.forEach((task: Api.Tasklist) => { tasklists.push({value: task.name, label: task.name}) }) this.setState({ types: tasklists }) }).catch((err) => { console.error(err) this.setState({ types: [] }) }) } componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any) { if (this.state.show === '') { if (FeedbackSupport) { FeedbackSupport.opened = false } } else { if (FeedbackSupport) { FeedbackSupport.opened = true } } if (this.state.screenshot && !this.state.uploadProgressTusScreenshot && !this.state.tusUniqIdScreenshot) { this.uploadTusScreenshot() } } onClose = () => { this.setState({ show: '', screenshot: undefined, record: undefined }) } onSubmit = async (data: Feedback.Data) => { const self = this // if (self.state.record) { // data.videoMimeType = self.state.record.mimeType // // await self.uploadTus(data, 'Record') // } else { // self.setState({ // tusUploadedRecord: true // }) // } // // if (self.state.record) { // // } else { // self.setState({ // uploadProgressTus: undefined // }) self.uploadApi(data) // } } uploadTusScreenshot = () => { const self = this self.setState({ uploadProgressTusScreenshot: 1 }) if (self.state.screenshot?.image) { const upload = new tus.Upload(self.state.screenshot?.blob, { endpoint: self.props.config.tus, retryDelays: [0, 3000, 5000, 10000, 20000], onError: function (error: Error) { console.error("Failed because: " + error) self.setState({ show: 'error' }) }, onProgress: function (bytesUploaded: number, bytesTotal: number) { const percentage = bytesUploaded / bytesTotal * 100 self.setState({ uploadProgressTusScreenshot: percentage }) }, onSuccess: function () { const urlParts = upload.url?.split('/') || []; self.setState({ tusUniqIdScreenshot: urlParts.length ? urlParts.pop() : undefined, tusUploadedScreenshot: true }) } }) // Check if there are any previous uploads to continue. upload.findPreviousUploads().then(function (previousUploads) { // Found previous uploads so we select the first one. if (previousUploads.length) { upload.resumeFromPreviousUpload(previousUploads[0]) } // Start the upload upload.start() }) } } uploadApi = async (data: Feedback.Data) => { if (data.assign?.length) { data.title = `${data.title} #assign:${data.assign}` if (data.assign) { delete(data['assign']) } } if (this.state.tusUploadedScreenshot) { data.tusUniqIdScreenshot = this.state.tusUniqIdScreenshot } try { const headers = this.props.config.headers || {} const indexedDb = await this.getIndexedDb() if (this.state.screenshotError !== undefined && this.state.screenshotError === 'failed') { data.description += '

!!! SELHALO HTML2IMAGE při tvoření screenshotu !!!

' } const formdata = { ...data, pageData: this.props.config.pageData || {}, project: this.props.config.project || undefined, url: window.location.href, cookies: this.props.allCookies, localStorage: this.getLocalStorage(), indexedDb } const result = await axios.post(this.props.config.url, formdata, { headers, onUploadProgress: progressEvent => { const progress = (progressEvent.loaded / (progressEvent.total || 1)) * 100; this.setState({ uploadProgress: progress }); } }) console.log(result) this.setState({ show: 'success', screenshot: undefined, record: undefined, uploadProgress: undefined, uploadProgressTusScreenshot: undefined, newTaskUrl: result.data.data.freeloLink }) } catch (e) { console.error(e) this.setState({ show: 'error', uploadProgress: undefined, uploadProgressTusScreenshot: undefined }) } } onScreenshot = () => { this.setState({ show: 'screenshot' }) } onScreenshotSuccess = (data: Screenshot.Data) => { this.setState({ show: 'feedback', screenshot: data, screenshotError: undefined }) } onScreenshotError = (error: Error) => { this.setState({ show: 'feedback', screenshot: undefined, screenshotError: 'failed' }) if (error !== undefined) { console.error(error) } } onRecorder = () => { this.setState({ show: 'recorder' }) } getLocalStorage = () => { const data: { [key: string]: any } = {} for (let i: number = 0; i < localStorage.length; i++) { const key = localStorage.key(i) if (key !== null) { data[key] = localStorage.getItem(key) } } return data } getIndexedDb = async () => { const idbs = await indexedDB.databases() const allData: { [key: string]: { [key: string]: any } } = {} await Promise.all( idbs.map(async (dbInfo) => { if (dbInfo.name) { allData[dbInfo.name] = {} const db = await openDB(dbInfo.name) // @ts-ignore for (const name of db.objectStoreNames) { const keys = await db.transaction(name, 'readonly') .objectStore(name) .getAllKeys() allData[dbInfo.name][name] = {} await Promise.all( keys.map(async (key) => { // @ts-ignore allData[dbInfo.name][name][key] = await db.transaction(name, 'readonly') .objectStore(name) .get(key) }) ) } } ; }) ) return allData } render(): ReactElement | null { if (this.state.types === undefined) { return false}> } else { return ( <> this.setState({ show: 'feedback' })} /> this.setState({ show: '', newTaskUrl: undefined })} /> {this.state.show === 'screenshot' && } onError={this.onScreenshotError} > } } onError={(error) => { this.setState({ recorderError: error }) }} > {false && { this.setState({ record: record, show: 'feedback' } ) }} />} {false && this.state.recorderError !== undefined && this.state.show === 'recorder' && { this.setState({ record: record, show: 'feedback' } ) }} /> } ) } } } export default withCookies(App);