mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Initial commit
This commit is contained in:
commit
5e0dfdac54
43 files changed
+12581
No files matched your search
@@ -0,0 +1,14 @@
|
||||
// @flow
|
||||
|
||||
import type { Bridge } from '../types';
|
||||
|
||||
type Shell = {|
|
||||
connect: (callback: Function) => void,
|
||||
onReload: (reloadFn: Function) => void,
|
||||
|};
|
||||
|
||||
export function initDevTools(shell: Shell) {
|
||||
shell.connect((bridge: Bridge) => {
|
||||
// TODO ...
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// @flow
|
||||
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import type { Element } from './types';
|
||||
import type { Bridge } from '../types';
|
||||
|
||||
/**
|
||||
* The store is the single source of truth for updates from the backend.
|
||||
* ContextProviders can subscribe to the Store for specific things they want to provide.
|
||||
*/
|
||||
class Store extends EventEmitter {
|
||||
_idToElement: Map<string, Element> = new Map();
|
||||
_idToParentID: Map<string, string> = new Map();
|
||||
|
||||
roots: Set<string> = new Set();
|
||||
|
||||
constructor(bridge: Bridge) {
|
||||
super();
|
||||
|
||||
bridge.on('root', this.onBridgeRoot);
|
||||
bridge.on('mount', this.onBridgeMount);
|
||||
bridge.on('update', this.onBridgeUpdated);
|
||||
bridge.on('unmount', this.onBridgeUnmounted);
|
||||
}
|
||||
|
||||
getElement(id: string) {
|
||||
return this._idToElement.get(id);
|
||||
}
|
||||
|
||||
geParent(id: string) {
|
||||
return this._idToParentID.get(id);
|
||||
}
|
||||
|
||||
onBridgeMount = (element: Element) => {
|
||||
const {id} = element;
|
||||
console.log('%cStore%c onBridgeMount()', 'color: red; font-weight: bold;', 'font-weight: bold;', element);
|
||||
this._idToElement.set(id, element);
|
||||
|
||||
element.children.forEach(childID => {
|
||||
this._idToParentID.set(childID, id);
|
||||
});
|
||||
|
||||
this.emit(id);
|
||||
};
|
||||
|
||||
onBridgeRoot = (id: string) => {
|
||||
console.log('%cStore%c onBridgeRoot()', 'color: red; font-weight: bold;', 'font-weight: bold;', id);
|
||||
if (!this.roots.has(id)) {
|
||||
this.roots.add(id);
|
||||
this.emit('roots');
|
||||
}
|
||||
};
|
||||
|
||||
onBridgeUnmounted = (id: string) => {
|
||||
console.log('%cStore%c onBridgeUnmounted()', 'color: red; font-weight: bold;', 'font-weight: bold;', id);
|
||||
this._idToElement.delete(id);
|
||||
|
||||
if (this._idToParentID.has(id)) {
|
||||
this._idToParentID.delete(id);
|
||||
}
|
||||
|
||||
if (this.roots.has(id)) {
|
||||
this.roots.delete(id);
|
||||
this.emit('roots');
|
||||
}
|
||||
};
|
||||
|
||||
onBridgeUpdated = (element: Element) => {
|
||||
const {id} = element;
|
||||
console.log('%cStore%c onBridgeUpdated()', 'color: red; font-weight: bold;', 'font-weight: bold;', element);
|
||||
this._idToElement.set(id, element);
|
||||
|
||||
this.emit(id);
|
||||
};
|
||||
}
|
||||
|
||||
export default Store;
|
||||
@@ -0,0 +1,38 @@
|
||||
// @flow
|
||||
|
||||
export const ElementTypeClassOrFunction = 1;
|
||||
export const ElementTypeContext = 2;
|
||||
export const ElementTypeForwardRef = 3;
|
||||
export const ElementTypeMemo = 4;
|
||||
export const ElementTypeOtherOrUnknown = 5;
|
||||
export const ElementTypeProfiler = 6;
|
||||
export const ElementTypeSuspense = 7;
|
||||
|
||||
export type ElementType =
|
||||
| 1
|
||||
| 2
|
||||
| 3
|
||||
| 4
|
||||
| 5
|
||||
| 6
|
||||
| 7;
|
||||
|
||||
// TODO: Add profiling node
|
||||
|
||||
export type Element = {|
|
||||
id: string,
|
||||
type: ElementType,
|
||||
key: React$Key | null,
|
||||
displayName: string | null,
|
||||
children: Array<string>,
|
||||
|};
|
||||
|
||||
export type InspectedElement = {|
|
||||
id: string,
|
||||
context: Object | null,
|
||||
hooks: Object | null,
|
||||
props: Object | null,
|
||||
state: Object | null,
|
||||
canEditProps: boolean,
|
||||
source: Object,
|
||||
|};
|
||||
@@ -0,0 +1,49 @@
|
||||
:root {
|
||||
/* TODO */
|
||||
--color-base00: #ffffff;
|
||||
--color-base01: #f3f3f3;
|
||||
--color-base02: #eeeeee;
|
||||
--color-base03: #dadada;
|
||||
--color-base04: #333333;
|
||||
--color-base05: #5a5a5a;
|
||||
--color-special00: #8155cb;
|
||||
--color-special01: #222222;
|
||||
--color-special02: #1a1aa6;
|
||||
--color-special03: #c80000;
|
||||
--color-special04: #236e25;
|
||||
--color-special05: #aa0d91;
|
||||
--color-special06: #ef6632;
|
||||
--color-special07: #777d88;
|
||||
--color-state00: #0088FA;
|
||||
--color-state01: #dadada;
|
||||
--color-state02: #ffffff;
|
||||
--color-state03: #ebf1fb;
|
||||
--color-state04: #FFFF00;
|
||||
--color-state05: #222222;
|
||||
--color-state06: #222222;
|
||||
|
||||
--color-component: var(--color-special00);
|
||||
--color-tree-tag: var(--color-base04);
|
||||
--color-tree-attr-name: var(--color-special06);
|
||||
--color-tree-attr-value: var(--color-special02);
|
||||
|
||||
--line-height-tree-compact: 1.5;
|
||||
--line-height-tree-normal: 1.5;
|
||||
|
||||
/* GitHub.com system fonts */
|
||||
--font-family-monospace: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
--font-size-tree-compact: 12px;
|
||||
--font-size-tree-normal: 14px;
|
||||
}
|
||||
|
||||
.App {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// @flow
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import Store from '../store';
|
||||
import Tree from './Tree';
|
||||
import {StoreContext} from './contexts';
|
||||
import styles from './App.css';
|
||||
|
||||
import type {Bridge} from '../../types';
|
||||
|
||||
type Props = {|
|
||||
bridge: Bridge,
|
||||
|};
|
||||
|
||||
export default function App({ bridge }: Props) {
|
||||
const store = useMemo(() => new Store(bridge), []);
|
||||
|
||||
return (
|
||||
<StoreContext.Provider value={store}>
|
||||
<div className={styles.App}>
|
||||
<Tree />
|
||||
</div>
|
||||
</StoreContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
.Element {
|
||||
width: 100%;
|
||||
border-radius: 0.25em;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
.Element:hover {
|
||||
background-color: var(--color-state03);
|
||||
}
|
||||
|
||||
.ArrowClosed,
|
||||
.ArrowOpen {
|
||||
position: absolute;
|
||||
margin-left: -1rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='%23777d88' d='M8 5v14l11-7z'/%3E%3Cpath fill='none' d='M0 24V0h24v24H0z'/%3E%3C/svg%3E");
|
||||
background-size: 12px 12px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
.ArrowOpen {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.Component {
|
||||
color: var(--color-component);
|
||||
}
|
||||
.Component:before {
|
||||
white-space: nowrap;
|
||||
content: "<";
|
||||
color: var(--color-tree-tag);
|
||||
}
|
||||
.Component:after {
|
||||
white-space: nowrap;
|
||||
content: ">";
|
||||
color: var(--color-tree-tag);
|
||||
}
|
||||
|
||||
.AttributeName {
|
||||
color: var(--color-tree-attr-name);
|
||||
}
|
||||
|
||||
.AttributeValue {
|
||||
color: var(--color-tree-attr-value);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// @flow
|
||||
|
||||
import React, {Fragment, useContext} from 'react';
|
||||
import {useElement} from './hooks';
|
||||
import {StoreContext} from './contexts';
|
||||
|
||||
import styles from './Element.css';
|
||||
|
||||
type Props = {|
|
||||
depth: number,
|
||||
id: string,
|
||||
|};
|
||||
|
||||
export default function Element({depth, id}: Props) {
|
||||
const store = useContext(StoreContext);
|
||||
const element = useElement(store, id);
|
||||
|
||||
const {children, displayName, key} = element;
|
||||
|
||||
// TODO: Toggle open/close state
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={styles.Element} style={{paddingLeft: `${1 + depth}rem`}}>
|
||||
{children.length > 0 && (
|
||||
<span className={styles.ArrowOpen}></span>
|
||||
)}
|
||||
|
||||
<span className={styles.Component}>
|
||||
{displayName}{key && (
|
||||
<Fragment>
|
||||
<span className={styles.AttributeName}>key</span>=<span className={styles.AttributeValue}>"{key}"</span>
|
||||
</Fragment>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{children.map(childID => (
|
||||
<Element key={childID} depth={depth + 1} id={childID} />
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
.Tree {
|
||||
width: 100%;
|
||||
padding: 0.25rem;
|
||||
overflow: auto;
|
||||
|
||||
font-family: var(--font-family-monospace);
|
||||
font-size: var(--font-size-tree-compact);
|
||||
|
||||
/* TODO */
|
||||
line-height: var(--line-height-tree-compact);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// @flow
|
||||
|
||||
import React, {useContext} from 'react';
|
||||
import Element from './Element';
|
||||
import {StoreContext} from './contexts';
|
||||
import {useElement, useRoots} from './hooks';
|
||||
|
||||
import styles from './Tree.css';
|
||||
|
||||
type TreeProps = {||};
|
||||
|
||||
export default function Tree({}: TreeProps) {
|
||||
const store = useContext(StoreContext);
|
||||
const roots = useRoots(store);
|
||||
|
||||
return (
|
||||
<div className={styles.Tree}>
|
||||
{roots.map(id => <Root key={id} id={id} />)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type RootProps = {|
|
||||
id: string,
|
||||
|};
|
||||
|
||||
function Root({id}: RootProps) {
|
||||
const store = useContext(StoreContext);
|
||||
const element = useElement(store, id);
|
||||
|
||||
return element.children.map(childID => (
|
||||
<Element key={childID} depth={0} id={childID} />
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// @flow
|
||||
|
||||
import { createContext } from 'react';
|
||||
|
||||
import Store from '../store';
|
||||
|
||||
export const RootsContext = createContext<Array<string>>([]);
|
||||
export const StoreContext = createContext<Store>(((null: any): Store));
|
||||
@@ -0,0 +1,30 @@
|
||||
// @flow
|
||||
|
||||
import {createContext, useLayoutEffect, useRef, useState} from 'react';
|
||||
|
||||
import type {Element} from '../types';
|
||||
import Store from '../store';
|
||||
|
||||
export function useElement(store: Store, id: string): Element {
|
||||
const [element, setElement] = useState<Element>(((store.getElement(id): any): Element));
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const handler = () => setElement(((store.getElement(id): any): Element));
|
||||
store.addListener(id, handler);
|
||||
return () => store.removeListener(id, handler);
|
||||
}, [store, id]);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
export function useRoots(store: Store): Array<string> {
|
||||
const [roots, setRoots] = useState<Array<string>>(Array.from(store.roots));
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const handler = () => setRoots(Array.from(store.roots));
|
||||
store.addListener('roots', handler);
|
||||
return () => store.removeListener('roots', handler);
|
||||
}, [store]);
|
||||
|
||||
return roots;
|
||||
}
|
||||
Reference in new issue
Block a user