From 3457bf7612a3ecb1a1388c7af80f27bfc20b398a Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Sun, 11 Sep 2022 13:02:43 +0200 Subject: [PATCH 01/26] Add script for unpacking examples from solid-site to solid-docs --- src/js-files/unpack.js | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/js-files/unpack.js diff --git a/src/js-files/unpack.js b/src/js-files/unpack.js new file mode 100644 index 00000000..26cf5fba --- /dev/null +++ b/src/js-files/unpack.js @@ -0,0 +1,54 @@ +// This script is meant to be run once per each JSON file of type JsFiles, like +// those in a tutorial or an example. Its purpose is to extract individual +// files, so that they can be more easily diffed in git. + +// input +// 1: source folder (eg: "/../solid-site/public/examples") +// 2: dest folder (eg: "/langs/en/examples") +// output +// - as many subfolders of the dest folder as files in the glob, each named after the basename of the file +// - as many files in each subfolder as there are packed into each file in the glob + +import { resolve, basename, dirname } from 'path'; +import { readdirSync, readFileSync, writeFileSync, mkdirSync } from 'fs'; + +let [sourceFolder, destFolder] = process.argv.slice(2); +if (!(sourceFolder && destFolder)) { + throw new Error('Expected both a source and a dest folder'); +} + +sourceFolder = resolve('./', sourceFolder); +destFolder = resolve('./', destFolder); + + +const sourceFiles = readdirSync(sourceFolder); +sourceFiles.forEach((sourceFilename) => { + const sourcePath = `${sourceFolder}/${sourceFilename}`; + if (!/\.json$/.test(sourcePath)) { + console.log(`skipping ${sourcePath} because not '.json'`); + return; + } + const json = readFileSync(sourcePath, 'utf8'); + let data; + try { + console.log(`parsing ${sourcePath}`); + data = JSON.parse(json); + } catch (e) { + console.log(`skipping ${sourcePath} because ${e}`); + return; + } + if (!data.files?.length) { + console.log('no files to extract'); + return; + } + console.log(`extracting ${data.files.length} files`); + data.files.forEach((file) => { + const destFilename = `${file.name}.${file.type || 'jsx'}`; + const destPath = `${destFolder}/${basename(sourceFilename, '.json')}/${destFilename}`; + mkdirSync(dirname(destPath), {recursive: true}); + let content = file.content; + if (Array.isArray(content)) content = content.join('\n'); + writeFileSync(destPath, content); + console.log(`- extracted ${destPath}`); + }); +}); From 75b6062bf1d76696dd5cfbf7c5dbfb87fa2dffef Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Sun, 11 Sep 2022 13:03:32 +0200 Subject: [PATCH 02/26] Unpack examples from solid-site to solid-docs --- langs/en/examples/asyncresource/main.jsx | 27 ++++ langs/en/examples/clock/Clock.tsx | 51 ++++++++ langs/en/examples/clock/Hand.tsx | 18 +++ langs/en/examples/clock/Lines.tsx | 21 +++ langs/en/examples/clock/main.tsx | 5 + langs/en/examples/clock/styles.css | 20 +++ langs/en/examples/clock/utils.tsx | 48 +++++++ langs/en/examples/context/main.tsx | 34 +++++ langs/en/examples/context/theme.tsx | 48 +++++++ langs/en/examples/counter/main.jsx | 14 ++ .../en/examples/counterstore/CounterStore.jsx | 33 +++++ langs/en/examples/counterstore/main.jsx | 23 ++++ langs/en/examples/cssanimations/main.jsx | 121 +++++++++++++++++ langs/en/examples/cssanimations/styles.css | 57 ++++++++ langs/en/examples/ethasketch/main.tsx | 69 ++++++++++ langs/en/examples/ethasketch/styles.css | 7 + langs/en/examples/forms/main.jsx | 76 +++++++++++ langs/en/examples/forms/styles.css | 13 ++ langs/en/examples/forms/validation.jsx | 61 +++++++++ langs/en/examples/routing/main.jsx | 65 ++++++++++ langs/en/examples/scoreboard/main.jsx | 122 ++++++++++++++++++ langs/en/examples/simpletodos/main.jsx | 79 ++++++++++++ .../examples/simpletodoshyperscript/main.jsx | 86 ++++++++++++ langs/en/examples/styledjsx/main.jsx | 33 +++++ langs/en/examples/styledjsx/tab1.jsx | 63 +++++++++ langs/en/examples/suspensetabs/child.jsx | 27 ++++ langs/en/examples/suspensetabs/main.jsx | 44 +++++++ langs/en/examples/suspensetabs/styles.css | 59 +++++++++ langs/en/examples/todos/main.tsx | 57 ++++++++ langs/en/examples/todos/utils.tsx | 18 +++ 30 files changed, 1399 insertions(+) create mode 100644 langs/en/examples/asyncresource/main.jsx create mode 100644 langs/en/examples/clock/Clock.tsx create mode 100644 langs/en/examples/clock/Hand.tsx create mode 100644 langs/en/examples/clock/Lines.tsx create mode 100644 langs/en/examples/clock/main.tsx create mode 100644 langs/en/examples/clock/styles.css create mode 100644 langs/en/examples/clock/utils.tsx create mode 100644 langs/en/examples/context/main.tsx create mode 100644 langs/en/examples/context/theme.tsx create mode 100644 langs/en/examples/counter/main.jsx create mode 100644 langs/en/examples/counterstore/CounterStore.jsx create mode 100644 langs/en/examples/counterstore/main.jsx create mode 100644 langs/en/examples/cssanimations/main.jsx create mode 100644 langs/en/examples/cssanimations/styles.css create mode 100644 langs/en/examples/ethasketch/main.tsx create mode 100644 langs/en/examples/ethasketch/styles.css create mode 100644 langs/en/examples/forms/main.jsx create mode 100644 langs/en/examples/forms/styles.css create mode 100644 langs/en/examples/forms/validation.jsx create mode 100644 langs/en/examples/routing/main.jsx create mode 100644 langs/en/examples/scoreboard/main.jsx create mode 100644 langs/en/examples/simpletodos/main.jsx create mode 100644 langs/en/examples/simpletodoshyperscript/main.jsx create mode 100644 langs/en/examples/styledjsx/main.jsx create mode 100644 langs/en/examples/styledjsx/tab1.jsx create mode 100644 langs/en/examples/suspensetabs/child.jsx create mode 100644 langs/en/examples/suspensetabs/main.jsx create mode 100644 langs/en/examples/suspensetabs/styles.css create mode 100644 langs/en/examples/todos/main.tsx create mode 100644 langs/en/examples/todos/utils.tsx diff --git a/langs/en/examples/asyncresource/main.jsx b/langs/en/examples/asyncresource/main.jsx new file mode 100644 index 00000000..f80207be --- /dev/null +++ b/langs/en/examples/asyncresource/main.jsx @@ -0,0 +1,27 @@ +import { createSignal, createResource } from "solid-js"; +import { render } from "solid-js/web"; + +const fetchUser = async (id) => + (await fetch(`https://swapi.dev/api/people/${id}/`)).json(); + +const App = () => { + const [userId, setUserId] = createSignal(); + const [user] = createResource(userId, fetchUser); + + return ( + <> + setUserId(e.currentTarget.value)} + /> + {user.loading && "Loading..."} +
+
{JSON.stringify(user(), null, 2)}
+
+ + ); +}; + +render(App, document.getElementById("app")); \ No newline at end of file diff --git a/langs/en/examples/clock/Clock.tsx b/langs/en/examples/clock/Clock.tsx new file mode 100644 index 00000000..a3389afc --- /dev/null +++ b/langs/en/examples/clock/Clock.tsx @@ -0,0 +1,51 @@ +import { createSignal, onCleanup } from 'solid-js'; +import { Hand } from './Hand'; +import { Lines } from './Lines'; +import { createAnimationLoop } from './utils'; +import type { Accessor, Component } from 'solid-js'; + +const getSecondsSinceMidnight = (): number => (Date.now() - new Date().setHours(0, 0, 0, 0)) / 1000; + +type ClockFaceProps = { + hour: string; + minute: string; + second: string; + subsecond: string; +}; + +export const ClockFace: Component = (props) => ( + + + {/* static */} + + + + {/* dynamic */} + + + + + + +); + +export const Clock: Component = () => { + const [time, setTime] = createSignal(getSecondsSinceMidnight()); + const dispose = createAnimationLoop(() => { + setTime(getSecondsSinceMidnight()); + }); + onCleanup(dispose); + + const rotate = (rotate: number, fixed: number = 1) => `rotate(${(rotate * 360).toFixed(fixed)})`; + + const subsecond = () => rotate(time() % 1); + const second = () => rotate((time() % 60) / 60); + const minute = () => rotate(((time() / 60) % 60) / 60); + const hour = () => rotate(((time() / 60 / 60) % 12) / 12); + + return ( +
+ +
+ ); +}; diff --git a/langs/en/examples/clock/Hand.tsx b/langs/en/examples/clock/Hand.tsx new file mode 100644 index 00000000..4391317d --- /dev/null +++ b/langs/en/examples/clock/Hand.tsx @@ -0,0 +1,18 @@ +import { Component, splitProps } from 'solid-js'; + +type HandProps = { rotate: string; class: string; length: number; width: number; fixed?: boolean }; + +export const Hand: Component = (props) => { + const [local, rest] = splitProps(props, ['rotate', 'length', 'width', 'fixed']); + return ( + + ); +}; diff --git a/langs/en/examples/clock/Lines.tsx b/langs/en/examples/clock/Lines.tsx new file mode 100644 index 00000000..662896c6 --- /dev/null +++ b/langs/en/examples/clock/Lines.tsx @@ -0,0 +1,21 @@ +import { Hand } from './Hand'; +import { Component, splitProps, For } from 'solid-js'; + +type LinesProps = { + numberOfLines: number; + class: string; + length: number; + width: number; +}; + +const rotate = (index: number, length: number) => `rotate(${(360 * index) / length})`; + +export const Lines: Component = (props) => { + const [local, rest] = splitProps(props, ['numberOfLines']); + + return ( + + {(_, index) => } + + ); +}; diff --git a/langs/en/examples/clock/main.tsx b/langs/en/examples/clock/main.tsx new file mode 100644 index 00000000..235250ea --- /dev/null +++ b/langs/en/examples/clock/main.tsx @@ -0,0 +1,5 @@ +import { render } from 'solid-js/web'; +import { Clock } from './Clock'; +import './styles.css'; + +render(() => , document.getElementById('app')!); diff --git a/langs/en/examples/clock/styles.css b/langs/en/examples/clock/styles.css new file mode 100644 index 00000000..22c6056c --- /dev/null +++ b/langs/en/examples/clock/styles.css @@ -0,0 +1,20 @@ +.clock { + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + height: 100vh; +} + +.subsecond { + color: silver; +} + +.hour, +.minute { + color: black; +} + +.second { + color: tomato; +} diff --git a/langs/en/examples/clock/utils.tsx b/langs/en/examples/clock/utils.tsx new file mode 100644 index 00000000..d7487148 --- /dev/null +++ b/langs/en/examples/clock/utils.tsx @@ -0,0 +1,48 @@ +// ported from voby https://github.com/vobyjs/voby/blob/master/src/hooks/use_scheduler.ts +import { Accessor } from 'solid-js'; + +type FN = ( + ...args: Arguments +) => Return; +type MaybeAccessor = Accessor | T; +const isFunction = (value: unknown): value is (...args: unknown[]) => unknown => + typeof value === 'function'; +const unwrap = (maybeValue: MaybeAccessor): T => + isFunction(maybeValue) ? maybeValue() : maybeValue; + +export const createScheduler = ({ + loop, + callback, + cancel, + schedule, +}: { + loop?: MaybeAccessor; + callback: MaybeAccessor>; + cancel: FN<[T]>; + schedule: (callback: FN<[U]>) => T; +}): (() => void) => { + let tickId: T; + const work = (): void => { + if (unwrap(loop)) tick(); + unwrap(callback); + }; + + const tick = (): void => { + tickId = schedule(work); + }; + + const dispose = (): void => { + cancel(tickId); + }; + + tick(); + return dispose; +}; + +export const createAnimationLoop = (callback: FrameRequestCallback) => + createScheduler({ + callback, + loop: true, + cancel: cancelAnimationFrame, + schedule: requestAnimationFrame, + }); diff --git a/langs/en/examples/context/main.tsx b/langs/en/examples/context/main.tsx new file mode 100644 index 00000000..36a7a614 --- /dev/null +++ b/langs/en/examples/context/main.tsx @@ -0,0 +1,34 @@ +import { render } from "solid-js/web"; +import { ThemeProvider, useTheme } from "./theme"; + +function App() { + const [theme, { changeColor }] = useTheme(); + + return ( + <> +

+ {theme.title} +

+ changeColor(e.currentTarget.value)} + /> + + + ); +} + +render( + () => ( + + + + ), + document.getElementById("app")! +); diff --git a/langs/en/examples/context/theme.tsx b/langs/en/examples/context/theme.tsx new file mode 100644 index 00000000..7ddc95eb --- /dev/null +++ b/langs/en/examples/context/theme.tsx @@ -0,0 +1,48 @@ +import { createContext, useContext, ParentComponent } from "solid-js"; +import { createStore } from "solid-js/store"; + +export type ThemeContextState = { + readonly color: string; + readonly title: string; +}; +export type ThemeContextValue = [ + state: ThemeContextState, + actions: { + changeColor: (color: string) => void; + changeTitle: (title: string) => void; + } +]; + +const defaultState = { + color: "#66e6ac", + title: "Fallback Title", +}; + +const ThemeContext = createContext([ + defaultState, + { + changeColor: () => undefined, + changeTitle: () => undefined, + }, +]); + +export const ThemeProvider: ParentComponent<{ + color?: string; + title?: string; +}> = (props) => { + const [state, setState] = createStore({ + color: props.color ?? defaultState.color, + title: props.title ?? defaultState.title, + }); + + const changeColor = (color: string) => setState("color", color); + const changeTitle = (title: string) => setState("title", title); + + return ( + + {props.children} + + ); +}; + +export const useTheme = () => useContext(ThemeContext); diff --git a/langs/en/examples/counter/main.jsx b/langs/en/examples/counter/main.jsx new file mode 100644 index 00000000..708e5aa1 --- /dev/null +++ b/langs/en/examples/counter/main.jsx @@ -0,0 +1,14 @@ +import { createSignal, onCleanup } from "solid-js"; +import { render } from "solid-js/web"; + +const CountingComponent = () => { + const [count, setCount] = createSignal(0); + const interval = setInterval( + () => setCount(c => c + 1), + 1000 + ); + onCleanup(() => clearInterval(interval)); + return
Count value is {count()}
; +}; + +render(() => , document.getElementById("app")); \ No newline at end of file diff --git a/langs/en/examples/counterstore/CounterStore.jsx b/langs/en/examples/counterstore/CounterStore.jsx new file mode 100644 index 00000000..c4ee33cc --- /dev/null +++ b/langs/en/examples/counterstore/CounterStore.jsx @@ -0,0 +1,33 @@ +import { createSignal, createContext, useContext, Component } from "solid-js"; + +type CounterStore = [ + () => number, + { increment?: () => void; decrement?: () => void } +]; + +const CounterContext = createContext([() => 0, {}]); + +export const CounterProvider: Component<{ count: number }> = props => { + const [count, setCount] = createSignal(props.count || 0), + store: CounterStore = [ + count, + { + increment() { + setCount(c => c + 1); + }, + decrement() { + setCount(c => c - 1); + } + } + ]; + + return ( + + {props.children} + + ); +}; + +export function useCounter() { + return useContext(CounterContext); +} \ No newline at end of file diff --git a/langs/en/examples/counterstore/main.jsx b/langs/en/examples/counterstore/main.jsx new file mode 100644 index 00000000..e47a120f --- /dev/null +++ b/langs/en/examples/counterstore/main.jsx @@ -0,0 +1,23 @@ +import { render } from "solid-js/web"; +import { CounterProvider, useCounter } from "CounterStore.tsx"; + +const MiddleComponent = () => ; + +const NestedComponent = () => { + const [count, { increment, decrement }] = useCounter(); + return ( + <> +

{count()}

+ + + + ); +}; + +const App = () => ( + + + +); + +render(App, document.getElementById("app")); \ No newline at end of file diff --git a/langs/en/examples/cssanimations/main.jsx b/langs/en/examples/cssanimations/main.jsx new file mode 100644 index 00000000..8d36d438 --- /dev/null +++ b/langs/en/examples/cssanimations/main.jsx @@ -0,0 +1,121 @@ +import { createSignal, For, Match, Switch } from "solid-js"; +import { render } from "solid-js/web"; +import { Transition, TransitionGroup } from "solid-transition-group"; +import "./styles.css" + +function shuffle(array) { + return array.sort(() => Math.random() - 0.5); +} +let nextId = 10; + +const App = () => { + const [show, toggleShow] = createSignal(true), + [select, setSelect] = createSignal(0), + [numList, setNumList] = createSignal([1, 2, 3, 4, 5, 6, 7, 8, 9]), + randomIndex = () => Math.floor(Math.random() * numList().length); + + return ( + <> + +
+ Transition: + + {show() && ( +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris + facilisis enim libero, at lacinia diam fermentum id. Pellentesque + habitant morbi tristique senectus et netus. +
+ )} +
+
+ Animation: + + {show() && ( +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris + facilisis enim libero, at lacinia diam fermentum id. Pellentesque + habitant morbi tristique senectus et netus. +
+ )} +
+
+ Custom JS: + { + const a = el.animate([{ opacity: 0 }, { opacity: 1 }], { + duration: 600 + }); + a.finished.then(done); + }} + onExit={(el, done) => { + const a = el.animate([{ opacity: 1 }, { opacity: 0 }], { + duration: 600 + }); + a.finished.then(done); + }} + > + {show() && ( +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris + facilisis enim libero, at lacinia diam fermentum id. Pellentesque + habitant morbi tristique senectus et netus. +
+ )} +
+
+ Switch OutIn +
+ + + + +

The First

+
+ +

The Second

+
+ +

The Third

+
+
+
+ Group +
+ + + +
+ + {(r) => {r}} + + + ); +}; + +render(App, document.getElementById("app")); \ No newline at end of file diff --git a/langs/en/examples/cssanimations/styles.css b/langs/en/examples/cssanimations/styles.css new file mode 100644 index 00000000..14108246 --- /dev/null +++ b/langs/en/examples/cssanimations/styles.css @@ -0,0 +1,57 @@ +.container { + position: relative; +} + +.fade-enter-active, +.fade-exit-active { + transition: opacity 0.5s; +} +.fade-enter, +.fade-exit-to { + opacity: 0; +} + +.slide-fade-enter-active { + transition: all 0.3s ease; +} +.slide-fade-exit-active { + transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1); +} +.slide-fade-enter, +.slide-fade-exit-to { + transform: translateX(10px); + opacity: 0; +} + +.bounce-enter-active { + animation: bounce-in 0.5s; +} +.bounce-exit-active { + animation: bounce-in 0.5s reverse; +} +@keyframes bounce-in { + 0% { + transform: scale(0); + } + 50% { + transform: scale(1.5); + } + 100% { + transform: scale(1); + } +} + +.list-item { + transition: all 0.5s; + display: inline-block; + margin-right: 10px; +} + +.list-item-enter, +.list-item-exit-to { + opacity: 0; + transform: translateY(30px); +} +.list-item-exit-active { + position: absolute; +} diff --git a/langs/en/examples/ethasketch/main.tsx b/langs/en/examples/ethasketch/main.tsx new file mode 100644 index 00000000..7034331b --- /dev/null +++ b/langs/en/examples/ethasketch/main.tsx @@ -0,0 +1,69 @@ +// Project idea from https://www.theodinproject.com/paths/foundations/courses/foundations/lessons/etch-a-sketch-project +import { render } from "solid-js/web"; +import { createSignal, createMemo, Index } from "solid-js"; + +import "./styles.css"; + +const maxGridPixelWidth = 500; + +function randomHexColorString(): string { + return "#" + Math.floor(Math.random() * 16777215).toString(16); +} + +function clampGridSideLength(newSideLength: number): number { + return Math.min(Math.max(newSideLength, 0), 100); +} + +function EtchASketch() { + const [gridSideLength, setGridSideLength] = createSignal(10); + const gridTemplateString = createMemo( + () => + `repeat(${gridSideLength()}, ${maxGridPixelWidth / gridSideLength()}px)` + ); + + return ( + <> +
+ + + setGridSideLength( + clampGridSideLength(e.currentTarget.valueAsNumber) + ) + } + /> +
+
+ + {() => ( +
{ + const eventEl = event.currentTarget; + + eventEl.style.backgroundColor = randomHexColorString(); + + setTimeout(() => { + eventEl.style.backgroundColor = "initial"; + }, 500); + }} + >
+ )} +
+
+ + ); +} + +render(() => , document.getElementById("app")); diff --git a/langs/en/examples/ethasketch/styles.css b/langs/en/examples/ethasketch/styles.css new file mode 100644 index 00000000..2b6b5cdf --- /dev/null +++ b/langs/en/examples/ethasketch/styles.css @@ -0,0 +1,7 @@ +.cell { + outline: 1px solid #1f1f1f; +} + +.dark .cell { + outline: 1px solid #efefef; +} diff --git a/langs/en/examples/forms/main.jsx b/langs/en/examples/forms/main.jsx new file mode 100644 index 00000000..fb76963f --- /dev/null +++ b/langs/en/examples/forms/main.jsx @@ -0,0 +1,76 @@ +// @ts-nocheck +import { render } from "solid-js/web"; +import { createStore } from "solid-js/store"; +import { useForm } from "./validation"; +import "./styles.css"; + +const EMAILS = ["johnsmith@outlook.com", "mary@gmail.com", "djacobs@move.org"]; + +function fetchUserName(name) { + return new Promise((resolve) => { + setTimeout(() => resolve(EMAILS.indexOf(name) > -1), 200); + }); +} + +const ErrorMessage = (props) => {props.error}; + +const App = () => { + const { validate, formSubmit, errors } = useForm({ + errorClass: "error-input" + }); + const [fields, setFields] = createStore(); + const fn = (form) => { + // form.submit() + console.log("Done"); + }; + const userNameExists = async ({ value }) => { + const exists = await fetchUserName(value); + return exists && `${value} is already being used`; + }; + const matchesPassword = ({ value }) => + value === fields.password ? false : "Passwords must Match"; + + return ( +
+

Sign Up

+
+ + {errors.email && } +
+
+ setFields("password", e.target.value)} + use:validate + /> + {errors.password && } +
+
+ + {errors.confirmpassword && ( + + )} +
+ + +
+ ); +}; + +render(App, document.getElementById("app")); diff --git a/langs/en/examples/forms/styles.css b/langs/en/examples/forms/styles.css new file mode 100644 index 00000000..0098ed1f --- /dev/null +++ b/langs/en/examples/forms/styles.css @@ -0,0 +1,13 @@ +input { + display: inline-block; + padding: 4px; + margin-top: 10px; + margin-bottom: 10px; +} +.error-message { + color: red; + padding: 8px; +} +.error-input { + box-shadow: 0px 0px 2px 1px red; +} \ No newline at end of file diff --git a/langs/en/examples/forms/validation.jsx b/langs/en/examples/forms/validation.jsx new file mode 100644 index 00000000..8493aadd --- /dev/null +++ b/langs/en/examples/forms/validation.jsx @@ -0,0 +1,61 @@ +import { createStore } from "solid-js/store"; + +function checkValid({ element, validators = [] }, setErrors, errorClass) { + return async () => { + element.setCustomValidity(""); + element.checkValidity(); + let message = element.validationMessage; + if (!message) { + for (const validator of validators) { + const text = await validator(element); + if (text) { + element.setCustomValidity(text); + break; + } + } + message = element.validationMessage; + } + if (message) { + errorClass && element.classList.toggle(errorClass, true); + setErrors({ [element.name]: message }); + } + }; +} + +export function useForm({ errorClass }) { + const [errors, setErrors] = createStore({}), + fields = {}; + + const validate = (ref, accessor) => { + const validators = accessor() || []; + let config; + fields[ref.name] = config = { element: ref, validators }; + ref.onblur = checkValid(config, setErrors, errorClass); + ref.oninput = () => { + if (!errors[ref.name]) return; + setErrors({ [ref.name]: undefined }); + errorClass && ref.classList.toggle(errorClass, false); + }; + }; + + const formSubmit = (ref, accessor) => { + const callback = accessor() || (() => {}); + ref.setAttribute("novalidate", ""); + ref.onsubmit = async (e) => { + e.preventDefault(); + let errored = false; + + for (const k in fields) { + const field = fields[k]; + await checkValid(field, setErrors, errorClass)(); + if (!errored && field.element.validationMessage) { + field.element.focus(); + errored = true; + } + } + !errored && callback(ref); + }; + }; + + return { validate, formSubmit, errors }; +} diff --git a/langs/en/examples/routing/main.jsx b/langs/en/examples/routing/main.jsx new file mode 100644 index 00000000..69cdd1ad --- /dev/null +++ b/langs/en/examples/routing/main.jsx @@ -0,0 +1,65 @@ +import { createSignal, onCleanup, Component } from "solid-js"; +import { render, Switch, Match } from "solid-js/web"; + +function createRouteHandler() { + const [location, setLocation] = createSignal( + window.location.hash.slice(1) || "home" + ), + locationHandler = () => setLocation(window.location.hash.slice(1)); + window.addEventListener("hashchange", locationHandler); + onCleanup(() => window.removeEventListener("hashchange", locationHandler)); + return (match: string) => match === location(); +} + +const Home: Component = () => ( + <> +

Welcome to this Simple Routing Example

+

Click the links in the Navigation above to load different routes.

+ +); + +const Profile: Component = () => ( + <> +

Your Profile

+

This section could be about you.

+ +); + +const Settings: Component = () => ( + <> +

Settings

+

All that configuration you never really ever want to look at.

+ +); + +const App = () => { + const matches = createRouteHandler(); + return ( + <> + + + + + + + + + + + + + + ); +}; + +render(App, document.getElementById("app")); \ No newline at end of file diff --git a/langs/en/examples/scoreboard/main.jsx b/langs/en/examples/scoreboard/main.jsx new file mode 100644 index 00000000..feb9c7fb --- /dev/null +++ b/langs/en/examples/scoreboard/main.jsx @@ -0,0 +1,122 @@ +import { + createMemo, + createSignal, + createComputed, + onCleanup, + For +} from "solid-js"; +import { createStore } from "solid-js/store"; +import { render } from "solid-js/web"; + +const App = () => { + let newName, newScore; + const [state, setState] = createStore({ + players: [ + { name: "Mark", score: 3 }, + { name: "Troy", score: 2 }, + { name: "Jenny", score: 1 }, + { name: "David", score: 8 } + ] + }), + lastPos = new WeakMap(), + curPos = new WeakMap(), + getSorted = createMemo((list = []) => { + list.forEach((p, i) => lastPos.set(p, i)); + const newList = state.players.slice().sort((a, b) => { + if (b.score === a.score) return a.name.localeCompare(b.name); // stabalize the sort + return b.score - a.score; + }); + let updated = newList.length !== list.length; + newList.forEach( + (p, i) => lastPos.get(p) !== i && (updated = true) && curPos.set(p, i) + ); + return updated ? newList : list; + }), + handleAddClick = () => { + const name = newName.value, + score = +newScore.value; + if (!name.length || isNaN(score)) return; + setState("players", (p) => [...p, { name: name, score: score }]); + newName.value = newScore.value = ""; + }, + handleDeleteClick = (player) => { + const idx = state.players.indexOf(player); + setState("players", (p) => [...p.slice(0, idx), ...p.slice(idx + 1)]); + }, + handleScoreChange = (player, { target }) => { + const score = +target.value; + const idx = state.players.indexOf(player); + if (isNaN(+score) || idx < 0) return; + setState("players", idx, "score", score); + }, + createStyles = (player) => { + const [style, setStyle] = createSignal(); + createComputed(() => { + getSorted(); + const offset = lastPos.get(player) * 18 - curPos.get(player) * 18, + t = setTimeout(() => + setStyle({ transition: "250ms", transform: null }) + ); + setStyle({ + transform: `translateY(${offset}px)`, + transition: null + }); + onCleanup(() => clearTimeout(t)); + }); + return style; + }; + + return ( +
+
+ + {(player) => { + const getStyles = createStyles(player), + { name } = player; + return ( +
+
{name}
+
{player.score}
+
+ ); + }} +
+
+
+ + {(player) => { + const { name, score } = player; + return ( +
+ {name} + + +
+ ); + }} +
+
+ + + +
+
+
+ ); +}; + +render(App, document.getElementById("app")); \ No newline at end of file diff --git a/langs/en/examples/simpletodos/main.jsx b/langs/en/examples/simpletodos/main.jsx new file mode 100644 index 00000000..b0ebbc1c --- /dev/null +++ b/langs/en/examples/simpletodos/main.jsx @@ -0,0 +1,79 @@ +import { createEffect, For } from "solid-js"; +import { createStore } from "solid-js/store"; +import { render } from "solid-js/web"; +import html from "solid-js/html"; + +function createLocalStore(initState) { + const [state, setState] = createStore(initState); + if (localStorage.todos) setState(JSON.parse(localStorage.todos)); + createEffect(() => (localStorage.todos = JSON.stringify(state))); + return [state, setState]; +} + +const App = () => { + const [state, setState] = createLocalStore({ + todos: [], + newTitle: "", + idCounter: 0 + }); + + return html` +
+

Simple Todos Example

+ state.newTitle} + oninput=${(e) => setState({ newTitle: e.target.value })} + /> + + <${For} each=${() => state.todos} + >${(todo) => + html` +
+ { + const idx = state.todos.findIndex((t) => t.id === todo.id); + setState("todos", idx, { done: e.target.checked }); + }} + /> + { + const idx = state.todos.findIndex((t) => t.id === todo.id); + setState("todos", idx, { title: e.target.value }); + }} + /> + +
+ `} + +
+ `; +}; + +render(App, document.getElementById("app")); \ No newline at end of file diff --git a/langs/en/examples/simpletodoshyperscript/main.jsx b/langs/en/examples/simpletodoshyperscript/main.jsx new file mode 100644 index 00000000..edb3308c --- /dev/null +++ b/langs/en/examples/simpletodoshyperscript/main.jsx @@ -0,0 +1,86 @@ +import { createEffect, For } from "solid-js"; +import { createStore } from "solid-js/store"; +import { render } from "solid-js/web"; +import h from "solid-js/h"; + +function createLocalStore(initState) { + const [state, setState] = createStore(initState); + if (localStorage.todos) setState(JSON.parse(localStorage.todos)); + createEffect(() => (localStorage.todos = JSON.stringify(state))); + return [state, setState]; +} + +const App = () => { + const [state, setState] = createLocalStore({ + todos: [], + newTitle: "", + idCounter: 0 + }); + return [ + h("h3", "Simple Todos Example"), + h("input", { + type: "text", + placeholder: "enter todo and click +", + value: () => state.newTitle, + onInput: (e) => setState("newTitle", e.target.value) + }), + h( + "button", + { + onClick: () => + setState((s) => ({ + idCounter: s.idCounter + 1, + todos: [ + ...s.todos, + { + id: state.idCounter, + title: state.newTitle, + done: false + } + ], + newTitle: "" + })) + }, + "+" + ), + h(For, { each: () => state.todos }, (todo) => + h( + "div", + h("input", { + type: "checkbox", + checked: todo.done, + onChange: (e) => + setState( + "todos", + state.todos.findIndex((t) => t.id === todo.id), + { + done: e.target.checked + } + ) + }), + h("input", { + type: "text", + value: todo.title, + onChange: (e) => + setState( + "todos", + state.todos.findIndex((t) => t.id === todo.id), + { + title: e.target.value + } + ) + }), + h( + "button", + { + onClick: () => + setState("todos", (t) => t.filter((t) => t.id !== todo.id)) + }, + "x" + ) + ) + ) + ]; +}; + +render(App, document.getElementById("app")); \ No newline at end of file diff --git a/langs/en/examples/styledjsx/main.jsx b/langs/en/examples/styledjsx/main.jsx new file mode 100644 index 00000000..004efad9 --- /dev/null +++ b/langs/en/examples/styledjsx/main.jsx @@ -0,0 +1,33 @@ +import { createSignal } from "solid-js"; +import { render } from "solid-js/web"; + +function Button() { + const [isLoggedIn, login] = createSignal(false); + return ( + <> + + + + ); +} + +render( + () => ( + <> + + + + {(todo, i) => ( +
+ setTodos(i(), "done", e.currentTarget.checked)} + /> + setTodos(i(), "title", e.currentTarget.value)} + /> + +
+ )} +
+ + ); +}; + +render(App, document.getElementById("app")!); diff --git a/langs/en/examples/todos/utils.tsx b/langs/en/examples/todos/utils.tsx new file mode 100644 index 00000000..90e36e0b --- /dev/null +++ b/langs/en/examples/todos/utils.tsx @@ -0,0 +1,18 @@ +import { createEffect } from "solid-js"; +import { createStore, SetStoreFunction, Store } from "solid-js/store"; + +export function createLocalStore( + name: string, + init: T +): [Store, SetStoreFunction] { + const localState = localStorage.getItem(name); + const [state, setState] = createStore( + localState ? JSON.parse(localState) : init + ); + createEffect(() => localStorage.setItem(name, JSON.stringify(state))); + return [state, setState]; +} + +export function removeIndex(array: readonly T[], index: number): T[] { + return [...array.slice(0, index), ...array.slice(index + 1)]; +} From 26a77eb4f127d2199ec2745c1216dfda621f4a6d Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Sun, 11 Sep 2022 14:54:36 +0200 Subject: [PATCH 03/26] Add a ".json-files" descriptor with the order of the files --- src/js-files/unpack.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/js-files/unpack.js b/src/js-files/unpack.js index 26cf5fba..84b6ed08 100644 --- a/src/js-files/unpack.js +++ b/src/js-files/unpack.js @@ -42,13 +42,17 @@ sourceFiles.forEach((sourceFilename) => { return; } console.log(`extracting ${data.files.length} files`); + const destPathParent = `${destFolder}/${basename(sourceFilename, '.json')}`; + mkdirSync(destPathParent, {recursive: true}); + const filesOrder = []; data.files.forEach((file) => { const destFilename = `${file.name}.${file.type || 'jsx'}`; - const destPath = `${destFolder}/${basename(sourceFilename, '.json')}/${destFilename}`; - mkdirSync(dirname(destPath), {recursive: true}); + filesOrder.push(destFilename); + const destPath = `${destPathParent}/${destFilename}`; let content = file.content; if (Array.isArray(content)) content = content.join('\n'); writeFileSync(destPath, content); console.log(`- extracted ${destPath}`); }); + writeFileSync(`${destPathParent}/.json-files`, JSON.stringify(filesOrder)); }); From b55a7be4ffb5b0678cc15348c4f2f9e6cb368148 Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Sun, 11 Sep 2022 14:55:46 +0200 Subject: [PATCH 04/26] Run "unpack" again to create all the ".json-files" files --- langs/en/examples/asyncresource/.json-files | 1 + langs/en/examples/clock/.json-files | 1 + langs/en/examples/context/.json-files | 1 + langs/en/examples/counter/.json-files | 1 + langs/en/examples/counterstore/.json-files | 1 + langs/en/examples/cssanimations/.json-files | 1 + langs/en/examples/ethasketch/.json-files | 1 + langs/en/examples/forms/.json-files | 1 + langs/en/examples/routing/.json-files | 1 + langs/en/examples/scoreboard/.json-files | 1 + langs/en/examples/simpletodos/.json-files | 1 + langs/en/examples/simpletodoshyperscript/.json-files | 1 + langs/en/examples/styledjsx/.json-files | 1 + langs/en/examples/suspensetabs/.json-files | 1 + langs/en/examples/todos/.json-files | 1 + 15 files changed, 15 insertions(+) create mode 100644 langs/en/examples/asyncresource/.json-files create mode 100644 langs/en/examples/clock/.json-files create mode 100644 langs/en/examples/context/.json-files create mode 100644 langs/en/examples/counter/.json-files create mode 100644 langs/en/examples/counterstore/.json-files create mode 100644 langs/en/examples/cssanimations/.json-files create mode 100644 langs/en/examples/ethasketch/.json-files create mode 100644 langs/en/examples/forms/.json-files create mode 100644 langs/en/examples/routing/.json-files create mode 100644 langs/en/examples/scoreboard/.json-files create mode 100644 langs/en/examples/simpletodos/.json-files create mode 100644 langs/en/examples/simpletodoshyperscript/.json-files create mode 100644 langs/en/examples/styledjsx/.json-files create mode 100644 langs/en/examples/suspensetabs/.json-files create mode 100644 langs/en/examples/todos/.json-files diff --git a/langs/en/examples/asyncresource/.json-files b/langs/en/examples/asyncresource/.json-files new file mode 100644 index 00000000..472905b2 --- /dev/null +++ b/langs/en/examples/asyncresource/.json-files @@ -0,0 +1 @@ +["main.jsx"] \ No newline at end of file diff --git a/langs/en/examples/clock/.json-files b/langs/en/examples/clock/.json-files new file mode 100644 index 00000000..ce930acc --- /dev/null +++ b/langs/en/examples/clock/.json-files @@ -0,0 +1 @@ +["main.tsx","Clock.tsx","Lines.tsx","Hand.tsx","utils.tsx","styles.css"] \ No newline at end of file diff --git a/langs/en/examples/context/.json-files b/langs/en/examples/context/.json-files new file mode 100644 index 00000000..b41cbc19 --- /dev/null +++ b/langs/en/examples/context/.json-files @@ -0,0 +1 @@ +["main.tsx","theme.tsx"] \ No newline at end of file diff --git a/langs/en/examples/counter/.json-files b/langs/en/examples/counter/.json-files new file mode 100644 index 00000000..472905b2 --- /dev/null +++ b/langs/en/examples/counter/.json-files @@ -0,0 +1 @@ +["main.jsx"] \ No newline at end of file diff --git a/langs/en/examples/counterstore/.json-files b/langs/en/examples/counterstore/.json-files new file mode 100644 index 00000000..92ab5bb8 --- /dev/null +++ b/langs/en/examples/counterstore/.json-files @@ -0,0 +1 @@ +["main.jsx","CounterStore.jsx"] \ No newline at end of file diff --git a/langs/en/examples/cssanimations/.json-files b/langs/en/examples/cssanimations/.json-files new file mode 100644 index 00000000..944f8e50 --- /dev/null +++ b/langs/en/examples/cssanimations/.json-files @@ -0,0 +1 @@ +["main.jsx","styles.css"] \ No newline at end of file diff --git a/langs/en/examples/ethasketch/.json-files b/langs/en/examples/ethasketch/.json-files new file mode 100644 index 00000000..7ee597cf --- /dev/null +++ b/langs/en/examples/ethasketch/.json-files @@ -0,0 +1 @@ +["main.tsx","styles.css"] \ No newline at end of file diff --git a/langs/en/examples/forms/.json-files b/langs/en/examples/forms/.json-files new file mode 100644 index 00000000..8b0a4c1e --- /dev/null +++ b/langs/en/examples/forms/.json-files @@ -0,0 +1 @@ +["main.jsx","validation.jsx","styles.css"] \ No newline at end of file diff --git a/langs/en/examples/routing/.json-files b/langs/en/examples/routing/.json-files new file mode 100644 index 00000000..472905b2 --- /dev/null +++ b/langs/en/examples/routing/.json-files @@ -0,0 +1 @@ +["main.jsx"] \ No newline at end of file diff --git a/langs/en/examples/scoreboard/.json-files b/langs/en/examples/scoreboard/.json-files new file mode 100644 index 00000000..472905b2 --- /dev/null +++ b/langs/en/examples/scoreboard/.json-files @@ -0,0 +1 @@ +["main.jsx"] \ No newline at end of file diff --git a/langs/en/examples/simpletodos/.json-files b/langs/en/examples/simpletodos/.json-files new file mode 100644 index 00000000..472905b2 --- /dev/null +++ b/langs/en/examples/simpletodos/.json-files @@ -0,0 +1 @@ +["main.jsx"] \ No newline at end of file diff --git a/langs/en/examples/simpletodoshyperscript/.json-files b/langs/en/examples/simpletodoshyperscript/.json-files new file mode 100644 index 00000000..472905b2 --- /dev/null +++ b/langs/en/examples/simpletodoshyperscript/.json-files @@ -0,0 +1 @@ +["main.jsx"] \ No newline at end of file diff --git a/langs/en/examples/styledjsx/.json-files b/langs/en/examples/styledjsx/.json-files new file mode 100644 index 00000000..9e020162 --- /dev/null +++ b/langs/en/examples/styledjsx/.json-files @@ -0,0 +1 @@ +["main.jsx","tab1.jsx"] \ No newline at end of file diff --git a/langs/en/examples/suspensetabs/.json-files b/langs/en/examples/suspensetabs/.json-files new file mode 100644 index 00000000..ca970f8e --- /dev/null +++ b/langs/en/examples/suspensetabs/.json-files @@ -0,0 +1 @@ +["main.jsx","child.jsx","styles.css"] \ No newline at end of file diff --git a/langs/en/examples/todos/.json-files b/langs/en/examples/todos/.json-files new file mode 100644 index 00000000..c7bf6a6d --- /dev/null +++ b/langs/en/examples/todos/.json-files @@ -0,0 +1 @@ +["main.tsx","utils.tsx"] \ No newline at end of file From bcdb4dbb8c3135d79820bf68801200429946cde0 Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Mon, 12 Sep 2022 11:55:01 +0200 Subject: [PATCH 05/26] Rename "src/js-files" to "src/json-files" --- src/{js-files => json-files}/unpack.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{js-files => json-files}/unpack.js (100%) diff --git a/src/js-files/unpack.js b/src/json-files/unpack.js similarity index 100% rename from src/js-files/unpack.js rename to src/json-files/unpack.js From 58c693e5001d23fbad1c9b5221a01d2e1e26d846 Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Mon, 12 Sep 2022 17:01:35 +0200 Subject: [PATCH 06/26] Add script for packing examples, as a Rollup plugin --- package.json | 1 + rollup.config.mjs | 2 ++ src/json-files/rollup-plugin.js | 56 +++++++++++++++++++++++++++++++++ yarn.lock | 25 +++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 src/json-files/rollup-plugin.js diff --git a/package.json b/package.json index 1d866159..ad7b62a9 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@rollup/plugin-typescript": "^8.3.3", "@types/node": "^18.0.3", "gitlog": "^4.0.4", + "glob": "^8.0.3", "jiti": "^1.14.0", "markdown-magic": "^2.6.0", "patch-package": "^6.4.7", diff --git a/rollup.config.mjs b/rollup.config.mjs index 2e4f1876..db6ae96f 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -7,6 +7,7 @@ import rehypeHighlight from "rehype-highlight"; import rehypeSlug from "rehype-slug"; import rehypeAutolinkHeadings from "rehype-autolink-headings"; import { remarkMdxToc } from "remark-mdx-toc"; +import jsonFiles from "./src/json-files/rollup-plugin.js"; export default { input: "src/index.ts", @@ -30,6 +31,7 @@ export default { ], }), typescript(), + jsonFiles({ include: ['**/examples/**', '**/tutorials/**'] }), json(), dynamicImportVars.default(), ], diff --git a/src/json-files/rollup-plugin.js b/src/json-files/rollup-plugin.js new file mode 100644 index 00000000..0e9ee511 --- /dev/null +++ b/src/json-files/rollup-plugin.js @@ -0,0 +1,56 @@ +// This script is meant to be run on each build, to join a group of files into a +// single JSON file of type JsFiles. Its purpose is to join individual files, so +// that the solid-site can keep using them. + + +// This will be a rollup plugin which joins all the files of a folder into a +// single ".json" file if the folder contains a ".json-files" file with +// the order of the files to join as a JSON stringified array. + +import { createFilter } from '@rollup/pluginutils'; +import glob from 'glob'; +import { resolve, basename, dirname, parse } from 'path'; +import { readdirSync, readFileSync, writeFileSync } from 'fs'; + +export default function pack(pluginOptions) { + const pluginFilter = createFilter(pluginOptions?.include, pluginOptions?.exclude); + + return { + name: 'json-files', // this name will show up in warnings and errors + buildStart() { + const allPaths = glob.sync('**/.json-files', {dot: true, absolute: true}); + const filteredPaths = allPaths.filter(pluginFilter); + // console.log('json-files count', filteredPaths.length); + filteredPaths.forEach((orderPath) => { + let order = []; + try { + const orderContent = readFileSync(orderPath); + order = JSON.parse(orderContent); + } catch (e) { + console.warn(`skipping ${orderPath} because ${e}`); + return; + } + if (!Array.isArray(order)) { + console.warn(`skipping ${orderPath} because it doesn't contain an array`); + return; + } + if (order.length === 0) { + console.warn(`skipping ${orderPath} because the array is empty`); + return; + } + const inputPath = dirname(orderPath); + const outputFilename = `${inputPath}.json`; + const files = order.map((filename) => { + const inputFilename = `${inputPath}/${filename}`; + const parsedFilename = parse(filename); + return { + name: parsedFilename.name, + type: parsedFilename.ext.slice(1), + content: readFileSync(inputFilename, 'utf8') + }; + }); + writeFileSync(outputFilename, JSON.stringify({files})); + }); + } + }; +} diff --git a/yarn.lock b/yarn.lock index 0116b548..bd021051 100644 --- a/yarn.lock +++ b/yarn.lock @@ -321,6 +321,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.1, braces@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -778,6 +785,17 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" + integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + globby@^10.0.2: version "10.0.2" resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" @@ -1757,6 +1775,13 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" + integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" From 7570a46f0a32fb878f2c5490c4fb98d36722cb3b Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Mon, 12 Sep 2022 17:02:32 +0200 Subject: [PATCH 07/26] Pack examples, by running "yarn build" --- langs/en/examples/asyncresource.json | 1 + langs/en/examples/clock.json | 1 + langs/en/examples/context.json | 1 + langs/en/examples/counter.json | 1 + langs/en/examples/counterstore.json | 1 + langs/en/examples/cssanimations.json | 1 + langs/en/examples/ethasketch.json | 1 + langs/en/examples/forms.json | 1 + langs/en/examples/routing.json | 1 + langs/en/examples/scoreboard.json | 1 + langs/en/examples/simpletodos.json | 1 + langs/en/examples/simpletodoshyperscript.json | 1 + langs/en/examples/styledjsx.json | 1 + langs/en/examples/suspensetabs.json | 1 + langs/en/examples/todos.json | 1 + 15 files changed, 15 insertions(+) create mode 100644 langs/en/examples/asyncresource.json create mode 100644 langs/en/examples/clock.json create mode 100644 langs/en/examples/context.json create mode 100644 langs/en/examples/counter.json create mode 100644 langs/en/examples/counterstore.json create mode 100644 langs/en/examples/cssanimations.json create mode 100644 langs/en/examples/ethasketch.json create mode 100644 langs/en/examples/forms.json create mode 100644 langs/en/examples/routing.json create mode 100644 langs/en/examples/scoreboard.json create mode 100644 langs/en/examples/simpletodos.json create mode 100644 langs/en/examples/simpletodoshyperscript.json create mode 100644 langs/en/examples/styledjsx.json create mode 100644 langs/en/examples/suspensetabs.json create mode 100644 langs/en/examples/todos.json diff --git a/langs/en/examples/asyncresource.json b/langs/en/examples/asyncresource.json new file mode 100644 index 00000000..03d62e2e --- /dev/null +++ b/langs/en/examples/asyncresource.json @@ -0,0 +1 @@ +{"files":[{"name":"main","type":"jsx","content":"import { createSignal, createResource } from \"solid-js\";\nimport { render } from \"solid-js/web\";\n\nconst fetchUser = async (id) =>\n (await fetch(`https://swapi.dev/api/people/${id}/`)).json();\n\nconst App = () => {\n const [userId, setUserId] = createSignal();\n const [user] = createResource(userId, fetchUser);\n\n return (\n <>\n setUserId(e.currentTarget.value)}\n />\n {user.loading && \"Loading...\"}\n
\n
{JSON.stringify(user(), null, 2)}
\n
\n \n );\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file diff --git a/langs/en/examples/clock.json b/langs/en/examples/clock.json new file mode 100644 index 00000000..71141470 --- /dev/null +++ b/langs/en/examples/clock.json @@ -0,0 +1 @@ +{"files":[{"name":"main","type":"tsx","content":"import { render } from 'solid-js/web';\nimport { Clock } from './Clock';\nimport './styles.css';\n\nrender(() => , document.getElementById('app')!);\n"},{"name":"Clock","type":"tsx","content":"import { createSignal, onCleanup } from 'solid-js';\nimport { Hand } from './Hand';\nimport { Lines } from './Lines';\nimport { createAnimationLoop } from './utils';\nimport type { Accessor, Component } from 'solid-js';\n\nconst getSecondsSinceMidnight = (): number => (Date.now() - new Date().setHours(0, 0, 0, 0)) / 1000;\n\ntype ClockFaceProps = {\n hour: string;\n minute: string;\n second: string;\n subsecond: string;\n};\n\nexport const ClockFace: Component = (props) => (\n \n \n {/* static */}\n \n \n \n {/* dynamic */}\n \n \n \n \n \n \n);\n\nexport const Clock: Component = () => {\n const [time, setTime] = createSignal(getSecondsSinceMidnight());\n const dispose = createAnimationLoop(() => {\n setTime(getSecondsSinceMidnight());\n });\n onCleanup(dispose);\n\n const rotate = (rotate: number, fixed: number = 1) => `rotate(${(rotate * 360).toFixed(fixed)})`;\n\n const subsecond = () => rotate(time() % 1);\n const second = () => rotate((time() % 60) / 60);\n const minute = () => rotate(((time() / 60) % 60) / 60);\n const hour = () => rotate(((time() / 60 / 60) % 12) / 12);\n\n return (\n
\n \n
\n );\n};\n"},{"name":"Lines","type":"tsx","content":"import { Hand } from './Hand';\nimport { Component, splitProps, For } from 'solid-js';\n\ntype LinesProps = {\n numberOfLines: number;\n class: string;\n length: number;\n width: number;\n};\n\nconst rotate = (index: number, length: number) => `rotate(${(360 * index) / length})`;\n\nexport const Lines: Component = (props) => {\n const [local, rest] = splitProps(props, ['numberOfLines']);\n\n return (\n \n {(_, index) => }\n \n );\n};\n"},{"name":"Hand","type":"tsx","content":"import { Component, splitProps } from 'solid-js';\n\ntype HandProps = { rotate: string; class: string; length: number; width: number; fixed?: boolean };\n\nexport const Hand: Component = (props) => {\n const [local, rest] = splitProps(props, ['rotate', 'length', 'width', 'fixed']);\n return (\n \n );\n};\n"},{"name":"utils","type":"tsx","content":"// ported from voby https://github.com/vobyjs/voby/blob/master/src/hooks/use_scheduler.ts\nimport { Accessor } from 'solid-js';\n\ntype FN = (\n ...args: Arguments\n) => Return;\ntype MaybeAccessor = Accessor | T;\nconst isFunction = (value: unknown): value is (...args: unknown[]) => unknown =>\n typeof value === 'function';\nconst unwrap = (maybeValue: MaybeAccessor): T =>\n isFunction(maybeValue) ? maybeValue() : maybeValue;\n\nexport const createScheduler = ({\n loop,\n callback,\n cancel,\n schedule,\n}: {\n loop?: MaybeAccessor;\n callback: MaybeAccessor>;\n cancel: FN<[T]>;\n schedule: (callback: FN<[U]>) => T;\n}): (() => void) => {\n let tickId: T;\n const work = (): void => {\n if (unwrap(loop)) tick();\n unwrap(callback);\n };\n\n const tick = (): void => {\n tickId = schedule(work);\n };\n\n const dispose = (): void => {\n cancel(tickId);\n };\n\n tick();\n return dispose;\n};\n\nexport const createAnimationLoop = (callback: FrameRequestCallback) =>\n createScheduler({\n callback,\n loop: true,\n cancel: cancelAnimationFrame,\n schedule: requestAnimationFrame,\n });\n"},{"name":"styles","type":"css","content":".clock {\n display: flex;\n justify-content: center;\n align-items: center;\n flex-wrap: wrap;\n height: 100vh;\n}\n\n.subsecond {\n color: silver;\n}\n\n.hour,\n.minute {\n color: black;\n}\n\n.second {\n color: tomato;\n}\n"}]} \ No newline at end of file diff --git a/langs/en/examples/context.json b/langs/en/examples/context.json new file mode 100644 index 00000000..5b9bd9bd --- /dev/null +++ b/langs/en/examples/context.json @@ -0,0 +1 @@ +{"files":[{"name":"main","type":"tsx","content":"import { render } from \"solid-js/web\";\nimport { ThemeProvider, useTheme } from \"./theme\";\n\nfunction App() {\n const [theme, { changeColor }] = useTheme();\n\n return (\n <>\n \n {theme.title}\n \n changeColor(e.currentTarget.value)}\n />\n \n \n );\n}\n\nrender(\n () => (\n \n \n \n ),\n document.getElementById(\"app\")!\n);\n"},{"name":"theme","type":"tsx","content":"import { createContext, useContext, ParentComponent } from \"solid-js\";\r\nimport { createStore } from \"solid-js/store\";\r\n\r\nexport type ThemeContextState = {\r\n readonly color: string;\r\n readonly title: string;\r\n};\r\nexport type ThemeContextValue = [\r\n state: ThemeContextState,\r\n actions: {\r\n changeColor: (color: string) => void;\r\n changeTitle: (title: string) => void;\r\n }\r\n];\r\n\r\nconst defaultState = {\r\n color: \"#66e6ac\",\r\n title: \"Fallback Title\",\r\n};\r\n\r\nconst ThemeContext = createContext([\r\n defaultState,\r\n {\r\n changeColor: () => undefined,\r\n changeTitle: () => undefined,\r\n },\r\n]);\r\n\r\nexport const ThemeProvider: ParentComponent<{\r\n color?: string;\r\n title?: string;\r\n}> = (props) => {\r\n const [state, setState] = createStore({\r\n color: props.color ?? defaultState.color,\r\n title: props.title ?? defaultState.title,\r\n });\r\n\r\n const changeColor = (color: string) => setState(\"color\", color);\r\n const changeTitle = (title: string) => setState(\"title\", title);\r\n\r\n return (\r\n \r\n {props.children}\r\n \r\n );\r\n};\r\n\r\nexport const useTheme = () => useContext(ThemeContext);\r\n"}]} \ No newline at end of file diff --git a/langs/en/examples/counter.json b/langs/en/examples/counter.json new file mode 100644 index 00000000..ce1bd284 --- /dev/null +++ b/langs/en/examples/counter.json @@ -0,0 +1 @@ +{"files":[{"name":"main","type":"jsx","content":"import { createSignal, onCleanup } from \"solid-js\";\nimport { render } from \"solid-js/web\";\n\nconst CountingComponent = () => {\n\tconst [count, setCount] = createSignal(0);\n\tconst interval = setInterval(\n\t\t() => setCount(c => c + 1),\n\t\t1000\n\t);\n\tonCleanup(() => clearInterval(interval));\n\treturn
Count value is {count()}
;\n};\n\nrender(() => , document.getElementById(\"app\"));"}]} \ No newline at end of file diff --git a/langs/en/examples/counterstore.json b/langs/en/examples/counterstore.json new file mode 100644 index 00000000..f323b584 --- /dev/null +++ b/langs/en/examples/counterstore.json @@ -0,0 +1 @@ +{"files":[{"name":"main","type":"jsx","content":"import { render } from \"solid-js/web\";\nimport { CounterProvider, useCounter } from \"CounterStore.tsx\";\n\nconst MiddleComponent = () => ;\n\nconst NestedComponent = () => {\n const [count, { increment, decrement }] = useCounter();\n return (\n <>\n

{count()}

\n \n \n \n );\n};\n\nconst App = () => (\n \n \n \n);\n\nrender(App, document.getElementById(\"app\"));"},{"name":"CounterStore","type":"jsx","content":"import { createSignal, createContext, useContext, Component } from \"solid-js\";\n\ntype CounterStore = [\n () => number,\n { increment?: () => void; decrement?: () => void }\n];\n\nconst CounterContext = createContext([() => 0, {}]);\n\nexport const CounterProvider: Component<{ count: number }> = props => {\n const [count, setCount] = createSignal(props.count || 0),\n store: CounterStore = [\n count,\n {\n increment() {\n setCount(c => c + 1);\n },\n decrement() {\n setCount(c => c - 1);\n }\n }\n ];\n\n return (\n \n {props.children}\n \n );\n};\n\nexport function useCounter() {\n return useContext(CounterContext);\n}"}]} \ No newline at end of file diff --git a/langs/en/examples/cssanimations.json b/langs/en/examples/cssanimations.json new file mode 100644 index 00000000..d3c15bb8 --- /dev/null +++ b/langs/en/examples/cssanimations.json @@ -0,0 +1 @@ +{"files":[{"name":"main","type":"jsx","content":"import { createSignal, For, Match, Switch } from \"solid-js\";\nimport { render } from \"solid-js/web\";\nimport { Transition, TransitionGroup } from \"solid-transition-group\";\nimport \"./styles.css\"\n\nfunction shuffle(array) {\n return array.sort(() => Math.random() - 0.5);\n}\nlet nextId = 10;\n\nconst App = () => {\n const [show, toggleShow] = createSignal(true),\n [select, setSelect] = createSignal(0),\n [numList, setNumList] = createSignal([1, 2, 3, 4, 5, 6, 7, 8, 9]),\n randomIndex = () => Math.floor(Math.random() * numList().length);\n\n return (\n <>\n \n
\n Transition:\n \n {show() && (\n
\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris\n facilisis enim libero, at lacinia diam fermentum id. Pellentesque\n habitant morbi tristique senectus et netus.\n
\n )}\n
\n
\n Animation:\n \n {show() && (\n
\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris\n facilisis enim libero, at lacinia diam fermentum id. Pellentesque\n habitant morbi tristique senectus et netus.\n
\n )}\n
\n
\n Custom JS:\n {\n const a = el.animate([{ opacity: 0 }, { opacity: 1 }], {\n duration: 600\n });\n a.finished.then(done);\n }}\n onExit={(el, done) => {\n const a = el.animate([{ opacity: 1 }, { opacity: 0 }], {\n duration: 600\n });\n a.finished.then(done);\n }}\n >\n {show() && (\n
\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris\n facilisis enim libero, at lacinia diam fermentum id. Pellentesque\n habitant morbi tristique senectus et netus.\n
\n )}\n \n
\n Switch OutIn\n
\n \n \n \n \n

The First

\n
\n \n

The Second

\n
\n \n

The Third

\n
\n
\n
\n Group\n
\n {\n const list = numList(),\n idx = randomIndex();\n setNumList([...list.slice(0, idx), nextId++, ...list.slice(idx)]);\n }}\n >\n Add\n \n {\n const list = numList(),\n idx = randomIndex();\n setNumList([...list.slice(0, idx), ...list.slice(idx + 1)]);\n }}\n >\n Remove\n \n {\n const randomList = shuffle(numList().slice());\n setNumList(randomList);\n }}\n >\n Shuffle\n \n
\n \n {(r) => {r}}\n \n \n );\n};\n\nrender(App, document.getElementById(\"app\"));"},{"name":"styles","type":"css","content":".container {\n position: relative;\n}\n\n.fade-enter-active,\n.fade-exit-active {\n transition: opacity 0.5s;\n}\n.fade-enter,\n.fade-exit-to {\n opacity: 0;\n}\n\n.slide-fade-enter-active {\n transition: all 0.3s ease;\n}\n.slide-fade-exit-active {\n transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);\n}\n.slide-fade-enter,\n.slide-fade-exit-to {\n transform: translateX(10px);\n opacity: 0;\n}\n\n.bounce-enter-active {\n animation: bounce-in 0.5s;\n}\n.bounce-exit-active {\n animation: bounce-in 0.5s reverse;\n}\n@keyframes bounce-in {\n 0% {\n transform: scale(0);\n }\n 50% {\n transform: scale(1.5);\n }\n 100% {\n transform: scale(1);\n }\n}\n\n.list-item {\n transition: all 0.5s;\n display: inline-block;\n margin-right: 10px;\n}\n\n.list-item-enter,\n.list-item-exit-to {\n opacity: 0;\n transform: translateY(30px);\n}\n.list-item-exit-active {\n position: absolute;\n}\n"}]} \ No newline at end of file diff --git a/langs/en/examples/ethasketch.json b/langs/en/examples/ethasketch.json new file mode 100644 index 00000000..24f54b79 --- /dev/null +++ b/langs/en/examples/ethasketch.json @@ -0,0 +1 @@ +{"files":[{"name":"main","type":"tsx","content":"// Project idea from https://www.theodinproject.com/paths/foundations/courses/foundations/lessons/etch-a-sketch-project\nimport { render } from \"solid-js/web\";\nimport { createSignal, createMemo, Index } from \"solid-js\";\n\nimport \"./styles.css\";\n\nconst maxGridPixelWidth = 500;\n\nfunction randomHexColorString(): string {\n return \"#\" + Math.floor(Math.random() * 16777215).toString(16);\n}\n\nfunction clampGridSideLength(newSideLength: number): number {\n return Math.min(Math.max(newSideLength, 0), 100);\n}\n\nfunction EtchASketch() {\n const [gridSideLength, setGridSideLength] = createSignal(10);\n const gridTemplateString = createMemo(\n () =>\n `repeat(${gridSideLength()}, ${maxGridPixelWidth / gridSideLength()}px)`\n );\n\n return (\n <>\n
\n \n \n setGridSideLength(\n clampGridSideLength(e.currentTarget.valueAsNumber)\n )\n }\n />\n
\n \n \n {() => (\n {\n const eventEl = event.currentTarget;\n\n eventEl.style.backgroundColor = randomHexColorString();\n\n setTimeout(() => {\n eventEl.style.backgroundColor = \"initial\";\n }, 500);\n }}\n >\n )}\n \n \n \n );\n}\n\nrender(() => , document.getElementById(\"app\"));\n"},{"name":"styles","type":"css","content":".cell {\n outline: 1px solid #1f1f1f;\n}\n\n.dark .cell {\n outline: 1px solid #efefef;\n}\n"}]} \ No newline at end of file diff --git a/langs/en/examples/forms.json b/langs/en/examples/forms.json new file mode 100644 index 00000000..4c3099d7 --- /dev/null +++ b/langs/en/examples/forms.json @@ -0,0 +1 @@ +{"files":[{"name":"main","type":"jsx","content":"// @ts-nocheck\nimport { render } from \"solid-js/web\";\nimport { createStore } from \"solid-js/store\";\nimport { useForm } from \"./validation\";\nimport \"./styles.css\";\n\nconst EMAILS = [\"johnsmith@outlook.com\", \"mary@gmail.com\", \"djacobs@move.org\"];\n\nfunction fetchUserName(name) {\n return new Promise((resolve) => {\n setTimeout(() => resolve(EMAILS.indexOf(name) > -1), 200);\n });\n}\n\nconst ErrorMessage = (props) => {props.error};\n\nconst App = () => {\n const { validate, formSubmit, errors } = useForm({\n errorClass: \"error-input\"\n });\n const [fields, setFields] = createStore();\n const fn = (form) => {\n // form.submit()\n console.log(\"Done\");\n };\n const userNameExists = async ({ value }) => {\n const exists = await fetchUserName(value);\n return exists && `${value} is already being used`;\n };\n const matchesPassword = ({ value }) =>\n value === fields.password ? false : \"Passwords must Match\";\n\n return (\n
\n

Sign Up

\n
\n \n {errors.email && }\n
\n
\n setFields(\"password\", e.target.value)}\n use:validate\n />\n {errors.password && }\n
\n
\n \n {errors.confirmpassword && (\n \n )}\n
\n\n \n
\n );\n};\n\nrender(App, document.getElementById(\"app\"));\n"},{"name":"validation","type":"jsx","content":"import { createStore } from \"solid-js/store\";\n\nfunction checkValid({ element, validators = [] }, setErrors, errorClass) {\n return async () => {\n element.setCustomValidity(\"\");\n element.checkValidity();\n let message = element.validationMessage;\n if (!message) {\n for (const validator of validators) {\n const text = await validator(element);\n if (text) {\n element.setCustomValidity(text);\n break;\n }\n }\n message = element.validationMessage;\n }\n if (message) {\n errorClass && element.classList.toggle(errorClass, true);\n setErrors({ [element.name]: message });\n }\n };\n}\n\nexport function useForm({ errorClass }) {\n const [errors, setErrors] = createStore({}),\n fields = {};\n\n const validate = (ref, accessor) => {\n const validators = accessor() || [];\n let config;\n fields[ref.name] = config = { element: ref, validators };\n ref.onblur = checkValid(config, setErrors, errorClass);\n ref.oninput = () => {\n if (!errors[ref.name]) return;\n setErrors({ [ref.name]: undefined });\n errorClass && ref.classList.toggle(errorClass, false);\n };\n };\n\n const formSubmit = (ref, accessor) => {\n const callback = accessor() || (() => {});\n ref.setAttribute(\"novalidate\", \"\");\n ref.onsubmit = async (e) => {\n e.preventDefault();\n let errored = false;\n\n for (const k in fields) {\n const field = fields[k];\n await checkValid(field, setErrors, errorClass)();\n if (!errored && field.element.validationMessage) {\n field.element.focus();\n errored = true;\n }\n }\n !errored && callback(ref);\n };\n };\n\n return { validate, formSubmit, errors };\n}\n"},{"name":"styles","type":"css","content":"input {\n display: inline-block;\n padding: 4px;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.error-message {\n color: red;\n padding: 8px;\n}\n.error-input {\n box-shadow: 0px 0px 2px 1px red;\n}"}]} \ No newline at end of file diff --git a/langs/en/examples/routing.json b/langs/en/examples/routing.json new file mode 100644 index 00000000..f22c9499 --- /dev/null +++ b/langs/en/examples/routing.json @@ -0,0 +1 @@ +{"files":[{"name":"main","type":"jsx","content":"import { createSignal, onCleanup, Component } from \"solid-js\";\nimport { render, Switch, Match } from \"solid-js/web\";\n\nfunction createRouteHandler() {\n const [location, setLocation] = createSignal(\n window.location.hash.slice(1) || \"home\"\n ),\n locationHandler = () => setLocation(window.location.hash.slice(1));\n window.addEventListener(\"hashchange\", locationHandler);\n onCleanup(() => window.removeEventListener(\"hashchange\", locationHandler));\n return (match: string) => match === location();\n}\n\nconst Home: Component = () => (\n <>\n

Welcome to this Simple Routing Example

\n

Click the links in the Navigation above to load different routes.

\n \n);\n\nconst Profile: Component = () => (\n <>\n

Your Profile

\n

This section could be about you.

\n \n);\n\nconst Settings: Component = () => (\n <>\n

Settings

\n

All that configuration you never really ever want to look at.

\n \n);\n\nconst App = () => {\n const matches = createRouteHandler();\n return (\n <>\n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file diff --git a/langs/en/examples/scoreboard.json b/langs/en/examples/scoreboard.json new file mode 100644 index 00000000..3ffecc69 --- /dev/null +++ b/langs/en/examples/scoreboard.json @@ -0,0 +1 @@ +{"files":[{"name":"main","type":"jsx","content":"import {\n\tcreateMemo,\n\tcreateSignal,\n\tcreateComputed,\n\tonCleanup,\n\tFor\n} from \"solid-js\";\nimport { createStore } from \"solid-js/store\";\nimport { render } from \"solid-js/web\";\n\nconst App = () => {\n\tlet newName, newScore;\n\tconst [state, setState] = createStore({\n\t\t\tplayers: [\n\t\t\t\t{ name: \"Mark\", score: 3 },\n\t\t\t\t{ name: \"Troy\", score: 2 },\n\t\t\t\t{ name: \"Jenny\", score: 1 },\n\t\t\t\t{ name: \"David\", score: 8 }\n\t\t\t]\n\t\t}),\n\t\tlastPos = new WeakMap(),\n\t\tcurPos = new WeakMap(),\n\t\tgetSorted = createMemo((list = []) => {\n\t\t\tlist.forEach((p, i) => lastPos.set(p, i));\n\t\t\tconst newList = state.players.slice().sort((a, b) => {\n\t\t\t\tif (b.score === a.score) return a.name.localeCompare(b.name); // stabalize the sort\n\t\t\t\treturn b.score - a.score;\n\t\t\t});\n\t\t\tlet updated = newList.length !== list.length;\n\t\t\tnewList.forEach(\n\t\t\t\t(p, i) => lastPos.get(p) !== i && (updated = true) && curPos.set(p, i)\n\t\t\t);\n\t\t\treturn updated ? newList : list;\n\t\t}),\n\t\thandleAddClick = () => {\n\t\t\tconst name = newName.value,\n\t\t\t\tscore = +newScore.value;\n\t\t\tif (!name.length || isNaN(score)) return;\n\t\t\tsetState(\"players\", (p) => [...p, { name: name, score: score }]);\n\t\t\tnewName.value = newScore.value = \"\";\n\t\t},\n\t\thandleDeleteClick = (player) => {\n\t\t\tconst idx = state.players.indexOf(player);\n\t\t\tsetState(\"players\", (p) => [...p.slice(0, idx), ...p.slice(idx + 1)]);\n\t\t},\n\t\thandleScoreChange = (player, { target }) => {\n\t\t\tconst score = +target.value;\n\t\t\tconst idx = state.players.indexOf(player);\n\t\t\tif (isNaN(+score) || idx < 0) return;\n\t\t\tsetState(\"players\", idx, \"score\", score);\n\t\t},\n\t\tcreateStyles = (player) => {\n\t\t\tconst [style, setStyle] = createSignal();\n\t\t\tcreateComputed(() => {\n\t\t\t\tgetSorted();\n\t\t\t\tconst offset = lastPos.get(player) * 18 - curPos.get(player) * 18,\n\t\t\t\t\tt = setTimeout(() =>\n\t\t\t\t\t\tsetStyle({ transition: \"250ms\", transform: null })\n\t\t\t\t\t);\n\t\t\t\tsetStyle({\n\t\t\t\t\ttransform: `translateY(${offset}px)`,\n\t\t\t\t\ttransition: null\n\t\t\t\t});\n\t\t\t\tonCleanup(() => clearTimeout(t));\n\t\t\t});\n\t\t\treturn style;\n\t\t};\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t{(player) => {\n\t\t\t\t\t\tconst getStyles = createStyles(player),\n\t\t\t\t\t\t\t{ name } = player;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t
{name}
\n\t\t\t\t\t\t\t\t
{player.score}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t);\n\t\t\t\t\t}}\n\t\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t{(player) => {\n\t\t\t\t\t\tconst { name, score } = player;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t);\n\t\t\t\t\t}}\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t
\n\t);\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file diff --git a/langs/en/examples/simpletodos.json b/langs/en/examples/simpletodos.json new file mode 100644 index 00000000..2195dfcc --- /dev/null +++ b/langs/en/examples/simpletodos.json @@ -0,0 +1 @@ +{"files":[{"name":"main","type":"jsx","content":"import { createEffect, For } from \"solid-js\";\nimport { createStore } from \"solid-js/store\";\nimport { render } from \"solid-js/web\";\nimport html from \"solid-js/html\";\n\nfunction createLocalStore(initState) {\n const [state, setState] = createStore(initState);\n if (localStorage.todos) setState(JSON.parse(localStorage.todos));\n createEffect(() => (localStorage.todos = JSON.stringify(state)));\n return [state, setState];\n}\n\nconst App = () => {\n const [state, setState] = createLocalStore({\n todos: [],\n newTitle: \"\",\n idCounter: 0\n });\n\n return html`\n
\n

Simple Todos Example

\n state.newTitle}\n oninput=${(e) => setState({ newTitle: e.target.value })}\n />\n \n setState({\n idCounter: state.idCounter + 1,\n todos: [\n ...state.todos,\n {\n id: state.idCounter,\n title: state.newTitle,\n done: false\n }\n ],\n newTitle: \"\"\n })}\n >\n +\n \n <${For} each=${() => state.todos}\n >${(todo) =>\n html`\n
\n {\n const idx = state.todos.findIndex((t) => t.id === todo.id);\n setState(\"todos\", idx, { done: e.target.checked });\n }}\n />\n {\n const idx = state.todos.findIndex((t) => t.id === todo.id);\n setState(\"todos\", idx, { title: e.target.value });\n }}\n />\n \n setState(\"todos\", (t) => t.filter((t) => t.id !== todo.id))}\n >\n x\n \n
\n `}\n \n
\n `;\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file diff --git a/langs/en/examples/simpletodoshyperscript.json b/langs/en/examples/simpletodoshyperscript.json new file mode 100644 index 00000000..26613121 --- /dev/null +++ b/langs/en/examples/simpletodoshyperscript.json @@ -0,0 +1 @@ +{"files":[{"name":"main","type":"jsx","content":"import { createEffect, For } from \"solid-js\";\nimport { createStore } from \"solid-js/store\";\nimport { render } from \"solid-js/web\";\nimport h from \"solid-js/h\";\n\nfunction createLocalStore(initState) {\n const [state, setState] = createStore(initState);\n if (localStorage.todos) setState(JSON.parse(localStorage.todos));\n createEffect(() => (localStorage.todos = JSON.stringify(state)));\n return [state, setState];\n}\n\nconst App = () => {\n const [state, setState] = createLocalStore({\n todos: [],\n newTitle: \"\",\n idCounter: 0\n });\n return [\n h(\"h3\", \"Simple Todos Example\"),\n h(\"input\", {\n type: \"text\",\n placeholder: \"enter todo and click +\",\n value: () => state.newTitle,\n onInput: (e) => setState(\"newTitle\", e.target.value)\n }),\n h(\n \"button\",\n {\n onClick: () =>\n setState((s) => ({\n idCounter: s.idCounter + 1,\n todos: [\n ...s.todos,\n {\n id: state.idCounter,\n title: state.newTitle,\n done: false\n }\n ],\n newTitle: \"\"\n }))\n },\n \"+\"\n ),\n h(For, { each: () => state.todos }, (todo) =>\n h(\n \"div\",\n h(\"input\", {\n type: \"checkbox\",\n checked: todo.done,\n onChange: (e) =>\n setState(\n \"todos\",\n state.todos.findIndex((t) => t.id === todo.id),\n {\n done: e.target.checked\n }\n )\n }),\n h(\"input\", {\n type: \"text\",\n value: todo.title,\n onChange: (e) =>\n setState(\n \"todos\",\n state.todos.findIndex((t) => t.id === todo.id),\n {\n title: e.target.value\n }\n )\n }),\n h(\n \"button\",\n {\n onClick: () =>\n setState(\"todos\", (t) => t.filter((t) => t.id !== todo.id))\n },\n \"x\"\n )\n )\n )\n ];\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file diff --git a/langs/en/examples/styledjsx.json b/langs/en/examples/styledjsx.json new file mode 100644 index 00000000..82f135b9 --- /dev/null +++ b/langs/en/examples/styledjsx.json @@ -0,0 +1 @@ +{"files":[{"name":"main","type":"jsx","content":"import { createSignal } from \"solid-js\";\nimport { render } from \"solid-js/web\";\n\nfunction Button() {\n const [isLoggedIn, login] = createSignal(false);\n return (\n <>\n \n \n \n );\n}\n\nrender(\n () => (\n <>\n \n \n \n {(todo, i) => (\n
\n setTodos(i(), \"done\", e.currentTarget.checked)}\n />\n setTodos(i(), \"title\", e.currentTarget.value)}\n />\n \n
\n )}\n
\n \n );\n};\n\nrender(App, document.getElementById(\"app\")!);\n"},{"name":"utils","type":"tsx","content":"import { createEffect } from \"solid-js\";\r\nimport { createStore, SetStoreFunction, Store } from \"solid-js/store\";\r\n\r\nexport function createLocalStore(\r\n name: string,\r\n init: T\r\n): [Store, SetStoreFunction] {\r\n const localState = localStorage.getItem(name);\r\n const [state, setState] = createStore(\r\n localState ? JSON.parse(localState) : init\r\n );\r\n createEffect(() => localStorage.setItem(name, JSON.stringify(state)));\r\n return [state, setState];\r\n}\r\n\r\nexport function removeIndex(array: readonly T[], index: number): T[] {\r\n return [...array.slice(0, index), ...array.slice(index + 1)];\r\n}\r\n"}]} \ No newline at end of file From d18945e69ba50f92563f4befbe5fb738d0795b8a Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Mon, 12 Sep 2022 20:33:54 +0200 Subject: [PATCH 08/26] Rename ".json-files" to "_index.json" and add "name" and "description" properties --- langs/en/examples/asyncresource/.json-files | 1 - langs/en/examples/asyncresource/_index.json | 5 +++++ langs/en/examples/clock/.json-files | 1 - langs/en/examples/clock/_index.json | 5 +++++ langs/en/examples/context/.json-files | 1 - langs/en/examples/context/_index.json | 5 +++++ langs/en/examples/counter/.json-files | 1 - langs/en/examples/counter/_index.json | 5 +++++ langs/en/examples/counterstore/.json-files | 1 - langs/en/examples/counterstore/_index.json | 3 +++ langs/en/examples/cssanimations/.json-files | 1 - langs/en/examples/cssanimations/_index.json | 5 +++++ langs/en/examples/ethasketch/.json-files | 1 - langs/en/examples/ethasketch/_index.json | 5 +++++ langs/en/examples/forms/.json-files | 1 - langs/en/examples/forms/_index.json | 5 +++++ langs/en/examples/routing/.json-files | 1 - langs/en/examples/routing/_index.json | 3 +++ langs/en/examples/scoreboard/.json-files | 1 - langs/en/examples/scoreboard/_index.json | 5 +++++ langs/en/examples/simpletodos/.json-files | 1 - langs/en/examples/simpletodos/_index.json | 5 +++++ langs/en/examples/simpletodoshyperscript/.json-files | 1 - langs/en/examples/simpletodoshyperscript/_index.json | 5 +++++ langs/en/examples/styledjsx/.json-files | 1 - langs/en/examples/styledjsx/_index.json | 3 +++ langs/en/examples/suspensetabs/.json-files | 1 - langs/en/examples/suspensetabs/_index.json | 5 +++++ langs/en/examples/todos/.json-files | 1 - langs/en/examples/todos/_index.json | 5 +++++ 30 files changed, 69 insertions(+), 15 deletions(-) delete mode 100644 langs/en/examples/asyncresource/.json-files create mode 100644 langs/en/examples/asyncresource/_index.json delete mode 100644 langs/en/examples/clock/.json-files create mode 100644 langs/en/examples/clock/_index.json delete mode 100644 langs/en/examples/context/.json-files create mode 100644 langs/en/examples/context/_index.json delete mode 100644 langs/en/examples/counter/.json-files create mode 100644 langs/en/examples/counter/_index.json delete mode 100644 langs/en/examples/counterstore/.json-files create mode 100644 langs/en/examples/counterstore/_index.json delete mode 100644 langs/en/examples/cssanimations/.json-files create mode 100644 langs/en/examples/cssanimations/_index.json delete mode 100644 langs/en/examples/ethasketch/.json-files create mode 100644 langs/en/examples/ethasketch/_index.json delete mode 100644 langs/en/examples/forms/.json-files create mode 100644 langs/en/examples/forms/_index.json delete mode 100644 langs/en/examples/routing/.json-files create mode 100644 langs/en/examples/routing/_index.json delete mode 100644 langs/en/examples/scoreboard/.json-files create mode 100644 langs/en/examples/scoreboard/_index.json delete mode 100644 langs/en/examples/simpletodos/.json-files create mode 100644 langs/en/examples/simpletodos/_index.json delete mode 100644 langs/en/examples/simpletodoshyperscript/.json-files create mode 100644 langs/en/examples/simpletodoshyperscript/_index.json delete mode 100644 langs/en/examples/styledjsx/.json-files create mode 100644 langs/en/examples/styledjsx/_index.json delete mode 100644 langs/en/examples/suspensetabs/.json-files create mode 100644 langs/en/examples/suspensetabs/_index.json delete mode 100644 langs/en/examples/todos/.json-files create mode 100644 langs/en/examples/todos/_index.json diff --git a/langs/en/examples/asyncresource/.json-files b/langs/en/examples/asyncresource/.json-files deleted file mode 100644 index 472905b2..00000000 --- a/langs/en/examples/asyncresource/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.jsx"] \ No newline at end of file diff --git a/langs/en/examples/asyncresource/_index.json b/langs/en/examples/asyncresource/_index.json new file mode 100644 index 00000000..d8dd20a6 --- /dev/null +++ b/langs/en/examples/asyncresource/_index.json @@ -0,0 +1,5 @@ +{ + "name": "Complex/Async Resource", + "description": "Ajax requests to SWAPI with Promise cancellation", + "files": ["main.jsx"] +} \ No newline at end of file diff --git a/langs/en/examples/clock/.json-files b/langs/en/examples/clock/.json-files deleted file mode 100644 index ce930acc..00000000 --- a/langs/en/examples/clock/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.tsx","Clock.tsx","Lines.tsx","Hand.tsx","utils.tsx","styles.css"] \ No newline at end of file diff --git a/langs/en/examples/clock/_index.json b/langs/en/examples/clock/_index.json new file mode 100644 index 00000000..09d0e56e --- /dev/null +++ b/langs/en/examples/clock/_index.json @@ -0,0 +1,5 @@ +{ + "name": "Complex/Clock", + "description": "Demonstrates Solid reactivity with a real-time clock example", + "files": ["main.tsx","Clock.tsx","Lines.tsx","Hand.tsx","utils.tsx","styles.css"] +} \ No newline at end of file diff --git a/langs/en/examples/context/.json-files b/langs/en/examples/context/.json-files deleted file mode 100644 index b41cbc19..00000000 --- a/langs/en/examples/context/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.tsx","theme.tsx"] \ No newline at end of file diff --git a/langs/en/examples/context/_index.json b/langs/en/examples/context/_index.json new file mode 100644 index 00000000..ae25e286 --- /dev/null +++ b/langs/en/examples/context/_index.json @@ -0,0 +1,5 @@ +{ + "name": "Basic/Context", + "description": "A simple color picker using Context", + "files": ["main.tsx","theme.tsx"] +} \ No newline at end of file diff --git a/langs/en/examples/counter/.json-files b/langs/en/examples/counter/.json-files deleted file mode 100644 index 472905b2..00000000 --- a/langs/en/examples/counter/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.jsx"] \ No newline at end of file diff --git a/langs/en/examples/counter/_index.json b/langs/en/examples/counter/_index.json new file mode 100644 index 00000000..5c160030 --- /dev/null +++ b/langs/en/examples/counter/_index.json @@ -0,0 +1,5 @@ +{ + "name": "Basic/Counter", + "description": "A simple standard counter example", + "files": ["main.jsx"] +} \ No newline at end of file diff --git a/langs/en/examples/counterstore/.json-files b/langs/en/examples/counterstore/.json-files deleted file mode 100644 index 92ab5bb8..00000000 --- a/langs/en/examples/counterstore/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.jsx","CounterStore.jsx"] \ No newline at end of file diff --git a/langs/en/examples/counterstore/_index.json b/langs/en/examples/counterstore/_index.json new file mode 100644 index 00000000..7668bc47 --- /dev/null +++ b/langs/en/examples/counterstore/_index.json @@ -0,0 +1,3 @@ +{ + "files": ["main.jsx","CounterStore.jsx"] +} \ No newline at end of file diff --git a/langs/en/examples/cssanimations/.json-files b/langs/en/examples/cssanimations/.json-files deleted file mode 100644 index 944f8e50..00000000 --- a/langs/en/examples/cssanimations/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.jsx","styles.css"] \ No newline at end of file diff --git a/langs/en/examples/cssanimations/_index.json b/langs/en/examples/cssanimations/_index.json new file mode 100644 index 00000000..023d35de --- /dev/null +++ b/langs/en/examples/cssanimations/_index.json @@ -0,0 +1,5 @@ +{ + "name": "Basic/CSS Animations", + "description": "Using Solid Transition Group", + "files": ["main.jsx","styles.css"] +} \ No newline at end of file diff --git a/langs/en/examples/ethasketch/.json-files b/langs/en/examples/ethasketch/.json-files deleted file mode 100644 index 7ee597cf..00000000 --- a/langs/en/examples/ethasketch/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.tsx","styles.css"] \ No newline at end of file diff --git a/langs/en/examples/ethasketch/_index.json b/langs/en/examples/ethasketch/_index.json new file mode 100644 index 00000000..c729e695 --- /dev/null +++ b/langs/en/examples/ethasketch/_index.json @@ -0,0 +1,5 @@ +{ + "name": "Complex/Etch A Sketch", + "description": "Uses Index and createMemo to create a grid graphic", + "files": ["main.tsx","styles.css"] +} \ No newline at end of file diff --git a/langs/en/examples/forms/.json-files b/langs/en/examples/forms/.json-files deleted file mode 100644 index 8b0a4c1e..00000000 --- a/langs/en/examples/forms/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.jsx","validation.jsx","styles.css"] \ No newline at end of file diff --git a/langs/en/examples/forms/_index.json b/langs/en/examples/forms/_index.json new file mode 100644 index 00000000..1684210a --- /dev/null +++ b/langs/en/examples/forms/_index.json @@ -0,0 +1,5 @@ +{ + "name": "Basic/Form Validation", + "description": "HTML 5 validators with custom async validation", + "files": ["main.jsx","validation.jsx","styles.css"] +} \ No newline at end of file diff --git a/langs/en/examples/routing/.json-files b/langs/en/examples/routing/.json-files deleted file mode 100644 index 472905b2..00000000 --- a/langs/en/examples/routing/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.jsx"] \ No newline at end of file diff --git a/langs/en/examples/routing/_index.json b/langs/en/examples/routing/_index.json new file mode 100644 index 00000000..270be50d --- /dev/null +++ b/langs/en/examples/routing/_index.json @@ -0,0 +1,3 @@ +{ + "files": ["main.jsx"] +} \ No newline at end of file diff --git a/langs/en/examples/scoreboard/.json-files b/langs/en/examples/scoreboard/.json-files deleted file mode 100644 index 472905b2..00000000 --- a/langs/en/examples/scoreboard/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.jsx"] \ No newline at end of file diff --git a/langs/en/examples/scoreboard/_index.json b/langs/en/examples/scoreboard/_index.json new file mode 100644 index 00000000..13d3a201 --- /dev/null +++ b/langs/en/examples/scoreboard/_index.json @@ -0,0 +1,5 @@ +{ + "name": "Complex/Scoreboard", + "description": "Make use of hooks to do simple transitions", + "files": ["main.jsx"] +} \ No newline at end of file diff --git a/langs/en/examples/simpletodos/.json-files b/langs/en/examples/simpletodos/.json-files deleted file mode 100644 index 472905b2..00000000 --- a/langs/en/examples/simpletodos/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.jsx"] \ No newline at end of file diff --git a/langs/en/examples/simpletodos/_index.json b/langs/en/examples/simpletodos/_index.json new file mode 100644 index 00000000..be30704f --- /dev/null +++ b/langs/en/examples/simpletodos/_index.json @@ -0,0 +1,5 @@ +{ + "name": "Complex/Simple Todos Template Literals", + "description": "Simple Todos using Lit DOM Expressions", + "files": ["main.jsx"] +} \ No newline at end of file diff --git a/langs/en/examples/simpletodoshyperscript/.json-files b/langs/en/examples/simpletodoshyperscript/.json-files deleted file mode 100644 index 472905b2..00000000 --- a/langs/en/examples/simpletodoshyperscript/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.jsx"] \ No newline at end of file diff --git a/langs/en/examples/simpletodoshyperscript/_index.json b/langs/en/examples/simpletodoshyperscript/_index.json new file mode 100644 index 00000000..dc0b43f5 --- /dev/null +++ b/langs/en/examples/simpletodoshyperscript/_index.json @@ -0,0 +1,5 @@ +{ + "name": "Complex/Simple Todos Hyperscript", + "description": "Simple Todos using Hyper DOM Expressions", + "files": ["main.jsx"] +} \ No newline at end of file diff --git a/langs/en/examples/styledjsx/.json-files b/langs/en/examples/styledjsx/.json-files deleted file mode 100644 index 9e020162..00000000 --- a/langs/en/examples/styledjsx/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.jsx","tab1.jsx"] \ No newline at end of file diff --git a/langs/en/examples/styledjsx/_index.json b/langs/en/examples/styledjsx/_index.json new file mode 100644 index 00000000..dae02405 --- /dev/null +++ b/langs/en/examples/styledjsx/_index.json @@ -0,0 +1,3 @@ +{ + "files": ["main.jsx","tab1.jsx"] +} \ No newline at end of file diff --git a/langs/en/examples/suspensetabs/.json-files b/langs/en/examples/suspensetabs/.json-files deleted file mode 100644 index ca970f8e..00000000 --- a/langs/en/examples/suspensetabs/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.jsx","child.jsx","styles.css"] \ No newline at end of file diff --git a/langs/en/examples/suspensetabs/_index.json b/langs/en/examples/suspensetabs/_index.json new file mode 100644 index 00000000..8538f66c --- /dev/null +++ b/langs/en/examples/suspensetabs/_index.json @@ -0,0 +1,5 @@ +{ + "name": "Complex/Suspense Transitions", + "description": "Deferred loading spinners for smooth UX", + "files": ["main.jsx","child.jsx","styles.css"] +} \ No newline at end of file diff --git a/langs/en/examples/todos/.json-files b/langs/en/examples/todos/.json-files deleted file mode 100644 index c7bf6a6d..00000000 --- a/langs/en/examples/todos/.json-files +++ /dev/null @@ -1 +0,0 @@ -["main.tsx","utils.tsx"] \ No newline at end of file diff --git a/langs/en/examples/todos/_index.json b/langs/en/examples/todos/_index.json new file mode 100644 index 00000000..d85d352e --- /dev/null +++ b/langs/en/examples/todos/_index.json @@ -0,0 +1,5 @@ +{ + "name": "Basic/Simple Todos", + "description": "Todos with LocalStorage persistence", + "files": ["main.tsx","utils.tsx"] +} \ No newline at end of file From 5ff1cd6e745afa18fa029b95ab0855ac17837f3f Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Mon, 12 Sep 2022 20:36:27 +0200 Subject: [PATCH 09/26] Add "_index.json" with an "examples" property --- langs/en/examples/_index.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 langs/en/examples/_index.json diff --git a/langs/en/examples/_index.json b/langs/en/examples/_index.json new file mode 100644 index 00000000..f029fb89 --- /dev/null +++ b/langs/en/examples/_index.json @@ -0,0 +1,3 @@ +{ + "examples": ["counter","todos","forms","cssanimations","context","clock","ethasketch","scoreboard","asyncresource","suspensetabs","simpletodos","simpletodoshyperscript"] +} From 87bd39bd14987126cd33c386db87a00d5dce7e92 Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Mon, 12 Sep 2022 20:38:26 +0200 Subject: [PATCH 10/26] Remove jsonFiles plugin options because too brittle --- rollup.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rollup.config.mjs b/rollup.config.mjs index db6ae96f..c324b2be 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -31,7 +31,7 @@ export default { ], }), typescript(), - jsonFiles({ include: ['**/examples/**', '**/tutorials/**'] }), + jsonFiles(), json(), dynamicImportVars.default(), ], From d62caf7734c7c5acb49fc9f9476ec2e2c7d1c925 Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Tue, 13 Sep 2022 08:57:02 +0200 Subject: [PATCH 11/26] Adapt plugin to new descriptor; hardcode options; export id, name, description too --- src/json-files/rollup-plugin.js | 56 +++++++++++++++------------------ 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/json-files/rollup-plugin.js b/src/json-files/rollup-plugin.js index 0e9ee511..170b6b00 100644 --- a/src/json-files/rollup-plugin.js +++ b/src/json-files/rollup-plugin.js @@ -2,46 +2,37 @@ // single JSON file of type JsFiles. Its purpose is to join individual files, so // that the solid-site can keep using them. - -// This will be a rollup plugin which joins all the files of a folder into a -// single ".json" file if the folder contains a ".json-files" file with -// the order of the files to join as a JSON stringified array. - -import { createFilter } from '@rollup/pluginutils'; import glob from 'glob'; -import { resolve, basename, dirname, parse } from 'path'; -import { readdirSync, readFileSync, writeFileSync } from 'fs'; +import { dirname, parse, basename } from 'path'; +import { readFileSync, writeFileSync } from 'fs'; export default function pack(pluginOptions) { - const pluginFilter = createFilter(pluginOptions?.include, pluginOptions?.exclude); - + const pluginName = 'json-files'; + if (pluginOptions) { + console.warn(`[${pluginName}] no plugin options are supported`); + } return { - name: 'json-files', // this name will show up in warnings and errors + name: pluginName, buildStart() { - const allPaths = glob.sync('**/.json-files', {dot: true, absolute: true}); - const filteredPaths = allPaths.filter(pluginFilter); - // console.log('json-files count', filteredPaths.length); - filteredPaths.forEach((orderPath) => { - let order = []; + const matchers = '{**/examples/*/_index.json,**/tutorials/*/*/_index.json}'; + const allPaths = glob.sync(matchers, {absolute: true}); + allPaths.forEach((descriptorPath) => { + let descriptor = {}; try { - const orderContent = readFileSync(orderPath); - order = JSON.parse(orderContent); + descriptor = JSON.parse(readFileSync(descriptorPath, 'utf8')); } catch (e) { - console.warn(`skipping ${orderPath} because ${e}`); - return; - } - if (!Array.isArray(order)) { - console.warn(`skipping ${orderPath} because it doesn't contain an array`); + console.warn(`[${pluginName}] skipping ${descriptorPath} because ${e}`); return; } - if (order.length === 0) { - console.warn(`skipping ${orderPath} because the array is empty`); + if (!Array.isArray(descriptor.files) || descriptor.files.length === 0) { + console.warn(`[${pluginName}] skipping ${descriptorPath} because the files property is not a list or it's an empty list`); return; } - const inputPath = dirname(orderPath); - const outputFilename = `${inputPath}.json`; - const files = order.map((filename) => { - const inputFilename = `${inputPath}/${filename}`; + + const examplePath = dirname(descriptorPath); + const outputFilename = `${examplePath}.json`; + const files = descriptor.files.map((filename) => { + const inputFilename = `${examplePath}/${filename}`; const parsedFilename = parse(filename); return { name: parsedFilename.name, @@ -49,7 +40,12 @@ export default function pack(pluginOptions) { content: readFileSync(inputFilename, 'utf8') }; }); - writeFileSync(outputFilename, JSON.stringify({files})); + writeFileSync(outputFilename, JSON.stringify({ + id: basename(examplePath), + name: descriptor.name, + description: descriptor.description, + files + })); }); } }; From dfa34933500356cb6521ac68f06db01bfb3fdc2f Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Tue, 13 Sep 2022 08:58:29 +0200 Subject: [PATCH 12/26] Run plugin again --- langs/en/examples/asyncresource.json | 2 +- langs/en/examples/clock.json | 2 +- langs/en/examples/context.json | 2 +- langs/en/examples/counter.json | 2 +- langs/en/examples/counterstore.json | 2 +- langs/en/examples/cssanimations.json | 2 +- langs/en/examples/ethasketch.json | 2 +- langs/en/examples/forms.json | 2 +- langs/en/examples/routing.json | 2 +- langs/en/examples/scoreboard.json | 2 +- langs/en/examples/simpletodos.json | 2 +- langs/en/examples/simpletodoshyperscript.json | 2 +- langs/en/examples/styledjsx.json | 2 +- langs/en/examples/suspensetabs.json | 2 +- langs/en/examples/todos.json | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/langs/en/examples/asyncresource.json b/langs/en/examples/asyncresource.json index 03d62e2e..fb3d002e 100644 --- a/langs/en/examples/asyncresource.json +++ b/langs/en/examples/asyncresource.json @@ -1 +1 @@ -{"files":[{"name":"main","type":"jsx","content":"import { createSignal, createResource } from \"solid-js\";\nimport { render } from \"solid-js/web\";\n\nconst fetchUser = async (id) =>\n (await fetch(`https://swapi.dev/api/people/${id}/`)).json();\n\nconst App = () => {\n const [userId, setUserId] = createSignal();\n const [user] = createResource(userId, fetchUser);\n\n return (\n <>\n setUserId(e.currentTarget.value)}\n />\n {user.loading && \"Loading...\"}\n
\n
{JSON.stringify(user(), null, 2)}
\n
\n \n );\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file +{"id":"asyncresource","name":"Complex/Async Resource","description":"Ajax requests to SWAPI with Promise cancellation","files":[{"name":"main","type":"jsx","content":"import { createSignal, createResource } from \"solid-js\";\nimport { render } from \"solid-js/web\";\n\nconst fetchUser = async (id) =>\n (await fetch(`https://swapi.dev/api/people/${id}/`)).json();\n\nconst App = () => {\n const [userId, setUserId] = createSignal();\n const [user] = createResource(userId, fetchUser);\n\n return (\n <>\n setUserId(e.currentTarget.value)}\n />\n {user.loading && \"Loading...\"}\n
\n
{JSON.stringify(user(), null, 2)}
\n
\n \n );\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file diff --git a/langs/en/examples/clock.json b/langs/en/examples/clock.json index 71141470..3b9f682c 100644 --- a/langs/en/examples/clock.json +++ b/langs/en/examples/clock.json @@ -1 +1 @@ -{"files":[{"name":"main","type":"tsx","content":"import { render } from 'solid-js/web';\nimport { Clock } from './Clock';\nimport './styles.css';\n\nrender(() => , document.getElementById('app')!);\n"},{"name":"Clock","type":"tsx","content":"import { createSignal, onCleanup } from 'solid-js';\nimport { Hand } from './Hand';\nimport { Lines } from './Lines';\nimport { createAnimationLoop } from './utils';\nimport type { Accessor, Component } from 'solid-js';\n\nconst getSecondsSinceMidnight = (): number => (Date.now() - new Date().setHours(0, 0, 0, 0)) / 1000;\n\ntype ClockFaceProps = {\n hour: string;\n minute: string;\n second: string;\n subsecond: string;\n};\n\nexport const ClockFace: Component = (props) => (\n \n \n {/* static */}\n \n \n \n {/* dynamic */}\n \n \n \n \n \n \n);\n\nexport const Clock: Component = () => {\n const [time, setTime] = createSignal(getSecondsSinceMidnight());\n const dispose = createAnimationLoop(() => {\n setTime(getSecondsSinceMidnight());\n });\n onCleanup(dispose);\n\n const rotate = (rotate: number, fixed: number = 1) => `rotate(${(rotate * 360).toFixed(fixed)})`;\n\n const subsecond = () => rotate(time() % 1);\n const second = () => rotate((time() % 60) / 60);\n const minute = () => rotate(((time() / 60) % 60) / 60);\n const hour = () => rotate(((time() / 60 / 60) % 12) / 12);\n\n return (\n
\n \n
\n );\n};\n"},{"name":"Lines","type":"tsx","content":"import { Hand } from './Hand';\nimport { Component, splitProps, For } from 'solid-js';\n\ntype LinesProps = {\n numberOfLines: number;\n class: string;\n length: number;\n width: number;\n};\n\nconst rotate = (index: number, length: number) => `rotate(${(360 * index) / length})`;\n\nexport const Lines: Component = (props) => {\n const [local, rest] = splitProps(props, ['numberOfLines']);\n\n return (\n \n {(_, index) => }\n \n );\n};\n"},{"name":"Hand","type":"tsx","content":"import { Component, splitProps } from 'solid-js';\n\ntype HandProps = { rotate: string; class: string; length: number; width: number; fixed?: boolean };\n\nexport const Hand: Component = (props) => {\n const [local, rest] = splitProps(props, ['rotate', 'length', 'width', 'fixed']);\n return (\n \n );\n};\n"},{"name":"utils","type":"tsx","content":"// ported from voby https://github.com/vobyjs/voby/blob/master/src/hooks/use_scheduler.ts\nimport { Accessor } from 'solid-js';\n\ntype FN = (\n ...args: Arguments\n) => Return;\ntype MaybeAccessor = Accessor | T;\nconst isFunction = (value: unknown): value is (...args: unknown[]) => unknown =>\n typeof value === 'function';\nconst unwrap = (maybeValue: MaybeAccessor): T =>\n isFunction(maybeValue) ? maybeValue() : maybeValue;\n\nexport const createScheduler = ({\n loop,\n callback,\n cancel,\n schedule,\n}: {\n loop?: MaybeAccessor;\n callback: MaybeAccessor>;\n cancel: FN<[T]>;\n schedule: (callback: FN<[U]>) => T;\n}): (() => void) => {\n let tickId: T;\n const work = (): void => {\n if (unwrap(loop)) tick();\n unwrap(callback);\n };\n\n const tick = (): void => {\n tickId = schedule(work);\n };\n\n const dispose = (): void => {\n cancel(tickId);\n };\n\n tick();\n return dispose;\n};\n\nexport const createAnimationLoop = (callback: FrameRequestCallback) =>\n createScheduler({\n callback,\n loop: true,\n cancel: cancelAnimationFrame,\n schedule: requestAnimationFrame,\n });\n"},{"name":"styles","type":"css","content":".clock {\n display: flex;\n justify-content: center;\n align-items: center;\n flex-wrap: wrap;\n height: 100vh;\n}\n\n.subsecond {\n color: silver;\n}\n\n.hour,\n.minute {\n color: black;\n}\n\n.second {\n color: tomato;\n}\n"}]} \ No newline at end of file +{"id":"clock","name":"Complex/Clock","description":"Demonstrates Solid reactivity with a real-time clock example","files":[{"name":"main","type":"tsx","content":"import { render } from 'solid-js/web';\nimport { Clock } from './Clock';\nimport './styles.css';\n\nrender(() => , document.getElementById('app')!);\n"},{"name":"Clock","type":"tsx","content":"import { createSignal, onCleanup } from 'solid-js';\nimport { Hand } from './Hand';\nimport { Lines } from './Lines';\nimport { createAnimationLoop } from './utils';\nimport type { Accessor, Component } from 'solid-js';\n\nconst getSecondsSinceMidnight = (): number => (Date.now() - new Date().setHours(0, 0, 0, 0)) / 1000;\n\ntype ClockFaceProps = {\n hour: string;\n minute: string;\n second: string;\n subsecond: string;\n};\n\nexport const ClockFace: Component = (props) => (\n \n \n {/* static */}\n \n \n \n {/* dynamic */}\n \n \n \n \n \n \n);\n\nexport const Clock: Component = () => {\n const [time, setTime] = createSignal(getSecondsSinceMidnight());\n const dispose = createAnimationLoop(() => {\n setTime(getSecondsSinceMidnight());\n });\n onCleanup(dispose);\n\n const rotate = (rotate: number, fixed: number = 1) => `rotate(${(rotate * 360).toFixed(fixed)})`;\n\n const subsecond = () => rotate(time() % 1);\n const second = () => rotate((time() % 60) / 60);\n const minute = () => rotate(((time() / 60) % 60) / 60);\n const hour = () => rotate(((time() / 60 / 60) % 12) / 12);\n\n return (\n
\n \n
\n );\n};\n"},{"name":"Lines","type":"tsx","content":"import { Hand } from './Hand';\nimport { Component, splitProps, For } from 'solid-js';\n\ntype LinesProps = {\n numberOfLines: number;\n class: string;\n length: number;\n width: number;\n};\n\nconst rotate = (index: number, length: number) => `rotate(${(360 * index) / length})`;\n\nexport const Lines: Component = (props) => {\n const [local, rest] = splitProps(props, ['numberOfLines']);\n\n return (\n \n {(_, index) => }\n \n );\n};\n"},{"name":"Hand","type":"tsx","content":"import { Component, splitProps } from 'solid-js';\n\ntype HandProps = { rotate: string; class: string; length: number; width: number; fixed?: boolean };\n\nexport const Hand: Component = (props) => {\n const [local, rest] = splitProps(props, ['rotate', 'length', 'width', 'fixed']);\n return (\n \n );\n};\n"},{"name":"utils","type":"tsx","content":"// ported from voby https://github.com/vobyjs/voby/blob/master/src/hooks/use_scheduler.ts\nimport { Accessor } from 'solid-js';\n\ntype FN = (\n ...args: Arguments\n) => Return;\ntype MaybeAccessor = Accessor | T;\nconst isFunction = (value: unknown): value is (...args: unknown[]) => unknown =>\n typeof value === 'function';\nconst unwrap = (maybeValue: MaybeAccessor): T =>\n isFunction(maybeValue) ? maybeValue() : maybeValue;\n\nexport const createScheduler = ({\n loop,\n callback,\n cancel,\n schedule,\n}: {\n loop?: MaybeAccessor;\n callback: MaybeAccessor>;\n cancel: FN<[T]>;\n schedule: (callback: FN<[U]>) => T;\n}): (() => void) => {\n let tickId: T;\n const work = (): void => {\n if (unwrap(loop)) tick();\n unwrap(callback);\n };\n\n const tick = (): void => {\n tickId = schedule(work);\n };\n\n const dispose = (): void => {\n cancel(tickId);\n };\n\n tick();\n return dispose;\n};\n\nexport const createAnimationLoop = (callback: FrameRequestCallback) =>\n createScheduler({\n callback,\n loop: true,\n cancel: cancelAnimationFrame,\n schedule: requestAnimationFrame,\n });\n"},{"name":"styles","type":"css","content":".clock {\n display: flex;\n justify-content: center;\n align-items: center;\n flex-wrap: wrap;\n height: 100vh;\n}\n\n.subsecond {\n color: silver;\n}\n\n.hour,\n.minute {\n color: black;\n}\n\n.second {\n color: tomato;\n}\n"}]} \ No newline at end of file diff --git a/langs/en/examples/context.json b/langs/en/examples/context.json index 5b9bd9bd..09f35862 100644 --- a/langs/en/examples/context.json +++ b/langs/en/examples/context.json @@ -1 +1 @@ -{"files":[{"name":"main","type":"tsx","content":"import { render } from \"solid-js/web\";\nimport { ThemeProvider, useTheme } from \"./theme\";\n\nfunction App() {\n const [theme, { changeColor }] = useTheme();\n\n return (\n <>\n \n {theme.title}\n \n changeColor(e.currentTarget.value)}\n />\n \n \n );\n}\n\nrender(\n () => (\n \n \n \n ),\n document.getElementById(\"app\")!\n);\n"},{"name":"theme","type":"tsx","content":"import { createContext, useContext, ParentComponent } from \"solid-js\";\r\nimport { createStore } from \"solid-js/store\";\r\n\r\nexport type ThemeContextState = {\r\n readonly color: string;\r\n readonly title: string;\r\n};\r\nexport type ThemeContextValue = [\r\n state: ThemeContextState,\r\n actions: {\r\n changeColor: (color: string) => void;\r\n changeTitle: (title: string) => void;\r\n }\r\n];\r\n\r\nconst defaultState = {\r\n color: \"#66e6ac\",\r\n title: \"Fallback Title\",\r\n};\r\n\r\nconst ThemeContext = createContext([\r\n defaultState,\r\n {\r\n changeColor: () => undefined,\r\n changeTitle: () => undefined,\r\n },\r\n]);\r\n\r\nexport const ThemeProvider: ParentComponent<{\r\n color?: string;\r\n title?: string;\r\n}> = (props) => {\r\n const [state, setState] = createStore({\r\n color: props.color ?? defaultState.color,\r\n title: props.title ?? defaultState.title,\r\n });\r\n\r\n const changeColor = (color: string) => setState(\"color\", color);\r\n const changeTitle = (title: string) => setState(\"title\", title);\r\n\r\n return (\r\n \r\n {props.children}\r\n \r\n );\r\n};\r\n\r\nexport const useTheme = () => useContext(ThemeContext);\r\n"}]} \ No newline at end of file +{"id":"context","name":"Basic/Context","description":"A simple color picker using Context","files":[{"name":"main","type":"tsx","content":"import { render } from \"solid-js/web\";\nimport { ThemeProvider, useTheme } from \"./theme\";\n\nfunction App() {\n const [theme, { changeColor }] = useTheme();\n\n return (\n <>\n \n {theme.title}\n \n changeColor(e.currentTarget.value)}\n />\n \n \n );\n}\n\nrender(\n () => (\n \n \n \n ),\n document.getElementById(\"app\")!\n);\n"},{"name":"theme","type":"tsx","content":"import { createContext, useContext, ParentComponent } from \"solid-js\";\r\nimport { createStore } from \"solid-js/store\";\r\n\r\nexport type ThemeContextState = {\r\n readonly color: string;\r\n readonly title: string;\r\n};\r\nexport type ThemeContextValue = [\r\n state: ThemeContextState,\r\n actions: {\r\n changeColor: (color: string) => void;\r\n changeTitle: (title: string) => void;\r\n }\r\n];\r\n\r\nconst defaultState = {\r\n color: \"#66e6ac\",\r\n title: \"Fallback Title\",\r\n};\r\n\r\nconst ThemeContext = createContext([\r\n defaultState,\r\n {\r\n changeColor: () => undefined,\r\n changeTitle: () => undefined,\r\n },\r\n]);\r\n\r\nexport const ThemeProvider: ParentComponent<{\r\n color?: string;\r\n title?: string;\r\n}> = (props) => {\r\n const [state, setState] = createStore({\r\n color: props.color ?? defaultState.color,\r\n title: props.title ?? defaultState.title,\r\n });\r\n\r\n const changeColor = (color: string) => setState(\"color\", color);\r\n const changeTitle = (title: string) => setState(\"title\", title);\r\n\r\n return (\r\n \r\n {props.children}\r\n \r\n );\r\n};\r\n\r\nexport const useTheme = () => useContext(ThemeContext);\r\n"}]} \ No newline at end of file diff --git a/langs/en/examples/counter.json b/langs/en/examples/counter.json index ce1bd284..ca000f81 100644 --- a/langs/en/examples/counter.json +++ b/langs/en/examples/counter.json @@ -1 +1 @@ -{"files":[{"name":"main","type":"jsx","content":"import { createSignal, onCleanup } from \"solid-js\";\nimport { render } from \"solid-js/web\";\n\nconst CountingComponent = () => {\n\tconst [count, setCount] = createSignal(0);\n\tconst interval = setInterval(\n\t\t() => setCount(c => c + 1),\n\t\t1000\n\t);\n\tonCleanup(() => clearInterval(interval));\n\treturn
Count value is {count()}
;\n};\n\nrender(() => , document.getElementById(\"app\"));"}]} \ No newline at end of file +{"id":"counter","name":"Basic/Counter","description":"A simple standard counter example","files":[{"name":"main","type":"jsx","content":"import { createSignal, onCleanup } from \"solid-js\";\nimport { render } from \"solid-js/web\";\n\nconst CountingComponent = () => {\n\tconst [count, setCount] = createSignal(0);\n\tconst interval = setInterval(\n\t\t() => setCount(c => c + 1),\n\t\t1000\n\t);\n\tonCleanup(() => clearInterval(interval));\n\treturn
Count value is {count()}
;\n};\n\nrender(() => , document.getElementById(\"app\"));"}]} \ No newline at end of file diff --git a/langs/en/examples/counterstore.json b/langs/en/examples/counterstore.json index f323b584..7a502f26 100644 --- a/langs/en/examples/counterstore.json +++ b/langs/en/examples/counterstore.json @@ -1 +1 @@ -{"files":[{"name":"main","type":"jsx","content":"import { render } from \"solid-js/web\";\nimport { CounterProvider, useCounter } from \"CounterStore.tsx\";\n\nconst MiddleComponent = () => ;\n\nconst NestedComponent = () => {\n const [count, { increment, decrement }] = useCounter();\n return (\n <>\n

{count()}

\n \n \n \n );\n};\n\nconst App = () => (\n \n \n \n);\n\nrender(App, document.getElementById(\"app\"));"},{"name":"CounterStore","type":"jsx","content":"import { createSignal, createContext, useContext, Component } from \"solid-js\";\n\ntype CounterStore = [\n () => number,\n { increment?: () => void; decrement?: () => void }\n];\n\nconst CounterContext = createContext([() => 0, {}]);\n\nexport const CounterProvider: Component<{ count: number }> = props => {\n const [count, setCount] = createSignal(props.count || 0),\n store: CounterStore = [\n count,\n {\n increment() {\n setCount(c => c + 1);\n },\n decrement() {\n setCount(c => c - 1);\n }\n }\n ];\n\n return (\n \n {props.children}\n \n );\n};\n\nexport function useCounter() {\n return useContext(CounterContext);\n}"}]} \ No newline at end of file +{"id":"counterstore","files":[{"name":"main","type":"jsx","content":"import { render } from \"solid-js/web\";\nimport { CounterProvider, useCounter } from \"CounterStore.tsx\";\n\nconst MiddleComponent = () => ;\n\nconst NestedComponent = () => {\n const [count, { increment, decrement }] = useCounter();\n return (\n <>\n

{count()}

\n \n \n \n );\n};\n\nconst App = () => (\n \n \n \n);\n\nrender(App, document.getElementById(\"app\"));"},{"name":"CounterStore","type":"jsx","content":"import { createSignal, createContext, useContext, Component } from \"solid-js\";\n\ntype CounterStore = [\n () => number,\n { increment?: () => void; decrement?: () => void }\n];\n\nconst CounterContext = createContext([() => 0, {}]);\n\nexport const CounterProvider: Component<{ count: number }> = props => {\n const [count, setCount] = createSignal(props.count || 0),\n store: CounterStore = [\n count,\n {\n increment() {\n setCount(c => c + 1);\n },\n decrement() {\n setCount(c => c - 1);\n }\n }\n ];\n\n return (\n \n {props.children}\n \n );\n};\n\nexport function useCounter() {\n return useContext(CounterContext);\n}"}]} \ No newline at end of file diff --git a/langs/en/examples/cssanimations.json b/langs/en/examples/cssanimations.json index d3c15bb8..7514ccc3 100644 --- a/langs/en/examples/cssanimations.json +++ b/langs/en/examples/cssanimations.json @@ -1 +1 @@ -{"files":[{"name":"main","type":"jsx","content":"import { createSignal, For, Match, Switch } from \"solid-js\";\nimport { render } from \"solid-js/web\";\nimport { Transition, TransitionGroup } from \"solid-transition-group\";\nimport \"./styles.css\"\n\nfunction shuffle(array) {\n return array.sort(() => Math.random() - 0.5);\n}\nlet nextId = 10;\n\nconst App = () => {\n const [show, toggleShow] = createSignal(true),\n [select, setSelect] = createSignal(0),\n [numList, setNumList] = createSignal([1, 2, 3, 4, 5, 6, 7, 8, 9]),\n randomIndex = () => Math.floor(Math.random() * numList().length);\n\n return (\n <>\n \n
\n Transition:\n \n {show() && (\n
\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris\n facilisis enim libero, at lacinia diam fermentum id. Pellentesque\n habitant morbi tristique senectus et netus.\n
\n )}\n
\n
\n Animation:\n \n {show() && (\n
\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris\n facilisis enim libero, at lacinia diam fermentum id. Pellentesque\n habitant morbi tristique senectus et netus.\n
\n )}\n
\n
\n Custom JS:\n {\n const a = el.animate([{ opacity: 0 }, { opacity: 1 }], {\n duration: 600\n });\n a.finished.then(done);\n }}\n onExit={(el, done) => {\n const a = el.animate([{ opacity: 1 }, { opacity: 0 }], {\n duration: 600\n });\n a.finished.then(done);\n }}\n >\n {show() && (\n
\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris\n facilisis enim libero, at lacinia diam fermentum id. Pellentesque\n habitant morbi tristique senectus et netus.\n
\n )}\n \n
\n Switch OutIn\n
\n \n \n \n \n

The First

\n
\n \n

The Second

\n
\n \n

The Third

\n
\n
\n
\n Group\n
\n {\n const list = numList(),\n idx = randomIndex();\n setNumList([...list.slice(0, idx), nextId++, ...list.slice(idx)]);\n }}\n >\n Add\n \n {\n const list = numList(),\n idx = randomIndex();\n setNumList([...list.slice(0, idx), ...list.slice(idx + 1)]);\n }}\n >\n Remove\n \n {\n const randomList = shuffle(numList().slice());\n setNumList(randomList);\n }}\n >\n Shuffle\n \n
\n \n {(r) => {r}}\n \n \n );\n};\n\nrender(App, document.getElementById(\"app\"));"},{"name":"styles","type":"css","content":".container {\n position: relative;\n}\n\n.fade-enter-active,\n.fade-exit-active {\n transition: opacity 0.5s;\n}\n.fade-enter,\n.fade-exit-to {\n opacity: 0;\n}\n\n.slide-fade-enter-active {\n transition: all 0.3s ease;\n}\n.slide-fade-exit-active {\n transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);\n}\n.slide-fade-enter,\n.slide-fade-exit-to {\n transform: translateX(10px);\n opacity: 0;\n}\n\n.bounce-enter-active {\n animation: bounce-in 0.5s;\n}\n.bounce-exit-active {\n animation: bounce-in 0.5s reverse;\n}\n@keyframes bounce-in {\n 0% {\n transform: scale(0);\n }\n 50% {\n transform: scale(1.5);\n }\n 100% {\n transform: scale(1);\n }\n}\n\n.list-item {\n transition: all 0.5s;\n display: inline-block;\n margin-right: 10px;\n}\n\n.list-item-enter,\n.list-item-exit-to {\n opacity: 0;\n transform: translateY(30px);\n}\n.list-item-exit-active {\n position: absolute;\n}\n"}]} \ No newline at end of file +{"id":"cssanimations","name":"Basic/CSS Animations","description":"Using Solid Transition Group","files":[{"name":"main","type":"jsx","content":"import { createSignal, For, Match, Switch } from \"solid-js\";\nimport { render } from \"solid-js/web\";\nimport { Transition, TransitionGroup } from \"solid-transition-group\";\nimport \"./styles.css\"\n\nfunction shuffle(array) {\n return array.sort(() => Math.random() - 0.5);\n}\nlet nextId = 10;\n\nconst App = () => {\n const [show, toggleShow] = createSignal(true),\n [select, setSelect] = createSignal(0),\n [numList, setNumList] = createSignal([1, 2, 3, 4, 5, 6, 7, 8, 9]),\n randomIndex = () => Math.floor(Math.random() * numList().length);\n\n return (\n <>\n \n
\n Transition:\n \n {show() && (\n
\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris\n facilisis enim libero, at lacinia diam fermentum id. Pellentesque\n habitant morbi tristique senectus et netus.\n
\n )}\n
\n
\n Animation:\n \n {show() && (\n
\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris\n facilisis enim libero, at lacinia diam fermentum id. Pellentesque\n habitant morbi tristique senectus et netus.\n
\n )}\n
\n
\n Custom JS:\n {\n const a = el.animate([{ opacity: 0 }, { opacity: 1 }], {\n duration: 600\n });\n a.finished.then(done);\n }}\n onExit={(el, done) => {\n const a = el.animate([{ opacity: 1 }, { opacity: 0 }], {\n duration: 600\n });\n a.finished.then(done);\n }}\n >\n {show() && (\n
\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris\n facilisis enim libero, at lacinia diam fermentum id. Pellentesque\n habitant morbi tristique senectus et netus.\n
\n )}\n \n
\n Switch OutIn\n
\n \n \n \n \n

The First

\n
\n \n

The Second

\n
\n \n

The Third

\n
\n
\n
\n Group\n
\n {\n const list = numList(),\n idx = randomIndex();\n setNumList([...list.slice(0, idx), nextId++, ...list.slice(idx)]);\n }}\n >\n Add\n \n {\n const list = numList(),\n idx = randomIndex();\n setNumList([...list.slice(0, idx), ...list.slice(idx + 1)]);\n }}\n >\n Remove\n \n {\n const randomList = shuffle(numList().slice());\n setNumList(randomList);\n }}\n >\n Shuffle\n \n
\n \n {(r) => {r}}\n \n \n );\n};\n\nrender(App, document.getElementById(\"app\"));"},{"name":"styles","type":"css","content":".container {\n position: relative;\n}\n\n.fade-enter-active,\n.fade-exit-active {\n transition: opacity 0.5s;\n}\n.fade-enter,\n.fade-exit-to {\n opacity: 0;\n}\n\n.slide-fade-enter-active {\n transition: all 0.3s ease;\n}\n.slide-fade-exit-active {\n transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);\n}\n.slide-fade-enter,\n.slide-fade-exit-to {\n transform: translateX(10px);\n opacity: 0;\n}\n\n.bounce-enter-active {\n animation: bounce-in 0.5s;\n}\n.bounce-exit-active {\n animation: bounce-in 0.5s reverse;\n}\n@keyframes bounce-in {\n 0% {\n transform: scale(0);\n }\n 50% {\n transform: scale(1.5);\n }\n 100% {\n transform: scale(1);\n }\n}\n\n.list-item {\n transition: all 0.5s;\n display: inline-block;\n margin-right: 10px;\n}\n\n.list-item-enter,\n.list-item-exit-to {\n opacity: 0;\n transform: translateY(30px);\n}\n.list-item-exit-active {\n position: absolute;\n}\n"}]} \ No newline at end of file diff --git a/langs/en/examples/ethasketch.json b/langs/en/examples/ethasketch.json index 24f54b79..f9153d5f 100644 --- a/langs/en/examples/ethasketch.json +++ b/langs/en/examples/ethasketch.json @@ -1 +1 @@ -{"files":[{"name":"main","type":"tsx","content":"// Project idea from https://www.theodinproject.com/paths/foundations/courses/foundations/lessons/etch-a-sketch-project\nimport { render } from \"solid-js/web\";\nimport { createSignal, createMemo, Index } from \"solid-js\";\n\nimport \"./styles.css\";\n\nconst maxGridPixelWidth = 500;\n\nfunction randomHexColorString(): string {\n return \"#\" + Math.floor(Math.random() * 16777215).toString(16);\n}\n\nfunction clampGridSideLength(newSideLength: number): number {\n return Math.min(Math.max(newSideLength, 0), 100);\n}\n\nfunction EtchASketch() {\n const [gridSideLength, setGridSideLength] = createSignal(10);\n const gridTemplateString = createMemo(\n () =>\n `repeat(${gridSideLength()}, ${maxGridPixelWidth / gridSideLength()}px)`\n );\n\n return (\n <>\n
\n \n \n setGridSideLength(\n clampGridSideLength(e.currentTarget.valueAsNumber)\n )\n }\n />\n
\n \n \n {() => (\n {\n const eventEl = event.currentTarget;\n\n eventEl.style.backgroundColor = randomHexColorString();\n\n setTimeout(() => {\n eventEl.style.backgroundColor = \"initial\";\n }, 500);\n }}\n >\n )}\n \n \n \n );\n}\n\nrender(() => , document.getElementById(\"app\"));\n"},{"name":"styles","type":"css","content":".cell {\n outline: 1px solid #1f1f1f;\n}\n\n.dark .cell {\n outline: 1px solid #efefef;\n}\n"}]} \ No newline at end of file +{"id":"ethasketch","name":"Complex/Etch A Sketch","description":"Uses Index and createMemo to create a grid graphic","files":[{"name":"main","type":"tsx","content":"// Project idea from https://www.theodinproject.com/paths/foundations/courses/foundations/lessons/etch-a-sketch-project\nimport { render } from \"solid-js/web\";\nimport { createSignal, createMemo, Index } from \"solid-js\";\n\nimport \"./styles.css\";\n\nconst maxGridPixelWidth = 500;\n\nfunction randomHexColorString(): string {\n return \"#\" + Math.floor(Math.random() * 16777215).toString(16);\n}\n\nfunction clampGridSideLength(newSideLength: number): number {\n return Math.min(Math.max(newSideLength, 0), 100);\n}\n\nfunction EtchASketch() {\n const [gridSideLength, setGridSideLength] = createSignal(10);\n const gridTemplateString = createMemo(\n () =>\n `repeat(${gridSideLength()}, ${maxGridPixelWidth / gridSideLength()}px)`\n );\n\n return (\n <>\n
\n \n \n setGridSideLength(\n clampGridSideLength(e.currentTarget.valueAsNumber)\n )\n }\n />\n
\n \n \n {() => (\n {\n const eventEl = event.currentTarget;\n\n eventEl.style.backgroundColor = randomHexColorString();\n\n setTimeout(() => {\n eventEl.style.backgroundColor = \"initial\";\n }, 500);\n }}\n >\n )}\n \n \n \n );\n}\n\nrender(() => , document.getElementById(\"app\"));\n"},{"name":"styles","type":"css","content":".cell {\n outline: 1px solid #1f1f1f;\n}\n\n.dark .cell {\n outline: 1px solid #efefef;\n}\n"}]} \ No newline at end of file diff --git a/langs/en/examples/forms.json b/langs/en/examples/forms.json index 4c3099d7..6a3eb1d7 100644 --- a/langs/en/examples/forms.json +++ b/langs/en/examples/forms.json @@ -1 +1 @@ -{"files":[{"name":"main","type":"jsx","content":"// @ts-nocheck\nimport { render } from \"solid-js/web\";\nimport { createStore } from \"solid-js/store\";\nimport { useForm } from \"./validation\";\nimport \"./styles.css\";\n\nconst EMAILS = [\"johnsmith@outlook.com\", \"mary@gmail.com\", \"djacobs@move.org\"];\n\nfunction fetchUserName(name) {\n return new Promise((resolve) => {\n setTimeout(() => resolve(EMAILS.indexOf(name) > -1), 200);\n });\n}\n\nconst ErrorMessage = (props) => {props.error};\n\nconst App = () => {\n const { validate, formSubmit, errors } = useForm({\n errorClass: \"error-input\"\n });\n const [fields, setFields] = createStore();\n const fn = (form) => {\n // form.submit()\n console.log(\"Done\");\n };\n const userNameExists = async ({ value }) => {\n const exists = await fetchUserName(value);\n return exists && `${value} is already being used`;\n };\n const matchesPassword = ({ value }) =>\n value === fields.password ? false : \"Passwords must Match\";\n\n return (\n
\n

Sign Up

\n
\n \n {errors.email && }\n
\n
\n setFields(\"password\", e.target.value)}\n use:validate\n />\n {errors.password && }\n
\n
\n \n {errors.confirmpassword && (\n \n )}\n
\n\n \n
\n );\n};\n\nrender(App, document.getElementById(\"app\"));\n"},{"name":"validation","type":"jsx","content":"import { createStore } from \"solid-js/store\";\n\nfunction checkValid({ element, validators = [] }, setErrors, errorClass) {\n return async () => {\n element.setCustomValidity(\"\");\n element.checkValidity();\n let message = element.validationMessage;\n if (!message) {\n for (const validator of validators) {\n const text = await validator(element);\n if (text) {\n element.setCustomValidity(text);\n break;\n }\n }\n message = element.validationMessage;\n }\n if (message) {\n errorClass && element.classList.toggle(errorClass, true);\n setErrors({ [element.name]: message });\n }\n };\n}\n\nexport function useForm({ errorClass }) {\n const [errors, setErrors] = createStore({}),\n fields = {};\n\n const validate = (ref, accessor) => {\n const validators = accessor() || [];\n let config;\n fields[ref.name] = config = { element: ref, validators };\n ref.onblur = checkValid(config, setErrors, errorClass);\n ref.oninput = () => {\n if (!errors[ref.name]) return;\n setErrors({ [ref.name]: undefined });\n errorClass && ref.classList.toggle(errorClass, false);\n };\n };\n\n const formSubmit = (ref, accessor) => {\n const callback = accessor() || (() => {});\n ref.setAttribute(\"novalidate\", \"\");\n ref.onsubmit = async (e) => {\n e.preventDefault();\n let errored = false;\n\n for (const k in fields) {\n const field = fields[k];\n await checkValid(field, setErrors, errorClass)();\n if (!errored && field.element.validationMessage) {\n field.element.focus();\n errored = true;\n }\n }\n !errored && callback(ref);\n };\n };\n\n return { validate, formSubmit, errors };\n}\n"},{"name":"styles","type":"css","content":"input {\n display: inline-block;\n padding: 4px;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.error-message {\n color: red;\n padding: 8px;\n}\n.error-input {\n box-shadow: 0px 0px 2px 1px red;\n}"}]} \ No newline at end of file +{"id":"forms","name":"Basic/Form Validation","description":"HTML 5 validators with custom async validation","files":[{"name":"main","type":"jsx","content":"// @ts-nocheck\nimport { render } from \"solid-js/web\";\nimport { createStore } from \"solid-js/store\";\nimport { useForm } from \"./validation\";\nimport \"./styles.css\";\n\nconst EMAILS = [\"johnsmith@outlook.com\", \"mary@gmail.com\", \"djacobs@move.org\"];\n\nfunction fetchUserName(name) {\n return new Promise((resolve) => {\n setTimeout(() => resolve(EMAILS.indexOf(name) > -1), 200);\n });\n}\n\nconst ErrorMessage = (props) => {props.error};\n\nconst App = () => {\n const { validate, formSubmit, errors } = useForm({\n errorClass: \"error-input\"\n });\n const [fields, setFields] = createStore();\n const fn = (form) => {\n // form.submit()\n console.log(\"Done\");\n };\n const userNameExists = async ({ value }) => {\n const exists = await fetchUserName(value);\n return exists && `${value} is already being used`;\n };\n const matchesPassword = ({ value }) =>\n value === fields.password ? false : \"Passwords must Match\";\n\n return (\n
\n

Sign Up

\n
\n \n {errors.email && }\n
\n
\n setFields(\"password\", e.target.value)}\n use:validate\n />\n {errors.password && }\n
\n
\n \n {errors.confirmpassword && (\n \n )}\n
\n\n \n
\n );\n};\n\nrender(App, document.getElementById(\"app\"));\n"},{"name":"validation","type":"jsx","content":"import { createStore } from \"solid-js/store\";\n\nfunction checkValid({ element, validators = [] }, setErrors, errorClass) {\n return async () => {\n element.setCustomValidity(\"\");\n element.checkValidity();\n let message = element.validationMessage;\n if (!message) {\n for (const validator of validators) {\n const text = await validator(element);\n if (text) {\n element.setCustomValidity(text);\n break;\n }\n }\n message = element.validationMessage;\n }\n if (message) {\n errorClass && element.classList.toggle(errorClass, true);\n setErrors({ [element.name]: message });\n }\n };\n}\n\nexport function useForm({ errorClass }) {\n const [errors, setErrors] = createStore({}),\n fields = {};\n\n const validate = (ref, accessor) => {\n const validators = accessor() || [];\n let config;\n fields[ref.name] = config = { element: ref, validators };\n ref.onblur = checkValid(config, setErrors, errorClass);\n ref.oninput = () => {\n if (!errors[ref.name]) return;\n setErrors({ [ref.name]: undefined });\n errorClass && ref.classList.toggle(errorClass, false);\n };\n };\n\n const formSubmit = (ref, accessor) => {\n const callback = accessor() || (() => {});\n ref.setAttribute(\"novalidate\", \"\");\n ref.onsubmit = async (e) => {\n e.preventDefault();\n let errored = false;\n\n for (const k in fields) {\n const field = fields[k];\n await checkValid(field, setErrors, errorClass)();\n if (!errored && field.element.validationMessage) {\n field.element.focus();\n errored = true;\n }\n }\n !errored && callback(ref);\n };\n };\n\n return { validate, formSubmit, errors };\n}\n"},{"name":"styles","type":"css","content":"input {\n display: inline-block;\n padding: 4px;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.error-message {\n color: red;\n padding: 8px;\n}\n.error-input {\n box-shadow: 0px 0px 2px 1px red;\n}"}]} \ No newline at end of file diff --git a/langs/en/examples/routing.json b/langs/en/examples/routing.json index f22c9499..bee3848e 100644 --- a/langs/en/examples/routing.json +++ b/langs/en/examples/routing.json @@ -1 +1 @@ -{"files":[{"name":"main","type":"jsx","content":"import { createSignal, onCleanup, Component } from \"solid-js\";\nimport { render, Switch, Match } from \"solid-js/web\";\n\nfunction createRouteHandler() {\n const [location, setLocation] = createSignal(\n window.location.hash.slice(1) || \"home\"\n ),\n locationHandler = () => setLocation(window.location.hash.slice(1));\n window.addEventListener(\"hashchange\", locationHandler);\n onCleanup(() => window.removeEventListener(\"hashchange\", locationHandler));\n return (match: string) => match === location();\n}\n\nconst Home: Component = () => (\n <>\n

Welcome to this Simple Routing Example

\n

Click the links in the Navigation above to load different routes.

\n \n);\n\nconst Profile: Component = () => (\n <>\n

Your Profile

\n

This section could be about you.

\n \n);\n\nconst Settings: Component = () => (\n <>\n

Settings

\n

All that configuration you never really ever want to look at.

\n \n);\n\nconst App = () => {\n const matches = createRouteHandler();\n return (\n <>\n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file +{"id":"routing","files":[{"name":"main","type":"jsx","content":"import { createSignal, onCleanup, Component } from \"solid-js\";\nimport { render, Switch, Match } from \"solid-js/web\";\n\nfunction createRouteHandler() {\n const [location, setLocation] = createSignal(\n window.location.hash.slice(1) || \"home\"\n ),\n locationHandler = () => setLocation(window.location.hash.slice(1));\n window.addEventListener(\"hashchange\", locationHandler);\n onCleanup(() => window.removeEventListener(\"hashchange\", locationHandler));\n return (match: string) => match === location();\n}\n\nconst Home: Component = () => (\n <>\n

Welcome to this Simple Routing Example

\n

Click the links in the Navigation above to load different routes.

\n \n);\n\nconst Profile: Component = () => (\n <>\n

Your Profile

\n

This section could be about you.

\n \n);\n\nconst Settings: Component = () => (\n <>\n

Settings

\n

All that configuration you never really ever want to look at.

\n \n);\n\nconst App = () => {\n const matches = createRouteHandler();\n return (\n <>\n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file diff --git a/langs/en/examples/scoreboard.json b/langs/en/examples/scoreboard.json index 3ffecc69..4eacc62e 100644 --- a/langs/en/examples/scoreboard.json +++ b/langs/en/examples/scoreboard.json @@ -1 +1 @@ -{"files":[{"name":"main","type":"jsx","content":"import {\n\tcreateMemo,\n\tcreateSignal,\n\tcreateComputed,\n\tonCleanup,\n\tFor\n} from \"solid-js\";\nimport { createStore } from \"solid-js/store\";\nimport { render } from \"solid-js/web\";\n\nconst App = () => {\n\tlet newName, newScore;\n\tconst [state, setState] = createStore({\n\t\t\tplayers: [\n\t\t\t\t{ name: \"Mark\", score: 3 },\n\t\t\t\t{ name: \"Troy\", score: 2 },\n\t\t\t\t{ name: \"Jenny\", score: 1 },\n\t\t\t\t{ name: \"David\", score: 8 }\n\t\t\t]\n\t\t}),\n\t\tlastPos = new WeakMap(),\n\t\tcurPos = new WeakMap(),\n\t\tgetSorted = createMemo((list = []) => {\n\t\t\tlist.forEach((p, i) => lastPos.set(p, i));\n\t\t\tconst newList = state.players.slice().sort((a, b) => {\n\t\t\t\tif (b.score === a.score) return a.name.localeCompare(b.name); // stabalize the sort\n\t\t\t\treturn b.score - a.score;\n\t\t\t});\n\t\t\tlet updated = newList.length !== list.length;\n\t\t\tnewList.forEach(\n\t\t\t\t(p, i) => lastPos.get(p) !== i && (updated = true) && curPos.set(p, i)\n\t\t\t);\n\t\t\treturn updated ? newList : list;\n\t\t}),\n\t\thandleAddClick = () => {\n\t\t\tconst name = newName.value,\n\t\t\t\tscore = +newScore.value;\n\t\t\tif (!name.length || isNaN(score)) return;\n\t\t\tsetState(\"players\", (p) => [...p, { name: name, score: score }]);\n\t\t\tnewName.value = newScore.value = \"\";\n\t\t},\n\t\thandleDeleteClick = (player) => {\n\t\t\tconst idx = state.players.indexOf(player);\n\t\t\tsetState(\"players\", (p) => [...p.slice(0, idx), ...p.slice(idx + 1)]);\n\t\t},\n\t\thandleScoreChange = (player, { target }) => {\n\t\t\tconst score = +target.value;\n\t\t\tconst idx = state.players.indexOf(player);\n\t\t\tif (isNaN(+score) || idx < 0) return;\n\t\t\tsetState(\"players\", idx, \"score\", score);\n\t\t},\n\t\tcreateStyles = (player) => {\n\t\t\tconst [style, setStyle] = createSignal();\n\t\t\tcreateComputed(() => {\n\t\t\t\tgetSorted();\n\t\t\t\tconst offset = lastPos.get(player) * 18 - curPos.get(player) * 18,\n\t\t\t\t\tt = setTimeout(() =>\n\t\t\t\t\t\tsetStyle({ transition: \"250ms\", transform: null })\n\t\t\t\t\t);\n\t\t\t\tsetStyle({\n\t\t\t\t\ttransform: `translateY(${offset}px)`,\n\t\t\t\t\ttransition: null\n\t\t\t\t});\n\t\t\t\tonCleanup(() => clearTimeout(t));\n\t\t\t});\n\t\t\treturn style;\n\t\t};\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t{(player) => {\n\t\t\t\t\t\tconst getStyles = createStyles(player),\n\t\t\t\t\t\t\t{ name } = player;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t
{name}
\n\t\t\t\t\t\t\t\t
{player.score}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t);\n\t\t\t\t\t}}\n\t\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t{(player) => {\n\t\t\t\t\t\tconst { name, score } = player;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t);\n\t\t\t\t\t}}\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t
\n\t);\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file +{"id":"scoreboard","name":"Complex/Scoreboard","description":"Make use of hooks to do simple transitions","files":[{"name":"main","type":"jsx","content":"import {\n\tcreateMemo,\n\tcreateSignal,\n\tcreateComputed,\n\tonCleanup,\n\tFor\n} from \"solid-js\";\nimport { createStore } from \"solid-js/store\";\nimport { render } from \"solid-js/web\";\n\nconst App = () => {\n\tlet newName, newScore;\n\tconst [state, setState] = createStore({\n\t\t\tplayers: [\n\t\t\t\t{ name: \"Mark\", score: 3 },\n\t\t\t\t{ name: \"Troy\", score: 2 },\n\t\t\t\t{ name: \"Jenny\", score: 1 },\n\t\t\t\t{ name: \"David\", score: 8 }\n\t\t\t]\n\t\t}),\n\t\tlastPos = new WeakMap(),\n\t\tcurPos = new WeakMap(),\n\t\tgetSorted = createMemo((list = []) => {\n\t\t\tlist.forEach((p, i) => lastPos.set(p, i));\n\t\t\tconst newList = state.players.slice().sort((a, b) => {\n\t\t\t\tif (b.score === a.score) return a.name.localeCompare(b.name); // stabalize the sort\n\t\t\t\treturn b.score - a.score;\n\t\t\t});\n\t\t\tlet updated = newList.length !== list.length;\n\t\t\tnewList.forEach(\n\t\t\t\t(p, i) => lastPos.get(p) !== i && (updated = true) && curPos.set(p, i)\n\t\t\t);\n\t\t\treturn updated ? newList : list;\n\t\t}),\n\t\thandleAddClick = () => {\n\t\t\tconst name = newName.value,\n\t\t\t\tscore = +newScore.value;\n\t\t\tif (!name.length || isNaN(score)) return;\n\t\t\tsetState(\"players\", (p) => [...p, { name: name, score: score }]);\n\t\t\tnewName.value = newScore.value = \"\";\n\t\t},\n\t\thandleDeleteClick = (player) => {\n\t\t\tconst idx = state.players.indexOf(player);\n\t\t\tsetState(\"players\", (p) => [...p.slice(0, idx), ...p.slice(idx + 1)]);\n\t\t},\n\t\thandleScoreChange = (player, { target }) => {\n\t\t\tconst score = +target.value;\n\t\t\tconst idx = state.players.indexOf(player);\n\t\t\tif (isNaN(+score) || idx < 0) return;\n\t\t\tsetState(\"players\", idx, \"score\", score);\n\t\t},\n\t\tcreateStyles = (player) => {\n\t\t\tconst [style, setStyle] = createSignal();\n\t\t\tcreateComputed(() => {\n\t\t\t\tgetSorted();\n\t\t\t\tconst offset = lastPos.get(player) * 18 - curPos.get(player) * 18,\n\t\t\t\t\tt = setTimeout(() =>\n\t\t\t\t\t\tsetStyle({ transition: \"250ms\", transform: null })\n\t\t\t\t\t);\n\t\t\t\tsetStyle({\n\t\t\t\t\ttransform: `translateY(${offset}px)`,\n\t\t\t\t\ttransition: null\n\t\t\t\t});\n\t\t\t\tonCleanup(() => clearTimeout(t));\n\t\t\t});\n\t\t\treturn style;\n\t\t};\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t{(player) => {\n\t\t\t\t\t\tconst getStyles = createStyles(player),\n\t\t\t\t\t\t\t{ name } = player;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t
{name}
\n\t\t\t\t\t\t\t\t
{player.score}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t);\n\t\t\t\t\t}}\n\t\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t{(player) => {\n\t\t\t\t\t\tconst { name, score } = player;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t);\n\t\t\t\t\t}}\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t
\n\t);\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file diff --git a/langs/en/examples/simpletodos.json b/langs/en/examples/simpletodos.json index 2195dfcc..2820b53f 100644 --- a/langs/en/examples/simpletodos.json +++ b/langs/en/examples/simpletodos.json @@ -1 +1 @@ -{"files":[{"name":"main","type":"jsx","content":"import { createEffect, For } from \"solid-js\";\nimport { createStore } from \"solid-js/store\";\nimport { render } from \"solid-js/web\";\nimport html from \"solid-js/html\";\n\nfunction createLocalStore(initState) {\n const [state, setState] = createStore(initState);\n if (localStorage.todos) setState(JSON.parse(localStorage.todos));\n createEffect(() => (localStorage.todos = JSON.stringify(state)));\n return [state, setState];\n}\n\nconst App = () => {\n const [state, setState] = createLocalStore({\n todos: [],\n newTitle: \"\",\n idCounter: 0\n });\n\n return html`\n
\n

Simple Todos Example

\n state.newTitle}\n oninput=${(e) => setState({ newTitle: e.target.value })}\n />\n \n setState({\n idCounter: state.idCounter + 1,\n todos: [\n ...state.todos,\n {\n id: state.idCounter,\n title: state.newTitle,\n done: false\n }\n ],\n newTitle: \"\"\n })}\n >\n +\n \n <${For} each=${() => state.todos}\n >${(todo) =>\n html`\n
\n {\n const idx = state.todos.findIndex((t) => t.id === todo.id);\n setState(\"todos\", idx, { done: e.target.checked });\n }}\n />\n {\n const idx = state.todos.findIndex((t) => t.id === todo.id);\n setState(\"todos\", idx, { title: e.target.value });\n }}\n />\n \n setState(\"todos\", (t) => t.filter((t) => t.id !== todo.id))}\n >\n x\n \n
\n `}\n \n
\n `;\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file +{"id":"simpletodos","name":"Complex/Simple Todos Template Literals","description":"Simple Todos using Lit DOM Expressions","files":[{"name":"main","type":"jsx","content":"import { createEffect, For } from \"solid-js\";\nimport { createStore } from \"solid-js/store\";\nimport { render } from \"solid-js/web\";\nimport html from \"solid-js/html\";\n\nfunction createLocalStore(initState) {\n const [state, setState] = createStore(initState);\n if (localStorage.todos) setState(JSON.parse(localStorage.todos));\n createEffect(() => (localStorage.todos = JSON.stringify(state)));\n return [state, setState];\n}\n\nconst App = () => {\n const [state, setState] = createLocalStore({\n todos: [],\n newTitle: \"\",\n idCounter: 0\n });\n\n return html`\n
\n

Simple Todos Example

\n state.newTitle}\n oninput=${(e) => setState({ newTitle: e.target.value })}\n />\n \n setState({\n idCounter: state.idCounter + 1,\n todos: [\n ...state.todos,\n {\n id: state.idCounter,\n title: state.newTitle,\n done: false\n }\n ],\n newTitle: \"\"\n })}\n >\n +\n \n <${For} each=${() => state.todos}\n >${(todo) =>\n html`\n
\n {\n const idx = state.todos.findIndex((t) => t.id === todo.id);\n setState(\"todos\", idx, { done: e.target.checked });\n }}\n />\n {\n const idx = state.todos.findIndex((t) => t.id === todo.id);\n setState(\"todos\", idx, { title: e.target.value });\n }}\n />\n \n setState(\"todos\", (t) => t.filter((t) => t.id !== todo.id))}\n >\n x\n \n
\n `}\n \n
\n `;\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file diff --git a/langs/en/examples/simpletodoshyperscript.json b/langs/en/examples/simpletodoshyperscript.json index 26613121..9464520a 100644 --- a/langs/en/examples/simpletodoshyperscript.json +++ b/langs/en/examples/simpletodoshyperscript.json @@ -1 +1 @@ -{"files":[{"name":"main","type":"jsx","content":"import { createEffect, For } from \"solid-js\";\nimport { createStore } from \"solid-js/store\";\nimport { render } from \"solid-js/web\";\nimport h from \"solid-js/h\";\n\nfunction createLocalStore(initState) {\n const [state, setState] = createStore(initState);\n if (localStorage.todos) setState(JSON.parse(localStorage.todos));\n createEffect(() => (localStorage.todos = JSON.stringify(state)));\n return [state, setState];\n}\n\nconst App = () => {\n const [state, setState] = createLocalStore({\n todos: [],\n newTitle: \"\",\n idCounter: 0\n });\n return [\n h(\"h3\", \"Simple Todos Example\"),\n h(\"input\", {\n type: \"text\",\n placeholder: \"enter todo and click +\",\n value: () => state.newTitle,\n onInput: (e) => setState(\"newTitle\", e.target.value)\n }),\n h(\n \"button\",\n {\n onClick: () =>\n setState((s) => ({\n idCounter: s.idCounter + 1,\n todos: [\n ...s.todos,\n {\n id: state.idCounter,\n title: state.newTitle,\n done: false\n }\n ],\n newTitle: \"\"\n }))\n },\n \"+\"\n ),\n h(For, { each: () => state.todos }, (todo) =>\n h(\n \"div\",\n h(\"input\", {\n type: \"checkbox\",\n checked: todo.done,\n onChange: (e) =>\n setState(\n \"todos\",\n state.todos.findIndex((t) => t.id === todo.id),\n {\n done: e.target.checked\n }\n )\n }),\n h(\"input\", {\n type: \"text\",\n value: todo.title,\n onChange: (e) =>\n setState(\n \"todos\",\n state.todos.findIndex((t) => t.id === todo.id),\n {\n title: e.target.value\n }\n )\n }),\n h(\n \"button\",\n {\n onClick: () =>\n setState(\"todos\", (t) => t.filter((t) => t.id !== todo.id))\n },\n \"x\"\n )\n )\n )\n ];\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file +{"id":"simpletodoshyperscript","name":"Complex/Simple Todos Hyperscript","description":"Simple Todos using Hyper DOM Expressions","files":[{"name":"main","type":"jsx","content":"import { createEffect, For } from \"solid-js\";\nimport { createStore } from \"solid-js/store\";\nimport { render } from \"solid-js/web\";\nimport h from \"solid-js/h\";\n\nfunction createLocalStore(initState) {\n const [state, setState] = createStore(initState);\n if (localStorage.todos) setState(JSON.parse(localStorage.todos));\n createEffect(() => (localStorage.todos = JSON.stringify(state)));\n return [state, setState];\n}\n\nconst App = () => {\n const [state, setState] = createLocalStore({\n todos: [],\n newTitle: \"\",\n idCounter: 0\n });\n return [\n h(\"h3\", \"Simple Todos Example\"),\n h(\"input\", {\n type: \"text\",\n placeholder: \"enter todo and click +\",\n value: () => state.newTitle,\n onInput: (e) => setState(\"newTitle\", e.target.value)\n }),\n h(\n \"button\",\n {\n onClick: () =>\n setState((s) => ({\n idCounter: s.idCounter + 1,\n todos: [\n ...s.todos,\n {\n id: state.idCounter,\n title: state.newTitle,\n done: false\n }\n ],\n newTitle: \"\"\n }))\n },\n \"+\"\n ),\n h(For, { each: () => state.todos }, (todo) =>\n h(\n \"div\",\n h(\"input\", {\n type: \"checkbox\",\n checked: todo.done,\n onChange: (e) =>\n setState(\n \"todos\",\n state.todos.findIndex((t) => t.id === todo.id),\n {\n done: e.target.checked\n }\n )\n }),\n h(\"input\", {\n type: \"text\",\n value: todo.title,\n onChange: (e) =>\n setState(\n \"todos\",\n state.todos.findIndex((t) => t.id === todo.id),\n {\n title: e.target.value\n }\n )\n }),\n h(\n \"button\",\n {\n onClick: () =>\n setState(\"todos\", (t) => t.filter((t) => t.id !== todo.id))\n },\n \"x\"\n )\n )\n )\n ];\n};\n\nrender(App, document.getElementById(\"app\"));"}]} \ No newline at end of file diff --git a/langs/en/examples/styledjsx.json b/langs/en/examples/styledjsx.json index 82f135b9..9b8f6f99 100644 --- a/langs/en/examples/styledjsx.json +++ b/langs/en/examples/styledjsx.json @@ -1 +1 @@ -{"files":[{"name":"main","type":"jsx","content":"import { createSignal } from \"solid-js\";\nimport { render } from \"solid-js/web\";\n\nfunction Button() {\n const [isLoggedIn, login] = createSignal(false);\n return (\n <>\n \n \n \n );\n}\n\nrender(\n () => (\n <>\n \n \n \n );\n}\n\nrender(\n () => (\n <>\n \n \n \n {(todo, i) => (\n
\n setTodos(i(), \"done\", e.currentTarget.checked)}\n />\n setTodos(i(), \"title\", e.currentTarget.value)}\n />\n \n
\n )}\n
\n \n );\n};\n\nrender(App, document.getElementById(\"app\")!);\n"},{"name":"utils","type":"tsx","content":"import { createEffect } from \"solid-js\";\r\nimport { createStore, SetStoreFunction, Store } from \"solid-js/store\";\r\n\r\nexport function createLocalStore(\r\n name: string,\r\n init: T\r\n): [Store, SetStoreFunction] {\r\n const localState = localStorage.getItem(name);\r\n const [state, setState] = createStore(\r\n localState ? JSON.parse(localState) : init\r\n );\r\n createEffect(() => localStorage.setItem(name, JSON.stringify(state)));\r\n return [state, setState];\r\n}\r\n\r\nexport function removeIndex(array: readonly T[], index: number): T[] {\r\n return [...array.slice(0, index), ...array.slice(index + 1)];\r\n}\r\n"}]} \ No newline at end of file +{"id":"todos","name":"Basic/Simple Todos","description":"Todos with LocalStorage persistence","files":[{"name":"main","type":"tsx","content":"import { createSignal, batch, For } from \"solid-js\";\nimport { render } from \"solid-js/web\";\nimport { createLocalStore, removeIndex } from \"./utils\";\n\ntype TodoItem = { title: string; done: boolean };\n\nconst App = () => {\n const [newTitle, setTitle] = createSignal(\"\");\n const [todos, setTodos] = createLocalStore(\"todos\", []);\n\n const addTodo = (e: SubmitEvent) => {\n e.preventDefault();\n batch(() => {\n setTodos(todos.length, {\n title: newTitle(),\n done: false,\n });\n setTitle(\"\");\n });\n };\n\n return (\n <>\n

Simple Todos Example

\n
\n setTitle(e.currentTarget.value)}\n />\n \n \n \n {(todo, i) => (\n
\n setTodos(i(), \"done\", e.currentTarget.checked)}\n />\n setTodos(i(), \"title\", e.currentTarget.value)}\n />\n \n
\n )}\n
\n \n );\n};\n\nrender(App, document.getElementById(\"app\")!);\n"},{"name":"utils","type":"tsx","content":"import { createEffect } from \"solid-js\";\r\nimport { createStore, SetStoreFunction, Store } from \"solid-js/store\";\r\n\r\nexport function createLocalStore(\r\n name: string,\r\n init: T\r\n): [Store, SetStoreFunction] {\r\n const localState = localStorage.getItem(name);\r\n const [state, setState] = createStore(\r\n localState ? JSON.parse(localState) : init\r\n );\r\n createEffect(() => localStorage.setItem(name, JSON.stringify(state)));\r\n return [state, setState];\r\n}\r\n\r\nexport function removeIndex(array: readonly T[], index: number): T[] {\r\n return [...array.slice(0, index), ...array.slice(index + 1)];\r\n}\r\n"}]} \ No newline at end of file From 9dbc7b0f3b4bb55149cd5000ea2a00d826a33b42 Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Tue, 13 Sep 2022 12:21:28 +0200 Subject: [PATCH 13/26] Add getExample, getExamplesDirectory for retrieving examples --- src/index.ts | 30 ++++++++++++++++++++++++++++-- src/types.ts | 7 +++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 16921e47..72eb7a4d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ -import { DocFile, LessonFile, LessonLookup, ResourceMetadata } from "./types"; +import { DocFile, LessonFile, LessonLookup, ResourceMetadata, Example } from "./types"; -export { DocFile, LessonFile, LessonLookup, ResourceMetadata }; +export { DocFile, LessonFile, LessonLookup, ResourceMetadata, Example }; function noThrow(x: Promise): Promise { return x.catch(() => undefined); @@ -50,3 +50,29 @@ export async function getTutorialDirectory( ); return directory?.default; } + +export async function getExample( + lang: string, + id: string +): Promise { + const example = await noThrow(import(`../langs/${lang}/examples/${id}.json`)); + return example?.default; +} + +export async function getExamplesDirectory( + lang: string +): Promise { + const ids: string[] = await noThrow(import(`../langs/${lang}/examples/_index.json`)); + if (!ids) return undefined; + const result: Example[] = []; + for (const id of ids) { + const example = await getExample(lang, id); + if (!example) { + console.warn(`Example ${id} not found`); + continue; + } + delete example.files; + result.push(example); + } + return result; +} diff --git a/src/types.ts b/src/types.ts index 9f434bf5..dc204985 100644 --- a/src/types.ts +++ b/src/types.ts @@ -25,3 +25,10 @@ export interface LessonFile { solved?: any; markdown?: string; } + +export interface Example { + id: string; + name: string; + description: string; + files?: any; +} From 15ae8ce9303aacb111053b94759ef7645ea29a56 Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Wed, 14 Sep 2022 14:04:43 +0200 Subject: [PATCH 14/26] Add proper type for "files" property of "Example" --- src/index.ts | 4 ++-- src/types.ts | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 72eb7a4d..eb5b5318 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ -import { DocFile, LessonFile, LessonLookup, ResourceMetadata, Example } from "./types"; +import { DocFile, LessonFile, LessonLookup, ResourceMetadata, JsonFile, Example } from "./types"; -export { DocFile, LessonFile, LessonLookup, ResourceMetadata, Example }; +export { DocFile, LessonFile, LessonLookup, ResourceMetadata, JsonFile, Example }; function noThrow(x: Promise): Promise { return x.catch(() => undefined); diff --git a/src/types.ts b/src/types.ts index dc204985..7e20dcad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -26,9 +26,15 @@ export interface LessonFile { markdown?: string; } +export interface JsonFile { + name: string; + type: string; + content: string; +} + export interface Example { id: string; name: string; description: string; - files?: any; + files?: JsonFile[]; } From f1d87e9273d5f7ccb580cdbc8bb888395896f2bb Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Wed, 14 Sep 2022 14:08:03 +0200 Subject: [PATCH 15/26] Fix, "examples/_index" provides an "examples" property --- src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index eb5b5318..a5af7f97 100644 --- a/src/index.ts +++ b/src/index.ts @@ -62,9 +62,9 @@ export async function getExample( export async function getExamplesDirectory( lang: string ): Promise { - const ids: string[] = await noThrow(import(`../langs/${lang}/examples/_index.json`)); + const {examples: ids} = await noThrow(import(`../langs/${lang}/examples/_index.json`)); if (!ids) return undefined; - const result: Example[] = []; + const result = []; for (const id of ids) { const example = await getExample(lang, id); if (!example) { From b19c00a95d08879f7b45acb01b500e758998459b Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Wed, 14 Sep 2022 15:32:05 +0200 Subject: [PATCH 16/26] Fix, account for import caching --- src/index.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index a5af7f97..1795b876 100644 --- a/src/index.ts +++ b/src/index.ts @@ -71,8 +71,13 @@ export async function getExamplesDirectory( console.warn(`Example ${id} not found`); continue; } - delete example.files; - result.push(example); + // REM clone before mutating, otherwise import() cached value will be mutated + const clonedExample = {...example}; + delete clonedExample.files; + // FIXME building the directory on demand is very slow because every user + // has to download all examples data (and their files). Instead, the + // directory should be precomputed by the rollup plugin + result.push(clonedExample); } return result; } From 1372ce214b5f30a74d81a9f710dc322f3078c77e Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Thu, 15 Sep 2022 10:24:44 +0200 Subject: [PATCH 17/26] Rename "/examples/" to "/examples-src/"; "_index" to "$descriptor" - the new "/examples/" folder will contain only the JSON files generated by the rollup plugin --- langs/en/examples-src/$descriptor.json | 14 ++++++++++++++ .../asyncresource/$descriptor.json} | 0 .../asyncresource/main.jsx | 0 .../clock/$descriptor.json} | 0 .../en/{examples => examples-src}/clock/Clock.tsx | 0 langs/en/{examples => examples-src}/clock/Hand.tsx | 0 .../en/{examples => examples-src}/clock/Lines.tsx | 0 langs/en/{examples => examples-src}/clock/main.tsx | 0 .../en/{examples => examples-src}/clock/styles.css | 0 .../en/{examples => examples-src}/clock/utils.tsx | 0 .../context/$descriptor.json} | 0 .../en/{examples => examples-src}/context/main.tsx | 0 .../{examples => examples-src}/context/theme.tsx | 0 .../counter/$descriptor.json} | 0 .../en/{examples => examples-src}/counter/main.jsx | 0 .../counterstore/$descriptor.json} | 0 .../counterstore/CounterStore.jsx | 0 .../counterstore/main.jsx | 0 .../cssanimations/$descriptor.json} | 0 .../cssanimations/main.jsx | 0 .../cssanimations/styles.css | 0 .../ethasketch/$descriptor.json} | 0 .../{examples => examples-src}/ethasketch/main.tsx | 0 .../ethasketch/styles.css | 0 .../forms/$descriptor.json} | 0 langs/en/{examples => examples-src}/forms/main.jsx | 0 .../en/{examples => examples-src}/forms/styles.css | 0 .../forms/validation.jsx | 0 .../routing/$descriptor.json} | 0 .../en/{examples => examples-src}/routing/main.jsx | 0 .../scoreboard/$descriptor.json} | 0 .../{examples => examples-src}/scoreboard/main.jsx | 0 .../simpletodos/$descriptor.json} | 0 .../simpletodos/main.jsx | 0 .../simpletodoshyperscript/$descriptor.json} | 0 .../simpletodoshyperscript/main.jsx | 0 .../styledjsx/$descriptor.json} | 0 .../{examples => examples-src}/styledjsx/main.jsx | 0 .../{examples => examples-src}/styledjsx/tab1.jsx | 0 .../suspensetabs/$descriptor.json} | 0 .../suspensetabs/child.jsx | 0 .../suspensetabs/main.jsx | 0 .../suspensetabs/styles.css | 0 .../todos/$descriptor.json} | 0 langs/en/{examples => examples-src}/todos/main.tsx | 0 .../en/{examples => examples-src}/todos/utils.tsx | 0 langs/en/examples/$descriptor.json | 1 + langs/en/examples/_index.json | 3 --- 48 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 langs/en/examples-src/$descriptor.json rename langs/en/{examples/asyncresource/_index.json => examples-src/asyncresource/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/asyncresource/main.jsx (100%) rename langs/en/{examples/clock/_index.json => examples-src/clock/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/clock/Clock.tsx (100%) rename langs/en/{examples => examples-src}/clock/Hand.tsx (100%) rename langs/en/{examples => examples-src}/clock/Lines.tsx (100%) rename langs/en/{examples => examples-src}/clock/main.tsx (100%) rename langs/en/{examples => examples-src}/clock/styles.css (100%) rename langs/en/{examples => examples-src}/clock/utils.tsx (100%) rename langs/en/{examples/context/_index.json => examples-src/context/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/context/main.tsx (100%) rename langs/en/{examples => examples-src}/context/theme.tsx (100%) rename langs/en/{examples/counter/_index.json => examples-src/counter/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/counter/main.jsx (100%) rename langs/en/{examples/counterstore/_index.json => examples-src/counterstore/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/counterstore/CounterStore.jsx (100%) rename langs/en/{examples => examples-src}/counterstore/main.jsx (100%) rename langs/en/{examples/cssanimations/_index.json => examples-src/cssanimations/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/cssanimations/main.jsx (100%) rename langs/en/{examples => examples-src}/cssanimations/styles.css (100%) rename langs/en/{examples/ethasketch/_index.json => examples-src/ethasketch/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/ethasketch/main.tsx (100%) rename langs/en/{examples => examples-src}/ethasketch/styles.css (100%) rename langs/en/{examples/forms/_index.json => examples-src/forms/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/forms/main.jsx (100%) rename langs/en/{examples => examples-src}/forms/styles.css (100%) rename langs/en/{examples => examples-src}/forms/validation.jsx (100%) rename langs/en/{examples/routing/_index.json => examples-src/routing/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/routing/main.jsx (100%) rename langs/en/{examples/scoreboard/_index.json => examples-src/scoreboard/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/scoreboard/main.jsx (100%) rename langs/en/{examples/simpletodos/_index.json => examples-src/simpletodos/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/simpletodos/main.jsx (100%) rename langs/en/{examples/simpletodoshyperscript/_index.json => examples-src/simpletodoshyperscript/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/simpletodoshyperscript/main.jsx (100%) rename langs/en/{examples/styledjsx/_index.json => examples-src/styledjsx/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/styledjsx/main.jsx (100%) rename langs/en/{examples => examples-src}/styledjsx/tab1.jsx (100%) rename langs/en/{examples/suspensetabs/_index.json => examples-src/suspensetabs/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/suspensetabs/child.jsx (100%) rename langs/en/{examples => examples-src}/suspensetabs/main.jsx (100%) rename langs/en/{examples => examples-src}/suspensetabs/styles.css (100%) rename langs/en/{examples/todos/_index.json => examples-src/todos/$descriptor.json} (100%) rename langs/en/{examples => examples-src}/todos/main.tsx (100%) rename langs/en/{examples => examples-src}/todos/utils.tsx (100%) create mode 100644 langs/en/examples/$descriptor.json delete mode 100644 langs/en/examples/_index.json diff --git a/langs/en/examples-src/$descriptor.json b/langs/en/examples-src/$descriptor.json new file mode 100644 index 00000000..95e1c458 --- /dev/null +++ b/langs/en/examples-src/$descriptor.json @@ -0,0 +1,14 @@ +[ + "counter", + "todos", + "forms", + "cssanimations", + "context", + "clock", + "ethasketch", + "scoreboard", + "asyncresource", + "suspensetabs", + "simpletodos", + "simpletodoshyperscript" +] diff --git a/langs/en/examples/asyncresource/_index.json b/langs/en/examples-src/asyncresource/$descriptor.json similarity index 100% rename from langs/en/examples/asyncresource/_index.json rename to langs/en/examples-src/asyncresource/$descriptor.json diff --git a/langs/en/examples/asyncresource/main.jsx b/langs/en/examples-src/asyncresource/main.jsx similarity index 100% rename from langs/en/examples/asyncresource/main.jsx rename to langs/en/examples-src/asyncresource/main.jsx diff --git a/langs/en/examples/clock/_index.json b/langs/en/examples-src/clock/$descriptor.json similarity index 100% rename from langs/en/examples/clock/_index.json rename to langs/en/examples-src/clock/$descriptor.json diff --git a/langs/en/examples/clock/Clock.tsx b/langs/en/examples-src/clock/Clock.tsx similarity index 100% rename from langs/en/examples/clock/Clock.tsx rename to langs/en/examples-src/clock/Clock.tsx diff --git a/langs/en/examples/clock/Hand.tsx b/langs/en/examples-src/clock/Hand.tsx similarity index 100% rename from langs/en/examples/clock/Hand.tsx rename to langs/en/examples-src/clock/Hand.tsx diff --git a/langs/en/examples/clock/Lines.tsx b/langs/en/examples-src/clock/Lines.tsx similarity index 100% rename from langs/en/examples/clock/Lines.tsx rename to langs/en/examples-src/clock/Lines.tsx diff --git a/langs/en/examples/clock/main.tsx b/langs/en/examples-src/clock/main.tsx similarity index 100% rename from langs/en/examples/clock/main.tsx rename to langs/en/examples-src/clock/main.tsx diff --git a/langs/en/examples/clock/styles.css b/langs/en/examples-src/clock/styles.css similarity index 100% rename from langs/en/examples/clock/styles.css rename to langs/en/examples-src/clock/styles.css diff --git a/langs/en/examples/clock/utils.tsx b/langs/en/examples-src/clock/utils.tsx similarity index 100% rename from langs/en/examples/clock/utils.tsx rename to langs/en/examples-src/clock/utils.tsx diff --git a/langs/en/examples/context/_index.json b/langs/en/examples-src/context/$descriptor.json similarity index 100% rename from langs/en/examples/context/_index.json rename to langs/en/examples-src/context/$descriptor.json diff --git a/langs/en/examples/context/main.tsx b/langs/en/examples-src/context/main.tsx similarity index 100% rename from langs/en/examples/context/main.tsx rename to langs/en/examples-src/context/main.tsx diff --git a/langs/en/examples/context/theme.tsx b/langs/en/examples-src/context/theme.tsx similarity index 100% rename from langs/en/examples/context/theme.tsx rename to langs/en/examples-src/context/theme.tsx diff --git a/langs/en/examples/counter/_index.json b/langs/en/examples-src/counter/$descriptor.json similarity index 100% rename from langs/en/examples/counter/_index.json rename to langs/en/examples-src/counter/$descriptor.json diff --git a/langs/en/examples/counter/main.jsx b/langs/en/examples-src/counter/main.jsx similarity index 100% rename from langs/en/examples/counter/main.jsx rename to langs/en/examples-src/counter/main.jsx diff --git a/langs/en/examples/counterstore/_index.json b/langs/en/examples-src/counterstore/$descriptor.json similarity index 100% rename from langs/en/examples/counterstore/_index.json rename to langs/en/examples-src/counterstore/$descriptor.json diff --git a/langs/en/examples/counterstore/CounterStore.jsx b/langs/en/examples-src/counterstore/CounterStore.jsx similarity index 100% rename from langs/en/examples/counterstore/CounterStore.jsx rename to langs/en/examples-src/counterstore/CounterStore.jsx diff --git a/langs/en/examples/counterstore/main.jsx b/langs/en/examples-src/counterstore/main.jsx similarity index 100% rename from langs/en/examples/counterstore/main.jsx rename to langs/en/examples-src/counterstore/main.jsx diff --git a/langs/en/examples/cssanimations/_index.json b/langs/en/examples-src/cssanimations/$descriptor.json similarity index 100% rename from langs/en/examples/cssanimations/_index.json rename to langs/en/examples-src/cssanimations/$descriptor.json diff --git a/langs/en/examples/cssanimations/main.jsx b/langs/en/examples-src/cssanimations/main.jsx similarity index 100% rename from langs/en/examples/cssanimations/main.jsx rename to langs/en/examples-src/cssanimations/main.jsx diff --git a/langs/en/examples/cssanimations/styles.css b/langs/en/examples-src/cssanimations/styles.css similarity index 100% rename from langs/en/examples/cssanimations/styles.css rename to langs/en/examples-src/cssanimations/styles.css diff --git a/langs/en/examples/ethasketch/_index.json b/langs/en/examples-src/ethasketch/$descriptor.json similarity index 100% rename from langs/en/examples/ethasketch/_index.json rename to langs/en/examples-src/ethasketch/$descriptor.json diff --git a/langs/en/examples/ethasketch/main.tsx b/langs/en/examples-src/ethasketch/main.tsx similarity index 100% rename from langs/en/examples/ethasketch/main.tsx rename to langs/en/examples-src/ethasketch/main.tsx diff --git a/langs/en/examples/ethasketch/styles.css b/langs/en/examples-src/ethasketch/styles.css similarity index 100% rename from langs/en/examples/ethasketch/styles.css rename to langs/en/examples-src/ethasketch/styles.css diff --git a/langs/en/examples/forms/_index.json b/langs/en/examples-src/forms/$descriptor.json similarity index 100% rename from langs/en/examples/forms/_index.json rename to langs/en/examples-src/forms/$descriptor.json diff --git a/langs/en/examples/forms/main.jsx b/langs/en/examples-src/forms/main.jsx similarity index 100% rename from langs/en/examples/forms/main.jsx rename to langs/en/examples-src/forms/main.jsx diff --git a/langs/en/examples/forms/styles.css b/langs/en/examples-src/forms/styles.css similarity index 100% rename from langs/en/examples/forms/styles.css rename to langs/en/examples-src/forms/styles.css diff --git a/langs/en/examples/forms/validation.jsx b/langs/en/examples-src/forms/validation.jsx similarity index 100% rename from langs/en/examples/forms/validation.jsx rename to langs/en/examples-src/forms/validation.jsx diff --git a/langs/en/examples/routing/_index.json b/langs/en/examples-src/routing/$descriptor.json similarity index 100% rename from langs/en/examples/routing/_index.json rename to langs/en/examples-src/routing/$descriptor.json diff --git a/langs/en/examples/routing/main.jsx b/langs/en/examples-src/routing/main.jsx similarity index 100% rename from langs/en/examples/routing/main.jsx rename to langs/en/examples-src/routing/main.jsx diff --git a/langs/en/examples/scoreboard/_index.json b/langs/en/examples-src/scoreboard/$descriptor.json similarity index 100% rename from langs/en/examples/scoreboard/_index.json rename to langs/en/examples-src/scoreboard/$descriptor.json diff --git a/langs/en/examples/scoreboard/main.jsx b/langs/en/examples-src/scoreboard/main.jsx similarity index 100% rename from langs/en/examples/scoreboard/main.jsx rename to langs/en/examples-src/scoreboard/main.jsx diff --git a/langs/en/examples/simpletodos/_index.json b/langs/en/examples-src/simpletodos/$descriptor.json similarity index 100% rename from langs/en/examples/simpletodos/_index.json rename to langs/en/examples-src/simpletodos/$descriptor.json diff --git a/langs/en/examples/simpletodos/main.jsx b/langs/en/examples-src/simpletodos/main.jsx similarity index 100% rename from langs/en/examples/simpletodos/main.jsx rename to langs/en/examples-src/simpletodos/main.jsx diff --git a/langs/en/examples/simpletodoshyperscript/_index.json b/langs/en/examples-src/simpletodoshyperscript/$descriptor.json similarity index 100% rename from langs/en/examples/simpletodoshyperscript/_index.json rename to langs/en/examples-src/simpletodoshyperscript/$descriptor.json diff --git a/langs/en/examples/simpletodoshyperscript/main.jsx b/langs/en/examples-src/simpletodoshyperscript/main.jsx similarity index 100% rename from langs/en/examples/simpletodoshyperscript/main.jsx rename to langs/en/examples-src/simpletodoshyperscript/main.jsx diff --git a/langs/en/examples/styledjsx/_index.json b/langs/en/examples-src/styledjsx/$descriptor.json similarity index 100% rename from langs/en/examples/styledjsx/_index.json rename to langs/en/examples-src/styledjsx/$descriptor.json diff --git a/langs/en/examples/styledjsx/main.jsx b/langs/en/examples-src/styledjsx/main.jsx similarity index 100% rename from langs/en/examples/styledjsx/main.jsx rename to langs/en/examples-src/styledjsx/main.jsx diff --git a/langs/en/examples/styledjsx/tab1.jsx b/langs/en/examples-src/styledjsx/tab1.jsx similarity index 100% rename from langs/en/examples/styledjsx/tab1.jsx rename to langs/en/examples-src/styledjsx/tab1.jsx diff --git a/langs/en/examples/suspensetabs/_index.json b/langs/en/examples-src/suspensetabs/$descriptor.json similarity index 100% rename from langs/en/examples/suspensetabs/_index.json rename to langs/en/examples-src/suspensetabs/$descriptor.json diff --git a/langs/en/examples/suspensetabs/child.jsx b/langs/en/examples-src/suspensetabs/child.jsx similarity index 100% rename from langs/en/examples/suspensetabs/child.jsx rename to langs/en/examples-src/suspensetabs/child.jsx diff --git a/langs/en/examples/suspensetabs/main.jsx b/langs/en/examples-src/suspensetabs/main.jsx similarity index 100% rename from langs/en/examples/suspensetabs/main.jsx rename to langs/en/examples-src/suspensetabs/main.jsx diff --git a/langs/en/examples/suspensetabs/styles.css b/langs/en/examples-src/suspensetabs/styles.css similarity index 100% rename from langs/en/examples/suspensetabs/styles.css rename to langs/en/examples-src/suspensetabs/styles.css diff --git a/langs/en/examples/todos/_index.json b/langs/en/examples-src/todos/$descriptor.json similarity index 100% rename from langs/en/examples/todos/_index.json rename to langs/en/examples-src/todos/$descriptor.json diff --git a/langs/en/examples/todos/main.tsx b/langs/en/examples-src/todos/main.tsx similarity index 100% rename from langs/en/examples/todos/main.tsx rename to langs/en/examples-src/todos/main.tsx diff --git a/langs/en/examples/todos/utils.tsx b/langs/en/examples-src/todos/utils.tsx similarity index 100% rename from langs/en/examples/todos/utils.tsx rename to langs/en/examples-src/todos/utils.tsx diff --git a/langs/en/examples/$descriptor.json b/langs/en/examples/$descriptor.json new file mode 100644 index 00000000..b14180b8 --- /dev/null +++ b/langs/en/examples/$descriptor.json @@ -0,0 +1 @@ +[{"id":"counter","name":"Basic/Counter","description":"A simple standard counter example"},{"id":"todos","name":"Basic/Simple Todos","description":"Todos with LocalStorage persistence"},{"id":"forms","name":"Basic/Form Validation","description":"HTML 5 validators with custom async validation"},{"id":"cssanimations","name":"Basic/CSS Animations","description":"Using Solid Transition Group"},{"id":"context","name":"Basic/Context","description":"A simple color picker using Context"},{"id":"clock","name":"Complex/Clock","description":"Demonstrates Solid reactivity with a real-time clock example"},{"id":"ethasketch","name":"Complex/Etch A Sketch","description":"Uses Index and createMemo to create a grid graphic"},{"id":"scoreboard","name":"Complex/Scoreboard","description":"Make use of hooks to do simple transitions"},{"id":"asyncresource","name":"Complex/Async Resource","description":"Ajax requests to SWAPI with Promise cancellation"},{"id":"suspensetabs","name":"Complex/Suspense Transitions","description":"Deferred loading spinners for smooth UX"},{"id":"simpletodos","name":"Complex/Simple Todos Template Literals","description":"Simple Todos using Lit DOM Expressions"},{"id":"simpletodoshyperscript","name":"Complex/Simple Todos Hyperscript","description":"Simple Todos using Hyper DOM Expressions"}] \ No newline at end of file diff --git a/langs/en/examples/_index.json b/langs/en/examples/_index.json deleted file mode 100644 index f029fb89..00000000 --- a/langs/en/examples/_index.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "examples": ["counter","todos","forms","cssanimations","context","clock","ethasketch","scoreboard","asyncresource","suspensetabs","simpletodos","simpletodoshyperscript"] -} From 30e1092a65592fca36901371778364f90e62888f Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Thu, 15 Sep 2022 10:25:44 +0200 Subject: [PATCH 18/26] Generate JSON files from "/examples-src/" to "/examples/" --- src/json-files/rollup-plugin.js | 98 +++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/src/json-files/rollup-plugin.js b/src/json-files/rollup-plugin.js index 170b6b00..67ce506a 100644 --- a/src/json-files/rollup-plugin.js +++ b/src/json-files/rollup-plugin.js @@ -6,47 +6,87 @@ import glob from 'glob'; import { dirname, parse, basename } from 'path'; import { readFileSync, writeFileSync } from 'fs'; +const pluginName = 'generate-json-folders'; + +const directoryPattern = '**/examples-src/$descriptor.json'; +const examplesPattern = '**/examples-src/*/$descriptor.json'; + +function getGlobPaths(...patterns) { + return glob.sync( + patterns.length > 1 ? `{${patterns.join(',')}}` : patterns[0], + {absolute: true} + ); +} + +function getDescriptor(descriptorPath) { + let descriptor = null; + try { + descriptor = JSON.parse(readFileSync(descriptorPath, 'utf8')); + } catch (e) { + console.warn(`[${pluginName}] skipping ${descriptorPath} because ${e}`); + return descriptor; + } + return descriptor; +} + +function getGeneratedPath(path) { + return path.replace('/examples-src/', '/examples/'); +} + +function getFiles(folderPath, filenames) { + return (filenames || []).map((filename) => { + const filepath = `${folderPath}/${filename}`; + const {name, ext} = parse(filename); + return { + name, + type: ext.slice(1), + content: readFileSync(filepath, 'utf8') + }; + }); +} + +function createExamplesDirectory(cachedExamples) { + const descriptorPaths = glob.sync(directoryPattern, {absolute: true}); + descriptorPaths.forEach((descriptorPath) => { + const descriptor = getDescriptor(descriptorPath); + if (!descriptor) return; // skip + const directory = descriptor.map((id) => { + const exampleSrcPath = `${dirname(descriptorPath)}/${id}`; + const example = cachedExamples[exampleSrcPath]; + return example; + }); + const directoryPath = getGeneratedPath(descriptorPath); + writeFileSync(directoryPath, JSON.stringify(directory)); + }); +} + export default function pack(pluginOptions) { - const pluginName = 'json-files'; if (pluginOptions) { console.warn(`[${pluginName}] no plugin options are supported`); } return { name: pluginName, buildStart() { - const matchers = '{**/examples/*/_index.json,**/tutorials/*/*/_index.json}'; - const allPaths = glob.sync(matchers, {absolute: true}); - allPaths.forEach((descriptorPath) => { - let descriptor = {}; - try { - descriptor = JSON.parse(readFileSync(descriptorPath, 'utf8')); - } catch (e) { - console.warn(`[${pluginName}] skipping ${descriptorPath} because ${e}`); - return; - } - if (!Array.isArray(descriptor.files) || descriptor.files.length === 0) { - console.warn(`[${pluginName}] skipping ${descriptorPath} because the files property is not a list or it's an empty list`); - return; - } + const descriptorPaths = getGlobPaths(examplesPattern); + const cachedExamples = {}; + descriptorPaths.forEach((descriptorPath) => { + const descriptor = getDescriptor(descriptorPath); + if (!descriptor) return; // skip - const examplePath = dirname(descriptorPath); - const outputFilename = `${examplePath}.json`; - const files = descriptor.files.map((filename) => { - const inputFilename = `${examplePath}/${filename}`; - const parsedFilename = parse(filename); - return { - name: parsedFilename.name, - type: parsedFilename.ext.slice(1), - content: readFileSync(inputFilename, 'utf8') - }; - }); - writeFileSync(outputFilename, JSON.stringify({ - id: basename(examplePath), + const exampleSrcPath = dirname(descriptorPath); + const files = getFiles(exampleSrcPath, descriptor.files); + const example = { + id: basename(exampleSrcPath), name: descriptor.name, description: descriptor.description, files - })); + }; + const examplePath = getGeneratedPath(exampleSrcPath); + writeFileSync(`${examplePath}.json`, JSON.stringify(example)); + delete example.files; + cachedExamples[exampleSrcPath] = example; }); + createExamplesDirectory(cachedExamples); } }; } From 999c72560062e822cedcb2d473305029d80d3d3d Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Thu, 15 Sep 2022 10:49:24 +0200 Subject: [PATCH 19/26] Rename Rollup plugin to "generate-json-folders" --- rollup.config.mjs | 4 ++-- .../generate-json-folders.js} | 0 src/{json-files => rollup-plugins}/unpack.js | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{json-files/rollup-plugin.js => rollup-plugins/generate-json-folders.js} (100%) rename src/{json-files => rollup-plugins}/unpack.js (100%) diff --git a/rollup.config.mjs b/rollup.config.mjs index c324b2be..fe031199 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -7,7 +7,7 @@ import rehypeHighlight from "rehype-highlight"; import rehypeSlug from "rehype-slug"; import rehypeAutolinkHeadings from "rehype-autolink-headings"; import { remarkMdxToc } from "remark-mdx-toc"; -import jsonFiles from "./src/json-files/rollup-plugin.js"; +import jsonFolders from "./src/rollup-plugins/generate-json-folders.js"; export default { input: "src/index.ts", @@ -31,7 +31,7 @@ export default { ], }), typescript(), - jsonFiles(), + jsonFolders(), json(), dynamicImportVars.default(), ], diff --git a/src/json-files/rollup-plugin.js b/src/rollup-plugins/generate-json-folders.js similarity index 100% rename from src/json-files/rollup-plugin.js rename to src/rollup-plugins/generate-json-folders.js diff --git a/src/json-files/unpack.js b/src/rollup-plugins/unpack.js similarity index 100% rename from src/json-files/unpack.js rename to src/rollup-plugins/unpack.js From 0b06ddcfe5ceb7a2c2a4ac3d0cccebcb0053d34e Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Thu, 15 Sep 2022 10:51:33 +0200 Subject: [PATCH 20/26] Move "rollup-plugins/unpack.js" to "scripts/unpack.s --- {src/rollup-plugins => scripts}/unpack.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {src/rollup-plugins => scripts}/unpack.js (100%) diff --git a/src/rollup-plugins/unpack.js b/scripts/unpack.js similarity index 100% rename from src/rollup-plugins/unpack.js rename to scripts/unpack.js From daa49c3550200e28d5fa39cab2dd2d99c28a82f5 Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Thu, 15 Sep 2022 11:00:10 +0200 Subject: [PATCH 21/26] Add a README for creating and publishing examples --- langs/en/examples-src/README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 langs/en/examples-src/README.md diff --git a/langs/en/examples-src/README.md b/langs/en/examples-src/README.md new file mode 100644 index 00000000..cd304b54 --- /dev/null +++ b/langs/en/examples-src/README.md @@ -0,0 +1,26 @@ +# README of /examples-src/ + +This folder contains all the existing examples. + + +## Creating an example + +1. add a new folder like `/examples-src/new-example` (`new-example` is later used as the example ID) +2. fill the `/examples-src/new-example` folder with all the files of the new example +3. add an `/examples-src/new-example/$descriptor.json` file with meaningful data (see existing examples) + +NOTE: only the files included in the `$descriptor.files` list will be published + + +## Publishing an example + +1. add the example ID to the `/examples-src/$descriptor.json` file + +NOTE: only the examples included in the `$descriptor` list will be published + + +## Rollup + +The `generate-json-folders` (rollup plugin) creates a bunch of JSON files based on the structure of the `examples-src` tree and its `$descriptor` files. + +Just run `$ yarn build` to see the result in `/examples/`. From 9e445d1f89c44bd6b9515556b779d8f0f73aad8c Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Thu, 15 Sep 2022 11:17:05 +0200 Subject: [PATCH 22/26] Move the "rollup-plugins" folder out of the "src" folder --- {src/rollup-plugins => rollup-plugins}/generate-json-folders.js | 0 rollup.config.mjs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {src/rollup-plugins => rollup-plugins}/generate-json-folders.js (100%) diff --git a/src/rollup-plugins/generate-json-folders.js b/rollup-plugins/generate-json-folders.js similarity index 100% rename from src/rollup-plugins/generate-json-folders.js rename to rollup-plugins/generate-json-folders.js diff --git a/rollup.config.mjs b/rollup.config.mjs index fe031199..00d1d5dd 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -7,7 +7,7 @@ import rehypeHighlight from "rehype-highlight"; import rehypeSlug from "rehype-slug"; import rehypeAutolinkHeadings from "rehype-autolink-headings"; import { remarkMdxToc } from "remark-mdx-toc"; -import jsonFolders from "./src/rollup-plugins/generate-json-folders.js"; +import jsonFolders from "./rollup-plugins/generate-json-folders.js"; export default { input: "src/index.ts", From 9a2f949ae3ce70a959ae8f2d22dbed9bfab93102 Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Thu, 15 Sep 2022 11:51:31 +0200 Subject: [PATCH 23/26] Rename "JsonFile" to "SourceFile"; directly serve generated "/examples/$descriptor" --- src/index.ts | 24 ++++-------------------- src/types.ts | 4 ++-- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1795b876..caae5a4a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ -import { DocFile, LessonFile, LessonLookup, ResourceMetadata, JsonFile, Example } from "./types"; +import { DocFile, LessonFile, LessonLookup, ResourceMetadata, SourceFile, Example } from "./types"; -export { DocFile, LessonFile, LessonLookup, ResourceMetadata, JsonFile, Example }; +export { DocFile, LessonFile, LessonLookup, ResourceMetadata, SourceFile, Example }; function noThrow(x: Promise): Promise { return x.catch(() => undefined); @@ -62,22 +62,6 @@ export async function getExample( export async function getExamplesDirectory( lang: string ): Promise { - const {examples: ids} = await noThrow(import(`../langs/${lang}/examples/_index.json`)); - if (!ids) return undefined; - const result = []; - for (const id of ids) { - const example = await getExample(lang, id); - if (!example) { - console.warn(`Example ${id} not found`); - continue; - } - // REM clone before mutating, otherwise import() cached value will be mutated - const clonedExample = {...example}; - delete clonedExample.files; - // FIXME building the directory on demand is very slow because every user - // has to download all examples data (and their files). Instead, the - // directory should be precomputed by the rollup plugin - result.push(clonedExample); - } - return result; + const directory = await noThrow(import(`../langs/${lang}/examples/$descriptor.json`)); + return directory?.default; } diff --git a/src/types.ts b/src/types.ts index 7e20dcad..f21fc568 100644 --- a/src/types.ts +++ b/src/types.ts @@ -26,7 +26,7 @@ export interface LessonFile { markdown?: string; } -export interface JsonFile { +export interface SourceFile { name: string; type: string; content: string; @@ -36,5 +36,5 @@ export interface Example { id: string; name: string; description: string; - files?: JsonFile[]; + files?: SourceFile[]; } From 7d3a5d42f61d41653249356560441f42905496fa Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Mon, 19 Sep 2022 10:17:08 +0200 Subject: [PATCH 24/26] Remove comments about intended use --- rollup-plugins/generate-json-folders.js | 4 ---- scripts/unpack.js | 4 ---- 2 files changed, 8 deletions(-) diff --git a/rollup-plugins/generate-json-folders.js b/rollup-plugins/generate-json-folders.js index 67ce506a..a30735ac 100644 --- a/rollup-plugins/generate-json-folders.js +++ b/rollup-plugins/generate-json-folders.js @@ -1,7 +1,3 @@ -// This script is meant to be run on each build, to join a group of files into a -// single JSON file of type JsFiles. Its purpose is to join individual files, so -// that the solid-site can keep using them. - import glob from 'glob'; import { dirname, parse, basename } from 'path'; import { readFileSync, writeFileSync } from 'fs'; diff --git a/scripts/unpack.js b/scripts/unpack.js index 84b6ed08..00f8d829 100644 --- a/scripts/unpack.js +++ b/scripts/unpack.js @@ -1,7 +1,3 @@ -// This script is meant to be run once per each JSON file of type JsFiles, like -// those in a tutorial or an example. Its purpose is to extract individual -// files, so that they can be more easily diffed in git. - // input // 1: source folder (eg: "/../solid-site/public/examples") // 2: dest folder (eg: "/langs/en/examples") From f743b22c9d37e08423d0705e3c0cb10a5c2453b9 Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Mon, 19 Sep 2022 10:28:34 +0200 Subject: [PATCH 25/26] Improve comments to use the script to refresh examples --- scripts/unpack.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/unpack.js b/scripts/unpack.js index 00f8d829..e9c6e040 100644 --- a/scripts/unpack.js +++ b/scripts/unpack.js @@ -1,6 +1,9 @@ +// Use this script to refresh examples from solid-site in case they were changed +// before merging the "split-js-files" branch in both solid-docs and solid-site. +// // input // 1: source folder (eg: "/../solid-site/public/examples") -// 2: dest folder (eg: "/langs/en/examples") +// 2: dest folder (eg: "/langs/en/examples-src") // output // - as many subfolders of the dest folder as files in the glob, each named after the basename of the file // - as many files in each subfolder as there are packed into each file in the glob @@ -50,5 +53,6 @@ sourceFiles.forEach((sourceFilename) => { writeFileSync(destPath, content); console.log(`- extracted ${destPath}`); }); - writeFileSync(`${destPathParent}/.json-files`, JSON.stringify(filesOrder)); + // Commented out the following write because not needed to only update examples + // writeFileSync(`${destPathParent}/.json-files`, JSON.stringify(filesOrder)); }); From 7fd6f432438c43d6ad0c3388b9540bc69c56b95f Mon Sep 17 00:00:00 2001 From: Andrea Ercolino Date: Mon, 19 Sep 2022 16:38:01 +0200 Subject: [PATCH 26/26] Rename "unpack.js" to "import-examples.js" --- scripts/{unpack.js => import-examples.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/{unpack.js => import-examples.js} (100%) diff --git a/scripts/unpack.js b/scripts/import-examples.js similarity index 100% rename from scripts/unpack.js rename to scripts/import-examples.js