Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
TylerMatteo authored May 10, 2024
0 parents commit 07ea193
Show file tree
Hide file tree
Showing 23 changed files with 20,575 additions and 0 deletions.
77 changes: 77 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
root: true,
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
},
env: {
browser: true,
commonjs: true,
es6: true,
},

// Base config
extends: ["eslint:recommended", "plugin:prettier/recommended"],

overrides: [
// React
{
files: ["**/*.{js,jsx,ts,tsx}"],
plugins: ["react", "jsx-a11y"],
extends: [
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended",
],
settings: {
react: {
version: "detect",
},
formComponents: ["Form"],
linkComponents: [
{ name: "Link", linkAttribute: "to" },
{ name: "NavLink", linkAttribute: "to" },
],
"import/resolver": {
typescript: {},
},
},
},

// Typescript
{
files: ["**/*.{ts,tsx}"],
plugins: ["@typescript-eslint", "import"],
parser: "@typescript-eslint/parser",
settings: {
"import/internal-regex": "^~/",
"import/resolver": {
node: {
extensions: [".ts", ".tsx"],
},
typescript: {
alwaysTryTypes: true,
},
},
},
extends: [
"plugin:@typescript-eslint/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
],
},

// Node
{
files: [".eslintrc.cjs", "vite.config.js"],
env: {
node: true,
},
},
],
};
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules

/.cache
/build
/public/build
.env
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx lint-staged
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v20.12.2
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Description

Template for Remix applications with Deck.gl and @nycplanning/streetscape (based on Chakra UI)

## Dev setup
- `nvm use`
- `npm i`
- `npm run dev`

## E2E Testing
This application is currently configured to use Playwright for end to end testing.

Tests should be kept in the `./tests` folder and can be run with the `npm run test` command.

The `playwright.config` is used to configure tests and can be used to select which browsers we run our tests on, including mobile.
9 changes: 9 additions & 0 deletions app/components/atlas.client.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { render } from "@testing-library/react";
import { Atlas } from "./atlas.client";

describe("Atlas", () => {
it("should render", () => {
const { container } = render(<Atlas />);
expect(container.querySelector("#deckgl-wrapper")).toBeInTheDocument();
});
});
25 changes: 25 additions & 0 deletions app/components/atlas.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { DeckGL } from "@deck.gl/react";
import { Map } from "react-map-gl/maplibre";
import "maplibre-gl/dist/maplibre-gl.css";

const INITIAL_VIEW_STATE = {
longitude: -74.0008,
latitude: 40.7018,
zoom: 10,
bearing: 0,
pitch: 0,
};

export function Atlas() {
return (
<DeckGL
initialViewState={INITIAL_VIEW_STATE}
controller={true}
style={{ height: "100vh", width: "100vw" }}
>
<Map
mapStyle={"https://tiles.planninglabs.nyc/styles/positron/style.json"}
></Map>
</DeckGL>
);
}
9 changes: 9 additions & 0 deletions app/components/header.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { render, screen } from "@testing-library/react";
import { Header } from "./header";

describe("Header", () => {
it("should render", () => {
render(<Header />);
expect(screen.getByText(/Hello world/)).toBeInTheDocument();
});
});
18 changes: 18 additions & 0 deletions app/components/header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Box, Button } from "@nycplanning/streetscape";

export function Header() {
return (
<Box
bg="gray.700"
w="100%"
p={4}
color="white"
position="absolute"
zIndex={2}
>
<Button variant="secondary" size="sm">
Hello world!
</Button>
</Box>
);
}
28 changes: 28 additions & 0 deletions app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import createEmotionCache from "@emotion/cache";
import { CacheProvider } from "@emotion/react";
import { RemixBrowser } from "@remix-run/react";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";

const hydrate = () => {
const emotionCache = createEmotionCache({ key: "css" });

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<CacheProvider value={emotionCache}>
<RemixBrowser />
</CacheProvider>
</StrictMode>,
);
});
};

if (typeof requestIdleCallback === "function") {
requestIdleCallback(hydrate);
} else {
// Safari doesn't support requestIdleCallback
// https://caniuse.com/requestidlecallback
setTimeout(hydrate, 1);
}
126 changes: 126 additions & 0 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { PassThrough } from "stream";

import createEmotionCache from "@emotion/cache";
import { CacheProvider as EmotionCacheProvider } from "@emotion/react";
import createEmotionServer from "@emotion/server/create-instance";
import type { EntryContext } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { isbot } from "isbot";
import { renderToPipeableStream } from "react-dom/server";

const ABORT_DELAY = 5000;

const handleRequest = (
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) =>
isbot(request.headers.get("user-agent") || "")
? handleBotRequest(
request,
responseStatusCode,
responseHeaders,
remixContext,
)
: handleBrowserRequest(
request,
responseStatusCode,
responseHeaders,
remixContext,
);
export default handleRequest;

const handleBotRequest = (
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) =>
new Promise((resolve, reject) => {
let didError = false;
const emotionCache = createEmotionCache({ key: "css" });

const { pipe, abort } = renderToPipeableStream(
<EmotionCacheProvider value={emotionCache}>
<RemixServer context={remixContext} url={request.url} />
</EmotionCacheProvider>,
{
onAllReady: () => {
const reactBody = new PassThrough();
const emotionServer = createEmotionServer(emotionCache);

const bodyWithStyles = emotionServer.renderStylesToNodeStream();
reactBody.pipe(bodyWithStyles);

responseHeaders.set("Content-Type", "text/html");

resolve(
new Response(bodyWithStyles as unknown as BodyInit, {
headers: responseHeaders,
status: didError ? 500 : responseStatusCode,
}),
);

pipe(reactBody);
},
onShellError: (error: unknown) => {
reject(error);
},
onError: (error: unknown) => {
didError = true;

console.error(error);
},
},
);

setTimeout(abort, ABORT_DELAY);
});

const handleBrowserRequest = (
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) =>
new Promise((resolve, reject) => {
let didError = false;
const emotionCache = createEmotionCache({ key: "css" });

const { pipe, abort } = renderToPipeableStream(
<EmotionCacheProvider value={emotionCache}>
<RemixServer context={remixContext} url={request.url} />
</EmotionCacheProvider>,
{
onShellReady: () => {
const reactBody = new PassThrough();
const emotionServer = createEmotionServer(emotionCache);

const bodyWithStyles = emotionServer.renderStylesToNodeStream();
reactBody.pipe(bodyWithStyles);

responseHeaders.set("Content-Type", "text/html");

resolve(
new Response(bodyWithStyles as unknown as BodyInit, {
headers: responseHeaders,
status: didError ? 500 : responseStatusCode,
}),
);

pipe(reactBody);
},
onShellError: (error: unknown) => {
reject(error);
},
onError: (error: unknown) => {
didError = true;

console.error(error);
},
},
);

setTimeout(abort, ABORT_DELAY);
});
Loading

0 comments on commit 07ea193

Please sign in to comment.