@@ -42,6 +42,7 @@
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-image-crop": "^8.6.6",
|
||||
"react-mosaic-component": "^4.1.1",
|
||||
"react-tabs": "^3.2.2",
|
||||
"react-three-fiber": "^5.3.19",
|
||||
"rich-markdown-editor": "^11.0.2",
|
||||
"styled-components": "^5.2.0",
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
import { useContext } from 'react'
|
||||
import { Menu } from '@headlessui/react'
|
||||
|
||||
import { IdeContext } from 'src/components/IdeToolbarNew/IdeToolbarNew'
|
||||
import Svg from 'src/components/Svg/Svg'
|
||||
import { useRender } from 'src/components/IdeToolbarNew/useRender'
|
||||
import {makeStlDownloadHandler, PullTitleFromFirstLine} from './helpers'
|
||||
|
||||
const EditorMenu = () => {
|
||||
const handleRender = useRender()
|
||||
const { state, thunkDispatch } = useContext(IdeContext)
|
||||
const handleStlDownload = makeStlDownloadHandler({
|
||||
type: state.objectData?.type,
|
||||
geometry: state.objectData?.data,
|
||||
fileName: PullTitleFromFirstLine(state.code || ''),
|
||||
thunkDispatch,
|
||||
})
|
||||
return (
|
||||
<div className="bg-gray-500 flex items-center h-9 w-full cursor-grab">
|
||||
<div className=" text-gray-500 bg-gray-300 cursor-grab px-2 h-full flex items-center">
|
||||
@@ -14,9 +28,10 @@ const EditorMenu = () => {
|
||||
</button>
|
||||
<div className="w-px h-full bg-gray-300"/>
|
||||
<div className="flex gap-6 px-6">
|
||||
<button className="text-gray-100">
|
||||
File
|
||||
</button>
|
||||
<FileDropdown
|
||||
handleRender={handleRender}
|
||||
handleStlDownload={handleStlDownload}
|
||||
/>
|
||||
<button className="text-gray-100">
|
||||
Edit
|
||||
</button>
|
||||
@@ -29,3 +44,37 @@ const EditorMenu = () => {
|
||||
}
|
||||
|
||||
export default EditorMenu
|
||||
|
||||
function FileDropdown({handleRender, handleStlDownload}) {
|
||||
return (
|
||||
<Menu>
|
||||
<Menu.Button className="text-gray-100">File</Menu.Button>
|
||||
<Menu.Items className="absolute flex flex-col mt-10 bg-gray-500 rounded shadow-md text-gray-100">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
className={`${active && 'bg-gray-600'} px-2 py-1`}
|
||||
onClick={handleRender}
|
||||
>
|
||||
Save & Render <span className="text-gray-400 pl-4">{
|
||||
/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ?
|
||||
<><Svg name="mac-cmd-key" className="h-3 w-3 inline-block text-left" />S</> :
|
||||
'Ctrl S'
|
||||
}</span>
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
className={`${active && 'bg-gray-600'} px-2 py-1 text-left`}
|
||||
onClick={handleStlDownload}
|
||||
>
|
||||
Download STL
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
|
||||
59
app/web/src/components/EditorMenu/helpers.ts
Normal file
59
app/web/src/components/EditorMenu/helpers.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { flow, identity } from 'lodash/fp'
|
||||
import { fileSave } from 'browser-fs-access'
|
||||
import { MeshBasicMaterial, Mesh, Scene } from 'three'
|
||||
import { STLExporter } from 'three/examples/jsm/exporters/STLExporter'
|
||||
import { requestRender } from 'src/helpers/hooks/useIdeState'
|
||||
|
||||
export const PullTitleFromFirstLine = (code: string = '') => {
|
||||
const firstLine = code.split('\n').filter(identity)[0] || ''
|
||||
if (!(firstLine.startsWith('//') || firstLine.startsWith('#'))) {
|
||||
return 'object.stl'
|
||||
}
|
||||
return (
|
||||
(firstLine.replace(/^(\/\/|#)\s*(.+)/, (_, __, titleWithSpaces) =>
|
||||
titleWithSpaces.replaceAll(/\s/g, '-')
|
||||
) || 'object') + '.stl'
|
||||
)
|
||||
}
|
||||
|
||||
export const makeStlDownloadHandler = (({ geometry, fileName, type, thunkDispatch}) => () => {
|
||||
const makeStlBlobFromGeo = flow(
|
||||
(geo) => new Mesh(geo, new MeshBasicMaterial()),
|
||||
(mesh) => new Scene().add(mesh),
|
||||
(scene) => new STLExporter().parse(scene),
|
||||
(stl) =>
|
||||
new Blob([stl], {
|
||||
type: 'text/plain',
|
||||
})
|
||||
)
|
||||
const saveFile = (geometry) => {
|
||||
const blob = makeStlBlobFromGeo(geometry)
|
||||
fileSave(blob, {
|
||||
fileName,
|
||||
extensions: ['.stl'],
|
||||
})
|
||||
}
|
||||
if (geometry) {
|
||||
if (type === 'geometry') {
|
||||
saveFile(geometry)
|
||||
} else {
|
||||
thunkDispatch((dispatch, getState) => {
|
||||
const state = getState()
|
||||
if (state.ideType === 'openScad') {
|
||||
thunkDispatch((dispatch, getState) => {
|
||||
const state = getState()
|
||||
dispatch({ type: 'setLoading' })
|
||||
requestRender({
|
||||
state,
|
||||
dispatch,
|
||||
code: state.code,
|
||||
viewerSize: state.viewerSize,
|
||||
camera: state.camera,
|
||||
specialCadProcess: 'stl',
|
||||
}).then((result) => result && saveFile(result.data))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,23 +1,57 @@
|
||||
import Svg from 'src/components/Svg/Svg'
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||
import { copyTextToClipboard } from 'src/helpers/clipboard'
|
||||
import { encode } from 'src/helpers/compress'
|
||||
|
||||
const TopButton = ({children}) => (
|
||||
<button className="flex bg-gray-500 h-10 justify-center items-center px-4 rounded">
|
||||
const TopButton = ({children, onClick}: {onClick?: () => void, children: React.ReactNode}) => (
|
||||
<button onClick={onClick} className="flex bg-gray-500 h-10 justify-center items-center px-4 rounded">
|
||||
<div className="rounded-full bg-gray-200 h-6 w-6 mr-4"/>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
|
||||
const IdeHeader = () => {
|
||||
const IdeHeader = ({handleRender}: {handleRender: () => void}) => {
|
||||
return (
|
||||
<div className="h-16 bg-gray-900 flex justify-between items-center">
|
||||
<div className="h-16 w-full bg-gray-900 flex justify-between items-center">
|
||||
<div className="bg-gray-700 pr-48 h-full">
|
||||
<div className="w-16 h-full flex items-center justify-center bg-gray-900">
|
||||
<Svg className="w-12" name="favicon" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-gray-200 flex gap-4 mr-4">
|
||||
<TopButton>Render</TopButton>
|
||||
<TopButton onClick={handleRender}>Render</TopButton>
|
||||
|
||||
|
||||
<Popover className="relative outline-none w-full h-full">
|
||||
{({open}) => {
|
||||
const encodedLink = makeEncodedLink('bing bong')
|
||||
return (
|
||||
<>
|
||||
<Popover.Button className="h-full w-full outline-none">
|
||||
<TopButton>Share</TopButton>
|
||||
</Popover.Button>
|
||||
|
||||
{open && <Popover.Panel className="absolute z-10 mt-4 right-0">
|
||||
<Tabs
|
||||
className="bg-gray-300 rounded-md shadow-md overflow-hidden text-gray-700"
|
||||
selectedTabClassName="bg-gray-200"
|
||||
>
|
||||
<TabPanel className="p-4">
|
||||
<p className="text-sm pb-4 border-b border-gray-700">Encodes your CodeCad script into a URL so that you can share your work</p>
|
||||
<input value={encodedLink.replace(/^.+:\/\//g, '')} readOnly className="p-1 mt-4 text-xs rounded-t border border-gray-700 w-full" />
|
||||
<button className="w-full bg-gray-700 py-1 rounded-b text-gray-300" onClick={() => copyTextToClipboard(encodedLink)} >Copy URL</button>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<h2 className="h-32">Any content 2</h2>
|
||||
</TabPanel>
|
||||
|
||||
<TabList className="flex whitespace-nowrap text-gray-700 border-t border-gray-700">
|
||||
<Tab className="p-3 px-5">encoded script</Tab>
|
||||
<Tab className="p-3 px-5">external script</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
</Popover.Panel>}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Popover>
|
||||
<TopButton>Fork</TopButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -25,3 +59,10 @@ const IdeHeader = () => {
|
||||
}
|
||||
|
||||
export default IdeHeader
|
||||
|
||||
const scriptKeyV2 = 'encoded_script_v2' // todo don't leave here
|
||||
|
||||
function makeEncodedLink(code: string): string {
|
||||
const encodedScript = encode(code)
|
||||
return `${location.href}#${scriptKeyV2}=${encodedScript}`
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import Svg from 'src/components/Svg/Svg'
|
||||
|
||||
const IdeSideBar = () => {
|
||||
return (
|
||||
<div className="h-full flex flex-col justify-end">
|
||||
<div className="h-full flex flex-col justify-between">
|
||||
<div className="w-16 h-16 flex items-center justify-center bg-gray-900">
|
||||
<Svg className="w-12" name="favicon" />
|
||||
</div>
|
||||
<button className=" text-gray-300 p-2 pb-4 flex justify-center" aria-label="IDE settings">
|
||||
<Svg name="big-gear" />
|
||||
</button>
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { createContext, useEffect } from 'react'
|
||||
import IdeContainer from 'src/components/IdeContainer'
|
||||
import { isBrowser } from '@redwoodjs/prerender/browserUtils'
|
||||
import { useIdeState, makeCodeStoreKey } from 'src/helpers/hooks/useIdeState'
|
||||
import { copyTextToClipboard } from 'src/helpers/clipboard'
|
||||
import { requestRender } from 'src/helpers/hooks/useIdeState'
|
||||
import { encode, decode } from 'src/helpers/compress'
|
||||
import { flow, identity } from 'lodash/fp'
|
||||
import { fileSave } from 'browser-fs-access'
|
||||
import { MeshBasicMaterial, Mesh, Scene } from 'three'
|
||||
import { STLExporter } from 'three/examples/jsm/exporters/STLExporter'
|
||||
import { useIdeState } from 'src/helpers/hooks/useIdeState'
|
||||
import { handleRenderVerbose } from './useRender'
|
||||
import { decode } from 'src/helpers/compress'
|
||||
import { flow } from 'lodash/fp'
|
||||
import OutBound from 'src/components/OutBound'
|
||||
import IdeSideBar from 'src/components/IdeSideBar'
|
||||
import IdeHeader from 'src/components/IdeHeader'
|
||||
|
||||
export const githubSafe = (url) =>
|
||||
url.includes('github.com')
|
||||
@@ -65,83 +62,8 @@ const IdeToolbarNew = ({ cadPackage }) => {
|
||||
window.location.hash = ''
|
||||
}, [cadPackage])
|
||||
function handleRender() {
|
||||
thunkDispatch((dispatch, getState) => {
|
||||
const state = getState()
|
||||
dispatch({ type: 'setLoading' })
|
||||
requestRender({
|
||||
state,
|
||||
dispatch,
|
||||
code: state.code,
|
||||
viewerSize: state.viewerSize,
|
||||
camera: state.camera,
|
||||
})
|
||||
})
|
||||
localStorage.setItem(makeCodeStoreKey(state.ideType), state.code)
|
||||
return handleRenderVerbose({thunkDispatch, state})
|
||||
}
|
||||
function handleMakeLink() {
|
||||
if (isBrowser) {
|
||||
const encodedScript = encode(state.code)
|
||||
window.location.hash = `${scriptKeyV2}=${encodedScript}`
|
||||
copyTextToClipboard(window.location.href)
|
||||
}
|
||||
}
|
||||
const PullTitleFromFirstLine = (code = '') => {
|
||||
const firstLine = code.split('\n').filter(identity)[0] || ''
|
||||
if (!(firstLine.startsWith('//') || firstLine.startsWith('#'))) {
|
||||
return 'object.stl'
|
||||
}
|
||||
return (
|
||||
(firstLine.replace(/^(\/\/|#)\s*(.+)/, (_, __, titleWithSpaces) =>
|
||||
titleWithSpaces.replaceAll(/\s/g, '-')
|
||||
) || 'object') + '.stl'
|
||||
)
|
||||
}
|
||||
|
||||
const handleStlDownload = (({ geometry, fileName, type }) => () => {
|
||||
const makeStlBlobFromGeo = flow(
|
||||
(geo) => new Mesh(geo, new MeshBasicMaterial()),
|
||||
(mesh) => new Scene().add(mesh),
|
||||
(scene) => new STLExporter().parse(scene),
|
||||
(stl) =>
|
||||
new Blob([stl], {
|
||||
type: 'text/plain',
|
||||
})
|
||||
)
|
||||
const saveFile = (geometry) => {
|
||||
const blob = makeStlBlobFromGeo(geometry)
|
||||
fileSave(blob, {
|
||||
fileName,
|
||||
extensions: ['.stl'],
|
||||
})
|
||||
}
|
||||
if (geometry) {
|
||||
if (type === 'geometry') {
|
||||
saveFile(geometry)
|
||||
} else {
|
||||
thunkDispatch((dispatch, getState) => {
|
||||
const state = getState()
|
||||
if (state.ideType === 'openScad') {
|
||||
thunkDispatch((dispatch, getState) => {
|
||||
const state = getState()
|
||||
dispatch({ type: 'setLoading' })
|
||||
requestRender({
|
||||
state,
|
||||
dispatch,
|
||||
code: state.code,
|
||||
viewerSize: state.viewerSize,
|
||||
camera: state.camera,
|
||||
specialCadProcess: 'stl',
|
||||
}).then((result) => result && saveFile(result.data))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})({
|
||||
type: state.objectData?.type,
|
||||
geometry: state.objectData?.data,
|
||||
fileName: PullTitleFromFirstLine(state.code),
|
||||
})
|
||||
|
||||
return (
|
||||
<IdeContext.Provider value={{ state, thunkDispatch }}>
|
||||
@@ -150,6 +72,9 @@ const IdeToolbarNew = ({ cadPackage }) => {
|
||||
<IdeSideBar />
|
||||
</div>
|
||||
<div className="h-full flex flex-grow flex-col">
|
||||
<nav className="flex">
|
||||
<IdeHeader handleRender={handleRender} />
|
||||
</nav>
|
||||
<div className="py-2 bg-pink-200">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
We're still working on this. Since you're here, have a look what{' '}
|
||||
@@ -162,26 +87,6 @@ const IdeToolbarNew = ({ cadPackage }) => {
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
<nav className="flex">
|
||||
<button
|
||||
onClick={handleRender}
|
||||
className="border-2 px-2 text-gray-700 text-sm m-1"
|
||||
>
|
||||
Render
|
||||
</button>
|
||||
<button
|
||||
onClick={handleMakeLink}
|
||||
className="border-2 text-gray-700 px-2 text-sm m-1 ml-2"
|
||||
>
|
||||
Copy link
|
||||
</button>
|
||||
<button
|
||||
onClick={handleStlDownload}
|
||||
className="border-2 text-gray-700 px-2 text-sm m-1 ml-2"
|
||||
>
|
||||
Download STL
|
||||
</button>
|
||||
</nav>
|
||||
<IdeContainer />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
25
app/web/src/components/IdeToolbarNew/useRender.ts
Normal file
25
app/web/src/components/IdeToolbarNew/useRender.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { makeCodeStoreKey } from 'src/helpers/hooks/useIdeState'
|
||||
import { requestRender } from 'src/helpers/hooks/useIdeState'
|
||||
import { useContext } from 'react'
|
||||
import { IdeContext } from 'src/components/IdeToolbarNew/IdeToolbarNew'
|
||||
|
||||
export const handleRenderVerbose = ({thunkDispatch, state}) => {
|
||||
thunkDispatch((dispatch, getState) => {
|
||||
const state = getState()
|
||||
dispatch({ type: 'setLoading' })
|
||||
requestRender({
|
||||
state,
|
||||
dispatch,
|
||||
code: state.code,
|
||||
viewerSize: state.viewerSize,
|
||||
camera: state.camera,
|
||||
})
|
||||
})
|
||||
localStorage.setItem(makeCodeStoreKey(state.ideType), state.code)
|
||||
}
|
||||
|
||||
export const useRender = () => {
|
||||
const { state, thunkDispatch } = useContext(IdeContext)
|
||||
return () => handleRenderVerbose({thunkDispatch, state})
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ type SvgNames = 'arrow-down' |
|
||||
'gear' |
|
||||
'lightbulb' |
|
||||
'logout' |
|
||||
'mac-cmd-key' |
|
||||
'pencil' |
|
||||
'plus' |
|
||||
'plus-circle' |
|
||||
@@ -323,6 +324,18 @@ const Svg = ({ name, className: className2 = '', strokeWidth = 2 }: {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
'mac-cmd-key': (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 220 220"
|
||||
>
|
||||
<
|
||||
path
|
||||
fill="currentColor"
|
||||
d="M57.254,121.064c-17.735,0-31.45,4.723-40.765,14.035c-19.39,19.391-19.39,50.943,0,70.334 C25.884,214.828,38.375,220,51.66,220c0,0,0,0,0,0c13.284,0,25.774-5.172,35.168-14.566c11.069-11.07,14.037-27.061,14.041-41.016 c0.001-3.531,0.017-10.09,0.017-10.09h18.227c0,0,0.016,6.559,0.018,10.09c0.004,13.955,2.971,29.945,14.041,41.016 C142.565,214.828,155.056,220,168.34,220c13.285,0,25.775-5.172,35.17-14.566c19.391-19.391,19.391-50.943,0-70.334 c-9.314-9.312-23.029-14.035-40.764-14.035c-3.602,0-10.346,0.02-10.346,0.02c0-0.932,0-22.178,0-22.178s6.744,0.029,10.346,0.029 c17.734,0,31.449-4.721,40.762-14.033c19.391-19.392,19.391-50.943,0.002-70.334C194.113,5.174,181.624,0,168.34,0 c-13.285,0-25.773,5.174-35.168,14.566c-10.67,10.672-13.812,25.914-14.029,39.498l-0.193,11.609H101.05l-0.192-11.609 c-0.217-13.584-3.359-28.826-14.03-39.498C77.434,5.174,64.944,0,51.66,0C38.376,0,25.886,5.174,16.49,14.568 C-2.899,33.959-2.899,65.51,16.491,84.902c9.314,9.313,23.028,14.033,40.762,14.033c3.601,0,10.346-0.029,10.346-0.029 s0,21.246,0,22.178C67.6,121.084,60.855,121.064,57.254,121.064z M154.328,35.587c3.727-3.726,8.683-5.779,13.954-5.779 s10.229,2.053,13.957,5.781c7.692,7.693,7.692,20.213-0.002,27.906c-3.384,3.385-10.327,5.248-19.549,5.248 c-4.6,0-14.107,0-14.107,0v-8.688C148.581,60.056,147.566,41.495,154.328,35.587z M148.581,159.945v-8.688c0,0,9.508,0,14.107,0 c9.222,0,16.165,1.863,19.549,5.248c7.694,7.693,7.694,20.213,0.002,27.906c-3.729,3.729-8.686,5.781-13.957,5.781 s-10.228-2.053-13.954-5.779C147.566,178.506,148.581,159.945,148.581,159.945z M93.75,93.75h32.5v32.5h-32.5V93.75z M57.312,68.743 c-9.222,0-16.165-1.863-19.549-5.248c-7.694-7.693-7.694-20.213-0.002-27.906c3.729-3.729,8.686-5.781,13.957-5.781 s10.228,2.053,13.954,5.779c6.762,5.908,5.747,24.469,5.747,24.469v8.688C71.419,68.743,61.911,68.743,57.312,68.743z M71.419,151.258v8.688c0,0,1.015,18.561-5.747,24.469c-3.727,3.727-8.683,5.779-13.954,5.779s-10.229-2.053-13.957-5.781 c-7.692-7.693-7.692-20.213,0.002-27.906c3.384-3.385,10.327-5.248,19.549-5.248C61.911,151.258,71.419,151.258,71.419,151.258z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
pencil: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
function fallbackCopyTextToClipboard(text) {
|
||||
import { toast } from '@redwoodjs/web/toast'
|
||||
|
||||
function fallbackCopyTextToClipboard(text: string) {
|
||||
var textArea = document.createElement('textarea')
|
||||
textArea.value = text
|
||||
|
||||
@@ -21,17 +23,28 @@ function fallbackCopyTextToClipboard(text) {
|
||||
|
||||
document.body.removeChild(textArea)
|
||||
}
|
||||
export function copyTextToClipboard(text) {
|
||||
|
||||
const clipboardSuccessToast = (text: string) => toast.success(() => (
|
||||
<div className="overflow-hidden">
|
||||
<p>link added to clipboard.</p>
|
||||
</div>
|
||||
))
|
||||
|
||||
const makeClipboardCopier = (success: Function) => (text: string) => {
|
||||
if (!navigator.clipboard) {
|
||||
fallbackCopyTextToClipboard(text)
|
||||
success(text)
|
||||
return
|
||||
}
|
||||
navigator.clipboard.writeText(text).then(
|
||||
function () {
|
||||
console.log('Async: Copying to clipboard was successful!')
|
||||
success(text)
|
||||
},
|
||||
function (err) {
|
||||
console.error('Async: Could not copy text: ', err)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const copyTextToClipboard = makeClipboardCopier(clipboardSuccessToast)
|
||||
@@ -1,6 +1,6 @@
|
||||
import Seo from 'src/components/Seo/Seo'
|
||||
import IdeToolbar from 'src/components/IdeToolbarNew'
|
||||
import IdeHeader from 'src/components/IdeHeader'
|
||||
import { Toaster } from '@redwoodjs/web/toast'
|
||||
|
||||
const DevIdePage = ({ cadPackage }) => {
|
||||
return (
|
||||
@@ -10,11 +10,9 @@ const DevIdePage = ({ cadPackage }) => {
|
||||
description="new ide in development"
|
||||
lang="en-US"
|
||||
/>
|
||||
<IdeHeader />
|
||||
<div className="flex-auto">
|
||||
<Toaster timeout={9000} />
|
||||
<IdeToolbar cadPackage={cadPackage} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -6827,7 +6827,7 @@ cloudinary@^1.23.0:
|
||||
lodash "^4.17.11"
|
||||
q "^1.5.1"
|
||||
|
||||
clsx@^1.0.4, clsx@^1.1.1:
|
||||
clsx@^1.0.4, clsx@^1.1.0, clsx@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
|
||||
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
|
||||
@@ -14898,7 +14898,7 @@ prompts@2.4.1, prompts@^2.0.1:
|
||||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||
@@ -15610,6 +15610,14 @@ react-syntax-highlighter@^13.5.0, react-syntax-highlighter@^13.5.3:
|
||||
prismjs "^1.21.0"
|
||||
refractor "^3.1.0"
|
||||
|
||||
react-tabs@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-tabs/-/react-tabs-3.2.2.tgz#07bdc3cdb17bdffedd02627f32a93cd4b3d6e4d0"
|
||||
integrity sha512-/o52eGKxFHRa+ssuTEgSM8qORnV4+k7ibW+aNQzKe+5gifeVz8nLxCrsI9xdRhfb0wCLdgIambIpb1qCxaMN+A==
|
||||
dependencies:
|
||||
clsx "^1.1.0"
|
||||
prop-types "^15.5.0"
|
||||
|
||||
react-textarea-autosize@^8.1.1:
|
||||
version "8.3.3"
|
||||
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8"
|
||||
|
||||
Reference in New Issue
Block a user