Added next and remix directories main

This commit is contained in:
Nikolaj Frey 2024-04-20 23:43:55 +10:00
parent d2bfd8f991
commit 4668eba03d
151 changed files with 9579 additions and 9465 deletions

3
.eslintrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
import React from 'react';
import { ThingtimeURL } from '~/components/Thingtime/ThingtimeURL';
export default function Index() {
return <ThingtimeURL></ThingtimeURL>;
}

View File

@ -1,3 +0,0 @@
export async function loader({ request }) {
return { message: 'Hello, World!' };
}

3
next/.eslintrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

36
next/.gitignore vendored Normal file
View 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
View 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
View File

@ -0,0 +1,3 @@
interface EventTarget {
value: any
}

4
next/next.config.mjs Normal file
View File

@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;

65
next/package.json Normal file
View 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
View File

@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

1
next/public/next.svg Normal file
View 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
View 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
View 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
View 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>
);
}

View 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);
}
}

View 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>;
}

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
</>
);
};

View 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>
);
};

View 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> */}
</>
);
};

View 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>
);
};

View 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>
</>
);
};

View 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>
);
};

View 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>
</>
);
};

View 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);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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 users 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>
);
};

View 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>
);
};

View 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;
};

View 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>
);
};

View 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>;
};

View 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;
}
};

View 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
View 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);
}

View 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
View File

@ -0,0 +1,4 @@
import { ThingtimeURL } from '@/components/Thingtime/ThingtimeURL';
export default function Index() {
return <ThingtimeURL></ThingtimeURL>;
}

View 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>
}

View 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"
}

View 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>
);
}

View 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>;
};

View 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;
};

View 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
}
};
};

View 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>
);
}

View 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
}
};
};

View 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;
}

View 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>
);
}

View 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
View 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
View 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"]
}

View File

@ -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",

View File

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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/",
} };

View 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",
},
}

View 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

View 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",
}

View 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>
)
}

View 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