Added next and remix directories main
This commit is contained in:
parent
d2bfd8f991
commit
4668eba03d
3
.eslintrc.json
Normal file
3
.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
9444
app/pnpm-lock.yaml
9444
app/pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,7 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { ThingtimeURL } from '~/components/Thingtime/ThingtimeURL';
|
|
||||||
|
|
||||||
export default function Index() {
|
|
||||||
return <ThingtimeURL></ThingtimeURL>;
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export async function loader({ request }) {
|
|
||||||
return { message: 'Hello, World!' };
|
|
||||||
}
|
|
3
next/.eslintrc.json
Normal file
3
next/.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
36
next/.gitignore
vendored
Normal file
36
next/.gitignore
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
.yarn/install-state.gz
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
36
next/README.md
Normal file
36
next/README.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
3
next/global.d.ts
vendored
Normal file
3
next/global.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
interface EventTarget {
|
||||||
|
value: any
|
||||||
|
}
|
4
next/next.config.mjs
Normal file
4
next/next.config.mjs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {};
|
||||||
|
|
||||||
|
export default nextConfig;
|
65
next/package.json
Normal file
65
next/package.json
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"name": "nexttime",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "next build",
|
||||||
|
"dev": "next dev",
|
||||||
|
"lint": "next lint",
|
||||||
|
"start": "next start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@chakra-ui/next-js": "^2.2.0",
|
||||||
|
"@chakra-ui/react": "^2.8.2",
|
||||||
|
"@editorjs/editorjs": "^2.27.2",
|
||||||
|
"@emotion/react": "^11.11.4",
|
||||||
|
"@emotion/styled": "^11.11.5",
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||||
|
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||||
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
|
"@vercel/analytics": "^0.1.11",
|
||||||
|
"axios": "^1.6.8",
|
||||||
|
"draft-js": "^0.11.7",
|
||||||
|
"emojis-list": "^3.0.0",
|
||||||
|
"emotion": "^11.0.0",
|
||||||
|
"flatted": "^3.2.7",
|
||||||
|
"framer-motion": "^11.0.28",
|
||||||
|
"fuse.js": "^6.6.2",
|
||||||
|
"gradient-path": "^2.3.0",
|
||||||
|
"hex-to-rgba": "^2.0.1",
|
||||||
|
"next": "14.2.1",
|
||||||
|
"react": "^18",
|
||||||
|
"react-click-away-listener": "^2.2.3",
|
||||||
|
"react-contenteditable": "^3.3.7",
|
||||||
|
"react-dom": "^18",
|
||||||
|
"react-icons": "^4.10.1",
|
||||||
|
"react-sticky": "^6.0.3",
|
||||||
|
"react-sticky-el": "^2.1.0",
|
||||||
|
"react-visibility-sensor": "^5.1.1",
|
||||||
|
"rxjs": "^7.8.1",
|
||||||
|
"tinygradient": "^1.1.5",
|
||||||
|
"uuid": "^9.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/eslint": "^8.40.2",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^18",
|
||||||
|
"@types/react-dom": "^18",
|
||||||
|
"@typescript-eslint/parser": "^5.60.1",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "14.2.1",
|
||||||
|
"eslint-config-prettier": "^8.8.0",
|
||||||
|
"eslint-loader": "^4.0.2",
|
||||||
|
"eslint-plugin-chakra-ui": "^0.8.0",
|
||||||
|
"eslint-plugin-hydrogen": "^0.12.3",
|
||||||
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"eslint-plugin-react": "^7.32.2",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
|
"postcss": "^8",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
8
next/postcss.config.mjs
Normal file
8
next/postcss.config.mjs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/** @type {import('postcss-load-config').Config} */
|
||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
1
next/public/next.svg
Normal file
1
next/public/next.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
next/public/vercel.svg
Normal file
1
next/public/vercel.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
After Width: | Height: | Size: 629 B |
33
next/src/app/globals.css
Normal file
33
next/src/app/globals.css
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--foreground-rgb: 0, 0, 0;
|
||||||
|
--background-start-rgb: 214, 219, 220;
|
||||||
|
--background-end-rgb: 255, 255, 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--foreground-rgb: 255, 255, 255;
|
||||||
|
--background-start-rgb: 0, 0, 0;
|
||||||
|
--background-end-rgb: 0, 0, 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: rgb(var(--foreground-rgb));
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent,
|
||||||
|
rgb(var(--background-end-rgb))
|
||||||
|
)
|
||||||
|
rgb(var(--background-start-rgb));
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.text-balance {
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
}
|
22
next/src/app/layout.tsx
Normal file
22
next/src/app/layout.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { Inter } from 'next/font/google';
|
||||||
|
import './globals.css';
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ['latin'] });
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Thingtime',
|
||||||
|
description: 'The future of tech'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={inter.className}>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
230
next/src/app/page.module.css
Normal file
230
next/src/app/page.module.css
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6rem;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
display: inherit;
|
||||||
|
justify-content: inherit;
|
||||||
|
align-items: inherit;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
max-width: var(--max-width);
|
||||||
|
width: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
.description a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description p {
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: rgba(var(--callout-rgb), 0.5);
|
||||||
|
border: 1px solid rgba(var(--callout-border-rgb), 0.3);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(25%, auto));
|
||||||
|
max-width: 100%;
|
||||||
|
width: var(--max-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 1rem 1.2rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background: rgba(var(--card-rgb), 0);
|
||||||
|
border: 1px solid rgba(var(--card-border-rgb), 0);
|
||||||
|
transition: background 200ms, border 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card span {
|
||||||
|
display: inline-block;
|
||||||
|
transition: transform 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h2 {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card p {
|
||||||
|
margin: 0;
|
||||||
|
opacity: 0.6;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
max-width: 30ch;
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
padding: 4rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center::before {
|
||||||
|
background: var(--secondary-glow);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 480px;
|
||||||
|
height: 360px;
|
||||||
|
margin-left: -400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center::after {
|
||||||
|
background: var(--primary-glow);
|
||||||
|
width: 240px;
|
||||||
|
height: 180px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center::before,
|
||||||
|
.center::after {
|
||||||
|
content: "";
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
filter: blur(45px);
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
/* Enable hover only on non-touch devices */
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.card:hover {
|
||||||
|
background: rgba(var(--card-rgb), 0.1);
|
||||||
|
border: 1px solid rgba(var(--card-border-rgb), 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover span {
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion) {
|
||||||
|
.card:hover span {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile */
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.content {
|
||||||
|
padding: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
margin-bottom: 120px;
|
||||||
|
max-width: 320px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 1rem 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h2 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
padding: 8rem 0 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center::before {
|
||||||
|
transform: none;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description a {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description p,
|
||||||
|
.description div {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description p {
|
||||||
|
align-items: center;
|
||||||
|
inset: 0 0 auto;
|
||||||
|
padding: 2rem 1rem 1.4rem;
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(var(--background-start-rgb), 1),
|
||||||
|
rgba(var(--callout-rgb), 0.5)
|
||||||
|
);
|
||||||
|
background-clip: padding-box;
|
||||||
|
backdrop-filter: blur(24px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.description div {
|
||||||
|
align-items: flex-end;
|
||||||
|
pointer-events: none;
|
||||||
|
inset: auto 0 0;
|
||||||
|
padding: 2rem;
|
||||||
|
height: 200px;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent 0%,
|
||||||
|
rgb(var(--background-end-rgb)) 40%
|
||||||
|
);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablet and Smaller Desktop */
|
||||||
|
@media (min-width: 701px) and (max-width: 1120px) {
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: repeat(2, 50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.vercelLogo {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
from {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
6
next/src/app/page_app.tsx
Normal file
6
next/src/app/page_app.tsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Image from 'next/image';
|
||||||
|
import styles from './page.module.css';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return <main className={styles.main}>Thingtime</main>;
|
||||||
|
}
|
26
next/src/components/Buttons/Attention.tsx
Normal file
26
next/src/components/Buttons/Attention.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export const Attention = (props: any) => {
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
32
next/src/components/Buttons/Hamburger.tsx
Normal file
32
next/src/components/Buttons/Hamburger.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export const Hamburger = (props: any) => {
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
545
next/src/components/Commander/Commander.tsx
Normal file
545
next/src/components/Commander/Commander.tsx
Normal file
@ -0,0 +1,545 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ClickAwayListener from 'react-click-away-listener';
|
||||||
|
import { Box, Center, Flex, Input } from '@chakra-ui/react';
|
||||||
|
import Fuse from 'fuse.js';
|
||||||
|
|
||||||
|
import { MagicInput } from '../MagicInput/MagicInput';
|
||||||
|
import { Rainbow } from '../Rainbow/Rainbow';
|
||||||
|
import { Thingtime } from '../Thingtime/Thingtime';
|
||||||
|
import { useThingtime } from '../Thingtime/useThingtime';
|
||||||
|
|
||||||
|
import { sanitise } from '@/functions/sanitise';
|
||||||
|
import { getParentPath } from '@/smarts';
|
||||||
|
|
||||||
|
export const Commander = (props: any) => {
|
||||||
|
const { thingtime, setThingtime, getThingtime, thingtimeRef, paths } = useThingtime();
|
||||||
|
|
||||||
|
const commanderId = React.useMemo(() => {
|
||||||
|
return props?.id || 'global';
|
||||||
|
}, [props?.id]);
|
||||||
|
|
||||||
|
const inputRef = React.useRef();
|
||||||
|
|
||||||
|
const global = props?.global;
|
||||||
|
|
||||||
|
const commanderSettings = React.useMemo(() => {
|
||||||
|
return thingtime?.settings?.commander?.[commanderId] || {};
|
||||||
|
}, [thingtime?.settings?.commander, thingtime?.settings?.commander?.[commanderId], commanderId]);
|
||||||
|
|
||||||
|
const [inputValue, setInputValue] = React.useState('');
|
||||||
|
const [virtualValue, setVirtualValue] = React.useState('');
|
||||||
|
const [hoveredSuggestion, setHoveredSuggestion] = React.useState();
|
||||||
|
const [active, setActive] = React.useState(false);
|
||||||
|
const [contextPath, setContextPath] = React.useState();
|
||||||
|
|
||||||
|
const mode = React.useMemo(() => {
|
||||||
|
return props?.mode || 'value';
|
||||||
|
}, [props?.mode]);
|
||||||
|
|
||||||
|
const [showContext, setShowContextState] = React.useState(false);
|
||||||
|
|
||||||
|
const mobileVW = React.useMemo(() => {
|
||||||
|
return 'calc(100vw - 55px)';
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const rainbowRepeats = 2;
|
||||||
|
|
||||||
|
const setShowContext = React.useCallback(
|
||||||
|
(value, from?: string) => {
|
||||||
|
setShowContextState(value);
|
||||||
|
},
|
||||||
|
[setShowContextState]
|
||||||
|
);
|
||||||
|
// const [suggestions, setSuggestions] = React.useState([])
|
||||||
|
|
||||||
|
const contextValue = React.useMemo(() => {
|
||||||
|
// TODO: Figure out why this is running on every click
|
||||||
|
const ret = getThingtime(contextPath);
|
||||||
|
return ret;
|
||||||
|
}, [contextPath, getThingtime]);
|
||||||
|
|
||||||
|
const commanderActive = React.useMemo(() => {
|
||||||
|
return thingtime?.settings?.commander?.[commanderId]?.commanderActive;
|
||||||
|
}, [commanderSettings, commanderId]);
|
||||||
|
|
||||||
|
// commanderActive useEffect
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (commanderActive) {
|
||||||
|
if (props?.global) {
|
||||||
|
inputRef?.current?.focus?.();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (props?.global) {
|
||||||
|
document.activeElement.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thingtimeRef?.current?.settings?.commander?.[commanderId]?.clearCommanderOnToggle) {
|
||||||
|
setInputValue('');
|
||||||
|
setHoveredSuggestion(null);
|
||||||
|
}
|
||||||
|
if (thingtimeRef?.current?.settings?.commander?.[commanderId]?.commander?.[commanderId]?.clearCommanderContextOnToggle) {
|
||||||
|
setShowContext(false, 'commanderActive useEffect');
|
||||||
|
}
|
||||||
|
if (contextPath !== undefined && !inputValue) {
|
||||||
|
setContextPath(undefined);
|
||||||
|
}
|
||||||
|
if (showContext !== false) {
|
||||||
|
setShowContext(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [commanderActive, thingtimeRef, setShowContext, props?.global, commanderId, inputValue, contextPath, showContext]);
|
||||||
|
|
||||||
|
const onInputChange = React.useCallback((e) => {
|
||||||
|
setInputValue(e.target.value);
|
||||||
|
setHoveredSuggestion(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validSetters = React.useMemo(() => {
|
||||||
|
return ['=', ' is ', ' IS ', ' Is ', ' iS '];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const command = React.useMemo(() => {
|
||||||
|
// const sanitizedCommand = sanitise(value)
|
||||||
|
// const sanitizedCommand = inputValue
|
||||||
|
const sanitizedInput = virtualValue;
|
||||||
|
|
||||||
|
const validSetter = validSetters?.find((setter) => {
|
||||||
|
if (sanitizedInput?.includes(setter)) {
|
||||||
|
return setter;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof validSetter === 'string') {
|
||||||
|
const indexOfSplitter = sanitizedInput?.indexOf(validSetter);
|
||||||
|
const [pathRaw, valRaw] = [sanitizedInput?.slice(0, indexOfSplitter), sanitizedInput?.slice(indexOfSplitter + validSetter?.length)];
|
||||||
|
|
||||||
|
const pathTrimmed = pathRaw?.trim();
|
||||||
|
|
||||||
|
let path = pathTrimmed;
|
||||||
|
|
||||||
|
if (pathTrimmed && props?.pathPrefix) {
|
||||||
|
path = props?.pathPrefix + '.' + pathTrimmed;
|
||||||
|
} else if (props?.pathPrefix) {
|
||||||
|
path = props?.pathPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [path, valRaw?.trim()];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props?.pathPrefix) {
|
||||||
|
return [props?.pathPrefix, sanitizedInput];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [sanitizedInput];
|
||||||
|
}, [
|
||||||
|
// inputValue,
|
||||||
|
props?.pathPrefix,
|
||||||
|
virtualValue,
|
||||||
|
validSetters
|
||||||
|
]);
|
||||||
|
|
||||||
|
const commandPath = React.useMemo(() => {
|
||||||
|
return command?.[0];
|
||||||
|
// return sanitise(command?.[0])
|
||||||
|
}, [command]);
|
||||||
|
|
||||||
|
const commandValue = React.useMemo(() => {
|
||||||
|
return command?.[1];
|
||||||
|
}, [command]);
|
||||||
|
|
||||||
|
const validQuotations = React.useMemo(() => {
|
||||||
|
return ['"', "'"];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const escapedCommandValue = React.useMemo(() => {
|
||||||
|
// replace quotations with escaped quoations except for first and last quotation
|
||||||
|
const startingQuotation = commandValue?.[0];
|
||||||
|
const endingQuotation = commandValue?.[commandValue?.length - 1];
|
||||||
|
const isQuoted = validQuotations?.includes(startingQuotation) && validQuotations?.includes(endingQuotation);
|
||||||
|
const restOfCommandValue = isQuoted ? commandValue?.slice(1, commandValue?.length - 1) : commandValue;
|
||||||
|
const escaped = restOfCommandValue?.replace(/"/g, '\\"')?.replace(/'/g, "\\'")?.replace(/`/g, '\\`');
|
||||||
|
|
||||||
|
const ret = `\`${escaped}\``;
|
||||||
|
return ret;
|
||||||
|
}, [commandValue, validQuotations]);
|
||||||
|
|
||||||
|
const commandIsAction = React.useMemo(() => {
|
||||||
|
return commandPath && commandValue;
|
||||||
|
}, [commandPath, commandValue]);
|
||||||
|
|
||||||
|
const suggestions = React.useMemo(() => {
|
||||||
|
try {
|
||||||
|
const fuse = new Fuse(paths);
|
||||||
|
|
||||||
|
const results = fuse.search(inputValue);
|
||||||
|
|
||||||
|
const mappedResults = results?.map((result) => {
|
||||||
|
return result?.item;
|
||||||
|
});
|
||||||
|
|
||||||
|
return mappedResults;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('fuse error', err);
|
||||||
|
}
|
||||||
|
}, [inputValue, paths]);
|
||||||
|
|
||||||
|
const showSuggestions = React.useMemo(() => {
|
||||||
|
return inputValue?.length && suggestions?.length && commanderActive && thingtime?.settings?.commander?.[commanderId]?.hideSuggestionsOnToggle;
|
||||||
|
}, [inputValue, suggestions, commanderActive, commanderId, thingtime?.settings?.commander, commanderSettings]);
|
||||||
|
|
||||||
|
const selectSuggestion = React.useCallback(
|
||||||
|
(suggestionIdx) => {
|
||||||
|
const suggestion = suggestions?.[suggestionIdx];
|
||||||
|
|
||||||
|
setInputValue(suggestion);
|
||||||
|
setHoveredSuggestion(null);
|
||||||
|
setContextPath(suggestion);
|
||||||
|
setShowContext(true, 'Select suggestion');
|
||||||
|
},
|
||||||
|
[setInputValue, setContextPath, setShowContext, suggestions]
|
||||||
|
);
|
||||||
|
|
||||||
|
const commandContainsPath = React.useMemo(() => {
|
||||||
|
const commandIncludesSuggestion = suggestions?.find((suggestion) => {
|
||||||
|
return commandPath?.includes(suggestion);
|
||||||
|
});
|
||||||
|
// return false
|
||||||
|
return commandIncludesSuggestion;
|
||||||
|
}, [commandPath, suggestions]);
|
||||||
|
|
||||||
|
const openCommander = React.useCallback(() => {
|
||||||
|
setThingtime(`settings.commander.${commanderId}.commanderActive`, true);
|
||||||
|
}, [setThingtime, commanderId]);
|
||||||
|
|
||||||
|
const closeCommander = React.useCallback(
|
||||||
|
(e?: any) => {
|
||||||
|
if (!e?.defaultPrevented) {
|
||||||
|
if (thingtime?.settings?.commander?.[commanderId]?.commanderActive) {
|
||||||
|
setThingtime(`settings.commander.${commanderId}.commanderActive`, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setThingtime, commanderId, commanderSettings, thingtime?.settings?.commander]
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleCommander = React.useCallback(() => {
|
||||||
|
if (thingtime?.settings?.commander?.[commanderId]?.commanderActive) {
|
||||||
|
closeCommander();
|
||||||
|
} else {
|
||||||
|
openCommander();
|
||||||
|
}
|
||||||
|
}, [thingtime?.settings?.commander, commanderSettings, commanderId, closeCommander, openCommander]);
|
||||||
|
|
||||||
|
const executeCommand = React.useCallback(() => {
|
||||||
|
// if selection is active then select it
|
||||||
|
const curSuggestionIdx = hoveredSuggestion;
|
||||||
|
if (curSuggestionIdx !== null) {
|
||||||
|
selectSuggestion(curSuggestionIdx);
|
||||||
|
}
|
||||||
|
if (commanderActive) {
|
||||||
|
try {
|
||||||
|
if (commandIsAction) {
|
||||||
|
// nothing
|
||||||
|
const prevVal = getThingtime(commandPath);
|
||||||
|
const parentPath = getParentPath(commandPath) || 'thingtime';
|
||||||
|
try {
|
||||||
|
// first try to execute literal javscript
|
||||||
|
const fn = `() => { return ${commandValue} }`;
|
||||||
|
const tt = thingtime;
|
||||||
|
const evalFn = eval(fn);
|
||||||
|
const realVal = evalFn();
|
||||||
|
setThingtime(commandPath, realVal);
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Caught error after trying to execute literal javascript', err);
|
||||||
|
|
||||||
|
// likely literaly javascript wasn't valid
|
||||||
|
try {
|
||||||
|
const fn = `() => { return ${escapedCommandValue} }`;
|
||||||
|
const tt = thingtime;
|
||||||
|
const evalFn = eval(fn);
|
||||||
|
const realVal = evalFn();
|
||||||
|
const prevVal = getThingtime(commandPath);
|
||||||
|
const parentPath = getParentPath(commandPath);
|
||||||
|
setThingtime(commandPath, realVal);
|
||||||
|
} catch {
|
||||||
|
// something very bad went wrong
|
||||||
|
console.log('Caught error after trying to execute escaped literal javascript', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if (!prevVal) {
|
||||||
|
setContextPath(commandPath);
|
||||||
|
setShowContext(true, 'commandIsAction check');
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
// if (commandContainsPath)
|
||||||
|
else {
|
||||||
|
// const prevValue = getThingtime(commandPath)
|
||||||
|
|
||||||
|
// const newValue = setThingtime(commandPath, prevValue)
|
||||||
|
|
||||||
|
console.log('Setting context path', commandPath);
|
||||||
|
setContextPath(commandPath);
|
||||||
|
setShowContext(true, 'commandContainsPath check');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Caught error on commander onEnter', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
hoveredSuggestion,
|
||||||
|
selectSuggestion,
|
||||||
|
commanderActive,
|
||||||
|
commandIsAction,
|
||||||
|
commandPath,
|
||||||
|
thingtime,
|
||||||
|
commandValue,
|
||||||
|
escapedCommandValue,
|
||||||
|
getThingtime,
|
||||||
|
setThingtime,
|
||||||
|
setContextPath,
|
||||||
|
setShowContext
|
||||||
|
]);
|
||||||
|
|
||||||
|
const allCommanderKeyListener = React.useCallback(
|
||||||
|
(e: any) => {
|
||||||
|
console.log('commander key listener e?.code', e?.code);
|
||||||
|
thingtimeRef.current = thingtime;
|
||||||
|
if (e?.metaKey && e?.code === 'KeyP') {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleCommander();
|
||||||
|
}
|
||||||
|
// if key escape close all modals
|
||||||
|
else if (e?.code === 'Escape') {
|
||||||
|
closeCommander();
|
||||||
|
}
|
||||||
|
|
||||||
|
// only run these if commander active
|
||||||
|
|
||||||
|
if (commanderActive) {
|
||||||
|
// if arrow keys then move selection
|
||||||
|
if (e?.code === 'ArrowUp') {
|
||||||
|
// move selection up
|
||||||
|
const curSuggestionIdx = typeof hoveredSuggestion === 'number' ? hoveredSuggestion : suggestions?.length;
|
||||||
|
const newSuggestionIdx = curSuggestionIdx - 1;
|
||||||
|
if (newSuggestionIdx >= 0) {
|
||||||
|
setHoveredSuggestion(newSuggestionIdx);
|
||||||
|
} else {
|
||||||
|
setHoveredSuggestion(suggestions?.length - 1);
|
||||||
|
}
|
||||||
|
} else if (e?.code === 'ArrowDown') {
|
||||||
|
// move selection down
|
||||||
|
const curSuggestionIdx = typeof hoveredSuggestion === 'number' ? hoveredSuggestion : -1;
|
||||||
|
const newSuggestionIdx = curSuggestionIdx + 1;
|
||||||
|
if (newSuggestionIdx < suggestions?.length) {
|
||||||
|
setHoveredSuggestion(newSuggestionIdx);
|
||||||
|
} else {
|
||||||
|
setHoveredSuggestion(0);
|
||||||
|
}
|
||||||
|
} else if (e?.code === 'Enter') {
|
||||||
|
// if not shift enter then execute command
|
||||||
|
if (!e?.shiftKey) {
|
||||||
|
executeCommand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[closeCommander, toggleCommander, hoveredSuggestion, suggestions, thingtime, thingtimeRef, commanderActive, executeCommand]
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
window.addEventListener('keydown', allCommanderKeyListener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', allCommanderKeyListener);
|
||||||
|
};
|
||||||
|
}, [allCommanderKeyListener]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (typeof hoveredSuggestion === 'number') {
|
||||||
|
setVirtualValue(suggestions?.[hoveredSuggestion]);
|
||||||
|
} else {
|
||||||
|
setVirtualValue(inputValue);
|
||||||
|
}
|
||||||
|
}, [hoveredSuggestion, inputValue, suggestions]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setVirtualValue(inputValue);
|
||||||
|
}, [inputValue]);
|
||||||
|
|
||||||
|
const onMagicInput = React.useCallback((args) => {
|
||||||
|
// props?.onValueChange?.(args)
|
||||||
|
|
||||||
|
setInputValue(args?.value);
|
||||||
|
setHoveredSuggestion(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const InputPartWrapper = React.useCallback(
|
||||||
|
(props: any) => {
|
||||||
|
return <Box paddingX={commanderActive ? 1 : 0}>{props?.children}</Box>;
|
||||||
|
},
|
||||||
|
[commanderActive]
|
||||||
|
);
|
||||||
|
|
||||||
|
const InputPart = React.useMemo(() => {
|
||||||
|
if (props?.simple) {
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
// display='none'
|
||||||
|
// opacity={0}
|
||||||
|
ref={inputRef}
|
||||||
|
sx={{
|
||||||
|
'&::placeholder': {
|
||||||
|
color: 'greys.dark'
|
||||||
|
// color: "white",
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
background="grey"
|
||||||
|
border="none"
|
||||||
|
borderRadius="5px"
|
||||||
|
outline="none"
|
||||||
|
onChange={onInputChange}
|
||||||
|
onFocus={openCommander}
|
||||||
|
placeholder="Imagine.."
|
||||||
|
value={inputValue}
|
||||||
|
></Input>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MagicInput
|
||||||
|
placeholder={props?.placeholder || 'Imagine..'}
|
||||||
|
onValueChange={onMagicInput}
|
||||||
|
onFocus={openCommander}
|
||||||
|
chakras={{
|
||||||
|
marginX: commanderActive && props?.rainbow ? 4 : 0
|
||||||
|
}}
|
||||||
|
transition="all 0.5s ease-in-out"
|
||||||
|
></MagicInput>
|
||||||
|
);
|
||||||
|
}, [inputRef, onInputChange, commanderActive, props?.rainbow, props?.placeholder, openCommander, onMagicInput, props?.simple, inputValue]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClickAwayListener onClickAway={closeCommander}>
|
||||||
|
<Flex
|
||||||
|
// position="absolute"
|
||||||
|
// top={0}
|
||||||
|
// right={0}
|
||||||
|
// left={0}
|
||||||
|
// zIndex={99999}
|
||||||
|
// position='fixed'
|
||||||
|
// top='100px'
|
||||||
|
className={'commander-uuid-' + commanderId}
|
||||||
|
// display={["flex", commanderActive ? "flex" : "none"]}
|
||||||
|
justifyContent="flex-start"
|
||||||
|
width="100%"
|
||||||
|
maxWidth="100%"
|
||||||
|
// height="100%"
|
||||||
|
// paddingX={1}
|
||||||
|
>
|
||||||
|
<Center position="relative" flexDirection="column" width={['100%', '400px']} maxWidth={[mobileVW, '100%']} height="100%">
|
||||||
|
{props?.rainbow && (
|
||||||
|
<Rainbow
|
||||||
|
filter="blur(15px)"
|
||||||
|
opacity={commanderActive ? 0.25 : 0}
|
||||||
|
repeats={rainbowRepeats}
|
||||||
|
thickness={10}
|
||||||
|
opacityTransition="all 1000ms ease-out"
|
||||||
|
overflow="visible"
|
||||||
|
>
|
||||||
|
<Center
|
||||||
|
position="relative"
|
||||||
|
overflow="hidden"
|
||||||
|
width={['100%', '400px']}
|
||||||
|
maxWidth={[mobileVW, '100%']}
|
||||||
|
height="100%"
|
||||||
|
padding="1px"
|
||||||
|
borderRadius="6px"
|
||||||
|
pointerEvents="all"
|
||||||
|
outline="none"
|
||||||
|
>
|
||||||
|
<Rainbow
|
||||||
|
opacity={commanderActive ? 0.6 : 0}
|
||||||
|
position="absolute"
|
||||||
|
repeats={rainbowRepeats}
|
||||||
|
opacityTransition="all 2500ms ease-out"
|
||||||
|
thickness={1}
|
||||||
|
>
|
||||||
|
{/* <InputPartWrapper>{InputPart}</InputPartWrapper> */}
|
||||||
|
{InputPart}
|
||||||
|
</Rainbow>
|
||||||
|
</Center>
|
||||||
|
</Rainbow>
|
||||||
|
)}
|
||||||
|
{!props?.rainbow && InputPart}
|
||||||
|
</Center>
|
||||||
|
<Flex
|
||||||
|
// position="absolute"
|
||||||
|
// top="100%"
|
||||||
|
// right={0}
|
||||||
|
// left={0}
|
||||||
|
alignItems="flex-start"
|
||||||
|
flexDirection="column"
|
||||||
|
maxWidth="100%"
|
||||||
|
height="auto"
|
||||||
|
// marginTop={2}
|
||||||
|
borderRadius="12px"
|
||||||
|
// marginX={1}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
alignItems={['flex-start', 'center']}
|
||||||
|
flexDirection="column"
|
||||||
|
overflowY="scroll"
|
||||||
|
width="auto"
|
||||||
|
maxWidth="100%"
|
||||||
|
maxHeight="90vh"
|
||||||
|
borderRadius="12px"
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
flexShrink={0}
|
||||||
|
display={showSuggestions ? 'flex' : 'none'}
|
||||||
|
overflowY="scroll"
|
||||||
|
width={['100%', '400px']}
|
||||||
|
maxWidth="100%"
|
||||||
|
maxHeight="300px"
|
||||||
|
marginBottom={3}
|
||||||
|
background="grey"
|
||||||
|
borderRadius="12px"
|
||||||
|
pointerEvents="all"
|
||||||
|
id="commander-suggestions"
|
||||||
|
onMouseLeave={() => setHoveredSuggestion(null)}
|
||||||
|
paddingY={3}
|
||||||
|
>
|
||||||
|
{suggestions?.map((suggestion, i) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={i}
|
||||||
|
background={hoveredSuggestion === i ? 'greys.lightt' : null}
|
||||||
|
_hover={{
|
||||||
|
background: 'greys.lightt'
|
||||||
|
}}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => selectSuggestion(i)}
|
||||||
|
onMouseEnter={() => setHoveredSuggestion(i)}
|
||||||
|
paddingX={4}
|
||||||
|
>
|
||||||
|
{suggestion}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
{showContext && props?.context && (
|
||||||
|
<Flex display={showContext ? 'flex' : 'none'} maxWidth="100%" background="grey" borderRadius="12px" pointerEvents="all" paddingY={3}>
|
||||||
|
<Thingtime width="600px" path={contextPath} thing={contextValue}></Thingtime>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</ClickAwayListener>
|
||||||
|
);
|
||||||
|
};
|
537
next/src/components/Commander/CommanderV2.tsx
Normal file
537
next/src/components/Commander/CommanderV2.tsx
Normal file
@ -0,0 +1,537 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ClickAwayListener from 'react-click-away-listener';
|
||||||
|
import { Center, Flex, Input } from '@chakra-ui/react';
|
||||||
|
import { useLocation } from '@remix-run/react';
|
||||||
|
import Fuse from 'fuse.js';
|
||||||
|
|
||||||
|
import { Rainbow } from '../Rainbow/Rainbow';
|
||||||
|
import { Thingtime } from '../Thingtime/Thingtime';
|
||||||
|
import { useThingtime } from '../Thingtime/useThingtime';
|
||||||
|
|
||||||
|
import { sanitise } from '@/functions/sanitise';
|
||||||
|
import { usePath } from '@/remix_providers/hooks/usePath';
|
||||||
|
import { getParentPath } from '@/smarts';
|
||||||
|
|
||||||
|
export const CommanderV1 = (props: any) => {
|
||||||
|
const { thingtime, setThingtime, getThingtime, thingtimeRef, paths } = useThingtime();
|
||||||
|
|
||||||
|
const { mode, changePath } = usePath();
|
||||||
|
|
||||||
|
const commanderId = React.useMemo(() => {
|
||||||
|
return props?.id || 'global';
|
||||||
|
}, [props?.id]);
|
||||||
|
|
||||||
|
const inputRef = React.useRef();
|
||||||
|
|
||||||
|
const global = props?.global;
|
||||||
|
|
||||||
|
const commanderSettings = React.useMemo(() => {
|
||||||
|
return thingtime?.settings?.commander?.[commanderId] || {};
|
||||||
|
}, [thingtime?.settings?.commander, thingtime?.settings?.commander?.[commanderId], commanderId]);
|
||||||
|
|
||||||
|
const [inputValue, setInputValue] = React.useState('');
|
||||||
|
const [virtualValue, setVirtualValue] = React.useState('');
|
||||||
|
const [hoveredSuggestion, setHoveredSuggestion] = React.useState();
|
||||||
|
const [active, setActive] = React.useState(false);
|
||||||
|
const [contextPath, setContextPath] = React.useState();
|
||||||
|
|
||||||
|
const commanderMode = React.useMemo(() => {
|
||||||
|
return props?.mode || 'value';
|
||||||
|
}, [props?.mode]);
|
||||||
|
|
||||||
|
const [showContext, setShowContextState] = React.useState(false);
|
||||||
|
|
||||||
|
const mobileVW = React.useMemo(() => {
|
||||||
|
return 'calc(100vw - 108px)';
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const rainbowRepeats = 2;
|
||||||
|
|
||||||
|
const setShowContext = React.useCallback(
|
||||||
|
(value, from?: string) => {
|
||||||
|
setShowContextState(value);
|
||||||
|
},
|
||||||
|
[setShowContextState]
|
||||||
|
);
|
||||||
|
// const [suggestions, setSuggestions] = React.useState([])
|
||||||
|
|
||||||
|
const contextValue = React.useMemo(() => {
|
||||||
|
// TODO: Figure out why this is running on every click
|
||||||
|
const ret = getThingtime(contextPath);
|
||||||
|
return ret;
|
||||||
|
}, [contextPath, getThingtime]);
|
||||||
|
|
||||||
|
const commanderActive = React.useMemo(() => {
|
||||||
|
return thingtime?.settings?.commander?.[commanderId]?.commanderActive;
|
||||||
|
}, [commanderSettings, commanderId]);
|
||||||
|
|
||||||
|
// commanderActive useEffect
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (commanderActive) {
|
||||||
|
inputRef?.current?.focus?.();
|
||||||
|
} else {
|
||||||
|
document.activeElement.blur();
|
||||||
|
|
||||||
|
if (thingtimeRef?.current?.settings?.commander?.[commanderId]?.clearCommanderOnToggle) {
|
||||||
|
setInputValue('');
|
||||||
|
setHoveredSuggestion(null);
|
||||||
|
}
|
||||||
|
if (thingtimeRef?.current?.settings?.commander?.[commanderId]?.commander?.[commanderId]?.clearCommanderContextOnToggle) {
|
||||||
|
setShowContext(false, 'commanderActive useEffect');
|
||||||
|
}
|
||||||
|
if (contextPath !== undefined && !inputValue) {
|
||||||
|
setContextPath(undefined);
|
||||||
|
}
|
||||||
|
if (showContext !== false) {
|
||||||
|
setShowContext(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [commanderActive, thingtimeRef, setShowContext, commanderId, inputValue, contextPath, showContext]);
|
||||||
|
|
||||||
|
const onInputChange = React.useCallback((e) => {
|
||||||
|
setInputValue(e.target.value);
|
||||||
|
setHoveredSuggestion(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validSetters = React.useMemo(() => {
|
||||||
|
return ['=', ' is ', ' IS ', ' Is ', ' iS '];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const command = React.useMemo(() => {
|
||||||
|
// const sanitizedCommand = sanitise(value)
|
||||||
|
// const sanitizedCommand = inputValue
|
||||||
|
const sanitizedCommand = virtualValue;
|
||||||
|
|
||||||
|
const validSetter = validSetters?.find((setter) => {
|
||||||
|
if (sanitizedCommand?.includes(setter)) {
|
||||||
|
return setter;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof validSetter === 'string') {
|
||||||
|
const indexOfSplitter = sanitizedCommand?.indexOf(validSetter);
|
||||||
|
const [pathRaw, valRaw] = [sanitizedCommand?.slice(0, indexOfSplitter), sanitizedCommand?.slice(indexOfSplitter + validSetter?.length)];
|
||||||
|
|
||||||
|
return [pathRaw?.trim(), valRaw?.trim()];
|
||||||
|
}
|
||||||
|
return [sanitizedCommand];
|
||||||
|
}, [
|
||||||
|
// inputValue,
|
||||||
|
virtualValue,
|
||||||
|
validSetters
|
||||||
|
]);
|
||||||
|
|
||||||
|
const commandPath = React.useMemo(() => {
|
||||||
|
return command?.[0];
|
||||||
|
// return sanitise(command?.[0])
|
||||||
|
}, [command]);
|
||||||
|
|
||||||
|
const commandValue = React.useMemo(() => {
|
||||||
|
return command?.[1];
|
||||||
|
}, [command]);
|
||||||
|
|
||||||
|
const validQuotations = React.useMemo(() => {
|
||||||
|
return ['"', "'"];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const escapedCommandValue = React.useMemo(() => {
|
||||||
|
// replace quotations with escaped quoations except for first and last quotation
|
||||||
|
const startingQuotation = commandValue?.[0];
|
||||||
|
const endingQuotation = commandValue?.[commandValue?.length - 1];
|
||||||
|
const isQuoted = validQuotations?.includes(startingQuotation) && validQuotations?.includes(endingQuotation);
|
||||||
|
const restOfCommandValue = isQuoted ? commandValue?.slice(1, commandValue?.length - 1) : commandValue;
|
||||||
|
const escaped = restOfCommandValue?.replace(/"/g, '\\"')?.replace(/'/g, "\\'");
|
||||||
|
const ret = `"${escaped}"`;
|
||||||
|
return ret;
|
||||||
|
}, [commandValue, validQuotations]);
|
||||||
|
|
||||||
|
const commandIsAction = React.useMemo(() => {
|
||||||
|
return commandPath && commandValue;
|
||||||
|
}, [commandPath, commandValue]);
|
||||||
|
|
||||||
|
const suggestions = React.useMemo(() => {
|
||||||
|
try {
|
||||||
|
const fuse = new Fuse(paths);
|
||||||
|
|
||||||
|
const results = fuse.search(inputValue);
|
||||||
|
|
||||||
|
const mappedResults = results?.map((result) => {
|
||||||
|
return result?.item;
|
||||||
|
});
|
||||||
|
|
||||||
|
return mappedResults;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('fuse error', err);
|
||||||
|
}
|
||||||
|
}, [inputValue, paths]);
|
||||||
|
|
||||||
|
const showSuggestions = React.useMemo(() => {
|
||||||
|
return inputValue?.length && suggestions?.length && commanderActive && thingtime?.settings?.commander?.[commanderId]?.hideSuggestionsOnToggle;
|
||||||
|
}, [inputValue, suggestions, commanderActive, commanderId, thingtime?.settings?.commander, commanderSettings]);
|
||||||
|
|
||||||
|
const selectSuggestion = React.useCallback(
|
||||||
|
(suggestionIdx) => {
|
||||||
|
const suggestion = suggestions?.[suggestionIdx];
|
||||||
|
|
||||||
|
setInputValue(suggestion);
|
||||||
|
setHoveredSuggestion(null);
|
||||||
|
setContextPath(suggestion);
|
||||||
|
setShowContext(true, 'Select suggestion');
|
||||||
|
},
|
||||||
|
[setInputValue, setContextPath, setShowContext, suggestions]
|
||||||
|
);
|
||||||
|
|
||||||
|
const commandContainsPath = React.useMemo(() => {
|
||||||
|
const commandIncludesSuggestion = suggestions?.find((suggestion) => {
|
||||||
|
return commandPath?.includes(suggestion);
|
||||||
|
});
|
||||||
|
// return false
|
||||||
|
return commandIncludesSuggestion;
|
||||||
|
}, [commandPath, suggestions]);
|
||||||
|
|
||||||
|
const openCommander = React.useCallback(() => {
|
||||||
|
setThingtime(`settings.commander.${commanderId}.commanderActive`, true);
|
||||||
|
}, [setThingtime, commanderId]);
|
||||||
|
|
||||||
|
const closeCommander = React.useCallback(
|
||||||
|
(e?: any) => {
|
||||||
|
if (!e?.defaultPrevented) {
|
||||||
|
if (thingtime?.settings?.commander?.[commanderId]?.commanderActive) {
|
||||||
|
setThingtime(`settings.commander.${commanderId}.commanderActive`, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setThingtime, commanderId, commanderSettings, thingtime?.settings?.commander]
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleCommander = React.useCallback(() => {
|
||||||
|
if (thingtime?.settings?.commander?.[commanderId]?.commanderActive) {
|
||||||
|
closeCommander();
|
||||||
|
} else {
|
||||||
|
openCommander();
|
||||||
|
}
|
||||||
|
}, [thingtime?.settings?.commander, commanderSettings, commanderId, closeCommander, openCommander]);
|
||||||
|
|
||||||
|
const executeCommand = React.useCallback(() => {
|
||||||
|
// if selection is active then select it
|
||||||
|
const curSuggestionIdx = hoveredSuggestion;
|
||||||
|
if (curSuggestionIdx !== null) {
|
||||||
|
selectSuggestion(curSuggestionIdx);
|
||||||
|
}
|
||||||
|
if (commanderActive) {
|
||||||
|
try {
|
||||||
|
if (commandIsAction) {
|
||||||
|
// nothing
|
||||||
|
const prevVal = getThingtime(commandPath);
|
||||||
|
const parentPath = getParentPath(commandPath) || 'thingtime';
|
||||||
|
try {
|
||||||
|
// first try to execute literal javscript
|
||||||
|
const fn = `() => { return ${commandValue} }`;
|
||||||
|
const tt = thingtime;
|
||||||
|
const evalFn = eval(fn);
|
||||||
|
const realVal = evalFn();
|
||||||
|
setThingtime(commandPath, realVal);
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Caught error after trying to execute literal javascript', err);
|
||||||
|
|
||||||
|
// likely literaly javascript wasn't valid
|
||||||
|
try {
|
||||||
|
const fn = `() => { return ${escapedCommandValue} }`;
|
||||||
|
const tt = thingtime;
|
||||||
|
const evalFn = eval(fn);
|
||||||
|
const realVal = evalFn();
|
||||||
|
const prevVal = getThingtime(commandPath);
|
||||||
|
const parentPath = getParentPath(commandPath);
|
||||||
|
setThingtime(commandPath, realVal);
|
||||||
|
} catch {
|
||||||
|
// something very bad went wrong
|
||||||
|
console.log('Caught error after trying to execute escaped literal javascript', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if (!prevVal) {
|
||||||
|
setContextPath(commandPath);
|
||||||
|
setShowContext(true, 'commandIsAction check');
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
// if (commandContainsPath)
|
||||||
|
else {
|
||||||
|
// const prevValue = getThingtime(commandPath)
|
||||||
|
|
||||||
|
// const newValue = setThingtime(commandPath, prevValue)
|
||||||
|
|
||||||
|
console.log('Setting context path', commandPath);
|
||||||
|
// setContextPath(commandPath)
|
||||||
|
|
||||||
|
changePath({
|
||||||
|
path: commandPath
|
||||||
|
});
|
||||||
|
|
||||||
|
// setShowContext(true, "commandContainsPath check")
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Caught error on commander onEnter', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
hoveredSuggestion,
|
||||||
|
selectSuggestion,
|
||||||
|
mode,
|
||||||
|
changePath,
|
||||||
|
commanderActive,
|
||||||
|
commandIsAction,
|
||||||
|
commandPath,
|
||||||
|
thingtime,
|
||||||
|
commandValue,
|
||||||
|
escapedCommandValue,
|
||||||
|
getThingtime,
|
||||||
|
setThingtime,
|
||||||
|
setContextPath,
|
||||||
|
setShowContext
|
||||||
|
]);
|
||||||
|
|
||||||
|
const allCommanderKeyListener = React.useCallback(
|
||||||
|
(e: any) => {
|
||||||
|
console.log('commander key listener e?.code', e?.code);
|
||||||
|
thingtimeRef.current = thingtime;
|
||||||
|
if (e?.metaKey && e?.code === 'KeyP') {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleCommander();
|
||||||
|
}
|
||||||
|
// if key escape close all modals
|
||||||
|
else if (e?.code === 'Escape') {
|
||||||
|
closeCommander();
|
||||||
|
}
|
||||||
|
|
||||||
|
// only run these if commander active
|
||||||
|
|
||||||
|
if (commanderActive) {
|
||||||
|
// if arrow keys then move selection
|
||||||
|
if (e?.code === 'ArrowUp') {
|
||||||
|
// move selection up
|
||||||
|
const curSuggestionIdx = typeof hoveredSuggestion === 'number' ? hoveredSuggestion : suggestions?.length;
|
||||||
|
const newSuggestionIdx = curSuggestionIdx - 1;
|
||||||
|
if (newSuggestionIdx >= 0) {
|
||||||
|
setHoveredSuggestion(newSuggestionIdx);
|
||||||
|
} else {
|
||||||
|
setHoveredSuggestion(suggestions?.length - 1);
|
||||||
|
}
|
||||||
|
} else if (e?.code === 'ArrowDown') {
|
||||||
|
// move selection down
|
||||||
|
const curSuggestionIdx = typeof hoveredSuggestion === 'number' ? hoveredSuggestion : -1;
|
||||||
|
const newSuggestionIdx = curSuggestionIdx + 1;
|
||||||
|
if (newSuggestionIdx < suggestions?.length) {
|
||||||
|
setHoveredSuggestion(newSuggestionIdx);
|
||||||
|
} else {
|
||||||
|
setHoveredSuggestion(0);
|
||||||
|
}
|
||||||
|
} else if (e?.code === 'Enter') {
|
||||||
|
executeCommand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[closeCommander, toggleCommander, hoveredSuggestion, suggestions, thingtime, thingtimeRef, commanderActive, executeCommand]
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
window.addEventListener('keydown', allCommanderKeyListener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', allCommanderKeyListener);
|
||||||
|
};
|
||||||
|
}, [allCommanderKeyListener]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (typeof hoveredSuggestion === 'number') {
|
||||||
|
setVirtualValue(suggestions?.[hoveredSuggestion]);
|
||||||
|
} else {
|
||||||
|
setVirtualValue(inputValue);
|
||||||
|
}
|
||||||
|
}, [hoveredSuggestion, inputValue, suggestions]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setVirtualValue(inputValue);
|
||||||
|
}, [inputValue]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClickAwayListener onClickAway={closeCommander}>
|
||||||
|
<Flex
|
||||||
|
position="absolute"
|
||||||
|
zIndex={9999}
|
||||||
|
top={0}
|
||||||
|
right={0}
|
||||||
|
// position='fixed'
|
||||||
|
// top='100px'
|
||||||
|
left={0}
|
||||||
|
justifyContent={['flex-start', 'center']}
|
||||||
|
// display={["flex", commanderActive ? "flex" : "none"]}
|
||||||
|
maxWidth="100%"
|
||||||
|
height={12}
|
||||||
|
// height="100%"
|
||||||
|
pointerEvents="none"
|
||||||
|
id="commander"
|
||||||
|
paddingX={1}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
position="absolute"
|
||||||
|
zIndex={9999}
|
||||||
|
top="100%"
|
||||||
|
right={0}
|
||||||
|
left={0}
|
||||||
|
alignItems={['flex-start', 'center']}
|
||||||
|
flexDirection="column"
|
||||||
|
maxWidth="100%"
|
||||||
|
height="auto"
|
||||||
|
marginTop={2}
|
||||||
|
borderRadius="12px"
|
||||||
|
marginX={1}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
alignItems={['flex-start', 'center']}
|
||||||
|
flexDirection="column"
|
||||||
|
overflowY="scroll"
|
||||||
|
width="auto"
|
||||||
|
maxWidth="100%"
|
||||||
|
maxHeight="90vh"
|
||||||
|
borderRadius="12px"
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
flexShrink={0}
|
||||||
|
display={showSuggestions ? 'flex' : 'none'}
|
||||||
|
overflowY="scroll"
|
||||||
|
width={['100%', '400px']}
|
||||||
|
maxWidth="100%"
|
||||||
|
maxHeight="300px"
|
||||||
|
marginBottom={3}
|
||||||
|
background="grey"
|
||||||
|
borderRadius="12px"
|
||||||
|
pointerEvents="all"
|
||||||
|
id="commander-suggestions"
|
||||||
|
onMouseLeave={() => setHoveredSuggestion(null)}
|
||||||
|
paddingY={3}
|
||||||
|
>
|
||||||
|
{suggestions?.map((suggestion, i) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={i}
|
||||||
|
background={hoveredSuggestion === i ? 'greys.lightt' : null}
|
||||||
|
_hover={{
|
||||||
|
background: 'greys.lightt'
|
||||||
|
}}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => selectSuggestion(i)}
|
||||||
|
onMouseEnter={() => setHoveredSuggestion(i)}
|
||||||
|
paddingX={4}
|
||||||
|
>
|
||||||
|
{suggestion}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
{showContext && (
|
||||||
|
<Flex display={showContext ? 'flex' : 'none'} maxWidth="100%" background="grey" borderRadius="12px" pointerEvents="all" paddingY={3}>
|
||||||
|
<Thingtime width="600px" path={contextPath} thing={contextValue}></Thingtime>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Center position="relative" width={['100%', '400px']} maxWidth={[mobileVW, '100%']} height="100%">
|
||||||
|
{/* TODO: Fix duplicate code because of rainbow mode disabling hack */}
|
||||||
|
{props?.rainbow && (
|
||||||
|
<Rainbow
|
||||||
|
filter="blur(15px)"
|
||||||
|
opacity={commanderActive ? 0.25 : 0}
|
||||||
|
repeats={rainbowRepeats}
|
||||||
|
thickness={8}
|
||||||
|
opacityTransition="all 1000ms ease-out"
|
||||||
|
overflow="visible"
|
||||||
|
>
|
||||||
|
<Center
|
||||||
|
position="relative"
|
||||||
|
zIndex={9999}
|
||||||
|
overflow="hidden"
|
||||||
|
width={['100%', '400px']}
|
||||||
|
maxWidth={[mobileVW, '100%']}
|
||||||
|
height="100%"
|
||||||
|
padding="1px"
|
||||||
|
borderRadius="6px"
|
||||||
|
pointerEvents="all"
|
||||||
|
outline="none"
|
||||||
|
>
|
||||||
|
<Rainbow
|
||||||
|
opacity={commanderActive ? 0.6 : 0}
|
||||||
|
position="absolute"
|
||||||
|
repeats={rainbowRepeats}
|
||||||
|
opacityTransition="all 2500ms ease-out"
|
||||||
|
thickness={10}
|
||||||
|
></Rainbow>
|
||||||
|
<Input
|
||||||
|
// display='none'
|
||||||
|
// opacity={0}
|
||||||
|
ref={inputRef}
|
||||||
|
sx={{
|
||||||
|
'&::placeholder': {
|
||||||
|
color: 'greys.dark'
|
||||||
|
// color: "white",
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
zIndex={9999}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
background="grey"
|
||||||
|
border="none"
|
||||||
|
borderRadius="5px"
|
||||||
|
outline="none"
|
||||||
|
onChange={onInputChange}
|
||||||
|
onFocus={openCommander}
|
||||||
|
placeholder="Imagine.."
|
||||||
|
value={inputValue}
|
||||||
|
></Input>
|
||||||
|
</Center>
|
||||||
|
</Rainbow>
|
||||||
|
)}
|
||||||
|
{!props?.rainbow && (
|
||||||
|
<Center
|
||||||
|
position="relative"
|
||||||
|
zIndex={9999}
|
||||||
|
overflow="hidden"
|
||||||
|
width={['100%', '400px']}
|
||||||
|
maxWidth={[mobileVW, '100%']}
|
||||||
|
height="100%"
|
||||||
|
padding="1px"
|
||||||
|
borderRadius="6px"
|
||||||
|
pointerEvents="all"
|
||||||
|
outline="none"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
// display='none'
|
||||||
|
// opacity={0}
|
||||||
|
ref={inputRef}
|
||||||
|
sx={{
|
||||||
|
'&::placeholder': {
|
||||||
|
color: 'greys.dark'
|
||||||
|
// color: "white",
|
||||||
|
// textShadow: "0 0 5px black",
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
zIndex={9999}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
background="grey"
|
||||||
|
border="none"
|
||||||
|
borderRadius="5px"
|
||||||
|
outline="none"
|
||||||
|
onChange={onInputChange}
|
||||||
|
onFocus={openCommander}
|
||||||
|
placeholder="Imagine.."
|
||||||
|
value={inputValue}
|
||||||
|
></Input>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</ClickAwayListener>
|
||||||
|
);
|
||||||
|
};
|
220
next/src/components/Icon/Icon.tsx
Normal file
220
next/src/components/Icon/Icon.tsx
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Center } from '@chakra-ui/react';
|
||||||
|
import emojis from 'emojis-list';
|
||||||
|
|
||||||
|
export const Icon = (props: any) => {
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
30
next/src/components/Layout/Main.tsx
Normal file
30
next/src/components/Layout/Main.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { Footer } from '../Nav/Footer';
|
||||||
|
import { Nav } from '../Nav/Nav';
|
||||||
|
import { ProfileDrawer } from '../Nav/ProfileDrawer';
|
||||||
|
|
||||||
|
export const Main = (props: any) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
'*': {
|
||||||
|
whiteSpace: 'pre-wrap'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
position="relative"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
flexDirection="column"
|
||||||
|
overflow="hidden"
|
||||||
|
maxWidth="100vw"
|
||||||
|
>
|
||||||
|
{/* <ProfileDrawer></ProfileDrawer> */}
|
||||||
|
<Nav />
|
||||||
|
<Box width="100%" minHeight="100vh">
|
||||||
|
{props.children}
|
||||||
|
</Box>
|
||||||
|
<Footer></Footer>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
136
next/src/components/Login/Login.tsx
Normal file
136
next/src/components/Login/Login.tsx
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Flex, Button, FormControl, Input, Spinner, Link } from '@chakra-ui/react';
|
||||||
|
import { useFetcher } from '@remix-run/react';
|
||||||
|
|
||||||
|
import { useLogin } from '@/api/v1/login/Login';
|
||||||
|
|
||||||
|
export const Login = (props: any) => {
|
||||||
|
const [username, setUsername] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const api = useFetcher();
|
||||||
|
|
||||||
|
const { login } = useLogin();
|
||||||
|
|
||||||
|
const handleLogin = (e) => {
|
||||||
|
e?.preventDefault();
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
login(username, password)
|
||||||
|
.then((response) => {
|
||||||
|
console.log('nik response 123', response);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('nik error 123', error);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('nik username', username);
|
||||||
|
console.log('nik password', password);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Flex alignItems="center" justifyContent="center" height="100vh" width="100%">
|
||||||
|
<Spinner
|
||||||
|
sx={{
|
||||||
|
'@keyframes moving-rainbow': {
|
||||||
|
'0%': { backgroundPosition: '0 0' },
|
||||||
|
'100%': { backgroundPosition: 'calc(100px + 400%) 0' }
|
||||||
|
},
|
||||||
|
'@keyframes rotate': {
|
||||||
|
'0%': { transform: 'rotate(0deg)' },
|
||||||
|
'100%': { transform: 'rotate(360deg)' }
|
||||||
|
},
|
||||||
|
animation: 'rotate 2s linear infinite, moving-rainbow 40s infinite linear'
|
||||||
|
}}
|
||||||
|
bgGradient="linear-gradient(to right, #47b5e6, #a555e8, #f34a4a, #ffbc48, #58ca70, #47b5e6)"
|
||||||
|
// rainbow gradient border
|
||||||
|
backgroundSize="calc(100px + 400%)"
|
||||||
|
color="transparent"
|
||||||
|
size="xl"
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleLogin}>
|
||||||
|
<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) => setUsername(e?.target?.value)}
|
||||||
|
placeholder="Email 💌"
|
||||||
|
type="email"
|
||||||
|
value={username}
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
62
next/src/components/Nav/Footer.tsx
Normal file
62
next/src/components/Nav/Footer.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, Center, Flex, Text } from '@chakra-ui/react';
|
||||||
|
// import Link from 'next/link';
|
||||||
|
import Link from 'next/link';
|
||||||
|
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 Footer = (props: any) => {
|
||||||
|
const investmentEmail = 'invest@thingtime.com';
|
||||||
|
const contactEmail = 'connect@thingtime.com';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Center width="100%" paddingTop="900px" paddingBottom={[5, 12]} paddingX={4}>
|
||||||
|
<Flex width={['500px', '500px']} maxWidth="100%">
|
||||||
|
{false && (
|
||||||
|
<Flex flexDirection="column" rowGap={3}>
|
||||||
|
<Flex flexDirection="column">
|
||||||
|
<Flex flexDirection="row" fontSize="xs">
|
||||||
|
<Icon name="cash" size="12px" chakras={{ pr: 1 }}></Icon>
|
||||||
|
To invest, please contact:
|
||||||
|
{/* <Icon name="money bag" size="10px" chakras={{ pl: 1 }}></Icon> */}
|
||||||
|
</Flex>
|
||||||
|
<Link href={`mailto:${investmentEmail}`}>
|
||||||
|
<Flex flexDirection="row">
|
||||||
|
<Text color="green">{investmentEmail}</Text>
|
||||||
|
</Flex>
|
||||||
|
</Link>
|
||||||
|
</Flex>
|
||||||
|
<Flex flexDirection="column" fontSize="xs">
|
||||||
|
{/* copyright message */}© 2023 Thingtime
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
<Flex flexDirection="column" rowGap={3}>
|
||||||
|
<Flex flexDirection="column">
|
||||||
|
<Flex flexDirection="row" fontSize="xs">
|
||||||
|
<Icon name="mail" size="12px" chakras={{ pr: 1 }}></Icon>
|
||||||
|
Contact
|
||||||
|
{/* <Icon name="money bag" size="10px" chakras={{ pl: 1 }}></Icon> */}
|
||||||
|
</Flex>
|
||||||
|
<Link href={`mailto:${contactEmail}`}>
|
||||||
|
<Flex flexDirection="row">
|
||||||
|
<Text>{contactEmail}</Text>
|
||||||
|
</Flex>
|
||||||
|
</Link>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems="center" flexDirection="row" fontSize="xs">
|
||||||
|
<Text>{/* copyright message */}© 2023 Thingtime</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex flexDirection="row" marginRight="auto">
|
||||||
|
<Icon name="rainbow" size="8px"></Icon>
|
||||||
|
<Icon name="unicorn" size="8px"></Icon>
|
||||||
|
<Icon name="wizard" size="8px"></Icon>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
};
|
169
next/src/components/Nav/Nav.tsx
Normal file
169
next/src/components/Nav/Nav.tsx
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, Center, Flex } from '@chakra-ui/react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
import { Commander } from '../Commander/Commander';
|
||||||
|
import { CommanderV1 } from '../Commander/CommanderV2';
|
||||||
|
import { Icon } from '../Icon/Icon';
|
||||||
|
import { RainbowSkeleton } from '../Skeleton/RainbowSkeleton';
|
||||||
|
import { ProfileDrawer } from './ProfileDrawer';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
|
||||||
|
export const Nav = (props: any) => {
|
||||||
|
const [profileDrawerOpen, setProfileDrawerOpen] = React.useState(false);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const location = router;
|
||||||
|
const { pathname } = location;
|
||||||
|
|
||||||
|
const navigate = router.push;
|
||||||
|
|
||||||
|
const toggleProfileDrawer = React.useCallback(() => {
|
||||||
|
setProfileDrawerOpen(!profileDrawerOpen);
|
||||||
|
}, [profileDrawerOpen]);
|
||||||
|
|
||||||
|
const inEditorMode = React.useMemo(() => {
|
||||||
|
if (pathname.slice(0, 7) === '/editor') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
const inEditMode = React.useMemo(() => {
|
||||||
|
if (pathname.slice(0, 5) === '/edit') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
const editorToggleable = React.useMemo(() => {
|
||||||
|
if (pathname.slice(0, 7) === '/things') {
|
||||||
|
return true;
|
||||||
|
} else if (pathname.slice(0, 5) === '/edit') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[pathname, navigate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleEditor = React.useCallback(
|
||||||
|
(e: any) => {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[pathname, navigate]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box position="fixed" zIndex={9999} top={0} right={0} left={0} maxWidth="100vw">
|
||||||
|
<Flex
|
||||||
|
as="nav"
|
||||||
|
position="relative"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
flexDirection="row"
|
||||||
|
width="100%"
|
||||||
|
maxWidth="100%"
|
||||||
|
marginY={1}
|
||||||
|
paddingX="18px"
|
||||||
|
paddingY="14px"
|
||||||
|
// 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 transform="scaleX(-100%)" cursor="pointer">
|
||||||
|
<Link href="/">
|
||||||
|
<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">
|
||||||
|
{inEditMode && (
|
||||||
|
<Center
|
||||||
|
// transform="scaleX(-100%)"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={toggleEditor}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
chakras={{
|
||||||
|
opacity: inEditorMode ? 1 : 0.3
|
||||||
|
}}
|
||||||
|
size="12px"
|
||||||
|
name="👀"
|
||||||
|
></Icon>
|
||||||
|
{/* <Icon
|
||||||
|
size="12px"
|
||||||
|
name={inEditorMode ? "glowing star" : "star"}
|
||||||
|
></Icon> */}
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
{editorToggleable && (
|
||||||
|
<Center transform="scaleX(-100%)" cursor="pointer" onClick={toggleEdit}>
|
||||||
|
<Icon
|
||||||
|
chakras={{
|
||||||
|
opacity: inEditMode ? 1 : 0.3
|
||||||
|
}}
|
||||||
|
size="12px"
|
||||||
|
name="🎨"
|
||||||
|
></Icon>
|
||||||
|
{/* <Icon
|
||||||
|
size="12px"
|
||||||
|
name={inEditMode ? "glowing star" : "star"}
|
||||||
|
></Icon> */}
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
<Center transform={['', 'scaleX(-100%)']} cursor="pointer">
|
||||||
|
<Link href="/login">
|
||||||
|
<Icon size="12px" name="🌈"></Icon>
|
||||||
|
</Link>
|
||||||
|
</Center>
|
||||||
|
<Center display={['flex', 'none']} cursor="pointer">
|
||||||
|
<Link href="/">
|
||||||
|
<Icon size="12px" name="🦄"></Icon>
|
||||||
|
</Link>
|
||||||
|
</Center>
|
||||||
|
</Center>
|
||||||
|
{/* <RainbowSkeleton
|
||||||
|
marginLeft="auto"
|
||||||
|
width="25px"
|
||||||
|
height="25px"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={toggleProfileDrawer}
|
||||||
|
background="rgba(0,0,0,0.1)"
|
||||||
|
sx={{}}
|
||||||
|
borderRadius="999px"
|
||||||
|
></RainbowSkeleton> */}
|
||||||
|
{/* <RainbowSkeleton w='40px' ml='auto' mr={"4px"}></RainbowSkeleton>
|
||||||
|
<RainbowSkeleton></RainbowSkeleton> */}
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
{/* <ProfileDrawer isOpen={profileDrawerOpen}></ProfileDrawer> */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
22
next/src/components/Nav/ProfileDrawer.tsx
Normal file
22
next/src/components/Nav/ProfileDrawer.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Drawer, DrawerBody, DrawerContent, Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export const ProfileDrawer = (props: any) => {
|
||||||
|
const navItems = ['settings'];
|
||||||
|
|
||||||
|
const onClose = React.useCallback(() => {
|
||||||
|
// nothing
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer isOpen={props?.isOpen} onClose={onClose} placement="right" size="md">
|
||||||
|
<DrawerContent className="ProfileDrawer" maxWidth="90%" marginTop="35px">
|
||||||
|
<DrawerBody>
|
||||||
|
{navItems.map((item, idx) => {
|
||||||
|
return <Flex key={idx}>{item}</Flex>;
|
||||||
|
})}
|
||||||
|
</DrawerBody>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
63
next/src/components/Nav/ReactiveNav.tsx
Normal file
63
next/src/components/Nav/ReactiveNav.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
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: any) => {
|
||||||
|
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
65
next/src/components/Nav/ReactiveRightNav.tsx
Normal file
65
next/src/components/Nav/ReactiveRightNav.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Center, Text } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export const ReactiveRightNav = (props: any) => {
|
||||||
|
const setNav = React.useCallback((active) => {
|
||||||
|
setMr(active ? '0%' : '-100%');
|
||||||
|
setNavActive(active);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [entered, setEntered] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
// react to mouse move event
|
||||||
|
const listener = (event) => {
|
||||||
|
const windowWidth = window.innerWidth;
|
||||||
|
if (!entered) {
|
||||||
|
if (event?.clientX >= windowWidth - 60) {
|
||||||
|
setNav(true);
|
||||||
|
} else if (event?.clientX <= windowWidth - 100) {
|
||||||
|
setNav(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('mousemove', listener);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('mousemove', listener);
|
||||||
|
};
|
||||||
|
}, [setNav, entered]);
|
||||||
|
|
||||||
|
const [navActive, setNavActive] = React.useState(false);
|
||||||
|
|
||||||
|
// const defaultMr = '-100%'
|
||||||
|
const defaultMr = '0%';
|
||||||
|
|
||||||
|
const [mr, setMr] = React.useState(defaultMr);
|
||||||
|
|
||||||
|
const navItems = React.useMemo(() => {
|
||||||
|
return ['home', 'imagine', 'create', 'share'];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Center position="fixed" top={0} right={0} 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)}
|
||||||
|
paddingX={100}
|
||||||
|
paddingY={80}
|
||||||
|
>
|
||||||
|
{navItems.map((navItem, idx) => {
|
||||||
|
return (
|
||||||
|
<Text key={idx} as="h2" color="black" fontSize="30px" fontWeight="semibold" textTransform="capitalize" cursor="pointer" marginY="auto">
|
||||||
|
{navItem}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Center>
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
};
|
206
next/src/components/Rainbow/Rainbow.tsx
Normal file
206
next/src/components/Rainbow/Rainbow.tsx
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, Center } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { GradientPath } from '@/gp/GradientPath';
|
||||||
|
import { useProps } from '@/remix_providers/hooks/useProps';
|
||||||
|
import { useTrace } from '@/remix_providers/hooks/useTrace';
|
||||||
|
import { useUuid } from '@/remix_providers/hooks/useUuid';
|
||||||
|
|
||||||
|
export const Rainbow = (allProps: any): JSX.Element => {
|
||||||
|
// return allProps.children
|
||||||
|
|
||||||
|
const rainbow = ['#f34a4a', '#ffbc48', '#58ca70', '#47b5e6', '#a555e8'];
|
||||||
|
|
||||||
|
const props = useProps(allProps);
|
||||||
|
|
||||||
|
const uuid = useUuid();
|
||||||
|
|
||||||
|
props.expand = props?.expand || true;
|
||||||
|
|
||||||
|
const [hidden, setHidden] = React.useState(true);
|
||||||
|
const repeats = props?.repeats || 1;
|
||||||
|
const [filter, setFilter] = React.useState(props?.filter);
|
||||||
|
const opacity = props?.opacity !== undefined ? props?.opacity : 1;
|
||||||
|
const [colors, setColors] = React.useState(props?.colors || rainbow);
|
||||||
|
const [pathWidth, setPathWidth] = React.useState(props?.thickness || 1);
|
||||||
|
const [overflow, setOverflow] = React.useState(props?.overflow || 'hidden');
|
||||||
|
const [opacityTransition, setOpacityTransition] = React.useState(props?.opacityTransition || 'all 10000ms ease-out');
|
||||||
|
|
||||||
|
const parentRef = React.useRef(null);
|
||||||
|
|
||||||
|
const repeatedColours = React.useMemo(() => {
|
||||||
|
const ret = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < repeats; i++) {
|
||||||
|
ret.push(...colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.push(colors[0]);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}, [colors, repeats]);
|
||||||
|
|
||||||
|
// make SVG that takes makes path in the shape of a box
|
||||||
|
// which adjusts to the parent containers size
|
||||||
|
|
||||||
|
const [state, setState] = React.useState({
|
||||||
|
width: 100,
|
||||||
|
height: 100
|
||||||
|
});
|
||||||
|
|
||||||
|
const [strokeWidth, setStrokeWidth] = React.useState(1);
|
||||||
|
|
||||||
|
const [extraStroke, setExtraStroke] = React.useState(0);
|
||||||
|
|
||||||
|
const [width, setWidth] = React.useState(props?.width || '100%');
|
||||||
|
const [height, setHeight] = React.useState(props?.height || '100%');
|
||||||
|
|
||||||
|
const pathString = React.useMemo(() => {
|
||||||
|
const startPoint = 0 + strokeWidth / 2;
|
||||||
|
const endPoint = 100 - strokeWidth / 2;
|
||||||
|
|
||||||
|
return `M -${4} ${startPoint} H ${endPoint} V ${endPoint} H ${startPoint} V ${startPoint}`;
|
||||||
|
}, [strokeWidth]);
|
||||||
|
|
||||||
|
const svgRef = React.useRef(null);
|
||||||
|
|
||||||
|
const colourKeyframes = React.useMemo(() => {
|
||||||
|
const ret = {};
|
||||||
|
|
||||||
|
repeatedColours.map((colour, i) => {
|
||||||
|
const keyframe = `${(i / (repeatedColours.length - 1)) * 100}%`;
|
||||||
|
ret[keyframe] = {
|
||||||
|
fill: colour,
|
||||||
|
animationTimingFunction: 'ease-out',
|
||||||
|
stroke: colour
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}, [repeatedColours]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const updateChildSize = () => {
|
||||||
|
const { width, height } = parentRef?.current?.getBoundingClientRect() || {};
|
||||||
|
setState({ width, height });
|
||||||
|
};
|
||||||
|
|
||||||
|
updateChildSize();
|
||||||
|
|
||||||
|
new ResizeObserver(updateChildSize).observe(parentRef?.current);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const rect = React.useMemo(() => {
|
||||||
|
return (
|
||||||
|
<rect
|
||||||
|
// stroke="url(#linear-gradient)"
|
||||||
|
x={0}
|
||||||
|
y={0}
|
||||||
|
width={state?.width || 100}
|
||||||
|
height={state?.height || 100}
|
||||||
|
rx={10}
|
||||||
|
ry={10}
|
||||||
|
></rect>
|
||||||
|
);
|
||||||
|
}, [state?.width, state?.height]);
|
||||||
|
|
||||||
|
const svg = React.useMemo(() => {
|
||||||
|
return (
|
||||||
|
<Box width="100%" height="100%" id={'rainbow-svg-container-' + uuid}>
|
||||||
|
<svg overflow="visible" viewBox={`0 0 ${state?.width || 100} ${state?.height || 100}`} width="100%" height="100%" preserveAspectRatio="none">
|
||||||
|
{rect}
|
||||||
|
{/* <path
|
||||||
|
fill="none"
|
||||||
|
stroke="blue"
|
||||||
|
strokeAlignment="inner"
|
||||||
|
strokeWidth={`${strokeWidth + extraStroke}px`}
|
||||||
|
d={pathString}
|
||||||
|
></path> */}
|
||||||
|
</svg>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}, [state, rect, uuid]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (uuid) {
|
||||||
|
const svg = svgRef?.current?.querySelector('svg');
|
||||||
|
|
||||||
|
// path is rect or insert new rect if empty svg
|
||||||
|
const rectSource = parentRef?.current?.querySelector('.svg-source rect');
|
||||||
|
const path = svg?.querySelector?.('rect') || svg?.appendChild?.(rectSource?.cloneNode?.());
|
||||||
|
|
||||||
|
if (path) {
|
||||||
|
const gp = new GradientPath({
|
||||||
|
path,
|
||||||
|
segments: props?.segments || 1000,
|
||||||
|
samples: props?.samples || 1,
|
||||||
|
precision: props?.precision || 5
|
||||||
|
});
|
||||||
|
|
||||||
|
gp.render({
|
||||||
|
type: 'path',
|
||||||
|
width: pathWidth || 1,
|
||||||
|
animation: {
|
||||||
|
name: `rainbow-${uuid}`,
|
||||||
|
duration: props?.duration || 7
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setHidden(false);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// setHidden(true)
|
||||||
|
gp.remove();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [uuid, props?.duration, props?.segments, props?.samples, props?.precision, pathWidth, repeatedColours, parentRef, svgRef, rect]);
|
||||||
|
|
||||||
|
const render = true;
|
||||||
|
|
||||||
|
// useTrace("Rainbow", {
|
||||||
|
// props,
|
||||||
|
// })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Center
|
||||||
|
className="Rainbow_n__n"
|
||||||
|
ref={parentRef}
|
||||||
|
sx={{
|
||||||
|
[`@keyframes rainbow-${uuid}`]: {
|
||||||
|
...colourKeyframes
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
position={props?.position || 'relative'}
|
||||||
|
overflow={overflow}
|
||||||
|
width={props?.expand ? '100%' : ''}
|
||||||
|
height={props?.expand ? '100%' : ''}
|
||||||
|
>
|
||||||
|
<Center
|
||||||
|
className="main-svg-container"
|
||||||
|
position="absolute"
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
overflow="visible"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
opacity={hidden ? '0' : opacity}
|
||||||
|
// pointerEvents="none"
|
||||||
|
transition={opacityTransition}
|
||||||
|
>
|
||||||
|
{/* debug svg */}
|
||||||
|
<Box className="svg-source" flexShrink={0} display="none" overflow="visible" width={width} height={height}>
|
||||||
|
{svg}
|
||||||
|
</Box>
|
||||||
|
<Box ref={svgRef} flexShrink={0} overflow="visible" width={width} height={height} opacity={hidden ? 0 : !render ? '0' : 1} filter={filter}>
|
||||||
|
{svg}
|
||||||
|
</Box>
|
||||||
|
</Center>
|
||||||
|
{allProps?.children}
|
||||||
|
</Center>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
7
next/src/components/Safety/Safe.tsx
Normal file
7
next/src/components/Safety/Safe.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { safe } from '@/functions/safe';
|
||||||
|
|
||||||
|
export const Safe = (props: any) => {
|
||||||
|
// do not render more than the limit of things to prevent infinite loops
|
||||||
|
|
||||||
|
return safe(props);
|
||||||
|
};
|
40
next/src/components/Skeleton/RainbowSkeleton.tsx
Normal file
40
next/src/components/Skeleton/RainbowSkeleton.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export const RainbowSkeleton = (props: any) => {
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
13
next/src/components/Splash/Splash.tsx
Normal file
13
next/src/components/Splash/Splash.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { TextAnimation1 } from '@/components/textAnimation1';
|
||||||
|
|
||||||
|
export const Splash = (props: any) => {
|
||||||
|
return (
|
||||||
|
<Flex alignItems="center" justifyContent="center" width="100vw" maxWidth="100vw" minHeight="100vh" paddingY="100px">
|
||||||
|
<Flex alignItems="center" justifyContent="center" width="800px" textAlign="center">
|
||||||
|
<TextAnimation1 ce={props?.ce} texts={props?.texts}></TextAnimation1>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
286
next/src/components/Thingtime/SettingsMenu.tsx
Normal file
286
next/src/components/Thingtime/SettingsMenu.tsx
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import ClickAwayListener from 'react-click-away-listener';
|
||||||
|
import { Center, Flex, Text } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { Icon } from '../Icon/Icon';
|
||||||
|
import { useThingtime } from './useThingtime';
|
||||||
|
|
||||||
|
export const SettingsMenu = (props: any) => {
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
const hideRef = React.useRef(null);
|
||||||
|
const [opacity, setOpacity] = React.useState(props?.opacity === 0 ? 0 : 1);
|
||||||
|
const [pinStatus, setPinStatus] = React.useState(false);
|
||||||
|
|
||||||
|
const stateRef = React.useRef({
|
||||||
|
pinStatus
|
||||||
|
});
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
stateRef.current.pinStatus = pinStatus;
|
||||||
|
}, [pinStatus]);
|
||||||
|
|
||||||
|
const { thingtime, events } = useThingtime();
|
||||||
|
|
||||||
|
const opacityRef = React.useRef(null);
|
||||||
|
|
||||||
|
const waitTime = 1555;
|
||||||
|
|
||||||
|
const [uuid, setUuid] = React.useState(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setUuid(Math.random().toString(36).substring(7));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const subscription = events.subscribe((event) => {
|
||||||
|
if (event?.type === 'settings-menu-hide' && event?.uuid !== uuid) {
|
||||||
|
if (!stateRef?.current?.pinStatus || event?.force) {
|
||||||
|
setShow(false);
|
||||||
|
setOpacity(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
subscription?.unsubscribe?.();
|
||||||
|
};
|
||||||
|
}, [events, uuid]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
clearInterval(opacityRef?.current);
|
||||||
|
if (props?.opacity) {
|
||||||
|
setOpacity(props?.opacity);
|
||||||
|
} else {
|
||||||
|
opacityRef.current = setInterval(() => {
|
||||||
|
if (!stateRef?.current?.pinStatus) {
|
||||||
|
setOpacity(props?.opacity);
|
||||||
|
setShow(false);
|
||||||
|
}
|
||||||
|
}, waitTime);
|
||||||
|
}
|
||||||
|
}, [props?.opacity]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (show || props?.opacity) {
|
||||||
|
clearInterval(hideRef?.current);
|
||||||
|
events.next({
|
||||||
|
type: 'settings-menu-hide',
|
||||||
|
uuid
|
||||||
|
});
|
||||||
|
} else if (!show) {
|
||||||
|
setPinStatus(false);
|
||||||
|
}
|
||||||
|
}, [show, props?.opacity, events, uuid]);
|
||||||
|
|
||||||
|
const maybeHide = React.useCallback(() => {
|
||||||
|
clearInterval(hideRef?.current);
|
||||||
|
hideRef.current = setTimeout(() => {
|
||||||
|
if (!stateRef?.current?.pinStatus) {
|
||||||
|
setShow(false);
|
||||||
|
setOpacity(0);
|
||||||
|
}
|
||||||
|
}, waitTime);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const showMenu = React.useCallback(() => {
|
||||||
|
clearInterval(hideRef?.current);
|
||||||
|
setShow(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const hideMenu = React.useCallback(() => {
|
||||||
|
setShow(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const basePadding = React.useMemo(() => {
|
||||||
|
return 4;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const types = React.useMemo(() => {
|
||||||
|
const baseTypes = thingtime?.settings?.types?.javascript || {};
|
||||||
|
const baseTypeKeys = Object.keys(baseTypes);
|
||||||
|
|
||||||
|
const customTypes = thingtime?.settings?.types?.custom || {};
|
||||||
|
const customTypeKeysRaw = Object.keys(customTypes);
|
||||||
|
const customTypeKeys = customTypeKeysRaw?.filter((key) => {
|
||||||
|
return !baseTypeKeys?.includes?.(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
const types = [
|
||||||
|
...(baseTypeKeys?.map?.((key) => {
|
||||||
|
return {
|
||||||
|
...baseTypes?.[key],
|
||||||
|
key
|
||||||
|
};
|
||||||
|
}) || []),
|
||||||
|
...(customTypeKeys?.map?.((key) => {
|
||||||
|
return {
|
||||||
|
...customTypes?.[key],
|
||||||
|
key
|
||||||
|
};
|
||||||
|
}) || [])
|
||||||
|
];
|
||||||
|
|
||||||
|
return types;
|
||||||
|
}, [thingtime?.settings?.types?.javascript, thingtime?.settings?.types?.custom]);
|
||||||
|
|
||||||
|
const onType = React.useCallback(
|
||||||
|
(args) => {
|
||||||
|
props?.onType?.(args);
|
||||||
|
},
|
||||||
|
[props?.onType]
|
||||||
|
);
|
||||||
|
const onDelete = React.useCallback(
|
||||||
|
(type) => {
|
||||||
|
props?.onDelete?.();
|
||||||
|
},
|
||||||
|
[props?.onDelete]
|
||||||
|
);
|
||||||
|
|
||||||
|
const childIconSize = 10;
|
||||||
|
const iconSize = props?.iconSize || 7;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClickAwayListener onClickAway={hideMenu}>
|
||||||
|
<Center
|
||||||
|
position="relative"
|
||||||
|
// width="100%"
|
||||||
|
paddingRight={36}
|
||||||
|
opacity={opacity}
|
||||||
|
transition={props?.transition || 'all 0.2s ease-in-out'}
|
||||||
|
onMouseEnter={showMenu}
|
||||||
|
onMouseLeave={maybeHide}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
paddingLeft={1}
|
||||||
|
// opacity={showContextIcon ? 1 : 0}
|
||||||
|
cursor="pointer"
|
||||||
|
// onClick={deleteValue}
|
||||||
|
transition="all 0.2s ease-in-out"
|
||||||
|
>
|
||||||
|
<Icon name="wizard" size={iconSize}></Icon>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
position="absolute"
|
||||||
|
zIndex={999}
|
||||||
|
top="100%"
|
||||||
|
left={0}
|
||||||
|
flexDirection="column"
|
||||||
|
opacity={show ? 1 : 0}
|
||||||
|
pointerEvents={show ? 'all' : 'none'}
|
||||||
|
>
|
||||||
|
<Flex position="absolute" top={0} right={0} padding="5px" cursor="pointer" onClick={() => setPinStatus((prev) => !prev)}>
|
||||||
|
<Icon opacity={pinStatus ? 1 : 0.5} name={pinStatus ? 'pinned' : 'pin'} size="8px"></Icon>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
// rowGap={basePadding / 3}
|
||||||
|
background="greys.lightt"
|
||||||
|
borderRadius={4}
|
||||||
|
boxShadow={props?.boxShadow || '0px 2px 7px 0px rgba(0,0,0,0.2)'}
|
||||||
|
paddingY={basePadding}
|
||||||
|
>
|
||||||
|
{!props?.readonly && (
|
||||||
|
<Flex
|
||||||
|
alignItems="center"
|
||||||
|
flexDirection="row"
|
||||||
|
// paddingRight={basePadding}
|
||||||
|
paddingLeft={basePadding}
|
||||||
|
_hover={{
|
||||||
|
background: 'greys.light'
|
||||||
|
}}
|
||||||
|
cursor="pointer"
|
||||||
|
// paddingX={basePadding * 1}
|
||||||
|
paddingY={basePadding / 2}
|
||||||
|
>
|
||||||
|
<Icon marginBottom="-2px" name="cyclone" size={childIconSize}></Icon>
|
||||||
|
<Text marginTop="-2px" paddingLeft={2} fontSize="xs">
|
||||||
|
Types
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
// rowGap={basePadding}
|
||||||
|
overflowY="scroll"
|
||||||
|
maxHeight="300px"
|
||||||
|
background="greys.lightt"
|
||||||
|
cursor="pointer"
|
||||||
|
>
|
||||||
|
{!props?.readonly &&
|
||||||
|
types.map((type, idx) => {
|
||||||
|
const ret = (
|
||||||
|
<Flex
|
||||||
|
key={props?.uuid + props?.fullPath + '-type-menu-' + idx}
|
||||||
|
width="100%"
|
||||||
|
_hover={{
|
||||||
|
'&>div': {
|
||||||
|
background: 'greys.light'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => onType({ type })}
|
||||||
|
paddingY={1}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
alignItems="center"
|
||||||
|
flexDirection="row"
|
||||||
|
width="100%"
|
||||||
|
paddingRight={basePadding}
|
||||||
|
paddingLeft={basePadding * 2}
|
||||||
|
paddingY={basePadding / 2}
|
||||||
|
>
|
||||||
|
<Icon marginBottom="-2px" name={type?.icon || type?.key || type?.label || type} size={childIconSize}></Icon>
|
||||||
|
<Text marginTop="-2px" paddingLeft={2} fontSize="xs">
|
||||||
|
{type?.label || type?.key || type}
|
||||||
|
</Text>
|
||||||
|
{type?.wrap && (
|
||||||
|
<Flex
|
||||||
|
marginLeft="auto"
|
||||||
|
_hover={{
|
||||||
|
transform: 'scale(1.3)'
|
||||||
|
}}
|
||||||
|
transition="all 0.2s ease-out"
|
||||||
|
onClick={(e) => {
|
||||||
|
e?.preventDefault?.();
|
||||||
|
e?.stopPropagation?.();
|
||||||
|
// cancel bubble
|
||||||
|
e?.nativeEvent?.stopImmediatePropagation?.();
|
||||||
|
onType({
|
||||||
|
type,
|
||||||
|
wrap: true
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name="wrap" size={childIconSize}></Icon>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
return ret;
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
{!props?.readonly && props?.onDelete && (
|
||||||
|
<Flex
|
||||||
|
alignItems="center"
|
||||||
|
flexDirection="row"
|
||||||
|
_hover={{
|
||||||
|
background: 'greys.light'
|
||||||
|
}}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={onDelete}
|
||||||
|
paddingX={basePadding * 1}
|
||||||
|
paddingY={basePadding / 2}
|
||||||
|
>
|
||||||
|
<Icon marginBottom="-2px" name="bin" size={childIconSize}></Icon>
|
||||||
|
<Text marginTop="-2px" paddingLeft={2} fontSize="xs">
|
||||||
|
Recycle
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Center>
|
||||||
|
</ClickAwayListener>
|
||||||
|
);
|
||||||
|
};
|
915
next/src/components/Thingtime/Thingtime.tsx
Normal file
915
next/src/components/Thingtime/Thingtime.tsx
Normal file
@ -0,0 +1,915 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ContentEditable from 'react-contenteditable';
|
||||||
|
import * as Chakras from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
Input,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputField,
|
||||||
|
NumberInputStepper,
|
||||||
|
Select,
|
||||||
|
Spinner,
|
||||||
|
Switch,
|
||||||
|
Textarea
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { Commander } from '../Commander/Commander';
|
||||||
|
// import { Magic } from "../Commander/Magic"
|
||||||
|
import { Icon } from '../Icon/Icon';
|
||||||
|
import { MagicInput } from '../MagicInput/MagicInput';
|
||||||
|
import { Safe } from '../Safety/Safe';
|
||||||
|
import { SettingsMenu } from './SettingsMenu';
|
||||||
|
import { useThingtime } from './useThingtime';
|
||||||
|
|
||||||
|
import { useThings } from '@/remix_providers/hooks/useThings';
|
||||||
|
import { getThing } from '@/smarts';
|
||||||
|
|
||||||
|
export const Thingtime = (props: any) => {
|
||||||
|
// TODO: Add a circular reference seen prop check
|
||||||
|
// and add button to expand circular reference
|
||||||
|
// up to 1 level deep
|
||||||
|
|
||||||
|
const { append } = useThings();
|
||||||
|
|
||||||
|
const { thingtime, setThingtime, getThingtime, loading, events } = useThingtime();
|
||||||
|
|
||||||
|
const [uuid, setUuid] = React.useState(undefined);
|
||||||
|
|
||||||
|
const [root, setRoot] = React.useState(props?.notRoot ? false : true);
|
||||||
|
|
||||||
|
const [circular, setCircular] = React.useState(props?.circular);
|
||||||
|
|
||||||
|
const thingtimeRef = React.useRef();
|
||||||
|
|
||||||
|
const [showFullPathContext, setShowFullPathContext] = React.useState(false);
|
||||||
|
|
||||||
|
const editValueRef = React.useRef({});
|
||||||
|
|
||||||
|
const depth = React.useMemo(() => {
|
||||||
|
return typeof props?.depth === 'number' ? props?.depth : 0;
|
||||||
|
}, [props?.depth]);
|
||||||
|
|
||||||
|
const render = React.useMemo(() => {
|
||||||
|
return !props?.edit || props?.render || false;
|
||||||
|
}, [props?.render, props?.edit]);
|
||||||
|
|
||||||
|
const width = React.useMemo(() => {
|
||||||
|
if (props?.width) {
|
||||||
|
return props?.width;
|
||||||
|
}
|
||||||
|
if (props?.w) {
|
||||||
|
return props?.w;
|
||||||
|
}
|
||||||
|
if (render) {
|
||||||
|
return 'auto';
|
||||||
|
}
|
||||||
|
return '100%';
|
||||||
|
}, [props?.width, props?.w, render]);
|
||||||
|
|
||||||
|
const chakraChild = React.useMemo(() => {
|
||||||
|
if (!props?.edit && props?.chakraChild) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, [props?.edit, props?.chakraChild]);
|
||||||
|
|
||||||
|
const pl = React.useMemo(() => {
|
||||||
|
if (!props.edit && chakraChild) {
|
||||||
|
return [0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return props?.pl || [4, 6];
|
||||||
|
}, [props?.pl, props?.edit, chakraChild]);
|
||||||
|
|
||||||
|
const pr = React.useMemo(() => {
|
||||||
|
return props?.pr || (depth === 0 ? [4, 6] : 0);
|
||||||
|
}, [props?.pr, depth]);
|
||||||
|
|
||||||
|
const multiplyPl = React.useCallback(
|
||||||
|
(num) => {
|
||||||
|
return pl.map((p) => p * num);
|
||||||
|
},
|
||||||
|
[pl]
|
||||||
|
);
|
||||||
|
|
||||||
|
const propsRef = React.useRef(props);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
propsRef.current = props;
|
||||||
|
}, [props]);
|
||||||
|
|
||||||
|
// will only run on the client
|
||||||
|
React.useEffect(() => {
|
||||||
|
setUuid(Math.random().toString(36).substring(7));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const childrenRef = React.useRef([]);
|
||||||
|
|
||||||
|
const [thingDep, setThingDep] = React.useState(childrenRef.current);
|
||||||
|
|
||||||
|
const createDependancies = () => {
|
||||||
|
// push all children into childrenRef.current
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.meta.db['createDependancies'] = window.meta.db['createDependancies'] || 0;
|
||||||
|
window.meta.db['createDependancies']++;
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const values = Object.values(props?.thing);
|
||||||
|
// if childrenRef.current does not shallow equal values then replace with array of values
|
||||||
|
const valuesNotEqual =
|
||||||
|
values?.length !== childrenRef.current?.length ||
|
||||||
|
!values?.every?.((value, idx) => {
|
||||||
|
return childrenRef.current[idx] === value;
|
||||||
|
});
|
||||||
|
if (valuesNotEqual) {
|
||||||
|
childrenRef.current = values;
|
||||||
|
setThingDep(childrenRef.current);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
createDependancies();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const thing = React.useMemo(() => {
|
||||||
|
return props.thing;
|
||||||
|
}, [props.thing, uuid, childrenRef.current]);
|
||||||
|
|
||||||
|
const chakra = React.useMemo(() => {
|
||||||
|
return !props?.edit && typeof thing?.chakra === 'string' && thing?.chakra;
|
||||||
|
}, [thing?.chakra, props?.edit]);
|
||||||
|
|
||||||
|
const path = React.useMemo(() => {
|
||||||
|
return props?.path?.key || props?.path || '';
|
||||||
|
}, [props?.path]);
|
||||||
|
|
||||||
|
const fullPath = React.useMemo(() => {
|
||||||
|
const ret = props?.fullPath || props?.path?.key || props?.path;
|
||||||
|
|
||||||
|
// store this thing in the global db
|
||||||
|
try {
|
||||||
|
window.meta.things[ret] = props?.thing;
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}, [props?.fullPath, props?.path, props?.thing]);
|
||||||
|
|
||||||
|
const parentPath = React.useMemo(() => {
|
||||||
|
const parentPath = fullPath?.split('.')?.slice(0, -1)?.join('.');
|
||||||
|
|
||||||
|
if (!parentPath) {
|
||||||
|
return 'thingtime';
|
||||||
|
}
|
||||||
|
|
||||||
|
return parentPath;
|
||||||
|
}, [fullPath]);
|
||||||
|
|
||||||
|
const parent = React.useMemo(() => {
|
||||||
|
return getThingtime(parentPath);
|
||||||
|
}, [parentPath, getThingtime]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
console.log('thingtime changed in path', props?.fullPath);
|
||||||
|
createDependancies();
|
||||||
|
}, [thingtime, props?.fullPath, childrenRef]);
|
||||||
|
|
||||||
|
const seen = React.useMemo(() => {
|
||||||
|
if (props?.seen instanceof Array) {
|
||||||
|
if (props?.seen?.includes(thing)) {
|
||||||
|
return props?.seen;
|
||||||
|
} else if (typeof thing === 'object') {
|
||||||
|
return [...props.seen, thing];
|
||||||
|
}
|
||||||
|
return props?.seen || [];
|
||||||
|
}
|
||||||
|
if (typeof thing === 'object') {
|
||||||
|
return [thing];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, [props?.seen, thing]);
|
||||||
|
|
||||||
|
const mode = React.useMemo(() => {
|
||||||
|
return 'view';
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validKeyTypes = React.useMemo(() => {
|
||||||
|
return ['object', 'array'];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const keys = React.useMemo(() => {
|
||||||
|
if (validKeyTypes?.includes(typeof thing)) {
|
||||||
|
try {
|
||||||
|
const keysRet = Object.keys(thing);
|
||||||
|
return keysRet;
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}, [thing, thingDep, validKeyTypes]);
|
||||||
|
|
||||||
|
const type = React.useMemo(() => {
|
||||||
|
if (thing === null) {
|
||||||
|
return 'undefined';
|
||||||
|
}
|
||||||
|
return typeof thing;
|
||||||
|
}, [thing]);
|
||||||
|
|
||||||
|
const typeIcon = React.useMemo(() => {
|
||||||
|
const size = 7;
|
||||||
|
if (thing instanceof Array) {
|
||||||
|
return <Icon name="array" size={size}></Icon>;
|
||||||
|
} else if (type === 'object') {
|
||||||
|
return <Icon name="object" size={size}></Icon>;
|
||||||
|
} else if (type === 'string') {
|
||||||
|
return <Icon name="string" size={size}></Icon>;
|
||||||
|
} else if (type === 'number') {
|
||||||
|
return <Icon name="number" size={size}></Icon>;
|
||||||
|
} else if (type === 'boolean') {
|
||||||
|
return <Icon name="boolean" size={size}></Icon>;
|
||||||
|
} else if (type === 'undefined') {
|
||||||
|
return <Icon name="undefined" size={size}></Icon>;
|
||||||
|
} else {
|
||||||
|
return <Icon name="box" size={size}></Icon>;
|
||||||
|
}
|
||||||
|
}, [type, thing]);
|
||||||
|
|
||||||
|
const valuePl = React.useMemo(() => {
|
||||||
|
if (typeof props?.valuePl === 'number') {
|
||||||
|
return props?.valuePl;
|
||||||
|
}
|
||||||
|
return props?.path ? [4, 6] : [0, 0];
|
||||||
|
}, [props?.valuePl, props?.path]);
|
||||||
|
|
||||||
|
const renderableValue = React.useMemo(() => {
|
||||||
|
if (chakraChild) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'string') {
|
||||||
|
return <MagicInput value={thing} readonly></MagicInput>;
|
||||||
|
} else if (type === 'number') {
|
||||||
|
return thing;
|
||||||
|
} else if (type === 'boolean') {
|
||||||
|
return thing ? 'true' : 'false';
|
||||||
|
} else if (type === 'object') {
|
||||||
|
if (thing === null) {
|
||||||
|
return 'null';
|
||||||
|
}
|
||||||
|
if (!keys?.length) {
|
||||||
|
return 'Something!';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.stringify(thing, null, 2);
|
||||||
|
} catch (err) {
|
||||||
|
// console.error(
|
||||||
|
// "Caught error making renderableValue of thing",
|
||||||
|
// err,
|
||||||
|
// thing
|
||||||
|
// )
|
||||||
|
return (
|
||||||
|
<Box cursor="pointer" onClick={() => setCircular(false)}>
|
||||||
|
Click to Expand
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (type === 'undefined') {
|
||||||
|
return 'Imagine..';
|
||||||
|
} else {
|
||||||
|
return 'Something..';
|
||||||
|
}
|
||||||
|
}, [thing, thingDep, type, chakraChild, keys]);
|
||||||
|
|
||||||
|
const renderChakra = React.useMemo(() => {
|
||||||
|
if (!props?.edit && chakra && render) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, [chakra, props?.edit, render]);
|
||||||
|
|
||||||
|
const keysToUse = React.useMemo(() => {
|
||||||
|
if (renderChakra) {
|
||||||
|
return ['children'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}, [keys, renderChakra]);
|
||||||
|
// const keysToUse = flattenedKeys
|
||||||
|
|
||||||
|
const AtomicWrapper = React.useCallback((args) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
className="atomic-wrapper"
|
||||||
|
position="relative"
|
||||||
|
flexDirection="row"
|
||||||
|
flexShrink={1}
|
||||||
|
width="100%"
|
||||||
|
paddingLeft={args?.pl || args?.paddingLeft}
|
||||||
|
fontSize="20px"
|
||||||
|
// whiteSpace="pre-line"
|
||||||
|
border="none"
|
||||||
|
whiteSpace="pre-wrap"
|
||||||
|
wordBreak={args?.wordBreak || 'break-word'}
|
||||||
|
// paddingY={2}
|
||||||
|
// dangerouslySetInnerHTML={{ __html: renderableValue }}
|
||||||
|
outline="none"
|
||||||
|
>
|
||||||
|
{args?.children}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const thingtimeChildren = React.useMemo(() => {
|
||||||
|
let inner = <AtomicWrapper paddingLeft={pl}>Imagine..</AtomicWrapper>;
|
||||||
|
if (keys?.length && !circular) {
|
||||||
|
inner = (
|
||||||
|
<>
|
||||||
|
{keysToUse?.length &&
|
||||||
|
keysToUse.map((key, idx) => {
|
||||||
|
if (!key?.human) {
|
||||||
|
key = {
|
||||||
|
human: key,
|
||||||
|
key: key
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextThing = thing[key?.key];
|
||||||
|
|
||||||
|
const nextSeen = [...seen];
|
||||||
|
|
||||||
|
if (typeof nextThing === 'object') {
|
||||||
|
nextSeen.push(nextThing);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Thingtime
|
||||||
|
key={idx}
|
||||||
|
seen={nextSeen}
|
||||||
|
edit={props?.edit}
|
||||||
|
render={render}
|
||||||
|
circular={seen?.includes?.(nextThing)}
|
||||||
|
depth={depth + 1}
|
||||||
|
parent={thing}
|
||||||
|
notRoot
|
||||||
|
fullPath={fullPath + '.' + key?.key}
|
||||||
|
path={key}
|
||||||
|
chakraChild={chakra}
|
||||||
|
thing={nextThing}
|
||||||
|
// thing={{ infinite: { yes: true } }}
|
||||||
|
valuePl={pl}
|
||||||
|
></Thingtime>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (type === 'object' && !circular) {
|
||||||
|
if (chakra) {
|
||||||
|
const ChakraComponent = Chakras[chakra];
|
||||||
|
|
||||||
|
console.log('Thingtime is chakra', fullPath, chakra);
|
||||||
|
|
||||||
|
const rawChildren = thing?.rawChildren;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (ChakraComponent) {
|
||||||
|
console.log('Thingtime found ChakraComponent', fullPath, ChakraComponent);
|
||||||
|
console.log('Thingtime found thing?.props', fullPath, thing?.props);
|
||||||
|
|
||||||
|
const ret = (
|
||||||
|
<ChakraComponent {...(thing?.props || {})}>
|
||||||
|
{rawChildren}
|
||||||
|
{inner}
|
||||||
|
</ChakraComponent>
|
||||||
|
);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// chakra error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Is it safe to spread props
|
||||||
|
// because having props as a dependency will cause a re-render every time
|
||||||
|
|
||||||
|
if (props?.chakraChild) {
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
className="nested-things"
|
||||||
|
position="relative"
|
||||||
|
flexDirection="column"
|
||||||
|
rowGap={!chakra ? 9 : 0}
|
||||||
|
// w={'500px'}
|
||||||
|
// w={['200px', '500px']}
|
||||||
|
maxWidth="100%"
|
||||||
|
paddingLeft={valuePl}
|
||||||
|
paddingY={!chakra && props?.path ? 4 : 0}
|
||||||
|
>
|
||||||
|
{inner}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
AtomicWrapper,
|
||||||
|
keysToUse,
|
||||||
|
circular,
|
||||||
|
seen,
|
||||||
|
type,
|
||||||
|
props?.chakraChild,
|
||||||
|
props?.path,
|
||||||
|
props?.edit,
|
||||||
|
chakra,
|
||||||
|
fullPath,
|
||||||
|
render,
|
||||||
|
depth,
|
||||||
|
thing,
|
||||||
|
thingDep,
|
||||||
|
valuePl,
|
||||||
|
pl,
|
||||||
|
keys
|
||||||
|
]);
|
||||||
|
|
||||||
|
const updateValue = React.useCallback(
|
||||||
|
(args) => {
|
||||||
|
const { value } = args;
|
||||||
|
|
||||||
|
setThingtime(fullPath, value);
|
||||||
|
},
|
||||||
|
[fullPath, setThingtime]
|
||||||
|
);
|
||||||
|
|
||||||
|
const resetValue = React.useCallback(() => {
|
||||||
|
updateValue({ value: null });
|
||||||
|
}, [updateValue]);
|
||||||
|
|
||||||
|
const onChangeType = React.useCallback(
|
||||||
|
(args) => {
|
||||||
|
const { type, wrap } = args;
|
||||||
|
const typeValue = typeof type?.value === 'function' ? type?.value() : type?.value;
|
||||||
|
|
||||||
|
if (type) {
|
||||||
|
const wrapTarget = type?.wrap;
|
||||||
|
if (wrap && wrapTarget) {
|
||||||
|
const newValue = append({
|
||||||
|
thing: typeValue[wrapTarget],
|
||||||
|
value: thing
|
||||||
|
});
|
||||||
|
typeValue[wrapTarget] = newValue;
|
||||||
|
if (typeValue) {
|
||||||
|
updateValue({ value: typeValue });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateValue({ value: typeValue });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[updateValue, thing, append, fullPath]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onWrapType = React.useCallback(
|
||||||
|
(type) => {
|
||||||
|
// nothing
|
||||||
|
},
|
||||||
|
[updateValue]
|
||||||
|
);
|
||||||
|
|
||||||
|
const deleteValue = React.useCallback(() => {
|
||||||
|
// use parent path to clone parent object but without this key
|
||||||
|
const clone = { ...parent };
|
||||||
|
|
||||||
|
delete clone[path];
|
||||||
|
|
||||||
|
setThingtime(parentPath, clone);
|
||||||
|
}, [path, parent, parentPath, setThingtime]);
|
||||||
|
|
||||||
|
const atomicValue = React.useMemo(() => {
|
||||||
|
if (props?.edit) {
|
||||||
|
if (type === 'boolean') {
|
||||||
|
return (
|
||||||
|
<AtomicWrapper paddingLeft={pl} className="boolean-atomic-wrapper">
|
||||||
|
<Box
|
||||||
|
onClick={(e) => {
|
||||||
|
e?.preventDefault?.();
|
||||||
|
e?.stopPropagation?.();
|
||||||
|
// cancel bubble
|
||||||
|
e?.nativeEvent?.stopImmediatePropagation?.();
|
||||||
|
setTimeout(() => {
|
||||||
|
updateValue({ value: !thing });
|
||||||
|
}, 1);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Switch isChecked={thing}></Switch>
|
||||||
|
</Box>
|
||||||
|
</AtomicWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (type === 'number') {
|
||||||
|
const numberPxLength = thing?.toString()?.length * 13 + 30;
|
||||||
|
return (
|
||||||
|
<AtomicWrapper paddingLeft={pl} className="number-atomic-wrapper">
|
||||||
|
<Flex>
|
||||||
|
<NumberInput
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
onChange={(value) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
const number = Number(value);
|
||||||
|
console.log('typeof number', typeof number);
|
||||||
|
updateValue({ value: number });
|
||||||
|
} catch {
|
||||||
|
// something went wrong casting to number
|
||||||
|
}
|
||||||
|
}, 1);
|
||||||
|
}}
|
||||||
|
value={thing}
|
||||||
|
>
|
||||||
|
<NumberInputField width={numberPxLength + 'px'} />
|
||||||
|
<NumberInputStepper transform="scale(0.9)">
|
||||||
|
<NumberIncrementStepper
|
||||||
|
// transform="scale(0.7)"
|
||||||
|
/>
|
||||||
|
<NumberDecrementStepper
|
||||||
|
// transform="scale(0.7)"
|
||||||
|
/>
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
</Flex>
|
||||||
|
</AtomicWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (type === 'string') {
|
||||||
|
return (
|
||||||
|
<AtomicWrapper paddingLeft={pl} className="string-atomic-wrapper">
|
||||||
|
<MagicInput value={thing} placeholder="Imagine.." onValueChange={updateValue}></MagicInput>
|
||||||
|
{/* <Box
|
||||||
|
ref={contentEditableRef}
|
||||||
|
width="100%"
|
||||||
|
border="none"
|
||||||
|
outline="none"
|
||||||
|
contentEditable={true}
|
||||||
|
dangerouslySetInnerHTML={{ __html: contentEditableThing }}
|
||||||
|
onInput={(value) => {
|
||||||
|
const innerText = value?.target?.innerText
|
||||||
|
if (typeof innerText === "string") {
|
||||||
|
const time = Date.now()
|
||||||
|
editValueRef.current[time] = innerText
|
||||||
|
updateValue({ value: innerText })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></Box> */}
|
||||||
|
</AtomicWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (type === 'undefined') {
|
||||||
|
return (
|
||||||
|
<AtomicWrapper paddingLeft={pl}>
|
||||||
|
{/* TODO: Implement UI-less commander */}
|
||||||
|
<Commander
|
||||||
|
// rainbow
|
||||||
|
id={uuid}
|
||||||
|
pathPrefix={fullPath}
|
||||||
|
placeholder="Imagine.."
|
||||||
|
// onValueChange={updateValue}
|
||||||
|
></Commander>
|
||||||
|
</AtomicWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AtomicWrapper paddingLeft={pl} className="default-atomic-wrapper">
|
||||||
|
{renderableValue}
|
||||||
|
</AtomicWrapper>
|
||||||
|
);
|
||||||
|
}, [renderableValue, pl, type, fullPath, uuid, AtomicWrapper, props?.edit, thing, thingDep, updateValue]);
|
||||||
|
|
||||||
|
const contextMenu = (
|
||||||
|
<Flex position="absolute" top={0} right={0} paddingRight={4} userSelect="none">
|
||||||
|
Settings
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
|
const [showContextMenu, setShowContextMenu] = React.useState(false);
|
||||||
|
|
||||||
|
const humanPath = React.useMemo(() => {
|
||||||
|
if (typeof props?.path === 'string') {
|
||||||
|
return props?.path;
|
||||||
|
}
|
||||||
|
return props?.path?.human || '';
|
||||||
|
}, [props?.path]);
|
||||||
|
|
||||||
|
const renderedPath = React.useMemo(() => {
|
||||||
|
if (props?.edit) {
|
||||||
|
return humanPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (humanPath?.includes?.('hidden')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (humanPath?.includes?.('unique')) {
|
||||||
|
// take only path from before the string unique
|
||||||
|
return humanPath.split?.('unique')?.[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return humanPath;
|
||||||
|
}, [humanPath, props?.edit]);
|
||||||
|
|
||||||
|
const updatePath = React.useCallback(
|
||||||
|
(args) => {
|
||||||
|
if (typeof args?.value === 'string') {
|
||||||
|
try {
|
||||||
|
const parentKeys = Object.keys(parent);
|
||||||
|
// create new object with new key order
|
||||||
|
const newObject = {};
|
||||||
|
|
||||||
|
parentKeys.forEach((key) => {
|
||||||
|
if (key === path) {
|
||||||
|
newObject[args.value] = parent[key];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newObject[key] = parent[key];
|
||||||
|
});
|
||||||
|
// set new object
|
||||||
|
setThingtime(parentPath, newObject);
|
||||||
|
|
||||||
|
// focus next input
|
||||||
|
const focusableNodeList = thingtimeRef?.current?.querySelectorAll?.('.magic-input-focusable');
|
||||||
|
|
||||||
|
// convert focusable to array
|
||||||
|
const focusable = Array.from(focusableNodeList);
|
||||||
|
|
||||||
|
const pathMagicInputFocusable = pathRef?.current?.querySelector?.('.magic-input-focusable');
|
||||||
|
|
||||||
|
const nearestMagicInput = focusable?.find((input) => {
|
||||||
|
if (input !== pathMagicInputFocusable) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (nearestMagicInput && nearestMagicInput?.focus) {
|
||||||
|
nearestMagicInput.focus();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Thingtime:657 Something went wrong reassigning path', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[parent, path, parentPath, setThingtime]
|
||||||
|
);
|
||||||
|
|
||||||
|
const pathRef = React.useRef(null);
|
||||||
|
|
||||||
|
const pathDom = React.useMemo(() => {
|
||||||
|
if (chakraChild) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renderedPath) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MagicInput
|
||||||
|
ref={pathRef}
|
||||||
|
value={renderedPath}
|
||||||
|
readonly={!props?.edit}
|
||||||
|
onEnter={updatePath}
|
||||||
|
chakras={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
paddingLeft: props?.pathPl || pl,
|
||||||
|
fontSize: '12px',
|
||||||
|
wordBreak: 'break-all'
|
||||||
|
}}
|
||||||
|
></MagicInput>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [renderedPath, pl, chakraChild, props?.edit, props?.pathPl]);
|
||||||
|
|
||||||
|
const handleMouseEvent = React.useCallback(
|
||||||
|
(e) => {
|
||||||
|
const target = e?.target;
|
||||||
|
// extract uuid from className
|
||||||
|
const className = target?.className;
|
||||||
|
if (className?.includes(uuid?.current)) {
|
||||||
|
setShowContextMenu(e?.type === 'mouseenter');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[uuid]
|
||||||
|
);
|
||||||
|
|
||||||
|
const addNewChild = React.useCallback(
|
||||||
|
(args) => {
|
||||||
|
const { type } = args;
|
||||||
|
const newChild = typeof type?.value === 'function' ? type?.value() : type?.value || null;
|
||||||
|
|
||||||
|
if (thing instanceof Array) {
|
||||||
|
// add new child to array
|
||||||
|
const newValue = [...thing, newChild];
|
||||||
|
setThingtime(fullPath, newValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newChildBasePath = 'New Value';
|
||||||
|
// find increment that thing doesn't already have New Value N+1
|
||||||
|
let increment = 0;
|
||||||
|
let newPath = newChildBasePath;
|
||||||
|
while (Object.hasOwnProperty.call(thing, newPath) && increment <= 999) {
|
||||||
|
increment++;
|
||||||
|
newPath = newChildBasePath + ' ' + increment;
|
||||||
|
}
|
||||||
|
const newChildPath = newPath;
|
||||||
|
const newChildFullPath = fullPath + '.' + newChildPath;
|
||||||
|
// create new child on thing using setThingtime
|
||||||
|
setThingtime(newChildFullPath, newChild);
|
||||||
|
},
|
||||||
|
[fullPath, setThingtime, thing]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [showContextIcon, setShowContextIcon] = React.useState(false);
|
||||||
|
const [showNewContextIcon, setShowNewContextIcon] = React.useState(false);
|
||||||
|
|
||||||
|
// should be absolute last
|
||||||
|
React.useEffect(() => {
|
||||||
|
try {
|
||||||
|
window.meta.things[uuid] = {
|
||||||
|
thing: props?.thing,
|
||||||
|
props,
|
||||||
|
state: {
|
||||||
|
chakra,
|
||||||
|
chakraChild,
|
||||||
|
circular,
|
||||||
|
depth,
|
||||||
|
fullPath,
|
||||||
|
parent,
|
||||||
|
parentPath,
|
||||||
|
path
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
}, [thing, props, uuid, chakra, chakraChild, circular, depth, fullPath, parent, parentPath, path]);
|
||||||
|
|
||||||
|
if (chakra || chakraChild) {
|
||||||
|
return thingtimeChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Safe {...props} depth={depth} uuid={uuid}>
|
||||||
|
<Flex
|
||||||
|
ref={thingtimeRef}
|
||||||
|
position="relative"
|
||||||
|
// width="500px"
|
||||||
|
flexDirection="column"
|
||||||
|
rowGap={2}
|
||||||
|
width={width}
|
||||||
|
maxWidth="100%"
|
||||||
|
// marginTop={3}
|
||||||
|
paddingRight={pr}
|
||||||
|
// minW={depth === 1 ? '120px' : null}
|
||||||
|
onMouseEnter={handleMouseEvent}
|
||||||
|
onMouseLeave={handleMouseEvent}
|
||||||
|
{...(props.chakras || {})}
|
||||||
|
className={`thing uuid-${uuid} edit-${props?.edit ? 'true' : 'false'}`}
|
||||||
|
data-path={props?.path}
|
||||||
|
>
|
||||||
|
{/* {uuid?.current} */}
|
||||||
|
{!chakraChild && !chakra && (
|
||||||
|
<Flex position="relative" flexDirection="row">
|
||||||
|
<Flex
|
||||||
|
alignItems="center"
|
||||||
|
flexDirection="row"
|
||||||
|
marginRight="auto"
|
||||||
|
onMouseEnter={() => setShowContextIcon(true)}
|
||||||
|
onMouseLeave={() => setShowContextIcon(false)}
|
||||||
|
>
|
||||||
|
<Flex>{pathDom}</Flex>
|
||||||
|
{props?.edit && (
|
||||||
|
<Box
|
||||||
|
// marginTop={-3}
|
||||||
|
marginTop={-1}
|
||||||
|
paddingLeft={1}
|
||||||
|
opacity={0.5}
|
||||||
|
cursor="pointer"
|
||||||
|
>
|
||||||
|
{typeIcon}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{pathDom && (
|
||||||
|
<Flex flexDirection="row" columnGap={1} marginTop={-1} paddingLeft={1}>
|
||||||
|
<SettingsMenu
|
||||||
|
transition="all 0.2s ease-in-out"
|
||||||
|
opacity={showContextIcon ? 1 : 0}
|
||||||
|
uuid={uuid}
|
||||||
|
fullPath={fullPath}
|
||||||
|
readonly={!props?.edit}
|
||||||
|
onType={onChangeType}
|
||||||
|
onDelete={deleteValue}
|
||||||
|
></SettingsMenu>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
{/* {showContextMenu && contextMenu} */}
|
||||||
|
{!loading && !thingtimeChildren && atomicValue && (
|
||||||
|
<Box className="atomicValue" width={render ? 'auto' : ''}>
|
||||||
|
{atomicValue}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{!loading && thingtimeChildren && (
|
||||||
|
<Box className="thingtimeChildren" flexGrow={0} flexShrink={1} width={render ? 'auto' : ''}>
|
||||||
|
{thingtimeChildren}
|
||||||
|
{!render && type === 'object' && (
|
||||||
|
<Flex
|
||||||
|
position="relative"
|
||||||
|
width="100%"
|
||||||
|
paddingLeft={multiplyPl(2)}
|
||||||
|
opacity={props?.edit ? 1 : 0}
|
||||||
|
cursor="pointer"
|
||||||
|
transition="all 0.2s ease-out"
|
||||||
|
onClick={addNewChild}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
setShowFullPathContext(true);
|
||||||
|
setShowNewContextIcon(true);
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => {
|
||||||
|
setShowFullPathContext(false);
|
||||||
|
setShowNewContextIcon(false);
|
||||||
|
}}
|
||||||
|
paddingY={2}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
position="absolute"
|
||||||
|
bottom="100%"
|
||||||
|
left={0}
|
||||||
|
display={showFullPathContext ? 'flex' : 'none'}
|
||||||
|
fontSize="sm"
|
||||||
|
background="greys.light"
|
||||||
|
borderRadius={6}
|
||||||
|
pointerEvents="none"
|
||||||
|
paddingX={3}
|
||||||
|
paddingY={1}
|
||||||
|
>
|
||||||
|
{fullPath}
|
||||||
|
</Flex>
|
||||||
|
<Icon
|
||||||
|
_focus={{
|
||||||
|
outline: 'none !important',
|
||||||
|
textShadow: '0px 0px 10px green'
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e?.key === 'Enter') {
|
||||||
|
addNewChild();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
tabIndex={0}
|
||||||
|
size={10}
|
||||||
|
name="seedling"
|
||||||
|
></Icon>
|
||||||
|
<Flex
|
||||||
|
marginLeft={2}
|
||||||
|
onClick={(e) => {
|
||||||
|
e?.preventDefault();
|
||||||
|
e?.stopPropagation();
|
||||||
|
e?.nativeEvent?.stopImmediatePropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SettingsMenu
|
||||||
|
transition="all 0.2s ease-in-out"
|
||||||
|
opacity={showNewContextIcon ? 1 : 0}
|
||||||
|
uuid={uuid}
|
||||||
|
iconSize={10}
|
||||||
|
fullPath={fullPath}
|
||||||
|
readonly={!props?.edit}
|
||||||
|
onType={addNewChild}
|
||||||
|
></SettingsMenu>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{/* <Icon size={7} name="plus"></Icon>
|
||||||
|
<Icon size={7} name="plus"></Icon> */}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Safe>
|
||||||
|
);
|
||||||
|
};
|
60
next/src/components/Thingtime/ThingtimeDemo.tsx
Normal file
60
next/src/components/Thingtime/ThingtimeDemo.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { Thingtime } from './Thingtime';
|
||||||
|
|
||||||
|
export const ThingtimeDemo = (props: any) => {
|
||||||
|
const thing = {
|
||||||
|
Suggestion: 'Press ctrl/cmd + P',
|
||||||
|
Name: 'thingtime',
|
||||||
|
Description: `
|
||||||
|
Thing Time is a versatile online platform that empowers users to organize, customize, and manage different types of data.
|
||||||
|
|
||||||
|
By generalizing the concept of social media feeds, Thing Time allows users to switch between various data schemas, essentially providing them with personalized feeds and organizational tools.
|
||||||
|
|
||||||
|
It's like a combination of Facebook, Twitter, and Evernote, but with the user in control of what kind of data they want to see and manage.
|
||||||
|
|
||||||
|
Whether it's social posts, marketplace listings, or personal notes, Thing Time puts the power of data organization and customization in the user’s hands.
|
||||||
|
`,
|
||||||
|
'Image URL': 'https://google.com/images',
|
||||||
|
Price: '$100',
|
||||||
|
QTY: 1,
|
||||||
|
ID: 123,
|
||||||
|
Nested: {
|
||||||
|
data: {
|
||||||
|
is: {
|
||||||
|
fun: "Isn't it?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Array: ['one', 'two', 'three'],
|
||||||
|
'Array of Objects': [
|
||||||
|
{
|
||||||
|
name: 'one'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'two'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'three'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'Identical Keys unique1': "I'm unique 1",
|
||||||
|
'Identical Keys unique2': "I'm unique 2",
|
||||||
|
'Identical Keys unique3': "I'm unique 3"
|
||||||
|
};
|
||||||
|
|
||||||
|
const [demoThing, setDemoThing] = React.useState(thing);
|
||||||
|
|
||||||
|
// const debug = {
|
||||||
|
// test: 'hey'
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex maxWidth="100%" paddingBottom={40}>
|
||||||
|
<Thingtime width="600px" thing={demoThing}></Thingtime>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
130
next/src/components/Thingtime/ThingtimeURL.tsx
Normal file
130
next/src/components/Thingtime/ThingtimeURL.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import React from 'react';
|
||||||
|
// import { Sticky, StickyContainer } from "react-sticky"
|
||||||
|
import Sticky from 'react-sticky-el';
|
||||||
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
|
// import { useLocation, useMatches } from '@remix-run/react';
|
||||||
|
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
|
||||||
|
import { Thingtime } from './Thingtime';
|
||||||
|
import { useThingtime } from './useThingtime';
|
||||||
|
|
||||||
|
export const ThingtimeURL = (props: any) => {
|
||||||
|
const { getThingtime } = useThingtime();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const location = router;
|
||||||
|
const { pathname } = location;
|
||||||
|
|
||||||
|
const navigate = router.push;
|
||||||
|
|
||||||
|
// const { pathname } = useLocation();
|
||||||
|
|
||||||
|
// const matches = useMatches();
|
||||||
|
// const location = React.useMemo(() => {
|
||||||
|
// return matches[matches.length - 1];
|
||||||
|
// }, [matches]);
|
||||||
|
|
||||||
|
const path = React.useMemo(() => {
|
||||||
|
console.log('ThingtimeURL location', location);
|
||||||
|
|
||||||
|
// const sanitisation = ["/things", "/edit", "/editor", "/code", "/coder"]
|
||||||
|
|
||||||
|
// // strip the leading /path1/path2 path1 section from the path
|
||||||
|
// let pathPartOne = location?.pathname?.split("/")[2]
|
||||||
|
|
||||||
|
// // remove all sanitsation strings from path
|
||||||
|
// sanitisation.forEach((sanitisationString) => {
|
||||||
|
// pathPartOne = pathPartOne?.replace(sanitisationString, "")
|
||||||
|
// })
|
||||||
|
|
||||||
|
// strip the leading /path1/path2 path1 section from the path
|
||||||
|
const pathPartOne = location?.pathname?.split('/')[2];
|
||||||
|
|
||||||
|
const path = pathPartOne?.replace(/\//g, '.');
|
||||||
|
|
||||||
|
return path || 'thingtime';
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
const thing = React.useMemo(() => {
|
||||||
|
// remove /things/ from path
|
||||||
|
|
||||||
|
const ret = getThingtime(path);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}, [path, getThingtime]);
|
||||||
|
|
||||||
|
const inEditorMode = React.useMemo(() => {
|
||||||
|
if (pathname.slice(0, 7) === '/editor') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
const inEditMode = React.useMemo(() => {
|
||||||
|
if (pathname.slice(0, 5) === '/edit') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
const containerRef: any = React.useRef(null);
|
||||||
|
const editorRef: any = React.useRef(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const scrollListener = () => {
|
||||||
|
if (containerRef?.current?.getBoundingClientRect) {
|
||||||
|
const { top } = containerRef?.current?.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (editorRef.current) {
|
||||||
|
editorRef.current.style.top = `${-top}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', scrollListener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', scrollListener);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
ref={containerRef}
|
||||||
|
// position="sticky"
|
||||||
|
position="relative"
|
||||||
|
alignItems={inEditorMode ? 'flex-start' : 'center'}
|
||||||
|
justifyContent="center"
|
||||||
|
// overflow="scroll"
|
||||||
|
// height="auto"
|
||||||
|
flexDirection={inEditorMode ? 'row' : 'column'}
|
||||||
|
maxWidth="100%"
|
||||||
|
// maxHeight="100vh"
|
||||||
|
>
|
||||||
|
{inEditorMode && (
|
||||||
|
<Box
|
||||||
|
ref={editorRef}
|
||||||
|
position="relative"
|
||||||
|
// position="sticky"
|
||||||
|
// top={200}
|
||||||
|
// alignSelf="flex-start"
|
||||||
|
overflow="scroll"
|
||||||
|
width="600px"
|
||||||
|
// width="100%"
|
||||||
|
maxHeight="100vh"
|
||||||
|
// paddingY={2}
|
||||||
|
>
|
||||||
|
<Thingtime
|
||||||
|
path={path}
|
||||||
|
thing={thing}
|
||||||
|
render
|
||||||
|
chakras={{ marginY: '200px' }}
|
||||||
|
// width="600px"
|
||||||
|
></Thingtime>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Thingtime edit={inEditMode} path={path} thing={thing} chakras={{ marginY: '200px' }} width="600px"></Thingtime>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
19
next/src/components/Thingtime/useThingtime.tsx
Normal file
19
next/src/components/Thingtime/useThingtime.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
|
||||||
|
import { ThingtimeContext } from '@/remix_providers/ThingtimeProvider';
|
||||||
|
|
||||||
|
const getGlobal = () => {
|
||||||
|
try {
|
||||||
|
return window;
|
||||||
|
} catch {
|
||||||
|
return globalThis;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useThingtime = (props?: any): any => {
|
||||||
|
const value = useContext(ThingtimeContext);
|
||||||
|
|
||||||
|
// const { thingtime, setThingtime, getThingtime, thingtimeRef } = value
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
37
next/src/components/rainbowText.tsx
Normal file
37
next/src/components/rainbowText.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Text } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export const RainbowText = (props: any) => {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
as="h1"
|
||||||
|
sx={{
|
||||||
|
'@keyframes moving-rainbow': {
|
||||||
|
'0%': { backgroundPosition: '0 0' },
|
||||||
|
'100%': { backgroundPosition: 'calc(100px + 200%) 0' }
|
||||||
|
},
|
||||||
|
animation: 'moving-rainbow 5s infinite linear',
|
||||||
|
'::selection': {
|
||||||
|
background: 'transparent'
|
||||||
|
},
|
||||||
|
'::-moz-selection': {
|
||||||
|
background: 'transparent'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
position="relative"
|
||||||
|
maxWidth="100%"
|
||||||
|
color="transparent"
|
||||||
|
fontSize="6xl"
|
||||||
|
fontWeight="bold"
|
||||||
|
backgroundSize="calc(100px + 200%)"
|
||||||
|
bgGradient="linear-gradient(to right, #f34a4a, #ffbc48, #58ca70, #47b5e6, #a555e8, #f34a4a)"
|
||||||
|
backgroundClip="text"
|
||||||
|
userSelect="none"
|
||||||
|
outline="none"
|
||||||
|
contentEditable={props?.ce}
|
||||||
|
spellcheck="false"
|
||||||
|
>
|
||||||
|
{props?.children}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
65
next/src/components/textAnimation1.tsx
Normal file
65
next/src/components/textAnimation1.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { RainbowText } from './rainbowText';
|
||||||
|
|
||||||
|
export const TextAnimation1 = (props: any) => {
|
||||||
|
const texts = React.useMemo(() => {
|
||||||
|
if (props?.texts?.length) {
|
||||||
|
return props?.texts;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'Thingtime',
|
||||||
|
'Vibrant',
|
||||||
|
'Infinite',
|
||||||
|
'Creative',
|
||||||
|
'Powerful',
|
||||||
|
'Magical',
|
||||||
|
'Inspiring',
|
||||||
|
'Love'
|
||||||
|
// 'tt'
|
||||||
|
];
|
||||||
|
}, [props?.texts]);
|
||||||
|
|
||||||
|
const [titleText, setTitleText] = React.useState(texts[0]);
|
||||||
|
|
||||||
|
const [usedTexts, setUsedTexts] = React.useState(['Thingtime']);
|
||||||
|
|
||||||
|
const state: any = React.useRef({});
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
state.current = { titleText, texts, usedTexts };
|
||||||
|
}, [titleText, texts, usedTexts]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const newTimeout = Math.random() * 1500 + 2500 + (titleText === 'thingtime' ? 750 : 0);
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
// choose an unused text or pick texts[0]
|
||||||
|
const usedTexts = state.current?.usedTexts;
|
||||||
|
const unusedTexts = texts.filter((text) => !usedTexts.includes(text));
|
||||||
|
// pick a random unused text
|
||||||
|
const randomIdx = Math.floor(Math.random() * unusedTexts.length);
|
||||||
|
const wasThingtime = false && titleText === 'Thingtime';
|
||||||
|
const newText = wasThingtime ? 'is' : unusedTexts.length > 0 ? unusedTexts[randomIdx] : texts[0];
|
||||||
|
console.log('nik newText', newText);
|
||||||
|
console.log('nik wasThingtime', wasThingtime);
|
||||||
|
console.log('nik usedTexts', usedTexts);
|
||||||
|
if (!unusedTexts.length) {
|
||||||
|
setUsedTexts(['Thingtime']);
|
||||||
|
} else {
|
||||||
|
setUsedTexts([...usedTexts, newText]);
|
||||||
|
}
|
||||||
|
setTitleText(newText);
|
||||||
|
}, newTimeout);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// cleanup
|
||||||
|
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
}, [titleText]);
|
||||||
|
|
||||||
|
return <RainbowText ce={props?.ce}>{titleText}</RainbowText>;
|
||||||
|
};
|
49
next/src/functions/safe.tsx
Normal file
49
next/src/functions/safe.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
export const safe = (props: any) => {
|
||||||
|
// do not render more than the limit of things to prevent infinite loops
|
||||||
|
const meta = getMeta();
|
||||||
|
|
||||||
|
const uuid = props?.uuid;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof meta?.stats?.count === 'number' && meta?.stats?.count >= meta?.stats?.limit) {
|
||||||
|
console.error('[codex] Maximum things reached', meta?.stats?.count, meta?.stats?.limit);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[codex] Error in Thingtime.tsx checking maximum things', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!meta?.stats?.db?.[uuid]) {
|
||||||
|
meta.stats.db[uuid] = {
|
||||||
|
count: 1
|
||||||
|
};
|
||||||
|
meta.stats.count++;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (props?.depth >= meta?.stats?.maxDepth) {
|
||||||
|
console.error('[codex] Reached max depth', props?.depth, meta?.stats?.maxDepth);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return props?.children;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Caught error returning children safely', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMeta = () => {
|
||||||
|
try {
|
||||||
|
return window?.meta || globalThis?.meta;
|
||||||
|
} catch {
|
||||||
|
return globalThis?.meta;
|
||||||
|
}
|
||||||
|
};
|
34
next/src/layouts/Default.tsx
Normal file
34
next/src/layouts/Default.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { Providers } from '@/providers/providers';
|
||||||
|
|
||||||
|
import { Footer } from '@/components/Nav/Footer';
|
||||||
|
import { Nav } from '@/components/Nav/Nav';
|
||||||
|
import { ProfileDrawer } from '@/components/Nav/ProfileDrawer';
|
||||||
|
|
||||||
|
export const DefaultLayout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<Providers>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
'*': {
|
||||||
|
whiteSpace: 'pre-wrap'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
position="relative"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
flexDirection="column"
|
||||||
|
overflow="hidden"
|
||||||
|
maxWidth="100vw"
|
||||||
|
>
|
||||||
|
{/* <ProfileDrawer></ProfileDrawer> */}
|
||||||
|
<Nav />
|
||||||
|
<Box width="100%" minHeight="100vh">
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
<Footer></Footer>
|
||||||
|
</Flex>
|
||||||
|
</Providers>
|
||||||
|
);
|
||||||
|
};
|
42
next/src/pages/_app.tsx
Normal file
42
next/src/pages/_app.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { DefaultLayout } from '@/layouts/Default';
|
||||||
|
import type { AppProps } from 'next/app';
|
||||||
|
import { useIcons } from '@/remix_providers/hooks/useIcons';
|
||||||
|
|
||||||
|
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||||
|
useIcons();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DefaultLayout>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</DefaultLayout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// limiter
|
||||||
|
const setThingtime = (glob: any) => {
|
||||||
|
try {
|
||||||
|
glob.meta = {
|
||||||
|
tmp: {},
|
||||||
|
subscribers: {},
|
||||||
|
state: {},
|
||||||
|
db: {},
|
||||||
|
stats: {
|
||||||
|
db: {},
|
||||||
|
limit: 9999,
|
||||||
|
maxDepth: 99,
|
||||||
|
count: 0
|
||||||
|
},
|
||||||
|
things: {}
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
// will error on server
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
setThingtime(window);
|
||||||
|
} catch {
|
||||||
|
setThingtime(globalThis);
|
||||||
|
}
|
24
next/src/pages/_document.tsx
Normal file
24
next/src/pages/_document.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// import type { MetaFunction } from "@vercel/remix"
|
||||||
|
import { Analytics } from '@vercel/analytics/react';
|
||||||
|
|
||||||
|
import { Html, Head, Main, NextScript } from 'next/document';
|
||||||
|
|
||||||
|
import { Globals } from '@/globals/GlobalStyles';
|
||||||
|
|
||||||
|
export default function Document({ children, title = 'Thingtime' }: { children: React.ReactNode; title?: string }) {
|
||||||
|
return (
|
||||||
|
<Html lang="en">
|
||||||
|
<Head>
|
||||||
|
<meta charSet="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>{title}</title>
|
||||||
|
</Head>
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
<Globals />
|
||||||
|
<Analytics />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
}
|
4
next/src/pages/index.tsx
Normal file
4
next/src/pages/index.tsx
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { ThingtimeURL } from '@/components/Thingtime/ThingtimeURL';
|
||||||
|
export default function Index() {
|
||||||
|
return <ThingtimeURL></ThingtimeURL>;
|
||||||
|
}
|
6
next/src/providers/chakra/ChakraWrapper.tsx
Normal file
6
next/src/providers/chakra/ChakraWrapper.tsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { ChakraProvider } from "@chakra-ui/react"
|
||||||
|
|
||||||
|
import { theme } from "./chakra"
|
||||||
|
export const ChakraWrapper = (props: any) => {
|
||||||
|
return <ChakraProvider theme={theme}>{props.children}</ChakraProvider>
|
||||||
|
}
|
271
next/src/providers/chakra/chakra.tsx
Normal file
271
next/src/providers/chakra/chakra.tsx
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
extendTheme,
|
||||||
|
Input,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputField,
|
||||||
|
NumberInputStepper,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
Textarea
|
||||||
|
} from "@chakra-ui/react"
|
||||||
|
|
||||||
|
import hexgba from "hex-to-rgba"
|
||||||
|
|
||||||
|
const space: any = {}
|
||||||
|
|
||||||
|
const start = 0
|
||||||
|
const end = 9999
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
space[i] = `${i * 0.25}rem`
|
||||||
|
}
|
||||||
|
|
||||||
|
const greys = {
|
||||||
|
light: "#F1F1F3",
|
||||||
|
lightt: "#E7E6E8",
|
||||||
|
medium: "#E0E0E0",
|
||||||
|
dark: "#BDBDBD"
|
||||||
|
}
|
||||||
|
|
||||||
|
const grey = "#F1F1F3"
|
||||||
|
|
||||||
|
const Grey = {
|
||||||
|
gray: grey,
|
||||||
|
grey,
|
||||||
|
grays: greys,
|
||||||
|
greys
|
||||||
|
}
|
||||||
|
|
||||||
|
// for bad spellers
|
||||||
|
Grey.gray = Grey.grey
|
||||||
|
Grey.grays = Grey.greys
|
||||||
|
|
||||||
|
export const colors: any = {
|
||||||
|
white: "#FFFFFF",
|
||||||
|
offwhite: "#F9F9F9",
|
||||||
|
black: "#000000",
|
||||||
|
subheading: "#4A4A4A",
|
||||||
|
...Grey,
|
||||||
|
bright: {
|
||||||
|
red: "#C62828",
|
||||||
|
opaqueRed: "rgba(198, 40, 40, 0.5)",
|
||||||
|
orange: "#FF7043",
|
||||||
|
yellow: "#FFEE58",
|
||||||
|
green: "#66BB6A",
|
||||||
|
blue: "#42A5F5",
|
||||||
|
indigo: "#5C6BC0",
|
||||||
|
violet: "#AB47BC"
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
red: "#8E0000",
|
||||||
|
orange: "#E65100",
|
||||||
|
yellow: "#FDD835",
|
||||||
|
green: "#33691E",
|
||||||
|
blue: "#1E88E5",
|
||||||
|
indigo: "#3949AB",
|
||||||
|
violet: "#6A1B9A"
|
||||||
|
},
|
||||||
|
// all colors of the rainbow
|
||||||
|
rainbow: {
|
||||||
|
red: "#FF0000",
|
||||||
|
orange: "#FF7F00",
|
||||||
|
yellow: "#FFFF00",
|
||||||
|
green: "#00FF00",
|
||||||
|
blue: "#0000FF",
|
||||||
|
indigo: "#4B0082",
|
||||||
|
violet: "#8F00FF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursively add rgba(hex, 0.5) versions for all colours
|
||||||
|
|
||||||
|
function addRgba(obj: any, parentKey: string, opacity = 0.5) {
|
||||||
|
for (const key in obj) {
|
||||||
|
// return early if key includes "-"" already
|
||||||
|
|
||||||
|
if (key.includes("-")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = obj[key]
|
||||||
|
if (typeof value === "object") {
|
||||||
|
addRgba(value, key)
|
||||||
|
} else {
|
||||||
|
obj[`${key}-${opacity}`] = hexgba(value, opacity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run for all opacities 0 - 1 with steps of 0.05
|
||||||
|
|
||||||
|
// const opacities = Array.from({ length: 21 }, (_, i) => i / 20)
|
||||||
|
|
||||||
|
// opacities.forEach((opacity) => {
|
||||||
|
// addRgba(colors, "", opacity)
|
||||||
|
// })
|
||||||
|
|
||||||
|
addRgba(colors, "", 0)
|
||||||
|
addRgba(colors, "", 0.25)
|
||||||
|
addRgba(colors, "", 0.5)
|
||||||
|
addRgba(colors, "", 0.75)
|
||||||
|
addRgba(colors, "", 1)
|
||||||
|
|
||||||
|
export const theme = extendTheme({
|
||||||
|
colors,
|
||||||
|
space,
|
||||||
|
styles: {
|
||||||
|
global: {
|
||||||
|
// make all elements padding and margin animate
|
||||||
|
"*": {
|
||||||
|
transition: "padding 0.2s ease, margin 0.2s ease-out"
|
||||||
|
}
|
||||||
|
// // make all elements have a transparent focus border
|
||||||
|
// "input:focus": {
|
||||||
|
// boxShadow: "none !important",
|
||||||
|
// borderColor: "transparent !important"
|
||||||
|
// },
|
||||||
|
// // make all elements have a transparent focus border
|
||||||
|
// "textarea:focus": {
|
||||||
|
// boxShadow: "none !important",
|
||||||
|
// borderColor: "transparent !important"
|
||||||
|
// },
|
||||||
|
// // make all elements have a transparent focus border
|
||||||
|
// "select:focus": {
|
||||||
|
// boxShadow: "none !important",
|
||||||
|
// borderColor: "transparent !important"
|
||||||
|
// },
|
||||||
|
// // make all elements have a transparent focus border
|
||||||
|
// "button:focus": {
|
||||||
|
// boxShadow: "none !important",
|
||||||
|
// borderColor: "transparent !important"
|
||||||
|
// },
|
||||||
|
// // make all elements have a transparent focus border
|
||||||
|
// "div:focus": {
|
||||||
|
// boxShadow: "none !important",
|
||||||
|
// borderColor: "transparent !important"
|
||||||
|
// },
|
||||||
|
// // make all elements have a transparent focus border
|
||||||
|
// "a:focus": {
|
||||||
|
// boxShadow: "none !important",
|
||||||
|
// borderColor: "transparent !important"
|
||||||
|
// },
|
||||||
|
// // make all elements have a transparent focus border
|
||||||
|
// "span:focus": {
|
||||||
|
// boxShadow: "none !important",
|
||||||
|
// borderColor: "transparent !important"
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Input: {
|
||||||
|
defaultProps: {},
|
||||||
|
baseStyle: {
|
||||||
|
field: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Select: {
|
||||||
|
baseStyle: {
|
||||||
|
field: {},
|
||||||
|
icon: {
|
||||||
|
width: "14px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Switch: {
|
||||||
|
baseStyle: {
|
||||||
|
track: {
|
||||||
|
background: "greys.medium",
|
||||||
|
_checked: {
|
||||||
|
background: "bright.blue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NumberInput: {
|
||||||
|
baseStyle: {
|
||||||
|
field: {
|
||||||
|
width: "auto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const formBg = {
|
||||||
|
bg: "offwhite"
|
||||||
|
}
|
||||||
|
|
||||||
|
const formBorder = {
|
||||||
|
borderColor: "black-0"
|
||||||
|
}
|
||||||
|
|
||||||
|
const formColours = {
|
||||||
|
...formBg,
|
||||||
|
...formBorder
|
||||||
|
}
|
||||||
|
|
||||||
|
const formInputProps = {
|
||||||
|
...formColours,
|
||||||
|
py: 6
|
||||||
|
}
|
||||||
|
|
||||||
|
Input.defaultProps = {
|
||||||
|
...formInputProps
|
||||||
|
}
|
||||||
|
|
||||||
|
Textarea.defaultProps = {
|
||||||
|
py: 3.5,
|
||||||
|
...formColours
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch.defaultProps = {
|
||||||
|
...Switch.defaultProps,
|
||||||
|
as: "div"
|
||||||
|
}
|
||||||
|
|
||||||
|
Select.defaultProps = {
|
||||||
|
...Select.defaultProps,
|
||||||
|
focusBorderColor: "transparent",
|
||||||
|
outline: "0px solid",
|
||||||
|
border: "none",
|
||||||
|
ps: "0px",
|
||||||
|
px: "0px",
|
||||||
|
sx: {
|
||||||
|
paddingInlineStart: "0px",
|
||||||
|
paddingInlineEnd: "24px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberInput.defaultProps = {
|
||||||
|
...NumberInput.defaultProps
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberInputField.defaultProps = {
|
||||||
|
...NumberInputField.defaultProps,
|
||||||
|
height: "100%",
|
||||||
|
pr: 3,
|
||||||
|
fontSize: "inherit"
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberInputStepper.defaultProps = {
|
||||||
|
...NumberInputStepper.defaultProps,
|
||||||
|
border: "none",
|
||||||
|
color: "grays.medium"
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberIncrementStepper.defaultProps = {
|
||||||
|
...NumberIncrementStepper.defaultProps,
|
||||||
|
border: "none"
|
||||||
|
}
|
||||||
|
NumberDecrementStepper.defaultProps = {
|
||||||
|
...NumberDecrementStepper.defaultProps,
|
||||||
|
border: "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
Button.defaultProps = {
|
||||||
|
...Button.defaultProps,
|
||||||
|
bg: "offwhite"
|
||||||
|
}
|
15
next/src/providers/providers.tsx
Normal file
15
next/src/providers/providers.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// app/providers.tsx
|
||||||
|
|
||||||
|
import { ThingtimeProvider } from '@/remix_providers/ThingtimeProvider';
|
||||||
|
import { ChakraWrapper } from './chakra/ChakraWrapper';
|
||||||
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
|
export function Providers({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<ChakraWrapper>
|
||||||
|
<ThingtimeProvider>{children}</ThingtimeProvider>
|
||||||
|
</ChakraWrapper>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
6
next/src/remix_providers/chakra/ChakraWrapper.tsx
Normal file
6
next/src/remix_providers/chakra/ChakraWrapper.tsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { ChakraProvider } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { theme } from './theme';
|
||||||
|
export const ChakraWrapper = (props: any) => {
|
||||||
|
return <ChakraProvider theme={theme}>{props.children}</ChakraProvider>;
|
||||||
|
};
|
54
next/src/remix_providers/hooks/usePath.tsx
Normal file
54
next/src/remix_providers/hooks/usePath.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import React from 'react';
|
||||||
|
// import { useLocation } from '@remix-run/react';
|
||||||
|
// import { useNavigate } from '@remix-run/react';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
|
||||||
|
export const usePath = (props?: any) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const location = router;
|
||||||
|
const { pathname } = location;
|
||||||
|
|
||||||
|
const navigate = router.push;
|
||||||
|
|
||||||
|
const [mode, setMode] = React.useState('');
|
||||||
|
|
||||||
|
const modes = React.useMemo(() => {
|
||||||
|
// make sure any substrings come first
|
||||||
|
return ['edit', 'editor', 'code', 'coder', 'thing', 'things', 'thingtime', 'thingtimes'];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
let set = false;
|
||||||
|
modes.forEach((mode) => {
|
||||||
|
const pathPart = pathname.slice(1, mode.length + 1);
|
||||||
|
if (pathPart === mode) {
|
||||||
|
setMode(mode);
|
||||||
|
set = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!set) {
|
||||||
|
setMode((mode) => {
|
||||||
|
if (!mode) {
|
||||||
|
return 'things';
|
||||||
|
}
|
||||||
|
return mode;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [pathname, modes]);
|
||||||
|
|
||||||
|
const changePath = React.useCallback(
|
||||||
|
(props: any) => {
|
||||||
|
const { path } = props;
|
||||||
|
|
||||||
|
navigate(`${mode}/${path}`);
|
||||||
|
},
|
||||||
|
[navigate, mode]
|
||||||
|
);
|
||||||
|
|
||||||
|
const ret = {
|
||||||
|
mode,
|
||||||
|
changePath
|
||||||
|
};
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
};
|
22
next/src/remix_routes/$.tsx
Normal file
22
next/src/remix_routes/$.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { ThingtimeURL } from '@/components/Thingtime/ThingtimeURL';
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
return <ThingtimeURL></ThingtimeURL>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const action = async ({ request }) => {
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
message: 'Hello, World! $ Action'
|
||||||
|
},
|
||||||
|
cache: {
|
||||||
|
revalidate: 60
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
31
next/src/remix_routes/_index.tsx
Normal file
31
next/src/remix_routes/_index.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { ProfileDrawer } from '@/components/Nav/ProfileDrawer';
|
||||||
|
import { Splash } from '@/components/Splash/Splash';
|
||||||
|
import { Thingtime } from '@/components/Thingtime/Thingtime';
|
||||||
|
import { ThingtimeDemo } from '@/components/Thingtime/ThingtimeDemo';
|
||||||
|
import { useThingtime } from '@/components/Thingtime/useThingtime';
|
||||||
|
import { GradientPath } from '@/gp/GradientPath';
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
const { thingtime } = useThingtime();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex alignItems="center" justifyContent="center" flexDirection="column" maxWidth="100%">
|
||||||
|
{/* <Box paddingTop={200}></Box> */}
|
||||||
|
<Splash></Splash>
|
||||||
|
<Thingtime
|
||||||
|
chakras={{
|
||||||
|
marginBottom: 200
|
||||||
|
}}
|
||||||
|
width="600px"
|
||||||
|
path="Content"
|
||||||
|
valuePl={0}
|
||||||
|
thing={thingtime?.['Content']}
|
||||||
|
></Thingtime>
|
||||||
|
<ThingtimeDemo></ThingtimeDemo>
|
||||||
|
<Thingtime thing={thingtime} chakras={{ marginY: 200 }} width="600px"></Thingtime>
|
||||||
|
<ProfileDrawer></ProfileDrawer>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
14
next/src/remix_routes/api/v1/login.tsx
Normal file
14
next/src/remix_routes/api/v1/login.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export const action = async ({ request }) => {
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
message: 'Hello, World!'
|
||||||
|
},
|
||||||
|
cache: {
|
||||||
|
revalidate: 60
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
15
next/src/remix_routes/login.tsx
Normal file
15
next/src/remix_routes/login.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { Login } from '@/components/Login/Login';
|
||||||
|
|
||||||
|
export default function login() {
|
||||||
|
const template = (
|
||||||
|
<>
|
||||||
|
<Flex alignItems="center" justifyContent="center" width="100%" height="100%" minHeight="100vh">
|
||||||
|
<Login></Login>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
43
next/src/remix_routes/ode.tsx
Normal file
43
next/src/remix_routes/ode.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { Box, Center, Flex } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { ProfileDrawer } from '@/components/Nav/ProfileDrawer';
|
||||||
|
import { Splash } from '@/components/Splash/Splash';
|
||||||
|
import { Thingtime } from '@/components/Thingtime/Thingtime';
|
||||||
|
import { ThingtimeDemo } from '@/components/Thingtime/ThingtimeDemo';
|
||||||
|
import { useThingtime } from '@/components/Thingtime/useThingtime';
|
||||||
|
import { GradientPath } from '@/gp/GradientPath';
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
const { thingtime } = useThingtime();
|
||||||
|
|
||||||
|
const ode = {
|
||||||
|
'Ode to Thingtime': `
|
||||||
|
|
||||||
|
In the infinite expanses of the digital universe, there exists a radiant realm, a pixel paradise known as Thing Time. Here, a cosmic canvas unfurls, inviting explorers from far and wide to etch their ideas, thoughts, and dreams into its ever-changing tapestry.
|
||||||
|
|
||||||
|
As you step into Thing Time, you find yourself in a world of binary bliss, where every pixel pulses with possibility. The air thrums with the hum of shared thought, each byte bending and reshaping to mirror the collective wisdom of countless minds.
|
||||||
|
|
||||||
|
The landscape morphs and molds to the whims of its inhabitants, each creating, collaborating, and curating their unique contributions. Trees of code reach their branches high into the cloud, blooming with arrays of astral ideas, their roots deep in the fertile ground of shared understanding.
|
||||||
|
|
||||||
|
Time here does not tick in minutes or hours, but in the rhythm of creation, inspiration, and exploration. And it's not just about your time - it's about our time, a communal clock synchronizing the heartbeats of thinkers, dreamers, and doers.
|
||||||
|
|
||||||
|
In Thing Time, we don't just observe - we participate, contribute, and shape. We paint with the brush of JavaScript, sketching out our thoughts in lines of lucid links and hyper harmonies. Together, we weave stories and knowledge into a vivid, ever-evolving tapestry of shared experience.
|
||||||
|
|
||||||
|
That's Thing Time - a dance of data, a symphony of syntax, a carnival of creation. An epic element of the digital era, forever inviting you to join in the melody of shared imagination. This is your call to the creative, a beacon in the binary, your invite to the infinite. This is Thing Time. Welcome.
|
||||||
|
|
||||||
|
- Codex (A ChatGPT 4.0 Session)
|
||||||
|
|
||||||
|
`
|
||||||
|
// "- ChatGPT 4.0 hidden": " ",
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex alignItems="center" justifyContent="center" flexDirection="column" maxWidth="100%">
|
||||||
|
{/* <Box paddingTop={200}></Box> */}
|
||||||
|
{/* <Splash></Splash> */}
|
||||||
|
<Center width="100vw" maxWidth="100%" minHeight="100vh" paddingY={100}>
|
||||||
|
<Thingtime width="700px" valuePl={0} thing={ode}></Thingtime>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
61
next/src/remix_routes/rainbow.$.tsx
Normal file
61
next/src/remix_routes/rainbow.$.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
import { useLocation } from '@remix-run/react';
|
||||||
|
|
||||||
|
import { Splash } from '@/components/Splash/Splash';
|
||||||
|
import { useThingtime } from '@/components/Thingtime/useThingtime';
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
const location = useLocation();
|
||||||
|
const { pathname } = location;
|
||||||
|
|
||||||
|
const strippedPathname = React.useMemo(() => {
|
||||||
|
// modify /rainbow/* to just *
|
||||||
|
|
||||||
|
const ret = pathname.split('/')[2];
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
return decodeURI(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'rainbow';
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
const texts = React.useMemo(() => {
|
||||||
|
const ret = [strippedPathname];
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}, [strippedPathname]);
|
||||||
|
|
||||||
|
console.log('texts', texts);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
'::selection': {
|
||||||
|
background: 'transparent'
|
||||||
|
},
|
||||||
|
'::-moz-selection': {
|
||||||
|
background: 'transparent'
|
||||||
|
},
|
||||||
|
'*': {
|
||||||
|
'::selection': {
|
||||||
|
background: 'transparent'
|
||||||
|
},
|
||||||
|
'::-moz-selection': {
|
||||||
|
background: 'transparent'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'* grammarly-extension': {
|
||||||
|
display: 'none !important'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
flexDirection="column"
|
||||||
|
maxWidth="100%"
|
||||||
|
>
|
||||||
|
<Splash texts={texts} ce={true}></Splash>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
20
next/tailwind.config.ts
Normal file
20
next/tailwind.config.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import type { Config } from "tailwindcss";
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
content: [
|
||||||
|
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
backgroundImage: {
|
||||||
|
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||||
|
"gradient-conic":
|
||||||
|
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
|
export default config;
|
26
next/tsconfig.json
Normal file
26
next/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
@ -4,10 +4,11 @@
|
|||||||
"description": "Thing Time",
|
"description": "Thing Time",
|
||||||
"main": "none",
|
"main": "none",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"app": "npm run dev --prefix app",
|
"remix": "npm run dev --prefix remix",
|
||||||
|
"next": "npm run dev --prefix next",
|
||||||
"api": "npm run dev --prefix api",
|
"api": "npm run dev --prefix api",
|
||||||
"build": "npm run build --prefix app",
|
"build": "npm run build --prefix next",
|
||||||
"postinstall": "pnpm i --prefix app ; pnpm i --prefix=api"
|
"postinstall": "pnpm i --prefix next ; pnpm i --prefix=api"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
0
app/.gitignore → remix/.gitignore
vendored
0
app/.gitignore → remix/.gitignore
vendored
@ -3,7 +3,7 @@
|
|||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "remix vite:build",
|
"build": "remix vite:build",
|
||||||
"dev": "remix vite:dev",
|
"dev": "remix vite:dev --host 127.0.0.1",
|
||||||
"lint": "eslint --ext .js,.ts,.jsx,.tsx .",
|
"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}\""
|
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,scss,md}\""
|
||||||
@ -20,6 +20,7 @@
|
|||||||
"@remix-run/serve": "^2.8.1",
|
"@remix-run/serve": "^2.8.1",
|
||||||
"@vercel/analytics": "^0.1.11",
|
"@vercel/analytics": "^0.1.11",
|
||||||
"@vercel/remix": "^1.15.0",
|
"@vercel/remix": "^1.15.0",
|
||||||
|
"axios": "^1.6.8",
|
||||||
"draft-js": "^0.11.7",
|
"draft-js": "^0.11.7",
|
||||||
"emojis-list": "^3.0.0",
|
"emojis-list": "^3.0.0",
|
||||||
"emotion": "^11.0.0",
|
"emotion": "^11.0.0",
|
BIN
remix/public/favicon.ico
Normal file
BIN
remix/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
@ -2,13 +2,13 @@
|
|||||||
* @type {import('@remix-run/dev').AppConfig}
|
* @type {import('@remix-run/dev').AppConfig}
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ignoredRouteFiles: ["**/.*"],
|
ignoredRouteFiles: ['**/.*'],
|
||||||
// future: {
|
// future: {
|
||||||
// unstable_dev: true
|
// unstable_dev: true
|
||||||
// appServerPort: 3999
|
// appServerPort: 3999
|
||||||
// }
|
// }
|
||||||
appDirectory: "src",
|
appDirectory: 'src'
|
||||||
// assetsBuildDirectory: "public/build",
|
// assetsBuildDirectory: "public/build",
|
||||||
// serverBuildPath: "build/index.js",
|
// serverBuildPath: "build/index.js",
|
||||||
// publicPath: "/build/",
|
// publicPath: "/build/",
|
||||||
}
|
};
|
68
remix/src/Providers/Chakra/colors.tsx
Normal file
68
remix/src/Providers/Chakra/colors.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
const greys = {
|
||||||
|
light: "#F1F1F3",
|
||||||
|
lightt: "#E7E6E8",
|
||||||
|
medium: "#E0E0E0",
|
||||||
|
dark: "#BDBDBD",
|
||||||
|
}
|
||||||
|
|
||||||
|
const grey = "#F1F1F3"
|
||||||
|
|
||||||
|
const g = {
|
||||||
|
gray: grey,
|
||||||
|
grey,
|
||||||
|
grays: greys,
|
||||||
|
greys,
|
||||||
|
}
|
||||||
|
|
||||||
|
// for bad spellers
|
||||||
|
g.gray = g.grey
|
||||||
|
g.grays = g.greys
|
||||||
|
|
||||||
|
export const colors = {
|
||||||
|
white: "#FFFFFF",
|
||||||
|
...g,
|
||||||
|
black: "#000000",
|
||||||
|
// all colors of the chakras
|
||||||
|
chakras: {
|
||||||
|
root: "#C62828",
|
||||||
|
sacral: "#FF7043",
|
||||||
|
solarPlexus: "#FFEE58",
|
||||||
|
heart: "#66BB6A",
|
||||||
|
throat: "#42A5F5",
|
||||||
|
thirdEye: "#5C6BC0",
|
||||||
|
crown: "#AB47BC",
|
||||||
|
red: "#C62828",
|
||||||
|
orange: "#FF7043",
|
||||||
|
yellow: "#FFEE58",
|
||||||
|
green: "#66BB6A",
|
||||||
|
blue: "#42A5F5",
|
||||||
|
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",
|
||||||
|
orange: "#FF7F00",
|
||||||
|
yellow: "#FFFF00",
|
||||||
|
green: "#00FF00",
|
||||||
|
blue: "#0000FF",
|
||||||
|
indigo: "#4B0082",
|
||||||
|
violet: "#8F00FF",
|
||||||
|
},
|
||||||
|
}
|
10
remix/src/Providers/Chakra/space.tsx
Normal file
10
remix/src/Providers/Chakra/space.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const spaceObj = {}
|
||||||
|
|
||||||
|
const start = 0
|
||||||
|
const end = 9999
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
spaceObj[i] = `${i * 0.25}rem`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const space = spaceObj
|
152
remix/src/Providers/Chakra/theme.tsx
Normal file
152
remix/src/Providers/Chakra/theme.tsx
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import {
|
||||||
|
extendTheme,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputField,
|
||||||
|
NumberInputStepper,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
} from "@chakra-ui/react"
|
||||||
|
|
||||||
|
import { colors } from "./colors"
|
||||||
|
import { space } from "./space"
|
||||||
|
|
||||||
|
export const theme = extendTheme({
|
||||||
|
colors,
|
||||||
|
// edit Input defaultProps
|
||||||
|
space,
|
||||||
|
styles: {
|
||||||
|
global: {
|
||||||
|
// make all elements padding and margin animate
|
||||||
|
"*": {
|
||||||
|
transition: "padding 0.2s ease, margin 0.2s ease-out",
|
||||||
|
},
|
||||||
|
// make all elements have a transparent focus border
|
||||||
|
"input:focus": {
|
||||||
|
boxShadow: "none !important",
|
||||||
|
borderColor: "transparent !important",
|
||||||
|
},
|
||||||
|
// make all elements have a transparent focus border
|
||||||
|
"textarea:focus": {
|
||||||
|
boxShadow: "none !important",
|
||||||
|
borderColor: "transparent !important",
|
||||||
|
},
|
||||||
|
// make all elements have a transparent focus border
|
||||||
|
"select:focus": {
|
||||||
|
boxShadow: "none !important",
|
||||||
|
borderColor: "transparent !important",
|
||||||
|
},
|
||||||
|
// make all elements have a transparent focus border
|
||||||
|
"button:focus": {
|
||||||
|
boxShadow: "none !important",
|
||||||
|
borderColor: "transparent !important",
|
||||||
|
},
|
||||||
|
// make all elements have a transparent focus border
|
||||||
|
"div:focus": {
|
||||||
|
boxShadow: "none !important",
|
||||||
|
borderColor: "transparent !important",
|
||||||
|
},
|
||||||
|
// make all elements have a transparent focus border
|
||||||
|
"a:focus": {
|
||||||
|
boxShadow: "none !important",
|
||||||
|
borderColor: "transparent !important",
|
||||||
|
},
|
||||||
|
// make all elements have a transparent focus border
|
||||||
|
"span:focus": {
|
||||||
|
boxShadow: "none !important",
|
||||||
|
borderColor: "transparent !important",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Input: {
|
||||||
|
defaultProps: {
|
||||||
|
// focusBorderColor: "transparent",
|
||||||
|
// outline: "0px solid",
|
||||||
|
// border: "0px solid",
|
||||||
|
},
|
||||||
|
baseStyle: {
|
||||||
|
// "padding-inline-start": "0px",
|
||||||
|
field: {
|
||||||
|
// "padding-inline-start": "0px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Select: {
|
||||||
|
baseStyle: {
|
||||||
|
field: {
|
||||||
|
// "padding-inline-start": "0px",
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
// height: "8px",
|
||||||
|
width: "14px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Switch: {
|
||||||
|
baseStyle: {
|
||||||
|
track: {
|
||||||
|
background: "grays.medium",
|
||||||
|
_checked: {
|
||||||
|
background: "chakras.throat",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NumberInput: {
|
||||||
|
baseStyle: {
|
||||||
|
field: {
|
||||||
|
width: "auto",
|
||||||
|
// border: "none",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
Switch.defaultProps = {
|
||||||
|
...Switch.defaultProps,
|
||||||
|
as: "div",
|
||||||
|
}
|
||||||
|
|
||||||
|
Select.defaultProps = {
|
||||||
|
...Select.defaultProps,
|
||||||
|
focusBorderColor: "transparent",
|
||||||
|
outline: "0px solid",
|
||||||
|
border: "none",
|
||||||
|
ps: "0px",
|
||||||
|
px: "0px",
|
||||||
|
sx: {
|
||||||
|
paddingInlineStart: "0px",
|
||||||
|
paddingInlineEnd: "24px",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberInput.defaultProps = {
|
||||||
|
...NumberInput.defaultProps,
|
||||||
|
variant: "unstyled",
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberInputField.defaultProps = {
|
||||||
|
...NumberInputField.defaultProps,
|
||||||
|
height: "100%",
|
||||||
|
pr: 3,
|
||||||
|
fontSize: "inherit",
|
||||||
|
// variant: "unstyled",
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberInputStepper.defaultProps = {
|
||||||
|
...NumberInputStepper.defaultProps,
|
||||||
|
border: "none",
|
||||||
|
color: "grays.medium",
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberIncrementStepper.defaultProps = {
|
||||||
|
...NumberIncrementStepper.defaultProps,
|
||||||
|
border: "none",
|
||||||
|
}
|
||||||
|
NumberDecrementStepper.defaultProps = {
|
||||||
|
...NumberDecrementStepper.defaultProps,
|
||||||
|
border: "none",
|
||||||
|
}
|
737
remix/src/Providers/ThingtimeProvider.tsx
Normal file
737
remix/src/Providers/ThingtimeProvider.tsx
Normal file
@ -0,0 +1,737 @@
|
|||||||
|
import React, { createContext } from "react"
|
||||||
|
import flatted, { parse, stringify } from "flatted"
|
||||||
|
import { Subject } from "rxjs"
|
||||||
|
|
||||||
|
import { sanitise } from "../functions/sanitise"
|
||||||
|
import { smarts } from "../smarts"
|
||||||
|
|
||||||
|
export interface ThingtimeContextInterface {
|
||||||
|
thingtime: any
|
||||||
|
setThingtime: any
|
||||||
|
getThingtime: any
|
||||||
|
thingtimeRef: any
|
||||||
|
loading: boolean
|
||||||
|
events: Subject<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ThingtimeContext = createContext<ThingtimeContextInterface | null>(
|
||||||
|
null
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.smarts = smarts
|
||||||
|
window.flatted = {
|
||||||
|
parse,
|
||||||
|
stringify,
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
const force = {
|
||||||
|
settings: {
|
||||||
|
types: {
|
||||||
|
javascript: {
|
||||||
|
any: {
|
||||||
|
type: "any",
|
||||||
|
value: () => {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
object: {
|
||||||
|
type: "object",
|
||||||
|
value: () => {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
array: {
|
||||||
|
type: "array",
|
||||||
|
value: () => {
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
},
|
||||||
|
string: {
|
||||||
|
type: "string",
|
||||||
|
value: () => {
|
||||||
|
return ""
|
||||||
|
},
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
type: "number",
|
||||||
|
value: () => {
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
boolean: {
|
||||||
|
type: "boolean",
|
||||||
|
value: () => {
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
function: {
|
||||||
|
type: "function",
|
||||||
|
value: () => {
|
||||||
|
return () => {}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
"Thingtime Logo": {
|
||||||
|
type: "chakra",
|
||||||
|
value: {
|
||||||
|
type: "chakra",
|
||||||
|
chakra: "Box",
|
||||||
|
props: {
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
rawChildren: ["🌈 Thingtime"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Violet Container Centered": {
|
||||||
|
name: "Violet Container Centered",
|
||||||
|
type: "chakra",
|
||||||
|
icon: "💜",
|
||||||
|
wrap: "children",
|
||||||
|
value: {
|
||||||
|
name: "Violet Container Centered",
|
||||||
|
type: "chakra",
|
||||||
|
chakra: "Center",
|
||||||
|
props: {
|
||||||
|
bg: "#AB47BC",
|
||||||
|
padding: 4,
|
||||||
|
borderRadius: 12,
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Indigo Container Centered": {
|
||||||
|
name: "Indigo Container Centered",
|
||||||
|
type: "chakra",
|
||||||
|
icon: "🩷",
|
||||||
|
wrap: "children",
|
||||||
|
value: {
|
||||||
|
name: "Indigo Container Centered",
|
||||||
|
type: "chakra",
|
||||||
|
chakra: "Center",
|
||||||
|
props: {
|
||||||
|
bg: "#5C6BC0",
|
||||||
|
padding: 4,
|
||||||
|
borderRadius: 12,
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Blue Container Centered": {
|
||||||
|
name: "Blue Container Centered",
|
||||||
|
type: "chakra",
|
||||||
|
icon: "💙",
|
||||||
|
wrap: "children",
|
||||||
|
value: {
|
||||||
|
name: "Blue Container Centered",
|
||||||
|
type: "chakra",
|
||||||
|
chakra: "Center",
|
||||||
|
props: {
|
||||||
|
bg: "#42A5F5",
|
||||||
|
padding: 4,
|
||||||
|
borderRadius: 12,
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Green Container Centered": {
|
||||||
|
name: "Green Container Centered",
|
||||||
|
type: "chakra",
|
||||||
|
icon: "💚",
|
||||||
|
wrap: "children",
|
||||||
|
value: {
|
||||||
|
name: "Green Container Centered",
|
||||||
|
type: "chakra",
|
||||||
|
chakra: "Center",
|
||||||
|
props: {
|
||||||
|
bg: "#66BB6A",
|
||||||
|
padding: 4,
|
||||||
|
borderRadius: 12,
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Yellow Container Centered": {
|
||||||
|
name: "Yellow Container Centered",
|
||||||
|
type: "chakra",
|
||||||
|
icon: "💛",
|
||||||
|
wrap: "children",
|
||||||
|
value: {
|
||||||
|
name: "Yellow Container Centered",
|
||||||
|
type: "chakra",
|
||||||
|
chakra: "Center",
|
||||||
|
props: {
|
||||||
|
bg: "#FFEE58",
|
||||||
|
padding: 4,
|
||||||
|
borderRadius: 12,
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Orange Container Centered": {
|
||||||
|
name: "Orange Container Centered",
|
||||||
|
type: "chakra",
|
||||||
|
icon: "🧡",
|
||||||
|
wrap: "children",
|
||||||
|
value: {
|
||||||
|
name: "Orange Container Centered",
|
||||||
|
type: "chakra",
|
||||||
|
chakra: "Center",
|
||||||
|
props: {
|
||||||
|
bg: "#FF7043",
|
||||||
|
padding: 4,
|
||||||
|
borderRadius: 12,
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Red Container Centered": {
|
||||||
|
name: "Red Container Centered",
|
||||||
|
type: "chakra",
|
||||||
|
icon: "❤️",
|
||||||
|
wrap: "children",
|
||||||
|
value: {
|
||||||
|
name: "Red Container Centered",
|
||||||
|
type: "chakra",
|
||||||
|
chakra: "Center",
|
||||||
|
props: {
|
||||||
|
bg: "#C62828",
|
||||||
|
padding: 4,
|
||||||
|
borderRadius: 12,
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Left Aligned": {
|
||||||
|
type: "chakra",
|
||||||
|
value: {
|
||||||
|
type: "chakra",
|
||||||
|
chakra: "Flex",
|
||||||
|
props: {
|
||||||
|
mr: "auto",
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Right Aligned": {
|
||||||
|
type: "chakra",
|
||||||
|
value: {
|
||||||
|
type: "chakra",
|
||||||
|
chakra: "Flex",
|
||||||
|
props: {
|
||||||
|
ml: "auto",
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// undoLimit: 999,
|
||||||
|
// commander: {
|
||||||
|
// nav: {
|
||||||
|
// commanderActive: false,
|
||||||
|
// clearCommanderOnToggle: true,
|
||||||
|
// clearCommanderContextOnToggle: true,
|
||||||
|
// hideSuggestionsOnToggle: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
// Content: {
|
||||||
|
// hidden1: "Edit this to your heart's desire.",
|
||||||
|
// "How?": "Just search for Content and edit the value to whatever you want.",
|
||||||
|
// "Example:": `Content = New Content!
|
||||||
|
// Content.Nested Content = New Nested Content!
|
||||||
|
// `,
|
||||||
|
// },
|
||||||
|
version: 24,
|
||||||
|
}
|
||||||
|
|
||||||
|
const newVersionData = {
|
||||||
|
Content: {
|
||||||
|
hidden1: "Edit this to your heart's desire.",
|
||||||
|
"How?": "Just search for Content and edit the value to whatever you want.",
|
||||||
|
"Example:": `Content = New Content!
|
||||||
|
Content.Nested Content = New Nested Content!
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
settings: {
|
||||||
|
commander: {
|
||||||
|
nav: {
|
||||||
|
commanderActive: false,
|
||||||
|
clearCommanderOnToggle: true,
|
||||||
|
clearCommanderContextOnToggle: true,
|
||||||
|
hideSuggestionsOnToggle: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Content: {
|
||||||
|
hidden1: "Edit this to your heart's desire.",
|
||||||
|
"How?": "Just search for Content and edit the value to whatever you want.",
|
||||||
|
"Example:": `
|
||||||
|
Content = New Content!
|
||||||
|
Content.Nested Content = New Nested Content!
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// const initialThingtime = createInitialThingtime()
|
||||||
|
const initialThingtime = smarts.merge(initialValues, force)
|
||||||
|
|
||||||
|
// initialise thingtime
|
||||||
|
initialThingtime.thingtime = initialThingtime
|
||||||
|
initialThingtime.tt = initialThingtime
|
||||||
|
|
||||||
|
export const ThingtimeProvider = (props: any): JSX.Element => {
|
||||||
|
const [thingtimeReference, rawSet] = React.useState()
|
||||||
|
|
||||||
|
const thingtimeRef = React.useRef(thingtimeReference)
|
||||||
|
const stateRef: any = React.useRef({
|
||||||
|
c: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [loading, setLoading] = React.useState(true)
|
||||||
|
|
||||||
|
const [events, setEvents] = React.useState(null)
|
||||||
|
|
||||||
|
if (!events) {
|
||||||
|
setEvents(() => new Subject())
|
||||||
|
}
|
||||||
|
|
||||||
|
const set = React.useCallback((newThingtime, ignoreUndoRedo?: any) => {
|
||||||
|
const newThingtimeReference = {
|
||||||
|
...newThingtime,
|
||||||
|
}
|
||||||
|
|
||||||
|
newThingtimeReference.tt = newThingtimeReference
|
||||||
|
newThingtimeReference.thingtime = newThingtimeReference
|
||||||
|
|
||||||
|
// store undo/redo history
|
||||||
|
if (!ignoreUndoRedo) {
|
||||||
|
try {
|
||||||
|
console.log(
|
||||||
|
"ThingtimeProvider setting thingtime to localStorage",
|
||||||
|
newThingtimeReference
|
||||||
|
)
|
||||||
|
// setTimeout(() => {
|
||||||
|
const stringified = stringify(newThingtimeReference)
|
||||||
|
let undoHistory = []
|
||||||
|
try {
|
||||||
|
const undoHistoryString = window.localStorage.getItem("undoHistory")
|
||||||
|
const parsedUndoHistory = JSON.parse(undoHistoryString)
|
||||||
|
if (parsedUndoHistory instanceof Array) {
|
||||||
|
undoHistory = parsedUndoHistory
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
// if last undoHistory does not equal new undo history
|
||||||
|
if (undoHistory[undoHistory.length - 1]?.value !== stringified) {
|
||||||
|
try {
|
||||||
|
const limit = newThingtimeReference?.settings?.undoLimit || 999
|
||||||
|
|
||||||
|
if (undoHistory?.length > limit) {
|
||||||
|
undoHistory = undoHistory.slice(undoHistory.length - limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
undoHistory.push({
|
||||||
|
timestamp: Date.now(),
|
||||||
|
value: stringify(newThingtimeReference),
|
||||||
|
})
|
||||||
|
const undoHistoryNewString = JSON.stringify(undoHistory)
|
||||||
|
window.localStorage.setItem("undoHistory", undoHistoryNewString)
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("There was an error saving thingtime to localStorage")
|
||||||
|
}
|
||||||
|
const saveRedo = false
|
||||||
|
if (saveRedo) {
|
||||||
|
try {
|
||||||
|
console.log(
|
||||||
|
"ThingtimeProvider setting thingtime to localStorage",
|
||||||
|
newThingtimeReference
|
||||||
|
)
|
||||||
|
// setTimeout(() => {
|
||||||
|
const stringified = stringify(newThingtimeReference)
|
||||||
|
let redoHistory = []
|
||||||
|
try {
|
||||||
|
const redoHistoryString = window.localStorage.getItem("redoHistory")
|
||||||
|
const parsedRedoHistory = JSON.parse(redoHistoryString)
|
||||||
|
if (parsedRedoHistory instanceof Array) {
|
||||||
|
redoHistory = parsedRedoHistory
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
if (redoHistory[redoHistory.length - 1]?.value !== stringified) {
|
||||||
|
try {
|
||||||
|
const limit = newThingtimeReference?.settings?.redoLimit || 999
|
||||||
|
|
||||||
|
if (redoHistory?.length > limit) {
|
||||||
|
redoHistory = redoHistory.slice(redoHistory.length - limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
redoHistory.push({
|
||||||
|
timestamp: Date.now(),
|
||||||
|
value: stringify(newThingtimeReference),
|
||||||
|
})
|
||||||
|
const redoHistoryNewString = JSON.stringify(redoHistory)
|
||||||
|
window.localStorage.setItem("redoHistory", redoHistoryNewString)
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("There was an error saving thingtime to localStorage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rawSet(newThingtimeReference)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const setThingtime = React.useCallback(
|
||||||
|
(path, value) => {
|
||||||
|
// TODO: make this a lot safer
|
||||||
|
if (["thingtime", "tt"]?.includes(path)) {
|
||||||
|
if (value) {
|
||||||
|
set(value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newThingtime: any = thingtimeReference || {}
|
||||||
|
|
||||||
|
const paths = smarts.parsePropertyPath(path)
|
||||||
|
|
||||||
|
// find first parent where a path is undefined
|
||||||
|
// paths is array of path parts such as ["path1", "path2", "path3"]
|
||||||
|
// we want to create a new reference at the first object which has an undefined part of the path
|
||||||
|
// and is an object itself
|
||||||
|
// so that react will detect the change and re-render
|
||||||
|
// "path1" = { ...thingtime["path1"] } if path1.path2 undefined
|
||||||
|
// "path1.path2" = { ...thingtime["path1"]["path2"] } if path1.path2.path3 undefined
|
||||||
|
// "path1.path2.path3" = { ...thingtime["path1"]["path2"]["path3"] }
|
||||||
|
// etc
|
||||||
|
let done = false
|
||||||
|
paths.forEach((pathPart, index) => {
|
||||||
|
if (!done) {
|
||||||
|
const pathParts = paths.slice(0, index + 1)
|
||||||
|
const tmpPath = pathParts.join(".")
|
||||||
|
const parentPath = pathParts.slice(0, -1).join(".")
|
||||||
|
|
||||||
|
const valAtPath = smarts.getsmart(newThingtime, tmpPath)
|
||||||
|
|
||||||
|
if (parentPath) {
|
||||||
|
if (typeof valAtPath !== "object" || valAtPath === null) {
|
||||||
|
const parentVal = smarts.getsmart(newThingtime, parentPath)
|
||||||
|
if (typeof parentVal === "object") {
|
||||||
|
const newParent = Array.isArray(parentVal)
|
||||||
|
? [...parentVal]
|
||||||
|
: { ...parentVal }
|
||||||
|
smarts.setsmart(newThingtime, parentPath, newParent)
|
||||||
|
}
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: make thingtime settable
|
||||||
|
newThingtime.thingtime = newThingtime
|
||||||
|
newThingtime.tt = newThingtime
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"ThingtimeProvider setting newThingtime value at path",
|
||||||
|
'"' + path + '"',
|
||||||
|
"value: ",
|
||||||
|
value
|
||||||
|
)
|
||||||
|
|
||||||
|
smarts.setsmart(newThingtime, path, value)
|
||||||
|
|
||||||
|
set(newThingtime)
|
||||||
|
},
|
||||||
|
[thingtimeReference, set]
|
||||||
|
)
|
||||||
|
|
||||||
|
const getThingtime = React.useCallback(
|
||||||
|
(...args) => {
|
||||||
|
const rawPath = args[0]
|
||||||
|
const path = rawPath
|
||||||
|
|
||||||
|
if (!path) {
|
||||||
|
return thingtimeReference
|
||||||
|
}
|
||||||
|
|
||||||
|
// do we need to sanitise?
|
||||||
|
// const path = sanitise(rawPath)
|
||||||
|
console.log("ThingtimeProvider getting thingtime at path", path)
|
||||||
|
// console.trace("Getting thingtime at path", path)
|
||||||
|
return smarts.getsmart(thingtimeReference, path)
|
||||||
|
},
|
||||||
|
[thingtimeReference]
|
||||||
|
)
|
||||||
|
|
||||||
|
const populatePaths = React.useCallback((obj, path, paths, seen = []) => {
|
||||||
|
try {
|
||||||
|
Object.keys(obj).forEach((key) => {
|
||||||
|
const val = obj[key]
|
||||||
|
const newPath = path ? `${path}${path ? "." : ""}${key}` : key
|
||||||
|
if (typeof val === "object") {
|
||||||
|
paths.push(newPath)
|
||||||
|
if (!seen?.includes(val)) {
|
||||||
|
seen.push(val)
|
||||||
|
populatePaths(val, newPath, paths, seen)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
paths.push(newPath)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const paths = React.useMemo(() => {
|
||||||
|
// const paths = ["tt", "thingtime", "."]
|
||||||
|
const paths = []
|
||||||
|
|
||||||
|
// populatePaths(thingtime, commandPath)
|
||||||
|
populatePaths(thingtimeReference, "", paths)
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}, [populatePaths, thingtimeReference])
|
||||||
|
|
||||||
|
// get thingtime from localstorage
|
||||||
|
React.useEffect(() => {
|
||||||
|
try {
|
||||||
|
const thingtimeFromLocalStorage = window.localStorage.getItem("thingtime")
|
||||||
|
|
||||||
|
if (thingtimeFromLocalStorage) {
|
||||||
|
const parsed = parse(thingtimeFromLocalStorage)
|
||||||
|
console.log(
|
||||||
|
"ThingtimeProvider thingtime restored from localstorage",
|
||||||
|
parsed
|
||||||
|
)
|
||||||
|
if (parsed) {
|
||||||
|
const localIsValid =
|
||||||
|
!parsed.version || parsed.version >= force.version
|
||||||
|
let newThingtime = smarts.merge(force, initialThingtime)
|
||||||
|
if (localIsValid) {
|
||||||
|
newThingtime = smarts.merge(parsed, newThingtime)
|
||||||
|
} else {
|
||||||
|
const withVersionUpdates = smarts.merge(newVersionData, parsed)
|
||||||
|
newThingtime = smarts.merge(force, withVersionUpdates)
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
"ThingtimeProvider restoring thingtime from localStorage",
|
||||||
|
newThingtime
|
||||||
|
)
|
||||||
|
set(newThingtime)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
set(initialThingtime)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("There was an error getting thingtime from localStorage")
|
||||||
|
}
|
||||||
|
setLoading(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// thingtime change listener
|
||||||
|
React.useEffect(() => {
|
||||||
|
try {
|
||||||
|
window.setThingtime = setThingtime
|
||||||
|
window.getThingtime = getThingtime
|
||||||
|
window.thingtime = thingtimeReference
|
||||||
|
window.tt = thingtimeReference
|
||||||
|
window.events = events
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stateRef.current.initialized) {
|
||||||
|
try {
|
||||||
|
console.log(
|
||||||
|
"ThingtimeProvider setting thingtime to localStorage",
|
||||||
|
thingtimeReference
|
||||||
|
)
|
||||||
|
// setTimeout(() => {
|
||||||
|
const stringified = stringify(thingtimeReference)
|
||||||
|
window.localStorage.setItem("thingtime", stringified)
|
||||||
|
// }, 600)
|
||||||
|
} catch (err) {
|
||||||
|
console.error("There was an error saving thingtime to localStorage")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stateRef.current.initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
thingtimeRef.current = thingtimeReference
|
||||||
|
|
||||||
|
const keyListener = (e) => {
|
||||||
|
// if ctrl + z, restore thingtime from localstorage history
|
||||||
|
|
||||||
|
console.log("ThingtimeProvider listened to key event e?.key", e?.key)
|
||||||
|
console.log(
|
||||||
|
"ThingtimeProvider listened to key event e?.ctrlKey",
|
||||||
|
e?.ctrlKey
|
||||||
|
)
|
||||||
|
console.log(
|
||||||
|
"ThingtimeProvider listened to key event e?.shiftKey",
|
||||||
|
e?.shiftKey
|
||||||
|
)
|
||||||
|
console.log(
|
||||||
|
"ThingtimeProvider listened to key event e?.metaKey",
|
||||||
|
e?.metaKey
|
||||||
|
)
|
||||||
|
|
||||||
|
if ((e?.ctrlKey || e?.metaKey) && e?.key === "z") {
|
||||||
|
e?.preventDefault()
|
||||||
|
|
||||||
|
console.log("ThingtimeProvider detected undo/redo request")
|
||||||
|
|
||||||
|
if (e.shiftKey) {
|
||||||
|
// redo
|
||||||
|
console.log("ThingtimeProvider redo")
|
||||||
|
const redoHistoryString = window.localStorage.getItem("redoHistory")
|
||||||
|
const parsedRedoHistory = JSON.parse(redoHistoryString)
|
||||||
|
if (parsedRedoHistory instanceof Array) {
|
||||||
|
const last = parsedRedoHistory[parsedRedoHistory.length - 1]
|
||||||
|
if (last) {
|
||||||
|
const parsed = parse(last.value)
|
||||||
|
if (parsed) {
|
||||||
|
// remove restored state from history
|
||||||
|
// const currentHistory = parsedRedoHistory.pop()
|
||||||
|
parsedRedoHistory.pop()
|
||||||
|
// parsedRedoHistory.push(currentHistory)
|
||||||
|
const newRedoHistoryString = JSON.stringify(parsedRedoHistory)
|
||||||
|
window.localStorage.setItem("redoHistory", newRedoHistoryString)
|
||||||
|
|
||||||
|
// save old/current state to undo history
|
||||||
|
let undoHistory = []
|
||||||
|
try {
|
||||||
|
const undoHistoryString =
|
||||||
|
window.localStorage.getItem("undoHistory")
|
||||||
|
const parsedUndoHistory = JSON.parse(undoHistoryString)
|
||||||
|
if (parsedUndoHistory instanceof Array) {
|
||||||
|
undoHistory = parsedUndoHistory
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
undoHistory.push({
|
||||||
|
timestamp: Date.now(),
|
||||||
|
value: stringify(thingtimeReference),
|
||||||
|
})
|
||||||
|
const undoHistoryNewString = JSON.stringify(undoHistory)
|
||||||
|
window.localStorage.setItem(
|
||||||
|
"undoHistory",
|
||||||
|
undoHistoryNewString
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
const newThingtime = parsed
|
||||||
|
set(newThingtime, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// undo
|
||||||
|
console.log("ThingtimeProvider undo")
|
||||||
|
try {
|
||||||
|
const undoHistoryString = window.localStorage.getItem("undoHistory")
|
||||||
|
const parsedUndoHistory = JSON.parse(undoHistoryString)
|
||||||
|
if (parsedUndoHistory instanceof Array) {
|
||||||
|
const last = parsedUndoHistory[parsedUndoHistory.length - 2]
|
||||||
|
if (last) {
|
||||||
|
const parsed = parse(last.value)
|
||||||
|
if (parsed) {
|
||||||
|
// remove restored state from history
|
||||||
|
|
||||||
|
const currentHistory = parsedUndoHistory.pop()
|
||||||
|
parsedUndoHistory.pop()
|
||||||
|
parsedUndoHistory.push(currentHistory)
|
||||||
|
|
||||||
|
const newUndoHistoryString = JSON.stringify(parsedUndoHistory)
|
||||||
|
window.localStorage.setItem(
|
||||||
|
"undoHistory",
|
||||||
|
newUndoHistoryString
|
||||||
|
)
|
||||||
|
|
||||||
|
// save old/current state to redo history
|
||||||
|
let redoHistory = []
|
||||||
|
try {
|
||||||
|
const redoHistoryString =
|
||||||
|
window.localStorage.getItem("redoHistory")
|
||||||
|
const parsedRedoHistory = JSON.parse(redoHistoryString)
|
||||||
|
if (parsedRedoHistory instanceof Array) {
|
||||||
|
redoHistory = parsedRedoHistory
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const newValue = stringify(thingtimeReference)
|
||||||
|
// if last history is not the same as new history
|
||||||
|
redoHistory.push({
|
||||||
|
timestamp: Date.now(),
|
||||||
|
value: newValue,
|
||||||
|
})
|
||||||
|
const redoHistoryNewString = JSON.stringify(redoHistory)
|
||||||
|
window.localStorage.setItem(
|
||||||
|
"redoHistory",
|
||||||
|
redoHistoryNewString
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
const newThingtime = parsed
|
||||||
|
set(newThingtime, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("keydown", keyListener)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", keyListener)
|
||||||
|
}
|
||||||
|
}, [setThingtime, events, getThingtime, thingtimeReference, set])
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
thingtime: thingtimeReference,
|
||||||
|
setThingtime,
|
||||||
|
getThingtime,
|
||||||
|
thingtimeRef,
|
||||||
|
paths,
|
||||||
|
loading,
|
||||||
|
events,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThingtimeContext.Provider value={value}>
|
||||||
|
{props?.children}
|
||||||
|
</ThingtimeContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
28
remix/src/api/v1/login/Login.tsx
Normal file
28
remix/src/api/v1/login/Login.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useFetcher } from '@remix-run/react';
|
||||||
|
|
||||||
|
export const useLogin = () => {
|
||||||
|
const fetcher = useFetcher();
|
||||||
|
|
||||||
|
const login = React.useCallback(async (email, password) => {
|
||||||
|
try {
|
||||||
|
fetcher.submit(
|
||||||
|
{ email, password },
|
||||||
|
{
|
||||||
|
method: 'post',
|
||||||
|
action: '/api/v1/login'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// const response = await axios.post('/api/v1/login', { email, password });
|
||||||
|
// console.log('nik response', response.data);
|
||||||
|
// return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { login };
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user