├── .babelrc
├── .eslintignore
├── .gitignore
├── .gitmodules
├── .storybook
├── .babelrc
├── config.js
└── webpack.config.js
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── .yarnclean
├── README.md
├── app
├── _tests
│ ├── __snapshots__
│ │ └── storyshots.test.js.snap
│ └── storyshots.test.ts
├── components
│ └── artist
│ │ ├── _stories
│ │ └── _header.story.tsx
│ │ ├── _tests
│ │ └── header.test.ts
│ │ └── header.tsx
├── containers
│ ├── pure-react
│ │ └── artist
│ │ │ ├── artwork.tsx
│ │ │ ├── browser.tsx
│ │ │ ├── grid.tsx
│ │ │ ├── index.tsx
│ │ │ └── style.css
│ ├── react-aphrodite
│ │ └── artist
│ │ │ ├── artwork.tsx
│ │ │ ├── browser.tsx
│ │ │ ├── grid.tsx
│ │ │ └── index.tsx
│ ├── react-inline-css
│ │ └── artist
│ │ │ ├── artwork.tsx
│ │ │ ├── browser.tsx
│ │ │ ├── grid.tsx
│ │ │ └── index.tsx
│ ├── react-jss
│ │ └── artist
│ │ │ ├── artwork.tsx
│ │ │ ├── browser.tsx
│ │ │ ├── grid.tsx
│ │ │ └── index.tsx
│ └── react-native-web
│ │ └── artist
│ │ ├── artwork.tsx
│ │ ├── browser.tsx
│ │ ├── grid.tsx
│ │ ├── header.tsx
│ │ ├── index.tsx
│ │ ├── inverted_button.tsx
│ │ ├── metaphysics.ts
│ │ ├── switch_board.ts
│ │ └── text
│ │ ├── headline.tsx
│ │ └── serif.tsx
├── gql.d.ts
├── relay
│ ├── config.ts
│ └── root_queries.ts
└── routes.tsx
├── data
├── colors.json
├── schema.graphql
├── schema.js
└── schema.json
├── index.js
├── jsconfig.json
├── package.json
├── tsconfig.json
├── tslint.json
├── types
├── global.d.ts
├── react-native-web.d.ts
└── react-native.d.ts
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "stage-3",
5 | "react"
6 | ],
7 | "plugins": [
8 | "./data/schema",
9 | "transform-flow-strip-types",
10 | "syntax-async-functions",
11 | "transform-regenerator",
12 | "transform-class-properties",
13 | ["module-resolver", { "alias": { "react-native": "react-native-web" } }]
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | externals
3 | data
4 | flow-typed
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | public
4 | out
5 | .awcache
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "externals/metaphysics"]
2 | path = externals/metaphysics
3 | url = https://github.com/artsy/metaphysics.git
4 | [submodule "externals/elan"]
5 | path = externals/elan
6 | url = https://github.com/artsy/elan.git
7 |
--------------------------------------------------------------------------------
/.storybook/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | // This needs to make sure that the relative path of the
3 | // relay config babel extension is set to the right path.
4 | "extends": "../.babelrc"
5 | }
6 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@kadira/storybook'
2 |
3 | // TODO: Add globbing: e.g. ../app/containers/*/_stories/*.story.js
4 | // could use micromatch, which is used in Jest
5 |
6 | function loadStories() {
7 | require('../app/containers/artist/_stories/_header.story')
8 | }
9 |
10 | configure(loadStories, module);
11 |
12 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | module.exports = {
4 | module: {
5 | loaders: [
6 | {
7 | include: path.resolve(__dirname, "../")
8 | loaders: ["style", "css", "sass"],
9 | test: /.scss$/,
10 | },
11 | ],
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for node debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Launch Program",
9 | "type": "node2",
10 | "request": "launch",
11 | "program": "${workspaceRoot}/index.js",
12 | "cwd": "${workspaceRoot}"
13 | },
14 | {
15 | "name": "Attach to Process",
16 | "type": "node2",
17 | "request": "attach",
18 | "port": 9229
19 | },
20 | {
21 | "name": "Run Tests With Debugger (slower, use npm run watch for normal work)",
22 | "type": "node2",
23 | "request": "launch",
24 | "port": 5858,
25 | "address": "localhost",
26 | "sourceMaps": true,
27 | "stopOnEntry": false,
28 | "runtimeExecutable": null,
29 | "runtimeArgs": [
30 | "--debug-brk",
31 | "./node_modules/.bin/jest",
32 | "-i"
33 | ],
34 | "cwd": "${workspaceRoot}"
35 | }
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "javascript.validate.enable": false,
4 | "tslint.run": "onType",
5 | "tslint.autoFixOnSave": true,
6 | "editor.formatOnType": true,
7 | "editor.formatOnSave": true
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // Available variables which can be used inside of strings.
2 | // ${workspaceRoot}: the root folder of the team
3 | // ${file}: the current opened file
4 | // ${fileBasename}: the current opened file's basename
5 | // ${fileDirname}: the current opened file's dirname
6 | // ${fileExtname}: the current opened file's extension
7 | // ${cwd}: the current working directory of the spawned process
8 |
9 | // A task runner that calls a custom npm script that compiles the extension.
10 | {
11 | "version": "0.1.0",
12 |
13 | // we want to run npm
14 | "command": "yarn",
15 |
16 | // the command is a shell script
17 | "isShellCommand": true,
18 |
19 | // show the output window only if unrecognized errors occur.
20 | "showOutput": "silent",
21 |
22 | // we run the custom script "compile" as defined in package.json
23 | "args": ["run", "compile", "--loglevel", "silent"],
24 |
25 | // The tsc compiler is started in watching mode
26 | "isWatching": true,
27 |
28 | // use the standard tsc in watch mode problem matcher to find compile problems in the output.
29 | "problemMatcher": "$tsc-watch"
30 | }
31 |
--------------------------------------------------------------------------------
/.yarnclean:
--------------------------------------------------------------------------------
1 | # test directories
2 | __tests__
3 | tests
4 | powered-test
5 |
6 | # asset directories
7 | docs
8 | doc
9 | website
10 | images
11 | assets
12 |
13 | # examples
14 | example
15 | examples
16 |
17 | # code coverage directories
18 | coverage
19 | .nyc_output
20 |
21 | # build scripts
22 | Makefile
23 | Gulpfile.js
24 | Gruntfile.js
25 |
26 | # configs
27 | .tern-project
28 | .gitattributes
29 | .editorconfig
30 | .*ignore
31 | .eslintrc
32 | .jshintrc
33 | .flowconfig
34 | .documentup.json
35 | .yarn-metadata.json
36 | .*.yml
37 | *.yml
38 |
39 | # misc
40 | *.gz
41 | *.md
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # About
2 |
3 | These are some experiments with using React and Relay for both server and client side rendering.
4 |
5 | ## Setup
6 |
7 | ```
8 | $ npm install yarn -g
9 | $ yarn install
10 | ```
11 |
12 | Then start the development server with:
13 |
14 | ```
15 | $ yarn start
16 | ```
17 |
18 | To run storybooks:
19 |
20 | ```
21 | $ yarn run storybooks
22 | ```
23 |
24 | ## Development environment
25 |
26 | * Rather than restart the full server process, which is often done with tools like `nodemon`, this project only reloads
27 | the files in `./app` when FS changes occur.
28 |
29 | * In addition, webpack is configured to dynamically host client sources, meaning that these will also automatically be
30 | recompiled when FS changes occur.
31 |
32 | * Finally, webpack is configured to notify the client of changes to sources which then fetches the updated sources and
33 | triggers a re-render of the React components that are on screen.
34 |
35 | This setup is largely based on https://github.com/glenjamin/ultimate-hot-reloading-example.
36 |
37 | ## TODO
38 |
39 | * Take a look at react-native-web, how well it works, and if it would make sharing our components across platforms
40 | possible out of the box.
41 |
42 | ## Notes
43 |
44 | * In order for `react-hot-loader` to be able to reload components and maintain their current state, rather than a page
45 | reload, the components have to be exported themselves, not just the Relay wrapper container
46 | ([more info](https://github.com/fortruce/relay-skeleton/issues/1)). E.g.
47 |
48 | ```js
49 | export class Artist extends React.component {
50 | ...
51 | }
52 |
53 | export default Relay.createContainer(Artist, {
54 | ...
55 | })
56 | ```
57 |
58 | ## Examples
59 |
60 | ### Hot Loader in Action
61 |
62 | 
63 |
64 | ### Hot Loader Syntax Error Overlay
65 |
66 | 
67 |
68 |
--------------------------------------------------------------------------------
/app/_tests/__snapshots__/storyshots.test.js.snap:
--------------------------------------------------------------------------------
1 | exports[`Storyshots Artist Header For Stubbed Data 1`] = `
2 |
3 |
4 | Another Example
5 |
6 |
7 |
8 | OK, b. 1999
9 |
10 |
11 |
12 | 12 Followers
13 |
14 |
15 | `;
16 |
--------------------------------------------------------------------------------
/app/_tests/storyshots.test.ts:
--------------------------------------------------------------------------------
1 | import initStoryshots from "storyshots"
2 |
3 | initStoryshots({
4 | storyRegex: /^((?!(r|R)elay).)*$/,
5 | })
6 |
--------------------------------------------------------------------------------
/app/components/artist/_stories/_header.story.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from "@kadira/storybook"
2 | import * as React from "react"
3 | import * as Relay from "react-relay"
4 | import StubContainer from "react-storybooks-relay-container"
5 |
6 | import Header from "../header"
7 |
8 | import { artsyNetworkLayer } from "../../../relay/config"
9 | import { ArtistQueryConfig } from "../../../relay/root_queries"
10 |
11 | storiesOf("Artist Header", module)
12 | .add("Relay : Artist - Leda Catunda", () => {
13 | Relay.injectNetworkLayer(artsyNetworkLayer())
14 | const artistRoute = new ArtistQueryConfig({ artistID: "leda-catunda" })
15 | return
16 | })
17 |
18 | .add("For Stubbed Data", () => {
19 | const api = {
20 | artist: {
21 | birthday: "1999",
22 | counts: { follows: 12 },
23 | name: "Another Example",
24 | nationality: "OK",
25 | },
26 | }
27 | return
28 | })
29 |
--------------------------------------------------------------------------------
/app/components/artist/_tests/header.test.ts:
--------------------------------------------------------------------------------
1 | describe("test", () => {
2 | it("tests", () => {
3 | expect(1).toEqual(1)
4 | })
5 | })
6 |
--------------------------------------------------------------------------------
/app/components/artist/header.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as Relay from "react-relay"
3 | import GQL from "../../gql"
4 |
5 | interface Props {
6 | artist: GQL.ArtistType
7 | }
8 |
9 | interface State {
10 | following: boolean | null,
11 | followersCount: number,
12 | }
13 |
14 | class Header extends React.Component {
15 |
16 | constructor(props: any) {
17 | super(props)
18 | this.state = { following: null, followersCount: props.artist.counts.follows }
19 | }
20 |
21 | render() {
22 | const artist = this.props.artist
23 | return (
24 |
25 |
{artist.name}
26 | {this.renderByline()}
27 | {this.renderFollowersCount()}
28 |
29 | )
30 | }
31 |
32 | renderFollowersCount() {
33 | const count = this.state.followersCount
34 | const followerString = count + (count === 1 ? " Follower" : " Followers")
35 | return (
36 | {followerString}
37 | )
38 | }
39 |
40 | renderByline() {
41 | const artist = this.props.artist
42 | const bylineRequired = (artist.nationality || artist.birthday)
43 | if (bylineRequired) {
44 | return (
45 |
46 | {this.descriptiveString()}
47 |
48 | )
49 | } else {
50 | return null
51 | }
52 | }
53 |
54 | descriptiveString() {
55 | const artist = this.props.artist
56 | const descriptiveString = (artist.nationality || "") + this.birthdayString()
57 | return descriptiveString
58 | }
59 |
60 | birthdayString() {
61 | const birthday = this.props.artist.birthday
62 | if (!birthday) { return "" }
63 |
64 | const leadingSubstring = this.props.artist.nationality ? ", b." : ""
65 |
66 | if (birthday.includes("born")) {
67 | return birthday.replace("born", leadingSubstring)
68 | } else if (birthday.includes("Est.") || birthday.includes("Founded")) {
69 | return " " + birthday
70 | }
71 |
72 | return leadingSubstring + " " + birthday
73 | }
74 | }
75 |
76 | // TODO: Orta - what's up with this?
77 | export default Relay.createContainer(Header as any, {
78 | fragments: {
79 | artist: () => Relay.QL`
80 | fragment on Artist {
81 | _id
82 | id
83 | name
84 | nationality
85 | birthday
86 | counts {
87 | follows
88 | }
89 | }
90 | `,
91 | },
92 | })
93 |
--------------------------------------------------------------------------------
/app/containers/pure-react/artist/artwork.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as Relay from "react-relay"
3 |
4 | interface Props {
5 | artwork: any
6 | }
7 | interface State {
8 | }
9 |
10 | export class Artwork extends React.Component {
11 | render() {
12 | return (
13 |
14 |

15 |
{this.props.artwork.artists.map((artist) => artist.name).join(", ")}
16 |
{this.props.artwork.title}
17 |
18 | )
19 | }
20 | }
21 |
22 | export default Relay.createContainer(Artwork, {
23 | fragments: {
24 | artwork: () => Relay.QL`
25 | fragment on Artwork {
26 | title
27 | artists {
28 | name
29 | }
30 | image {
31 | url
32 | }
33 | }
34 | `,
35 | },
36 | })
37 |
--------------------------------------------------------------------------------
/app/containers/pure-react/artist/browser.tsx:
--------------------------------------------------------------------------------
1 | import IsomorphicRelay from "isomorphic-relay"
2 | import * as React from "react"
3 | import * as ReactDOM from "react-dom"
4 |
5 | import { artsyRelayEnvironment } from "../../../relay/config"
6 | import { ArtistQueryConfig } from "../../../relay/root_queries"
7 |
8 | declare var document: any
9 | declare var window: any
10 |
11 | import Artist from "./index"
12 |
13 | const rootElement = document.getElementById("root")
14 |
15 | const environment = artsyRelayEnvironment()
16 | IsomorphicRelay.injectPreparedData(environment, window.ARTIST_PROPS)
17 |
18 | IsomorphicRelay.prepareInitialRender({
19 | Container: Artist,
20 | queryConfig: new ArtistQueryConfig({ artistID: window.ARTIST_ID }),
21 | environment,
22 | }).then((props) => {
23 | ReactDOM.render(, rootElement)
24 | })
25 |
--------------------------------------------------------------------------------
/app/containers/pure-react/artist/grid.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as Relay from "react-relay"
3 |
4 | import Artwork from "./artwork"
5 |
6 | interface Props {
7 | sectionMargin: number,
8 | itemMargin: number,
9 | artworks: any[]
10 | }
11 | interface State {
12 | sectionCount: number,
13 | }
14 |
15 | export class Grid extends React.Component {
16 | // state: {
17 | // sectionDimension: number,
18 | // sectionCount: number,
19 | // };
20 |
21 | static defaultProps = {
22 | // sectionDirection: 'column',
23 | itemMargin: 20,
24 | sectionMargin: 20,
25 | }
26 |
27 | constructor(props) {
28 | super(props)
29 | this.state = {
30 | // sectionDimension: 0,
31 | sectionCount: 3,
32 | }
33 |
34 | // this.onLayout = this.onLayout.bind(this)
35 | }
36 |
37 | // layoutState(currentLayout) : Object {
38 | // const width = currentLayout.width
39 | // const isPad = width > 600
40 | // const isPadHorizontal = width > 900
41 |
42 | // const sectionCount = isPad ? (isPadHorizontal ? 4 : 3) : 2
43 | // const sectionMargins = this.props.sectionMargin * (sectionCount - 1)
44 | // const sectionDimension = (currentLayout.width - sectionMargins) / sectionCount
45 |
46 | // return { sectionCount: sectionCount,
47 | // sectionDimension: sectionDimension,
48 | // }
49 | // }
50 |
51 | // onLayout = (event: LayoutEvent) => {
52 | // const layout = event.nativeEvent.layout
53 | // const newLayoutState = this.layoutState(layout)
54 | // if (layout.width > 0) {
55 | // this.setState(newLayoutState)
56 | // }
57 | // }
58 |
59 | sectionedArtworks() {
60 | const sectionedArtworks = []
61 | const sectionRatioSums = []
62 | for (let i = 0; i < this.state.sectionCount; i++) {
63 | sectionedArtworks.push([])
64 | sectionRatioSums.push(0)
65 | }
66 |
67 | const artworks = this.props.artworks
68 | for (const artwork of artworks) {
69 | if (artwork.image) {
70 | let lowestRatioSum = Number.MAX_VALUE
71 | let sectionIndex: number | null = null
72 |
73 | for (let j = 0; j < sectionRatioSums.length; j++) {
74 | const ratioSum = sectionRatioSums[j]
75 | if (ratioSum < lowestRatioSum) {
76 | sectionIndex = j
77 | lowestRatioSum = ratioSum
78 | }
79 | }
80 |
81 | if (sectionIndex != null) {
82 | const section = sectionedArtworks[sectionIndex]
83 | section.push(artwork)
84 |
85 | // total section aspect ratio
86 | const aspectRatio = artwork.image.aspect_ratio || 1
87 | sectionRatioSums[sectionIndex] += (1 / aspectRatio)
88 | }
89 | }
90 | }
91 | return sectionedArtworks
92 | }
93 |
94 | renderSections() {
95 | // const spacerStyle = {
96 | // height: this.props.itemMargin,
97 | // }
98 | const sectionedArtworks = this.sectionedArtworks()
99 | const sections = []
100 | for (let i = 0; i < this.state.sectionCount; i++) {
101 | const artworkComponents = []
102 | const artworks = sectionedArtworks[i]
103 | for (let j = 0; j < artworks.length; j++) {
104 | const artwork = artworks[j]
105 | artworkComponents.push()
106 | if (j < artworks.length - 1) {
107 | // artworkComponents.push()
108 | artworkComponents.push()
109 | }
110 | }
111 |
112 | // const sectionSpecificStlye = {
113 | // width: this.state.sectionDimension,
114 | // marginRight: (i === this.state.sectionCount - 1 ? 0 : this.props.sectionMargin),
115 | // }
116 | sections.push(
117 | //
118 |
119 | {artworkComponents}
120 |
)
121 | }
122 | return sections
123 | }
124 |
125 | render() {
126 | return
{this.renderSections()}
127 | }
128 | }
129 |
130 | // const styles = StyleSheet.create({
131 | // container: {
132 | // flexDirection: 'row',
133 | // },
134 | // section: {
135 | // flexDirection: 'column',
136 | // },
137 | // })
138 |
139 | export default Relay.createContainer(Grid, {
140 | fragments: {
141 | artworks: () => Relay.QL`
142 | fragment on Artwork @relay(plural: true) {
143 | __id
144 | image {
145 | aspect_ratio
146 | }
147 | ${Artwork.getFragment("artwork")}
148 | }
149 | `,
150 | },
151 | })
152 |
--------------------------------------------------------------------------------
/app/containers/pure-react/artist/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as Relay from "react-relay"
3 |
4 | import ArtistHeader from "../../../components/artist/header"
5 | import Grid from "./grid"
6 | import GQL from "../../../gql"
7 |
8 | interface Props {
9 | artist: GQL.ArtistType,
10 | }
11 | interface State {
12 | following: boolean | null,
13 | followersCount: number,
14 | }
15 |
16 | export class Artist extends React.Component
{
17 | render() {
18 | return (
19 |
24 | )
25 | }
26 | }
27 |
28 | export default Relay.createContainer(Artist, {
29 | fragments: {
30 | artist: () => Relay.QL`
31 | fragment on Artist {
32 | name
33 | counts {
34 | artworks,
35 | partner_shows
36 | articles
37 | }
38 | artworks(size: 30) {
39 | ${Grid.getFragment("artworks")}
40 | }
41 | ${ArtistHeader.getFragment("artist")}
42 | }
43 | `,
44 | },
45 | })
46 |
--------------------------------------------------------------------------------
/app/containers/pure-react/artist/style.css:
--------------------------------------------------------------------------------
1 | .grid {
2 | display: flex;
3 | flex-direction: row;
4 | }
5 |
6 | .grid .column {
7 | flex-direction: column;
8 | flex: 1 0 0px;
9 | max-width: 300px;
10 | margin-right: 20px;
11 | }
12 |
13 | .grid .column .artwork {
14 | flex: 1;
15 | margin-bottom: 20px;
16 | }
17 |
18 | .artwork img {
19 | width: 100%;
20 | }
21 |
22 | .artwork h3,h4 {
23 | margin: 4px 0;
24 | color: rgb(102, 102, 102);
25 | font-size: 15px;
26 | }
27 |
28 | .artwork h3 {
29 | font-style: normal;
30 | font-weight: bold;
31 | }
32 |
33 | .artwork h4 {
34 | font-style: italic;
35 | font-weight: normal
36 | }
37 |
--------------------------------------------------------------------------------
/app/containers/react-aphrodite/artist/artwork.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as Relay from "react-relay"
3 |
4 | import { css, StyleSheet } from "aphrodite/no-important"
5 |
6 | interface Props {
7 | artwork: any
8 | }
9 | interface State {
10 | }
11 |
12 | export class Artwork extends React.Component {
13 | render() {
14 | const artists = this.props.artwork.artists.map(artist => artist.name).join(", ")
15 | return (
16 |
17 |

18 |
{artists}
19 |
{this.props.artwork.title}
20 |
21 | )
22 | }
23 | }
24 |
25 | // .grid .column .artwork {
26 | // flex: 1;
27 | // margin-bottom: 20px;
28 | // }
29 |
30 | // .artwork img {
31 | // width: 100%;
32 | // }
33 |
34 | // .artwork h3,h4 {
35 | // margin: 4px 0;
36 | // color: rgb(102, 102, 102);
37 | // font-size: 15px;
38 | // }
39 |
40 | // .artwork h3 {
41 | // font-style: normal;
42 | // font-weight: bold;
43 | // }
44 |
45 | // .artwork h4 {
46 | // font-style: italic;
47 | // font-weight: normal
48 | // }
49 |
50 | const styles = StyleSheet.create({
51 | artists: {
52 | fontStyle: "normal",
53 | fontWeight: "bold",
54 | },
55 | container: {
56 | flex: 1,
57 | marginBottom: 20,
58 | },
59 | heading: {
60 | color: "rgb(102, 102, 102)",
61 | fontSize: 15,
62 | margin: "4px 0",
63 | },
64 | image: {
65 | width: "100%",
66 | },
67 | title: {
68 | fontStyle: "italic",
69 | fontWeight: "normal",
70 | },
71 | })
72 |
73 | export default Relay.createContainer(Artwork, {
74 | fragments: {
75 | artwork: () => Relay.QL`
76 | fragment on Artwork {
77 | title
78 | artists {
79 | name
80 | }
81 | image {
82 | url
83 | }
84 | }
85 | `,
86 | },
87 | })
88 |
--------------------------------------------------------------------------------
/app/containers/react-aphrodite/artist/browser.tsx:
--------------------------------------------------------------------------------
1 | import IsomorphicRelay from "isomorphic-relay"
2 | import React from "react"
3 | import ReactDOM from "react-dom"
4 |
5 | import { StyleSheet } from "aphrodite"
6 |
7 | import { artsyRelayEnvironment } from "../../../relay/config"
8 | import { ArtistQueryConfig } from "../../../relay/root_queries"
9 |
10 | import Artist from "./index"
11 |
12 | declare var document: any
13 | declare var window: any
14 |
15 | StyleSheet.rehydrate(window.STYLE_SHEET)
16 |
17 | const rootElement = document.getElementById("root")
18 |
19 | const environment = artsyRelayEnvironment()
20 | IsomorphicRelay.injectPreparedData(environment, window.ARTIST_PROPS)
21 |
22 | IsomorphicRelay.prepareInitialRender({
23 | Container: Artist,
24 | queryConfig: new ArtistQueryConfig({ artistID: window.ARTIST_ID }),
25 | environment,
26 | }).then(props => {
27 | ReactDOM.render(, rootElement)
28 | })
29 |
--------------------------------------------------------------------------------
/app/containers/react-aphrodite/artist/grid.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as Relay from "react-relay"
3 |
4 | import { css, StyleSheet } from "aphrodite/no-important"
5 |
6 | import Artwork from "./artwork"
7 |
8 | interface Props {
9 | sectionMargin: number,
10 | itemMargin: number,
11 | artworks: any[]
12 | }
13 |
14 | interface State {
15 | sectionCount: number,
16 | }
17 |
18 | export class Grid extends React.Component {
19 | // state: {
20 | // sectionDimension: number,
21 | // sectionCount: number,
22 | // };
23 |
24 | static defaultProps = {
25 | // sectionDirection: "column",
26 | itemMargin: 20,
27 | sectionMargin: 20,
28 | }
29 |
30 | constructor(props) {
31 | super(props)
32 | this.state = {
33 | // sectionDimension: 0,
34 | sectionCount: 3,
35 | }
36 |
37 | // this.onLayout = this.onLayout.bind(this)
38 | }
39 |
40 | // layoutState(currentLayout) : Object {
41 | // const width = currentLayout.width
42 | // const isPad = width > 600
43 | // const isPadHorizontal = width > 900
44 |
45 | // const sectionCount = isPad ? (isPadHorizontal ? 4 : 3) : 2
46 | // const sectionMargins = this.props.sectionMargin * (sectionCount - 1)
47 | // const sectionDimension = (currentLayout.width - sectionMargins) / sectionCount
48 |
49 | // return { sectionCount: sectionCount,
50 | // sectionDimension: sectionDimension,
51 | // }
52 | // }
53 |
54 | // onLayout = (event: LayoutEvent) => {
55 | // const layout = event.nativeEvent.layout
56 | // const newLayoutState = this.layoutState(layout)
57 | // if (layout.width > 0) {
58 | // this.setState(newLayoutState)
59 | // }
60 | // }
61 |
62 | sectionedArtworks() {
63 | const sectionedArtworks = []
64 | const sectionRatioSums = []
65 | for (let i = 0; i < this.state.sectionCount; i++) {
66 | sectionedArtworks.push([])
67 | sectionRatioSums.push(0)
68 | }
69 |
70 | const artworks = this.props.artworks
71 | for (const artwork of artworks) {
72 |
73 | if (artwork.image) {
74 | let lowestRatioSum = Number.MAX_VALUE
75 | let sectionIndex: number | null = null
76 |
77 | for (let j = 0; j < sectionRatioSums.length; j++) {
78 | const ratioSum = sectionRatioSums[j]
79 | if (ratioSum < lowestRatioSum) {
80 | sectionIndex = j
81 | lowestRatioSum = ratioSum
82 | }
83 | }
84 |
85 | if (sectionIndex != null) {
86 | const section = sectionedArtworks[sectionIndex]
87 | section.push(artwork)
88 |
89 | // total section aspect ratio
90 | const aspectRatio = artwork.image.aspect_ratio || 1
91 | sectionRatioSums[sectionIndex] += (1 / aspectRatio)
92 | }
93 | }
94 | }
95 | return sectionedArtworks
96 | }
97 |
98 | renderSections() {
99 | // const spacerStyle = {
100 | // height: this.props.itemMargin,
101 | // }
102 | const sectionedArtworks = this.sectionedArtworks()
103 | const sections = []
104 | for (let i = 0; i < this.state.sectionCount; i++) {
105 | const artworkComponents = []
106 | const artworks = sectionedArtworks[i]
107 | for (let j = 0; j < artworks.length; j++) {
108 | const artwork = artworks[j]
109 | artworkComponents.push()
110 | if (j < artworks.length - 1) {
111 | // artworkComponents.push()
112 | artworkComponents.push()
113 | }
114 | }
115 |
116 | // const sectionSpecificStlye = {
117 | // width: this.state.sectionDimension,
118 | // marginRight: (i === this.state.sectionCount - 1 ? 0 : this.props.sectionMargin),
119 | // }
120 | sections.push(
121 | //
122 |
{artworkComponents}
,
123 | )
124 | }
125 | return sections
126 | }
127 |
128 | render() {
129 | return
{this.renderSections()}
130 | }
131 | }
132 |
133 | // .grid {
134 | // display: flex;
135 | // flex-direction: row;
136 | // }
137 |
138 | // .grid .column {
139 | // flex-direction: column;
140 | // flex: 1 0 0px;
141 | // max-width: 300px;
142 | // margin-right: 20px;
143 | // }
144 |
145 | const styles = StyleSheet.create({
146 | column: {
147 | flex: "1 0 0px",
148 | flexDirection: "column",
149 | marginRight: 20,
150 | maxWidth: 300,
151 | },
152 | container: {
153 | display: "flex",
154 | flexDirection: "row",
155 | },
156 | })
157 |
158 | export default Relay.createContainer(Grid, {
159 | fragments: {
160 | artworks: () => Relay.QL`
161 | fragment on Artwork @relay(plural: true) {
162 | __id
163 | image {
164 | aspect_ratio
165 | }
166 | ${Artwork.getFragment("artwork")}
167 | }
168 | `,
169 | },
170 | })
171 |
--------------------------------------------------------------------------------
/app/containers/react-aphrodite/artist/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as Relay from "react-relay"
3 |
4 | import ArtistHeader from "../../../components/artist/header"
5 | import Grid from "./grid"
6 |
7 | interface Props {
8 | artist: any
9 | }
10 | interface State {
11 | following: boolean | null,
12 | followersCount: number,
13 | }
14 |
15 | export class Artist extends React.Component
{
16 | render() {
17 | return (
18 |
23 | )
24 | }
25 | }
26 |
27 | export default Relay.createContainer(Artist, {
28 | fragments: {
29 | artist: () => Relay.QL`
30 | fragment on Artist {
31 | name
32 | counts {
33 | artworks,
34 | partner_shows
35 | articles
36 | }
37 | artworks(size: 30) {
38 | ${Grid.getFragment("artworks")}
39 | }
40 | ${ArtistHeader.getFragment("artist")}
41 | }
42 | `,
43 | },
44 | })
45 |
--------------------------------------------------------------------------------
/app/containers/react-inline-css/artist/artwork.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as Relay from "react-relay"
3 |
4 | interface Props {
5 | artwork: any
6 | }
7 | interface State {
8 | }
9 |
10 | export class Artwork extends React.Component {
11 | render() {
12 | const names = this.props.artwork.artists.map(artist => artist.name)
13 | return (
14 |
15 |

16 |
{names.join(", ")}
17 |
{this.props.artwork.title}
18 |
19 | )
20 | }
21 | }
22 |
23 | // .grid .column .artwork {
24 | // flex: 1;
25 | // margin-bottom: 20px;
26 | // }
27 |
28 | // .artwork img {
29 | // width: 100%;
30 | // }
31 |
32 | // .artwork h3,h4 {
33 | // margin: 4px 0;
34 | // color: rgb(102, 102, 102);
35 | // font-size: 15px;
36 | // }
37 |
38 | // .artwork h3 {
39 | // font-style: normal;
40 | // font-weight: bold;
41 | // }
42 |
43 | // .artwork h4 {
44 | // font-style: italic;
45 | // font-weight: normal
46 | // }
47 |
48 | const styles = {
49 | artists: {
50 | fontStyle: "normal",
51 | fontWeight: "bold",
52 | },
53 | container: {
54 | flex: 1,
55 | marginBottom: 20,
56 | },
57 | heading: {
58 | color: "rgb(102, 102, 102)",
59 | fontSize: 15,
60 | margin: "4px 0",
61 | },
62 | image: {
63 | width: "100%",
64 | },
65 | title: {
66 | fontStyle: "italic",
67 | fontWeight: "normal",
68 | },
69 | }
70 |
71 | export default Relay.createContainer(Artwork, {
72 | fragments: {
73 | artwork: () => Relay.QL`
74 | fragment on Artwork {
75 | title
76 | artists {
77 | name
78 | }
79 | image {
80 | url
81 | }
82 | }
83 | `,
84 | },
85 | })
86 |
--------------------------------------------------------------------------------
/app/containers/react-inline-css/artist/browser.tsx:
--------------------------------------------------------------------------------
1 | import IsomorphicRelay from "isomorphic-relay"
2 | import React from "react"
3 | import ReactDOM from "react-dom"
4 |
5 | import { artsyRelayEnvironment } from "../../../relay/config"
6 | import { ArtistQueryConfig } from "../../../relay/root_queries"
7 |
8 | import Artist from "./index"
9 |
10 | const rootElement = document.getElementById("root")
11 |
12 | declare var window: any
13 |
14 | const environment = artsyRelayEnvironment()
15 | IsomorphicRelay.injectPreparedData(environment, window.ARTIST_PROPS)
16 |
17 | IsomorphicRelay.prepareInitialRender({
18 | Container: Artist,
19 | queryConfig: new ArtistQueryConfig({ artistID: window.ARTIST_ID }),
20 | environment,
21 | }).then(props => {
22 | ReactDOM.render(, rootElement)
23 | })
24 |
--------------------------------------------------------------------------------
/app/containers/react-inline-css/artist/grid.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as Relay from "react-relay"
3 |
4 | import Artwork from "./artwork"
5 |
6 | interface Props {
7 | sectionMargin: number,
8 | itemMargin: number,
9 | artworks: any[]
10 | }
11 |
12 | interface State {
13 | sectionCount: number,
14 | }
15 |
16 | export class Grid extends React.Component {
17 | // state: {
18 | // sectionDimension: number,
19 | // sectionCount: number,
20 | // };
21 |
22 | static defaultProps = {
23 | // sectionDirection: "column",
24 | itemMargin: 20,
25 | sectionMargin: 20,
26 | }
27 |
28 | constructor(props) {
29 | super(props)
30 | this.state = {
31 | // sectionDimension: 0,
32 | sectionCount: 3,
33 | }
34 |
35 | // this.onLayout = this.onLayout.bind(this)
36 | }
37 |
38 | // layoutState(currentLayout) : Object {
39 | // const width = currentLayout.width
40 | // const isPad = width > 600
41 | // const isPadHorizontal = width > 900
42 |
43 | // const sectionCount = isPad ? (isPadHorizontal ? 4 : 3) : 2
44 | // const sectionMargins = this.props.sectionMargin * (sectionCount - 1)
45 | // const sectionDimension = (currentLayout.width - sectionMargins) / sectionCount
46 |
47 | // return { sectionCount: sectionCount,
48 | // sectionDimension: sectionDimension,
49 | // }
50 | // }
51 |
52 | // onLayout = (event: LayoutEvent) => {
53 | // const layout = event.nativeEvent.layout
54 | // const newLayoutState = this.layoutState(layout)
55 | // if (layout.width > 0) {
56 | // this.setState(newLayoutState)
57 | // }
58 | // }
59 |
60 | sectionedArtworks() {
61 | const sectionedArtworks = []
62 | const sectionRatioSums = []
63 | for (let i = 0; i < this.state.sectionCount; i++) {
64 | sectionedArtworks.push([])
65 | sectionRatioSums.push(0)
66 | }
67 |
68 | const artworks = this.props.artworks
69 | for (const artwork of artworks) {
70 |
71 | if (artwork.image) {
72 | let lowestRatioSum = Number.MAX_VALUE
73 | let sectionIndex: number | null = null
74 |
75 | for (let j = 0; j < sectionRatioSums.length; j++) {
76 | const ratioSum = sectionRatioSums[j]
77 | if (ratioSum < lowestRatioSum) {
78 | sectionIndex = j
79 | lowestRatioSum = ratioSum
80 | }
81 | }
82 |
83 | if (sectionIndex != null) {
84 | const section = sectionedArtworks[sectionIndex]
85 | section.push(artwork)
86 |
87 | // total section aspect ratio
88 | const aspectRatio = artwork.image.aspect_ratio || 1
89 | sectionRatioSums[sectionIndex] += (1 / aspectRatio)
90 | }
91 | }
92 | }
93 | return sectionedArtworks
94 | }
95 |
96 | renderSections() {
97 | // const spacerStyle = {
98 | // height: this.props.itemMargin,
99 | // }
100 | const sectionedArtworks = this.sectionedArtworks()
101 | const sections = []
102 | for (let i = 0; i < this.state.sectionCount; i++) {
103 | const artworkComponents = []
104 | const artworks = sectionedArtworks[i]
105 | for (let j = 0; j < artworks.length; j++) {
106 | const artwork = artworks[j]
107 | artworkComponents.push()
108 | if (j < artworks.length - 1) {
109 | // artworkComponents.push()
110 | artworkComponents.push()
111 | }
112 | }
113 |
114 | // const sectionSpecificStlye = {
115 | // width: this.state.sectionDimension,
116 | // marginRight: (i === this.state.sectionCount - 1 ? 0 : this.props.sectionMargin),
117 | // }
118 | sections.push(
119 | //
120 |
121 | {artworkComponents}
122 |
123 | )
124 | }
125 | return sections
126 | }
127 |
128 | render() {
129 | return
{this.renderSections()}
130 | }
131 | }
132 |
133 | // .grid {
134 | // display: flex;
135 | // flex-direction: row;
136 | // }
137 |
138 | // .grid .column {
139 | // flex-direction: column;
140 | // flex: 1 0 0px;
141 | // max-width: 300px;
142 | // margin-right: 20px;
143 | // }
144 |
145 | const styles = {
146 | column: {
147 | flex: "1 0 0px",
148 | flexDirection: "column",
149 | marginRight: 20,
150 | maxWidth: 300,
151 | },
152 | container: {
153 | display: "flex",
154 | flexDirection: "row",
155 | },
156 | }
157 |
158 | export default Relay.createContainer(Grid, {
159 | fragments: {
160 | artworks: () => Relay.QL`
161 | fragment on Artwork @relay(plural: true) {
162 | __id
163 | image {
164 | aspect_ratio
165 | }
166 | ${Artwork.getFragment("artwork")}
167 | }
168 | `,
169 | },
170 | })
171 |
--------------------------------------------------------------------------------
/app/containers/react-inline-css/artist/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as Relay from "react-relay"
3 |
4 | import ArtistHeader from "../../../components/artist/header"
5 | import Grid from "./grid"
6 |
7 | interface Props {
8 | artist: any
9 | }
10 | interface State {
11 | following: boolean | null,
12 | followersCount: number,
13 | }
14 |
15 | export class Artist extends React.Component
{
16 | render() {
17 | return (
18 |
24 | )
25 | }
26 | }
27 |
28 | export default Relay.createContainer(Artist, {
29 | fragments: {
30 | artist: () => Relay.QL`
31 | fragment on Artist {
32 | name
33 | counts {
34 | artworks,
35 | partner_shows
36 | articles
37 | }
38 | artworks(size: 30) {
39 | ${Grid.getFragment("artworks")}
40 | }
41 | ${ArtistHeader.getFragment("artist")}
42 | }
43 | `,
44 | },
45 | })
46 |
--------------------------------------------------------------------------------
/app/containers/react-jss/artist/artwork.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as Relay from "react-relay"
3 |
4 | import injectSheet from "react-jss"
5 |
6 | interface Props {
7 | artwork: any
8 | sheet: any
9 | }
10 | interface State {
11 | }
12 |
13 | export class Artwork extends React.Component {
14 | render() {
15 | const classes = this.props.sheet.classes
16 | const names = this.props.artwork.artists.map(artist => artist.name)
17 | return (
18 |
19 |

20 |
{names.join(", ")}
21 |
{this.props.artwork.title}
22 |
23 | )
24 | }
25 | }
26 |
27 | // .grid .column .artwork {
28 | // flex: 1;
29 | // margin-bottom: 20px;
30 | // }
31 |
32 | // .artwork img {
33 | // width: 100%;
34 | // }
35 |
36 | // .artwork h3,h4 {
37 | // margin: 4px 0;
38 | // color: rgb(102, 102, 102);
39 | // font-size: 15px;
40 | // }
41 |
42 | // .artwork h3 {
43 | // font-style: normal;
44 | // font-weight: bold;
45 | // }
46 |
47 | // .artwork h4 {
48 | // font-style: italic;
49 | // font-weight: normal
50 | // }
51 |
52 | const styles = {
53 | artists: {
54 | fontStyle: "normal",
55 | fontWeight: "bold",
56 | },
57 | container: {
58 | flex: 1,
59 | marginBottom: 20,
60 | },
61 | heading: {
62 | color: "rgb(102, 102, 102)",
63 | fontSize: 15,
64 | margin: "4px 0",
65 | },
66 | image: {
67 | width: "100%",
68 | },
69 | title: {
70 | fontStyle: "italic",
71 | fontWeight: "normal",
72 | },
73 | }
74 |
75 | export default Relay.createContainer(injectSheet(styles)(Artwork), {
76 | fragments: {
77 | artwork: () => Relay.QL`
78 | fragment on Artwork {
79 | title
80 | artists {
81 | name
82 | }
83 | image {
84 | url
85 | }
86 | }
87 | `,
88 | },
89 | })
90 |
--------------------------------------------------------------------------------
/app/containers/react-jss/artist/browser.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom"
3 |
4 | import IsomorphicRelay from "isomorphic-relay"
5 |
6 | import { artsyRelayEnvironment } from "../../../relay/config"
7 | import { ArtistQueryConfig } from "../../../relay/root_queries"
8 |
9 | import Artist from "./index"
10 |
11 | const rootElement = document.getElementById("root")
12 | declare var window: any
13 |
14 | const environment = artsyRelayEnvironment()
15 | IsomorphicRelay.injectPreparedData(environment, window.ARTIST_PROPS)
16 |
17 | IsomorphicRelay.prepareInitialRender({
18 | Container: Artist,
19 | queryConfig: new ArtistQueryConfig({ artistID: window.ARTIST_ID }),
20 | environment,
21 | }).then(props => {
22 | ReactDOM.render(, rootElement)
23 | })
24 |
--------------------------------------------------------------------------------
/app/containers/react-jss/artist/grid.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as Relay from "react-relay"
3 |
4 | import injectSheet from "react-jss"
5 |
6 | import Artwork from "./artwork"
7 |
8 | interface Props {
9 | sectionMargin: number,
10 | itemMargin: number,
11 | artworks: any[],
12 | sheet: any
13 | }
14 | interface State {
15 | sectionCount: number,
16 | }
17 |
18 | export class Grid extends React.Component {
19 | // sectionDimension: number,
20 | // sectionCount: number,
21 | // };
22 |
23 | static defaultProps = {
24 | // sectionDirection: "column",
25 | itemMargin: 20,
26 | sectionMargin: 20,
27 | }
28 |
29 | constructor(props) {
30 | super(props)
31 | this.state = {
32 | // sectionDimension: 0,
33 | sectionCount: 3,
34 | }
35 |
36 | // this.onLayout = this.onLayout.bind(this)
37 | }
38 |
39 | // layoutState(currentLayout) : Object {
40 | // const width = currentLayout.width
41 | // const isPad = width > 600
42 | // const isPadHorizontal = width > 900
43 |
44 | // const sectionCount = isPad ? (isPadHorizontal ? 4 : 3) : 2
45 | // const sectionMargins = this.props.sectionMargin * (sectionCount - 1)
46 | // const sectionDimension = (currentLayout.width - sectionMargins) / sectionCount
47 |
48 | // return { sectionCount: sectionCount,
49 | // sectionDimension: sectionDimension,
50 | // }
51 | // }
52 |
53 | // onLayout = (event: LayoutEvent) => {
54 | // const layout = event.nativeEvent.layout
55 | // const newLayoutState = this.layoutState(layout)
56 | // if (layout.width > 0) {
57 | // this.setState(newLayoutState)
58 | // }
59 | // }
60 |
61 | sectionedArtworks() {
62 | const sectionedArtworks = []
63 | const sectionRatioSums = []
64 | for (let i = 0; i < this.state.sectionCount; i++) {
65 | sectionedArtworks.push([])
66 | sectionRatioSums.push(0)
67 | }
68 |
69 | const artworks = this.props.artworks
70 | for (const artwork of artworks) {
71 |
72 | if (artwork.image) {
73 | let lowestRatioSum = Number.MAX_VALUE
74 | let sectionIndex: number | null = null
75 |
76 | for (let j = 0; j < sectionRatioSums.length; j++) {
77 | const ratioSum = sectionRatioSums[j]
78 | if (ratioSum < lowestRatioSum) {
79 | sectionIndex = j
80 | lowestRatioSum = ratioSum
81 | }
82 | }
83 |
84 | if (sectionIndex != null) {
85 | const section = sectionedArtworks[sectionIndex]
86 | section.push(artwork)
87 |
88 | // total section aspect ratio
89 | const aspectRatio = artwork.image.aspect_ratio || 1
90 | sectionRatioSums[sectionIndex] += (1 / aspectRatio)
91 | }
92 | }
93 | }
94 | return sectionedArtworks
95 | }
96 |
97 | renderSections() {
98 | // const spacerStyle = {
99 | // height: this.props.itemMargin,
100 | // }
101 | const sectionedArtworks = this.sectionedArtworks()
102 | const sections = []
103 | for (let i = 0; i < this.state.sectionCount; i++) {
104 | const artworkComponents = []
105 | const artworks = sectionedArtworks[i]
106 | for (let j = 0; j < artworks.length; j++) {
107 | const artwork = artworks[j]
108 | artworkComponents.push()
109 | if (j < artworks.length - 1) {
110 | // artworkComponents.push()
111 | artworkComponents.push()
112 | }
113 | }
114 |
115 | // const sectionSpecificStlye = {
116 | // width: this.state.sectionDimension,
117 | // marginRight: (i === this.state.sectionCount - 1 ? 0 : this.props.sectionMargin),
118 | // }
119 | sections.push(
120 | //
121 |
122 | {artworkComponents}
123 |
124 | )
125 | }
126 | return sections
127 | }
128 |
129 | render() {
130 | return
{this.renderSections()}
131 | }
132 | }
133 |
134 | // .grid {
135 | // display: flex;
136 | // flex-direction: row;
137 | // }
138 |
139 | // .grid .column {
140 | // flex-direction: column;
141 | // flex: 1 0 0px;
142 | // max-width: 300px;
143 | // margin-right: 20px;
144 | // }
145 |
146 | const styles = {
147 | column: {
148 | flex: "1 0 0px",
149 | flexDirection: "column",
150 | marginRight: 20,
151 | maxWidth: 300,
152 | },
153 | container: {
154 | display: "flex",
155 | flexDirection: "row",
156 | },
157 | }
158 |
159 | export default Relay.createContainer(injectSheet(styles)(Grid), {
160 | fragments: {
161 | artworks: () => Relay.QL`
162 | fragment on Artwork @relay(plural: true) {
163 | __id
164 | image {
165 | aspect_ratio
166 | }
167 | ${Artwork.getFragment("artwork")}
168 | }
169 | `,
170 | },
171 | })
172 |
--------------------------------------------------------------------------------
/app/containers/react-jss/artist/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as Relay from "react-relay"
3 |
4 | import ArtistHeader from "../../../components/artist/header"
5 | import Grid from "./grid"
6 |
7 | interface Props {
8 | artist: any
9 | }
10 | interface State {
11 | following: boolean | null,
12 | followersCount: number,
13 | }
14 |
15 | export class Artist extends React.Component
{
16 | render() {
17 | return (
18 |
23 | )
24 | }
25 | }
26 |
27 | export default Relay.createContainer(Artist, {
28 | fragments: {
29 | artist: () => Relay.QL`
30 | fragment on Artist {
31 | name
32 | counts {
33 | artworks,
34 | partner_shows
35 | articles
36 | }
37 | artworks(size: 30) {
38 | ${Grid.getFragment("artworks")}
39 | }
40 | ${ArtistHeader.getFragment("artist")}
41 | }
42 | `,
43 | },
44 | })
45 |
--------------------------------------------------------------------------------
/app/containers/react-native-web/artist/artwork.tsx:
--------------------------------------------------------------------------------
1 | import { map } from "lodash"
2 | import * as React from "react"
3 | import { Image, StyleSheet, TextStyle, TouchableWithoutFeedback, View, ViewStyle } from "react-native-web"
4 | import * as Relay from "react-relay"
5 |
6 | // import ImageView from '../opaque_image_view'
7 | const ImageView = (props: { style: ViewStyle, aspectRatio: number, imageURL: string }) => {
8 | return
9 | }
10 |
11 | import GQL from "../../../gql"
12 | import SerifText from "./text/serif"
13 |
14 | import * as colors from "../../../../data/colors.json"
15 |
16 | import SwitchBoard from "./switch_board"
17 |
18 | interface Props {
19 | artwork: GQL.ArtworkType,
20 | }
21 |
22 | class Artwork extends React.Component {
23 | handleTap() {
24 | SwitchBoard.presentNavigationViewController(this, this.props.artwork.href)
25 | }
26 |
27 | render() {
28 | const artwork = this.props.artwork
29 | return (
30 |
31 |
32 |
33 | {this.artists()}
34 | {this.artworkTitle()}
35 | {this.props.artwork.partner && {this.props.artwork.partner.name}}
36 | {this.saleMessage()}
37 |
38 |
39 | )
40 | }
41 |
42 | artists() {
43 | const artists = this.props.artwork.artists
44 | if (artists && artists.length > 0) {
45 | return (
46 |
47 | {map(artists, "name").join(", ")}
48 |
49 | )
50 | } else {
51 | return null
52 | }
53 | }
54 |
55 | artworkTitle() {
56 | const artwork = this.props.artwork
57 | if (artwork.title) {
58 | return (
59 |
60 | {artwork.title}
61 | {artwork.date ? (", " + artwork.date) : ""}
62 |
63 | )
64 | } else {
65 | return null
66 | }
67 | }
68 |
69 | saleMessage() {
70 | const artwork = this.props.artwork
71 | if (artwork.is_in_auction) {
72 | return (
73 |
74 | {/**/}
75 | Bid now
76 |
77 | )
78 | } else {
79 | return artwork.sale_message && {artwork.sale_message}
80 | }
81 | }
82 | }
83 |
84 | interface Styles {
85 | artist: TextStyle,
86 | image: ViewStyle,
87 | text: TextStyle,
88 | title: TextStyle,
89 | }
90 |
91 | const styles = StyleSheet.create({
92 | artist: {
93 | fontWeight: "bold",
94 | },
95 | image: {
96 | marginBottom: 10,
97 | },
98 | text: {
99 | color: colors["gray-semibold"],
100 | fontSize: 12,
101 | },
102 | title: {
103 | fontStyle: "italic",
104 | }
105 | })
106 |
107 | export default Relay.createContainer(Artwork, {
108 | fragments: {
109 | artwork: () => Relay.QL`
110 | fragment on Artwork {
111 | title
112 | date
113 | sale_message
114 | is_in_auction
115 | image {
116 | url(version: "large")
117 | aspect_ratio
118 | }
119 | artists {
120 | name
121 | }
122 | partner {
123 | name
124 | }
125 | href
126 | }
127 | `
128 | }
129 | })
130 |
--------------------------------------------------------------------------------
/app/containers/react-native-web/artist/browser.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ReactDOM from "react-dom"
3 |
4 | import IsomorphicRelay from "isomorphic-relay"
5 |
6 | import { artsyRelayEnvironment } from "../../../relay/config"
7 | import { ArtistQueryConfig } from "../../../relay/root_queries"
8 |
9 | import Artist from "./index"
10 |
11 | const rootElement = document.getElementById("root")
12 |
13 | declare var window: any
14 |
15 | const environment = artsyRelayEnvironment()
16 | IsomorphicRelay.injectPreparedData(environment, window.ARTIST_PROPS)
17 |
18 | IsomorphicRelay.prepareInitialRender({
19 | Container: Artist,
20 | queryConfig: new ArtistQueryConfig({ artistID: window.ARTIST_ID }),
21 | environment,
22 | }).then(props => {
23 | ReactDOM.render(, rootElement)
24 | })
25 |
--------------------------------------------------------------------------------
/app/containers/react-native-web/artist/grid.tsx:
--------------------------------------------------------------------------------
1 | // 1. Get first layout pass of grid view so we have a total width and calculate the column width (componentDidMount?).
2 | // 2. Possibly do artwork column layout now, as we can do so based just on the aspect ratio, assuming the text height
3 | // won't be too different between artworks.
4 | // 3. Get artwork heights by either:
5 | // - calculating the item size upfront with aspect ratio and a static height for the text labels.
6 | // - leting the artwork component do a layout pass and calculate its own height based on the column width.
7 | // 4. Update height of grid to encompass all items.
8 |
9 | import * as React from "react"
10 | import { Dimensions, ScrollView, StyleSheet, View, ViewStyle } from "react-native-web"
11 | import * as Relay from "react-relay"
12 |
13 | import Artwork from "./artwork"
14 | // import Spinner from '../spinner'
15 | const Spinner = ({ spinnerColor = null, style }) =>
16 |
17 | import GQL from "../../../gql"
18 | import metaphysics from "./metaphysics"
19 |
20 | import { get, isEqual } from "lodash"
21 |
22 | // const isPad = Dimensions.get('window').width > 700
23 | const isPad = true
24 |
25 | const PageSize = 10
26 | const PageEndThreshold = 1000
27 |
28 | /**
29 | * TODOs:
30 | * - currently all the code assumes column layout
31 | * - do no invert aspect ratios in row layout
32 | * - deal with edge-cases when calculating in which section an artwork should go
33 | * - see ARMasonryCollectionViewLayout for details on how to deal with last works sticking out
34 | * - the calculation currently only takes into account the size of the image, not if e.g. the sale message is present
35 | */
36 |
37 | interface Props {
38 | /** The direction for the grid, currently only 'column' is supported . */
39 | sectionDirection: string,
40 |
41 | /** The arity of the number of sections (e.g. columns) to show */
42 | sectionCount: number,
43 |
44 | /** The inset margin for the whole grid */
45 | sectionMargin: number,
46 |
47 | /** The per-item margin */
48 | itemMargin: number,
49 |
50 | /** All the artworks for the grid */
51 | artworks: GQL.ArtworkType[],
52 |
53 | /** A non-optional object for the request state.
54 | * When this changes, it will reset the component.
55 | * We recommend sending in your query params.
56 | * This gets passed back to your request query below.
57 | */
58 | queryState: any,
59 |
60 | /** A non-optional callback to generate the GraphQL query. */
61 | queryForPage: (component: InfiniteScrollArtworksGrid, page: number, queryState: any) => string,
62 |
63 | /** A callback that is called once all artworks have been queried. */
64 | onComplete?: () => void,
65 |
66 | /** When you get the results from the GraphQL, this is the keypath from
67 | * which the artworks can be found, applied via `_.get()`
68 | */
69 | queryArtworksKeypath: string,
70 | }
71 |
72 | interface State {
73 | sectionDimension: number,
74 | artworks: GQL.ArtworkType[],
75 | page: number,
76 | completed: boolean,
77 | fetchingNextPage: boolean,
78 | sentEndForContentLength: null | number
79 | }
80 |
81 | class InfiniteScrollArtworksGrid extends React.Component {
82 | static defaultProps = {
83 | itemMargin: 20,
84 | sectionCount: isPad ? 3 : 2,
85 | sectionDirection: "column",
86 | sectionMargin: 20,
87 | }
88 |
89 | constructor(props) {
90 | super(props)
91 | this.state = {
92 | artworks: this.props.artworks,
93 | completed: false,
94 | fetchingNextPage: false,
95 | page: this.props.artworks.length ? 1 : 0,
96 | sectionDimension: 0,
97 | sentEndForContentLength: null,
98 | }
99 | }
100 |
101 | // Initial setup
102 | componentWillMount() {
103 | if (this.state.artworks.length === 0) {
104 | this.fetchNextPage()
105 | }
106 | }
107 |
108 | // Reset detection
109 | componentDidUpdate() {
110 | if (this.state.artworks.length === 0) {
111 | this.fetchNextPage()
112 | }
113 | }
114 |
115 | /** Download new Artworks, and update the internal state accordingly */
116 | fetchNextPage() {
117 | if (this.state.fetchingNextPage || this.state.completed) {
118 | return
119 | }
120 |
121 | const nextPage = this.state.page + 1
122 | const queryState = this.props.queryState
123 | const query = this.props.queryForPage(this, nextPage, queryState)
124 |
125 | metaphysics(query)
126 | .then((results) => {
127 | // this.debugLog(query, results, null)
128 |
129 | const artworks: GQL.ArtworkType[] = get(results, this.props.queryArtworksKeypath)
130 | if (artworks === undefined) {
131 | console.error("Your queryArtworksKeypath could be wrong in the infinite_scroll_grid")
132 | }
133 | const completed = artworks.length < PageSize
134 | if (completed && this.props.onComplete) {
135 | this.props.onComplete()
136 | }
137 | this.setState({
138 | artworks: this.state.artworks.concat(artworks),
139 | completed,
140 | fetchingNextPage: false,
141 | page: nextPage,
142 | } as State) // TODO Required until this is merged: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/13155
143 | })
144 | .catch((error) => {
145 | this.setState({ fetchingNextPage: false } as State)
146 | // this.debugLog(query, null, error)
147 | })
148 |
149 | this.setState({ fetchingNextPage: true } as State)
150 | }
151 |
152 | /** A simplified version of the Relay debugging logs for infinite scrolls */
153 | // debugLog(query: string, response?: any, error?: any) {
154 | // if (__DEV__ && global.originalXMLHttpRequest !== undefined) {
155 | // var groupName = '%c[' + this.state.page + '] ' + 'Infinite scroll request'
156 | // console.groupCollapsed(groupName, 'color:' + (response ? 'black' : 'red') + ';')
157 | // console.log('Query:\n', query)
158 | // if (response) {
159 | // console.log('Response:\n', response)
160 | // }
161 | // console.groupEnd()
162 | // if (error) {
163 | // console.error('Error:\n', error)
164 | // }
165 | // }
166 | // }
167 |
168 | shouldComponentUpdate(nextProps, nextState) {
169 | if (this.props === undefined) {
170 | return true
171 | }
172 |
173 | if (!isEqual(this.props.queryState, nextProps.queryState)) {
174 | // Empty the artworks, and reset the state as we have new a query object
175 | this.setState({
176 | artworks: [],
177 | completed: false,
178 | fetchingNextPage: false,
179 | page: 0,
180 | } as State)
181 | return true
182 | }
183 |
184 | return (!isEqual(this.props, nextProps) || !isEqual(this.state, nextState))
185 | }
186 |
187 | // onLayout = (event: React.LayoutChangeEvent) => {
188 | // const layout = event.nativeEvent.layout
189 | // if (layout.width > 0) {
190 | // // This is the sum of all margins in between sections, so do not count to the right of last column.
191 | // const sectionMargins = this.props.sectionMargin * (this.props.sectionCount - 1)
192 | // this.setState({ sectionDimension: (layout.width - sectionMargins) / this.props.sectionCount } as State)
193 | // }
194 | // }
195 |
196 | sectionedArtworks() {
197 | const sectionedArtworks: GQL.ArtworkType[][] = []
198 | const sectionRatioSums: number[] = []
199 | for (let i = 0; i < this.props.sectionCount; i++) {
200 | sectionedArtworks.push([])
201 | sectionRatioSums.push(0)
202 | }
203 |
204 | const artworks = this.state.artworks
205 | for (let i = 0; i < artworks.length; i++) {
206 | const artwork = this.state.artworks[i]
207 |
208 | // There are artworks without images and other ‘issues’. Like Force we’re just going to reject those for now.
209 | // See: https://github.com/artsy/eigen/issues/1667
210 | //
211 | if (artwork.image) {
212 | // Find section with lowest *inverted* aspect ratio sum, which is the shortest column.
213 | let lowestRatioSum = Number.MAX_VALUE // Start higher, so we always find a
214 | let sectionIndex: number = null
215 | for (let j = 0; j < sectionRatioSums.length; j++) {
216 | const ratioSum = sectionRatioSums[j]
217 | if (ratioSum < lowestRatioSum) {
218 | sectionIndex = j
219 | lowestRatioSum = ratioSum
220 | }
221 | }
222 |
223 | if (sectionIndex != null) {
224 | const section = sectionedArtworks[sectionIndex]
225 | section.push(artwork)
226 |
227 | // Keep track of total section aspect ratio
228 | const aspectRatio = artwork.image.aspect_ratio || 1 // Ensure we never divide by null/0
229 | // Invert the aspect ratio so that a lower value means a shorter section.
230 | sectionRatioSums[sectionIndex] += (1 / aspectRatio)
231 | }
232 | }
233 | }
234 |
235 | return sectionedArtworks
236 | }
237 |
238 | renderSections() {
239 | const spacerStyle = {
240 | height: this.props.itemMargin,
241 | }
242 |
243 | const sectionedArtworks = this.sectionedArtworks()
244 | const sections = []
245 | for (let i = 0; i < this.props.sectionCount; i++) {
246 | const artworkComponents = []
247 | const artworks = sectionedArtworks[i]
248 | for (let j = 0; j < artworks.length; j++) {
249 | const artwork = artworks[j]
250 | artworkComponents.push()
251 | // Setting a marginBottom on the artwork component didn’t work, so using a spacer view instead.
252 | if (j < artworks.length - 1) {
253 | artworkComponents.push(
254 |
255 | )
256 | }
257 | }
258 |
259 | const sectionSpecificStyle = {
260 | marginRight: (i === this.props.sectionCount - 1 ? 0 : this.props.sectionMargin),
261 | width: this.state.sectionDimension,
262 | }
263 |
264 | sections.push(
265 |
266 | {artworkComponents}
267 |
268 | )
269 | }
270 | return sections
271 | }
272 |
273 | // Lifted pretty much straight from RN’s ListView.js
274 | onScroll = (event: React.NativeSyntheticEvent) => {
275 | const scrollProperties = event.nativeEvent
276 | const contentLength = scrollProperties.contentSize.height
277 | if (contentLength !== this.state.sentEndForContentLength) {
278 | const offset = scrollProperties.contentOffset.y
279 | const visibleLength = scrollProperties.layoutMeasurement.height
280 | const distanceFromEnd = contentLength - visibleLength - offset
281 | if (distanceFromEnd < PageEndThreshold) {
282 | this.state.sentEndForContentLength = contentLength
283 | this.fetchNextPage()
284 | }
285 | }
286 | }
287 |
288 | render() {
289 | // const artworks = this.state.sectionDimension ? this.renderSections() : null
290 | const artworks = this.renderSections()
291 | return (
292 |
297 |
298 | {artworks}
299 |
300 | {this.state.fetchingNextPage ? : null}
301 |
302 | )
303 | }
304 | }
305 |
306 | interface Styles {
307 | container: ViewStyle,
308 | section: ViewStyle,
309 | spinner: ViewStyle,
310 | }
311 |
312 | const styles = StyleSheet.create({
313 | container: {
314 | display: "flex",
315 | flexDirection: "row",
316 | } as ViewStyle, // FIXME: Needed because `display` is not a prop that non-web RN has.
317 | section: {
318 | // flex: "1 0 0px",
319 | flex: 1,
320 | flexDirection: "column",
321 | marginRight: 20,
322 | maxWidth: 300,
323 | },
324 | spinner: {
325 | marginTop: 20,
326 | },
327 | })
328 |
329 | const InfiniteScrollArtworksGridContainer = Relay.createContainer(InfiniteScrollArtworksGrid, {
330 | fragments: {
331 | artworks: () => Relay.QL`
332 | fragment on Artwork @relay(plural: true) {
333 | __id
334 | image {
335 | aspect_ratio
336 | }
337 | ${Artwork.getFragment("artwork")}
338 | }
339 | `,
340 | }
341 | })
342 |
343 | // TODO: While we do pagination manually, we can’t actually use a Relay container around Artwork.
344 | const container: any = InfiniteScrollArtworksGridContainer
345 | container.artworksQuery = (artistID, filter, page) => {
346 | return `
347 | query {
348 | artist(id: "${artistID}") {
349 | artworks(sort: partner_updated_at_desc, filter: ${filter} size: ${PageSize}, page: ${page}) {
350 | __id
351 | title
352 | date
353 | sale_message
354 | is_in_auction
355 | image {
356 | url(version: "large")
357 | aspect_ratio
358 | }
359 | artist {
360 | name
361 | }
362 | partner {
363 | name
364 | }
365 | href
366 | }
367 | }
368 | }
369 | `
370 | }
371 |
372 | export default InfiniteScrollArtworksGridContainer
373 |
--------------------------------------------------------------------------------
/app/containers/react-native-web/artist/header.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Dimensions, FlexAlignType, StyleSheet, Text, TextStyle, View, ViewStyle } from "react-native-web"
3 | import * as Relay from "react-relay"
4 |
5 | // import Events from '../../native_modules/events'
6 |
7 | import * as colors from "../../../../data/colors.json"
8 |
9 | import GQL from "../../../gql"
10 |
11 | import InvertedButton from "./inverted_button"
12 | import Headline from "./text/headline"
13 | import SerifText from "./text/serif"
14 |
15 | // const isPad = Dimensions.get('window').width > 700
16 | const isPad = true
17 |
18 | interface Props {
19 | artist: GQL.ArtistType,
20 | }
21 |
22 | interface State {
23 | following: boolean | null,
24 | followersCount: number,
25 | }
26 |
27 | class Header extends React.Component {
28 | constructor(props) {
29 | super(props)
30 | this.state = { following: true, followersCount: props.artist.counts.follows }
31 | }
32 |
33 | // componentDidMount() {
34 | // ARTemporaryAPIModule.followStatusForArtist(this.props.artist._id, (error, following) => {
35 | // this.setState({ following: following })
36 | // })
37 | // }
38 |
39 | handleFollowChange = () => {
40 | const { following, followersCount } = this.state
41 | const newFollowersCount: number = following ? (followersCount - 1) : (followersCount + 1)
42 | // ARTemporaryAPIModule.setFollowArtistStatus(!this.state.following, this.props.artist._id, (error, following) => {
43 | // if (error) {
44 | // console.error(error)
45 | // } else {
46 | // Events.postEvent(this, {
47 | // name: following ? 'Follow artist' : 'Unfollow artist',
48 | // artist_id: this.props.artist._id,
49 | // artist_slug: this.props.artist.id,
50 | // // TODO At some point, this component might be on other screens.
51 | // source_screen: 'artist page',
52 | // })
53 | // }
54 | // this.setState({ following: following, followersCount: newFollowersCount })
55 | // })
56 | this.setState({ following: !following, followersCount: newFollowersCount })
57 | }
58 |
59 | render() {
60 | const artist = this.props.artist
61 | return (
62 |
63 |
64 | {artist.name}
65 |
66 | {this.renderByline()}
67 | {this.renderFollowersCount()}
68 | {this.renderFollowButton()}
69 |
70 | )
71 | }
72 |
73 | renderFollowButton() {
74 | if (this.state.following !== null) {
75 | return (
76 |
77 |
80 |
81 | )
82 | }
83 | }
84 |
85 | renderFollowersCount() {
86 | const count = this.state.followersCount
87 | const followerString = count + (count === 1 ? " Follower" : " Followers")
88 | return (
89 |
90 | {followerString}
91 |
92 | )
93 | }
94 |
95 | renderByline() {
96 | const artist = this.props.artist
97 | const bylineRequired = (artist.nationality || artist.birthday)
98 | if (bylineRequired) {
99 | return (
100 |
101 |
102 | {this.descriptiveString()}
103 |
104 |
105 | )
106 | } else {
107 | return null
108 | }
109 | }
110 |
111 | descriptiveString() {
112 | const artist = this.props.artist
113 | const descriptiveString = (artist.nationality || "") + this.birthdayString()
114 | return descriptiveString
115 | }
116 |
117 | birthdayString() {
118 | const birthday = this.props.artist.birthday
119 | if (!birthday) { return "" }
120 |
121 | const leadingSubstring = this.props.artist.nationality ? ", b." : ""
122 |
123 | if (birthday.includes("born")) {
124 | return birthday.replace("born", leadingSubstring)
125 | } else if (birthday.includes("Est.") || birthday.includes("Founded")) {
126 | return " " + birthday
127 | }
128 |
129 | return leadingSubstring + " " + birthday
130 | }
131 | }
132 |
133 | interface Styles {
134 | base: TextStyle,
135 | headline: TextStyle,
136 | followCount: TextStyle,
137 | followButton: ViewStyle,
138 | }
139 |
140 | const styles = StyleSheet.create({
141 | base: {
142 | textAlign: "center",
143 | },
144 | followButton: {
145 | alignSelf: isPad ? "center" : null,
146 | height: 40,
147 | marginLeft: 0,
148 | marginRight: 0,
149 | width: isPad ? 330 : null,
150 | },
151 | followCount: {
152 | color: colors["gray-semibold"],
153 | marginBottom: 30
154 | },
155 | headline: {
156 | fontSize: 14,
157 | },
158 | })
159 |
160 | export default Relay.createContainer(Header as any, {
161 | fragments: {
162 | artist: () => Relay.QL`
163 | fragment on Artist {
164 | _id
165 | id
166 | name
167 | nationality
168 | birthday
169 | counts {
170 | follows
171 | }
172 | }
173 | `,
174 | }
175 | })
176 |
--------------------------------------------------------------------------------
/app/containers/react-native-web/artist/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { StyleSheet, TextStyle, View, ViewStyle } from "react-native-web"
3 | import * as Relay from "react-relay"
4 |
5 | import ArtworksGrid from "./grid"
6 | import ArtistHeader from "./header"
7 | import SerifText from "./text/serif"
8 |
9 | import GQL from "../../../gql"
10 |
11 | import * as colors from "../../../../data/colors.json"
12 |
13 | export const Artist = ({ artist }: { artist: GQL.ArtistType }) => (
14 |
15 |
16 |
17 |
18 |
19 | Works for Sale
20 | ({artist.counts.for_sale_artworks})
21 |
22 | console.log("Done paging!")}
28 | queryArtworksKeypath="artist.artworks" />
29 |
30 |
31 | )
32 |
33 | const resolveQuery = (artistID: string) => {
34 | return (component: any, page: number, state: any): string => {
35 | // The page + 1 is to take into account the fact that we _start_ with results already
36 | const grid: any = ArtworksGrid
37 | return grid.artworksQuery(artistID, state.availability, component.state.page + 1)
38 | }
39 | }
40 |
41 | interface Styles {
42 | appContainer: ViewStyle,
43 | heading: TextStyle,
44 | text: TextStyle,
45 | count: TextStyle,
46 | }
47 |
48 | const styles = StyleSheet.create({
49 | /**
50 | * Ensure that the application covers the whole screen.
51 | */
52 | appContainer: {
53 | bottom: 0,
54 | left: 40,
55 | position: "absolute",
56 | right: 40,
57 | top: 0,
58 | },
59 | count: {
60 | color: colors["gray-semibold"],
61 | },
62 | heading: {
63 | marginBottom: 20,
64 | },
65 | text: {
66 | fontSize: 20,
67 | },
68 | })
69 |
70 | export default Relay.createContainer(Artist, {
71 | fragments: {
72 | artist: () => Relay.QL`
73 | fragment on Artist {
74 | _id
75 | id
76 | counts {
77 | for_sale_artworks
78 | }
79 | ${ArtistHeader.getFragment("artist")}
80 | artworks(sort: partner_updated_at_desc, filter: IS_FOR_SALE, size: 10) {
81 | ${ArtworksGrid.getFragment("artworks")}
82 | }
83 | }
84 | `,
85 | },
86 | })
87 |
--------------------------------------------------------------------------------
/app/containers/react-native-web/artist/inverted_button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Animated, StyleSheet, Text, TouchableHighlight, View } from "react-native-web"
3 |
4 | import Headline from "./text/headline"
5 |
6 | import * as colors from "../../../../data/colors.json"
7 |
8 | // import Spinner from '../spinner'
9 | const Spinner = ({ spinnerColor, style }) => SPINNER
10 |
11 | const AnimationDuration = 250
12 | const AnimatedTouchable = Animated.createAnimatedComponent(TouchableHighlight)
13 | const AnimatedHeadline = Animated.createAnimatedComponent(Headline)
14 |
15 | interface Props {
16 | text: string,
17 | selected: boolean,
18 | inProgress?: boolean,
19 | onPress: () => void,
20 | onSelectionAnimationFinished?: () => void,
21 | }
22 |
23 | interface State {
24 | textOpacity: Animated.Value,
25 | backgroundColor: Animated.Value
26 | }
27 |
28 | export default class InvertedButton extends React.Component {
29 | constructor(props: any) {
30 | super(props)
31 | this.state = {
32 | backgroundColor: new Animated.Value(props.selected ? 1 : 0),
33 | textOpacity: new Animated.Value(1),
34 | }
35 | }
36 |
37 | componentWillUpdate(nextProps: any, nextState: any) {
38 | if (this.props.selected !== nextProps.selected) {
39 | nextState.textOpacity.setValue(0)
40 | }
41 | }
42 |
43 | componentDidUpdate(prevProps: any, prevState: any) {
44 | if (this.props.selected !== prevProps.selected) {
45 | const duration = AnimationDuration
46 | Animated.parallel([
47 | Animated.timing(this.state.textOpacity, { toValue: 1, duration }),
48 | Animated.timing(this.state.backgroundColor, { toValue: this.props.selected ? 1 : 0, duration }),
49 | ]).start(this.props.onSelectionAnimationFinished)
50 | }
51 | }
52 |
53 | render() {
54 | const backgroundColor = this.state.backgroundColor.interpolate({
55 | inputRange: [0, 1],
56 | outputRange: (["black", colors["purple-regular"]])
57 | })
58 | const styling = {
59 | style: [styles.button, { backgroundColor }],
60 | underlayColor: (this.props.selected ? "black" : colors["purple-regular"]),
61 | }
62 | let content = null
63 | if (this.props.inProgress) {
64 | content =
65 | } else {
66 | content = (
67 |
68 | {this.props.text}
69 |
70 | )
71 | }
72 | return (
73 |
74 | {content}
75 |
76 | )
77 | }
78 | }
79 |
80 | const styles = StyleSheet.create({
81 | button: {
82 | alignItems: "center",
83 | flex: 1,
84 | justifyContent: "center",
85 | },
86 | text: {
87 | color: "white"
88 | }
89 | })
90 |
--------------------------------------------------------------------------------
/app/containers/react-native-web/artist/metaphysics.ts:
--------------------------------------------------------------------------------
1 | // import { NativeModules } from 'react-native'
2 | // const { Emission } = NativeModules
3 |
4 | import { metaphysicsURL } from '../../../relay/config'
5 | import 'isomorphic-fetch'
6 |
7 | class NetworkError extends Error {
8 | response: any
9 | }
10 |
11 | export default function metaphysics(query: string): Promise