diff --git a/assets/js/Components/AboutModal.js b/assets/js/Components/AboutModal.js index 478aefcf..27b45377 100644 --- a/assets/js/Components/AboutModal.js +++ b/assets/js/Components/AboutModal.js @@ -6,38 +6,30 @@ import { CloseButton } from '@instructure/ui-buttons' import AboutPage from './AboutPage' -class AboutModal extends React.Component { - constructor(props) { - super(props) - } - - render() { - return ( - - - - - {this.props.t('label.about')} - - - this.props.handleModal(null)} - /> - - - - - - - - ) - } -} - -export default AboutModal \ No newline at end of file +export default function AboutModal({ t, settings, handleModal }) { + return ( + + + + + {t('label.about')} + + + handleModal(null)} + /> + + + + + + + + ) +} \ No newline at end of file diff --git a/assets/js/Components/AboutPage.js b/assets/js/Components/AboutPage.js index 85474ac5..7802d07e 100644 --- a/assets/js/Components/AboutPage.js +++ b/assets/js/Components/AboutPage.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useState, useEffect } from 'react' import { Heading } from '@instructure/ui-heading' import { View } from '@instructure/ui-view' import { Text } from '@instructure/ui-text' @@ -10,133 +10,127 @@ import * as Html from '../Services/Html' import ReactHtmlParser from 'react-html-parser' import Classes from '../../css/theme-overrides.css' -class AboutPage extends React.Component { +export default function AboutPage({ t, settings }) { + const [expandDetails, setExpandDetails] = useState(false) + const [issues, setIssues] = useState({"error": [], "suggestion": []}) - constructor(props) { - super(props) - - this.state = { - expandDetails: false - } - - this.handleDetailsToggle = this.handleDetailsToggle.bind(this) + const handleDetailsToggle = () => { + setExpandDetails(!expandDetails) } - handleDetailsToggle() { - this.setState({ expandDetails: !this.state.expandDetails }) - } + useEffect(() => { + const suggestionTypes = (settings.suggestionRuleIds != null) ? settings.suggestionRuleIds : '' - render() { - const suggestionTypes = (this.props.settings.suggestionRuleIds != null) ? this.props.settings.suggestionRuleIds : '' - this.issues = { + let currentIssues = { "error": [], "suggestion": [] } issueRuleIds.forEach(issue => { if (suggestionTypes.includes(issue)) { - this.issues.suggestion.push(issue) + currentIssues.suggestion.push(issue) } else { - this.issues.error.push(issue) + currentIssues.error.push(issue) } }) - return ( - - - + setIssues(currentIssues) + }, []) + + return ( + + + + + + {ReactHtmlParser(t('about.description'), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, settings) })} + + + + {t('about.disclaimer_title')} + + {ReactHtmlParser(t('about.disclaimer'), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, settings) })} + + + + + + {ReactHtmlParser(t('about.video_embed'), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, settings) })} + + + {ReactHtmlParser(t('about.video_link'), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, settings) })} + + + - - {ReactHtmlParser(this.props.t('about.description'), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, this.props.settings) })} - + {t('about.resources')} - - {this.props.t('about.disclaimer_title')} - - {ReactHtmlParser(this.props.t('about.disclaimer'), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, this.props.settings) })} - + + + {ReactHtmlParser(t('about.user_guide_link'), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, settings) })} + - - + + + - {ReactHtmlParser(this.props.t('about.video_embed'), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, this.props.settings) })} + {t('about.policies')} - - {ReactHtmlParser(this.props.t('about.video_link'), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, this.props.settings) })} + + {ReactHtmlParser(t('about.youtube_terms'), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, settings) })} + + + {ReactHtmlParser(t('about.google_privacy'), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, settings) })} + + + + + - - - {this.props.t('about.resources')} - - - - {ReactHtmlParser(this.props.t('about.user_guide_link'), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, this.props.settings) })} + {Object.keys(issues).map((issueType) => { + const type = issues[issueType] + return ( + + + + {('error' === issueType) ? : }  + {t(`label.plural.${issueType}`)} +
+ {type.map((rule) => { + let showExample = false + if (!t(`rule.example.${rule}`).includes('rule.example')) { + showExample = true + } + return ( + + {t(`rule.label.${rule}`)} + } + > + + {ReactHtmlParser(t(`rule.desc.${rule}`), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, settings) })} + { + (showExample) && + {ReactHtmlParser(t(`rule.example.${rule}`), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, settings) })} + } + + + ) + })}
-
- - - - {this.props.t('about.policies')} - - - {ReactHtmlParser(this.props.t('about.youtube_terms'), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, this.props.settings) })} - - - {ReactHtmlParser(this.props.t('about.google_privacy'), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, this.props.settings) })} - - - - - - - - {Object.keys(this.issues).map((issueType) => { - const type = this.issues[issueType] - return ( - - - - {('error' === issueType) ? : }  - {this.props.t(`label.plural.${issueType}`)} -
-
- {type.map((rule) => { - if (!this.props.t(`rule.example.${rule}`).includes('rule.example')) { - var showExample = true - } - return ( - - {this.props.t(`rule.label.${rule}`)} - } - > - - {ReactHtmlParser(this.props.t(`rule.desc.${rule}`), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, this.props.settings) })} - { - (showExample) && - {ReactHtmlParser(this.props.t(`rule.example.${rule}`), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, this.props.settings) })} - } - - - ) - })} -
- ) - })} -
-
- - {this.props.t('label.version')} {this.props.settings.versionNumber} - + ) + })} +
- ) - } -} - -export default AboutPage + + {t('label.version')} {settings.versionNumber} + +
+ ) +} \ No newline at end of file diff --git a/assets/js/Components/App.js b/assets/js/Components/App.js index b73b593b..cae4bd27 100644 --- a/assets/js/Components/App.js +++ b/assets/js/Components/App.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useState, useCallback, useEffect } from 'react' import WelcomePage from './WelcomePage' import Header from './Header' import SummaryPage from './SummaryPage' @@ -11,266 +11,245 @@ import MessageTray from './MessageTray' import FilesPage from './FilesPage' import SummaryBar from './SummaryBar' -class App extends React.Component { - constructor(props) { - super(props) - - this.initialReport = props.report - this.appFilters = {} - this.settings = props.settings - this.reportHistory = [] - this.messages = props.messages - this.newReportInterval = 5000 - - this.state = { - report: this.initialReport, - navigation: 'welcome', - modal: null, - syncComplete: false, - hasNewReport: false, - } - - this.handleNavigation = this.handleNavigation.bind(this) - this.handleModal = this.handleModal.bind(this) - this.handleAppFilters = this.handleAppFilters.bind(this) - this.clearMessages = this.clearMessages.bind(this) - this.addMessage = this.addMessage.bind(this) - this.t = this.t.bind(this) - this.handleIssueSave = this.handleIssueSave.bind(this) - this.handleFileSave = this.handleFileSave.bind(this) - this.handleCourseRescan = this.handleCourseRescan.bind(this) - this.handleFullCourseRescan = this.handleFullCourseRescan.bind(this) - this.handleNewReport = this.handleNewReport.bind(this) - this.resizeFrame = this.resizeFrame.bind(this) - } - - render() { - return ( - -
- - {(('welcome' !== this.state.navigation) && ('summary' !== this.state.navigation)) && - +export default function App(initialData) { + + // The initialData object that is passed to the App will generally contain: + // { + // messages: [], + // report: { ... The report from the most recent scan ... }, + // settings: { ... From src/Controller/DashboardController.php => getSettings() ... }, + // } + + const [messages, setMessages] = useState(initialData.messages || []) + const [report, setReport] = useState(initialData.report || null) + const [settings, setSettings] = useState(initialData.settings || null) + + // The reportHistory and newReportInterval variables are not used in the current codebase + // const [reportHistory, setReportHistory] = useState([]) + // const [newReportInterval, setNewReportInterval] = useState(5000) + + const [appFilters, setAppFilters] = useState({}) + const [navigation, setNavigation] = useState('welcome') + const [modal, setModal] = useState(null) + const [syncComplete, setSyncComplete] = useState(false) + const [hasNewReport, setHasNewReport] = useState(false) + const [disableReview, setDisableReview] = useState(false) + + // `t` is used for text/translation. It will return the translated string if it exists + // in the settings.labels object. + const t = useCallback((key) => { + return (settings.labels[key]) ? settings.labels[key] : key + }, [settings.labels]) + + const scanCourse = useCallback(() => { + let api = new Api(settings) + return api.scanCourse(settings.course.id) + }, [settings]) + + const fullRescan = useCallback(() => { + let api = new Api(settings) + return api.fullRescan(settings.course.id) + }, [settings]) + + const handleNewReport = (data) => { + let newReport = report + let newHasNewReport = hasNewReport + let newDisableReview = disableReview + if (data.messages) { + data.messages.forEach((msg) => { + if (msg.visible) { + addMessage(msg) } - - - -
- {('welcome' === this.state.navigation) && - - } - {('summary' === this.state.navigation) && - - } - {('content' === this.state.navigation) && - - } - {('files' === this.state.navigation) && - - } - {('reports' === this.state.navigation) && - - } -
- - {('about' === this.state.modal) && - + if ('msg.no_report_created' === msg.message) { + addMessage(msg) + newReport = null + newDisableReview = true } - - ) - } - - componentDidMount() { - if (this.settings.user && Array.isArray(this.settings.user.roles)) { - if (this.settings.user.roles.includes('ROLE_ADVANCED_USER')) { - if (this.initialReport) { - this.setState({report: this.initialReport, navigation: 'summary'}) + if ("msg.sync.course_inactive" === msg.message) { + newDisableReview = true } - } + }) } - - this.scanCourse() - .then((response) => response.json()) - .then(this.handleNewReport) - - // update iframe height on resize - window.addEventListener("resize", this.resizeFrame); - - this.resizeFrame(); + if (data.data && data.data.id) { + newReport = data.data + newHasNewReport = true + } + setSyncComplete(true) + setHasNewReport(newHasNewReport) + setReport(newReport) + setDisableReview(newDisableReview) } - componentWillUnmount() { - window.removeEventListener('resize', this.resizeFrame); + const handleNavigation = (navigation) => { + console.log('handleNavigation to: ', navigation) + setNavigation(navigation) } - t(key) { - return (this.settings.labels[key]) ? this.settings.labels[key] : key + const handleModal = (modal) => { + setModal(modal) } - scanCourse() { - let api = new Api(this.settings) - return api.scanCourse(this.settings.course.id) + const handleAppFilters = (filters) => { + setAppFilters(filters) } - fullRescan() { - let api = new Api(this.settings) - return api.fullRescan(this.settings.course.id) + const addMessage = (msg) => { + setMessages(prevMessages => [...prevMessages, msg]) } - disableReview = () => { - return this.state.syncComplete && !this.state.disableReview + const clearMessages = () => { + setMessages([]) } - handleCourseRescan() { - if (this.state.hasNewReport) { - this.setState({ hasNewReport: false, syncComplete: false }) - this.scanCourse() - .then((response) => response.json()) - .then(this.handleNewReport) - } - this.forceUpdate() - } + const handleIssueSave = (newIssue, newReport) => { + const oldReport = report + const updatedReport = { ...oldReport, ...newReport } - handleFullCourseRescan() { - if (this.state.hasNewReport) { - this.setState({ hasNewReport: false, syncComplete: false }) - this.fullRescan() - .then((response) => response.json()) - .then(this.handleNewReport) + if (updatedReport && Array.isArray(updatedReport.issues)) { + updatedReport.issues = updatedReport.issues.map((issue) => { + if (issue.id === newIssue.id) return newIssue + const oldIssue = oldReport.issues.find((oldReportIssue) => oldReportIssue.id === issue.id) + return oldIssue !== undefined ? { ...oldIssue, ...issue } : issue + }) } - this.forceUpdate() - } - handleNewReport(data) { - let report = this.state.report - let hasNewReport = this.state.hasNewReport - let disableReview = this.state.disableReview - if (data.messages) { - data.messages.forEach((msg) => { - if (msg.visible) { - this.addMessage(msg) - } - if ('msg.no_report_created' === msg.message) { - this.addMessage(msg) - report = null - // no report, do not do any review actions - disableReview = true - } - if ("msg.sync.course_inactive" === msg.message) { - // course scan failed, issues may be outdated - disableReview = true - } - }); - } - if (data.data && data.data.id) { - report = data.data - hasNewReport = true - } - this.setState({ - syncComplete: true, - hasNewReport, - report, - disableReview, - }) + setReport(updatedReport) } - handleNavigation(navigation) { - this.setState({navigation}) - } + const handleFileSave = (newFile, newReport) => { + let updatedReport = { ...report, ...newReport } - handleModal(modal) { - this.setState({modal}) - } + if (updatedReport && updatedReport.files) { + updatedReport.files[newFile.id] = newFile + } - handleAppFilters = (filters) => { - this.appFilters = filters; + setReport(updatedReport) } - addMessage = (msg) => { - this.messages.push(msg) + const handleCourseRescan = () => { + if (hasNewReport) { + setHasNewReport(false) + setSyncComplete(false) + scanCourse() + .then((response) => response.json()) + .then(handleNewReport) + } } - clearMessages = () => { - this.messages = []; + const handleFullCourseRescan = () => { + if (hasNewReport) { + setHasNewReport(false) + setSyncComplete(false) + fullRescan() + .then((response) => response.json()) + .then(handleNewReport) + } } - handleIssueSave(newIssue, newReport) { - const oldReport = this.state.report; + const resizeFrame = useCallback(() => { + let default_height = document.body.scrollHeight + 50 + default_height = default_height > 1000 ? default_height : 1000 - const report = { ...oldReport, ...newReport }; - - if (report && Array.isArray(report.issues)) { - // Combine backend issues with frontend issue state - report.issues = report.issues.map((issue) => { - if (issue.id === newIssue.id) return newIssue; - const oldIssue = oldReport.issues.find((oldReportIssue) => oldReportIssue.id === issue.id); - return oldIssue !== undefined ? { ...oldIssue, ...issue } : issue; - }); + parent.postMessage(JSON.stringify({ + subject: "lti.frameResize", + height: default_height + }), "*") + }, []) + + useEffect(() => { + if (settings.user && Array.isArray(settings.user.roles)) { + if (settings.user.roles.includes('ROLE_ADVANCED_USER')) { + if (initialData.report) { + setReport(initialData.report) + setNavigation('summary') + } + } } - this.setState({ report }); - } + scanCourse() + .then((response) => response.json()) + .then(handleNewReport) - handleFileSave(newFile, newReport) { - let { report } = this.state - report = { ...report, ...newReport } + window.addEventListener("resize", resizeFrame) + resizeFrame() - if (report && report.files) { - report.files[newFile.id] = newFile + return () => { + window.removeEventListener('resize', resizeFrame) } + }, [settings, initialData.report, scanCourse, resizeFrame]) + + return ( + +
+ + {(('welcome' !== navigation) && ('summary' !== navigation)) && + + } - this.setState({ report }) - } + - // resize containing iframe height - resizeFrame(){ - let default_height = document.body.scrollHeight + 50; - default_height = default_height > 1000 ? default_height : 1000; +
+ {('welcome' === navigation) && + + } + {('summary' === navigation) && + + } + {('content' === navigation) && + + } + {('files' === navigation) && + + } + {('reports' === navigation) && + + } +
- // IE 8 & 9 only support string data, so send objects as string - parent.postMessage(JSON.stringify({ - subject: "lti.frameResize", - height: default_height - }), "*"); - } + {('about' === modal) && + + } + + ) } - -export default App diff --git a/assets/js/Components/ContentPage.js b/assets/js/Components/ContentPage.js index 54124c10..3eb5a14e 100644 --- a/assets/js/Components/ContentPage.js +++ b/assets/js/Components/ContentPage.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Button } from '@instructure/ui-buttons' import { IconCheckLine, IconInfoBorderlessLine, IconNoLine } from '@instructure/ui-icons' import { Billboard } from '@instructure/ui-billboard' @@ -18,136 +18,112 @@ const issueStatusKeys = [ 'resolved' ] -class ContentPage extends React.Component { - constructor(props) { - super(props); - - this.filteredIssues = []; - this.headers = [ - {id: "status", text: '', alignText: "center"}, - { id: "contentTitle", text: this.props.t('label.header.title') }, - { id: "contentType", text: this.props.t('label.header.type') }, - { id: "scanRuleLabel", text: this.props.t('label.issue') }, - {id: "action", text: "", alignText: "end"} - ] - - this.easyRules = issueRuleIds.filter(rule => this.props.settings.easyRuleIds.includes(rule)) - - this.visualRules = issueRuleIds.filter(rule => this.props.settings.visualRuleIds.includes(rule)) - this.auditoryRules = issueRuleIds.filter(rule => this.props.settings.auditoryRuleIds.includes(rule)) - this.cognitiveRules = issueRuleIds.filter(rule => this.props.settings.cognitiveRuleIds.includes(rule)) - this.motorRules = issueRuleIds.filter(rule => this.props.settings.motorRuleIds.includes(rule)) - - this.state = { - activeIssue: null, - trayOpen: false, - modalOpen: false, - activeIndex: -1, - searchTerm: '', - filters: { - contentTypes: [], - issueTypes: [], - issueTitles: [], - issueStatus: ['active'], - issueImpacts: [], - hideUnpublishedContentItems: false, - easyIssues: false, - }, - tableSettings: { - sortBy: 'contentTitle', - ascending: true, - pageNum: 0, - rowsPerPage: (localStorage.getItem('rowsPerPage')) ? localStorage.getItem('rowsPerPage') : '10' - }, - }; - - this.handleTrayToggle = this.handleTrayToggle.bind(this); - this.handleSearchTerm = this.handleSearchTerm.bind(this); - this.handleTableSettings = this.handleTableSettings.bind(this); - this.handleFilter = this.handleFilter.bind(this); - this.handleActiveIssue = this.handleActiveIssue.bind(this); - //this.handleIssueSave = this.handleIssueSave.bind(this) - } - - componentDidMount() { - if (Object.keys(this.props.appFilters).length > 0) { - const newFilters = Object.assign({}, this.resetFilters(), this.props.appFilters); - this.props.handleAppFilters({}); - this.setState({ filters: newFilters }); +export default function ContentPage({ report, setReport, settings, handleIssueSave, appFilters, handleAppFilters, disableReview, t }) { + const filteredIssues = []; + const headers = [ + { id: "status", text: '', alignText: "center" }, + { id: "contentTitle", text: t('label.header.title') }, + { id: "contentType", text: t('label.header.type') }, + { id: "scanRuleLabel", text: t('label.issue') }, + { id: "action", text: "", alignText: "end" } + ] + + const easyRules = issueRuleIds.filter(rule => settings.easyRuleIds.includes(rule)) + const visualRules = issueRuleIds.filter(rule => settings.visualRuleIds.includes(rule)) + const auditoryRules = issueRuleIds.filter(rule => settings.auditoryRuleIds.includes(rule)) + const cognitiveRules = issueRuleIds.filter(rule => settings.cognitiveRuleIds.includes(rule)) + const motorRules = issueRuleIds.filter(rule => settings.motorRuleIds.includes(rule)) + + const [activeIssue, setActiveIssue] = useState(null) + const [trayOpen, setTrayOpen] = useState(false) + const [modalOpen, setModalOpen] = useState(false) + const [activeIndex, setActiveIndex] = useState(-1) + const [searchTerm, setSearchTerm] = useState('') + const [filters, setFilters] = useState({ + contentTypes: [], + issueTypes: [], + issueTitles: [], + issueStatus: ['active'], + issueImpacts: [], + hideUnpublishedContentItems: false, + easyIssues: false, + }) + const [tableSettings, setTableSettings] = useState({ + sortBy: 'contentTitle', + ascending: true, + pageNum: 0, + rowsPerPage: (localStorage.getItem('rowsPerPage')) ? localStorage.getItem('rowsPerPage') : '10' + }) + + useEffect(() => { + if (Object.keys(appFilters).length > 0) { + const newFilters = Object.assign({}, resetFilters(), appFilters); + handleAppFilters({}); + setFilters(newFilters); } - } + }, []) - handleSearchTerm = (e, val) => { - this.setState({searchTerm: val, filteredIssues: [], tableSettings: Object.assign({}, this.state.tableSettings, {pageNum: 0})}); + const handleSearchTerm = (e, val) => { + setSearchTerm(val); + setFilteredIssues([]); + setTableSettings(Object.assign({}, tableSettings, { pageNum: 0 })); } - // Opens the modal with the appropriate form based on the issue passed in - handleReviewClick = (activeIssue) => { - if (!this.props.disableReview) return; - this.setState({ - modalOpen: true, - activeIssue: activeIssue - }) + const handleReviewClick = (activeIssue) => { + if (!disableReview) return; + setModalOpen(true); + setActiveIssue(activeIssue); } - handleCloseButton = () => { - const newReport = { ...this.props.report }; + const handleCloseButton = () => { + const newReport = { ...report }; newReport.issues = newReport.issues.map((issue) => { issue.recentlyResolved = false; issue.recentlyUpdated = false; return issue; }); - - this.setState({ - report: newReport, - modalOpen: false - }); + + setModalOpen(false); + setReport(newReport); } - handleTrayToggle = (e, val) => { - this.setState({trayOpen: !this.state.trayOpen}); + const handleTrayToggle = (e, val) => { + setTrayOpen(!trayOpen); } - handleFilter = (filter) => { - this.setState({ - filters: Object.assign({}, this.state.filters, filter), - tableSettings: { - sortBy: 'scanRuleLabel', - ascending: true, - pageNum: 0, - }, - activeIndex: -1, - }) + const handleFilter = (filter) => { + setFilters(Object.assign({}, filters, filter)); + setTableSettings({ + sortBy: 'scanRuleLabel', + ascending: true, + pageNum: 0, + }); + setActiveIndex(-1); } - handleActiveIssue(newIssue, newIndex) { - this.setState({ - activeIssue: newIssue, - activeIndex: Number(newIndex) - }) + const handleActiveIssue = (newIssue, newIndex = undefined) => { + setActiveIssue(newIssue); + if(newIndex !== undefined) { + setActiveIndex(Number(newIndex)); + } } - handleTableSettings = (setting) => { - this.setState({ - tableSettings: Object.assign({}, this.state.tableSettings, setting) - }); + const handleTableSettings = (setting) => { + setTableSettings(Object.assign({}, tableSettings, setting)); } - getContentById = (contentId) => { - return Object.assign({}, this.props.report.contentItems[contentId]); + const getContentById = (contentId) => { + return Object.assign({}, report.contentItems[contentId]); } - getFilteredContent = () => { - const report = this.props.report; - const filters = Object.assign({}, this.state.filters); - const { sortBy, ascending } = this.state.tableSettings - + const getFilteredContent = () => { let filteredList = []; let issueList = Object.assign({}, report.issues); + const tempFilters = Object.assign({}, filters); // Check for easy issues filter - if(filters.easyIssues && filters.issueTitles.length == 0) { - filters.issueTitles = this.easyRules + if (tempFilters.easyIssues && tempFilters.issueTitles.length == 0) { + tempFilters.issueTitles = easyRules } // Loop through the issues @@ -155,51 +131,51 @@ class ContentPage extends React.Component { let issue = Object.assign({}, value) // Check if we are interested in this issue severity, aka "type" - if (filters.issueTypes.length !== 0 && !filters.issueTypes.includes(issue.type)) { + if (tempFilters.issueTypes.length !== 0 && !tempFilters.issueTypes.includes(issue.type)) { continue; } // Check if we are interested in issues with this rule impact - if (filters.issueImpacts.length !== 0 - && !(filters.issueImpacts.includes('visual') && this.visualRules.includes(issue.scanRuleId)) - && !(filters.issueImpacts.includes('auditory') && this.auditoryRules.includes(issue.scanRuleId)) - && !(filters.issueImpacts.includes('cognitive') && this.cognitiveRules.includes(issue.scanRuleId)) - && !(filters.issueImpacts.includes('motor') && this.motorRules.includes(issue.scanRuleId)) - ) { + if (tempFilters.issueImpacts.length !== 0 + && !(tempFilters.issueImpacts.includes('visual') && visualRules.includes(issue.scanRuleId)) + && !(tempFilters.issueImpacts.includes('auditory') && auditoryRules.includes(issue.scanRuleId)) + && !(tempFilters.issueImpacts.includes('cognitive') && cognitiveRules.includes(issue.scanRuleId)) + && !(tempFilters.issueImpacts.includes('motor') && motorRules.includes(issue.scanRuleId)) + ) { continue; } // Check if we are interested in issues with this rule title - if (filters.issueTitles.length !== 0 && !filters.issueTitles.includes(issue.scanRuleId)) { + if (tempFilters.issueTitles.length !== 0 && !tempFilters.issueTitles.includes(issue.scanRuleId)) { continue; } // Check if we are filtering by issue status if (!issue.recentlyUpdated && !issue.recentlyResolved) { - if (filters.issueStatus.length !== 0 && !filters.issueStatus.includes(issueStatusKeys[issue.status])) { + if (tempFilters.issueStatus.length !== 0 && !tempFilters.issueStatus.includes(issueStatusKeys[issue.status])) { continue; } } // Get information about the content the issue refers to - var contentItem = this.getContentById(issue.contentItemId) + var contentItem = getContentById(issue.contentItemId) // Check if we are showing unpublished content items - if (filters.hideUnpublishedContentItems && !contentItem.status) { + if (tempFilters.hideUnpublishedContentItems && !contentItem.status) { continue; } // Check if we are filtering by content type - if (filters.contentTypes.length !== 0 && !filters.contentTypes.includes(contentItem.contentType)) { + if (tempFilters.contentTypes.length !== 0 && !tempFilters.contentTypes.includes(contentItem.contentType)) { continue; } // Filter by search term if (!issue.keywords) { - issue.keywords = this.createKeywords(issue, contentItem); + issue.keywords = createKeywords(issue, contentItem); } - if (this.state.searchTerm !== '') { - const searchTerms = this.state.searchTerm.toLowerCase().split(' '); + if (searchTerm !== '') { + const searchTerms = searchTerm.toLowerCase().split(' '); if (Array.isArray(searchTerms)) { for (let term of searchTerms) { @@ -213,26 +189,26 @@ class ContentPage extends React.Component { let status if (issue.status == 2) { status = <> - {this.props.t('label.resolved')} + {t('label.resolved')} } else if (issue.status == 1) { status = <> - {this.props.t('label.fixed')} + {t('label.fixed')} } else { if ('error' === issue.type) { status = <> - {this.props.t('label.error')} + {t('label.error')} } else { status = <> - {this.props.t('label.suggestion')} + {t('label.suggestion')} } @@ -243,113 +219,51 @@ class ContentPage extends React.Component { id: issue.id, issue, status, - scanRuleLabel: this.props.t(`rule.label.${issue.scanRuleId}`), - contentType: this.props.t(`content.${contentItem.contentType}`), + scanRuleLabel: t(`rule.label.${issue.scanRuleId}`), + contentType: t(`content.${contentItem.contentType}`), contentTitle: contentItem.title, action: ( ), - onClick: () => this.handleReviewClick(issue), + onClick: () => handleReviewClick(issue), } ); } filteredList.sort((a, b) => { - if (isNaN(a[sortBy]) || isNaN(b[sortBy])) { - return (a[sortBy].toLowerCase() < b[sortBy].toLowerCase()) ? -1 : 1; + if (isNaN(a[tableSettings.sortBy]) || isNaN(b[tableSettings.sortBy])) { + return (a[tableSettings.sortBy].toLowerCase() < b[tableSettings.sortBy].toLowerCase()) ? -1 : 1; } else { - return (Number(a[sortBy]) < Number(b[sortBy])) ? -1 : 1; + return (Number(a[tableSettings.sortBy]) < Number(b[tableSettings.sortBy])) ? -1 : 1; } }); - if (!ascending) { + if (!tableSettings.ascending) { filteredList.reverse(); } return filteredList; } - render() { - const filteredRows = this.getFilteredContent(); - const activeContentItem = (this.state.activeIssue) ? this.getContentById(this.state.activeIssue.contentItemId) : null - - return ( - - this.contentPageForm = node} - handleTableSettings={this.handleTableSettings} - tableSettings={this.state.tableSettings} - /> - - {this.renderFilterTags()} - - - {this.state.trayOpen && } - {this.state.modalOpen && } - - {filteredRows.length === 0 && - } - - ) - } - - createKeywords(issue, contentItem) { + const createKeywords = (issue, contentItem) => { let keywords = []; - keywords.push(this.props.t(`rule.label.${issue.scanRuleId}`).toLowerCase()); - keywords.push(this.props.t(`label.${contentItem.contentType}`).toLowerCase()); + keywords.push(t(`rule.label.${issue.scanRuleId}`).toLowerCase()); + keywords.push(t(`label.${contentItem.contentType}`).toLowerCase()); keywords.push(contentItem.title.toLowerCase()); return keywords.join(' '); } - resetFilters() { + const resetFilters = () => { return { contentTypes: [], hideUnpublishedContentItems: false, @@ -360,100 +274,148 @@ class ContentPage extends React.Component { }; } - renderFilterTags() { + const renderFilterTags = () => { let tags = []; - for (const contentType of this.state.filters.contentTypes) { + for (const contentType of filters.contentTypes) { const id = `contentTypes||${contentType}`; - tags.push({ id: id, label: this.props.t(`content.plural.${contentType}`)}); + tags.push({ id: id, label: t(`content.plural.${contentType}`)}); } - for (const issueType of this.state.filters.issueTypes) { + for (const issueType of filters.issueTypes) { const id = `issueTypes||${issueType}` - tags.push({ id: id, label: this.props.t(`label.plural.${issueType}`)}); + tags.push({ id: id, label: t(`label.plural.${issueType}`)}); } - for (const issueImpact of this.state.filters.issueImpacts) { + for (const issueImpact of filters.issueImpacts) { const id = `issueImpacts||${issueImpact}` - tags.push({ id: id, label: this.props.t(`label.filter.${issueImpact}`)}); + tags.push({ id: id, label: t(`label.filter.${issueImpact}`)}); } - for (const ruleId of this.state.filters.issueTitles) { + for (const ruleId of filters.issueTitles) { const id = `issueTitles||${ruleId}` - tags.push({ id: id, label: this.props.t(`rule.label.${ruleId}`) }); + tags.push({ id: id, label: t(`rule.label.${ruleId}`) }); } - for (const statusVal of this.state.filters.issueStatus) { + for (const statusVal of filters.issueStatus) { const id = `issueStatus||${statusVal}` - tags.push({ id: id, label: this.props.t(`label.filter.${statusVal}`) }); + tags.push({ id: id, label: t(`label.filter.${statusVal}`) }); } - if (this.state.filters.hideUnpublishedContentItems) { - tags.push({ id: `hideUnpublishedContentItems||true`, label: this.props.t(`label.hide_unpublished`) }); + if (filters.hideUnpublishedContentItems) { + tags.push({ id: `hideUnpublishedContentItems||true`, label: t(`label.hide_unpublished`) }); } - if (this.state.filters.easyIssues) { - tags.push({ id: `easyIssues||true`, label: this.props.t(`label.show_easy_issues`) }); + + if (filters.easyIssues) { + tags.push({ id: `easyIssues||true`, label: t(`label.show_easy_issues`) }); } return tags.map((tag, i) => { return ( - this.handleTagClick(tag.id, e)} - key={i} - elementRef={(node) => this[`tag${i}`] = node} - /> - )}); + handleTagClick(tag.id, e)} + key={i} + /> + ) + }); } - handleTagClick(tagId, e) { + const handleTagClick = (tagId, e) => { let [filterType, filterId] = tagId.split('||'); let results = null; let index = 0 switch (filterType) { case 'contentTypes': - index += this.state.filters.contentTypes.findIndex((val) => filterId == val) - results = this.state.filters.contentTypes.filter((val) => filterId !== val); + index += filters.contentTypes.findIndex((val) => filterId == val) + results = filters.contentTypes.filter((val) => filterId !== val); break; case 'issueTypes': - index = this.state.filters.contentTypes.length - index += this.state.filters.issueTypes.findIndex((val) => filterId == val) - results = this.state.filters.issueTypes.filter((val) => filterId !== val); + index = filters.contentTypes.length + index += filters.issueTypes.findIndex((val) => filterId == val) + results = filters.issueTypes.filter((val) => filterId !== val); break; case 'issueTitles': - index = this.state.filters.contentTypes.length + this.state.filters.issueTypes.length - index += this.state.filters.issueTitles.findIndex((val) => filterId == val) - results = this.state.filters.issueTitles.filter((val) => filterId !== val); + index = filters.contentTypes.length + filters.issueTypes.length + index += filters.issueTitles.findIndex((val) => filterId == val) + results = filters.issueTitles.filter((val) => filterId !== val); break; case 'issueStatus': - index = this.state.filters.contentTypes.length + this.state.filters.issueTypes.length + this.state.filters.issueTitles.length - index += this.state.filters.issueStatus.findIndex((val) => filterId == val) - results = this.state.filters.issueStatus.filter((val) => filterId != val); + index = filters.contentTypes.length + filters.issueTypes.length + filters.issueTitles.length + index += filters.issueStatus.findIndex((val) => filterId == val) + results = filters.issueStatus.filter((val) => filterId != val); break; case 'issueImpacts': - index = this.state.filters.contentTypes.length + this.state.filters.issueTypes.length + this.state.filters.issueTitles.length + this.state.filters.issueStatus.length - index += this.state.filters.issueImpacts.findIndex((val) => filterId == val) - results = this.state.filters.issueImpacts.filter((val) => filterId != val); + index = filters.contentTypes.length + filters.issueTypes.length + filters.issueTitles.length + filters.issueStatus.length + index += filters.issueImpacts.findIndex((val) => filterId == val) + results = filters.issueImpacts.filter((val) => filterId != val); break; case 'hideUnpublishedContentItems': - index = this.state.filters.contentTypes.length + this.state.filters.issueTypes.length + this.state.filters.issueTitles.length + this.state.filters.issueStatus.length + this.state.filters.issueImpacts.length + index = filters.contentTypes.length + filters.issueTypes.length + filters.issueTitles.length + filters.issueStatus.length + filters.issueImpacts.length results = false; break; } - this.handleFilter({ [filterType]: results }); - if (index - 1 >= 0) { - setTimeout(() => { - this[`tag${index - 1}`].focus() - }) - } else { - setTimeout(() => { - this.contentPageForm.focus() - }) - } + handleFilter({ [filterType]: results }); } -} -export default ContentPage; + const filteredRows = getFilteredContent() + + return ( + + + + {renderFilterTags()} + + + {trayOpen && } + {modalOpen && } + + {filteredRows.length === 0 && + } + + ) +} \ No newline at end of file diff --git a/assets/js/Components/ContentPageForm.js b/assets/js/Components/ContentPageForm.js index b06a78a5..684afb15 100644 --- a/assets/js/Components/ContentPageForm.js +++ b/assets/js/Components/ContentPageForm.js @@ -6,73 +6,59 @@ import { Flex } from '@instructure/ui-flex' import { ScreenReaderContent } from '@instructure/ui-a11y-content' import { SimpleSelect } from '@instructure/ui-simple-select' -class ContentPageForm extends React.Component { +export default function ContentPageForm({ t, searchTerm, handleSearchTerm, handleTableSettings, handleTrayToggle, tableSettings }) { + const options = ['10', '25', '50'] - constructor(props) { - super(props); - - } - - focus = () => { - this.filterButton.focus() - } - - render() { - const options = ['10', '25', '50']; - return ( - - - - - Search Term} - renderBeforeInput={} - placeholder={this.props.t('placeholder.keyword')} - onChange={this.props.handleSearchTerm} - value={this.props.searchTerm} - /> - - - { - this.props.handleTableSettings({ - rowsPerPage: value - }) - localStorage.setItem('rowsPerPage', value) - }} - width="13vw" - size="small" - > - {options.map((opt, index) => ( - - { opt } - - ))} - - - - - - {this.props.handleTrayToggle && - } - - - ); - } -} - -export default ContentPageForm; \ No newline at end of file + {options.map((opt, index) => ( + + { opt } + + ))} + + + + + + {handleTrayToggle && + } + + + ) +} \ No newline at end of file diff --git a/assets/js/Components/ContentTrayForm.js b/assets/js/Components/ContentTrayForm.js index 0d0f0688..8124ce20 100644 --- a/assets/js/Components/ContentTrayForm.js +++ b/assets/js/Components/ContentTrayForm.js @@ -7,181 +7,164 @@ import { View } from '@instructure/ui-view' import { Heading } from '@instructure/ui-heading' import IssueRuleSelect from './IssueRuleSelect'; -const issueTypes = [ - 'error', - 'suggestion' -] - -const issueStatus = [ - 'active', - 'fixed', - 'resolved' -] - -const issueImpacts = [ - 'visual', - 'auditory', - 'cognitive', - 'motor' -] - -class ContentTrayForm extends React.Component { - - constructor(props) { - super(props); - - this.handleContentTypeChange = this.handleContentTypeChange.bind(this) - this.handleIssueStatusChange = this.handleIssueStatusChange.bind(this) - this.handleUnpublishedContent = this.handleUnpublishedContent.bind(this) - this.handleIssueTypeChange = this.handleIssueTypeChange.bind(this) - this.handleIssueTitleChange = this.handleIssueTitleChange.bind(this) - this.handleIssueImpactChange = this.handleIssueImpactChange.bind(this) - this.handleEasyIssues = this.handleEasyIssues.bind(this) +export default function ContentTrayForm({ t, trayOpen, filters, handleTrayToggle, handleFilter, settings, report }) { + + const issueTypes = [ + 'error', + 'suggestion' + ] + + const issueStatus = [ + 'active', + 'fixed', + 'resolved' + ] + + const issueImpacts = [ + 'visual', + 'auditory', + 'cognitive', + 'motor' + ] + + const renderContentTypeCheckboxes = () => { + return settings.contentTypes.map((type) => ) } - - render() { - - return ( - - - - - {this.props.t('label.plural.filter')} - - - - - - - - {this.renderContentTypeCheckboxes()} - - - - - {this.renderIssueStatusCheckboxes()} - - - - - {this.renderIssueTypeCheckboxes()} - - - - - {this.renderIssueImpactCheckboxes()} - - - - - - - - - - - - - - ); + + const renderIssueStatusCheckboxes = () => { + return issueStatus.map((status) => ) } - - renderContentTypeCheckboxes() { - return this.props.settings.contentTypes.map((type) => ); + + const renderIssueTypeCheckboxes = () => { + return issueTypes.map((type) => ) } - - renderIssueStatusCheckboxes() { - return issueStatus.map((status) => ) + + const renderIssueImpactCheckboxes = () => { + return issueImpacts.map((impact) => ) } - - renderIssueTypeCheckboxes() { - return issueTypes.map((type) => ); + + const handleContentTypeChange = (values) => { + handleFilter({contentTypes: values}) } - - renderIssueImpactCheckboxes() { - return issueImpacts.map((impact) => ); + + const handleIssueStatusChange = (values) => { + handleFilter({issueStatus: values}) } - - handleContentTypeChange(values) { - this.props.handleFilter({contentTypes: values}); + + const handleIssueTypeChange = (values) => { + handleFilter({issueTypes: values}) } - - handleIssueStatusChange(values) { - this.props.handleFilter({issueStatus: values}) + + const handleIssueTitleChange = (values) => { + handleFilter({issueTitles: values}) } - - handleIssueTypeChange(values) { - this.props.handleFilter({issueTypes: values}); + + const handleIssueImpactChange = (values) => { + handleFilter({issueImpacts: values}) } - - handleIssueTitleChange(values) { - this.props.handleFilter({issueTitles: values}); + + const handleUnpublishedContent = () => { + handleFilter({ hideUnpublishedContentItems: !filters.hideUnpublishedContentItems }); } - - handleIssueImpactChange(values) { - this.props.handleFilter({issueImpacts: values}); + + const handleEasyIssues = () => { + handleFilter({ easyIssues: !filters.easyIssues }); } - - handleUnpublishedContent(e) { - this.props.handleFilter({ hideUnpublishedContentItems: !this.props.filters.hideUnpublishedContentItems }); - } - - handleEasyIssues(e) { - this.props.handleFilter({ easyIssues: !this.props.filters.easyIssues }); - } - - getRuleOptions() { + + const getRuleOptions = () => { let ruleOptions = {}; - - Object.values(this.props.report.issues).forEach( + + Object.values(report.issues).forEach( (issue) => { ruleOptions[issue.scanRuleId] = { id: issue.scanRuleId, - label: this.props.t(`rule.label.${issue.scanRuleId}`) + label: t(`rule.label.${issue.scanRuleId}`) }; } ); - + return Object.values(ruleOptions).sort((a, b) => (a.label.toLowerCase() < b.label.toLowerCase()) ? -1 : 1); } -} -export default ContentTrayForm; + return ( + + + + + {t('label.plural.filter')} + + + + + + + + {renderContentTypeCheckboxes()} + + + + + {renderIssueStatusCheckboxes()} + + + + + {renderIssueTypeCheckboxes()} + + + + + {renderIssueImpactCheckboxes()} + + + + + + + + + + + + + + ) +} \ No newline at end of file diff --git a/assets/js/Components/FilesPage.js b/assets/js/Components/FilesPage.js index 70807e2a..3bd42f14 100644 --- a/assets/js/Components/FilesPage.js +++ b/assets/js/Components/FilesPage.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Button } from '@instructure/ui-buttons' import { IconCheckLine, IconEyeLine } from '@instructure/ui-icons' import SortableTable from './SortableTable' @@ -17,97 +17,72 @@ const fileTypes = [ 'xls', ] -class FilesPage extends React.Component { - constructor(props) { - super(props); - - this.headers = [ - {id: "status", text: '', alignText: "center"}, - {id: "fileName", text: this.props.t('label.file_name')}, - {id: "fileType", text: this.props.t('label.file_type')}, - {id: "fileSize", text: this.props.t('label.file_size'), format: this.formatFileSize}, - {id: "updated", text: this.props.t('label.file_updated'), format: this.formatDate}, - {id: "action", text: "", alignText: "end"} - ]; - - this.state = { - activeFile: null, - activeIndex: -1, - trayOpen: false, - modalOpen: false, - searchTerm: '', - filters: { - fileTypes: [], - hideReviewed: true - }, - tableSettings: { - sortBy: 'fileName', - ascending: true, - pageNum: 0, - rowsPerPage: (localStorage.getItem('rowsPerPage')) ? localStorage.getItem('rowsPerPage') : '10' - } - } - - this.handleTrayToggle = this.handleTrayToggle.bind(this) - this.handleSearchTerm = this.handleSearchTerm.bind(this) - this.handleTableSettings = this.handleTableSettings.bind(this) - this.handleFilter = this.handleFilter.bind(this) - this.handleReviewClick = this.handleReviewClick.bind(this) - this.handleActiveFile = this.handleActiveFile.bind(this) - } - - componentDidMount() { - } - - handleSearchTerm = (e, val) => { - this.setState({searchTerm: val, tableSettings: Object.assign({}, this.state.tableSettings, {pageNum: 0})}); +export default function FilesPage({ t, report, settings, handleFileSave }) { + const headers = [ + {id: "status", text: '', alignText: "center"}, + {id: "fileName", text: t('label.file_name')}, + {id: "fileType", text: t('label.file_type')}, + {id: "fileSize", text: t('label.file_size'), format: formatFileSize}, + {id: "updated", text: t('label.file_updated'), format: formatDate}, + {id: "action", text: "", alignText: "end"} + ]; + + const [activeFile, setActiveFile] = useState(null) + const [activeIndex, setActiveIndex] = useState(-1) + const [filteredFiles, setFilteredFiles] = useState([]) + const [trayOpen, setTrayOpen] = useState(false) + const [modalOpen, setModalOpen] = useState(false) + const [searchTerm, setSearchTerm] = useState('') + const [filters, setFilters] = useState({ + fileTypes: [], + hideReviewed: true + }) + const [tableSettings, setTableSettings] = useState({ + sortBy: 'fileName', + ascending: true, + pageNum: 0, + rowsPerPage: (localStorage.getItem('rowsPerPage')) ? localStorage.getItem('rowsPerPage') : '10' + }) + + const handleSearchTerm = (e, val) => { + setSearchTerm(val) + setTableSettings({...tableSettings, pageNum: 0}) } - handleTrayToggle = (e, val) => { - this.setState({trayOpen: !this.state.trayOpen}); + const handleTrayToggle = (e, val) => { + setTrayOpen(!trayOpen) } - handleFilter = (filter) => { - this.setState({ - filters: Object.assign({}, this.state.filters, filter), - tableSettings: { - sortBy: 'fileName', - ascending: true, - pageNum: 0, - } + const handleFilter = (filter) => { + setFilters({...filters, ...filter}) + setTableSettings({ + sortBy: 'fileName', + ascending: true, + pageNum: 0, + rowsPerPage: tableSettings.rowsPerPage }) } - handleTableSettings = (setting) => { - this.setState({ - tableSettings: Object.assign({}, this.state.tableSettings, setting) - }); + const handleTableSettings = (setting) => { + setTableSettings({...tableSettings, ...setting}) } - handleActiveFile(newFile, newIndex) { - this.setState({ - activeFile: newFile, - activeIndex: Number(newIndex) - }) + const handleActiveFile = (newFile, newIndex) => { + setActiveFile(newFile) + setActiveIndex(Number(newIndex)) } - handleReviewClick(activeFile) { - this.setState({ - modalOpen: true, - activeFile: activeFile - }) + const handleReviewClick = (activeFile) => { + setModalOpen(true) + setActiveFile(activeFile) } - handleCloseButton = () => { - this.setState({ - modalOpen: false - }) + const handleCloseButton = () => { + setModalOpen(false) } - getFilteredFiles = () => { - const report = this.props.report; - const filters = this.state.filters; - const { sortBy, ascending } = this.state.tableSettings + const getFilteredFiles = () => { + const { sortBy, ascending } = tableSettings let filteredList = []; let fileList = Object.assign({}, report.files); @@ -126,8 +101,8 @@ class FilesPage extends React.Component { } // Filter by search term - if (this.state.searchTerm !== '') { - const searchTerms = this.state.searchTerm.toLowerCase().split(' '); + if (searchTerm !== '') { + const searchTerms = searchTerm.toLowerCase().split(' '); if (Array.isArray(searchTerms)) { for (let term of searchTerms) { @@ -141,12 +116,12 @@ class FilesPage extends React.Component { let status if (file.reviewed) { status = <> - {this.props.t('label.file.reviewed')} + {t('label.file.reviewed')} } else { status = <> - {this.props.t('label.file.needs_review')} + {t('label.file.needs_review')} } @@ -162,14 +137,13 @@ class FilesPage extends React.Component { updated: file.updated, action: , - onClick: () => this.handleReviewClick(file), + onClick={() => handleReviewClick(file)} + textAlign="center" >{t('label.review')}, + onClick: () => handleReviewClick(file), } ); } - filteredList.sort((a, b) => { if (isNaN(a[sortBy]) || isNaN(b[sortBy])) { return (a[sortBy].toLowerCase() < b[sortBy].toLowerCase()) ? -1 : 1; @@ -185,127 +159,67 @@ class FilesPage extends React.Component { return filteredList; } - - render() { - const filteredFiles = this.getFilteredFiles() - const report = this.props.report - - return ( - - this.filesPageForm = node} - handleTableSettings={this.handleTableSettings} - tableSettings={this.state.tableSettings} - /> - - {this.renderFilterTags()} - - - {this.state.trayOpen && } - {this.state.modalOpen && - } - - {filteredFiles.length === 0 && - } - - ) - } - - resetFilters() { + + const resetFilters = () => { return { fileTypes: [], hideReviewed: false, }; } - renderFilterTags() { + const renderFilterTags = () => { let tags = []; - for (const fileType of this.state.filters.fileTypes) { + for (const fileType of filters.fileTypes) { const id = `fileTypes||${fileType}`; - tags.push({ id: id, label: this.props.t(`label.mime.${fileType}`) }); + tags.push({ id: id, label: t(`label.mime.${fileType}`) }); } - if (this.state.filters.hideReviewed) { - tags.push({ id: `hideReviewed||true`, label: this.props.t(`label.hide_reviewed`)}); + if (filters.hideReviewed) { + tags.push({ id: `hideReviewed||true`, label: t(`label.hide_reviewed`)}); } return tags.map((tag, i) => ( this.handleTagClick(tag.id, e)} + onClick={(e) => handleTagClick(tag.id, e)} key={i} - elementRef={(node) => this[`tag${i}`] = node} + // elementRef={(node) => this[`tag${i}`] = node} + elementRef={() => { React.createRef() }} /> )); } - handleTagClick(tagId, e) { + const handleTagClick = (tagId, e) => { let [filterType, filterId] = tagId.split('||'); let results = null; let index = 0 switch (filterType) { case 'fileTypes': - index += this.state.filters.fileTypes.findIndex((val) => filterId == val) - results = this.state.filters.fileTypes.filter((val) => filterId !== val); + index += filters.fileTypes.findIndex((val) => filterId == val) + results = filters.fileTypes.filter((val) => filterId !== val); break; case 'hideReviewed': - index = this.state.filters.fileTypes.length + index = filters.fileTypes.length results = false; break; } - this.handleFilter({ [filterType]: results }); + handleFilter({ [filterType]: results }); if (index - 1 >= 0) { setTimeout(() => { this[`tag${index - 1}`].focus() }) } else { setTimeout(() => { - this.filesPageForm.focus() + filesPageForm.focus() }) } } - formatFileSize(size) { + const formatFileSize = (size) => { if (!size) { return 'N/A' } @@ -321,11 +235,69 @@ class FilesPage extends React.Component { return size; } - formatDate(date) { + const formatDate = (date) => { let parts = date.split('T') return parts[0] } -} -export default FilesPage; + useEffect(() => { + setFilteredFiles(getFilteredFiles()) + }, [report, filters, searchTerm, tableSettings]) + + return ( + + filesPageForm = node} + handleTableSettings={handleTableSettings} + tableSettings={tableSettings} + /> + + {renderFilterTags()} + + + {trayOpen && } + {modalOpen && + } + + {filteredFiles.length === 0 && + } + + ) +} \ No newline at end of file diff --git a/assets/js/Components/FilesPageForm.js b/assets/js/Components/FilesPageForm.js index 3abd9a3f..8e3b8c4b 100644 --- a/assets/js/Components/FilesPageForm.js +++ b/assets/js/Components/FilesPageForm.js @@ -6,72 +6,59 @@ import { Flex } from '@instructure/ui-flex' import { ScreenReaderContent } from '@instructure/ui-a11y-content' import { SimpleSelect } from '@instructure/ui-simple-select' -class FilesPageForm extends React.Component { +export default function FilesPageForm({t, searchTerm, handleSearchTerm, handleTableSettings, tableSettings, handleTrayToggle}) { - constructor(props) { - super(props); + const options = ['10', '25', '50']; - } - - focus = () => { - this.buttonRef.focus() - } - - render() { - const options = ['10', '25', '50']; - return ( - - - - - Search Term} - renderBeforeInput={} - placeholder={this.props.t('placeholder.keyword')} - onChange={this.props.handleSearchTerm} - value={this.props.searchTerm} - /> - - - { - this.props.handleTableSettings({ - rowsPerPage: value - }) - localStorage.setItem('rowsPerPage', value) - }} - width="13vw" - size="small" - > - {options.map((opt, index) => ( - - { opt } - - ))} - - - - - - - - - ); - } -} - -export default FilesPageForm; \ No newline at end of file + return ( + + + + + Search Term} + renderBeforeInput={} + placeholder={t('placeholder.keyword')} + onChange={handleSearchTerm} + value={searchTerm} + /> + + + { + handleTableSettings({ + rowsPerPage: value + }) + localStorage.setItem('rowsPerPage', value) + }} + width="13vw" + size="small" + > + {options.map((opt, index) => ( + + { opt } + + ))} + + + + + + + + + ); +} \ No newline at end of file diff --git a/assets/js/Components/FilesTrayForm.js b/assets/js/Components/FilesTrayForm.js index e0f170d5..df0b97b3 100644 --- a/assets/js/Components/FilesTrayForm.js +++ b/assets/js/Components/FilesTrayForm.js @@ -6,82 +6,69 @@ import { Tray } from '@instructure/ui-tray' import { View } from '@instructure/ui-view' import { Heading } from '@instructure/ui-heading' -class FilesTrayForm extends React.Component { +export default function FilesTrayForm({t, trayOpen, handleTrayToggle, filters, handleFilter, fileTypes}) { - constructor(props) { - super(props); - - this.handleFileTypeChange = this.handleFileTypeChange.bind(this); - this.handleHideReviewed = this.handleHideReviewed.bind(this); + const renderFileTypeCheckboxes = () => { + return fileTypes.map((type) => ); } - render() { - - return ( - - - - - {this.props.t('label.plural.filter')} - - - - - - - - {this.renderFileTypeCheckboxes()} - - - - - - - - - - - ); + const handleFileTypeChange = (values) => { + handleFilter({fileTypes: values}) } - renderFileTypeCheckboxes() { - return this.props.fileTypes.map((type) => ); + const handleUnpublishedFiles = (e) => { + handleFilter({ hideUnpublishedFiles: !filters.hideUnpublishedFiles }) } - handleFileTypeChange(values) { - this.props.handleFilter({fileTypes: values}); - } - - handleUnpublishedFiles(e) { - this.props.handleFilter({ hideUnpublishedFiles: !this.props.filters.hideUnpublishedFiles }); + const handleHideReviewed = (e) => { + handleFilter({ hideReviewed: !filters.hideReviewed }) } - handleHideReviewed(e) { - this.props.handleFilter({ hideReviewed: !this.props.filters.hideReviewed }); - } - -} - -export default FilesTrayForm; \ No newline at end of file + return ( + + + + + {t('label.plural.filter')} + + + + + + + + {renderFileTypeCheckboxes()} + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/assets/js/Components/Header.js b/assets/js/Components/Header.js index ff5f492c..aa6d9edb 100644 --- a/assets/js/Components/Header.js +++ b/assets/js/Components/Header.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useState, useEffect } from 'react' import Logo from '../../mediaAssets/udoit_logo.svg' import Classes from '../../css/header.css' import { View } from '@instructure/ui-view' @@ -8,73 +8,62 @@ import { Menu } from '@instructure/ui-menu' import { AppNav } from '@instructure/ui-navigation' import Api from '../Services/Api' -class Header extends React.Component { +export default function Header({ t, settings, hasNewReport, navigation, handleNavigation, handleCourseRescan, handleFullCourseRescan, handleModal }) { + const [pdfUrl, setPdfUrl] = useState('') - constructor(props) { - super(props); - - this.state = { - showPopover: false - } - - this.getPdfUrl = this.getPdfUrl.bind(this) + const getPdfUrl = () => { + let api = new Api(settings) + return api.getPdfUrl() } - render() { - const pdfUrl = this.getPdfUrl() + useEffect(() => { + setPdfUrl(getPdfUrl()) + }, []) - return ( -
- - UDOIT - - } - visibleItemsCount={3} - renderAfterItems={ - } - > - this.props.handleModal('about')}>{this.props.t('menu.about')} - this.props.handleNavigation('reports')}>{this.props.t('menu.reports')} - {/* this.handleMoreNav('settings')}>{this.props.t('menu.settings')} */} - - {this.props.t('menu.scan_course')} - {this.props.t('menu.full_rescan')} - - {this.props.t('menu.download_pdf')} - - } + return ( +
+ + UDOIT + + } + visibleItemsCount={3} + renderAfterItems={ + } > - this.props.handleNavigation('summary')} /> - this.props.handleNavigation('content')} /> - this.props.handleNavigation('files')} /> + handleModal('about')}>{t('menu.about')} + handleNavigation('reports')}>{t('menu.reports')} - -
- ) - } - - getPdfUrl() { - let api = new Api(this.props.settings) - return api.getPdfUrl() - } -} + + {t('menu.scan_course')} + {t('menu.full_rescan')} + + {t('menu.download_pdf')} + + } + > + handleNavigation('summary')} /> + handleNavigation('content')} /> + handleNavigation('files')} /> -export default Header +
+
+ ) +} \ No newline at end of file diff --git a/assets/js/Components/IssueRuleSelect.js b/assets/js/Components/IssueRuleSelect.js index 4774e89f..bed30803 100644 --- a/assets/js/Components/IssueRuleSelect.js +++ b/assets/js/Components/IssueRuleSelect.js @@ -1,242 +1,206 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Select } from '@instructure/ui-select' import { Tag } from '@instructure/ui-tag' import { Alert } from '@instructure/ui-alerts' import { View } from '@instructure/ui-view' -class IssueRuleSelect extends React.Component { - constructor(props) { - super(props); +export default function IssueRuleSelect({ t, options, issueTitles, handleIssueTitleChange }) { - this.state = { - isShowingOptions: false, - inputValue: '', - highlightedOptionId: null, - selectedOptionIds: this.props.issueTitles, - filteredOptions: this.props.options, - } - } - - getOptionById (queryId) { - return this.props.options.find(({ id }) => id === queryId) + const [ isShowingOptions, setIsShowingOptions ] = useState(false) + const [ inputValue, setInputValue ] = useState('') + const [ highlightedOptionId, setHighlightedOptionId ] = useState(null) + const [ selectedOptionIds, setSelectedOptionIds ] = useState(issueTitles) + const [ filteredOptions, setFilteredOptions ] = useState(options) + const [ announcement, setAnnouncement ] = useState('') + + const getOptionById = (queryId) => { + return options.find(({ id }) => id === queryId) } - getOptionsChangedMessage (newOptions) { - let message = newOptions.length !== this.state.filteredOptions.length - ? `${newOptions.length} ${this.props.t('options.available')}.` // options changed, announce new total + const getOptionsChangedMessage = (newOptions) => { + let message = newOptions.length !== filteredOptions.length + ? `${newOptions.length} ${t('options.available')}.` // options changed, announce new total : null // options haven't changed, don't announce if (message && newOptions.length > 0) { // options still available - if (this.state.highlightedOptionId !== newOptions[0].id) { + if (highlightedOptionId !== newOptions[0].id) { // highlighted option hasn't been announced - const option = this.getOptionById(newOptions[0].id).label + const option = getOptionById(newOptions[0].id).label message = `${option}. ${message}` } } return message } - filterOptions = (value) => { - const { selectedOptionIds } = this.state - return this.props.options.filter(option => ( + const filterOptions = (value) => { + return options.filter(option => ( selectedOptionIds.indexOf(option.id) === -1 // ignore selected options removed from list && option.label.toLowerCase().includes(value.toLowerCase()) )) } - matchValue() { - const { - filteredOptions, - inputValue, - highlightedOptionId, - selectedOptionIds - } = this.state + const matchValue = () => { // an option matching user input exists if (filteredOptions.length === 1) { const onlyOption = filteredOptions[0] // automatically select the matching option if (onlyOption.label.toLowerCase() === inputValue.toLowerCase()) { - return { - inputValue: '', - selectedOptionIds: [...selectedOptionIds, onlyOption.id], - filteredOptions: this.filterOptions('') - } + setInputValue('') + setSelectedOptionIds([...selectedOptionIds, onlyOption.id]) + setFilteredOptions(filterOptions('')) } } // input value is from highlighted option, not user input // clear input, reset options if (highlightedOptionId) { - if (inputValue === this.getOptionById(highlightedOptionId).label) { - return { - inputValue: '', - filteredOptions: this.filterOptions('') - } + if (inputValue === getOptionById(highlightedOptionId).label) { + setInputValue('') + setFilteredOptions(filterOptions('')) } } } - handleShowOptions = (event) => { - this.setState({ isShowingOptions: true }) + const handleShowOptions = () => { + setIsShowingOptions(true) } - handleHideOptions = (event) => { - this.setState({ - isShowingOptions: false, - ...this.matchValue() - }) + const handleHideOptions = () => { + setIsShowingOptions(false) + matchValue() } - handleBlur = (event) => { - this.setState({ - highlightedOptionId: null - }) + const handleBlur = () => { + setHighlightedOptionId(null) } - handleHighlightOption = (event, { id }) => { + const handleHighlightOption = (event, { id }) => { event.persist() - const option = this.getOptionById(id) + const option = getOptionById(id) if (!option) return // prevent highlighting empty option - this.setState((state) => ({ - highlightedOptionId: id, - inputValue: event.type === 'keydown' ? option.label : state.inputValue, - announcement: option.label - })) + setHighlightedOptionId(id) + setInputValue(event.type === 'keydown' ? option.label : inputValue) + + setHighlightedOptionId(id) + setInputValue(event.type === 'keydown' ? option.label : inputValue) + setAnnouncement(option.label) } - handleSelectOption = (event, { id }) => { - const option = this.getOptionById(id) + const handleSelectOption = (event, { id }) => { + const option = getOptionById(id) if (!option) return // prevent selecting of empty option - this.setState((state) => ({ - selectedOptionIds: [...state.selectedOptionIds, id], - highlightedOptionId: null, - filteredOptions: this.filterOptions(''), - inputValue: '', - isShowingOptions: false, - announcement: `${option.label} ${this.props.t('label.selected.list_collapsed')}.` - })) - - this.props.handleIssueTitleChange([...this.state.selectedOptionIds, id]) + setSelectedOptionIds([...selectedOptionIds, id]) + setHighlightedOptionId(null) + setFilteredOptions(filterOptions('')) + setInputValue('') + setIsShowingOptions(false) + setAnnouncement(`${option.label} ${t('label.selected.list_collapsed')}.`) + + handleIssueTitleChange([...selectedOptionIds, id]) } - handleInputChange = (event) => { + const handleInputChange = (event) => { const value = event.target.value - const newOptions = this.filterOptions(value) - this.setState({ - inputValue: value, - filteredOptions: newOptions, - highlightedOptionId: newOptions.length > 0 ? newOptions[0].id : null, - isShowingOptions: true, - announcement: this.getOptionsChangedMessage(newOptions) - }) + const newOptions = filterOptions(value) + + setInputValue(value) + setFilteredOptions(newOptions) + setHighlightedOptionId(newOptions.length > 0 ? newOptions[0].id : null) + setIsShowingOptions(true) + setAnnouncement(getOptionsChangedMessage(newOptions)) } - handleKeyDown = (event) => { - const { selectedOptionId, inputValue } = this.state + const handleKeyDown = (event) => { + if (event.keyCode === 8) { // when backspace key is pressed if (inputValue === '' && selectedOptionId.length > 0) { // remove last selected option, if input has no entered text - this.setState((state) => ({ - highlightedOptionId: null, - selectedOptionIds: state.selectedOptionIds.slice(0, -1) - })) + setHighlightedOptionId(null) + setSelectedOptionIds(selectedOptionIds.slice(0, -1)) } } } + // remove a selected option tag - dismissTag (e, tag) { + const dismissTag = (e, tag) => { // prevent closing of list e.stopPropagation() e.preventDefault() - const newSelection = this.state.selectedOptionIds.filter((id) => id !== tag) - this.setState({ - selectedOptionIds: newSelection, - highlightedOptionId: null - }, () => { - this.inputRef.focus() - }) + const newSelection = selectedOptionIds.filter((id) => id !== tag) + + setSelectedOptionIds(newSelection) + setHighlightedOptionId(null) + + // this?.inputRef.focus() - this.props.handleIssueTitleChange(newSelection) + handleIssueTitleChange(newSelection) } // render tags when multiple options are selected - renderTags () { - const { selectedOptionIds } = this.state + const renderTags = () => { + return selectedOptionIds.map((id, index) => ( this.dismissTag(e, id)} + onClick={(e) => dismissTag(e, id)} /> )) } - render () { - const { - inputValue, - isShowingOptions, - highlightedOptionId, - selectedOptionIds, - filteredOptions, - announcement - } = this.state - - return ( - - - document.getElementById('flash-messages')} - liveRegionPoliteness="polite" - isLiveRegionAtomic - screenReaderOnly - > - { announcement } - - - {selectedOptionIds.length > 0 ? this.renderTags() : null} - + return ( + + + document.getElementById('flash-messages')} + liveRegionPoliteness="polite" + isLiveRegionAtomic + screenReaderOnly + > + { announcement } + + + {selectedOptionIds.length > 0 ? renderTags() : null} - ) - } -} - -export default IssueRuleSelect; \ No newline at end of file + + ) +} \ No newline at end of file diff --git a/assets/js/Components/MessageTray.js b/assets/js/Components/MessageTray.js index ad15426a..b903300e 100644 --- a/assets/js/Components/MessageTray.js +++ b/assets/js/Components/MessageTray.js @@ -1,48 +1,40 @@ -import React from 'react' +import React, { useEffect} from 'react' import { Alert } from '@instructure/ui-alerts' import { Spinner } from '@instructure/ui-spinner' - import Classes from '../../css/app.css' -class MessageTray extends React.Component { - constructor(props) { - super(props) - } - - componentDidMount() { - this.props.clearMessages() - } +export default function MessageTray ({ messages, hasNewReport, clearMessages, t }) { - render() { - return ( -
- {!this.props.hasNewReport && ( - document.getElementsByClassName(Classes.messagesTray)[0]} - > - {this.props.t('label.content_loading_msg')} - - - )} - {this.props.messages.map((msg, i) => ( - - {this.props.t(msg.message)} - - ))} -
- ) - } -} + useEffect(() => { + clearMessages() + }, []) -export default MessageTray + return ( +
+ {!hasNewReport && ( + document.getElementsByClassName(Classes.messagesTray)[0]} + > + {t('label.content_loading_msg')} + + + )} + {messages.map((msg, i) => ( + + {t(msg.message)} + + ))} +
+ ) +} \ No newline at end of file diff --git a/assets/js/Components/ReportsPage.js b/assets/js/Components/ReportsPage.js index bcd83285..65afcbf4 100644 --- a/assets/js/Components/ReportsPage.js +++ b/assets/js/Components/ReportsPage.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useState, useEffect } from 'react' import { View } from '@instructure/ui-view' import { Flex } from '@instructure/ui-flex' import { Text } from '@instructure/ui-text' @@ -12,84 +12,28 @@ import ResolutionsReport from './Reports/ResolutionsReport' import ReportsTable from './Reports/ReportsTable' import IssuesTable from './Reports/IssuesTable' -class ReportsPage extends React.Component { - constructor(props) { - super(props) +export default function ReportsPage({t, report, settings}) { - this.issues = this.processIssues(props.report) + const [reports, setReports] = useState([]) + const [issues, setIssues] = useState([]) - this.state = { - reports: [] - } - } - - componentDidMount() { - if (this.state.reports.length === 0) { - this.getReportHistory() - } - } - - componentDidUpdate() { - this.issues = this.processIssues(this.props.report) - } - - render() { - if (this.state.reports.length === 0) { - return ( - - - - {this.props.t('label.loading_reports')} - - - ) - } else { - return ( - - {this.props.t('label.reports')} - - - - - - - - - - - - - - - - - - ) - } - } - - getReportHistory() { - const api = new Api(this.props.settings) + const getReportHistory = () => { + const api = new Api(settings) api.getReportHistory() .then((responseStr) => responseStr.json()) .then((response) => { - this.setState({reports: response.data}) + setReports(response.data) }) } - processIssues(report) { + const processIssues = (report) => { + let rules = [] for (let issue of report.issues) { const rule = issue.scanRuleId const status = issue.status - + if (!rules[rule]) { rules[rule] = { id: rule, @@ -116,7 +60,53 @@ class ReportsPage extends React.Component { return rules } + useEffect(() => { + if (reports.length === 0) { + getReportHistory() + } + }, []) + + useEffect(() => { + setIssues(processIssues(report)) + }, [report]) + + if (report.length === 0) { + return ( + + + + {t('label.loading_reports')} + + + ) + } else { + return ( + + {t('label.reports')} + + + + + + + + + + + + + + + + + + ) + } -} - -export default ReportsPage \ No newline at end of file +} \ No newline at end of file diff --git a/assets/js/Components/SummaryBar.js b/assets/js/Components/SummaryBar.js index 4f407218..84ad27a1 100644 --- a/assets/js/Components/SummaryBar.js +++ b/assets/js/Components/SummaryBar.js @@ -1,60 +1,52 @@ -import React from 'react'; +import React from 'react' import { View } from '@instructure/ui-view' import { Text } from '@instructure/ui-text' import { Flex } from '@instructure/ui-flex' import { InlineList } from '@instructure/ui-list' -class SummaryBar extends React.Component { - constructor(props) { - super(props) - } +export default function SummaryBar({ t, report }) { - render() { - const report = this.props.report - return ( - - - - {/* {this.props.t('label.summary')} */} - - - - - - {report.errors} - {this.props.t('label.plural.error')} - - - - - {report.suggestions} - {this.props.t('label.plural.suggestion')} - - - - - {report.contentFixed} - {this.props.t('label.plural.fixed')} - - - - - {report.contentResolved} - {this.props.t('label.manually_resolved')} - - - - - {report.filesReviewed} - {this.props.t('label.files_reviewed')} - - - - - - - ) - } -} - -export default SummaryBar; \ No newline at end of file + return ( + + + + {/* {t('label.summary')} */} + + + + + + {report.errors} + {t('label.plural.error')} + + + + + {report.suggestions} + {t('label.plural.suggestion')} + + + + + {report.contentFixed} + {t('label.plural.fixed')} + + + + + {report.contentResolved} + {t('label.manually_resolved')} + + + + + {report.filesReviewed} + {t('label.files_reviewed')} + + + + + + + ) +} \ No newline at end of file diff --git a/assets/js/Components/SummaryForm.js b/assets/js/Components/SummaryForm.js index 25e38738..b5d78230 100644 --- a/assets/js/Components/SummaryForm.js +++ b/assets/js/Components/SummaryForm.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useState } from 'react' import { Heading } from '@instructure/ui-heading' import { Button } from '@instructure/ui-buttons' import { View } from '@instructure/ui-view' @@ -18,180 +18,96 @@ const startOptions = [ 'by_impact' ] -class SummaryForm extends React.Component { - - constructor(props) { - super(props); - - this.state = { - selectFilter: 'easy', - selectRule: '', - selectContentType: '', - selectImpact: '', - } - - this.visualRules = issueRuleIds.filter(rule => this.props.settings.visualRuleIds.includes(rule)) - this.auditoryRules = issueRuleIds.filter(rule => this.props.settings.auditoryRuleIds.includes(rule)) - this.cognitiveRules = issueRuleIds.filter(rule => this.props.settings.cognitiveRuleIds.includes(rule)) - this.motorRules = issueRuleIds.filter(rule => this.props.settings.motorRuleIds.includes(rule)) - - this.handleFilterSelect = this.handleFilterSelect.bind(this) - this.handleRuleSelect = this.handleRuleSelect.bind(this) - this.handleContentTypeSelect = this.handleContentTypeSelect.bind(this) - this.handleImpactSelect = this.handleImpactSelect.bind(this) - this.handleSubmit = this.handleSubmit.bind(this) - } - - render() { - let canSubmit = true - let easyRuleIds = this.props.settings.easyRuleIds - if ('by_issue' === this.state.selectFilter) { - canSubmit = (this.state.selectRule) +export default function SummaryForm({ t, settings, report, handleAppFilters, handleNavigation }) { + const [selectFilter, setSelectFilter] = useState('easy') + const [selectRule, setSelectRule] = useState('') + const [selectContentType, setSelectContentType] = useState('') + const [selectImpact, setSelectImpact] = useState('') + + const visualRules = issueRuleIds.filter(rule => settings.visualRuleIds.includes(rule)) + const auditoryRules = issueRuleIds.filter(rule => settings.auditoryRuleIds.includes(rule)) + const cognitiveRules = issueRuleIds.filter(rule => settings.cognitiveRuleIds.includes(rule)) + const motorRules = issueRuleIds.filter(rule => settings.motorRuleIds.includes(rule)) + const easyRules = issueRuleIds.filter(rule => settings.easyRuleIds.includes(rule)) + + const checkCanSubmit = () => { + let newCanSubmit = true + if (selectFilter === 'by_issue') { + newCanSubmit = selectRule } - if ('by_content' === this.state.selectFilter) { - canSubmit = (this.state.selectContentType) + if (selectFilter === 'by_content') { + newCanSubmit = selectContentType } - if ('by_impact' === this.state.selectFilter) { - canSubmit = (this.state.selectImpact) + if (selectFilter === 'by_impact') { + newCanSubmit = selectImpact } - - this.easyRules = issueRuleIds.filter(rule => easyRuleIds.includes(rule)) - - - - return ( - - {this.props.t('form.summary.heading')} - {/* {this.props.t('form.summary.description')} */} - - - {startOptions.map((key) => )} - - - {('by_issue' === this.state.selectFilter) && - - - {this.renderIssueOptions()} - - - } - - {('by_content' === this.state.selectFilter) && - - - {this.renderContentTypeOptions()} - - - } - - {('by_impact' === this.state.selectFilter) && - - - {this.renderImpactOptions()} - - - } - - - {this.renderIssueCount()} - - - - - - - ) + return newCanSubmit; } + const canSubmit = checkCanSubmit() - handleFilterSelect(e, val) - { - this.setState({selectFilter: val}) + const handleFilterSelect = (e, val) => { + setSelectFilter(val) } - handleRuleSelect(e, val) - { - this.setState({selectRule: val.id}) + const handleRuleSelect = (e, val) => { + setSelectRule(val.id) } - handleContentTypeSelect(e, val) - { - this.setState({ selectContentType: val.id }) + const handleContentTypeSelect = (e, val) => { + setSelectContentType(val.id) } - handleImpactSelect(e, val) - { - this.setState({ selectImpact: val.id }) + const handleImpactSelect = (e, val) => { + setSelectImpact(val.id) } - handleSubmit(e) - { - const { selectFilter, selectRule, selectContentType, selectImpact} = this.state + const handleSubmit = (e) => { let filters = {} switch (selectFilter) { case 'easy': filters = {easyIssues: true} - break + break case 'errors_only': filters = {issueTypes: ['error']} - break + break case 'active': filters = {issueStatus: ['active']} - break + break case 'by_issue': filters = {issueTitles: [selectRule]} - break + break case 'by_content': filters = {contentTypes: [selectContentType]} - break + break case 'by_impact': filters = {issueImpacts: [selectImpact]} - break + break } - this.props.handleAppFilters(filters); - this.props.handleNavigation('content'); + handleAppFilters(filters) + handleNavigation('content') } - renderIssueOptions() { + const renderIssueOptions = () => { let options = {} let out = [ -- ] - for (const issue of this.props.report.issues) { + for (const issue of report.issues) { options[issue.scanRuleId] = issue.type } for (let ruleId of Object.keys(options)) { - out.push({this.props.t(`rule.label.${ruleId}`)}) + out.push({t(`rule.label.${ruleId}`)}) } return out } - renderContentTypeOptions() { - let types = this.props.settings.contentTypes + const renderContentTypeOptions = () => { + let types = settings.contentTypes let out = [ -- ] @@ -199,51 +115,51 @@ class SummaryForm extends React.Component { types.sort() for (let type of types) { - out.push({this.props.t(`content.plural.${type}`)}) + out.push({t(`content.plural.${type}`)}) } return out } - renderImpactOptions() { + const renderImpactOptions = () => { let impacts = ['visual', 'auditory', 'cognitive', 'motor'] let out = [ -- ] for (let impact of impacts) { - out.push({this.props.t(`label.filter.${impact}`)}) + out.push({t(`label.filter.${impact}`)}) } return out } - renderIssueCount() { + const renderIssueCount = () => { let values = ['-', '-'] - switch (this.state.selectFilter) { + switch (selectFilter) { case 'easy': - values = this.getEasyCount() + values = getEasyCount() break case 'errors_only': - values = this.getErrorCount() + values = getErrorCount() break case 'active': - values = this.getActiveIssueCount() + values = getActiveIssueCount() break case 'by_issue': - if (this.state.selectRule) { - values = this.getIssueTypeCount() + if (selectRule) { + values = getIssueTypeCount() } break case 'by_content': - if (this.state.selectContentType) { - values = this.getContentTypeCount() + if (selectContentType) { + values = getContentTypeCount() } break case 'by_impact': - if (this.state.selectImpact) { - values = this.getImpactCount() + if (selectImpact) { + values = getImpactCount() } break } @@ -253,19 +169,18 @@ class SummaryForm extends React.Component { {values[0] && - {values[0]} {this.props.t('label.plural.error')} + {values[0]} {t('label.plural.error')} } {values[1] && - {values[1]} {this.props.t('label.plural.suggestion')} + {values[1]} {t('label.plural.suggestion')} } ) } - getEasyCount() { - const report = this.props.report + const getEasyCount = () => { let errors = 0 let suggestions = 0 @@ -274,7 +189,7 @@ class SummaryForm extends React.Component { continue } - if (this.easyRules.includes(issue.scanRuleId)) { + if (easyRules.includes(issue.scanRuleId)) { if ('error' === issue.type) { errors++ } @@ -287,8 +202,7 @@ class SummaryForm extends React.Component { return [errors, suggestions] } - getErrorCount() { - const report = this.props.report + const getErrorCount = () => { let errors = 0 for (const issue of report.issues) { @@ -303,8 +217,7 @@ class SummaryForm extends React.Component { return [errors, 0] } - getActiveIssueCount() { - const report = this.props.report + const getActiveIssueCount = () => { let errors = 0 let suggestions = 0 @@ -323,8 +236,7 @@ class SummaryForm extends React.Component { return [errors, suggestions] } - getIssueTypeCount() { - const report = this.props.report + const getIssueTypeCount = () => { let errors = 0 let suggestions = 0 @@ -332,7 +244,7 @@ class SummaryForm extends React.Component { if (issue.status) { continue } - if (this.state.selectRule === issue.scanRuleId) { + if (selectRule === issue.scanRuleId) { if ('error' === issue.type) { errors++ } @@ -345,8 +257,7 @@ class SummaryForm extends React.Component { return [errors, suggestions] } - getContentTypeCount() { - const report = this.props.report + const getContentTypeCount = () => { let errors = 0 let suggestions = 0 @@ -356,7 +267,7 @@ class SummaryForm extends React.Component { } const contentItem = Object.assign({}, report.contentItems[issue.contentItemId]) - if (this.state.selectContentType === contentItem.contentType) { + if (selectContentType === contentItem.contentType) { if ('error' === issue.type) { errors++ } @@ -369,8 +280,7 @@ class SummaryForm extends React.Component { return [errors, suggestions] } - getImpactCount() { - const report = this.props.report + const getImpactCount = () => { let errors = 0 let suggestions = 0 @@ -379,10 +289,10 @@ class SummaryForm extends React.Component { continue } - if ((this.state.selectImpact === 'visual' && this.visualRules.includes(issue.scanRuleId)) - || (this.state.selectImpact === 'auditory' && this.auditoryRules.includes(issue.scanRuleId)) - || (this.state.selectImpact === 'cognitive' && this.cognitiveRules.includes(issue.scanRuleId)) - || (this.state.selectImpact === 'motor' && this.motorRules.includes(issue.scanRuleId)) + if ((selectImpact === 'visual' && visualRules.includes(issue.scanRuleId)) + || (selectImpact === 'auditory' && auditoryRules.includes(issue.scanRuleId)) + || (selectImpact === 'cognitive' && cognitiveRules.includes(issue.scanRuleId)) + || (selectImpact === 'motor' && motorRules.includes(issue.scanRuleId)) ) { if ('error' === issue.type) { errors++ @@ -395,6 +305,66 @@ class SummaryForm extends React.Component { return [errors, suggestions] } -} -export default SummaryForm + return ( + + {t('form.summary.heading')} + + + {startOptions.map((key) => )} + + + {('by_issue' === selectFilter) && + + + {renderIssueOptions()} + + + } + + {('by_content' === selectFilter) && + + + {renderContentTypeOptions()} + + + } + + {('by_impact' === selectFilter) && + + + {renderImpactOptions()} + + + } + + + {renderIssueCount()} + + + + + + + ) +} \ No newline at end of file diff --git a/assets/js/Components/SummaryPage.js b/assets/js/Components/SummaryPage.js index 621d8041..824e44e1 100644 --- a/assets/js/Components/SummaryPage.js +++ b/assets/js/Components/SummaryPage.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useState } from 'react' import { Table } from '@instructure/ui-table' import { Text } from '@instructure/ui-text' import { View } from '@instructure/ui-view' @@ -11,31 +11,16 @@ import Classes from '../../css/theme-overrides.css' import SummaryForm from './SummaryForm' -class SummaryPage extends React.Component { +export default function SummaryPage({ report, t, handleAppFilters, handleNavigation, settings }) { - constructor(props) { - super(props); - - this.state = { - showMoreSummary: false - } - - //this.handleIssueTypeLink = this.handleIssueTypeLink.bind(this) - //this.handleShowMore = this.handleShowMore.bind(this) - } - - processReportData(report) { + const processReportData = () => { let issueResults = { - error: {}, - suggestion: {} - }; - - this.contentResults = {}; - this.issueResults = { error: [], suggestion: [] }; + let contentResults = {}; + if (report && report.issues) { for (let issueId in report.issues) { const issue = report.issues[issueId]; @@ -46,13 +31,13 @@ class SummaryPage extends React.Component { } if (contentItem && contentItem.contentType) { - if (!this.contentResults[contentItem.contentType]) { - this.contentResults[contentItem.contentType] = { + if (!contentResults[contentItem.contentType]) { + contentResults[contentItem.contentType] = { error: 0, suggestion: 0 }; } - this.contentResults[contentItem.contentType][issue.type]++; + contentResults[contentItem.contentType][issue.type]++; } if(!issueResults[issue.type].hasOwnProperty(issue.scanRuleId)) { @@ -64,66 +49,29 @@ class SummaryPage extends React.Component { for (const type in issueResults) { for (const ruleId in issueResults[type]) { - this.issueResults[type].push([ruleId, issueResults[type][ruleId]]); + issueResults[type].push([ruleId, issueResults[type][ruleId]]); } } - this.issueResults.error.sort((a, b) => (a[1] > b[1]) ? -1 : 1 ); - this.issueResults.suggestion.sort((a, b) => (a[1] > b[1]) ? -1 : 1); + issueResults.error.sort((a, b) => (a[1] > b[1]) ? -1 : 1 ); + issueResults.suggestion.sort((a, b) => (a[1] > b[1]) ? -1 : 1); } + + return { issueResults, contentResults } } - render() { - const report = this.props.report - const infoLabel = (this.state.showMoreSummary) ? 'label.less_info' : 'label.more_info' + const handleIssueTitleLink = (ruleId) => { + handleAppFilters({issueTitles: [ruleId]}); + handleNavigation('content'); + } - return ( - - {/* {this.props.t('label.summary')} */} - - - - - - - - - - - - {this.renderSummaryTables()} - - - - - - - - - ) + const handleMetricClick = (val) => { + handleAppFilters({issueTypes: [val] }) + handleNavigation('content') } - renderSummaryTables() { - const report = this.props.report; - this.processReportData(report) + const renderSummaryTables = () => { + const { issueResults, contentResults } = processReportData() const maxRows = 3 return ( @@ -131,11 +79,11 @@ class SummaryPage extends React.Component { - {`${report.errors} ${this.props.t(`label.plural.error`)}`} + {`${report.errors} ${t(`label.plural.error`)}`} - {this.props.t(`label.most_common`) + ' ' + this.props.t(`label.plural.error`)} + {t(`label.most_common`) + ' ' + t(`label.plural.error`)} - {this.issueResults['error'].map((val, ind) => { + {issueResults['error'].map((val, ind) => { if (ind >= maxRows) { return } return ( - this.handleIssueTitleLink(val[0])}> + handleIssueTitleLink(val[0])}> {val[1]} - {this.props.t(`rule.label.${val[0]}`)} + {t(`rule.label.${val[0]}`)} - this.handleIssueTitleLink(val[0])}> + handleIssueTitleLink(val[0])}> @@ -180,11 +128,11 @@ class SummaryPage extends React.Component { - {`${report.suggestions} ${this.props.t(`label.plural.suggestion`)}`} + {`${report.suggestions} ${t(`label.plural.suggestion`)}`}
- {this.props.t(`label.most_common`) + ' ' + this.props.t(`label.plural.suggestion`)} + {t(`label.most_common`) + ' ' + t(`label.plural.suggestion`)} - {this.issueResults['suggestion'].map((val, ind) => { + {issueResults['suggestion'].map((val, ind) => { if (ind >= maxRows) { return } return ( - this.handleIssueTitleLink(val[0])}> + handleIssueTitleLink(val[0])}> {val[1]} - {this.props.t(`rule.label.${val[0]}`)} + {t(`rule.label.${val[0]}`)} @@ -228,24 +176,46 @@ class SummaryPage extends React.Component { ) } - // handleShowMore(e) { - // this.setState({ showMoreSummary: !this.state.showMoreSummary }) - // } - - // handleIssueTypeLink(contentType, issueType) { - // this.props.handleAppFilters({contentTypes: [contentType], issueTypes: [issueType]}); - // this.props.handleNavigation('content'); - // } - - handleIssueTitleLink(ruleId) { - this.props.handleAppFilters({issueTitles: [ruleId]}); - this.props.handleNavigation('content'); - } - - handleMetricClick(val) { - this.props.handleAppFilters({issueTypes: [val] }) - this.props.handleNavigation('content') - } -} - -export default SummaryPage; + return ( + + {/* {t('label.summary')} */} + + + + + + + + + + + + { renderSummaryTables() } + + + + + + + + + ) +} \ No newline at end of file diff --git a/assets/js/Components/UfixitModal.js b/assets/js/Components/UfixitModal.js index cfa1e03f..1aa06823 100644 --- a/assets/js/Components/UfixitModal.js +++ b/assets/js/Components/UfixitModal.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Modal } from '@instructure/ui-modal' import { Heading } from '@instructure/ui-heading' import { Button } from '@instructure/ui-buttons' @@ -24,283 +24,107 @@ import * as Html from '../Services/Html' import Pretty from 'pretty' -class UfixitModal extends React.Component { - - constructor(props) { - super(props); - - this.state = { - windowContents: 'preview', - expandExample: false, - } - - this.modalMessages = [] - - this.handleWindowToggle = this.handleWindowToggle.bind(this) - this.addMessage = this.addMessage.bind(this) - this.clearMessages = this.clearMessages.bind(this) - this.handleIssueSave = this.handleIssueSave.bind(this) - this.handleIssueResolve = this.handleIssueResolve.bind(this) - this.handleOpenContent = this.handleOpenContent.bind(this) - this.handleExampleToggle = this.handleExampleToggle.bind(this) - this.handleManualScan = this.handleManualScan.bind(this) - } - - findActiveIndex() { - if (this.props.filteredRows && this.props.activeIssue) { - for (const i in this.props.filteredRows) { - let issue = this.props.filteredRows[i] - if (issue.issue.id === this.props.activeIssue.id) { +export default function UfixitModal({ + t, + settings, + open, + activeIssue, + activeIndex, + activeContentItem, + filteredRows, + handleCloseButton, + handleActiveIssue, + handleIssueSave +}) { + + const [windowContents, setWindowContents] = useState('preview') + const [expandExample, setExpandExample] = useState(false) + const [showExample, setShowExample] = useState(false) + const [pending, setPending] = useState(false) + const [currentIndex, setCurrentIndex] = useState(0) + const [modalMessages, setModalMessages] = useState([]) + const [code, setCode] = useState('') + let UfixitForm = returnIssueForm(activeIssue) + + const findActiveIndex = () => { + if (filteredRows && activeIssue) { + for (const i in filteredRows) { + let issue = filteredRows[i] + if (issue.issue.id === activeIssue.id) { return Number(i) } } } - return 0; } // Handler for the previous and next buttons on the modal // Will wrap around if the index goes out of bounds - handleIssueChange(newIndex) { + const handleIssueChange = (newIndex) => { if (newIndex < 0) { - newIndex = this.props.filteredRows.length - 1 + newIndex = filteredRows.length - 1 } - if (newIndex > (this.props.filteredRows.length - 1)) { + if (newIndex > (filteredRows.length - 1)) { newIndex = 0 } - this.clearMessages() - this.props.handleActiveIssue(this.props.filteredRows[newIndex].issue, newIndex) - } - - handleWindowToggle(val) { - this.setState({windowContents: val}) + clearMessages() + handleActiveIssue(filteredRows[newIndex].issue, newIndex) } - handleOpenContent(e) { - const contentItem = this.props.activeContentItem - window.open(contentItem.url, '_blank', 'noopener,noreferrer') + const handleWindowToggle = (val) => { + setWindowContents(val) } - render() { - const { activeIssue, activeContentItem } = this.props - - const pending = (this.props.activeIssue && (this.props.activeIssue.pending == '1')) - - let activeIndex = this.findActiveIndex(); - const UfixitForm = returnIssueForm(activeIssue) - - let showExample = false - if (!this.props.t(`rule.example.${activeIssue.scanRuleId}`).includes('rule.example')) { - showExample = true - } - - let code = this.prepareCode(activeIssue) - - return ( - - {this.props.open && - - - - - {this.props.t(`rule.label.${activeIssue.scanRuleId}`)} - - - - - - - - - - - - {ReactHtmlParser(this.props.t(`rule.desc.${activeIssue.scanRuleId}`), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, this.props.settings) })} - - - {showExample && - - - {ReactHtmlParser(this.props.t(`rule.example.${activeIssue.scanRuleId}`), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, this.props.settings) })} - - - } - - - - - - - {('module' !== activeContentItem.contentType) && - - {this.props.t('label.manual_resolution')} - {this.props.t('label.resolved_description')} - - {('2' == activeIssue.pending) ? - - : - - } - - - } - - - - - - {('preview' === this.state.windowContents) ? - {this.props.t('label.preview')} - : - this.handleWindowToggle('preview')}> - {this.props.t('label.preview')} - } - - - {('html' === this.state.windowContents) ? - {this.props.t('label.view_source')} - : - this.handleWindowToggle('html')}> - {this.props.t('label.view_source')} - } - - - - {('preview' === this.state.windowContents) && - - - } - {('html' === this.state.windowContents) && - - } - - - - {/* {this.props.t('label.source')} */} - {activeContentItem && - - {activeContentItem.contentType} - } iconPlacement="end"> - {activeContentItem.title} - - - } - - - - - - - - - - - - {this.props.t('label.issue')} {(activeIndex + 1)} {this.props.t('label.of')} {this.props.filteredRows.length} - - {activeIssue.status && !activeIssue.pending && - - {('1' == activeIssue.status) && - - - {this.props.t('label.fixed')} - - } - {('2' == activeIssue.status) && - - - {this.props.t('label.resolved')} - - } - - } - - - - - - - - - - - - } - ) + const handleOpenContent = (e) => { + const contentItem = activeContentItem + window.open(contentItem.url, '_blank', 'noopener,noreferrer') } - prepareCode(activeIssue) { + const prepareCode = (activeIssue) => { let sourceCode = (activeIssue.newHtml) ? activeIssue.newHtml : activeIssue.sourceHtml - let code = sourceCode + let tempCode = sourceCode if (sourceCode.length === 0 || sourceCode.length > 3000) { - code = 'Not Available' + tempCode = 'Not Available' } else { let element = Html.toElement(sourceCode) if(element && element.tagName === 'TH') { - code = activeIssue.previewHtml + tempCode = activeIssue.previewHtml } } - return Pretty(code) + return Pretty(tempCode) } - - handleIssueResolve() { - let activeIssue = Object.assign({}, this.props.activeIssue) - if (activeIssue.pending) { + const handleIssueResolve = () => { + let tempIssue = Object.assign({}, activeIssue) + if (tempIssue.pending) { return } - if (activeIssue.status) { - activeIssue.status = false - activeIssue.newHtml = Html.toString(Html.removeClass(activeIssue.sourceHtml, 'phpally-ignore')) + if (tempIssue.status) { + tempIssue.status = false + tempIssue.newHtml = Html.toString(Html.removeClass(tempIssue.sourceHtml, 'phpally-ignore')) } else { - activeIssue.status = 2 - activeIssue.newHtml = Html.toString(Html.addClass(activeIssue.sourceHtml, 'phpally-ignore')) + tempIssue.status = 2 + tempIssue.newHtml = Html.toString(Html.addClass(tempIssue.sourceHtml, 'phpally-ignore')) } - - let api = new Api(this.props.settings) - api.resolveIssue(activeIssue) + + let api = new Api(settings) + api.resolveIssue(tempIssue) .then((responseStr) => responseStr.json()) .then((response) => { // set messages - response.messages.forEach((msg) => this.addMessage(msg)) + response.messages.forEach((msg) => addMessage(msg)) if (response.data.issue) { - const newIssue = { ...activeIssue, ...response.data.issue } + const newIssue = { ...tempIssue, ...response.data.issue } const newReport = response.data.report // update activeIssue newIssue.pending = false - newIssue.recentlyResolved = !!activeIssue.status + newIssue.recentlyResolved = !!tempIssue.status newIssue.sourceHtml = newIssue.newHtml newIssue.newHtml = '' // Get updated report @@ -308,44 +132,44 @@ class UfixitModal extends React.Component { .then((responseStr) => responseStr.json()) .then((res) => { // update activeIssue - this.props.handleActiveIssue(newIssue) + handleActiveIssue(newIssue) - this.props.handleIssueSave(newIssue, res.data) + handleIssueSave(newIssue, res.data) }) } else { - activeIssue.pending = false - this.props.handleActiveIssue(activeIssue) + tempIssue.pending = false + handleActiveIssue(tempIssue) } }) - activeIssue.pending = 2 - this.props.handleActiveIssue(activeIssue) + tempIssue.pending = 2 + handleActiveIssue(tempIssue) } - handleIssueSave(issue) { + const handleSingleIssueSave = (issue) => { // send issue obj to PHP - let api = new Api(this.props.settings) + let api = new Api(settings) api.saveIssue(issue) .then((responseStr) => responseStr.json()) .then((response) => { // specific to a failed rescan of the HTML if (response.data.failed) { - response.messages.forEach((msg) => this.addMessage(msg)) + response.messages.forEach((msg) => addMessage(msg)) if (Array.isArray(response.data.issues)) { response.data.issues.forEach((issue) => { - this.addMessage({ + addMessage({ severity: 'error', - message: this.props.t(`form.error.${issue.ruleId}`) + message: t(`form.error.${issue.ruleId}`) }) }) } if (Array.isArray(response.data.errors)) { response.data.errors.forEach((error) => { - this.addMessage({ + addMessage({ severity: 'error', message: error }) @@ -354,11 +178,11 @@ class UfixitModal extends React.Component { // update activeIssue issue.pending = false - this.props.handleActiveIssue(issue) + handleActiveIssue(issue) } else { // set messages - response.messages.forEach((msg) => this.addMessage(msg)) + response.messages.forEach((msg) => addMessage(msg)) if (response.data.issue) { const newIssue = {...issue, ...response.data.issue} @@ -370,33 +194,31 @@ class UfixitModal extends React.Component { .then((responseStr) => responseStr.json()) .then((res) => { // update activeIssue - this.props.handleActiveIssue(newIssue) - - this.props.handleIssueSave(newIssue, res.data) + handleActiveIssue(newIssue) + handleIssueSave(newIssue, res.data) }) } else { issue.pending = false - this.props.handleActiveIssue(issue) + handleActiveIssue(issue) } } }) // update activeIssue issue.pending = 1 - this.props.handleActiveIssue(issue) + handleActiveIssue(issue) } - - handleManualScan(issue) { - let api = new Api(this.props.settings) + const handleManualScan = (issue) => { + let api = new Api(settings) api.scanIssue(issue.id) .then((response) => response.json()) .then((data) => { if (data.messages) { data.messages.forEach((msg) => { if (msg.visible) { - this.addMessage(msg); + addMessage(msg); } }); } @@ -405,33 +227,629 @@ class UfixitModal extends React.Component { newIssue.pending = false newIssue.recentlyUpdated = true - this.props.handleIssueSave(newIssue, data.data.report) + handleIssueSave(newIssue, data.data.report) // update activeIssue - this.props.handleActiveIssue(newIssue) + handleActiveIssue(newIssue) } else { issue.pending = false - this.props.handleActiveIssue(issue) + handleActiveIssue(issue) } }) // update activeIssue issue.pending = 1 - this.props.handleActiveIssue(issue) - } - - handleExampleToggle() { - this.setState({expandExample: !this.state.expandExample}) + handleActiveIssue(issue) } - addMessage = (msg) => { - this.modalMessages.push(msg); + const handleExampleToggle = () => { + setExpandExample(!expandExample) } - clearMessages() { - this.modalMessages = []; + const addMessage = (msg) => { + setModalMessages([...modalMessages, msg]) } + + const clearMessages = () => { + setModalMessages([]) + } + + useEffect(() => { + console.info('UfixitModal loaded') + console.info(JSON.stringify(activeIssue)) + + setPending(activeIssue && (activeIssue.pending == '1')) + UfixitForm = returnIssueForm(activeIssue) + + if (!t(`rule.example.${activeIssue.scanRuleId}`).includes('rule.example')) { + setShowExample(true) + } + else { + setShowExample(false) + } + + setCurrentIndex(findActiveIndex()) + setCode(prepareCode(activeIssue)) + + }, [activeIssue, activeContentItem]) + + return ( + + {open && + + + + + {t(`rule.label.${activeIssue.scanRuleId}`)} + + + + + + + + + + + + {ReactHtmlParser(t(`rule.desc.${activeIssue.scanRuleId}`), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, settings) })} + + + {showExample && + + + {ReactHtmlParser(t(`rule.example.${activeIssue.scanRuleId}`), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, settings) })} + + + } + + + + + {UfixitForm && + } + + {('module' !== activeContentItem.contentType) && + + {t('label.manual_resolution')} + {t('label.resolved_description')} + + {('2' == activeIssue.pending) ? + + : + + } + + + } + + + + + + {('preview' === windowContents) ? + {t('label.preview')} + : + handleWindowToggle('preview')}> + {t('label.preview')} + } + + + {('html' === windowContents) ? + {t('label.view_source')} + : + handleWindowToggle('html')}> + {t('label.view_source')} + } + + + + {('preview' === windowContents) && + + + } + {('html' === windowContents) && + + } + + + + {/* {t('label.source')} */} + {activeContentItem && + + {activeContentItem.contentType} + } iconPlacement="end"> + {activeContentItem.title} + + + } + + + + + + + + + + + + {t('label.issue')} {(currentIndex + 1)} {t('label.of')} {filteredRows.length} + + {activeIssue.status && !activeIssue.pending && + + {('1' == activeIssue.status) && + + + {t('label.fixed')} + + } + {('2' == activeIssue.status) && + + + {t('label.resolved')} + + } + + } + + + + + + + + + + + + } + + ) } -export default UfixitModal; + + + + +// class UfixitModal extends React.Component { + +// constructor(props) { +// super(props); + +// this.state = { +// windowContents: 'preview', +// expandExample: false, +// } + +// this.modalMessages = [] + +// this.handleWindowToggle = this.handleWindowToggle.bind(this) +// this.addMessage = this.addMessage.bind(this) +// this.clearMessages = this.clearMessages.bind(this) +// this.handleIssueSave = this.handleIssueSave.bind(this) +// this.handleIssueResolve = this.handleIssueResolve.bind(this) +// this.handleOpenContent = this.handleOpenContent.bind(this) +// this.handleExampleToggle = this.handleExampleToggle.bind(this) +// this.handleManualScan = this.handleManualScan.bind(this) +// } + +// findActiveIndex() { +// if (this.props.filteredRows && this.props.activeIssue) { +// for (const i in this.props.filteredRows) { +// let issue = this.props.filteredRows[i] +// if (issue.issue.id === this.props.activeIssue.id) { +// return Number(i) +// } +// } +// } + +// return 0; +// } + +// // Handler for the previous and next buttons on the modal +// // Will wrap around if the index goes out of bounds +// handleIssueChange(newIndex) { +// if (newIndex < 0) { +// newIndex = this.props.filteredRows.length - 1 +// } +// if (newIndex > (this.props.filteredRows.length - 1)) { +// newIndex = 0 +// } +// this.clearMessages() +// this.props.handleActiveIssue(this.props.filteredRows[newIndex].issue, newIndex) +// } + +// handleWindowToggle(val) { +// this.setState({windowContents: val}) +// } + +// handleOpenContent(e) { +// const contentItem = this.props.activeContentItem +// window.open(contentItem.url, '_blank', 'noopener,noreferrer') +// } + +// render() { +// const { activeIssue, activeContentItem } = this.props + +// const pending = (this.props.activeIssue && (this.props.activeIssue.pending == '1')) + +// let activeIndex = this.findActiveIndex(); +// const UfixitForm = returnIssueForm(activeIssue) + +// let showExample = false +// if (!this.props.t(`rule.example.${activeIssue.scanRuleId}`).includes('rule.example')) { +// showExample = true +// } + +// let code = this.prepareCode(activeIssue) + +// return ( +// +// {this.props.open && +// +// +// +// +// {this.props.t(`rule.label.${activeIssue.scanRuleId}`)} +// +// +// +// +// +// +// +// +// +// +// +// {ReactHtmlParser(this.props.t(`rule.desc.${activeIssue.scanRuleId}`), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, this.props.settings) })} +// +// +// {showExample && +// +// +// {ReactHtmlParser(this.props.t(`rule.example.${activeIssue.scanRuleId}`), { preprocessNodes: (nodes) => Html.processStaticHtml(nodes, this.props.settings) })} +// +// +// } +// +// +// +// +// +// +// {('module' !== activeContentItem.contentType) && +// +// {this.props.t('label.manual_resolution')} +// {this.props.t('label.resolved_description')} +// +// {('2' == activeIssue.pending) ? +// +// : +// +// } +// +// +// } +// +// +// +// +// +// {('preview' === this.state.windowContents) ? +// {this.props.t('label.preview')} +// : +// this.handleWindowToggle('preview')}> +// {this.props.t('label.preview')} +// } +// +// +// {('html' === this.state.windowContents) ? +// {this.props.t('label.view_source')} +// : +// this.handleWindowToggle('html')}> +// {this.props.t('label.view_source')} +// } +// +// +// +// {('preview' === this.state.windowContents) && +// +// +// } +// {('html' === this.state.windowContents) && +// +// } +// +// +// +// {/* {this.props.t('label.source')} */} +// {activeContentItem && +// +// {activeContentItem.contentType} +// } iconPlacement="end"> +// {activeContentItem.title} +// +// +// } +// +// +// +// + +// +// +// +// +// +// +// {this.props.t('label.issue')} {(activeIndex + 1)} {this.props.t('label.of')} {this.props.filteredRows.length} +// +// {activeIssue.status && !activeIssue.pending && +// +// {('1' == activeIssue.status) && +// +// +// {this.props.t('label.fixed')} +// +// } +// {('2' == activeIssue.status) && +// +// +// {this.props.t('label.resolved')} +// +// } +// +// } +// +// +// +// +// +// +// +// +// +// +// +// } +// ) +// } + +// prepareCode(activeIssue) { +// let sourceCode = (activeIssue.newHtml) ? activeIssue.newHtml : activeIssue.sourceHtml +// let code = sourceCode + +// if (sourceCode.length === 0 || sourceCode.length > 3000) { +// code = 'Not Available' +// } else { +// let element = Html.toElement(sourceCode) + +// if(element && element.tagName === 'TH') { +// code = activeIssue.previewHtml +// } +// } +// return Pretty(code) +// } + + +// handleIssueResolve() { +// let activeIssue = Object.assign({}, this.props.activeIssue) +// if (activeIssue.pending) { +// return +// } + +// if (activeIssue.status) { +// activeIssue.status = false +// activeIssue.newHtml = Html.toString(Html.removeClass(activeIssue.sourceHtml, 'phpally-ignore')) +// } +// else { +// activeIssue.status = 2 +// activeIssue.newHtml = Html.toString(Html.addClass(activeIssue.sourceHtml, 'phpally-ignore')) +// } + +// let api = new Api(this.props.settings) +// api.resolveIssue(activeIssue) +// .then((responseStr) => responseStr.json()) +// .then((response) => { +// // set messages +// response.messages.forEach((msg) => this.addMessage(msg)) + +// if (response.data.issue) { +// const newIssue = { ...activeIssue, ...response.data.issue } +// const newReport = response.data.report + +// // update activeIssue +// newIssue.pending = false +// newIssue.recentlyResolved = !!activeIssue.status +// newIssue.sourceHtml = newIssue.newHtml +// newIssue.newHtml = '' +// // Get updated report +// api.scanContent(newIssue.contentItemId) +// .then((responseStr) => responseStr.json()) +// .then((res) => { +// // update activeIssue +// this.props.handleActiveIssue(newIssue) + +// this.props.handleIssueSave(newIssue, res.data) +// }) +// } +// else { +// activeIssue.pending = false +// this.props.handleActiveIssue(activeIssue) +// } +// }) + +// activeIssue.pending = 2 +// this.props.handleActiveIssue(activeIssue) +// } + +// handleIssueSave(issue) { +// // send issue obj to PHP +// let api = new Api(this.props.settings) + +// api.saveIssue(issue) +// .then((responseStr) => responseStr.json()) +// .then((response) => { +// // specific to a failed rescan of the HTML +// if (response.data.failed) { +// response.messages.forEach((msg) => this.addMessage(msg)) + +// if (Array.isArray(response.data.issues)) { +// response.data.issues.forEach((issue) => { +// this.addMessage({ +// severity: 'error', +// message: this.props.t(`form.error.${issue.ruleId}`) +// }) +// }) +// } + +// if (Array.isArray(response.data.errors)) { +// response.data.errors.forEach((error) => { +// this.addMessage({ +// severity: 'error', +// message: error +// }) +// }) +// } + +// // update activeIssue +// issue.pending = false +// this.props.handleActiveIssue(issue) +// } +// else { +// // set messages +// response.messages.forEach((msg) => this.addMessage(msg)) + +// if (response.data.issue) { +// const newIssue = {...issue, ...response.data.issue} +// newIssue.pending = false +// newIssue.recentlyUpdated = true + +// // Get updated report +// api.scanContent(newIssue.contentItemId) +// .then((responseStr) => responseStr.json()) +// .then((res) => { +// // update activeIssue +// this.props.handleActiveIssue(newIssue) + +// this.props.handleIssueSave(newIssue, res.data) +// }) +// } +// else { +// issue.pending = false +// this.props.handleActiveIssue(issue) +// } +// } +// }) + +// // update activeIssue +// issue.pending = 1 +// this.props.handleActiveIssue(issue) +// } + + +// handleManualScan(issue) { +// let api = new Api(this.props.settings) +// api.scanIssue(issue.id) +// .then((response) => response.json()) +// .then((data) => { +// if (data.messages) { +// data.messages.forEach((msg) => { +// if (msg.visible) { +// this.addMessage(msg); +// } +// }); +// } +// if (data.data.issue) { +// const newIssue = { ...issue, ...data.data.issue } +// newIssue.pending = false +// newIssue.recentlyUpdated = true + +// this.props.handleIssueSave(newIssue, data.data.report) + +// // update activeIssue +// this.props.handleActiveIssue(newIssue) +// } +// else { +// issue.pending = false +// this.props.handleActiveIssue(issue) +// } +// }) + +// // update activeIssue +// issue.pending = 1 +// this.props.handleActiveIssue(issue) +// } + +// handleExampleToggle() { +// this.setState({expandExample: !this.state.expandExample}) +// } + +// addMessage = (msg) => { +// this.modalMessages.push(msg); +// } + +// clearMessages() { +// this.modalMessages = []; +// } +// } + +// export default UfixitModal; diff --git a/assets/js/Components/WelcomePage.js b/assets/js/Components/WelcomePage.js index 4adb7fff..22b73070 100644 --- a/assets/js/Components/WelcomePage.js +++ b/assets/js/Components/WelcomePage.js @@ -6,36 +6,11 @@ import { Checkbox } from '@instructure/ui-checkbox' import AboutPage from './AboutPage' import Api from '../Services/Api' -class WelcomePage extends React.Component { - constructor(props) { - super(props) +export default function WelcomePage({ t, settings, setSettings, hasNewReport, handleNavigation }) { - this.handleSkipWelcomeMessage = this.handleSkipWelcomeMessage.bind(this) - } - - render() { - return ( - - - {this.props.t('about.title')} - - - - - - - - - - ) - } - - handleSkipWelcomeMessage(event) { - let api = new Api(this.props.settings) - let user = this.props.settings.user + const handleSkipWelcomeMessage = (event) => { + let api = new Api(settings) + let user = settings.user if (event.target.checked) { user.roles = ['ROLE_ADVANCED_USER'] @@ -47,10 +22,27 @@ class WelcomePage extends React.Component { api.updateUser(user) .then((response) => response.json()) .then((data) => { - this.props.settings.user = data + let newSettings = [...settings] + newSettings.user = data + setSettings(newSettings) }) - } -} -export default WelcomePage; \ No newline at end of file + return ( + + + {t('about.title')} + + + + + + + + + + ) +} \ No newline at end of file diff --git a/assets/js/getInitialData.js b/assets/js/getInitialData.js index 107f76fb..830be3fb 100644 --- a/assets/js/getInitialData.js +++ b/assets/js/getInitialData.js @@ -16,7 +16,7 @@ export default function getInitialData() { data = JSON.parse(settingsElement.textContent) if (Object.keys(data).length > 0) { - console.log('Data was found and loaded!') + console.info('UDOIT initial LMS data was found and loaded!') } else { console.error('No data loaded!') } diff --git a/assets/js/index.js b/assets/js/index.js index 67fa0014..d10fbbbe 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -1,5 +1,5 @@ import React from 'react' -import ReactDOM from 'react-dom' +import { createRoot } from 'react-dom/client' import App from './Components/App' import getInitialData from './getInitialData' import { theme } from '@instructure/canvas-theme' @@ -7,4 +7,7 @@ import { theme } from '@instructure/canvas-theme' theme.use() const data = getInitialData() -ReactDOM.render(, document.getElementById('root')) +// Updated from react-dom's `render` to `createRoot.render` for React 18: +// https://react.dev/blog/2022/03/08/react-18-upgrade-guide#updates-to-client-rendering-apis +const root = createRoot(document.getElementById('root')) +root.render() \ No newline at end of file diff --git a/package.json b/package.json index 41e82c34..b880732b 100644 --- a/package.json +++ b/package.json @@ -81,10 +81,10 @@ "moment": "^2.29.4", "pretty": "^2.0.0", "prop-types": "^15.7.2", - "react": "^16.14.0", - "react-dom": "^16.14.0", + "react": "18.3.1", + "react-dom": "^18.3.1", "react-html-parser": "^2.0.2", - "react-router-dom": "^5.2.0", + "react-router-dom": "^6.27.0", "reactstrap": "^8.7.1" }, "resolutions": { diff --git a/yarn.lock b/yarn.lock index b706f4df..c4114f40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1129,7 +1129,7 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.14.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6" integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA== @@ -3130,6 +3130,11 @@ lodash "^4.17.19" prop-types "^15.7.2" +"@remix-run/router@1.21.0": + version "1.21.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.21.0.tgz#c65ae4262bdcfe415dbd4f64ec87676e4a56e2b5" + integrity sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA== + "@sheerun/mutationobserver-shim@^0.3.2": version "0.3.3" resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz#5405ee8e444ed212db44e79351f0c70a582aae25" @@ -6618,18 +6623,6 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -history@^4.9.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" - integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== - dependencies: - "@babel/runtime" "^7.1.2" - loose-envify "^1.2.0" - resolve-pathname "^3.0.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - value-equal "^1.0.1" - hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -6639,7 +6632,7 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -8033,7 +8026,7 @@ lolex@^5.0.1: dependencies: "@sinonjs/commons" "^1.7.0" -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -8240,14 +8233,6 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -mini-create-react-context@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz#072171561bfdc922da08a60c2197a497cc2d1d5e" - integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ== - dependencies: - "@babel/runtime" "^7.12.1" - tiny-warning "^1.0.3" - "mini-css-extract-plugin@>=0.4.0 <0.4.3": version "0.4.2" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.2.tgz#b3ecc0d6b1bbe5ff14add42b946a7b200cf78651" @@ -9680,15 +9665,13 @@ react-codemirror2@^7.1.0: resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-7.2.1.tgz#38dab492fcbe5fb8ebf5630e5bb7922db8d3a10c" integrity sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw== -react-dom@^16.14.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89" - integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw== +react-dom@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" + integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.1" + scheduler "^0.23.2" react-html-parser@^2.0.2: version "2.0.2" @@ -9697,7 +9680,7 @@ react-html-parser@^2.0.2: dependencies: htmlparser2 "^3.9.0" -react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: +react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -9725,34 +9708,20 @@ react-popper@^1.3.6: typed-styles "^0.0.7" warning "^4.0.2" -react-router-dom@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662" - integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA== +react-router-dom@^6.27.0: + version "6.28.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.28.1.tgz#b78fe452d2cd31919b80e57047a896bfa1509f8c" + integrity sha512-YraE27C/RdjcZwl5UCqF/ffXnZDxpJdk9Q6jw38SZHjXs7NNdpViq2l2c7fO7+4uWaEfcwfGCv3RSg4e1By/fQ== dependencies: - "@babel/runtime" "^7.1.2" - history "^4.9.0" - loose-envify "^1.3.1" - prop-types "^15.6.2" - react-router "5.2.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" + "@remix-run/router" "1.21.0" + react-router "6.28.1" -react-router@5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293" - integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw== +react-router@6.28.1: + version "6.28.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.28.1.tgz#f82317ab24eee67d7beb7b304c0378b2b48fa178" + integrity sha512-2omQTA3rkMljmrvvo6WtewGdVh45SpL9hGiCI9uUrwGGfNFDIvGK4gYJsKlJoNVi6AQZcopSCballL+QGOm7fA== dependencies: - "@babel/runtime" "^7.1.2" - history "^4.9.0" - hoist-non-react-statics "^3.1.0" - loose-envify "^1.3.1" - mini-create-react-context "^0.4.0" - path-to-regexp "^1.7.0" - prop-types "^15.6.2" - react-is "^16.6.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" + "@remix-run/router" "1.21.0" react-transition-group@^2.3.1: version "2.9.0" @@ -9764,14 +9733,12 @@ react-transition-group@^2.3.1: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" -react@^16.14.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" - integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== +react@18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" reactstrap@^8.7.1: version "8.9.0" @@ -9996,11 +9963,6 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-pathname@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" - integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== - resolve-url-loader@^3.1.2: version "3.1.4" resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.4.tgz#3c16caebe0b9faea9c7cc252fa49d2353c412320" @@ -10189,13 +10151,12 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -scheduler@^0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" - integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== +scheduler@^0.23.2: + version "0.23.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" + integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" schema-utils@^1.0.0: version "1.0.0" @@ -10977,16 +10938,6 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -tiny-invariant@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" - integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== - -tiny-warning@^1.0.0, tiny-warning@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" - integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== - tinycolor2@^1.4.1, tinycolor2@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" @@ -11356,11 +11307,6 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -value-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" - integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== - vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"