๐คฏ React๋ฅผ ์ฌ์ฉํ์ฌ ์ฒซ ๋ฒ์งธ Neuro ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์ถ
์ค๋๋ , ์ฐ๋ฆฌ๋ ์๋ก ๋ค๋ฅธ ์ ํ์ ์์ฉ ํ๋ก๊ทธ๋จ์ ๊ฐ๋ฐํ ๊ฒ์ด๋ค.์ฐ๋ฆฌ๋ ๋น์ ์ ์ธ์ง ์ํ์ ๋ฐ๋ผ ์ํ๋ฅผ ๋ฐ๊พธ๋ ์์ฉ ํ๋ก๊ทธ๋จ์ ๊ฐ๋ฐํ ๊ฒ์ ๋๋ค.
๋ด ๋ง ๋ค ๋ค์ด.
๋ง์ฝ ์ฐ๋ฆฌ์ ์์ฉ ํ๋ก๊ทธ๋จ์ด ๋น์ ์ ํ์จ๋์ ๋ฐ๋ผ WebGL ํด์์ ์ด๋์ ๋ฐ๊พธ๋ฉด ์ด๋ป๊ฒ ๋ ๊น์?๋น์ ์ ๊ฐ๊ฐ์ ์ํด ๊ตฌ๋๋๋'์๊ฐ ๋ช ์'์ฒดํ.
ํธ์ํฐ ๊ฐ๋ ๋์
์ ๋ฌธ
Create React ์ฑ(CRA)์ผ๋ก ๋จผ์ ์ดํ๋ฆฌ์ผ์ด์ ์ ์๋ดํฉ๋๋ค.VS ์ฝ๋์์ ํญ๋ชฉ์ ์ด๊ณ ๋ก์ปฌ์์ ํ๋ก๊ทธ๋จ์ ์คํํฉ๋๋ค.
npx create-react-app mind-controlled-ocean
code mind-controlled-ocean
npm start
React ์์ฉ ํ๋ก๊ทธ๋จ ๊ธฐ๋ณธ ๋ณด๊ธฐ ๋ง๋ค๊ธฐ
๐ ์ธ์ฆ
์ฐ๋ฆฌ๋ ํ๋ผ์ด๋ฒ์๋ฅผ ๋ฏฟ๋๋ค.์ด๊ฒ์ด ๋ฐ๋ก ๊ฐ๋ ์ด ์ฒ์์ผ๋ก ์ ๋ถ ๊ฒ์ฆ ๊ธฐ๋ฅ์ ๊ฐ์ถ ๋์ปดํจํฐ์ธ ์ด์ ๋ค.auth๋ฅผ ์์ฉ ํ๋ก๊ทธ๋จ์ ์ถ๊ฐํ๋ ๊ฒ์ ๋งค์ฐ ๊ฐ๋จํฉ๋๋ค.์ด๋ฅผ ์ํด์๋ ์ธ์ฆ ์ํ๋ฅผ ๋๊ธฐํํ๋ ๋ก๊ทธ์ธ ํผ๊ณผ ๋ถ์์ฉ 3๊ฐ๊ฐ ํ์ํฉ๋๋ค.
๋๋ ์ปดํจํฐ์ ์ฐ๊ฒฐํด์ผ ํ ๊ฒ์ ๋จ์ง ํ๋Neurosity account์ ์ฅ์น ID์ ๋๋ค. ๋ก๊ทธ์ธ ํผ์ ์ํ ์ ๊ตฌ์ฑ ์์๋ฅผ ๋ง๋๋ ๊ฒ๋ถํฐ ์์ํ๊ฒ ์ต๋๋ค. ์ด ๊ตฌ์ฑ ์์๋ ์ด ์ ๋ณด๋ฅผ ์์งํฉ๋๋ค.
// src/components/LoginForm.js
import React, { useState } from "react";
export function LoginForm({ onLogin, loading, error }) {
const [deviceId, setDeviceId] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
function onSubmit(event) {
event.preventDefault();
onLogin({ deviceId, email, password });
}
return (
<form className="card login-form" onSubmit={onSubmit}>
<h3 className="card-heading">Login</h3>
{!!error ? <h4 className="card-error">{error}</h4> : null}
<div className="row">
<label>Notion Device ID</label>
<input
type="text"
value={deviceId}
disabled={loading}
onChange={e => setDeviceId(e.target.value)}
/>
</div>
<div className="row">
<label>Email</label>
<input
type="email"
value={email}
disabled={loading}
onChange={e => setEmail(e.target.value)}
/>
</div>
<div className="row">
<label>Password</label>
<input
type="password"
value={password}
disabled={loading}
onChange={e => setPassword(e.target.value)}
/>
</div>
<div className="row">
<button type="submit" className="card-btn" disabled={loading}>
{loading ? "Logging in..." : "Login"}
</button>
</div>
</form>
);
}
๐ Add styles the form - here's the CSS I used.
์ด ๊ตฌ์ฑ ์์๋
deviceId
, email
๋ฐ password
์ํ๋ฅผ ์ ์งํฉ๋๋ค.๋ํ ์ฐ๋ฆฌ์ ํผ ๊ตฌ์ฑ ์์๋ ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ ๋จ์ถ๋ฅผ ๋๋ ์ ๋ ์ด ๋๊ตฌ๋ฅผ ์คํํ onLogin
๋๊ตฌ๋ฅผ ๋ฐ์๋ค์ผ ๊ฒ์
๋๋ค.์ฐ๋ฆฌ๋ ๋ํ ํผ ์ ์ถ ๊ณผ์ ์์ loading
์์ดํ
๊ณผ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ ๋ ํ์๋๋ error
๋ฉ์์ง ์์ดํ
์ ๋ฐ์๋ค์ผ ๊ฒ์
๋๋ค.ํ์ฌ ๋ก๊ทธ์ธ ๊ตฌ์ฑ ์์๋ฅผ ๋ง๋ค์์ต๋๋ค. ๋ก๊ทธ์ธ ํ์ด์ง๋ฅผ ์ถ๊ฐํ๋ฉด ์ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํ ๊ฒ์ ๋๋ค.
// src/pages/Login.js
import React, { useState, useEffect } from "react";
import { LoginForm } from "../components/LoginForm";
export function Login({ notion, user, setUser, setDeviceId }) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [isLoggingIn, setIsLoggingIn] = useState(false);
function onLogin({ email, password, deviceId }) {
if (email && password && deviceId) {
setError("");
setEmail(email);
setPassword(password);
setDeviceId(deviceId);
} else {
setError("Please fill the form");
}
}
return (
<LoginForm
onLogin={onLogin}
loading={isLoggingIn}
error={error}
/>
);
}
๋ก๊ทธ์ธ ์์ ๊ตฌ์ฑ ์์์ด ํ์ด์ง์ ๋ชฉํ๋ ๋ก๊ทธ์ธ ํผ์ ํ์ํ๊ณ
setError
๊ธฐ๋ฅ์ ํตํด ๊ธฐ๋ณธ ํผ ๊ฒ์ฆ์ ์ถ๊ฐํ๊ณ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ์คํํ๋ ๊ฒ์
๋๋ค.ํ์์ ๋ํด ๋ถ์์ฉ์ ์ถ๊ฐํฉ๋๋ค. ๋ถ์์ฉ์ email
, password
, ํ์ด์ง๋ฅผ ๋ฐ์ ๋๊ตฌ์ ๋๊ธฐํ๋ฉ๋๋ค.useEffect(() => {
if (!user && notion && email && password) {
login();
}
async function login() {
setIsLoggingIn(true);
const auth = await notion
.login({ email, password })
.catch(error => {
setError(error.message);
});
if (auth) {
setUser(auth.user);
}
setIsLoggingIn(false);
}
}, [email, password, notion, user, setUser, setError]);
๊ฐ๋
API๋ก ์ค์ ๋ ์ธ์ฆ ์ฌ์ฉ์ ์ธ์
์ ์ ์ฅํ๋ ๊ฐ์ฒด๋ก ๊ฐ์ฃผํ ์ ์์ต๋๋ค.๋ฐ๋ผ์ ์ฐ๋ฆฌ๋ ์ธ์ฆ ์ธ์
์ด ์๋ ์ํฉ์์๋ง user
ํจ์๋ฅผ ํธ์ถํฉ๋๋ค. ์ฐ๋ฆฌ๋ ์ํ์ ๊ฐ๋
์ ์ธ ์ค๋ก๊ฐ ์๊ณ ์ฌ์ฉ์๊ฐ ์ ์๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ์ถํ์ต๋๋ค.๊ณง ์ฐ๋ฆฌ๊ฐ ์ด๋ป๊ฒ ์์ดํ ์ ์ป์ ์ ์๋์ง ์๊ฒ ๋ ๊ฒ์ด๋ค:
login()
.๊ทธ๋ฌ๋ ์ฐ๋ฆฌ๊ฐ ์ด ์ผ์ ํ๊ธฐ ์ ์ ์ฐ๋ฆฌ notion, user, setUser, setDeviceId
๋ก ๋์๊ฐ ๊ทธ๊ฒ์ ๋ชจ๋ ํจ๊ป ๋๊ธฐ ์์ํ์.โ๏ธ ์ ํ๋ฆฌ์ผ์ด์ ์ํ
์ด ํ๋ก๊ทธ๋จ์ ๋จ์์ฑ์ ์ ์งํ๊ธฐ ์ํด์, ์ฐ๋ฆฌ๋ React์
App.js
๊ฐ๊ณ ๋ฆฌ, Reach ๊ณต์ ๊ธฐ, react-use ๐ ๋ฅผ ์ฌ์ฉํ์ฌ ๋น์ ์๊ฒ ๊ฐ์ ธ๋ค ์ค ๋ก์ปฌ ์ ์ฅ ๊ฐ๊ณ ๋ฆฌ๋ง ์ฌ์ฉํ ๊ฒ์
๋๋ค.์ด๊ฒ์ ์ฐ๋ฆฌ์ ์ผ๋ฐ์ ์ธ ์์ฉ ํ๋ก๊ทธ๋จ ์ํ ์ ์ฑ
์ ์ ์ญ ์ํ๋ฅผ ์์ฉ ํ๋ก๊ทธ๋จ ๊ตฌ์ฑ ์์ ์์ค์ผ๋ก ์ ์งํ๊ณ ํ์ํ ๋๊ตฌ๋ฅผ ํ์ ๊ตฌ์ฑ ์์์ ์ ๋ฌํ๋ ๊ฒ์ ํฌํจํ ๊ฒ์ด๋ค.useState
// src/App.js
import React, { useState, useEffect } from "react";
import { Router, navigate } from "@reach/router";
import useLocalStorage from "react-use/lib/useLocalStorage";
import { Login } from "./pages/Login";
export function App() {
const [notion, setNotion] = useState(null);
const [user, setUser] = useState(null);
const [deviceId, setDeviceId] = useLocalStorage("deviceId");
const [loading, setLoading] = useState(true);
return (
<Router>
<Login
path="/"
notion={notion}
user={user}
setUser={setUser}
setDeviceId={setDeviceId}
/>
</Router>
);
}
๐ I found the Reach Router by Ryan Florence (and friends) to be the perfect balance between simplicity and predictability. Their documentation is extremely clear and allowed me to implement basic routing within minutes.
๋ก์ปฌ ์ ์ฅ์์
npm install @reach/router react-use
๋ฅผ ์ ์ฅํ๊ธฐ๋ก ๊ฒฐ์ ํ ์ด์ ๋ฅผ ์๊ณ ์ถ๋ค๋ฉด, ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธํ๊ธฐ ์ ๊ณผ ํ์ ์ ๊ทผํด์ผ ํ๊ธฐ ๋๋ฌธ์
๋๋ค.๊ทธ๊ฒ์ ์ฌ๋ฌ ๋ฒ ๋ค์ด๊ฐ ํ์๊ฐ ์์ ์ ๋๋ก ๋ ์ข์ ์ฌ์ฉ์ ์ฒดํ์ ์ ๊ณตํ๋ค.๐ง ๊ฐ๋
์ด์ ์ฐ๋ฆฌ๋ API๋ฅผ ์ค์นํ๊ณ ๊ฐ์ ธ์ค๊ธฐ
deviceId
๋ฅผ ํตํด ์์ฉ ํ๋ก๊ทธ๋จ๊ณผ ๊ฐ๋
์ ํตํฉํ ์ ์๋ ๊ธฐ๋ณธ์ ์ธ ์ํ ๊ด๋ฆฌ๋ฅผ ๊ฐ์ถ์๋ค.App.js
๐คฏ The Notion API enables full communication between the device and the app. We'll use it to get real-time feedback based on the user's cognitive state. For this app, we'll work specifically with the calm API.
import { Notion } from "@neurosity/notion";
๊ฐ๋
์ฅ์น์ ์ฐ๊ฒฐํ๋ ๊ฒ์ ๋งค์ฐ ๊ฐ๋จํ๋ค.์ฐ๋ฆฌ๋ ์๋ก์ด ๊ฐ๋
์ ์ค๋กํํ๊ณ ์ฅ์น ID๋ฅผ ์ ๋ฌํฉ๋๋ค. ๋ถ์์ฉ์ ์ถ๊ฐํ ์ ์์ต๋๋ค. npm install @neurosity/notion
๊ณผ ๋๊ธฐํํ์ฌ ์ค๋ก๋ฅผ ์์ฉ ํ๋ก๊ทธ๋จ ๊ตฌ์ฑ ์์ ์ํ๋ก ์ค์ ํ ์ ์์ต๋๋ค.๐ docs.neurosity.co์์ ์ ์ฒด ๊ฐ๋ ๋ฌธ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.
useEffect(() => {
if (deviceId) {
const notion = new Notion({ deviceId }); // ๐ฒ
setNotion(notion);
} else {
setLoading(false);
}
}, [deviceId]);
์ฐ๋ฆฌ๊ฐ ๋๊ธฐํํ๊ณ ์ ํ๋ ๋ ๋ค๋ฅธ ์ํ๋ deviceId
์ํ์ด๋ค.๋ค์ ์์์์ ์ฐ๋ฆฌ๋
user
์ค๋ก์ ๊ฐ๊ณผ ๋๊ธฐํํ๋ ๋ถ์์ฉ์ ์ถ๊ฐํ ๊ฒ์ด๋ค.๋ง์ฝ notion
์ด ์ค์ ๋์ง ์์๋ค๋ฉด, notion
์ค๋ก๋ฅผ ๋ง๋ค๊ธฐ ์ ์, ์ฐ๋ฆฌ๋calm ์ด๋ฒคํธ ๊ตฌ๋
์ ๊ฑด๋๋ธ ๊ฒ์
๋๋ค.useEffect(() => {
if (!notion) {
return;
}
const subscription = notion.onAuthStateChanged().subscribe(user => {
if (user) {
setUser(user);
} else {
navigate("/");
}
setLoading(false);
});
return () => {
subscription.unsubscribe();
};
}, [notion]);
๋ง์ฝ ์์ฉ ํ๋ก๊ทธ๋จ์ ํ์ฑ ์ฌ์ฉ์ ์ธ์
์ด ์๋ค๋ฉด, ์ด ์ธ์
์ ๊ฐ๋
๊ฒ์ฆ์ ์ํด ์ง์๋ฉ๋๋ค. ์ฐ๋ฆฌ๋ ํ์ฌ ๋ก๊ทธ์ธํ ์ฌ์ฉ์๋ฅผ ์ป๊ณ ์ด๋ฅผ ์์ฉ ํ๋ก๊ทธ๋จ ๊ตฌ์ฑ ์์์ ์ํ๋ก ์ค์ ํ ๊ฒ์
๋๋ค.notion
๋ฐฉ๋ฒ์ ๊ด์ฐฐํ ์ ์๋ ์ฌ์ฉ์ ์ธ์ฆ ์ด๋ฒคํธ๋ฅผ ๋๋๋ ค์ค๋๋ค.์ฃผ์ํด์ผ ํ ๊ฒ์ ๋ธ๋ผ์ฐ์ ์์ ๊ฐ๋
API๋ฅผ ์ฌ์ฉํ ๋ ์ธ์
์ ๋ก์ปฌ ์ ์ฅ์๋ฅผ ํตํด ์ง์๋๋ค๋ ๊ฒ์ด๋ค.๋ฐ๋ผ์ ์์ฉ ํ๋ก๊ทธ๋จ์ ๋ซ๊ฑฐ๋ ํ์ด์ง๋ฅผ ๋ค์ ๋ก๋ํ๋ฉด ์ธ์
์ด ์ง์๋๊ณ onAuthStateChanged
์ด ์๋ ์ฌ์ฉ์ ์ธ์
์ผ๋ก ๋์๊ฐ๋๋ค.์ด๊ฒ์ด ๋ฐ๋ก ์ฐ๋ฆฌ๊ฐ ์ํ๋ ๊ฒ์ด๋ค.์ธ์ ์ด ๊ฐ์ง๋์ง ์์ผ๋ฉด ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋ํ ์ ์์ต๋๋ค.๊ทธ๋ ์ง ์์ผ๋ฉด
onAuthStateChanged
์ ์ด์
๋ธ๋ฆฌ ์ํ๋ก ์ค์ ํฉ๋๋ค.์ฐ๋ฆฌ๋ ๋ก๊ทธ์์ ํ์ด์ง๋ฅผ ์ถ๊ฐํด์ ์์ ํ ์ธ์ฆ์ ์์ฑํ ์ ์๋ค.
// src/pages/Logout.js
import { useEffect } from "react";
import { navigate } from "@reach/router";
export function Logout({ notion, resetState }) {
useEffect(() => {
if (notion) {
notion.logout().then(() => {
resetState();
navigate("/");
});
}
}, [notion, resetState]);
return null;
}
๋ก๊ทธ์์ ํ์ด์ง๋ React ๊ตฌ์ฑ ์์์ผ ๋ฟ DOM ์์๋ ์์ต๋๋ค.์ฐ๋ฆฌ๊ฐ ํ์๋ก ํ๋ ์ ์ผํ ๋
ผ๋ฆฌ๋ ๋ถ์์ฉ์ด๋ค. ๋ง์ฝ null
์ค๋ก๊ฐ ์กด์ฌํ๋ค๋ฉด, ๊ทธ๊ฒ์ user
๋ฐฉ๋ฒ์ ํธ์ถํ ๊ฒ์ด๋ค.๋ง์ง๋ง์ผ๋ก, ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์์ํ ํ์ ์ด๊ธฐ ๊ฒฝ๋ก๋ก ๋ค์ ์ง์ ํฉ๋๋ค.์ด ๊ตฌ์ฑ ์์๋ ํ์ฌ
notion.logout()
์์ ๋ผ์ฐํ
์ผ๋ก ์ถ๊ฐํ ์ ์์ต๋๋ค.// src/App.js
// ...
import { Logout } from "./pages/Logout";
// ...
return (
<Router>
{/* ... */}
<Logout path="/logout" notion={notion} resetState={() => {
setNotion(null);
setUser(null);
setDeviceId("");
}} />
</Router>
);
์ด์ ๊ฒ์ฆ์ด ๋๋ฌ์ต๋๋ค. ์ฐ๋ฆฌ์ ์ธ์ง ์ํ์ ๋ฐ๋ผ ์์ฉ ํ๋ก๊ทธ๋จ ๋
ผ๋ฆฌ๋ฅผ ์ถ๊ฐํฉ์๋ค!๐ WebGL ํด์
David์ WebGL ocean์ ๋ณด์์ ๋, ๋๋ ๊ทธ๊ฒ์ ์ฌ๋ํ๊ฒ ๋์๋ค.๊ทธ๋์ ์ด ๊ฐ๋ ์ผ๋ก ๋ ์จ๊ฐ ํ๋๋ฅผ ์์ง์ด๋ ๋ฐ ์ํฅ์ ์ฃผ๋ ๊ฒ์ ํฅ๋ฏธ๋ก์ด ์คํ๊ณผ ๊ฐ๋ค.
๐ก Fun fact: This ocean wave simulation was created in 2013 by David Li with JavaScript and WebGL. It has been featured as part of Google Experiments. I was happy to see it was open-sourced under the MIT license.
๋ค์ ๋ถ๋ถ์์ ์ฐ๋ฆฌ์ ์๊ฐ์ ์ ๊ตฌ์ฑ ์์๋ฅผ ๋ง๋๋ ๊ฒ์ ๋๋ค. ์ด ๊ตฌ์ฑ ์์๋ WebGL ocean์ ์ฌ์ฉํ ๊ฒ์ ๋๋ค.๋ฐ๋ผ์ Ocean
notion
์ด๋ผ๋ ๋๋ ํฐ๋ฆฌ๋ฅผ ๋ง๋ค๊ณ ๋ค์ ํ์ผ์ ์ถ๊ฐํฉ๋๋ค.// src/components/Ocean/Ocean.js
import React, { useState, useEffect, useRef } from "react";
import useRafState from "react-use/lib/useRafState";
import { Simulator, Camera } from "./simulation.js"; // by David Li
import { mapCalmToWeather } from "./weather.js";
const camera = new Camera();
export function Ocean({ calm }) {
const ref = useRef();
const [simulator, setSimulator] = useState();
const [lastTime, setLastTime] = useRafState(Date.now());
useEffect(() => {
const { innerWidth, innerHeight } = window;
const simulator = new Simulator(ref.current, innerWidth, innerHeight);
setSimulator(simulator);
}, [ref, setSimulator]);
useEffect(() => {
if (simulator) {
const currentTime = Date.now();
const deltaTime = (currentTime - lastTime) / 1000 || 0.0;
setLastTime(currentTime);
simulator.render(deltaTime, camera);
}
}, [simulator, lastTime, setLastTime]);
return <canvas className="simulation" ref={ref}></canvas>;
}
๋ง์ฝ ๋ชจ๋ ๊ฒ์ด ์์กฐ๋กญ๋ค๋ฉด, ์ฐ๋ฆฌ๋ ๋ฐ๋์ ์ด ์ ์ ๋ณด์์ผ ํ๋ค.ํ๋ ์๋ฎฌ๋ ์ด์
์ฌ๊ธฐ์ ์ผ์ด๋ ์ผ์ ์์ธํ ์๊ฐํ๊ฒ ์ต๋๋ค.
App.js
๋ฅผ ์ฌ์ฉํ์ฌ canvas HTML ์์./src/components/Ocean
์ ์ค๋กํํฉ๋๋ค.useRef
๋ฅ๋ ๋ฐ๋, ๊ธฐ๋ณต๋, ํฌ๊ธฐ ๋ฑ ๋ ๋๋ง๊ณผ ๋ ์จ ์์ฑ์ ์ ์ดํ๋ค.Simulator
(request Animation Frame) ๊ฐ๊ณ ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋ชจ๋ ์ ๋๋ฉ์ด์
ํ๋ ์์์ ์คํ๋๋ ์ํ์ ๋ง๋ญ๋๋ค.Simulator
๋ถ์์ ๋ฐ๋ผ ์ด๋ฐ ๋ ์จ ์ค์ ์ ๋น์ถ๋์?์ด๋ฅผ ์ํด ์ ๋
useRaf
์์ ํ์จํ ์ ์๋ฅผ ์์ํ๋ ๋ ์จ ์ค์ ์ธ ๊ธฐ๋ณต๋, ๋ฐ๋๊ณผ ํฌ๊ธฐ์ ๋น์ถ๋ ์ค์ฉ ํจ์๋ฅผ ๋ง๋ค์์ต๋๋ค.๊ทธ๋ฆฌ๊ณ ์ฐ๋ฆฌ๋ ๋งค๋ฒ calm
์ ์๊ฐ ๋ณํ ๋๋ง๋ค ๋๊ธฐํํ๋ ๋ถ์์ฉ์ ๋ง๋ค ์ ์๋ค.useEffect(() => {
if (simulator) {
setWeatherBasedOnCalm(animatedCalm, 0, 0);
}
function setWeatherBasedOnCalm(calm) {
const { choppiness, wind, size } = mapCalmToWeather(calm);
simulator.setChoppiness(choppiness);
simulator.setWind(wind, wind);
simulator.setSize(size);
}
}, [calm, simulator]);
์ธ์ง ์ํ
์ด๊ฒ์ ์ฌ๋ฏธ์๋ ๋ถ๋ถ์ด๋ค.์ด๊ฒ์ ์ฐ๋ฆฌ๊ฐ ๋๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐฉ๋ฌธํ์ฌ ์์ฉ ํ๋ก๊ทธ๋จ ์ํ์ ๋น์ถ๋ ๊ณณ์ด๋ค.
๊ตฌ๋
weather.js
์ ํตํด ์ฐ๋ฆฌ๋ ์ฝ 1์ด์ ์๋ก์ด calm
์ ์๋ฅผ ์ป๋๋ค.๊ทธ๋ฌ๋ฉด notion.calm()
๊ตฌ์ฑ ์์๋ฅผ ์ถ๊ฐํ๊ณ calm
๋๊ตฌ๋ก ์ถ๊ฐํ๋ฉฐ <Ocean calm={calm} />
๋ฐ calm
์ค๋ก์ ๋๊ธฐํ๋ ๋ถ์์ฉ์ ๋ง๋ญ๋๋ค.๋ง์ฝ ์ด ๋ ๊ฐ์ง ์ํ๊ฐ ๋ชจ๋ ์กด์ฌํ๋ค๋ฉด, ์ฐ๋ฆฌ๋ ์์ฌํ๊ณ calm๋ฅผ ๋ฐ์๋ค์ผ ์ ์๋ค.๐ง๐ฟโโ๏ธ The calm score is derived from your passive cognitive state. This metric based on the alpha wave. The calm score ranges from
0.0
to1.0
. The higher the score, the higher the probability a calm feeling is detected. Getting a calm score over0.3
is significant. Things that can help increase the calm score include closing your eyes, keeping still, breathing deeply, or meditating.
// src/pages/Calm.js
import React, { useState, useEffect } from "react";
import { Ocean } from "../components/Ocean/Ocean";
export function Calm({ user, notion }) {
const [calm, setCalm] = useState(0);
useEffect(() => {
if (!user || !notion) {
return;
}
const subscription = notion.calm().subscribe(calm => {
const calmScore = Number(calm.probability.toFixed(2));
setCalm(calmScore);
});
return () => {
subscription.unsubscribe();
};
}, [user, notion]);
return (
<Ocean calm={calm} />
);
}
๐ก All notion metrics, including
notion.calm()
return an RxJS subscription that we can use to safely unsubscribe when the component unmounts.
๋ง์ง๋ง์ผ๋ก ์์ํ ํ์ด์ง๋ฅผ
notion
์ ์ถ๊ฐํฉ๋๋ค.// src/App.js
// ...
import { Calm } from "./pages/Calm";
// ...
// If already authenticated, redirect user to the Calm page
useEffect(() => {
if (user) {
navigate("/calm");
}
}, [user]);
return (
<Router>
{/* ... */}
<Calm path="/calm" notion={notion} user={user} />
</Router>
);
์ด๋ก์จ Neuro React ์์ฉ ํ๋ก๊ทธ๋จ์ด ์๋ฃ๋์์ต๋๋ค.์ ๊ฒฝ์ฆ / ๊ฐ๋ ํด์
๐ ๋ ์ปดํจํฐ๋ฅผ ์ฌ์ฉํ์ฌ WebGL ํด์์ ์ด๋์ ์ ์ดํ๋ค
๋๋ ์์ฉ ํ๋ก๊ทธ๋จ ์ฒดํ์ ๋ํด ํฅ๋ถ์ ๋๊ผ๋ค. ์ด๋ฐ ์ฒดํ๋ค์ ์ฐ๋ฆฌ๊ฐ ํ ์ฌ๋์ผ๋ก์์ ์ํฅ์ ๋ฐ์๋ค.๋ชจ๋ ๋๋ ๋ค๋ฅด์ง๋ง, ์ฐ๋ฆฌ๋ ๋์์์ด ์์ฉ ํ๋ก๊ทธ๋จ์ ๊ฐ๋ฐํ์ฌ ๋ชจ๋ ์ฌ์ฉ์์๊ฒ ๊ฐ์ ์ฒดํ์ ์ ๊ณตํ๋ค.๋ง์ฝ ์์ฉ ํ๋ก๊ทธ๋จ์ด ๋น์ ์ ์ํด ๋ง์ถคํ ์ ์์ ํ๋ค๋ฉด?
๋ง์ฝ ์์ฉ ํ๋ก๊ทธ๋จ์ด ๋น์ ์ด ์คํธ๋ ์ค ์์์ ๊ธด์ฅ์ ํธ๋ ๊ฒ์ ๋์ธ ์ ์๋ค๋ฉด?
๋ง์ฝ ๋น์ ์ ๋ํ๋ก ์์ฉ ํ๋ก๊ทธ๋จ์ ๊ฒ์ฆํ ์ ์๋ค๋ฉด?
๋ง์ฝ ๋น๋์ค ๊ฒ์์ด ๋น์ ์ ๋๋์ ๋ฐ๋ผ ๊ทธ๋ค์ ์์ ์ ๋ฐ๊ฟ ์ ์๋ค๋ฉด?
๋ง์ฝ... ์ด๋กํ์ง...
Reference
์ด ๋ฌธ์ ์ ๊ดํ์ฌ(๐คฏ React๋ฅผ ์ฌ์ฉํ์ฌ ์ฒซ ๋ฒ์งธ Neuro ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์ถ), ์ฐ๋ฆฌ๋ ์ด๊ณณ์์ ๋ ๋ง์ ์๋ฃ๋ฅผ ๋ฐ๊ฒฌํ๊ณ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ๋ณด์๋ค https://dev.to/neurosity/building-your-first-neuro-app-with-react-49pjํ ์คํธ๋ฅผ ์์ ๋กญ๊ฒ ๊ณต์ ํ๊ฑฐ๋ ๋ณต์ฌํ ์ ์์ต๋๋ค.ํ์ง๋ง ์ด ๋ฌธ์์ URL์ ์ฐธ์กฐ URL๋ก ๋จ๊ฒจ ๋์ญ์์ค.
์ฐ์ํ ๊ฐ๋ฐ์ ์ฝํ ์ธ ๋ฐ๊ฒฌ์ ์ ๋ (Collection and Share based on the CC Protocol.)
์ข์ ์นํ์ด์ง ์ฆ๊ฒจ์ฐพ๊ธฐ
๊ฐ๋ฐ์ ์ฐ์ ์ฌ์ดํธ ์์ง
๊ฐ๋ฐ์๊ฐ ์์์ผ ํ ํ์ ์ฌ์ดํธ 100์ ์ถ์ฒ ์ฐ๋ฆฌ๋ ๋น์ ์ ์ํด 100๊ฐ์ ์์ฃผ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์ ํ์ต ์ฌ์ดํธ๋ฅผ ์ ๋ฆฌํ์ต๋๋ค