IDE redesign, initial implementation #362
131
app/web/src/components/EncodedUrl/ExternalScript.tsx
Normal file
131
app/web/src/components/EncodedUrl/ExternalScript.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import { useState } from 'react'
|
||||
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
|
||||
import OutBound from 'src/components/OutBound/OutBound'
|
||||
import { prepareEncodedUrl, makeExternalUrl } from './helpers'
|
||||
import { copyTextToClipboard } from 'src/helpers/clipboard'
|
||||
import { useRender } from 'src/components/IdeWrapper/useRender'
|
||||
import { toast } from '@redwoodjs/web/toast'
|
||||
|
||||
const ideTypeNameMap = {
|
||||
|
|
||||
openScad: 'OpenSCAD',
|
||||
cadQuery: 'CadQuery',
|
||||
}
|
||||
|
||||
const ExternalScript = () => {
|
||||
const { state, thunkDispatch } = useIdeContext()
|
||||
const handleRender = useRender()
|
||||
const [rawUrl, setRawUrl] = useState('')
|
||||
const [script, setScript] = useState('')
|
||||
const [asyncState, setAsyncState] =
|
||||
useState<'INIT' | 'SUCCESS' | 'ERROR' | 'LOADING'>('INIT')
|
||||
|
||||
const cadName = ideTypeNameMap[state.ideType]
|
||||
|
||||
const onPaste: React.ClipboardEventHandler<HTMLInputElement> = async ({
|
||||
clipboardData,
|
||||
}) => {
|
||||
const url = clipboardData.getData('Text')
|
||||
processUserUrl(url)
|
||||
}
|
||||
|
When a user pastes a url it will immediately start processing it without having a "submit" button. I think this is appropriate since the whole point is to link to an external URL and no one will be typing those, you can type and hit enter though. When a user pastes a url it will immediately start processing it without having a "submit" button. I think this is appropriate since the whole point is to link to an external URL and no one will be typing those, you can type and hit enter though.
|
||||
const onChange: React.ChangeEventHandler<HTMLInputElement> = async ({
|
||||
target,
|
||||
}) => setRawUrl(target.value)
|
||||
const onKeyDown = async ({ key, target }) =>
|
||||
key === 'Enter' && processUserUrl(target.value)
|
||||
|
||||
async function processUserUrl(url: string) {
|
||||
setRawUrl(url)
|
||||
try {
|
||||
setAsyncState('LOADING')
|
||||
const response = await fetch(prepareEncodedUrl(url))
|
||||
if (response.status === 404) throw new Error("couldn't find script")
|
||||
const script2 = await response.text()
|
||||
if (script2.startsWith('<!DOCTYPE html>'))
|
||||
throw new Error('got html document, not a script')
|
||||
setScript(script2)
|
||||
setAsyncState('SUCCESS')
|
||||
} catch (e) {
|
||||
setAsyncState('ERROR')
|
||||
toast.error(
|
||||
"We had trouble with you're URL, are you sure it was correct?"
|
||||
)
|
||||
}
|
||||
}
|
||||
const onCopyRender: React.MouseEventHandler<HTMLButtonElement> = () => {
|
||||
copyTextToClipboard(makeExternalUrl(rawUrl))
|
||||
thunkDispatch({ type: 'updateCode', payload: script })
|
||||
setTimeout(handleRender)
|
||||
}
|
||||
return (
|
||||
<div className="p-4">
|
||||
<p className="text-sm pb-4">
|
||||
Paste an external url containing a {cadName} script to generate a new
|
||||
CadHub url for this resource.{' '}
|
||||
<OutBound
|
||||
className="underline text-gray-500"
|
||||
to="TODO learn.cadhub link"
|
||||
>
|
||||
Learn more
|
||||
</OutBound>{' '}
|
||||
about this feature.
|
||||
</p>
|
||||
{['INIT', 'ERROR'].includes(asyncState) && (
|
||||
<>
|
||||
<p>Paste url</p>
|
||||
<input
|
||||
className="p-1 text-xs rounded border border-gray-700 w-full"
|
||||
value={rawUrl}
|
||||
onChange={onChange}
|
||||
onPaste={onPaste}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{asyncState === 'ERROR' && (
|
||||
<p className="text-sm text-red-800">That didn't work, try again.</p>
|
||||
)}
|
||||
{asyncState === 'LOADING' && (
|
||||
<div className="h-10 relative">
|
||||
<div className="inset-0 absolute flex items-center justify-center">
|
||||
<div className="h-6 w-6 bg-pink-600 rounded-full animate-ping"></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{asyncState === 'SUCCESS' && (
|
||||
<>
|
||||
<input
|
||||
value={makeExternalUrl(rawUrl).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(makeExternalUrl(rawUrl))}
|
||||
>
|
||||
Copy URL
|
||||
</button>
|
||||
<div className="flex flex-col gap-2 pt-2">
|
||||
<button
|
||||
className="bg-gray-500 p-1 px-2 rounded text-gray-300"
|
||||
onClick={onCopyRender}
|
||||
>
|
||||
Copy & Render
|
||||
</button>
|
||||
<button
|
||||
className="bg-gray-500 p-1 px-2 rounded text-gray-300"
|
||||
onClick={() => {
|
||||
setAsyncState('INIT')
|
||||
setRawUrl('')
|
||||
setScript('')
|
||||
}}
|
||||
>
|
||||
Create another URL
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ExternalScript
|
||||
@@ -6,7 +6,7 @@ const FullScriptEncoding = () => {
|
||||
const { state } = useIdeContext()
|
||||
const encodedLink = makeEncodedLink(state.code)
|
||||
return (
|
||||
<>
|
||||
<div 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>
|
||||
@@ -21,7 +21,7 @@ const FullScriptEncoding = () => {
|
||||
>
|
||||
Copy URL
|
||||
</button>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,6 @@ const scriptKey = 'encoded_script'
|
||||
const scriptKeyV2 = 'encoded_script_v2'
|
||||
const fetchText = 'fetch_text_v1'
|
||||
|
||||
export function makeEncodedLink(code: string): string {
|
||||
const encodedScript = encode(code)
|
||||
return `${location.origin}${location.pathname}#${scriptKeyV2}=${encodedScript}`
|
||||
}
|
||||
|
||||
export const githubSafe = (url: string): string =>
|
||||
url.includes('github.com')
|
||||
? url
|
||||
@@ -22,7 +17,20 @@ export const githubSafe = (url: string): string =>
|
||||
.replace('/blob/', '/')
|
||||
: url
|
||||
|
||||
const prepareEncodedUrl = flow(decodeURIComponent, githubSafe)
|
||||
export const prepareEncodedUrl = flow(decodeURIComponent, githubSafe)
|
||||
|
||||
const prepareDecodedUrl = flow(githubSafe, encodeURIComponent)
|
||||
|
||||
export function makeEncodedLink(code: string): string {
|
||||
const encodedScript = encode(code)
|
||||
return `${location.origin}${location.pathname}#${scriptKeyV2}=${encodedScript}`
|
||||
}
|
||||
|
||||
export function makeExternalUrl(resourceUrl: string): string {
|
||||
return `${location.origin}${
|
||||
location.pathname
|
||||
}#${fetchText}=${prepareDecodedUrl(resourceUrl)}`
|
||||
}
|
||||
|
||||
export function useIdeInit(cadPackage: string) {
|
||||
|
All of the logic in this hook I had in the I've also now rename All of the logic in this hook I had in the `IdeToolbarNew` because that was the main wrapping component that had the context provider, so it made sense to have init useEffects there. However since most of the logic relates to checking if there is something encoded in the URL, I thought it made sense to bundle the logic here with the rest of the encoded url code, and bring it into `IdeToolbarNew` as a custom hook.
I've also now rename `IdeToolbarNew` to `IdeWrapper`
|
||||
const { thunkDispatch } = useIdeContext()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'
|
||||
import FullScriptEncoding from 'src/components/EncodedUrl/FullScriptEncoding'
|
||||
import ExternalScript from 'src/components/EncodedUrl/ExternalScript'
|
||||
|
||||
const TopButton = ({
|
||||
onClick,
|
||||
@@ -50,12 +51,11 @@ const IdeHeader = ({ handleRender }: { handleRender: () => void }) => {
|
||||
className="bg-gray-300 rounded-md shadow-md overflow-hidden text-gray-700"
|
||||
selectedTabClassName="bg-gray-200"
|
||||
>
|
||||
<TabPanel className="p-4">
|
||||
<TabPanel>
|
||||
<FullScriptEncoding />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<p>blah</p>
|
||||
<input onPaste={(e) => console.log(e)} />
|
||||
<ExternalScript />
|
||||
</TabPanel>
|
||||
|
||||
<TabList className="flex whitespace-nowrap text-gray-700 border-t border-gray-700">
|
||||
|
||||
Reference in New Issue
Block a user
and