Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor to use Remix #65

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
4 changes: 4 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
};
27 changes: 5 additions & 22 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,23 +1,6 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
node_modules

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
/.cache
/.netlify
/public/build
.env
82 changes: 47 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,70 @@
# Rica
> **Warning**
> The `@remix-run/netlify` runtime adapter has been deprecated in favor of
> `@netlify/remix-adapter` and will be removed in Remix v2. Please update your
> code by changing all `@remix-run/netlify` imports to `@netlify/remix-adapter`.
> Keep in mind that `@netlify/remix-adapter` requires `@netlify/functions@^1.0.0`,
> which is a breaking change compared to the current supported `@netlify/functions`
> versions in `@remix-run/netlify`.

[![DOI](https://zenodo.org/badge/391155862.svg)](https://zenodo.org/badge/latestdoi/391155862)
# Welcome to Remix!

As part of the ME-ICA pipeline, `Rica` (Reports for ICA) provides a reporting tool for ICA decompositions performed with [tedana](https://github.com/ME-ICA/tedana) and [aroma](https://github.com/ME-ICA/aroma).
- [Remix Docs](https://remix.run/docs)
- [Netlify Functions](https://www.netlify.com/products/functions/)

**Pronunciation:** [ˈrika]. For an audio recording on how to pronounce Rica [see here](https://easypronunciation.com/en/spanish/word/rica).
## Netlify Setup

## About
1. Install the [Netlify CLI](https://www.netlify.com/products/dev/):

`Rica` originally came out as an alternative to the reports provided by [tedana](https://github.com/ME-ICA/tedana), with the aim of making manual classification of ICA components possible. At the same time, the tool aspires to be of value for ICA decompositions made with tools other than `tedana`. `Rica` assumes you're working with files that mimic the outputs of `tedana`.

## How to use Rica
```sh
npm i -g netlify-cli
```

Even if Rica is designed to be simple to use, you might want to see how you can use the app by watching this [tutorial video](https://www.loom.com/share/ad37cf6f3c2d41e48721f62168a8284e).
If you have previously installed the Netlify CLI, you should update it to the latest version:

Rica also supports keyboard shortcuts on the ICA components page. You can use the following shortcuts:
```sh
npm i -g netlify-cli@latest
```

- `a`: Accept component.
- `r`: Reject component.
- `i`: Ignore component.
- `left arrow`: Go to previous component.
- `right arrow`: Go to next component.
2. Sign up and log in to Netlify:

## Using Rica online
```sh
netlify login
```

Just head over to https://rica-fmri.netlify.app and have fun!
3. Create a new site:

## Using Rica locally
```sh
netlify init
```

### Installation
## Development

`Rica` can be installed by cloning this repository and executing the following command in the cloned repository:
The Remix dev server starts your app in development mode, rebuilding assets on file changes. To start the Remix dev server:

```npm install```
```sh
npm run dev
```

In order to run the tool locally, two options exist:
Open up [http://localhost:3000](http://localhost:3000), and you should be ready to go!

#### 1. Using a localhost
The Netlify CLI builds a production version of your Remix App Server and splits it into Netlify Functions that run locally. This includes any custom Netlify functions you've developed. The Netlify CLI runs all of this in its development mode.

By executing the `npm start` command in the cloned repository, `Rica` will open in a new browser tab at [http://localhost:3000](http://localhost:3000) and you will be able to use the tool.
```sh
netlify dev
```

#### 2. Compiling the tool
Open up [http://localhost:3000](http://localhost:3000), and you should be ready to go!

You could also compile the project so that you can use the tool just by opening an HTML file. For that, it is necessary to execute the following commands in the cloned repository.
Note: When running the Netlify CLI, file changes will rebuild assets, but you will not see the changes to the page you are on unless you do a browser refresh of the page. Due to how the Netlify CLI builds the Remix App Server, it does not support hot module reloading.

```bash
npm run build
npx gulp
mv build/index.html build/rica.html
open build/rica.html
```
## Deployment

> Pro tip: when you open rica.html for the first time, BOOKMARK IT 😉
There are two ways to deploy your app to Netlify, you can either link your app to your git repo and have it auto deploy changes to Netlify, or you can deploy your app manually. If you've followed the setup instructions already, all you need to do is run this:

## Getting involved
```sh
# preview deployment
netlify deploy --build

Want to learn more about our plans for developing `Rica`? Have a question, comment, or suggestion? Open or comment on one of our issues!
# production deployment
netlify deploy --build --prod
```
18 changes: 18 additions & 0 deletions app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* By default, Remix will handle hydrating your app on the client for you.
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
* For more information, see https://remix.run/file-conventions/entry.client
*/

import { RemixBrowser } from "@remix-run/react";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
</StrictMode>
);
});
135 changes: 135 additions & 0 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/**
* By default, Remix will handle generating the HTTP Response for you.
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
* For more information, see https://remix.run/file-conventions/entry.server
*/

import { PassThrough } from "node:stream";

import type { AppLoadContext, EntryContext } from "@remix-run/node";
import { Response } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import isbot from "isbot";
import { renderToPipeableStream } from "react-dom/server";

const ABORT_DELAY = 5_000;

export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
loadContext: AppLoadContext
) {
return isbot(request.headers.get("user-agent"))
? handleBotRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
)
: handleBrowserRequest(
request,
responseStatusCode,
responseHeaders,
remixContext
);
}

function handleBotRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
return new Promise((resolve, reject) => {
let shellRendered = false;
const { pipe, abort } = renderToPipeableStream(
<RemixServer
context={remixContext}
url={request.url}
abortDelay={ABORT_DELAY}
/>,
{
onAllReady() {
shellRendered = true;
const body = new PassThrough();

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

resolve(
new Response(body, {
headers: responseHeaders,
status: responseStatusCode,
})
);

pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error(error);
}
},
}
);

setTimeout(abort, ABORT_DELAY);
});
}

function handleBrowserRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
return new Promise((resolve, reject) => {
let shellRendered = false;
const { pipe, abort } = renderToPipeableStream(
<RemixServer
context={remixContext}
url={request.url}
abortDelay={ABORT_DELAY}
/>,
{
onShellReady() {
shellRendered = true;
const body = new PassThrough();

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

resolve(
new Response(body, {
headers: responseHeaders,
status: responseStatusCode,
})
);

pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error(error);
}
},
}
);

setTimeout(abort, ABORT_DELAY);
});
}
34 changes: 34 additions & 0 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { cssBundleHref } from "@remix-run/css-bundle";
import type { LinksFunction } from "@remix-run/node";
import stylesheet from "~/tailwind.css";
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";

export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet },
];

export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
36 changes: 36 additions & 0 deletions app/routes/_index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { ActionArgs, V2_MetaFunction } from "@remix-run/node";
import TabsRica from "./tabs";
import LoadButton from "./loadButton";
import { useLoaderData } from "@remix-run/react";
import { getSession } from "../sessions";

export const meta: V2_MetaFunction = () => {
return [
{ title: "Rica" },
{ name: "description", content: "Reports for ICA" },
];
};

// Set default values and save into cookies
export const loader = async ({ request }: ActionArgs) => {
const session = await getSession(request.headers.get("Cookie"));
session.set("info", "This is an example of a tabbed interface.");
session.set("path", "/User/rica/data/tedana/results");
// etc.
return { message: "Hello, World!" };
};

export default function Index() {
let data = useLoaderData();

console.log("data", data);

return (
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
<div className="flex flex-col h-[100%]">
<TabsRica data={data} />
<LoadButton />
</div>
</div>
);
}
Loading