🦄 Refactored app to src main

This commit is contained in:
Nikolaj Frey 2024-03-24 09:19:49 +11:00
parent 59c04276ac
commit cf67d463b3
76 changed files with 828 additions and 823 deletions

9
.gitignore vendored
View File

@ -5,10 +5,17 @@
app/node_modules
app-next/node_modules
api/node_modules
/node_modules
node_modules
localhost-key.pem
localhost.pem
yarn-error.log
pnpm-lock.yaml
build/*
public/build/*
api/index.js
api/index.js.map
pnpm-lock.yaml

12
.prettierrc Normal file
View File

@ -0,0 +1,12 @@
{
"endOfLine": "lf",
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "none",
"printWidth": 150,
"singleAttributePerLine": false,
"arrowParens": "always",
"plugins": []
}

View File

@ -1,96 +0,0 @@
module.exports = {
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
},
env: {
browser: true,
node: true,
es6: true,
},
settings: {
react: {
version: "detect",
},
},
plugins: [
"@typescript-eslint",
"react",
// "unused-imports",
"prettier",
"simple-import-sort",
"chakra-ui",
],
extends: [
"@remix-run/eslint-config",
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
],
rules: {
"react/jsx-curly-brace-presence": [
"error",
{ props: "never", children: "never" },
],
"direct-eval/direct-eval": "off",
"no-eval": "off",
"no-async-promise-executor": "off",
"react/prop-types": "off",
"react/display-name": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": "off",
// "unused-imports/no-unused-imports": "error",
// "unused-imports/no-unused-vars": "error",
"react/react-in-jsx-scope": "off",
"chakra-ui/props-order": "error",
"chakra-ui/props-shorthand": [
"error",
{
noShorthand: true,
applyToAllComponents: true,
},
],
"chakra-ui/require-specific-component": "error",
"prettier/prettier": [
"error",
{
semi: false,
singleQuote: false,
parser: "typescript",
bracketSpacing: true,
},
],
"simple-import-sort/imports": [
"error",
{
groups: [
// Packages `react` related packages come first.
["^react", "^@?\\w"],
// Internal packages.
["^(@root)(/.*|$)"],
["^(@app)(/.*|$)"],
// Side effect imports.
["^\\u0000"],
// Other relative imports. Put same-folder imports and `.` last.
["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"],
],
},
],
},
overrides: [
{
files: ["*.js"],
rules: {
"@typescript-eslint/no-var-requires": "off",
},
},
],
}

10
app/.gitignore vendored
View File

@ -5,7 +5,9 @@ node_modules
.vercel
.output
/build/
/public/build
/api/index.js
/api/index.js.map
build/*
public/build/*
api/index.js
api/index.js.map
pnpm-lock.yaml

12
app/.prettierrc Normal file
View File

@ -0,0 +1,12 @@
{
"endOfLine": "lf",
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "none",
"printWidth": 150,
"singleAttributePerLine": false,
"arrowParens": "always",
"plugins": []
}

View File

@ -1,5 +0,0 @@
import { ChakraProvider } from '@chakra-ui/react'
import { theme } from './theme'
export const ChakraWrapper = props => {
return <ChakraProvider theme={theme}>{props.children}</ChakraProvider>
}

View File

@ -1,32 +0,0 @@
import React from 'react'
import { Flex } from '@chakra-ui/react'
export const Attention = props => {
return (
<Flex
{...props}
flexDirection={'column'}
justifyContent={'center'}
alignItems={'center'}
cursor={'pointer'}
>
<Flex
w={props.w || '40px'}
mb='10px'
h='2px'
bg='linear-gradient(to right, #f34a4a, #ffbc48, #58ca70, #47b5e6, #a555e8, #f34a4a);'
backgroundSize='200%'
borderBottomRadius={'20px'}
transition='all 0.5s ease-in-out'
sx={{
'@keyframes moving-rainbow': {
'0%': { backgroundPosition: '0 0' },
'100%': { backgroundPosition: '200% 0' }
},
// add delay
animation: `moving-rainbow 3s infinite linear`
}}
></Flex>
</Flex>
)
}

View File

@ -1,38 +0,0 @@
import React from 'react'
import { Flex } from '@chakra-ui/react'
export const Hamburger = props => {
const lineCount = [1, 2, 3]
return (
<Flex
{...props}
flexDirection={'column'}
justifyContent={'center'}
alignItems={'center'}
cursor={'pointer'}
>
{lineCount.map((line, idx) => {
return (
<Flex
key={idx}
w='40px'
mb='10px'
h='3px'
bg='linear-gradient(to right, #f34a4a, #ffbc48, #58ca70, #47b5e6, #a555e8, #f34a4a);'
backgroundSize='200%'
borderRadius={'9px'}
sx={{
'@keyframes moving-rainbow': {
'0%': { backgroundPosition: '0 0' },
'100%': { backgroundPosition: '200% 0' }
},
// add delay
animation: `moving-rainbow 3s infinite linear -${idx * 0.3}s}`
}}
></Flex>
)
})}
</Flex>
)
}

View File

@ -1,225 +0,0 @@
import React from "react"
import { Center } from "@chakra-ui/react"
import emojis from "emojis-list"
export const Icon = (props) => {
const name = props?.name
const icon = React.useMemo(() => {
// nothing
if (["gear", "cog"]?.includes(name)) {
return "⚙️"
}
if (["crystal"]?.includes(name)) {
return "🔮"
}
if (["flower", "hibiscus"]?.includes(name)) {
return "🌺"
}
if (["sparke", "magic"]?.includes(name)) {
return "✨"
}
if (["wizard", "gandalf"]?.includes(name)) {
return "🧙‍♂️"
}
if (["two eyes"]?.includes(name)) {
return "👀"
}
if (["box", "thing", "object"]?.includes(name)) {
return "📦"
}
if (["pencil"]?.includes(name)) {
return "✏️"
}
if (["edit", "paint", "create"]?.includes(name)) {
return "🎨"
}
if (["book", "books"]?.includes(name)) {
return "📚"
}
if (["any", "magic wand"]?.includes(name)) {
return "🪄"
}
if (["book-open", "books-open"]?.includes(name)) {
return "📖"
}
if (["book-reader", "books-reader"]?.includes(name)) {
return "👩‍🏫"
}
if (["number", "hundred"]?.includes(name)) {
return "💯"
}
if (["puzzle", "types"]?.includes(name)) {
return "🧩"
}
if (["heart"]?.includes(name)) {
return "❤️"
}
if (["heart-broken"]?.includes(name)) {
return "💔"
}
if (["heart-pulse"]?.includes(name)) {
return "💗"
}
if (["string", "text"]?.includes(name)) {
return "💬"
}
if (["array", "list"]?.includes(name)) {
return "📚"
}
if (["boolean", "bool"]?.includes(name)) {
return "🌗"
// return "⚖️"
}
if (["rainbow"]?.includes(name)) {
return "🌈"
}
if (["sun"]?.includes(name)) {
return "☀️"
}
if (["moon"]?.includes(name)) {
return "🌙"
}
if (["unicorn"]?.includes(name)) {
return "🦄"
}
if (["user", "person"]?.includes(name)) {
return "👤"
}
if (["group", "team"]?.includes(name)) {
return "👥"
}
if (["success", "check"]?.includes(name)) {
return "✅"
}
if (["error", "stop"]?.includes(name)) {
return "❌"
}
if (["warning", "alert"]?.includes(name)) {
return "⚠️"
}
if (["time", "clock"]?.includes(name)) {
return "⏰"
}
if (["star", "favorite"]?.includes(name)) {
return "⭐"
}
if (["glowing star", "glowing favorite"]?.includes(name)) {
return "🌟"
}
if (["question", "help"]?.includes(name)) {
return "❓"
}
if (["video", "media"]?.includes(name)) {
return "🎥"
}
if (["music", "audio"]?.includes(name)) {
return "🎵"
}
if (["image", "picture"]?.includes(name)) {
return "🖼️"
}
if (["email", "mail"]?.includes(name)) {
return "✉️"
}
if (["computer", "laptop"]?.includes(name)) {
return "💻"
}
if (["mobile", "phone"]?.includes(name)) {
return "📱"
}
if (["world", "globe"]?.includes(name)) {
return "🌍"
}
if (["rocket", "launch"]?.includes(name)) {
return "🚀"
}
if (["pencil", "edit"]?.includes(name)) {
return "✏️"
}
if (["search", "magnify"]?.includes(name)) {
return "🔍"
}
if (["lock", "secure"]?.includes(name)) {
return "🔒"
}
if (["unlock", "access"]?.includes(name)) {
return "🔓"
}
if (["thumb-up", "like"]?.includes(name)) {
return "👍"
}
if (["thumb-down", "dislike"]?.includes(name)) {
return "👎"
}
if (["plus", "add"]?.includes(name)) {
return ""
}
if (["seedling", "seed"]?.includes(name)) {
return "🌱"
}
if (["undefined", "null", "question", "confused"]?.includes(name)) {
return "❓"
// return "🌫️"
}
if (["codex", "robot", "ai", "chatgpt"]?.includes(name)) {
return "🤖"
}
if (["trash", "bin", "delete", "remove"]?.includes(name)) {
return "🗑️"
}
if (["cash", "money"]?.includes(name)) {
// return "💰"
return "💵"
}
if (["money bag"]?.includes(name)) {
return "💰"
}
if (["cyclone", "tornado"]?.includes(name)) {
return "🌀"
}
if (["thingtime"]?.includes(name)) {
if (Math.random() > 0.5) {
return "🌳"
}
return "🌀"
}
if (["function", "lambda"]?.includes(name)) {
return "📐"
}
if (["pin", "pinned", "located"]?.includes(name)) {
return "📌"
}
if (["wrap", "wrapped"]?.includes(name)) {
return "🎁"
// return "🎀"
}
if (["dinosaur", "dino"]?.includes(name)) {
return "🦕"
}
if (emojis?.includes(name)) {
return name
}
if (["random"]?.includes(name)) {
return emojis[Math.floor(Math.random() * emojis.length)]
}
// question mark
return "❓"
}, [name])
return (
<Center
transition="all 0.2s ease-out"
{...props}
{...props?.chakras}
fontSize={props?.size}
>
{icon}
</Center>
)
}

View File

@ -1,47 +0,0 @@
import React, { useState } from "react"
import { Flex, FormControl, Input } from "@chakra-ui/react"
export const Login = (props) => {
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const handleLogin = () => {
// handle login
}
return (
<>
<Flex flexDirection="column" gap={4} width="auto">
<FormControl>
<Input
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
type="email"
value={email}
/>
</FormControl>
<FormControl>
<Input
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
type="password"
value={password}
/>
</FormControl>
<Flex
width="100%"
color="white"
background="chakras.violet"
borderRadius={6}
cursor="pointer"
paddingX={5}
paddingY={2}
>
Login
</Flex>
</Flex>
</>
)
}

View File

@ -1,69 +0,0 @@
import React from 'react'
import { Flex } from '@chakra-ui/react'
import { Hamburger } from '../Buttons/Hamburger'
import { Attention } from '../Buttons/Attention'
import { RightNav } from './ReactiveRightNav'
export const ReactiveNav = props => {
const [navItems] = React.useState([{}])
const setNav = React.useCallback(active => {
setMt(active ? '100%' : '0%')
setNavActive(active)
}, [])
React.useEffect(() => {
// react to mouse move event
const listener = event => {
if (event?.clientY <= 20) {
setNav(true)
} else if (event?.clientY >= 100) {
setNav(false)
}
}
window.addEventListener('mousemove', listener)
return () => {
window.removeEventListener('mousemove', listener)
}
}, [setNav])
const [navActive, setNavActive] = React.useState(false)
const [mt, setMt] = React.useState('0%')
return (
<>
<Flex
as='nav'
w='100%'
alignItems={'center'}
justifyContent='center'
flexDir={'row'}
position='fixed'
py={'1%'}
px={'1%'}
bottom={'100%'}
transform={`translateY(${mt})`}
transition={'all 0.3s ease-in-out'}
// top={'100%'}
// top={0}
left={0}
right={0}
>
<Hamburger ml='auto'></Hamburger>
<Flex
opacity={navActive ? 0 : 1}
position='absolute'
transition={'all 0.2s ease-in-out'}
top='100%'
left={'50%'}
translateX={'-50%'}
>
{/* w={navActive ? '0px' : '40px'} */}
<Attention></Attention>
</Flex>
</Flex>
<RightNav></RightNav>
</>
)
}

View File

@ -1,46 +0,0 @@
import React from 'react'
import { Flex } from '@chakra-ui/react'
export const RainbowSkeleton = props => {
const [rainbowColours] = React.useState([
'#f34a4a',
'#ffbc48',
'#58ca70',
'#47b5e6',
'#a555e8'
])
const keyframes = React.useMemo(() => {
let keyframes = {}
rainbowColours.forEach((colour, idx) => {
keyframes[Math.round((idx * 100) / rainbowColours.length) + '%'] = {
backgroundColor: colour
}
})
keyframes['100%'] = { backgroundColor: rainbowColours[0] }
return keyframes
}, [rainbowColours])
if (props?.loaded) {
return props?.children
}
return (
<Flex
cursor='pointer'
w='10px'
h='8px'
borderRadius={'2px'}
sx={{
'@keyframes placeholder-rainbow': keyframes,
'@keyframes placeholder-opacity': {
'0%': { opacity: 0.2 },
'100%': { opacity: 1 }
},
// add delay
animation: `placeholder-rainbow 8s infinite linear, placeholder-opacity 1.3s linear 0s infinite alternate none running}`
}}
{...props}
></Flex>
)
}

View File

@ -1,21 +0,0 @@
import { renderToString } from 'react-dom/server'
import { RemixServer } from '@remix-run/react'
import type { EntryContext } from '@remix-run/react/dist/entry'
export default function handleRequest (
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
let markup = renderToString(
<RemixServer context={remixContext} url={request.url} />
)
responseHeaders.set('Content-Type', 'text/html')
return new Response('<!DOCTYPE html>' + markup, {
status: responseStatusCode,
headers: responseHeaders
})
}

View File

@ -1,8 +0,0 @@
export default class Sample {
constructor({ x, y, progress, segment }) {
this.x = x;
this.y = y;
this.progress = progress;
this.segment = segment;
}
}

View File

@ -1,8 +0,0 @@
import { getMiddleSample } from './_utils';
export default class Segment {
constructor({ samples }) {
this.samples = samples;
this.progress = getMiddleSample(samples).progress;
}
}

View File

@ -1 +0,0 @@
export const DEFAULT_PRECISION = 2;

View File

@ -1,2 +0,0 @@
export { default as GradientPath } from './GradientPath';
export { getData, strokeToFill } from './_data';

View File

@ -5,7 +5,8 @@
"build": "remix build",
"dev": "remix dev --port 9999",
"lint": "eslint --ext .js,.ts,.jsx,.tsx .",
"lint-fix": "eslint --ext .js,.ts,.jsx,.tsx . --fix"
"lint-fix": "eslint --ext .js,.ts,.jsx,.tsx . --fix",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,scss,md}\""
},
"dependencies": {
"@chakra-ui/react": "^2.7.1",

View File

@ -2,12 +2,12 @@
* @type {import('@remix-run/dev').AppConfig}
*/
module.exports = {
ignoredRouteFiles: ['**/.*']
ignoredRouteFiles: ["**/.*"],
// future: {
// unstable_dev: true
// appServerPort: 3999
// }
// appDirectory: "app",
appDirectory: "src",
// assetsBuildDirectory: "public/build",
// serverBuildPath: "build/index.js",
// publicPath: "/build/",

View File

@ -0,0 +1,6 @@
import { ChakraProvider } from "@chakra-ui/react"
import { theme } from "./theme"
export const ChakraWrapper = (props) => {
return <ChakraProvider theme={theme}>{props.children}</ChakraProvider>
}

View File

@ -1,11 +1,17 @@
const greys = {
light: "#F1F1F3",
lightt: "#E7E6E8",
medium: "#E0E0E0",
dark: "#BDBDBD",
}
const grey = "#F1F1F3"
const g = {
grey: "#F1F1F3",
greys: {
light: "#F1F1F3",
lightt: "#E7E6E8",
medium: "#E0E0E0",
dark: "#BDBDBD",
},
gray: grey,
grey,
grays: greys,
greys,
}
// for bad spellers
@ -33,6 +39,22 @@ export const colors = {
indigo: "#5C6BC0",
violet: "#AB47BC",
},
chakrasDark: {
root: "#8E0000",
sacral: "#E65100",
solarPlexus: "#FDD835",
heart: "#33691E",
throat: "#1E88E5",
thirdEye: "#3949AB",
crown: "#6A1B9A",
red: "#8E0000",
orange: "#E65100",
yellow: "#FDD835",
green: "#33691E",
blue: "#1E88E5",
indigo: "#3949AB",
violet: "#6A1B9A",
},
// all colors of the rainbow
rainbow: {
red: "#FF0000",

View File

@ -2,8 +2,8 @@ import React, { createContext } from "react"
import flatted, { parse, stringify } from "flatted"
import { Subject } from "rxjs"
import { sanitise } from "~/functions/sanitise"
import { smarts } from "~/smarts"
import { sanitise } from "../functions/sanitise"
import { smarts } from "../smarts"
export interface ThingtimeContextInterface {
thingtime: any
@ -292,7 +292,7 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
const [thingtimeReference, rawSet] = React.useState()
const thingtimeRef = React.useRef(thingtimeReference)
const stateRef = React.useRef({
const stateRef: any = React.useRef({
c: 1,
})
@ -409,7 +409,7 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
}
}
const newThingtime = thingtimeReference
const newThingtime: any = thingtimeReference || {}
const paths = smarts.parsePropertyPath(path)

View File

@ -0,0 +1,32 @@
import React from "react"
import { Flex } from "@chakra-ui/react"
export const Attention = (props) => {
return (
<Flex
{...props}
alignItems="center"
justifyContent="center"
flexDirection="column"
cursor="pointer"
>
<Flex
sx={{
"@keyframes moving-rainbow": {
"0%": { backgroundPosition: "0 0" },
"100%": { backgroundPosition: "200% 0" },
},
// add delay
animation: `moving-rainbow 3s infinite linear`,
}}
width={props.w || "40px"}
height="2px"
marginBottom="10px"
background="linear-gradient(to right, #f34a4a, #ffbc48, #58ca70, #47b5e6, #a555e8, #f34a4a);"
backgroundSize="200%"
borderBottomRadius="20px"
transition="all 0.5s ease-in-out"
></Flex>
</Flex>
)
}

View File

@ -0,0 +1,38 @@
import React from "react"
import { Flex } from "@chakra-ui/react"
export const Hamburger = (props) => {
const lineCount = [1, 2, 3]
return (
<Flex
{...props}
alignItems="center"
justifyContent="center"
flexDirection="column"
cursor="pointer"
>
{lineCount.map((line, idx) => {
return (
<Flex
key={idx}
sx={{
"@keyframes moving-rainbow": {
"0%": { backgroundPosition: "0 0" },
"100%": { backgroundPosition: "200% 0" },
},
// add delay
animation: `moving-rainbow 3s infinite linear -${idx * 0.3}s}`,
}}
width="40px"
height="3px"
marginBottom="10px"
background="linear-gradient(to right, #f34a4a, #ffbc48, #58ca70, #47b5e6, #a555e8, #f34a4a);"
backgroundSize="200%"
borderRadius="9px"
></Flex>
)
})}
</Flex>
)
}

View File

@ -468,6 +468,7 @@ export const Commander = (props) => {
sx={{
"&::placeholder": {
color: "greys.dark",
// color: "white",
},
}}
width="100%"

View File

@ -564,6 +564,7 @@ export const CommanderV1 = (props) => {
sx={{
"&::placeholder": {
color: "greys.dark",
// color: "white",
},
}}
zIndex={9999}
@ -601,6 +602,8 @@ export const CommanderV1 = (props) => {
sx={{
"&::placeholder": {
color: "greys.dark",
// color: "white",
// textShadow: "0 0 5px black",
},
}}
zIndex={9999}

View File

@ -0,0 +1,225 @@
import React from "react"
import { Center } from "@chakra-ui/react"
import emojis from "emojis-list"
export const Icon = (props) => {
const name = props?.name
const icon = React.useMemo(() => {
// nothing
if (["⚙️", "gear", "cog"]?.includes(name)) {
return "⚙️"
}
if (["🔮", "crystal"]?.includes(name)) {
return "🔮"
}
if (["🌺", "flower", "hibiscus"]?.includes(name)) {
return "🌺"
}
if (["✨", "sparke", "magic"]?.includes(name)) {
return "✨"
}
if (["🧙‍♂️", "wizard", "gandalf"]?.includes(name)) {
return "🧙‍♂️"
}
if (["👀", "two eyes"]?.includes(name)) {
return "👀"
}
if (["📦", "box", "thing", "object"]?.includes(name)) {
return "📦"
}
if (["✏️", "pencil"]?.includes(name)) {
return "✏️"
}
if (["🎨", "edit", "paint", "create"]?.includes(name)) {
return "🎨"
}
if (["📚", "book", "books"]?.includes(name)) {
return "📚"
}
if (["🪄", "any", "magic wand"]?.includes(name)) {
return "🪄"
}
if (["📖", "book-open", "books-open"]?.includes(name)) {
return "📖"
}
if (["👩‍🏫", "book-reader", "books-reader"]?.includes(name)) {
return "👩‍🏫"
}
if (["💯", "number", "hundred"]?.includes(name)) {
return "💯"
}
if (["🧩", "puzzle", "types"]?.includes(name)) {
return "🧩"
}
if (["❤️", "heart"]?.includes(name)) {
return "❤️"
}
if (["💔", "heart-broken"]?.includes(name)) {
return "💔"
}
if (["💗", "heart-pulse"]?.includes(name)) {
return "💗"
}
if (["💬", "string", "text"]?.includes(name)) {
return "💬"
}
if (["📚", "array", "list"]?.includes(name)) {
return "📚"
}
if (["🌗", "boolean", "bool"]?.includes(name)) {
return "🌗"
// return "⚖️"
}
if (["🌈", "rainbow"]?.includes(name)) {
return "🌈"
}
if (["☀️", "sun"]?.includes(name)) {
return "☀️"
}
if (["🌙", "moon"]?.includes(name)) {
return "🌙"
}
if (["🦄", "unicorn"]?.includes(name)) {
return "🦄"
}
if (["👤", "user", "person"]?.includes(name)) {
return "👤"
}
if (["👥", "group", "team"]?.includes(name)) {
return "👥"
}
if (["✅", "success", "check"]?.includes(name)) {
return "✅"
}
if (["❌", "error", "stop"]?.includes(name)) {
return "❌"
}
if (["⚠️", "warning", "alert"]?.includes(name)) {
return "⚠️"
}
if (["⏰", "time", "clock"]?.includes(name)) {
return "⏰"
}
if (["⭐", "star", "favorite"]?.includes(name)) {
return "⭐"
}
if (["🌟", "glowing star", "glowing favorite"]?.includes(name)) {
return "🌟"
}
if (["❓", "question", "help"]?.includes(name)) {
return "❓"
}
if (["🎥", "video", "media"]?.includes(name)) {
return "🎥"
}
if (["🎵", "music", "audio"]?.includes(name)) {
return "🎵"
}
if (["🖼️", "image", "picture"]?.includes(name)) {
return "🖼️"
}
if (["✉️", "email", "mail"]?.includes(name)) {
return "✉️"
}
if (["💻", "computer", "laptop"]?.includes(name)) {
return "💻"
}
if (["📱", "mobile", "phone"]?.includes(name)) {
return "📱"
}
if (["🌍", "world", "globe"]?.includes(name)) {
return "🌍"
}
if (["🚀", "rocket", "launch"]?.includes(name)) {
return "🚀"
}
if (["✏️", "pencil", "edit"]?.includes(name)) {
return "✏️"
}
if (["🔍", "search", "magnify"]?.includes(name)) {
return "🔍"
}
if (["🔒", "lock", "secure"]?.includes(name)) {
return "🔒"
}
if (["🔓", "unlock", "access"]?.includes(name)) {
return "🔓"
}
if (["👍", "thumb-up", "like"]?.includes(name)) {
return "👍"
}
if (["👎", "thumb-down", "dislike"]?.includes(name)) {
return "👎"
}
if (["", "plus", "add"]?.includes(name)) {
return ""
}
if (["🌱", "seedling", "seed"]?.includes(name)) {
return "🌱"
}
if (["❓", "undefined", "null", "question", "confused"]?.includes(name)) {
return "❓"
// return "🌫️"
}
if (["🤖", "codex", "robot", "ai", "chatgpt"]?.includes(name)) {
return "🤖"
}
if (["🗑️", "trash", "bin", "delete", "remove"]?.includes(name)) {
return "🗑️"
}
if (["cash", "money"]?.includes(name)) {
// return "💰"
return "💵"
}
if (["💰", "money bag"]?.includes(name)) {
return "💰"
}
if (["🌀", "cyclone", "tornado"]?.includes(name)) {
return "🌀"
}
if (["thingtime"]?.includes(name)) {
if (Math.random() > 0.5) {
return "🌳"
}
return "🌀"
}
if (["📐", "function", "lambda"]?.includes(name)) {
return "📐"
}
if (["📌", "pin", "pinned", "located"]?.includes(name)) {
return "📌"
}
if (["🎁", "wrap", "wrapped"]?.includes(name)) {
return "🎁"
// return "🎀"
}
if (["🦕", "dinosaur", "dino"]?.includes(name)) {
return "🦕"
}
if (emojis?.includes(name)) {
return name
}
if (["random"]?.includes(name)) {
return emojis[Math.floor(Math.random() * emojis.length)]
}
// question mark
return "🤷‍♂️"
}, [name])
return (
<Center
transition="all 0.2s ease-out"
{...props}
{...props?.chakras}
fontSize={props?.size}
>
{icon}
</Center>
)
}

View File

@ -0,0 +1,92 @@
import React, { useState } from 'react';
import { Flex, Button, FormControl, Input } from '@chakra-ui/react';
export const Login = (props) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleLogin = (props) => {
// handle login
const { username, password } = props;
console.log('nik username', username);
console.log('nik password', password);
};
return (
<>
<form>
<Flex flexDirection="column" gap={4} width="255px" maxWidth="100%">
<FormControl>
<Input
sx={{
'&::placeholder': {
color: 'greys.dark'
// color: "white",
}
}}
background="grey"
border="none"
borderRadius="5px"
outline="none"
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
type="username"
value={email}
/>
</FormControl>
<FormControl>
<Input
sx={{
'&::placeholder': {
color: 'greys.dark'
// color: "white",
}
}}
background="grey"
border="none"
borderRadius="5px"
outline="none"
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
type="password"
value={password}
/>
</FormControl>
<Button
sx={{
'@keyframes moving-rainbow': {
'0%': { backgroundPosition: '0 0' },
'100%': { backgroundPosition: 'calc(100px + 400%) 0' }
},
animation: 'moving-rainbow 40s infinite linear'
}}
type="submit"
display="Flex"
justifyContent="flex-start"
width="100%"
color="white"
fontWeight="bold"
background="chakras.violet"
backgroundSize="calc(100px + 400%)"
// Add rainbow animation background gradient right to left
bgGradient="linear-gradient(to right, #47b5e6, #a555e8, #f34a4a, #ffbc48, #58ca70, #47b5e6)"
borderRadius={6}
_hover={{
opacity: 0.9
}}
cursor="pointer"
transition="all 150ms ease-in-out"
paddingX={4}
paddingY={2}
>
Login
</Button>
</Flex>
</form>
</>
);
};

View File

@ -3,7 +3,7 @@ import { Box } from "@chakra-ui/react"
import { useThingtime } from "../Thingtime/useThingtime"
export const MagicInput = React.forwardRef((props, ref) => {
export const MagicInput = React.forwardRef((props: any, ref) => {
const { thingtime, setThingtime, loading } = useThingtime()
const [inputValue, setInputValue] = React.useState()
@ -153,7 +153,7 @@ export const MagicInput = React.forwardRef((props, ref) => {
contentEditable={!props?.readonly ? true : false}
dangerouslySetInnerHTML={dangerouslySetInnerHTML}
onFocus={onFocus}
onInput={(value) => {
onInput={(value: EventTarget | any) => {
const innerText = value?.target?.innerText
console.log("MagicInput got onInput event value", value)
console.log("MagicInput got onInput event innerText", innerText)

View File

@ -3,7 +3,7 @@ import { Box, Center, Flex, Text } from "@chakra-ui/react"
import { Link, useLocation, useNavigate } from "@remix-run/react"
import { Commander } from "../Commander/Commander"
import { CommanderV1 } from "../Commander/CommanderV1"
import { CommanderV1 } from "../Commander/CommanderV2"
import { Icon } from "../Icon/Icon"
import { RainbowSkeleton } from "../Skeleton/RainbowSkeleton"
import { ProfileDrawer } from "./ProfileDrawer"

View File

@ -1,90 +1,83 @@
import React from "react"
import { Box, Center, Flex } from "@chakra-ui/react"
import { Link, useLocation, useNavigate } from "@remix-run/react"
import React from 'react';
import { Box, Center, Flex } from '@chakra-ui/react';
import { Link, useLocation, useNavigate } from '@remix-run/react';
import { Commander } from "../Commander/Commander"
import { CommanderV1 } from "../Commander/CommanderV1"
import { Icon } from "../Icon/Icon"
import { RainbowSkeleton } from "../Skeleton/RainbowSkeleton"
import { ProfileDrawer } from "./ProfileDrawer"
import { Commander } from '../Commander/Commander';
import { CommanderV1 } from '../Commander/CommanderV2';
import { Icon } from '../Icon/Icon';
import { RainbowSkeleton } from '../Skeleton/RainbowSkeleton';
import { ProfileDrawer } from './ProfileDrawer';
export const Nav = (props) => {
const [profileDrawerOpen, setProfileDrawerOpen] = React.useState(false)
const [profileDrawerOpen, setProfileDrawerOpen] = React.useState(false);
const { pathname } = useLocation()
const { pathname } = useLocation();
const navigate = useNavigate()
const navigate = useNavigate();
const toggleProfileDrawer = React.useCallback(() => {
setProfileDrawerOpen(!profileDrawerOpen)
}, [profileDrawerOpen])
setProfileDrawerOpen(!profileDrawerOpen);
}, [profileDrawerOpen]);
const inEditorMode = React.useMemo(() => {
if (pathname.slice(0, 7) === "/editor") {
return true
if (pathname.slice(0, 7) === '/editor') {
return true;
}
return false
}, [pathname])
return false;
}, [pathname]);
const inEditMode = React.useMemo(() => {
if (pathname.slice(0, 5) === "/edit") {
return true
if (pathname.slice(0, 5) === '/edit') {
return true;
}
return false
}, [pathname])
return false;
}, [pathname]);
const editorToggleable = React.useMemo(() => {
if (pathname.slice(0, 7) === "/things") {
return true
} else if (pathname.slice(0, 5) === "/edit") {
return true
if (pathname.slice(0, 7) === '/things') {
return true;
} else if (pathname.slice(0, 5) === '/edit') {
return true;
}
return false
}, [pathname])
return false;
}, [pathname]);
const toggleEdit = React.useCallback(
(e) => {
// if first characters of pathname are /things replace with /edit
// or if first characters of pathname are /edit replace with /things
if (pathname.slice(0, 7) === "/things") {
const newPathname = pathname.replace("/things", "/edit")
navigate(newPathname)
} else if (pathname.slice(0, 7) === "/editor") {
const newPathname = pathname.replace("/editor", "/things")
navigate(newPathname)
} else if (pathname.slice(0, 5) === "/edit") {
const newPathname = pathname.replace("/edit", "/things")
navigate(newPathname)
if (pathname.slice(0, 7) === '/things') {
const newPathname = pathname.replace('/things', '/edit');
navigate(newPathname);
} else if (pathname.slice(0, 7) === '/editor') {
const newPathname = pathname.replace('/editor', '/things');
navigate(newPathname);
} else if (pathname.slice(0, 5) === '/edit') {
const newPathname = pathname.replace('/edit', '/things');
navigate(newPathname);
}
},
[pathname, navigate]
)
);
const toggleEditor = React.useCallback(
(e) => {
// if first characters of pathname are /things replace with /edit
// or if first characters of pathname are /edit replace with /things
if (pathname.slice(0, 7) === "/editor") {
const newPathname = pathname.replace("/editor", "/edit")
navigate(newPathname)
} else if (pathname.slice(0, 5) === "/edit") {
const newPathname = pathname.replace("/edit", "/editor")
navigate(newPathname)
if (pathname.slice(0, 7) === '/editor') {
const newPathname = pathname.replace('/editor', '/edit');
navigate(newPathname);
} else if (pathname.slice(0, 5) === '/edit') {
const newPathname = pathname.replace('/edit', '/editor');
navigate(newPathname);
}
},
[pathname, navigate]
)
);
return (
<>
<Box
position="fixed"
zIndex={9999}
top={0}
right={0}
left={0}
maxWidth="100vw"
>
<Box position="fixed" zIndex={9999} top={0} right={0} left={0} maxWidth="100vw">
<Flex
as="nav"
position="relative"
@ -99,25 +92,15 @@ export const Nav = (props) => {
// bg='white'
// boxShadow={'0px 0px 10px rgba(0,0,0,0.1)'}
>
<Center
className="nav-left-section"
display={["none", "flex"]}
height="100%"
marginRight="auto"
>
<Center className="nav-left-section" display={['none', 'flex']} height="100%" marginRight="auto">
<Center transform="scaleX(-100%)" cursor="pointer">
<Link to="/">
<Icon size="12px" name="unicorn"></Icon>
<Icon size="12px" name="🦄"></Icon>
</Link>
</Center>
</Center>
<CommanderV1 global id="nav" rainbow={false}></CommanderV1>
<Center
className="nav-right-section"
columnGap={[3, 8]}
height="100%"
marginLeft="auto"
>
<Center className="nav-right-section" columnGap={[3, 8]} height="100%" marginLeft="auto">
{inEditMode && (
<Center
// transform="scaleX(-100%)"
@ -126,10 +109,10 @@ export const Nav = (props) => {
>
<Icon
chakras={{
opacity: inEditorMode ? 1 : 0.3,
opacity: inEditorMode ? 1 : 0.3
}}
size="12px"
name="two eyes"
name="👀"
></Icon>
{/* <Icon
size="12px"
@ -138,17 +121,13 @@ export const Nav = (props) => {
</Center>
)}
{editorToggleable && (
<Center
transform="scaleX(-100%)"
cursor="pointer"
onClick={toggleEdit}
>
<Center transform="scaleX(-100%)" cursor="pointer" onClick={toggleEdit}>
<Icon
chakras={{
opacity: inEditMode ? 1 : 0.3,
opacity: inEditMode ? 1 : 0.3
}}
size="12px"
name="paint"
name="🎨"
></Icon>
{/* <Icon
size="12px"
@ -156,12 +135,12 @@ export const Nav = (props) => {
></Icon> */}
</Center>
)}
<Center transform={["", "scaleX(-100%)"]} cursor="pointer">
<Icon size="12px" name="rainbow"></Icon>
<Center transform={['', 'scaleX(-100%)']} cursor="pointer">
<Icon size="12px" name="🌈"></Icon>
</Center>
<Center display={["flex", "none"]} cursor="pointer">
<Center display={['flex', 'none']} cursor="pointer">
<Link to="/">
<Icon size="12px" name="unicorn"></Icon>
<Icon size="12px" name="🦄"></Icon>
</Link>
</Center>
</Center>
@ -181,5 +160,5 @@ export const Nav = (props) => {
</Box>
{/* <ProfileDrawer isOpen={profileDrawerOpen}></ProfileDrawer> */}
</>
)
}
);
};

View File

@ -1,8 +1,8 @@
import React from 'react'
import { Drawer, DrawerBody, DrawerContent, Flex } from '@chakra-ui/react'
import React from "react"
import { Drawer, DrawerBody, DrawerContent, Flex } from "@chakra-ui/react"
export const ProfileDrawer = props => {
const navItems = ['settings']
export const ProfileDrawer = (props) => {
const navItems = ["settings"]
const onClose = React.useCallback(() => {
// nothing
@ -10,12 +10,12 @@ export const ProfileDrawer = props => {
return (
<Drawer
placement='right'
size='md'
isOpen={props?.isOpen}
onClose={onClose}
placement="right"
size="md"
>
<DrawerContent maxW={'90%'} mt={'35px'} className='ProfileDrawer'>
<DrawerContent className="ProfileDrawer" maxWidth="90%" marginTop="35px">
<DrawerBody>
{navItems.map((item, idx) => {
return <Flex key={idx}>{item}</Flex>

View File

@ -0,0 +1,70 @@
import React from "react"
import { Flex } from "@chakra-ui/react"
import { Attention } from "../Buttons/Attention"
import { Hamburger } from "../Buttons/Hamburger"
import { RightNav } from "./ReactiveRightNav"
export const ReactiveNav = (props) => {
const [navItems] = React.useState([{}])
const setNav = React.useCallback((active) => {
setMt(active ? "100%" : "0%")
setNavActive(active)
}, [])
React.useEffect(() => {
// react to mouse move event
const listener = (event) => {
if (event?.clientY <= 20) {
setNav(true)
} else if (event?.clientY >= 100) {
setNav(false)
}
}
window.addEventListener("mousemove", listener)
return () => {
window.removeEventListener("mousemove", listener)
}
}, [setNav])
const [navActive, setNavActive] = React.useState(false)
const [mt, setMt] = React.useState("0%")
return (
<>
<Flex
as="nav"
position="fixed"
right={0}
bottom="100%"
left={0}
alignItems="center"
justifyContent="center"
flexDirection="row"
width="100%"
transform={`translateY(${mt})`}
transition="all 0.3s ease-in-out"
// top={'100%'}
// top={0}
paddingX="1%"
paddingY="1%"
>
<Hamburger marginLeft="auto"></Hamburger>
<Flex
position="absolute"
top="100%"
left="50%"
opacity={navActive ? 0 : 1}
transition="all 0.2s ease-in-out"
translateX="-50%"
>
{/* w={navActive ? '0px' : '40px'} */}
<Attention></Attention>
</Flex>
</Flex>
<RightNav></RightNav>
</>
)
}

View File

@ -1,9 +1,9 @@
import React from 'react'
import { Center, Text } from '@chakra-ui/react'
import React from "react"
import { Center, Text } from "@chakra-ui/react"
export const ReactiveRightNav = props => {
const setNav = React.useCallback(active => {
setMr(active ? '0%' : '-100%')
export const ReactiveRightNav = (props) => {
const setNav = React.useCallback((active) => {
setMr(active ? "0%" : "-100%")
setNavActive(active)
}, [])
@ -11,7 +11,7 @@ export const ReactiveRightNav = props => {
React.useEffect(() => {
// react to mouse move event
const listener = event => {
const listener = (event) => {
const windowWidth = window.innerWidth
if (!entered) {
if (event?.clientX >= windowWidth - 60) {
@ -21,56 +21,56 @@ export const ReactiveRightNav = props => {
}
}
}
window.addEventListener('mousemove', listener)
window.addEventListener("mousemove", listener)
return () => {
window.removeEventListener('mousemove', listener)
window.removeEventListener("mousemove", listener)
}
}, [setNav, entered])
const [navActive, setNavActive] = React.useState(false)
// const defaultMr = '-100%'
const defaultMr = '0%'
const defaultMr = "0%"
const [mr, setMr] = React.useState(defaultMr)
const navItems = React.useMemo(() => {
return ['home', 'imagine', 'create', 'share']
return ["home", "imagine", "create", "share"]
}, [])
return (
<Center
mr={mr}
transition={'all 0.3s ease-in-out'}
position='fixed'
position="fixed"
top={0}
right={0}
h='100%'
flexDir='column'
flexDirection="column"
height="100%"
marginRight={mr}
transition="all 0.3s ease-in-out"
>
<Center
flexDirection="column"
minWidth="600px"
maxWidth="100%"
height="85%"
background="rgba(0, 0, 0, 0.01)"
borderLeftRadius="40px"
onMouseEnter={() => setEntered(true)}
onMouseLeave={() => setEntered(false)}
flexDir={'column'}
borderLeftRadius={'40px'}
h='85%'
minW={'600px'}
maxW='100%'
py={80}
px={100}
bg={`rgba(0, 0, 0, 0.01)`}
paddingX={100}
paddingY={80}
>
{navItems.map((navItem, idx) => {
return (
<Text
color='black'
my={'auto'}
fontWeight='semibold'
key={idx}
textTransform={'capitalize'}
as='h2'
cursor='pointer'
fontSize='30px'
as="h2"
color="black"
fontSize="30px"
fontWeight="semibold"
textTransform="capitalize"
cursor="pointer"
marginY="auto"
>
{navItem}
</Text>

View File

@ -1,6 +1,6 @@
import { safe } from '~/functions/safe'
import { safe } from "~/functions/safe"
export const Safe = props => {
export const Safe = (props) => {
// do not render more than the limit of things to prevent infinite loops
return safe(props)

View File

@ -0,0 +1,46 @@
import React from "react"
import { Flex } from "@chakra-ui/react"
export const RainbowSkeleton = (props) => {
const [rainbowColours] = React.useState([
"#f34a4a",
"#ffbc48",
"#58ca70",
"#47b5e6",
"#a555e8",
])
const keyframes = React.useMemo(() => {
const keyframes = {}
rainbowColours.forEach((colour, idx) => {
keyframes[Math.round((idx * 100) / rainbowColours.length) + "%"] = {
backgroundColor: colour,
}
})
keyframes["100%"] = { backgroundColor: rainbowColours[0] }
return keyframes
}, [rainbowColours])
if (props?.loaded) {
return props?.children
}
return (
<Flex
sx={{
"@keyframes placeholder-rainbow": keyframes,
"@keyframes placeholder-opacity": {
"0%": { opacity: 0.2 },
"100%": { opacity: 1 },
},
// add delay
animation: `placeholder-rainbow 8s infinite linear, placeholder-opacity 1.3s linear 0s infinite alternate none running}`,
}}
width="10px"
height="8px"
borderRadius="2px"
cursor="pointer"
{...props}
></Flex>
)
}

View File

@ -1,6 +1,6 @@
import { useContext } from "react"
import { ThingtimeContext } from "~/Providers/ThingtimeProvider"
import { ThingtimeContext } from "../../Providers/ThingtimeProvider"
const getGlobal = () => {
try {
@ -10,7 +10,7 @@ const getGlobal = () => {
}
}
export const useThingtime = (props?: any) => {
export const useThingtime = (props?: any): any => {
const value = useContext(ThingtimeContext)
// const { thingtime, setThingtime, getThingtime, thingtimeRef } = value

View File

@ -1,6 +1,6 @@
import { hydrate } from 'react-dom'
import { hydrate } from "react-dom"
// import { RemixBrowser } from "remix";
import { RemixBrowser } from '@remix-run/react'
import { RemixBrowser } from "@remix-run/react"
try {
window.process = {}
} catch (err) {

21
app/src/entry.server.tsx Normal file
View File

@ -0,0 +1,21 @@
import { renderToString } from "react-dom/server"
import { RemixServer } from "@remix-run/react"
import type { EntryContext } from "@remix-run/react/dist/entry"
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
const markup = renderToString(
<RemixServer context={remixContext} url={request.url} />
)
responseHeaders.set("Content-Type", "text/html")
return new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders,
})
}

21
app/src/global-types.d.ts vendored Normal file
View File

@ -0,0 +1,21 @@
// modify window / globalThis to support any properties
// so there's no property does not exist on window typescript errors
// Path: app/src/global-types.d.ts
declare global {
interface Window {
[key: string]: any;
}
// Modify React component props/args to allow anything
// so we don't get errors
// Property 'fullPath' does not exist on type '{ children?: ReactNode; }'.
interface ForwardRefRenderFunction {
T: any;
(props: any, ref: React.Ref<any>): React.ReactElement | null;
}
}
export {};

8
app/src/gp/Sample.js Normal file
View File

@ -0,0 +1,8 @@
export default class Sample {
constructor({ x, y, progress, segment }) {
this.x = x
this.y = y
this.progress = progress
this.segment = segment
}
}

8
app/src/gp/Segment.js Normal file
View File

@ -0,0 +1,8 @@
import { getMiddleSample } from "./_utils"
export default class Segment {
constructor({ samples }) {
this.samples = samples
this.progress = getMiddleSample(samples).progress
}
}

1
app/src/gp/_constants.js Normal file
View File

@ -0,0 +1 @@
export const DEFAULT_PRECISION = 2

View File

@ -1,7 +1,7 @@
import Sample from './Sample';
import Segment from './Segment';
import { convertPathToNode } from './_utils';
import { DEFAULT_PRECISION } from './_constants';
import { DEFAULT_PRECISION } from "./_constants"
import { convertPathToNode } from "./_utils"
import Sample from "./Sample"
import Segment from "./Segment"
// The main function responsible for getting data
// This will take a path, number of samples, number of samples, and a precision value
@ -11,34 +11,34 @@ export const getData = ({
path,
segments,
samples,
precision = DEFAULT_PRECISION
precision = DEFAULT_PRECISION,
}) => {
// Convert the given path to a DOM node if it isn't already one
path = convertPathToNode(path);
path = convertPathToNode(path)
// We decrement the number of samples per segment because when we group them later we will add on the first sample of the following segment
if (samples > 1) samples--;
if (samples > 1) samples--
// Get total length of path, total number of samples we will be generating, and two blank arrays to hold samples and segments
const pathLength = path.getTotalLength(),
totalSamples = segments * samples,
allSamples = [],
allSegments = [];
allSegments = []
// For the number of total samples, get the x, y, and progress values for each sample along the path
for (let sample = 0; sample <= totalSamples; sample++) {
const progress = sample / totalSamples;
const progress = sample / totalSamples
let { x, y } = path.getPointAtLength(progress * pathLength);
let { x, y } = path.getPointAtLength(progress * pathLength)
// If the user asks to round our x and y values, do so
if (precision) {
x = +x.toFixed(precision);
y = +y.toFixed(precision);
x = +x.toFixed(precision)
y = +y.toFixed(precision)
}
// Create a new Sample and push it onto the allSamples array
allSamples.push(new Sample({ x, y, progress }));
allSamples.push(new Sample({ x, y, progress }))
}
// Out of all the samples gathered previously, sort them into groups of segments
@ -46,23 +46,23 @@ export const getData = ({
for (let segment = 0; segment < segments; segment++) {
const currentStart = segment * samples,
nextStart = currentStart + samples,
segmentSamples = [];
segmentSamples = []
// Push all current samples onto segmentSamples
for (let samInSeg = 0; samInSeg < samples; samInSeg++) {
segmentSamples.push(allSamples[currentStart + samInSeg]);
segmentSamples.push(allSamples[currentStart + samInSeg])
}
// Push the first sample from the next segment onto segmentSamples
segmentSamples.push(allSamples[nextStart]);
segmentSamples.push(allSamples[nextStart])
// Create a new Segment with the samples from segmentSamples
allSegments.push(new Segment({ samples: segmentSamples }));
allSegments.push(new Segment({ samples: segmentSamples }))
}
// Return our group of segments
return allSegments;
};
return allSegments
}
// The function responsible for converting strokable data (from getData()) into fillable data
// This allows any SVG path to be filled instead of just stroked, allowing for the user to fill and stroke paths simultaneously
@ -73,10 +73,10 @@ export const strokeToFill = (data, width, precision, pathClosed) => {
outlinedStrokes,
precision,
pathClosed
);
)
return averagedSegmentJoins;
};
return averagedSegmentJoins
}
// An internal function for outlining stroked data
const outlineStrokes = (data, width, precision) => {
@ -85,53 +85,53 @@ const outlineStrokes = (data, width, precision) => {
const p0 = new Sample({
...startPoint,
x: Math.sin(angle) * radius + startPoint.x,
y: -Math.cos(angle) * radius + startPoint.y
y: -Math.cos(angle) * radius + startPoint.y,
}),
p1 = new Sample({
...startPoint,
x: -Math.sin(angle) * radius + startPoint.x,
y: Math.cos(angle) * radius + startPoint.y
});
y: Math.cos(angle) * radius + startPoint.y,
})
// If the user asks to round our x and y values, do so
if (precision) {
p0.x = +p0.x.toFixed(precision);
p0.y = +p0.y.toFixed(precision);
p1.x = +p1.x.toFixed(precision);
p1.y = +p1.y.toFixed(precision);
p0.x = +p0.x.toFixed(precision)
p0.y = +p0.y.toFixed(precision)
p1.x = +p1.x.toFixed(precision)
p1.y = +p1.y.toFixed(precision)
}
return [p0, p1];
};
return [p0, p1]
}
// We need to set the radius (half of the width) and have a holding array for outlined Segments
const radius = width / 2,
outlinedData = [];
outlinedData = []
for (let i = 0; i < data.length; i++) {
const samples = data[i].samples,
segmentSamples = [];
segmentSamples = []
// For each sample point and the following sample point (if there is one) compute the angle
// Also compute the sample's various perpendicular points (with a distance of radius away from the sample point)
for (let j = 0; j < samples.length; j++) {
// If we're at the end of the segment and there are no further points, get outta here!
if (samples[j + 1] === undefined) break;
if (samples[j + 1] === undefined) break
const p0 = samples[j], // First point
p1 = samples[j + 1], // Second point
angle = Math.atan2(p1.y - p0.y, p1.x - p0.x), // Perpendicular angle to p0 and p1
p0Perps = getPerpSamples(angle, radius, precision, p0), // Get perpedicular points with a distance of radius away from p0
p1Perps = getPerpSamples(angle, radius, precision, p1); // Get perpedicular points with a distance of radius away from p1
p1Perps = getPerpSamples(angle, radius, precision, p1) // Get perpedicular points with a distance of radius away from p1
// We only need the p0 perpendenciular points for the first sample
// The p0 for j > 0 will always be the same as p1 anyhow, so let's not add redundant points
if (j === 0) {
segmentSamples.push(...p0Perps);
segmentSamples.push(...p0Perps)
}
// Always push the second sample point's perpendicular points
segmentSamples.push(...p1Perps);
segmentSamples.push(...p1Perps)
}
// segmentSamples is out of order...
@ -140,14 +140,14 @@ const outlineStrokes = (data, width, precision) => {
new Segment({
samples: [
...segmentSamples.filter((s, i) => i % 2 === 0),
...segmentSamples.filter((s, i) => i % 2 === 1).reverse()
]
...segmentSamples.filter((s, i) => i % 2 === 1).reverse(),
],
})
);
)
}
return outlinedData;
};
return outlinedData
}
// An internal function taking outlinedData (from outlineStrokes()) and averaging adjacent edges
// If we didn't do this, our data would be fillable, but it would look stroked
@ -156,17 +156,17 @@ const averageSegmentJoins = (outlinedData, precision, pathClosed) => {
// Find the average x and y between two points (p0 and p1)
const avg = (p0, p1) => ({
x: (p0.x + p1.x) / 2,
y: (p0.y + p1.y) / 2
});
y: (p0.y + p1.y) / 2,
})
// Recombine the new x and y positions with all the other keys in the object
const combine = (segment, pos, avg) => ({
...segment[pos],
x: avg.x,
y: avg.y
});
y: avg.y,
})
const init_outlinedData = JSON.parse(JSON.stringify(outlinedData)); //clone initial outlinedData Object
const init_outlinedData = JSON.parse(JSON.stringify(outlinedData)) //clone initial outlinedData Object
for (let i = 0; i < outlinedData.length; i++) {
// If path is closed: the current segment's samples;
@ -186,34 +186,34 @@ const averageSegmentJoins = (outlinedData, precision, pathClosed) => {
? outlinedData[i + 1].samples
: init_outlinedData[0].samples,
currentMiddle = currentSamples.length / 2, // The "middle" sample in the current segment's samples
nextEnd = nextSamples.length - 1; // The last sample in the next segment's samples
nextEnd = nextSamples.length - 1 // The last sample in the next segment's samples
// Average two sets of outlined samples to create p0Average and p1Average
const p0Average = avg(currentSamples[currentMiddle - 1], nextSamples[0]),
p1Average = avg(currentSamples[currentMiddle], nextSamples[nextEnd]);
p1Average = avg(currentSamples[currentMiddle], nextSamples[nextEnd])
// If the user asks to round our x and y values, do so
if (precision) {
p0Average.x = +p0Average.x.toFixed(precision);
p0Average.y = +p0Average.y.toFixed(precision);
p1Average.x = +p1Average.x.toFixed(precision);
p1Average.y = +p1Average.y.toFixed(precision);
p0Average.x = +p0Average.x.toFixed(precision)
p0Average.y = +p0Average.y.toFixed(precision)
p1Average.x = +p1Average.x.toFixed(precision)
p1Average.y = +p1Average.y.toFixed(precision)
}
// Replace the previous values with new Samples
currentSamples[currentMiddle - 1] = new Sample({
...combine(currentSamples, currentMiddle - 1, p0Average)
});
...combine(currentSamples, currentMiddle - 1, p0Average),
})
currentSamples[currentMiddle] = new Sample({
...combine(currentSamples, currentMiddle, p1Average)
});
...combine(currentSamples, currentMiddle, p1Average),
})
nextSamples[0] = new Sample({
...combine(nextSamples, 0, p0Average)
});
...combine(nextSamples, 0, p0Average),
})
nextSamples[nextEnd] = new Sample({
...combine(nextSamples, nextEnd, p1Average)
});
...combine(nextSamples, nextEnd, p1Average),
})
}
return outlinedData;
};
return outlinedData
}

View File

@ -15,13 +15,7 @@ export const svgElem = (type, attrs) => {
}
// An internal function to help with the repetition of adding fill, stroke, and stroke-width attributes
export const styleAttrs = (
fill,
stroke,
strokeWidth,
progress,
animation
) => {
export const styleAttrs = (fill, stroke, strokeWidth, progress, animation) => {
const determineColor = (type, progress) =>
typeof type === "string" ? type : tinygradient(type).rgbAt(progress)
@ -38,9 +32,9 @@ export const styleAttrs = (
if (animation?.name) {
// TODO: add animation-direction support
const duration = animation.duration || 5
attrs["style"] = `
animation-name: ${animation?.name};
animation-delay: ${progress * duration - duration}s;

2
app/src/gp/index.js Normal file
View File

@ -0,0 +1,2 @@
export { default as GradientPath } from "./GradientPath"
export { getData, strokeToFill } from "./_data"

View File

@ -1,6 +1,6 @@
export const config = { runtime: 'edge' }
export const config = { runtime: "edge" }
export default function Edge () {
export default function Edge() {
return (
<div>
<h1>Welcome to Thingtime@Edge</h1>

View File

@ -15,7 +15,7 @@
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
"~/*": ["./src/*"]
},
// Remix takes care of building everything in `remix build`.

View File

@ -1,7 +1,7 @@
{
"build": {
"env": {
"ENABLE_FILE_SYSTEM_API": "1"
}
"build": {
"env": {
"ENABLE_FILE_SYSTEM_API": "1"
}
}
}
}