diff --git a/src/components/src/common/data-table/grid.tsx b/src/components/src/common/data-table/grid.tsx index 43d391186e..8c05edc342 100644 --- a/src/components/src/common/data-table/grid.tsx +++ b/src/components/src/common/data-table/grid.tsx @@ -42,7 +42,7 @@ export default class GridHack extends PureComponent { } componentWillUnmount() { - //@ts-expect-error _scrollingContainer not typed in Grid + // @ts-expect-error _scrollingContainer not typed in Grid this.grid?._scrollingContainer?.removeEventListener('wheel', this._preventScrollBack, { passive: false }); @@ -67,7 +67,7 @@ export default class GridHack extends PureComponent { * This hack exists because we need to add wheel event listener to the div rendered by Grid * */ - //@ts-expect-error _scrollingContainer not typed in Grid + // @ts-expect-error _scrollingContainer not typed in Grid this.grid?._scrollingContainer?.addEventListener('wheel', this._preventScrollBack, { passive: false }); diff --git a/src/components/src/common/data-table/index.tsx b/src/components/src/common/data-table/index.tsx index 6b1499db38..1c0396f32b 100644 --- a/src/components/src/common/data-table/index.tsx +++ b/src/components/src/common/data-table/index.tsx @@ -222,7 +222,7 @@ const getRowCell = ( const rowIdx = sortOrder && sortOrder.length ? get(sortOrder, rowIndex) : rowIndex; const {type} = colMeta[column]; - let value = dataContainer.valueAt(rowIdx, columns.indexOf(column)); + const value = dataContainer.valueAt(rowIdx, columns.indexOf(column)); return value === null || value === undefined || value === '' ? '' : formatter diff --git a/src/components/src/common/item-selector/item-selector.tsx b/src/components/src/common/item-selector/item-selector.tsx index 55471dfc5b..32642c87cf 100644 --- a/src/components/src/common/item-selector/item-selector.tsx +++ b/src/components/src/common/item-selector/item-selector.tsx @@ -324,7 +324,7 @@ class ItemSelectorUnmemoized extends Component { active: this.state.showTypeahead }), displayOption, - disabled: disabled, + disabled, onClick: this._showTypeahead, error: this.props.isError, inputTheme, diff --git a/src/components/src/map-container.tsx b/src/components/src/map-container.tsx index 5acad0399c..018d3cf7d1 100644 --- a/src/components/src/map-container.tsx +++ b/src/components/src/map-container.tsx @@ -516,7 +516,7 @@ export default function MapContainerFactory( _onDeckError = (error, layer) => { const errorMessage = error?.message || 'unknown-error'; const layerMessage = layer?.id ? ` in ${layer.id} layer` : ''; - let errorMessageFull = + const errorMessageFull = errorMessage === 'WebGL context is lost' ? 'Your GPU was disconnected. This can happen if your computer goes to sleep. It can also occur for other reasons, such as if you are running too many GPU applications.' : `An error in deck.gl: ${errorMessage}${layerMessage}.`; diff --git a/test/browser/components/map/map-control-test.js b/test/browser/components/map/map-control-test.js index fdb58b77db..e40f93c248 100644 --- a/test/browser/components/map/map-control-test.js +++ b/test/browser/components/map/map-control-test.js @@ -122,7 +122,7 @@ test('MapControlFactory - click options', t => { toggleMapControl: onToggleMapControl, setLocale: onSetLocale }; - let updateState = keplerGlReducerCore(StateWSplitMaps, toggleMapControl('mapLegend', 0)); + const updateState = keplerGlReducerCore(StateWSplitMaps, toggleMapControl('mapLegend', 0)); const mapContainerProps = mapFieldsSelector( mockKeplerPropsWithState({ state: updateState, diff --git a/test/browser/components/modals/share-map-modal-test.js b/test/browser/components/modals/share-map-modal-test.js index 86981cf49a..c4b54ce95c 100644 --- a/test/browser/components/modals/share-map-modal-test.js +++ b/test/browser/components/modals/share-map-modal-test.js @@ -22,12 +22,7 @@ import React from 'react'; import test from 'tape'; import {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils'; import sinon from 'sinon'; -import { - ShareMapUrlModalFactory, - SharingUrl, - CloudTile, - StatusPanel -} from '@kepler.gl/components'; +import {ShareMapUrlModalFactory, SharingUrl, CloudTile, StatusPanel} from '@kepler.gl/components'; const ShareMapUrlModal = ShareMapUrlModalFactory(); test('Components -> ShareMapUrlModal.mount', t => { diff --git a/test/browser/components/plot-container-test.js b/test/browser/components/plot-container-test.js index d2c66ca481..397debcafe 100644 --- a/test/browser/components/plot-container-test.js +++ b/test/browser/components/plot-container-test.js @@ -22,11 +22,7 @@ import React from 'react'; import {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils'; import test from 'tape'; -import { - appInjector, - PlotContainerFactory, - plotContainerSelector -} from '@kepler.gl/components'; +import {appInjector, PlotContainerFactory, plotContainerSelector} from '@kepler.gl/components'; import {mockKeplerProps} from '../../helpers/mock-state'; const PlotContainer = appInjector.get(PlotContainerFactory); diff --git a/test/browser/layer-tests/geojson-layer-specs.js b/test/browser/layer-tests/geojson-layer-specs.js index 171f9c2b88..e6f3a19f45 100644 --- a/test/browser/layer-tests/geojson-layer-specs.js +++ b/test/browser/layer-tests/geojson-layer-specs.js @@ -19,12 +19,7 @@ // THE SOFTWARE. import test from 'tape'; -import { - defaultElevation, - defaultLineWidth, - defaultRadius, - KeplerGlLayers -} from '@kepler.gl/layers'; +import {defaultElevation, defaultLineWidth, defaultRadius, KeplerGlLayers} from '@kepler.gl/layers'; import {copyTableAndUpdate, createNewDataEntry} from '@kepler.gl/table'; const {GeojsonLayer} = KeplerGlLayers; diff --git a/test/node/utils/editor-utils-test.js b/test/node/utils/editor-utils-test.js index c40d9a0fb9..dd20c3caac 100644 --- a/test/node/utils/editor-utils-test.js +++ b/test/node/utils/editor-utils-test.js @@ -24,10 +24,7 @@ import {EditableGeoJsonLayer} from '@nebula.gl/layers'; import {INITIAL_VIS_STATE} from '@kepler.gl/reducers'; import {VisStateActions} from '@kepler.gl/actions'; import {EDITOR_LAYER_ID, EDITOR_MODES} from '@kepler.gl/constants'; -import { - EditorLayerUtils, - getEditorLayer -} from '@kepler.gl/layers'; +import {EditorLayerUtils, getEditorLayer} from '@kepler.gl/layers'; test('editorLayerUtils -> isDrawingActive', t => { t.equal( @@ -54,7 +51,11 @@ test('editorLayerUtils -> getCursor', t => { editorMenuActive: true, editor }; - t.equal(EditorLayerUtils.getCursor(mockSettings), 'crosshair', 'Should return crosshair for active drawing mode'); + t.equal( + EditorLayerUtils.getCursor(mockSettings), + 'crosshair', + 'Should return crosshair for active drawing mode' + ); mockSettings.editorMenuActive = false; t.equal( @@ -214,20 +215,35 @@ test('editorLayerUtils -> onClick', t => { const {onLayerClick, setSelectedFeature} = VisStateActions; t.equal( - EditorLayerUtils.onClick(info, event, {editor, editorMenuActive: true, onLayerClick, setSelectedFeature}), + EditorLayerUtils.onClick(info, event, { + editor, + editorMenuActive: true, + onLayerClick, + setSelectedFeature + }), true, 'Should return true - onClick is handled as drawing is active' ); t.equal( - EditorLayerUtils.onClick(info, event, {editor, editorMenuActive: false, onLayerClick, setSelectedFeature}), + EditorLayerUtils.onClick(info, event, { + editor, + editorMenuActive: false, + onLayerClick, + setSelectedFeature + }), false, "Should return false - onClick isn't handled" ); info.layer.id = EDITOR_LAYER_ID; t.equal( - EditorLayerUtils.onClick(info, event, {editor, editorMenuActive: false, onLayerClick, setSelectedFeature}), + EditorLayerUtils.onClick(info, event, { + editor, + editorMenuActive: false, + onLayerClick, + setSelectedFeature + }), true, 'Should return true - onClick is handled' ); diff --git a/website/src/components/common/section.js b/website/src/components/common/section.js index 7f50cc9081..847f7a8827 100644 --- a/website/src/components/common/section.js +++ b/website/src/components/common/section.js @@ -41,7 +41,7 @@ export const SectionContainer = styled.div` ? `url(${props.background})` : 'white'}; padding: ${props => props.theme.margins.huge}; - margin-bottom: ${props => props.theme.margins.large}; + // margin-bottom: ${props => props.theme.margins.large}; background-size: cover; ${media.portable` @@ -57,6 +57,7 @@ export const SectionHeader = styled.div` display: flex; flex-direction: column; align-items: center; + gap: 40px; `; export const SectionTitle = styled.div` @@ -69,7 +70,7 @@ export const SectionTitle = styled.div` export const SectionDescription = styled.div` font-size: 20px; - max-width: 500px; + max-width: 700px; ${media.palm` font-size: 16px; `}; diff --git a/website/src/components/common/swipeable.js b/website/src/components/common/swipeable.js index 6cfa403579..f39abb3544 100644 --- a/website/src/components/common/swipeable.js +++ b/website/src/components/common/swipeable.js @@ -22,6 +22,12 @@ import React, {PureComponent} from 'react'; import styled from 'styled-components'; import SwipeableViews from 'react-swipeable-views'; +const Container = styled.div` + display: flex; + flex-direction: column; + gap: 72px; +`; + const PaginationContainer = styled.div` display: flex; justify-content: center; @@ -58,12 +64,14 @@ export default class Swipeable extends PureComponent { render() { const {children, onChange, selectedIndex} = this.props; return ( -
+ {children} - -
+
+ +
+ ); } } diff --git a/website/src/components/footer.js b/website/src/components/footer.js index ceaa6784bb..4fb0ec7ba1 100644 --- a/website/src/components/footer.js +++ b/website/src/components/footer.js @@ -27,6 +27,7 @@ import {LinkButton} from './common/styled-components'; import {media} from '../styles'; import MapboxLogo from './mapbox-logo'; import NetlifyLogo from './netlify-logo'; +import FoursquareLogo from './foursquare-logo'; import {DEMO_LINK} from '../constants'; const Container = styled.div` @@ -49,9 +50,8 @@ const BrandingContainer = styled.div` margin-top: ${props => props.theme.margins.small}; `}; - display: grid; - grid-template-columns: repeat(4, auto); - column-gap: 20px; + display: flex; + gap: 20px; align-items: center; `; @@ -139,6 +139,8 @@ const SocialContainer = styled.div` `}; `; +const GITHUB_BUTTON_STYLE = {marginLeft: '5px'}; + export default class Footer extends PureComponent { render() { return ( @@ -149,6 +151,7 @@ export default class Footer extends PureComponent { + created by @@ -168,7 +171,7 @@ export default class Footer extends PureComponent { large outlineDark href="https://github.com/keplergl/kepler.gl" - style={{marginLeft: '5px'}} + style={GITHUB_BUTTON_STYLE} > Github diff --git a/website/src/components/foursquare-logo.js b/website/src/components/foursquare-logo.js new file mode 100644 index 0000000000..9662469416 --- /dev/null +++ b/website/src/components/foursquare-logo.js @@ -0,0 +1,63 @@ +import React from 'react'; +import {LOCATION_FOURSQUARE_LINK} from '../components/studio'; + +const LINK_STYLE = {display: 'flex'}; +const FoursquareLogo = () => ( + + + + + + + + + + + + + + + +); + +export default FoursquareLogo; diff --git a/website/src/components/home.js b/website/src/components/home.js index 9eae8ab9fb..c364c48126 100644 --- a/website/src/components/home.js +++ b/website/src/components/home.js @@ -31,6 +31,7 @@ import Tutorials from './tutorials'; import Walkthrough from './walkthrough'; import Features from './features'; import Ecosystems from './ecosystems'; +import Studio from './studio'; import Footer from './footer'; import Section from './common/section'; import Header from './header'; @@ -47,7 +48,8 @@ const SECTION_CONTENT = { features: Features, examples: Examples, tutorials: Tutorials, - ecosystems: Ecosystems + ecosystems: Ecosystems, + studio: Studio }; export default class Home extends PureComponent { diff --git a/website/src/components/mapbox-logo.js b/website/src/components/mapbox-logo.js index 2fe420c519..f7f4338ccf 100644 --- a/website/src/components/mapbox-logo.js +++ b/website/src/components/mapbox-logo.js @@ -25,7 +25,7 @@ const MapboxLogo = styled.a` background-size: 99px 28px; background-repeat: no-repeat; width: 100px; - display: inline-block; + display: flex; height: 28px; flex-shrink: 0; `; diff --git a/website/src/components/netlify-logo.js b/website/src/components/netlify-logo.js index f5652932d2..19db09faa2 100644 --- a/website/src/components/netlify-logo.js +++ b/website/src/components/netlify-logo.js @@ -20,8 +20,10 @@ import React from 'react'; +const LINK_STYLE = {display: 'flex'}; + const NetlifyLogo = () => ( - + + `${LOCATION_FOURSQUARE_LINK}?utm_source=Kepler&&utm_medium=partner_site&utm_campaign=${utmCampaign}`; + +const LEARN_MORE_LINK = getFSQLink('studio_signup'); + +const SECTIONS = [ + [ + { + imageUrl: fsqStudioUrl('screenshots/Flow_layer.png'), + icon: fsqStudioUrl('logos/Flow_layer.png'), + title: 'Flow layer', + description: 'Visualize origin-destination movement patterns', + link: getFSQLink('Keplermap1_Flow_layer') + }, + { + imageUrl: fsqStudioUrl('screenshots/3D_tiles.png'), + icon: fsqStudioUrl('logos/3D_tiles.png'), + title: '3D tiles', + description: 'Stream and render massive 3D geospatial datasets', + link: getFSQLink('Keplermap2_3D_tiles') + }, + { + imageUrl: fsqStudioUrl('screenshots/Analytics_modules.png'), + icon: fsqStudioUrl('logos/Analytics_modules.png'), + title: 'Analytics modules', + description: 'Expedite spatial data analysis to extract valuable information', + link: getFSQLink('Keplermap3_Analytics_modules') + } + ], + [ + { + imageUrl: fsqStudioUrl('screenshots/Charts.png'), + icon: fsqStudioUrl('logos/Charts.png'), + title: 'Charts', + description: 'Add statistics and charts to maps including tooltip, bar and line charts', + link: getFSQLink('Keplermap4_Charts') + }, + { + imageUrl: fsqStudioUrl('screenshots/Administrative_boundaries.png'), + icon: fsqStudioUrl('logos/Administrative_boundaries.png'), + title: 'Administrative boundaries', + description: 'Generate the boundary from columns containing administrative identifiers', + link: getFSQLink('Keplermap5_Adnministrative_boundaries') + }, + { + imageUrl: fsqStudioUrl('screenshots/Host_published_maps.png'), + icon: fsqStudioUrl('logos/Host_published_maps.png'), + title: 'Host published maps', + description: 'Host published maps that can be shared with a link', + link: getFSQLink('Keplermap6_Host_published_maps') + } + ], + [ + { + imageUrl: fsqStudioUrl('screenshots/Fleet_Visualization.png'), + icon: fsqStudioUrl('logos/Fleet_Visualization.png'), + title: 'Fleet Visualization', + description: 'Playback and study of the movement of entire fleets', + link: getFSQLink('Keplermap7_Fleet_Visualization') + }, + { + imageUrl: fsqStudioUrl('screenshots/Hextile_Analysis.png'), + icon: fsqStudioUrl('logos/Hextile_Analysis.png'), + title: 'Hextile Analysis', + description: 'H3 based tiling system designed for spatial analytics', + link: getFSQLink('Keplermap8_Hextile_Analysis') + }, + { + imageUrl: fsqStudioUrl('screenshots/Tile_Layers.png'), + icon: fsqStudioUrl('logos/Tile_Layers.png'), + title: 'Tile Layers', + description: 'Add layers from Vector tile, Raster tile, and WMS format', + link: getFSQLink('Keplermap9_Tile_Layers') + } + ], + [ + { + imageUrl: fsqStudioUrl('screenshots/Dataset_Operation.png'), + icon: fsqStudioUrl('logos/Dataset_Operation.png'), + title: 'Dataset Operation', + description: 'Apply expression, group-by, spatial join operations.', + link: getFSQLink('Keplermap10_Dataset_Operation') + }, + { + imageUrl: fsqStudioUrl('screenshots/Globe.png'), + icon: fsqStudioUrl('logos/Globe.png'), + title: 'Globe', + description: 'A 3D globe view of the Earth for planetary data.', + link: getFSQLink('Keplermap11_Globe') + }, + { + imageUrl: fsqStudioUrl('screenshots/Remote_Sensing.png'), + icon: fsqStudioUrl('logos/Remote_Sensing.png'), + title: 'Remote Sensing', + description: 'Analysis-Ready high bit-depth raster data in your browser', + link: getFSQLink('Keplermap12_Remote_Sensing') + } + ] +]; + +const Flex = styled.div` + display: flex; +`; + +const StudioContainer = styled(Flex)` + width: 100%; + align-items: center; + flex-direction: column; + gap: 72px; +`; + +const Section = styled(Flex)` + gap: 56px; + width: 100%; + justify-content: center; + justify-items: center; + padding-top: 8px; +`; + +const MapCardImage = styled.img` + height: 230px; + width: 386px; + box-shadow: 0 12px 24px 0 rgba(0, 0, 0, 0.5); + transition: transform 350ms; +`; + +const MapCard = styled.a` + display: flex; + flex-direction: column; + gap: 56px; + align-items: center; + cursor: pointer; + color: white; + + :visited { + color: white; + } + + :hover { + ${MapCardImage} { + transform: scale3d(1.05, 1.05, 1.05); + } + } +`; + +const MapCardBody = styled(Flex)` + flex-direction: column; + gap: 16px; + max-width: 300px; + align-items: center; +`; + +const MapCardIcon = styled.img` + height: 24px; + width: 24px; +`; + +const MapCardTitle = styled.div` + font-size: 20px; + line-height: 28px; + text-align: center; +`; + +const MapCardDescription = styled.div` + font-size: 16px; + line-height: 26px; + text-align: center; + opacity: 0.7; +`; + +const CardSection = ({cards}) => ( +
+ {cards.map(card => ( + + + + + {card.title} + {card.description} + + + ))} +
+); + +const WhiteLinkButton = styled(LinkButton)` + border-color: white; + color: white; + :hover, + :visited { + color: white; + } +`; + +const Studio = () => { + const [selectedIndex, setSelectedIndex] = useState(0); + const onChange = useCallback(index => { + setSelectedIndex(index); + }, []); + + return ( + + + {SECTIONS.map((cards, index) => ( + + ))} + + + Learn More + + + ); +}; + +export default React.memo(Studio); diff --git a/website/src/content.js b/website/src/content.js index a5b23ac33c..63b4efdfe9 100644 --- a/website/src/content.js +++ b/website/src/content.js @@ -41,12 +41,20 @@ export const SECTIONS = [ icon: cdnUrl('icons/features.png') }, { - id: 'ecosystems', - title: 'Ecosystem', - description: 'A collection of kepler.gl plugins built for common data analytics tools', - icon: fsqCdnUrl('ecosystem.png'), + id: 'studio', + title: 'Take The Next Step', + description: `Built on top of kelper.gl’s framework, Foursquare Studio is a free, +powerful geospatial analytics and visualization tool, with new features and updates released every few weeks.`, + icon: fsqCdnUrl('fsqlogo.png'), isDark: true }, + // { + // id: 'ecosystems', + // title: 'Ecosystem', + // description: 'A collection of kepler.gl plugins built for common data analytics tools', + // icon: fsqCdnUrl('ecosystem.png'), + // isDark: true + // }, { id: 'examples', title: 'See What People Created', diff --git a/website/src/utils.js b/website/src/utils.js index a230b2bf90..37d01133f3 100644 --- a/website/src/utils.js +++ b/website/src/utils.js @@ -26,3 +26,7 @@ export function cdnUrl(path) { export function fsqCdnUrl(path) { return `${KEPLER_FSQ_BUCKET}/${path}`; } + +export function fsqStudioUrl(path) { + return fsqCdnUrl(`studio/${path}`); +}