import Engine from 'publicodes' import { Documentation, getDocumentationSiteMap } from 'publicodes-react' import { invertObj, last } from 'ramda' import React, { useCallback, useEffect, useMemo, useState } from 'react' import MonacoEditor from 'react-monaco-editor' import { useHistory, useLocation } from 'react-router-dom' import styled from 'styled-components' const EXAMPLE_CODE = ` # Bienvenue dans le bac à sable du langage publicodes ! # Pour en savoir plus sur le langage : # => https://publi.codes/documentation/principes-de-base prix: prix . carottes: 2€/kg prix . champignons: 5€/kg prix . avocat: 2€/avocat dépenses primeur: formule: somme: - prix . carottes * 1.5 kg - prix . champignons * 500g - prix . avocat * 3 avocat ` export default function Studio() { const { search, pathname } = useLocation() const initialValue = useMemo(() => { const code = new URLSearchParams(search ?? '').get('code') return code ? code : EXAMPLE_CODE }, [search]) const [editorValue, setEditorValue] = useState(initialValue) const debouncedEditorValue = useDebounce(editorValue, 1000) const history = useHistory() useEffect(() => { history.replace({ pathname, search: `?code=${encodeURIComponent(debouncedEditorValue)}`, }) }, [debouncedEditorValue, history]) const handleShare = useCallback(() => { navigator.clipboard.writeText(window.location.href) }, [window.location.href]) return ( setEditorValue(newValue ?? '')} options={{ minimap: { enabled: false }, }} />
{/* TODO: prévoir de changer la signature de EngineProvider */}
) } type ResultsProps = { rules: string onClickShare: React.MouseEventHandler } class Logger { messages: string[] = [] warn(message: string) { this.messages.push(message) } error(message: string) { this.messages.push(message) } log(message: string) { this.messages.push(message) } toJSX() { return this.messages.map((m) => (
{nl2br(m)}
)) } } export const Results = ({ onClickShare, rules }: ResultsProps) => { const logger = useMemo(() => new Logger(), [rules]) const engine = useMemo(() => new Engine(rules, { logger }), [rules, logger]) const targets = useMemo(() => Object.keys(engine.getParsedRules()), [engine]) const documentationPath = '/studio' const pathToRules = useMemo( () => getDocumentationSiteMap({ engine, documentationPath }), [engine, documentationPath] ) const ruleToPaths = useMemo(() => invertObj(pathToRules), [pathToRules]) const { search, pathname } = useLocation() const history = useHistory() const setCurrentTarget = useCallback( (target) => history.replace({ pathname: ruleToPaths[target], search, }), [ruleToPaths, history, search] ) useEffect(() => { if (!pathToRules[pathname]) { setCurrentTarget(last(targets)) } }) return ( <> {logger.toJSX()}
Aller à{' '}
) } const newlineRegex = /(\r\n|\r|\n)/g function nl2br(str: string) { if (typeof str !== 'string') { return str } return str.split(newlineRegex).map(function (line, index) { if (line.match(newlineRegex)) { return React.createElement('br', { key: index }) } return line }) } const Layout = styled.div` flex-grow: 1; display: flex; height: 100%; > :first-child { width: 55% !important; } @media (max-width: 960px) { flex-direction: column; padding: 20px; > :first-child { width: 100% !important; } } ` class ErrorBoundary extends React.Component { state: { error: false | { message: string; name: string } } = { error: false } static getDerivedStateFromError(error: Error) { console.error(error) return { error: { message: error.message, name: error.name } } } render() { if (this.state.error) { return (
{this.state.error.name}
{nl2br(this.state.error.message)}

window.location.reload()}>Rafraichir
) } return this.props.children } } function useDebounce(value: T, delay: number) { const [debouncedValue, setDebouncedValue] = useState(value) useEffect( () => { // Update debounced value after delay const handler = setTimeout(() => { setDebouncedValue(value) }, delay) // Cancel the timeout if value changes (also on delay change or unmount) // This is how we prevent debounced value from updating if value is changed ... // .. within the delay period. Timeout gets cleared and restarted. return () => { clearTimeout(handler) } }, [value, delay] // Only re-call effect if value or delay changes ) return debouncedValue }