diff --git a/.npmignore b/.npmignore index 8fd84a6..c0544ab 100644 --- a/.npmignore +++ b/.npmignore @@ -8,5 +8,8 @@ reference .gitignore webpack.config.js -# Ignore doc files -docs \ No newline at end of file +# Ignore doc and example file +docs +example +example_old + diff --git a/example/App.js b/example/App.js index f94fedd..ebe71ab 100644 --- a/example/App.js +++ b/example/App.js @@ -1,6 +1,9 @@ import Brahmos, { render, Component, useState } from '../src'; import TodoList from './todo-list/TodoList'; -import SierpinskiTriangleDemo from './sierpinski-triangle/SierpinskiTriangle'; +import SierpinskiTriangleDemo from './sierpinski-triangle'; +import ConcurrentModeDemo from './concurrent-mode'; +import SuspenseListDemo from './suspense-list'; +import SVGDemo from './svg-chart'; import './App.scss'; @@ -15,6 +18,21 @@ const examples = [ id: 'sierpinski-triangle', Component: SierpinskiTriangleDemo, }, + { + title: 'Concurrent Mode Demo', + id: 'concurrent-mode', + Component: ConcurrentModeDemo, + }, + { + title: 'Suspense List Demo', + id: 'suspense-list', + Component: SuspenseListDemo, + }, + { + title: 'SVG Chart', + id: 'svg-chart', + Component: SVGDemo, + }, ]; export default function App() { diff --git a/example/concurrent-mode/fakeApi.js b/example/concurrent-mode/fakeApi.js new file mode 100644 index 0000000..291f4d5 --- /dev/null +++ b/example/concurrent-mode/fakeApi.js @@ -0,0 +1,149 @@ +export function fetchProfileData(userId) { + const userPromise = fetchUser(userId); + const postsPromise = fetchPosts(userId); + return { + userId, + user: wrapPromise(userPromise), + posts: wrapPromise(postsPromise), + }; +} + +// Suspense integrations like Relay implement +// a contract like this to integrate with React. +// Real implementations can be significantly more complex. +// Don't copy-paste this into your project! +function wrapPromise(promise) { + let status = 'pending'; + let result; + const suspender = promise.then( + (r) => { + status = 'success'; + result = r; + }, + (e) => { + status = 'error'; + result = e; + }, + ); + return { + read() { + if (status === 'pending') { + throw suspender; + } else if (status === 'error') { + throw result; + } else if (status === 'success') { + return result; + } + }, + }; +} + +export function fetchUser(userId) { + console.log('fetch user ' + userId + '...'); + return new Promise((resolve) => { + setTimeout(() => { + console.log('fetched user ' + userId); + switch (userId) { + case 0: + resolve({ + name: 'Ringo Starr', + }); + break; + case 1: + resolve({ + name: 'George Harrison', + }); + break; + case 2: + resolve({ + name: 'John Lennon', + }); + break; + case 3: + resolve({ + name: 'Paul McCartney', + }); + break; + default: + throw Error('Unknown user.'); + } + }, 2000 * Math.random()); + }); +} + +export function fetchPosts(userId) { + console.log('fetch posts for ' + userId + '...'); + return new Promise((resolve) => { + setTimeout(() => { + console.log('fetched posts for ' + userId); + switch (userId) { + case 0: + resolve([ + { + id: 0, + text: 'I get by with a little help from my friends', + }, + { + id: 1, + text: "I'd like to be under the sea in an octupus's garden", + }, + { + id: 2, + text: 'You got that sand all over your feet', + }, + ]); + break; + case 1: + resolve([ + { + id: 0, + text: 'Turn off your mind, relax, and float downstream', + }, + { + id: 1, + text: 'All things must pass', + }, + { + id: 2, + text: "I look at the world and I notice it's turning", + }, + ]); + break; + case 2: + resolve([ + { + id: 0, + text: 'Living is easy with eyes closed', + }, + { + id: 1, + text: "Nothing's gonna change my world", + }, + { + id: 2, + text: 'I am the walrus', + }, + ]); + break; + case 3: + resolve([ + { + id: 0, + text: 'Woke up, fell out of bed', + }, + { + id: 1, + text: 'Here, there, and everywhere', + }, + { + id: 2, + text: 'Two of us sending postcards, writing letters', + }, + ]); + break; + default: + throw Error('Unknown user.'); + } + }, 2000 * Math.random()); + }); +} diff --git a/example/concurrent-mode/index.js b/example/concurrent-mode/index.js new file mode 100644 index 0000000..4045ad9 --- /dev/null +++ b/example/concurrent-mode/index.js @@ -0,0 +1,76 @@ +/** + * Forked from: https://codesandbox.io/s/jovial-lalande-26yep?file=/src/index.js:0-1646 + */ +import Brahmos, { useState, useTransition, Suspense } from '../../src'; + +import { fetchProfileData } from './fakeApi'; + +function getNextId(id) { + return id === 3 ? 0 : id + 1; +} + +const initialResource = fetchProfileData(0); + +function App() { + const [resource, setResource] = useState(initialResource); + const [startTransition, isPending] = useTransition({ + timeoutMs: 3000, + }); + return ( + <> + + {isPending ? ' Loading...' : null} + +

+ This demo is forked from concurrent mode demo of React: +
+ Source: + + https://codesandbox.io/s/jovial-lalande-26yep?file=/src/index.js:0-1646 + +

+ + ); +} + +function ProfilePage({ resource }) { + return ( + Loading profile...}> + + Loading posts...}> + + + + ); +} + +function ProfileDetails({ resource }) { + const user = resource.user.read(); + return

{user.name}

; +} + +function ProfileTimeline({ resource }) { + const posts = resource.posts.read(); + return ( + + ); +} + +export default App; diff --git a/example/sierpinski-triangle/SierpinskiTriangle.js b/example/sierpinski-triangle/index.js similarity index 92% rename from example/sierpinski-triangle/SierpinskiTriangle.js rename to example/sierpinski-triangle/index.js index f98c139..3d66267 100644 --- a/example/sierpinski-triangle/SierpinskiTriangle.js +++ b/example/sierpinski-triangle/index.js @@ -1,6 +1,4 @@ -import Brahmos, { Component, unstable_deferredUpdates } from '../../src'; -import { withUpdateSource } from '../../src/updateMetaUtils'; -import { UPDATE_SOURCE_EVENT } from '../../src/configs'; +import Brahmos, { Component, unstable_deferredUpdates, unstable_syncUpdates } from '../../src'; /** * Source: https://github.com/facebook/react/blob/master/fixtures/fiber-triangle/index.html @@ -155,7 +153,7 @@ class SierpinskiWrapper extends Component { }); } else { // Update is not time-sliced. Causes demo to stutter. - this.setState((state) => ({ seconds: (state.seconds % 10) + 1 })); + this.setState({ seconds: (this.state.seconds % 10) + 1 }); } } @@ -187,7 +185,7 @@ class SierpinskiWrapper extends Component {
- + {seconds}
@@ -212,7 +210,7 @@ export default class DemoApp extends Component { updateElapsed() { requestAnimationFrame(() => { - withUpdateSource(UPDATE_SOURCE_EVENT, () => { + unstable_syncUpdates(() => { this.setState({ elapsed: Date.now() - this.start, }); diff --git a/example/suspense-list/fakeApi.js b/example/suspense-list/fakeApi.js new file mode 100644 index 0000000..be9288e --- /dev/null +++ b/example/suspense-list/fakeApi.js @@ -0,0 +1,99 @@ +export function fetchProfileData() { + const userPromise = fetchUser(); + const postsPromise = fetchPosts(); + const triviaPromise = fetchTrivia(); + return { + user: wrapPromise(userPromise), + posts: wrapPromise(postsPromise), + trivia: wrapPromise(triviaPromise), + }; +} + +// Suspense integrations like Relay implement +// a contract like this to integrate with React. +// Real implementations can be significantly more complex. +// Don't copy-paste this into your project! +function wrapPromise(promise) { + let status = 'pending'; + let result; + const suspender = promise.then( + (r) => { + status = 'success'; + result = r; + }, + (e) => { + status = 'error'; + result = e; + }, + ); + return { + read() { + if (status === 'pending') { + throw suspender; + } else if (status === 'error') { + throw result; + } else if (status === 'success') { + return result; + } + }, + }; +} + +function fetchUser() { + console.log('fetch user...'); + return new Promise((resolve) => { + setTimeout(() => { + console.log('fetched user'); + resolve({ + name: 'Ringo Starr', + }); + }, 500); + }); +} + +const ringoPosts = [ + { + id: 0, + text: 'I get by with a little help from my friends', + }, + { + id: 1, + text: "I'd like to be under the sea in an octupus's garden", + }, + { + id: 2, + text: 'You got that sand all over your feet', + }, +]; + +function fetchPosts() { + const ringoPostsAtTheTime = ringoPosts; + console.log('fetch posts...'); + return new Promise((resolve) => { + setTimeout(() => { + console.log('fetched posts'); + resolve(ringoPostsAtTheTime); + }, 3000 * Math.random()); + }); +} + +function fetchTrivia() { + return new Promise((resolve) => { + setTimeout(() => { + resolve([ + { + id: 1, + text: 'The nickname "Ringo" came from his habit of wearing numerous rings.', + }, + { + id: 2, + text: 'Plays the drums left-handed with a right-handed drum set.', + }, + { + id: 3, + text: 'Nominated for one Daytime Emmy Award, but did not win', + }, + ]); + }, 3000 * Math.random()); + }); +} diff --git a/example/suspense-list/index.js b/example/suspense-list/index.js new file mode 100644 index 0000000..caa069d --- /dev/null +++ b/example/suspense-list/index.js @@ -0,0 +1,72 @@ +/** + * Forked from: https://codesandbox.io/s/black-wind-byilt + */ + +import Brahmos, { SuspenseList, Suspense, useEffect } from '../../src'; + +import { fetchProfileData } from './fakeApi'; + +function App() { + const initialResource = fetchProfileData(0); + return ( + <> + Loading...}> + + +

+ This demo is forked from Suspense List demo of React: +
+ Source: + + https://codesandbox.io/s/black-wind-byilt + +

+ + ); +} + +function ProfilePage({ resource }) { + return ( + + + Loading posts...}> + + + Loading fun facts...}> + + + + ); +} + +function ProfileDetails({ resource }) { + const user = resource.user.read(); + return

{user.name}

; +} + +function ProfileTimeline({ resource }) { + const posts = resource.posts.read(); + return ( + + ); +} + +function ProfileTrivia({ resource }) { + const trivia = resource.trivia.read(); + return ( + <> +

Fun Facts

+ + + ); +} + +export default App; diff --git a/example/svg-chart/index.js b/example/svg-chart/index.js new file mode 100644 index 0000000..b684ff0 --- /dev/null +++ b/example/svg-chart/index.js @@ -0,0 +1,55 @@ +import Brahmos, { Component } from '../../src'; + +function Rect({ height, index }) { + return ( + + ); +} + +function Chart({ data }) { + return ( + + {data.map((height, index) => ( + + ))} + + ); +} + +class SVGExample extends Component { + state = { + data: [99, 44, 11, 55, 33, 115, 4], + }; + + _shuffuleArray(array) { + var j, temp, i; + for (i = array.length; i; i--) { + j = Math.floor(Math.random() * i); + temp = array[i - 1]; + array[i - 1] = array[j]; + array[j] = temp; + } + return array; + } + + shuffule = () => this.setState({ data: this._shuffuleArray(this.state.data) }); + + render() { + const { data } = this.state; + return ( +
+ + +
+ ); + } +} + +export default SVGExample;