diff --git a/packages/website/public/.DS_Store b/packages/website/public/.DS_Store
deleted file mode 100644
index 902acb2aea..0000000000
Binary files a/packages/website/public/.DS_Store and /dev/null differ
diff --git a/packages/website/public/index.html b/packages/website/public/index.html
index 43aa52b122..b760443739 100644
--- a/packages/website/public/index.html
+++ b/packages/website/public/index.html
@@ -17,6 +17,7 @@
+
diff --git a/packages/website/ts/components/modals/modal_contact.tsx b/packages/website/ts/components/modals/modal_contact.tsx
index b753462701..752160b5b8 100644
--- a/packages/website/ts/components/modals/modal_contact.tsx
+++ b/packages/website/ts/components/modals/modal_contact.tsx
@@ -18,6 +18,7 @@ export enum ModalContactType {
General = 'GENERAL',
MarketMaker = 'MARKET_MAKER',
Credits = 'CREDITS',
+ Explore = 'EXPLORE',
}
interface ServiceOptionMetadata {
@@ -142,6 +143,8 @@ export class ModalContact extends React.Component {
return this._renderMarketMakerFormContent(errors);
case ModalContactType.Credits:
return this._renderCreditsFormContent(errors);
+ case ModalContactType.Explore:
+ return this._renderExploreFormContent(errors);
case ModalContactType.General:
default:
return this._renderGeneralFormContent(errors);
@@ -218,6 +221,99 @@ export class ModalContact extends React.Component {
);
}
+ private _renderExploreFormContent(errors: ErrorProps): React.ReactNode {
+ return (
+ <>
+
+ If you’re working on an awesome 0x project, we would love to share it on our explore page. Fill out the form
+ so we can connect you with the right person to help you get started.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Details for 0x Explore page:
+
+
+
+
+
+
+ {[{label: 'Yes', name: 'yes'}, {label: 'No', name: 'no'}].map((metadata: ServiceOptionMetadata) => {
+ return (
+
+ );
+ })}
+
+
+ >
+ );
+ }
+
private _renderCreditsFormContent(errors: ErrorProps): React.ReactNode {
return (
<>
diff --git a/packages/website/ts/components/ui/switch.tsx b/packages/website/ts/components/ui/switch.tsx
new file mode 100644
index 0000000000..483ed8f6ee
--- /dev/null
+++ b/packages/website/ts/components/ui/switch.tsx
@@ -0,0 +1,20 @@
+import * as React from 'react';
+import styled, { withTheme } from 'styled-components';
+
+import { Heading } from 'ts/components/text';
+
+const SwitchWrapper = styled.div`
+ display: flex;
+ justify-content: space-between;
+ padding: 0.5rem 0;
+`;
+
+export interface SwitchProps {
+ label: string;
+}
+
+export const Switch = (props: SwitchProps) => {
+ return
+ {props.label}
+ ;
+};
diff --git a/packages/website/ts/globals.d.ts b/packages/website/ts/globals.d.ts
index 05f3c7f886..5f918aaea6 100644
--- a/packages/website/ts/globals.d.ts
+++ b/packages/website/ts/globals.d.ts
@@ -10,6 +10,7 @@ declare module 'react-anchor-link-smooth-scroll';
declare module 'react-responsive';
declare module 'react-scrollable-anchor';
declare module 'react-headroom';
+declare module 'zeroExInstant';
declare module '*.json' {
const json: any;
diff --git a/packages/website/ts/pages/explore.tsx b/packages/website/ts/pages/explore.tsx
index 4d4038ce2a..b54768ca32 100644
--- a/packages/website/ts/pages/explore.tsx
+++ b/packages/website/ts/pages/explore.tsx
@@ -1,6 +1,7 @@
import * as _ from 'lodash';
import * as React from 'react';
import styled from 'styled-components';
+import * as zeroExInstant from 'zeroExInstant';
import { Banner } from 'ts/components/banner';
import { DocumentTitle } from 'ts/components/document_title';
@@ -10,11 +11,13 @@ import { Section } from 'ts/components/newLayout';
import { SiteWrap } from 'ts/components/siteWrap';
import { Heading } from 'ts/components/text';
import { Input as SearchInput } from 'ts/components/ui/search_textfield';
-import { ExploreGrid } from 'ts/pages/explore/explore_grid';
+import { ExploreGrid, ExploreGridListTile, ExploreGridListTileVisibility, ExploreGridListTileWidth } from 'ts/pages/explore/explore_grid';
+import { EXPLORE_STATE_DIALOGS, ExploreGridDialogTile } from 'ts/pages/explore/explore_grid_state_tile';
import { Button as ExploreTagButton } from 'ts/pages/explore/explore_tag_button';
import { colors } from 'ts/style/colors';
-import { ExploreEntry, ExploreEntryVisibility, ExploreFilterMetadata, ExploreFilterType, RicherExploreEntry } from 'ts/types';
+import { ExploreEntry, ExploreEntryInstantMetadata, RicherExploreEntry } from 'ts/types';
import { documentConstants } from 'ts/utils/document_meta_constants';
+import { ExploreSettingsDropdown } from 'ts/pages/explore/explore_dropdown';
export interface ExploreProps {}
@@ -26,9 +29,6 @@ const PROJECTS: { [s: string]: ExploreEntry } = {
theme_color: '#151628',
url: 'https://paradex.io/',
keywords: ['relayer'],
- instant: {
- orderSource: '',
- },
},
veil: {
label: 'Veil',
@@ -45,6 +45,9 @@ const PROJECTS: { [s: string]: ExploreEntry } = {
theme_color: '#262626',
url: 'https://radarrelay.com/',
keywords: ['relayer'],
+ instant: {
+ orderSource: 'https://api.radarrelay.com/0x/v2/',
+ },
},
emoon: {
label: 'Emoon',
@@ -72,6 +75,19 @@ const PROJECTS: { [s: string]: ExploreEntry } = {
},
};
+enum ExploreFilterType {
+ All = 'ALL',
+ Keyword = 'Keyword',
+}
+
+interface ExploreFilterMetadata {
+ label: string;
+ filterType: ExploreFilterType;
+ name: string;
+ keyword?: string;
+ active?: boolean;
+}
+
const FILTERS: ExploreFilterMetadata[] = [{
label: 'All',
name: 'all',
@@ -94,12 +110,18 @@ enum ExploreEntriesModifiers {
}
enum ExploreEntriesOrdering {
- None = 'NONE',
+ None = 'None',
+ Latest = 'Latest',
+ Popular = 'Popular',
}
+const ORDERINGS = [ExploreEntriesOrdering.None, ExploreEntriesOrdering.Latest, ExploreEntriesOrdering.Popular];
+
export class Explore extends React.Component {
public state = {
+ isEntriesLoading: false,
isContactModalOpen: false,
+ tiles: [] as ExploreGridListTile[],
entries: [] as RicherExploreEntry[],
entriesOrdering: ExploreEntriesOrdering.None,
filters: FILTERS,
@@ -124,10 +146,10 @@ export class Explore extends React.Component {
{
);
}
-
private readonly _onOpenContactModal = (): void => {
this.setState({ isContactModalOpen: true });
};
@@ -150,10 +171,30 @@ export class Explore extends React.Component {
this.setState({ isContactModalOpen: false });
};
- private _launchInstantAsync = async (): Promise => {
-
+ private _launchInstantAsync = (params: ExploreEntryInstantMetadata): void => {
+ zeroExInstant.render(params, 'body');
};
+ private _generateTilesFromState = (): ExploreGridListTile[] => {
+ if (this.state.isEntriesLoading) {
+ return [{
+ name: 'loading',
+ component: ,
+ visibility: ExploreGridListTileVisibility.Visible,
+ width: ExploreGridListTileWidth.FullWidth,
+ }];
+ }
+ if (_.isEmpty(this.state.tiles.filter(t => !!t.exploreEntry && t.visibility !== ExploreGridListTileVisibility.Hidden))) {
+ return [{
+ name: 'empty',
+ component: ,
+ visibility: ExploreGridListTileVisibility.Visible,
+ width: ExploreGridListTileWidth.FullWidth,
+ }];
+ }
+ return this.state.tiles;
+ }
+
private _changeSearchResults = (query: string): void => {
this.setState({ query: query.trim().toLowerCase() }, () => {
this._setEntriesModifier(ExploreEntriesModifiers.Search);
@@ -187,30 +228,32 @@ export class Explore extends React.Component {
};
private _setEntriesModifier = async (modifier: ExploreEntriesModifiers): Promise => {
- let newEntries: RicherExploreEntry[];
+ let newTiles: ExploreGridListTile[];
if (modifier === ExploreEntriesModifiers.Filter || modifier === ExploreEntriesModifiers.Search) {
const activeFilters = _.filter(this.state.filters, f => f.active);
if (activeFilters.length === 1 && activeFilters[0].name === 'all') {
- newEntries = _.concat([], this.state.entries).map(e => {
- const newEntry = _.assign({}, e);
- newEntry.visibility = ExploreEntryVisibility.Visible;
- if (modifier === ExploreEntriesModifiers.Search && newEntry.visibility === ExploreEntryVisibility.Visible) {
- newEntry.visibility = (_.includes(newEntry.label.toLowerCase(), this.state.query) && ExploreEntryVisibility.Visible) || ExploreEntryVisibility.Hidden;
+ newTiles = _.concat([], this.state.tiles).map(t => {
+ const newTile = _.assign({}, t);
+ newTile.visibility = ExploreGridListTileVisibility.Visible;
+ if (modifier === ExploreEntriesModifiers.Search && !!newTile.exploreEntry) {
+ newTile.visibility = (_.includes(newTile.exploreEntry.label.toLowerCase(), this.state.query) && ExploreGridListTileVisibility.Visible) || ExploreGridListTileVisibility.Hidden;
}
- return newEntry;
+ return newTile;
});
} else {
- newEntries = _.concat([], this.state.entries).map(e => {
- const newEntry = _.assign({}, e);
- newEntry.visibility = _.intersectionWith(activeFilters, newEntry.keywords, (f, k) => k === f.name).length !== 0 ? ExploreEntryVisibility.Visible : ExploreEntryVisibility.Hidden;
- if (modifier === ExploreEntriesModifiers.Search && newEntry.visibility === ExploreEntryVisibility.Visible) {
- newEntry.visibility = (_.includes(newEntry.label.toLowerCase(), this.state.query) && ExploreEntryVisibility.Visible) || ExploreEntryVisibility.Hidden;
+ newTiles = _.concat([], this.state.tiles).map(t => {
+ const newTile = _.assign({}, t);
+ if (!!newTile.exploreEntry) {
+ newTile.visibility = _.intersectionWith(activeFilters, newTile.exploreEntry.keywords, (f, k) => k === f.name).length !== 0 ? ExploreGridListTileVisibility.Visible : ExploreGridListTileVisibility.Hidden;
+ if (modifier === ExploreEntriesModifiers.Search && newTile.visibility === ExploreGridListTileVisibility.Visible) {
+ newTile.visibility = (_.includes(newTile.exploreEntry.label.toLowerCase(), this.state.query) && ExploreGridListTileVisibility.Visible) || ExploreGridListTileVisibility.Hidden;
+ }
}
- return newEntry;
+ return newTile;
});
}
}
- this.setState({ entries: newEntries});
+ this.setState({ tiles: newTiles });
};
// For future versions, ordering can be determined by async processes
@@ -222,9 +265,21 @@ export class Explore extends React.Component {
// For future versions, the load entries function can be async
private _loadEntriesAsync = async (): Promise => {
- const rawEntries = _.values(PROJECTS).map(e => _.assign(e, { visibility: ExploreEntryVisibility.Visible})) as RicherExploreEntry[];
- const entries = await this._setEntriesOrderingAsync(rawEntries);
- this.setState({ entries });
+ this.setState({ isEntriesLoading: true });
+ const rawEntries = _.values(PROJECTS);
+ const tiles = (await this._setEntriesOrderingAsync(rawEntries)).map(e => {
+ const richExploreEntry = _.assign({}, e) as RicherExploreEntry;
+ if (!!richExploreEntry.instant) {
+ richExploreEntry.onInstantClick = () => this._launchInstantAsync(richExploreEntry.instant);
+ }
+ return {
+ name: e.label.toLowerCase(),
+ exploreEntry: richExploreEntry,
+ visibility: ExploreGridListTileVisibility.Visible,
+ width: ExploreGridListTileWidth.OneThird,
+ };
+ });
+ this.setState({ entries: rawEntries, tiles, isEntriesLoading: false });
}
}
@@ -237,6 +292,7 @@ const ExploreHeroContentWrapper = styled.div`
interface ExploreHeroProps {
onSearch(query: string): void;
}
+
const ExploreHero = (props: ExploreHeroProps) => {
const onSearchDebounce = _.debounce(props.onSearch, 300);
const onChange = (e: any) => { onSearchDebounce(e.target.value); };
@@ -272,14 +328,6 @@ interface ExploreToolBarProps {
onFilterClick(filterName: string, active: boolean): void;
}
-const SettingsIconWrapper = styled.div`
- padding-right: 0.4rem;
- display: inline;
- & > * {
- transform: translateY(2px);
- }
-`;
-
const ExploreToolBar = (props: ExploreToolBarProps) => {
return
@@ -289,12 +337,7 @@ const ExploreToolBar = (props: ExploreToolBarProps) => {
})}
-
-
-
-
- Featured
-
+
;
};
diff --git a/packages/website/ts/pages/explore/explore_dropdown.tsx b/packages/website/ts/pages/explore/explore_dropdown.tsx
index e69de29bb2..2b542c92b7 100644
--- a/packages/website/ts/pages/explore/explore_dropdown.tsx
+++ b/packages/website/ts/pages/explore/explore_dropdown.tsx
@@ -0,0 +1,152 @@
+import * as React from 'react';
+import styled from 'styled-components';
+
+import { Icon } from 'ts/components/icon';
+import { Heading, Paragraph } from 'ts/components/text';
+import { Switch } from 'ts/components/ui/switch';
+import { Button as ExploreTagButton } from 'ts/pages/explore/explore_tag_button';
+import { colors } from 'ts/style/colors';
+
+const ExploreSettingsDropdownButton = ({}) => {
+ return
+
+
+
+ Settings
+ ;
+};
+
+const SettingsIconWrapper = styled.div`
+ padding-right: 0.4rem;
+ display: inline;
+ & > * {
+ transform: translateY(2px);
+ }
+`;
+
+const SettingsWrapper = styled.div`
+ position: relative;
+
+ @media (min-width: 800px) {
+ &:hover > div {
+ display: block;
+ visibility: visible;
+ opacity: 1;
+ transform: translate3d(0, 0, 0);
+ transition: opacity 0.35s, transform 0.35s, visibility 0s;
+ }
+ }
+`;
+
+interface DropdownWrapInterface {
+ width?: number;
+}
+
+const DropdownWrap = styled.div`
+ width: ${props => props.width || 280}px;
+ margin-top: 16px;
+ padding: 16px 24px;
+ border: 1px solid transparent;
+ border-color: ${props => props.theme.dropdownBorderColor};
+ background-color: ${props => props.theme.dropdownBg};
+ color: ${props => props.theme.dropdownColor};
+ position: absolute;
+ top: 100%;
+ right: 0%;
+ visibility: hidden;
+ opacity: 0;
+ transform: translate3d(0, -10px, 0);
+ transition: opacity 0.35s, transform 0.35s, visibility 0s 0.35s;
+ z-index: 20;
+
+ &:after,
+ &:before {
+ bottom: 100%;
+ left: 90%;
+ border: solid transparent;
+ content: ' ';
+ height: 0;
+ width: 0;
+ position: absolute;
+ pointer-events: none;
+ }
+ &:after {
+ border-color: rgba(255, 255, 255, 0);
+ border-bottom-color: ${props => props.theme.dropdownBg};
+ border-width: 10px;
+ margin-left: -10px;
+ }
+ &:before {
+ border-color: rgba(255, 0, 0, 0);
+ border-bottom-color: ${props => props.theme.dropdownBorderColor};
+ border-width: 11px;
+ margin-left: -11px;
+ }
+
+ @media (max-width: 768px) {
+ display: none;
+ }
+`;
+
+export interface ExploreSettingsDropdownProps {
+ orderings: string[];
+}
+
+export class ExploreSettingsDropdown extends React.Component {
+ constructor(props: ExploreSettingsDropdownProps) {
+ super(props);
+ }
+
+ public render(): React.ReactNode {
+ return
+
+
+
+
+ ;
+ }
+}
+
+const DropdownContentWrapper = styled.div`
+`;
+
+const OrderingWrapper = styled.div`
+ padding-top: 20px;
+ margin-top: 20px;
+ margin-bottom: 20px;
+ position: relative;
+
+ &:before {
+ content: '';
+ width: 100%;
+ height: 1px;
+ background-color: ${props => props.theme.dropdownColor};
+ opacity: 0.15;
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+`;
+
+interface DropdownContentProps {
+ orderings: string[];
+}
+
+const DropdownContent = (props: DropdownContentProps) => {
+ return
+
+
+
+ Editorial content reflects the views of the 0x core team.
+
+
+
+
+ Ordering
+
+ {props.orderings.map(o => {
+ return {o};
+ })}
+
+ ;
+};
diff --git a/packages/website/ts/pages/explore/explore_grid.tsx b/packages/website/ts/pages/explore/explore_grid.tsx
index cfc568116d..abf888080a 100644
--- a/packages/website/ts/pages/explore/explore_grid.tsx
+++ b/packages/website/ts/pages/explore/explore_grid.tsx
@@ -3,10 +3,35 @@ import * as React from 'react';
import styled from 'styled-components';
import { ExploreGridTile } from 'ts/pages/explore/explore_grid_tile';
-import { ExploreEntryVisibility, RicherExploreEntry} from 'ts/types';
+import { RicherExploreEntry} from 'ts/types';
export interface ExptoreGridProps {
- entries: RicherExploreEntry[];
+ tiles: ExploreGridListTile[];
+}
+
+export enum ExploreGridListTileVisibility {
+ Hidden = 'HIDDEN',
+ Visible = 'VISIBLE',
+}
+
+export enum ExploreGridListTileWidth {
+ OneThird = 2,
+ FullWidth = 6,
+ Half = 3,
+ TwoThirds = 4,
+}
+
+export interface ExploreGridListTile {
+ name: string;
+ visibility: ExploreGridListTileVisibility;
+ width?: ExploreGridListTileWidth;
+ exploreEntry?: RicherExploreEntry;
+ component?: React.ReactNode;
+}
+
+interface RicherExploreGridListTile extends ExploreGridListTile {
+ gridStart: number;
+ gridEnd: number;
}
export class ExploreGrid extends React.Component {
@@ -17,21 +42,58 @@ export class ExploreGrid extends React.Component {
public render(): React.ReactNode {
return (
- {this.props.entries.filter(e => e.visibility !== ExploreEntryVisibility.Hidden).map(e => {
- return ;
- })}
+ {this._prepareTiles().map(t => {
+ if (!!t.exploreEntry) {
+ return
+
+ ;
+ } else {
+ return
+ {!!t.component && t.component}
+ ;
+ }
+ })}
);
}
+
+ private _prepareTiles = (): RicherExploreGridListTile[] => {
+ const visibleTiles = this.props.tiles.filter(t => t.visibility !== ExploreGridListTileVisibility.Hidden);
+ return this._generateGridValues(visibleTiles);
+ }
+
+ private _generateGridValues = (tiles: ExploreGridListTile[]): RicherExploreGridListTile[] => {
+ let gridEndCounter = 1;
+ const richerTiles = tiles.map(t => {
+ if (gridEndCounter + t.width > (ExploreGridListTileWidth.FullWidth + 1)) {
+ gridEndCounter = 1;
+ }
+ const gridStart = gridEndCounter;
+ const gridEnd = gridEndCounter + t.width;
+ gridEndCounter = gridEnd;
+ const newTile = _.assign({ gridStart, gridEnd }, t);
+ return newTile as RicherExploreGridListTile;
+ });
+ return richerTiles;
+ }
}
interface ExploreGridListProps {
-
}
+interface ExploreGridTileWrapperProps {
+ gridStart: number;
+ gridEnd: number;
+}
+
+const ExploreGridTileWrapper = styled.div`
+ grid-column-start: ${props => props.gridStart};
+ grid-column-end: ${props => props.gridEnd};
+`;
+
const ExploreGridList = styled.div`
display: grid;
- grid-template-columns: repeat(3, 1fr);
+ grid-template-columns: repeat(${ExploreGridListTileWidth.FullWidth}, 1fr);
grid-column-gap: 1.5rem;
grid-row-gap: 1.5rem;
@media (max-width: 56rem) {
diff --git a/packages/website/ts/pages/explore/explore_grid_state_tile.tsx b/packages/website/ts/pages/explore/explore_grid_state_tile.tsx
new file mode 100644
index 0000000000..f79343b3c2
--- /dev/null
+++ b/packages/website/ts/pages/explore/explore_grid_state_tile.tsx
@@ -0,0 +1,46 @@
+import * as React from 'react';
+import styled from 'styled-components';
+
+import { Button } from 'ts/components/button';
+import { Heading, Paragraph } from 'ts/components/text';
+import { Image } from 'ts/components/ui/image';
+import { ExploreGridTileWrapper } from 'ts/pages/explore/explore_grid_tile';
+
+export interface ExploreGridDialogTileProps {
+ dialogImageUrl?: string;
+ title?: string;
+ description: string;
+}
+
+export const EXPLORE_STATE_DIALOGS: { [s: string]: ExploreGridDialogTileProps } = {
+ ERROR: {
+ title: 'Something went wrong.',
+ description: 'Try refreshing the page after a few moments',
+ },
+ LOADING: {
+ description: 'Loading...',
+ },
+ EMPTY: {
+ title: 'No projects found.',
+ description: 'Try deselecting a few tags or changing your search.',
+ },
+};
+
+export const ExploreGridDialogTile = (props: ExploreGridDialogTileProps) => {
+ return
+ {!!props.dialogImageUrl && }
+ {!!props.title && {props.title}}
+ {props.description}
+ ;
+};
+
+const ExploreGridDialogTileWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ margin: 120px 0;
+`;
diff --git a/packages/website/ts/pages/explore/explore_grid_tile.tsx b/packages/website/ts/pages/explore/explore_grid_tile.tsx
index 70b7e65ebe..e869949226 100644
--- a/packages/website/ts/pages/explore/explore_grid_tile.tsx
+++ b/packages/website/ts/pages/explore/explore_grid_tile.tsx
@@ -47,15 +47,15 @@ const ExploreGridTileLink = styled.a`
display: block;
`;
-const ExploreGridTileWrapper = styled.div`
+export const ExploreGridTileWrapper = styled.div`
display: block;
position: relative;
background-color: white;
- box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.1);
- transition: box-shadow 200ms ease-in-out;
- &:hover {
- box-shadow: 0px 8px 24px rgba(0, 0, 0, 0.1);
- }
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ // transition: box-shadow 200ms ease-in-out;
+ // &:hover {
+ // box-shadow: 0px 8px 24px rgba(0, 0, 0, 0.1);
+ // }
`;
const ExploreGridButtonWrapper = styled.div`
diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts
index 288f1ad466..2f2be4ac3b 100644
--- a/packages/website/ts/types.ts
+++ b/packages/website/ts/types.ts
@@ -256,30 +256,10 @@ export interface ExploreEntry {
instant?: ExploreEntryInstantMetadata;
}
-export enum ExploreEntryVisibility {
- Hidden = 'HIDDEN',
- Featured = 'FEATURED', // Temporarily unused feature
- Visible = 'VISIBLE',
-}
-
export interface RicherExploreEntry extends ExploreEntry {
- visibility: ExploreEntryVisibility;
onInstantClick?(): void;
}
-export enum ExploreFilterType {
- All = 'ALL',
- Keyword = 'Keyword',
-}
-
-export interface ExploreFilterMetadata {
- label: string;
- filterType: ExploreFilterType;
- name: string;
- keyword?: string;
- active?: boolean;
-}
-
export interface FAQQuestion {
prompt: string;
answer: React.ReactNode;
diff --git a/packages/website/webpack.config.js b/packages/website/webpack.config.js
index d9bdd91ad3..689fe0152a 100644
--- a/packages/website/webpack.config.js
+++ b/packages/website/webpack.config.js
@@ -18,6 +18,9 @@ const config = {
chunkFilename: 'bundle-[name].js',
publicPath: '/',
},
+ externals: {
+ zeroExInstant: 'zeroExInstant'
+ },
devtool: 'source-map',
resolve: {
modules: [path.join(__dirname, '/ts'), 'node_modules'],