Temporarily remove the odyssey plugin (#1178)

In #1132 and #1134, I started work on the Odyssey plugin. However,
before getting it to a state where it's usefully included in SourceCred,
I decided to pivot to focus on timeline cred first.

Now I'm merging significant refactors as a part of timeline cred
(#1136). As a side effect of this refactor, the Odyssey plugin should
undergo significant changes (OdysseyInstance is now basically redundant
with base Graph.) Rather than incrementally update unused code, I elect
to remove the plugin. This code should be revived on a side branch, and
then merged into master once we have a fully functioning prototype.

Test plan: `yarn test` passes.
This commit is contained in:
Dandelion Mané 2019-06-13 17:07:05 +03:00 committed by GitHub
parent 3c8fd0e701
commit e916bc91c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 0 additions and 907 deletions

View File

@ -1,12 +0,0 @@
// @flow
import React from "react";
import OdysseyApp from "../plugins/odyssey/ui/App";
import type {Assets} from "../webutil/assets";
export default class HomePage extends React.Component<{|+assets: Assets|}> {
render() {
return <OdysseyApp />;
}
}

View File

@ -85,15 +85,6 @@ function makeRouteData(registry /*: RepoIdRegistry */) /*: RouteData */ {
title: `${entry.repoId.owner}/${entry.repoId.name} • SourceCred`,
navTitle: null,
})),
{
path: "/odyssey/",
contents: {
type: "PAGE",
component: () => require("./OdysseyPage").default,
},
title: "Odyssey Prototype",
navTitle: null,
},
{
path: "/discord-invite/",
contents: {

View File

@ -1,38 +0,0 @@
// @flow
import type {RepoId} from "../../core/repoId";
import {type NodeAddressT} from "../../core/graph";
import type {
IBackendAdapterLoader,
IAnalysisAdapter,
} from "../../analysis/analysisAdapter";
import {hackathonExample} from "./example";
import {declaration} from "./declaration";
export class BackendAdapterLoader implements IBackendAdapterLoader {
declaration() {
return declaration;
}
// TODO(@decentralion): Enable loading graphs other than the hackathon example.
load(
_unused_sourcecredDirectory: string,
_unused_repoId: RepoId
): Promise<AnalysisAdapter> {
const aa: AnalysisAdapter = new AnalysisAdapter();
// HACK: This any-coercion should be unncessary. Sad flow.
return Promise.resolve((aa: any));
}
}
export class AnalysisAdapter implements IAnalysisAdapter {
declaration() {
return declaration;
}
// TODO(@decentralion): Add real creation times to the data model
createdAt(_unused_node: NodeAddressT): null {
return null;
}
graph() {
return hackathonExample().graph();
}
}

View File

@ -1,82 +0,0 @@
// @flow
import {EdgeAddress, NodeAddress} from "../../core/graph";
import type {PluginDeclaration} from "../../analysis/pluginDeclaration";
import type {NodeType} from "../../analysis/types";
export type OdysseyNodeTypeIdentifier =
| "ARTIFACT"
| "CONTRIBUTION"
| "VALUE"
| "PERSON";
export type OdysseyEdgeTypeIdentifier = "DEPENDS_ON";
export function isOdysseyNodeTypeIdentifier(
n: OdysseyNodeTypeIdentifier
): boolean {
return ["ARTIFACT", "CONTRIBUTION", "VALUE", "PERSON"].indexOf(n) !== -1;
}
export const NODE_PREFIX = NodeAddress.fromParts(["sourcecred", "odyssey"]);
export function isOdysseyEdgeTypeIdentifier(x: string): boolean {
return x === "DEPENDS_ON";
}
export const EDGE_PREFIX = EdgeAddress.fromParts(["sourcecred", "odyssey"]);
const artifactNodeType: NodeType = Object.freeze({
name: "Artifact",
pluralName: "Artifacts",
prefix: NodeAddress.append(NODE_PREFIX, "ARTIFACT"),
defaultWeight: 2,
description:
"Represents a durably valuable piece of a project, e.g. a major subcomponent.",
});
const contributionNodeType: NodeType = Object.freeze({
name: "Contribution",
pluralName: "Contributions",
prefix: NodeAddress.append(NODE_PREFIX, "CONTRIBUTION"),
defaultWeight: 1,
description:
"Represents any specific work or labor that went into a project.",
});
const valueNodeType: NodeType = Object.freeze({
name: "Value",
pluralName: "Values",
prefix: NodeAddress.append(NODE_PREFIX, "VALUE"),
defaultWeight: 4,
description: "Represents a high-level value of the project.",
});
const personNodeType: NodeType = Object.freeze({
name: "Person",
pluralName: "People",
prefix: NodeAddress.append(NODE_PREFIX, "PERSON"),
defaultWeight: 1,
description: "Represents an individual contributor.",
});
const dependsOnEdgeType = Object.freeze({
forwardName: "depends on",
backwardName: "is depended on by",
prefix: EdgeAddress.append(EDGE_PREFIX, "DEPENDS_ON"),
defaultWeight: {forwards: 1, backwards: 0},
description: "Generic edge for flowing credit in the Odyssey plugin",
});
export const declaration: PluginDeclaration = Object.freeze({
name: "Odyssey",
nodePrefix: NODE_PREFIX,
edgePrefix: EDGE_PREFIX,
nodeTypes: [
contributionNodeType,
valueNodeType,
personNodeType,
artifactNodeType,
],
edgeTypes: [dependsOnEdgeType],
});

View File

@ -1,114 +0,0 @@
// @flow
import {OdysseyInstance, type Node} from "./instance";
/**
* An example Odyssey instance, based on work at the Odyssey Hackathon.
*/
export function hackathonExample(): OdysseyInstance {
// Define the types of nodes allowed in our instance
const instance = new OdysseyInstance();
// define our values for the hackathon
const logistics = instance.addNode("VALUE", "logistics");
const design = instance.addNode("VALUE", "design");
const narrative = instance.addNode("VALUE", "narrative");
const prototype = instance.addNode("VALUE", "prototype");
const outreach = instance.addNode("VALUE", "outreach");
// the cast of characters
const dl = instance.addNode("PERSON", "dandelion");
const mz = instance.addNode("PERSON", "z zargham");
const irene = instance.addNode("PERSON", "irene");
const max = instance.addNode("PERSON", "max");
const dennis = instance.addNode("PERSON", "dennis");
const jonathan = instance.addNode("PERSON", "jonathan");
const lb = instance.addNode("PERSON", "lb");
const brian = instance.addNode("PERSON", "brian");
const sarah = instance.addNode("PERSON", "sarah");
const jmnemo = instance.addNode("PERSON", "@jmnemo");
const talbott = instance.addNode("PERSON", "jonathan talbott");
const agata = instance.addNode("PERSON", "agata");
// the artifacts
const graphviz = instance.addNode("ARTIFACT", "graph visualizer");
const backend = instance.addNode("ARTIFACT", "backend");
const frontend = instance.addNode("ARTIFACT", "frontend");
const seededPagerank = instance.addNode("ARTIFACT", "seeded pagerank");
const canvas = instance.addNode(
"ARTIFACT",
"the awesome illustrated poster board"
);
const logo = instance.addNode("ARTIFACT", "the broken-lightbulb logo");
instance.addEdge("DEPENDS_ON", prototype, graphviz);
instance.addEdge("DEPENDS_ON", prototype, backend);
instance.addEdge("DEPENDS_ON", prototype, frontend);
instance.addEdge("DEPENDS_ON", frontend, seededPagerank);
instance.addEdge("DEPENDS_ON", design, graphviz);
instance.addEdge("DEPENDS_ON", design, frontend);
instance.addEdge("DEPENDS_ON", design, logo);
instance.addEdge("DEPENDS_ON", narrative, logo);
instance.addEdge("DEPENDS_ON", narrative, canvas);
function addContribution(
description: string,
authors: Node[],
impacted: Node[]
) {
const contrib = instance.addNode("CONTRIBUTION", description);
for (const author of authors) {
instance.addEdge("DEPENDS_ON", contrib, author);
}
for (const impact of impacted) {
instance.addEdge("DEPENDS_ON", impact, contrib);
}
}
addContribution(
"colors for the graph visualizer",
[dennis, irene, lb, max, dl],
[design, graphviz]
);
addContribution("design for graph visualizer", [dennis], [graphviz, design]);
addContribution(
"design for the frontend",
[dennis, irene],
[design, frontend]
);
addContribution(
"pre-hack planning and project management",
[brian],
[prototype, logistics]
);
addContribution("implementing the graph visualizer", [dl], [graphviz]);
addContribution("implementing seeded PageRank", [dl, mz], [seededPagerank]);
addContribution("implementing the frontend", [dl, jmnemo], [frontend]);
addContribution("implementing the backend", [dl], [backend]);
addContribution("logo--preliminary work", [lb, agata], [logo]);
addContribution("logo--lightbulb moment", [lb, max], [logo]);
addContribution("drawing the canvas", [lb], [canvas]);
addContribution(
"narrative shaping for canvas",
[lb, dl, mz],
[canvas, narrative]
);
addContribution("oneline narrative statement", [dl, talbott], [narrative]);
addContribution("oneline narrative review", [max, dl], [narrative]);
addContribution("booking hotel stay", [sarah], [logistics]);
addContribution("booking plane tickets", [sarah], [logistics]);
addContribution("helping Dennis get into the space", [sarah], [logistics]);
addContribution(
"example prototype dataset",
[mz, brian],
[prototype, outreach]
);
addContribution("final presentation", [dl, prototype], [narrative]);
addContribution("connection to the common stack", [mz], [outreach]);
addContribution("discussing the canvas with folks", [lb], [outreach]);
addContribution("general logistical defense", [jonathan], [logistics]);
addContribution("recruiting Max & company", [mz], [outreach, logistics]);
addContribution("forming the team", [mz], [logistics]);
return instance;
}

View File

@ -1,48 +0,0 @@
// @flow
import type {Assets} from "../../webutil/assets";
import type {PluginDeclaration} from "../../analysis/pluginDeclaration";
import type {RepoId} from "../../core/repoId";
import type {
StaticExplorerAdapter as IStaticExplorerAdapter,
DynamicExplorerAdapter as IDynamicExplorerAdapter,
} from "../../explorer/adapters/explorerAdapter";
import {NodeAddress, type NodeAddressT} from "../../core/graph";
import {declaration} from "./declaration";
import {OdysseyInstance} from "./instance";
import {hackathonExample} from "./example";
export class StaticExplorerAdapter implements IStaticExplorerAdapter {
declaration(): PluginDeclaration {
return declaration;
}
// TODO(@decentralion): Enable loading instances other than the hackathon example.
async load(
_unused_assets: Assets,
_unused_repoId: RepoId
): Promise<IDynamicExplorerAdapter> {
const instance = hackathonExample();
return new DynamicExplorerAdapter(instance);
}
}
class DynamicExplorerAdapter implements IDynamicExplorerAdapter {
+_instance: OdysseyInstance;
constructor(instance: OdysseyInstance): void {
this._instance = instance;
}
nodeDescription(address: NodeAddressT) {
const node = this._instance.node(address);
if (node == null) {
throw new Error(`No Odyssey node for: ${NodeAddress.toString(address)}`);
}
return node.description;
}
graph() {
return this._instance.graph();
}
static() {
return new StaticExplorerAdapter();
}
}

View File

@ -1,225 +0,0 @@
// @flow
/**
* Core "model" logic for the Odyssey plugin.
* Basically allows creating a data store of priorities, contributions, and people,
* and compiling that data store into a cred Graph.
*/
import {
Graph,
EdgeAddress,
NodeAddress,
type NodeAddressT,
type GraphJSON,
sortedNodeAddressesFromJSON,
} from "../../core/graph";
import deepEqual from "lodash.isequal";
import {
NODE_PREFIX,
EDGE_PREFIX,
type OdysseyNodeTypeIdentifier,
isOdysseyNodeTypeIdentifier,
type OdysseyEdgeTypeIdentifier,
isOdysseyEdgeTypeIdentifier,
} from "./declaration";
import {toCompat, fromCompat, type Compatible} from "../../util/compat";
import * as NullUtil from "../../util/null";
export type Node = {|
+nodeTypeIdentifier: OdysseyNodeTypeIdentifier,
+address: NodeAddressT,
+description: string,
|};
const COMPAT_INFO = {type: "sourcecred/odyssey/instance", version: "0.1.0"};
export type InstanceJSON = Compatible<{|
+graphJSON: GraphJSON,
+sortedDescriptions: $ReadOnlyArray<string>,
+count: number,
|}>;
/**
* This is the data model for a particular instance in the Odyssey Plugin.
* The OdysseyInstance allows adding "Entities", which are basically nodes
* in the Odyssey graph augmented with a type identifier, and a description.
* Currently, the types are restricted to types hard-coded in the
* [declaration](./declaration.js) but we intend to allow instance-specified
* types in the future.
*
* The OdysseyInstance maintains an internal graph which actually stores the
* node identities, as well as added nodes. You can get a copy of this graph
* by calling the `.graph()` method.
*
* Entities are identified by an incrementing id (`._count`). This is
* convenient for implementation, although it will make reconciling
* simultaneous edits challenging. Once that becomes a real issue, we should
* switch to a different node/edge identification strategy.
*/
export class OdysseyInstance {
_graph: Graph;
_descriptions: Map<NodeAddressT, string>;
_count: number;
/**
* Construct an Odyssey Instance.
*/
constructor() {
this._graph = new Graph();
this._descriptions = new Map();
this._count = 0;
}
/**
* Add a new node to the instance (and a corresponding node to the graph).
*
* Requires a valid node type identifier (a string that uniquely identifies
* an Odyssey node type; see [declaration.js](./declaration.js)).
*/
addNode(
typeIdentifier: OdysseyNodeTypeIdentifier,
description: string
): Node {
if (!isOdysseyNodeTypeIdentifier(typeIdentifier)) {
throw new Error(
`Tried to add node with invalid type identifier: ${typeIdentifier}`
);
}
const address = NodeAddress.append(
NODE_PREFIX,
typeIdentifier,
String(this._count)
);
this._graph.addNode(address);
this._count++;
this._descriptions.set(address, description);
return NullUtil.get(this.node(address));
}
/**
* Retrieve the Node corresponding to a given node address, if it exists.
*/
node(address: NodeAddressT): ?Node {
if (!this._graph.hasNode(address)) {
return null;
}
const parts = NodeAddress.toParts(address);
// We know it is an OdysseyNodeTypeIdentifier because the instance's internal
// graph only has Odyssey nodes in it.
const nodeTypeIdentifier: OdysseyNodeTypeIdentifier = (parts[2]: any);
if (!isOdysseyNodeTypeIdentifier(nodeTypeIdentifier)) {
throw new Error(
`Invariant violation: ${nodeTypeIdentifier} is not odyssey type identifier`
);
}
const description = NullUtil.get(this._descriptions.get(address));
return {address, nodeTypeIdentifier, description};
}
/**
* Retrieve all the Nodes in the instance.
*
* Optionally filter to only nodes of a chosen type.
*/
nodes(typeIdentifier?: OdysseyNodeTypeIdentifier): Iterator<Node> {
const prefix =
typeIdentifier == null
? NODE_PREFIX
: NodeAddress.append(NODE_PREFIX, typeIdentifier);
return this._nodesIterator(prefix);
}
*_nodesIterator(prefix: NodeAddressT): Iterator<Node> {
for (const a of this._graph.nodes({prefix})) {
yield NullUtil.get(this.node(a));
}
}
/**
* Add an edge to the Odyssey instance.
*/
// TODO(@decentralion): Add support for edge types (also configured on a per-instance basis).
addEdge(
type: OdysseyEdgeTypeIdentifier,
src: Node,
dst: Node
): OdysseyInstance {
if (!isOdysseyEdgeTypeIdentifier(type)) {
throw new Error(`Invalid Odyssey edge type identifier: ${type}`);
}
const edge = {
src: src.address,
dst: dst.address,
address: EdgeAddress.append(EDGE_PREFIX, type, String(this._count)),
};
this._graph.addEdge(edge);
this._count++;
return this;
}
/**
* Returns a copy of the graph underlying this instance.
*/
graph(): Graph {
return this._graph.copy();
}
toJSON(): InstanceJSON {
const graphJSON = this._graph.toJSON();
const sortedNodeAddresses = sortedNodeAddressesFromJSON(graphJSON);
const sortedDescriptions = sortedNodeAddresses.map((a) =>
NullUtil.get(this._descriptions.get(a))
);
return toCompat(COMPAT_INFO, {
graphJSON,
sortedDescriptions,
count: this._count,
});
}
static fromJSON(j: InstanceJSON): OdysseyInstance {
const {graphJSON, sortedDescriptions, count} = fromCompat(COMPAT_INFO, j);
const instance = new OdysseyInstance();
instance._graph = Graph.fromJSON(graphJSON);
instance._count = count;
const descriptions = new Map();
const sortedNodeAddresses = sortedNodeAddressesFromJSON(graphJSON);
for (let i = 0; i < sortedNodeAddresses.length; i++) {
descriptions.set(sortedNodeAddresses[i], sortedDescriptions[i]);
}
instance._descriptions = descriptions;
return instance;
}
/**
* Returns whether two Odyssey instances have identical histories.
*
* Two instances are historically identical if they have the same ordered
* sequence of node additions and deletions. This is because the address of
* Nodes and Edges in the instance is determined by the order in which they
* were added.
*
* For an illustration, consider the following case:
* ```js
* const i1 = new OdysseyInstance();
* i1.addNode("PERSON", "me")
* i1.addNode("PERSON", "you")
*
* const i2 = new OdysseyInstance();
* i2.addNode("PERSON", "you")
* i2.addNode("PERSON", "me")
*
* expect(i1.isHistoricallyIdentical(i2)).toBe(false);
* ```
*/
isHistoricallyIdentical(that: OdysseyInstance): boolean {
return (
this._count === that._count &&
this._graph.equals(that._graph) &&
deepEqual(this._descriptions, that._descriptions)
);
}
}

View File

@ -1,152 +0,0 @@
// @flow
import {OdysseyInstance} from "./instance";
import {EdgeAddress, NodeAddress, Direction} from "../../core/graph";
describe("plugins/odyssey/instance", () => {
function exampleInstance() {
const instance = new OdysseyInstance();
const me = instance.addNode("PERSON", "me");
const you = instance.addNode("PERSON", "you");
const value = instance.addNode("VALUE", "valuable-ness");
const contribution = instance.addNode("CONTRIBUTION", "a good deed");
const artifact = instance.addNode(
"ARTIFACT",
"the thing that creates value"
);
// first off I'd like to thank myself
instance
.addEdge("DEPENDS_ON", me, me)
// thank you for your support
.addEdge("DEPENDS_ON", me, you)
.addEdge("DEPENDS_ON", contribution, me)
.addEdge("DEPENDS_ON", artifact, contribution)
.addEdge("DEPENDS_ON", value, artifact);
return {instance, you, me, value, contribution, artifact};
}
it("can retrieve the graph", () => {
const {instance, me} = exampleInstance();
const graph = instance.graph();
const nodes = Array.from(graph.nodes());
const edges = Array.from(graph.edges());
expect(nodes).toHaveLength(5);
expect(edges).toHaveLength(5);
const myNeighbors = graph.neighbors(me.address, {
direction: Direction.ANY,
nodePrefix: NodeAddress.empty,
edgePrefix: EdgeAddress.empty,
});
expect(Array.from(myNeighbors)).toHaveLength(3);
});
it("retrieved graph is a copy", () => {
const {instance} = exampleInstance();
expect(instance.graph()).not.toBe(instance.graph());
});
it("returns nodes as they are added", () => {
const {me} = exampleInstance();
expect(me).toEqual({
address: NodeAddress.fromParts(["sourcecred", "odyssey", "PERSON", "0"]),
nodeTypeIdentifier: "PERSON",
description: "me",
});
});
it("can retrieve nodes by address", () => {
const {instance, me} = exampleInstance();
expect(instance.node(me.address)).toEqual(me);
});
it("returns null for non-existent node", () => {
const instance = new OdysseyInstance();
expect(instance.node(NodeAddress.empty)).toEqual(null);
});
it("throws an error when adding an node with bad type", () => {
const instance = new OdysseyInstance();
// $ExpectFlowError
expect(() => instance.addNode("FOO", "foo")).toThrowError(
"invalid type identifier: FOO"
);
});
it("can retrieve all nodes", () => {
const {instance} = exampleInstance();
expect(Array.from(instance.nodes())).toHaveLength(5);
});
it("can retrieve nodes by type", () => {
const {instance, artifact, contribution, value} = exampleInstance();
expect(Array.from(instance.nodes("ARTIFACT"))).toEqual([artifact]);
expect(Array.from(instance.nodes("VALUE"))).toEqual([value]);
expect(Array.from(instance.nodes("CONTRIBUTION"))).toEqual([contribution]);
// $ExpectFlowError
expect(Array.from(instance.nodes("NONEXISTENT"))).toEqual([]);
});
it("errors if adding edge between Nodes that don't exist", () => {
const {me, you} = exampleInstance();
const i = new OdysseyInstance();
expect(() => i.addEdge("DEPENDS_ON", me, you)).toThrowError(
"Missing src on edge:"
);
});
describe("equality", () => {
it("empty instance isHistoricallyIdentical empty instance", () => {
const a = new OdysseyInstance();
const b = new OdysseyInstance();
expect(a.isHistoricallyIdentical(b)).toBe(true);
});
it("empty instance does not equal nonempty instance", () => {
const a = new OdysseyInstance();
a.addNode("PERSON", "me");
const b = new OdysseyInstance();
expect(a.isHistoricallyIdentical(b)).toBe(false);
});
it("complex but identically generated instances are equal", () => {
const {instance: a} = exampleInstance();
const {instance: b} = exampleInstance();
expect(a.isHistoricallyIdentical(b)).toBe(true);
});
it("instances with different descriptions are not equal", () => {
const a = new OdysseyInstance();
const b = new OdysseyInstance();
a.addNode("PERSON", "me");
b.addNode("PERSON", "me2");
expect(a.isHistoricallyIdentical(b)).toBe(false);
});
it("instances with different types are not equal", () => {
const a = new OdysseyInstance();
const b = new OdysseyInstance();
a.addNode("PERSON", "me");
b.addNode("ARTIFACT", "me");
expect(a.isHistoricallyIdentical(b)).toBe(false);
});
it("instances with different edges are not equal", () => {
const a = new OdysseyInstance();
const b = new OdysseyInstance();
const me = a.addNode("PERSON", "me");
b.addNode("PERSON", "me");
a.addEdge("DEPENDS_ON", me, me);
expect(a.isHistoricallyIdentical(b)).toBe(false);
});
it("instances with different histories are not equal", () => {
const i1 = new OdysseyInstance();
i1.addNode("PERSON", "me");
i1.addNode("PERSON", "you");
const i2 = new OdysseyInstance();
i2.addNode("PERSON", "you");
i2.addNode("PERSON", "me");
expect(i1.isHistoricallyIdentical(i2)).toBe(false);
});
});
describe("to/from JSON", () => {
it("to->fro is identity", () => {
const {instance} = exampleInstance();
const instance2 = OdysseyInstance.fromJSON(instance.toJSON());
expect(instance.isHistoricallyIdentical(instance2)).toBe(true);
});
it("fro->to is identity", () => {
const {instance} = exampleInstance();
const json1 = instance.toJSON();
const json2 = OdysseyInstance.fromJSON(json1).toJSON();
expect(json1).toEqual(json2);
});
});
});

View File

@ -1,153 +0,0 @@
// @flow
import React, {Component} from "react";
import {StyleSheet, css} from "aphrodite/no-important";
import {Header} from "./Header";
type AppProps = {||};
type AppState = {||};
type Entity = {|
+name: string,
+score: number,
|};
const exampleValues: $ReadOnlyArray<Entity> = [
{name: "Implementation", score: 1002},
{name: "Research", score: 1001},
{name: "Ethics", score: 1000},
{name: "Learning", score: 999},
{name: "Something With A Long Name, Really Quite Long", score: 999},
{name: "@1", score: 998},
{name: "@2", score: 998},
{name: "@3", score: 998},
{name: "@4", score: 998},
{name: "@5", score: 998},
{name: "@6", score: 998},
];
const examplePeople: $ReadOnlyArray<Entity> = [
{name: "@decentralion", score: 1002},
{name: "@wchargin", score: 1001},
{name: "@mzargham", score: 1000},
{name: "@brianlitwin", score: 999},
{name: "@anthrocypher", score: 998},
{name: "@brutalfluffy", score: 998},
{name: "@1", score: 998},
{name: "@2", score: 998},
{name: "@3", score: 998},
{name: "@4", score: 998},
{name: "@5", score: 998},
{name: "@6", score: 998},
{name: "@7", score: 998},
{name: "@8", score: 998},
{name: "@9", score: 998},
];
class App extends Component<AppProps, AppState> {
scoreList(title: string, entities: $ReadOnlyArray<Entity>) {
const entries = entities.map(({name, score}) => (
<div key={name} className={css(styles.entityRow)}>
<div className={css(styles.entityName)}>{name}</div>
<div className={css(styles.entityScore)}>{score.toFixed(0)} ¤</div>
</div>
));
return (
<div className={css(styles.scoreList)}>
<h1 className={css(styles.scoreListTitle)}>{title}</h1>
{entries}
</div>
);
}
render() {
return (
<div className={css(styles.app)}>
<Header />
<div className={css(styles.nonHeader)}>
<div className={css(styles.scoreListsContainer)}>
{this.scoreList("Our Values", exampleValues)}
{this.scoreList("Our People", examplePeople)}
</div>
<div className={css(styles.chartContainer)}>Chart to go here.</div>
</div>
</div>
);
}
}
const styles = StyleSheet.create({
app: {
// HACK: Position absolute / top:0 to cover up the header from the
// default SourceCred UI. There's some discussion in the pull request:
// https://github.com/sourcecred/sourcecred/pull/1132
top: "0px",
position: "absolute",
display: "flex",
flexDirection: "column",
height: "100%",
width: "100%",
},
scoreList: {
padding: "20px 20px 50px 20px",
},
scoreListTitle: {
fontSize: "28px",
lineHeight: "36px",
color: "#fff",
fontFamily: "'DINCondensed', sans-serif",
fontWeight: "700",
letterSpacing: "0.04em",
margin: "0 0 20px 0",
},
entityRow: {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
fontSize: "16px",
lineHeight: "19px",
marginTop: "20px",
cursor: "pointer",
},
entityName: {
fontWeight: "700",
color: "#E9EDEC",
letterSpacing: "-0.2px",
},
entityScore: {
color: "#EDAD47",
fontWeight: "600",
flexShrink: 0,
paddingLeft: "5px",
},
scoreListsContainer: {
display: "flex",
flexDirection: "column",
backgroundColor: "#242424",
"overflow-y": "scroll",
minWidth: "400px",
},
nonHeader: {
display: "flex",
flexDirection: "row",
paddingTop: "80px",
overflow: "hidden",
},
chartContainer: {
width: "100%",
padding: "42px",
display: "flex",
},
});
export default App;

View File

@ -1,66 +0,0 @@
// @flow
import React, {Component} from "react";
import {StyleSheet, css} from "aphrodite/no-important";
import LogoIcon from "./img/logo.svg";
export type Props = {||};
export class Header extends Component<Props> {
render() {
return (
<div className={css(styles.header)}>
<div className={css(styles.titleBlock)}>
<div className={css(styles.projectName)}>Project Name Here</div>
<div className={css(styles.logo)}>
<span>SourceCred</span>
<LogoIcon />
</div>
</div>
</div>
);
}
}
const styles = StyleSheet.create({
header: {
position: "fixed",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
top: "0",
left: "0",
height: "80px",
width: "100%",
backgroundColor: "#1D1D1C",
padding: "0 50px",
zIndex: "776",
},
titleBlock: {
minWidth: "380px",
color: "#E9EDEC",
fontSize: "32px",
lineHeight: "30px",
fontFamily: "'DINCondensed', sans-serif",
fontWeight: "700",
},
logo: {
display: "flex",
alignItems: "center",
fontSize: "20px",
lineHeight: "20px",
color: "#8E8F91",
letterSpacing: "-0.25px",
span: {
height: "16px",
},
svg: {
width: "20px",
height: "20px",
marginLeft: "7px",
},
},
});

View File

@ -1,8 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.78285 18.2825C14.4771 18.2825 18.2826 14.477 18.2826 9.78272C18.2826 5.08843 14.4771 1.28296 9.78285 1.28296C5.08856 1.28296 1.28308 5.08843 1.28308 9.78272C1.28308 14.477 5.08856 18.2825 9.78285 18.2825Z" fill="#EDAD47"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.78263 16.6788C13.5912 16.6788 16.6787 13.5913 16.6787 9.78275C16.6787 5.97418 13.5912 2.88672 9.78263 2.88672C5.97406 2.88672 2.8866 5.97418 2.8866 9.78275C2.8866 13.5913 5.97406 16.6788 9.78263 16.6788Z" fill="#1D1D1C"/>
<path d="M15.0929 12.4474C15.3059 12.4013 15.5278 12.4666 15.6819 12.6208L19.5457 16.4846C19.7963 16.7351 19.7963 17.1413 19.5457 17.3918L17.3911 19.5464C17.1406 19.7969 16.7344 19.7969 16.4839 19.5464L12.6201 15.6826C12.466 15.5285 12.4007 15.3066 12.4467 15.0935L12.8295 13.3217C12.8827 13.0756 13.0749 12.8833 13.321 12.8301L15.0929 12.4474Z" fill="#EDAD47"/>
<path d="M4.90639 12.4474C4.69333 12.4013 4.47146 12.4666 4.31733 12.6208L0.453529 16.4846C0.203011 16.7351 0.203011 17.1413 0.453529 17.3918L2.60814 19.5464C2.85866 19.7969 3.26483 19.7969 3.51535 19.5464L7.37915 15.6826C7.53329 15.5285 7.5986 15.3066 7.55257 15.0935L7.16979 13.3217C7.11661 13.0756 6.92436 12.8833 6.67822 12.8301L4.90639 12.4474Z" fill="#EDAD47"/>
<path d="M15.0929 7.55264C15.3059 7.59867 15.5278 7.53335 15.6819 7.37922L19.5457 3.51542C19.7963 3.2649 19.7963 2.85873 19.5457 2.60821L17.3911 0.453598C17.1406 0.20308 16.7344 0.20308 16.4839 0.453598L12.6201 4.3174C12.466 4.47153 12.4007 4.69339 12.4467 4.90646L12.8295 6.67829C12.8827 6.92442 13.0749 7.11668 13.321 7.16986L15.0929 7.55264Z" fill="#EDAD47"/>
<path d="M4.90639 7.55264C4.69333 7.59867 4.47146 7.53335 4.31733 7.37922L0.453529 3.51542C0.203011 3.2649 0.203011 2.85873 0.453529 2.60821L2.60814 0.453598C2.85866 0.20308 3.26483 0.20308 3.51535 0.453598L7.37915 4.3174C7.53329 4.47153 7.5986 4.69339 7.55257 4.90646L7.16979 6.67829C7.11661 6.92442 6.92436 7.11668 6.67822 7.16986L4.90639 7.55264Z" fill="#EDAD47"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB