diff --git a/package.json b/package.json index 6ea3ff1..b514c1d 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "babel-sandboxes", "version": "1.0.0", "description": "", + "proxy": "http://localhost:3005", "dependencies": { "@babel/core": "~7.10.0", "@babel/plugin-external-helpers": "~7.10.0", diff --git a/src/components/App.js b/src/components/App.js index 647ad32..bf42083 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -8,7 +8,7 @@ import { Output } from "./Output"; import { gzipSize } from "../gzip"; import { Root } from "./styles"; import { useDebounce } from "../utils/useDebounce"; -import REPLState from "../state/REPLState.js"; +import { REPLState } from "../state"; import { Grid } from "semantic-ui-react"; import { plugins } from "../plugins-list"; @@ -17,18 +17,34 @@ window.babel = Babel; /** * Converts internal json plugin/preset config to babel form - * @param {Object} jsonConfig + * @param {Object} jsonConfig */ export function convertToBabelConfig(jsonConfig) { let result = { plugins: [], presets: [] }; +<<<<<<< HEAD + result.plugins = jsonConfig.plugins?.map(plugin => [ + plugin.name, + plugin.defaultConfig, + ]); + result.presets = jsonConfig.presets?.map(preset => [ + preset.name, + preset.defaultConfig, + ]); +======= result.plugins = jsonConfig.plugins?.map(plugin => [plugin.name, plugin.defaultConfig]); result.presets = jsonConfig.presets?.map(preset => [preset.name, preset.defaultConfig]); +>>>>>>> master return result; } export function convertToJsonConfig(babelConfig) { +<<<<<<< HEAD + let result = { plugins: [], presets: [] }; + result.plugins = babelConfig.plugins?.map(plugin => { +======= let result = { plugins: [], presets: [] } result.plugins = babelConfig.plugins?.map((plugin) => { +>>>>>>> master return { name: plugin[0], description: plugins[plugin[0]].description, @@ -71,16 +87,12 @@ function registerDefaultPlugins() { ); } - - export const App = ({ defaultSource, defaultConfig, defCustomPlugin }) => { const [source, setSource] = React.useState(defaultSource); const [enableCustomPlugin, toggleCustomPlugin] = React.useState(false); const [customPlugin, setCustomPlugin] = React.useState(defCustomPlugin); const [jsonConfig, setJsonConfig] = useState( - Array.isArray(defaultConfig) - ? defaultConfig - : [defaultConfig] + Array.isArray(defaultConfig) ? defaultConfig : [defaultConfig] ); const [size, setSize] = useState(null); const [gzip, setGzip] = useState(null); @@ -89,7 +101,7 @@ export const App = ({ defaultSource, defaultConfig, defCustomPlugin }) => { const [showShareLink, setShowShareLink] = React.useState(false); const updateBabelConfig = useCallback((config, index) => { - setJsonConfig((configs) => { + setJsonConfig(configs => { const newConfigs = [...configs]; newConfigs[index] = config; @@ -97,8 +109,8 @@ export const App = ({ defaultSource, defaultConfig, defCustomPlugin }) => { }); }, []); - const removeBabelConfig = useCallback((index) => { - setJsonConfig((configs) => configs.filter((c, i) => index !== i)); + const removeBabelConfig = useCallback(index => { + setJsonConfig(configs => configs.filter((c, i) => index !== i)); }, []); useEffect(() => { @@ -124,18 +136,17 @@ export const App = ({ defaultSource, defaultConfig, defCustomPlugin }) => { const state = new REPLState( source, enableCustomPlugin ? customPlugin : "", - jsonConfig.map((config) => JSON.stringify(config)) + jsonConfig.map(config => JSON.stringify(config)) ); const link = await state.Link(); + setShareLink(link); setShowShareLink(true); }} > Share - - {showShareLink && ( - - )} + + {showShareLink && } {enableCustomPlugin && ( diff --git a/src/index.js b/src/index.js index a88fc39..0f12bdb 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,7 @@ import React from "react"; import { render } from "react-dom"; import { App } from "./components/App"; +import { extractID, isShareLink, REPLState } from "./state"; // css import "semantic-ui-less/semantic.less"; @@ -45,7 +46,7 @@ const CONFIG = [ // description: "does this", // defaultConfig: {}, // } - ] + ], }, // {}, ]; @@ -61,11 +62,31 @@ const PLUGIN = `export default function customPlugin(babel) { } `; -render( - , - document.getElementById("root") +const defaultState = new REPLState( + SOURCE, + PLUGIN, + CONFIG.map(conf => JSON.stringify(conf)) ); + +/** + * @returns {Promise} + */ +async function getState() { + if (!isShareLink()) { + return defaultState; + } + const state = await REPLState.FromID(extractID()); + return state === null ? defaultState : state; +} + +(async () => { + const state = await getState(); + render( + JSON.parse(conf))} + defaultSource={state.jsSource} + defCustomPlugin={state.pluginSource} + />, + document.getElementById("root") + ); +})(); diff --git a/src/state/REPLState.js b/src/state/REPLState.js index 853e5b1..3cb1e39 100644 --- a/src/state/REPLState.js +++ b/src/state/REPLState.js @@ -62,30 +62,12 @@ class REPLState { return JSON.stringify({ source: encodeToBase64(this.jsSource), plugin: encodeToBase64(this.pluginSource), - configs: this.configs.map((configSrc) => { + configs: this.configs.map(configSrc => { return encodeToBase64(configSrc); }), }); } - /** - * Link gets the sharing the sharing link - * for the given REPL state. - * @returns {Promise} String URL. - */ - async Link() { - const url = "http://localhost:1337/api/v1/blobs/create"; - const resp = await fetch(url, { - method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: this.Encode(), - }); - return resp.text(); - } - /** * Decode decodes the json string. * @param {string} encodedState @@ -94,13 +76,58 @@ class REPLState { static Decode(encodedState) { let jsonState = JSON.parse(encodedState); return new REPLState( - decodeBase64(jsonState.source), - decodeBase64(jsonState.plugin), - jsonState.configs.map((configs) => { + decodeBase64(jsonState.base64SourceKey), + decodeBase64(jsonState.base64PluginKey), + jsonState.configIDs.map(configs => { return decodeBase64(configs); }) ); } + + /** + * Link gets the sharing the sharing link + * for the given REPL state. + * @returns {Promise} String URL. + */ + async Link() { + const url = `/api/v1/blobs/create`; + try { + const resp = await fetch(url, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: this.Encode(), + }); + const message = await resp.json(); + + // https://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url + return ( + window.location.href.split("/").slice(0, 3).join("/") + message.url + ); + } catch (err) { + console.error(err); + return err; + } + } + + /** + * REPLState.FromID returns a REPLState given a unique identifier. + * @param {string} ID + * @returns {Promise} + */ + static async FromID(ID) { + const url = `/api/v1/blobs/${ID}`; + try { + const resp = await fetch(url); + const text = await resp.text(); + return REPLState.Decode(text); + } catch (err) { + console.error(err); + return null; + } + } } export default REPLState; diff --git a/src/state/detectShare.js b/src/state/detectShare.js new file mode 100644 index 0000000..8ccb288 --- /dev/null +++ b/src/state/detectShare.js @@ -0,0 +1,22 @@ +/** + * isShareLink returns true iff the pathname contains the text `share` + * @returns {boolean} + */ +function isShareLink() { + const path = window.location.pathname; + const shareIndicator = "share"; + return path.includes(shareIndicator); +} + +function extractID() { + + // Attempt to capture :key from http://example.com/share/:key/ + const pathParts = window.location.pathname.split('/'); + if (pathParts[1] == 'share' && pathParts[2]) { + return pathParts[2]; + } else { + throw new Error("Trying to extract ID from a share link that doesn't have one.") + } +} + +export { isShareLink, extractID }; diff --git a/src/state/index.js b/src/state/index.js new file mode 100644 index 0000000..c241dc1 --- /dev/null +++ b/src/state/index.js @@ -0,0 +1,4 @@ +import REPLState from "./REPLState.js"; +import { extractID, isShareLink } from "./detectShare.js"; + +export { REPLState, extractID, isShareLink };