Spaces:
Paused
Paused
matt HOFFNER
commited on
Commit
•
3c3f089
1
Parent(s):
65ba288
init
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .eslintrc +6 -0
- .gitignore +37 -0
- .npmrc +1 -0
- Dockerfile +60 -0
- README.md +26 -10
- _types/compilerTypes.ts +9 -0
- _types/editorTypes.ts +23 -0
- additional.d.ts +21 -0
- codegen.yml +12 -0
- components/Avatar.tsx +57 -0
- components/Dropdown.tsx +77 -0
- components/Header.tsx +20 -0
- components/Loader.tsx +64 -0
- components/Modals/AuthModal.tsx +73 -0
- components/Modals/RootModal.tsx +47 -0
- components/Modals/SettingsModal.tsx +196 -0
- components/Modals/TemplateModal.tsx +65 -0
- components/Modals/config.ts +7 -0
- components/Playground/ConsoleLog.tsx +47 -0
- components/Playground/Footer.tsx +49 -0
- components/Playground/Header.tsx +27 -0
- components/Playground/Iframe.tsx +101 -0
- components/Playground/IframeErrorScreen.tsx +9 -0
- components/Playground/IframeLoaderScreen.tsx +32 -0
- components/Playground/InputCodeTab.tsx +36 -0
- components/Playground/Monaco.tsx +34 -0
- components/Playground/Pane.tsx +41 -0
- components/Playground/index.tsx +170 -0
- components/Skeleton/TemplateSelectionSkeleton.tsx +19 -0
- constants/icon.tsx +18 -0
- constants/index.ts +2 -0
- constants/monacoOptions.ts +16 -0
- constants/templates.ts +271 -0
- editor.d.ts +2 -0
- esbuild/plugins/index.ts +2 -0
- esbuild/plugins/unpkg-fecth-plugin.ts +85 -0
- esbuild/plugins/unpkg-path-plugin.ts +31 -0
- graphql.schema.json +0 -0
- graphql/definitions/auth.definition.ts +152 -0
- graphql/definitions/comment.definition.ts +97 -0
- graphql/definitions/notification.definition.ts +31 -0
- graphql/definitions/pageView.definition.ts +14 -0
- graphql/definitions/project.definition.ts +148 -0
- graphql/definitions/tag.definition.ts +12 -0
- graphql/definitions/upload.definition.ts +10 -0
- graphql/definitions/user.definitions.ts +54 -0
- graphql/generated/graphql.d.ts +563 -0
- hooks/index.ts +2 -0
- hooks/useMonaco.ts +117 -0
- hooks/useOutsideRef.ts +26 -0
.eslintrc
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"extends": ["next", "next/core-web-vitals"],
|
3 |
+
"rules": {
|
4 |
+
"@next/next/no-img-element": "off"
|
5 |
+
}
|
6 |
+
}
|
.gitignore
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
2 |
+
|
3 |
+
# dependencies
|
4 |
+
/node_modules
|
5 |
+
/.pnp
|
6 |
+
.pnp.js
|
7 |
+
|
8 |
+
# testing
|
9 |
+
/coverage
|
10 |
+
|
11 |
+
# next.js
|
12 |
+
/.next/
|
13 |
+
/out/
|
14 |
+
|
15 |
+
# production
|
16 |
+
/build
|
17 |
+
|
18 |
+
# misc
|
19 |
+
.DS_Store
|
20 |
+
*.pem
|
21 |
+
|
22 |
+
# debug
|
23 |
+
npm-debug.log*
|
24 |
+
yarn-debug.log*
|
25 |
+
yarn-error.log*
|
26 |
+
|
27 |
+
# local env files
|
28 |
+
.env
|
29 |
+
.env.local
|
30 |
+
.env.development.local
|
31 |
+
.env.test.local
|
32 |
+
.env.production.local
|
33 |
+
|
34 |
+
# vercel
|
35 |
+
.vercel
|
36 |
+
|
37 |
+
.idea
|
.npmrc
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
legacy-peer-deps=true
|
Dockerfile
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM node:18 AS base
|
2 |
+
|
3 |
+
# Install dependencies only when needed
|
4 |
+
FROM base AS deps
|
5 |
+
|
6 |
+
WORKDIR /app
|
7 |
+
|
8 |
+
# Install dependencies based on the preferred package manager
|
9 |
+
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
10 |
+
RUN \
|
11 |
+
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
12 |
+
elif [ -f package-lock.json ]; then npm ci; \
|
13 |
+
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
|
14 |
+
else echo "Lockfile not found." && exit 1; \
|
15 |
+
fi
|
16 |
+
|
17 |
+
# Uncomment the following lines if you want to use a secret at buildtime,
|
18 |
+
# for example to access your private npm packages
|
19 |
+
# RUN --mount=type=secret,id=HF_EXAMPLE_SECRET,mode=0444,required=true \
|
20 |
+
# $(cat /run/secrets/HF_EXAMPLE_SECRET)
|
21 |
+
|
22 |
+
# Rebuild the source code only when needed
|
23 |
+
FROM base AS builder
|
24 |
+
WORKDIR /app
|
25 |
+
COPY --from=deps /app/node_modules ./node_modules
|
26 |
+
COPY . .
|
27 |
+
|
28 |
+
# Next.js collects completely anonymous telemetry data about general usage.
|
29 |
+
# Learn more here: https://nextjs.org/telemetry
|
30 |
+
# Uncomment the following line in case you want to disable telemetry during the build.
|
31 |
+
# ENV NEXT_TELEMETRY_DISABLED 1
|
32 |
+
|
33 |
+
# RUN yarn build
|
34 |
+
|
35 |
+
# If you use yarn, comment out this line and use the line above
|
36 |
+
RUN npm run build
|
37 |
+
|
38 |
+
# Production image, copy all the files and run next
|
39 |
+
FROM base AS runner
|
40 |
+
WORKDIR /app
|
41 |
+
|
42 |
+
ENV NODE_ENV production
|
43 |
+
# Uncomment the following line in case you want to disable telemetry during runtime.
|
44 |
+
# ENV NEXT_TELEMETRY_DISABLED 1
|
45 |
+
|
46 |
+
RUN addgroup --system --gid 1001 nodejs
|
47 |
+
RUN adduser --system --uid 1001 nextjs
|
48 |
+
|
49 |
+
COPY --from=builder /app/public ./public
|
50 |
+
|
51 |
+
# Automatically leverage output traces to reduce image size
|
52 |
+
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
53 |
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
54 |
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
55 |
+
|
56 |
+
USER nextjs
|
57 |
+
|
58 |
+
EXPOSE 3000
|
59 |
+
|
60 |
+
ENV PORT 3000
|
README.md
CHANGED
@@ -1,10 +1,26 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<p align="center">
|
2 |
+
Next-gen online editor Playground, built with <a href="https://nextjs.org/" target="_blank">Next</a> and hosted with <a href="https://vercel.com/" target="_blank">Vercel</a>
|
3 |
+
</p>
|
4 |
+
<p align="center">
|
5 |
+
<img src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat" alt="Netlify Status" />
|
6 |
+
</p>
|
7 |
+
|
8 |
+
![](public/preview-image.png)
|
9 |
+
|
10 |
+
# Codetree : Standalone version
|
11 |
+
|
12 |
+
Codetree is a lightning fast ⚡️⚡️⚡️ code playground with automatic module detection as main feature. Unlike https://codepen.io/, Codetree is built on top of WebAssembly using esbuild, the code is compiled directly in your browser, without any backend and converted into machine language, allowing extremely fast execution and offline-mode.
|
13 |
+
|
14 |
+
## Usage
|
15 |
+
|
16 |
+
No need to install any npm package manually, codetree automatically detects the presence of import/require syntax in your file, downloads and installs npm package for you, for example just type `import React from "react"` for installing React library.
|
17 |
+
|
18 |
+
## Features
|
19 |
+
|
20 |
+
- Instant code compilation and preview (15x faster than codepen/codesanbox).
|
21 |
+
|
22 |
+
- Automatic import of external library.
|
23 |
+
|
24 |
+
- Auto-completion and intelliSense.
|
25 |
+
|
26 |
+
- Offline mode.
|
_types/compilerTypes.ts
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export interface CompilerStatus {
|
2 |
+
isReady: boolean;
|
3 |
+
error: string;
|
4 |
+
}
|
5 |
+
|
6 |
+
export interface CompilerOutput {
|
7 |
+
code: string;
|
8 |
+
error: string;
|
9 |
+
}
|
_types/editorTypes.ts
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export interface LanguagePropsInterface {
|
2 |
+
title: string;
|
3 |
+
entryPoints: string;
|
4 |
+
monacoLanguage: string;
|
5 |
+
data: string;
|
6 |
+
}
|
7 |
+
|
8 |
+
export interface IObjectKeys {
|
9 |
+
[key: string]: LanguagePropsInterface;
|
10 |
+
}
|
11 |
+
|
12 |
+
export interface LanguagesInterface extends IObjectKeys {
|
13 |
+
javascript: LanguagePropsInterface;
|
14 |
+
css: LanguagePropsInterface;
|
15 |
+
html: LanguagePropsInterface;
|
16 |
+
}
|
17 |
+
|
18 |
+
export interface EditorValueInterface {
|
19 |
+
name: string;
|
20 |
+
description: string;
|
21 |
+
public: boolean;
|
22 |
+
tabs: LanguagesInterface;
|
23 |
+
}
|
additional.d.ts
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import "iron-session";
|
2 |
+
import { User } from "./graphql/generated/graphql";
|
3 |
+
import { OauthInput, OauthProvider } from "./store/features/authSlice";
|
4 |
+
|
5 |
+
declare global {
|
6 |
+
interface Window {
|
7 |
+
withOauth: (input: OauthInput, provider: OauthProvider) => void;
|
8 |
+
}
|
9 |
+
}
|
10 |
+
|
11 |
+
declare module "iron-session" {
|
12 |
+
interface IronSessionData {
|
13 |
+
user?: {
|
14 |
+
message?: string;
|
15 |
+
token?: string | null;
|
16 |
+
status: boolean;
|
17 |
+
data: User;
|
18 |
+
isLoggedIn?: boolean;
|
19 |
+
};
|
20 |
+
}
|
21 |
+
}
|
codegen.yml
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
overwrite: true
|
2 |
+
schema: "http://localhost:4000/graphql"
|
3 |
+
documents: null
|
4 |
+
generates:
|
5 |
+
graphql/generated/graphql.d.ts:
|
6 |
+
plugins:
|
7 |
+
- "typescript"
|
8 |
+
- "typescript-operations"
|
9 |
+
- "typescript-react-apollo"
|
10 |
+
./graphql.schema.json:
|
11 |
+
plugins:
|
12 |
+
- "introspection"
|
components/Avatar.tsx
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Image from "next/image";
|
2 |
+
import React from "react";
|
3 |
+
|
4 |
+
interface AvatarProps {
|
5 |
+
image?: string;
|
6 |
+
username?: string;
|
7 |
+
size?: number;
|
8 |
+
gradient?: boolean;
|
9 |
+
className?: string;
|
10 |
+
placeholderType?: "blur" | "empty" | undefined;
|
11 |
+
}
|
12 |
+
|
13 |
+
export const Avatar = ({
|
14 |
+
image,
|
15 |
+
username,
|
16 |
+
size = 45,
|
17 |
+
gradient,
|
18 |
+
className,
|
19 |
+
placeholderType = "blur",
|
20 |
+
}: AvatarProps) => {
|
21 |
+
if (image)
|
22 |
+
return (
|
23 |
+
<div
|
24 |
+
className={` ${
|
25 |
+
gradient && `bg-gradient-to-t from-violet-400 to-violet-100 p-0.5`
|
26 |
+
} relative flex justify-center items-center rounded-full shadow-xl overflow-hidden ${className}`}
|
27 |
+
>
|
28 |
+
<div className="flex">
|
29 |
+
<Image
|
30 |
+
src={image}
|
31 |
+
alt="avatar"
|
32 |
+
className="cursor-pointer rounded-full"
|
33 |
+
objectFit="cover"
|
34 |
+
width={size}
|
35 |
+
height={size}
|
36 |
+
placeholder={placeholderType}
|
37 |
+
quality={100}
|
38 |
+
blurDataURL={placeholderType === "blur" ? image : undefined}
|
39 |
+
/>
|
40 |
+
</div>
|
41 |
+
</div>
|
42 |
+
);
|
43 |
+
|
44 |
+
return (
|
45 |
+
<div className="flex justify-center items-center relative">
|
46 |
+
<div
|
47 |
+
style={{
|
48 |
+
width: size,
|
49 |
+
height: size,
|
50 |
+
}}
|
51 |
+
className={`flex items-center justify-center bg-zinc-50 max-w-xs rounded-full mb-1 cursor-pointer border ${className}`}
|
52 |
+
>
|
53 |
+
{username?.charAt(0).toUpperCase()}
|
54 |
+
</div>
|
55 |
+
</div>
|
56 |
+
);
|
57 |
+
};
|
components/Dropdown.tsx
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { ReactNode, useEffect, useRef, useState } from "react";
|
2 |
+
import { motion, Variants } from "framer-motion";
|
3 |
+
import useOutsideRef from "../hooks/useOutsideRef";
|
4 |
+
import Router from "next/router";
|
5 |
+
import { useAppSelector } from "../store/hook";
|
6 |
+
import { theme_state } from "../store/features/themeSlice";
|
7 |
+
|
8 |
+
interface DropdownProps {
|
9 |
+
trigger: ReactNode;
|
10 |
+
children: ReactNode;
|
11 |
+
classname: string;
|
12 |
+
}
|
13 |
+
|
14 |
+
export const Dropdown = ({ trigger, children, classname }: DropdownProps) => {
|
15 |
+
const [isOpen, setIsOpen] = useState(false);
|
16 |
+
const dropdownRef = useRef(null);
|
17 |
+
const { theme } = useAppSelector(theme_state);
|
18 |
+
|
19 |
+
const { isOutsideRef } = useOutsideRef(dropdownRef);
|
20 |
+
|
21 |
+
useEffect(() => {
|
22 |
+
if (isOutsideRef) {
|
23 |
+
setIsOpen(false);
|
24 |
+
}
|
25 |
+
}, [isOutsideRef]);
|
26 |
+
|
27 |
+
const toggleMenu = () => {
|
28 |
+
setIsOpen(!isOpen);
|
29 |
+
};
|
30 |
+
|
31 |
+
Router.events.on("routeChangeStart", () => {
|
32 |
+
setIsOpen(false);
|
33 |
+
});
|
34 |
+
|
35 |
+
const animation: Variants = {
|
36 |
+
enter: {
|
37 |
+
opacity: 1,
|
38 |
+
scale: 1,
|
39 |
+
transformOrigin: "top right",
|
40 |
+
transition: {
|
41 |
+
duration: 0.25,
|
42 |
+
},
|
43 |
+
display: "block",
|
44 |
+
},
|
45 |
+
exit: {
|
46 |
+
opacity: 0,
|
47 |
+
scale: 0.7,
|
48 |
+
transformOrigin: "top right",
|
49 |
+
transition: {
|
50 |
+
duration: 0.2,
|
51 |
+
delay: 0.1,
|
52 |
+
},
|
53 |
+
transitionEnd: {
|
54 |
+
display: "none",
|
55 |
+
},
|
56 |
+
},
|
57 |
+
};
|
58 |
+
|
59 |
+
return (
|
60 |
+
<div ref={dropdownRef}>
|
61 |
+
<motion.div className="menu-item relative">
|
62 |
+
<div className="cursor-pointer" onClick={toggleMenu}>
|
63 |
+
{trigger}
|
64 |
+
</div>
|
65 |
+
|
66 |
+
<motion.div
|
67 |
+
className={`${classname} absolute shadow rounded-lg z-50 mt-3 glassmorphism`}
|
68 |
+
initial="exit"
|
69 |
+
animate={isOpen ? "enter" : "exit"}
|
70 |
+
variants={animation}
|
71 |
+
>
|
72 |
+
<div>{children}</div>
|
73 |
+
</motion.div>
|
74 |
+
</motion.div>
|
75 |
+
</div>
|
76 |
+
);
|
77 |
+
};
|
components/Header.tsx
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
import { useAppDispatch, useAppSelector } from "../store/hook";
|
3 |
+
import { theme_state } from "../store/features/themeSlice";
|
4 |
+
|
5 |
+
export const Header = () => {
|
6 |
+
const dispatch = useAppDispatch();
|
7 |
+
const { theme } = useAppSelector(theme_state);
|
8 |
+
return (
|
9 |
+
<div
|
10 |
+
style={{ height: "3rem", background: theme.background }}
|
11 |
+
className="flex items-center pl-5 pr-12 justify-between"
|
12 |
+
>
|
13 |
+
<div>
|
14 |
+
<div className="text-2xl font-medium text-gray-200">
|
15 |
+
Codetree AI
|
16 |
+
</div>
|
17 |
+
</div>
|
18 |
+
</div>
|
19 |
+
);
|
20 |
+
};
|
components/Loader.tsx
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
|
3 |
+
interface LoaderProps {
|
4 |
+
size?: number;
|
5 |
+
color?: string;
|
6 |
+
}
|
7 |
+
|
8 |
+
const Loader = ({ size = 50, color = "#FFFFFF" }: LoaderProps) => {
|
9 |
+
return (
|
10 |
+
<div>
|
11 |
+
<svg
|
12 |
+
width={size}
|
13 |
+
height={size}
|
14 |
+
viewBox="0 0 16 16"
|
15 |
+
color={color}
|
16 |
+
fill="none"
|
17 |
+
xmlns="http://www.w3.org/2000/svg"
|
18 |
+
>
|
19 |
+
<path
|
20 |
+
d="M3.05022 3.05026 C 2.65969 3.44078 2.65969 4.07394 3.05022 4.46447 C 3.44074 4.85499 4.07391 4.85499 4.46443 4.46447 L 3.05022 3.05026Z M 4.46443 4.46447 C 5.16369 3.76521 6.05461 3.289 7.02451 3.09608 L 6.63433 1.13451 C 5.27647 1.4046 4.02918 2.07129 3.05022 3.05026 L 4.46443 4.46447Z M 7.02451 3.09608 C 7.99442 2.90315 8.99975 3.00217 9.91338 3.3806 L 10.6787 1.53285C9.39967 1.00303 7.9922 0.864409 6.63433 1.13451 L 7.02451 3.09608ZM9.91338 3.3806C10.827 3.75904 11.6079 4.39991 12.1573 5.22215L13.8203 4.11101C13.0511 2.95987 11.9578 2.06266 10.6787 1.53285L9.91338 3.3806ZM12.1573 5.22215C12.7067 6.0444 13 7.01109 13 8L15 8C15 6.61553 14.5894 5.26215 13.8203 4.11101L12.1573 5.22215Z"
|
21 |
+
fill="url(#paint0_linear_11825_47664)"
|
22 |
+
/>
|
23 |
+
<path
|
24 |
+
d="M3 8C3 7.44772 2.55228 7 2 7C1.44772 7 1 7.44772 1 8L3 8ZM1 8C1 8.91925 1.18106 9.82951 1.53284 10.6788L3.3806 9.91342C3.12933 9.30679 3 8.65661 3 8L1 8ZM1.53284 10.6788C1.88463 11.5281 2.40024 12.2997 3.05025 12.9497L4.46447 11.5355C4.00017 11.0712 3.63188 10.52 3.3806 9.91342L1.53284 10.6788ZM3.05025 12.9497C3.70026 13.5998 4.47194 14.1154 5.32122 14.4672L6.08658 12.6194C5.47996 12.3681 4.92876 11.9998 4.46447 11.5355L3.05025 12.9497ZM5.32122 14.4672C6.1705 14.8189 7.08075 15 8 15L8 13C7.34339 13 6.69321 12.8707 6.08658 12.6194L5.32122 14.4672ZM8 15C8.91925 15 9.82951 14.8189 10.6788 14.4672L9.91342 12.6194C9.30679 12.8707 8.65661 13 8 13L8 15ZM10.6788 14.4672C11.5281 14.1154 12.2997 13.5998 12.9497 12.9497L11.5355 11.5355C11.0712 11.9998 10.52 12.3681 9.91342 12.6194L10.6788 14.4672ZM12.9497 12.9497C13.5998 12.2997 14.1154 11.5281 14.4672 10.6788L12.6194 9.91342C12.3681 10.52 11.9998 11.0712 11.5355 11.5355L12.9497 12.9497ZM14.4672 10.6788C14.8189 9.8295 15 8.91925 15 8L13 8C13 8.65661 12.8707 9.30679 12.6194 9.91342L14.4672 10.6788Z"
|
25 |
+
fill="url(#paint1_linear_11825_47664)"
|
26 |
+
/>
|
27 |
+
<defs>
|
28 |
+
<linearGradient
|
29 |
+
id="paint0_linear_11825_47664"
|
30 |
+
x1="14"
|
31 |
+
y1="8"
|
32 |
+
x2="2"
|
33 |
+
y2="8"
|
34 |
+
gradientUnits="userSpaceOnUse"
|
35 |
+
>
|
36 |
+
<stop stopColor="currentColor" stopOpacity="0.5" />
|
37 |
+
<stop offset="1" stopColor="currentColor" stopOpacity="0" />
|
38 |
+
</linearGradient>
|
39 |
+
<linearGradient
|
40 |
+
id="paint1_linear_11825_47664"
|
41 |
+
x1="2"
|
42 |
+
y1="8"
|
43 |
+
x2="14"
|
44 |
+
y2="8"
|
45 |
+
gradientUnits="userSpaceOnUse"
|
46 |
+
>
|
47 |
+
<stop stopColor="currentColor" />
|
48 |
+
<stop offset="1" stopColor="currentColor" stopOpacity="0.5" />
|
49 |
+
</linearGradient>
|
50 |
+
</defs>
|
51 |
+
<animateTransform
|
52 |
+
from="0 0 0"
|
53 |
+
to="360 0 0"
|
54 |
+
attributeName="transform"
|
55 |
+
type="rotate"
|
56 |
+
repeatCount="indefinite"
|
57 |
+
dur="1300ms"
|
58 |
+
/>
|
59 |
+
</svg>
|
60 |
+
</div>
|
61 |
+
);
|
62 |
+
};
|
63 |
+
|
64 |
+
export default Loader;
|
components/Modals/AuthModal.tsx
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
import { motion } from "framer-motion";
|
3 |
+
import { useAppDispatch } from "../../store/hook";
|
4 |
+
import { getGithubOAuthURL, getGoogleOAuthURL } from "../../utils/getOAuthUrl";
|
5 |
+
import { nativePopup } from "../../utils/nativePopup";
|
6 |
+
import { close_modal } from "../../store/features/modalSlice";
|
7 |
+
import { modalVariant } from "./config";
|
8 |
+
import { RiGithubFill, RiGoogleFill } from "react-icons/ri";
|
9 |
+
|
10 |
+
const AuthModal = () => {
|
11 |
+
const dispatch = useAppDispatch();
|
12 |
+
|
13 |
+
return (
|
14 |
+
<motion.div
|
15 |
+
className="sm:mt-10 mx-auto h-full sm:h-auto sm:w-8/12 lg:w-6/12 xl:w-4/12"
|
16 |
+
variants={modalVariant}
|
17 |
+
initial="initial"
|
18 |
+
animate="animate"
|
19 |
+
exit="exit"
|
20 |
+
transition={{ ease: "easeOut", duration: 0.3 }}
|
21 |
+
onClick={(e) => e.stopPropagation()}
|
22 |
+
>
|
23 |
+
<div className="h-full flex flex-col shadow-xl sm:rounded-xl overflow-hidden">
|
24 |
+
<div
|
25 |
+
className="h-64 w-full"
|
26 |
+
style={{
|
27 |
+
backgroundImage: `url(https://cdn.dribbble.com/userupload/3396431/file/original-d865684d8f3afbc99fcab7ab38f9ee9e.png?compress=1&resize=1504x1128)`,
|
28 |
+
backgroundPosition: "center",
|
29 |
+
backgroundSize: "cover",
|
30 |
+
}}
|
31 |
+
/>
|
32 |
+
|
33 |
+
<div className="flex h-52 flex-col justify-center items-center gap-7 glassmorphism">
|
34 |
+
<button
|
35 |
+
onClick={() => {
|
36 |
+
nativePopup({
|
37 |
+
pageURL: getGoogleOAuthURL(),
|
38 |
+
pageTitle: "Codetree authentication",
|
39 |
+
popupWinWidth: 490,
|
40 |
+
popupWinHeight: 600,
|
41 |
+
});
|
42 |
+
|
43 |
+
dispatch(close_modal());
|
44 |
+
}}
|
45 |
+
className="px-5 border border-gray-400 flex gap-x-5 items-center justify-center w-80 h-12 rounded-xl text-gray-300"
|
46 |
+
>
|
47 |
+
<RiGoogleFill size={24} />
|
48 |
+
<div>Sign in with google</div>
|
49 |
+
</button>
|
50 |
+
|
51 |
+
<button
|
52 |
+
onClick={() => {
|
53 |
+
nativePopup({
|
54 |
+
pageURL: getGithubOAuthURL(),
|
55 |
+
pageTitle: "Codetree authentication",
|
56 |
+
popupWinWidth: 490,
|
57 |
+
popupWinHeight: 600,
|
58 |
+
});
|
59 |
+
|
60 |
+
dispatch(close_modal());
|
61 |
+
}}
|
62 |
+
className="px-5 border border-gray-400 flex gap-x-5 items-center justify-center w-80 h-12 rounded-xl text-gray-300"
|
63 |
+
>
|
64 |
+
<RiGithubFill size={24} />
|
65 |
+
<div>Sign in with github</div>
|
66 |
+
</button>
|
67 |
+
</div>
|
68 |
+
</div>
|
69 |
+
</motion.div>
|
70 |
+
);
|
71 |
+
};
|
72 |
+
|
73 |
+
export default AuthModal;
|
components/Modals/RootModal.tsx
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
import { motion, AnimatePresence } from "framer-motion";
|
3 |
+
|
4 |
+
import { useAppDispatch, useAppSelector } from "../../store/hook";
|
5 |
+
import {
|
6 |
+
modal_state,
|
7 |
+
close_modal,
|
8 |
+
ModalEnum,
|
9 |
+
} from "../../store/features/modalSlice";
|
10 |
+
|
11 |
+
import AuthModal from "./AuthModal";
|
12 |
+
import TemplateModal from "./TemplateModal";
|
13 |
+
import SettingsModal from "./SettingsModal";
|
14 |
+
|
15 |
+
export const RootModal = () => {
|
16 |
+
const { type, visible } = useAppSelector(modal_state);
|
17 |
+
const dispatch = useAppDispatch();
|
18 |
+
|
19 |
+
const renderModal = (type: ModalEnum) => {
|
20 |
+
switch (type) {
|
21 |
+
case ModalEnum.AUTH:
|
22 |
+
return <AuthModal />;
|
23 |
+
|
24 |
+
case ModalEnum.TEMPLATE:
|
25 |
+
return <TemplateModal />;
|
26 |
+
|
27 |
+
case ModalEnum.SETTINGS:
|
28 |
+
return <SettingsModal />;
|
29 |
+
|
30 |
+
case ModalEnum.IDLE:
|
31 |
+
return <div />;
|
32 |
+
}
|
33 |
+
};
|
34 |
+
|
35 |
+
return (
|
36 |
+
<AnimatePresence exitBeforeEnter onExitComplete={() => null}>
|
37 |
+
{visible && (
|
38 |
+
<motion.div
|
39 |
+
className="backdrop"
|
40 |
+
onClick={() => dispatch(close_modal())}
|
41 |
+
>
|
42 |
+
{renderModal(type)}
|
43 |
+
</motion.div>
|
44 |
+
)}
|
45 |
+
</AnimatePresence>
|
46 |
+
);
|
47 |
+
};
|
components/Modals/SettingsModal.tsx
ADDED
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
import { motion } from "framer-motion";
|
3 |
+
import { useForm } from "react-hook-form";
|
4 |
+
import { useAppDispatch, useAppSelector } from "../../store/hook";
|
5 |
+
import { editor_state, set_options } from "../../store/features/editorSlice";
|
6 |
+
import { modalVariant } from "./config";
|
7 |
+
import { theme_state } from "../../store/features/themeSlice";
|
8 |
+
|
9 |
+
const SettingsModal = () => {
|
10 |
+
const dispatch = useAppDispatch();
|
11 |
+
const { options } = useAppSelector(editor_state);
|
12 |
+
const { theme } = useAppSelector(theme_state);
|
13 |
+
|
14 |
+
const { register, handleSubmit } = useForm();
|
15 |
+
|
16 |
+
const onChangeOptions = ({
|
17 |
+
fontSize,
|
18 |
+
fontWeight,
|
19 |
+
minimapEnabled,
|
20 |
+
minimapScale,
|
21 |
+
wordWrap,
|
22 |
+
autoClosingBrackets,
|
23 |
+
}: any) => {
|
24 |
+
const custom = {
|
25 |
+
fontSize: parseInt(fontSize),
|
26 |
+
fontWeight: fontWeight,
|
27 |
+
minimap: {
|
28 |
+
enabled: minimapEnabled,
|
29 |
+
scale: parseInt(minimapScale),
|
30 |
+
},
|
31 |
+
wordWrap: wordWrap,
|
32 |
+
autoClosingBrackets: { autoClosingBrackets },
|
33 |
+
showUnused: true,
|
34 |
+
automaticLayout: true,
|
35 |
+
tabSize: 2,
|
36 |
+
renderLineHighlight: "none",
|
37 |
+
scrollbar: { verticalScrollbarSize: 10, verticalSliderSize: 10 },
|
38 |
+
};
|
39 |
+
|
40 |
+
dispatch(set_options(custom));
|
41 |
+
};
|
42 |
+
|
43 |
+
return (
|
44 |
+
<motion.div
|
45 |
+
className="sm:mt-10 mx-auto h-full sm:h-auto sm:w-8/12 lg:w-6/12 sm:rounded-xl overflow-hidden glassmorphism"
|
46 |
+
variants={modalVariant}
|
47 |
+
initial="initial"
|
48 |
+
animate="animate"
|
49 |
+
exit="exit"
|
50 |
+
transition={{ ease: "easeOut", duration: 0.3 }}
|
51 |
+
onClick={(e) => e.stopPropagation()}
|
52 |
+
>
|
53 |
+
<div
|
54 |
+
style={{ backgroundColor: theme.foreground }}
|
55 |
+
className="border-b border-black h-10 flex items-center px-7"
|
56 |
+
>
|
57 |
+
<h1 className="text-xl"> Settings</h1>
|
58 |
+
</div>
|
59 |
+
|
60 |
+
<div className="overflow-auto h-full">
|
61 |
+
<div className="pt-6 px-7 ">
|
62 |
+
<form
|
63 |
+
className=" mb-12 overflow-auto"
|
64 |
+
onSubmit={handleSubmit(onChangeOptions)}
|
65 |
+
>
|
66 |
+
{/* Fonts */}
|
67 |
+
<div className="editor-sub-settings">
|
68 |
+
<div className="title">Font size : </div>
|
69 |
+
<div className="description">Set the font size in pixels.</div>
|
70 |
+
<select
|
71 |
+
{...register("fontSize")}
|
72 |
+
id="fontSize"
|
73 |
+
className="editor-select"
|
74 |
+
defaultValue={options.fontSize}
|
75 |
+
style={{
|
76 |
+
backgroundColor: theme.foreground,
|
77 |
+
}}
|
78 |
+
>
|
79 |
+
<option value={9}>9</option>
|
80 |
+
<option value={10}>10</option>
|
81 |
+
<option value={11}>11</option>
|
82 |
+
<option value={12}>12</option>
|
83 |
+
<option value={13}>13</option>
|
84 |
+
<option value={14}>14</option>
|
85 |
+
<option value={15}>15</option>
|
86 |
+
<option value={16}>16</option>
|
87 |
+
<option value={17}>17</option>
|
88 |
+
</select>
|
89 |
+
</div>
|
90 |
+
|
91 |
+
<div className="editor-sub-settings">
|
92 |
+
<div className="title">Font weight : </div>
|
93 |
+
<div className="description">Defines how bold you text are.</div>
|
94 |
+
<select
|
95 |
+
{...register("fontWeight")}
|
96 |
+
id="fontWeight"
|
97 |
+
className="editor-select"
|
98 |
+
defaultValue={options.fontWeight}
|
99 |
+
style={{
|
100 |
+
backgroundColor: theme.foreground,
|
101 |
+
}}
|
102 |
+
>
|
103 |
+
<option value="500">Regular</option>
|
104 |
+
<option value="600">Bold</option>
|
105 |
+
</select>
|
106 |
+
</div>
|
107 |
+
|
108 |
+
{/* Minimap */}
|
109 |
+
<div className="editor-sub-settings">
|
110 |
+
<div className="title">Enabled minimap : </div>
|
111 |
+
<div className="description">
|
112 |
+
Control if the minimap should be shown.
|
113 |
+
</div>
|
114 |
+
<div className="flex items-center">
|
115 |
+
<input
|
116 |
+
{...register("minimapEnabled")}
|
117 |
+
id="minimap-enabled"
|
118 |
+
className="editor-select mr-2"
|
119 |
+
type="checkbox"
|
120 |
+
defaultChecked={options.minimap?.enabled}
|
121 |
+
style={{
|
122 |
+
backgroundColor: theme.foreground,
|
123 |
+
}}
|
124 |
+
/>
|
125 |
+
<label>On </label>
|
126 |
+
</div>
|
127 |
+
</div>
|
128 |
+
|
129 |
+
<div className="editor-sub-settings">
|
130 |
+
<div className="title">Scale : </div>
|
131 |
+
<div className="description">Set the size of the minimap.</div>
|
132 |
+
<select
|
133 |
+
{...register("minimapScale")}
|
134 |
+
id="minimap-scale"
|
135 |
+
className="editor-select"
|
136 |
+
defaultValue={options.minimap?.scale}
|
137 |
+
style={{
|
138 |
+
backgroundColor: theme.foreground,
|
139 |
+
}}
|
140 |
+
>
|
141 |
+
<option value={1}>1</option>
|
142 |
+
<option value={2}>2</option>
|
143 |
+
<option value={3}>3</option>
|
144 |
+
</select>
|
145 |
+
</div>
|
146 |
+
|
147 |
+
{/* Others */}
|
148 |
+
<div className="editor-sub-settings">
|
149 |
+
<div className="title">Word wrap : </div>
|
150 |
+
<div className="description">Control if lines should wrap.</div>
|
151 |
+
<select
|
152 |
+
{...register("wordWrap")}
|
153 |
+
id="wordWrap"
|
154 |
+
className="editor-select"
|
155 |
+
defaultValue={options.wordWrap}
|
156 |
+
style={{
|
157 |
+
backgroundColor: theme.foreground,
|
158 |
+
}}
|
159 |
+
>
|
160 |
+
<option value="on">On</option>
|
161 |
+
<option value="off">Off</option>
|
162 |
+
</select>
|
163 |
+
</div>
|
164 |
+
|
165 |
+
<div className="editor-sub-settings">
|
166 |
+
<div className="title">Auto Closing Brackets : </div>
|
167 |
+
<div className="description">
|
168 |
+
Controls if brackets should close automatically
|
169 |
+
</div>
|
170 |
+
<select
|
171 |
+
{...register("autoClosingBrackets")}
|
172 |
+
id="autoClosingBrackets"
|
173 |
+
className="editor-select"
|
174 |
+
defaultValue={options.autoClosingBrackets}
|
175 |
+
style={{
|
176 |
+
backgroundColor: theme.foreground,
|
177 |
+
}}
|
178 |
+
>
|
179 |
+
<option value="always">always</option>
|
180 |
+
<option value="languageDefined">languageDefined</option>
|
181 |
+
<option value="beforeWhitespace">beforeWhitespace</option>
|
182 |
+
<option value="never">never</option>
|
183 |
+
</select>
|
184 |
+
</div>
|
185 |
+
|
186 |
+
<button className="editor-button h-9 mt-6" type="submit">
|
187 |
+
Save settings
|
188 |
+
</button>
|
189 |
+
</form>
|
190 |
+
</div>
|
191 |
+
</div>
|
192 |
+
</motion.div>
|
193 |
+
);
|
194 |
+
};
|
195 |
+
|
196 |
+
export default SettingsModal;
|
components/Modals/TemplateModal.tsx
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { motion } from "framer-motion";
|
2 |
+
import { useTree } from "../../hooks";
|
3 |
+
import { treeTemplates } from "../../constants";
|
4 |
+
import { useAppSelector } from "../../store/hook";
|
5 |
+
import { theme_state } from "../../store/features/themeSlice";
|
6 |
+
import { compiler_state } from "../../store/features/compilerSlice";
|
7 |
+
import { TemplateSelectionSkeleton } from "../Skeleton/TemplateSelectionSkeleton";
|
8 |
+
import { modalVariant } from "./config";
|
9 |
+
|
10 |
+
const TemplateModal = () => {
|
11 |
+
const { theme } = useAppSelector(theme_state);
|
12 |
+
const { esbuildStatus } = useAppSelector(compiler_state);
|
13 |
+
const { setTree } = useTree();
|
14 |
+
|
15 |
+
let arr = [];
|
16 |
+
|
17 |
+
for (const item of Object.entries(treeTemplates)) {
|
18 |
+
arr.push(item);
|
19 |
+
}
|
20 |
+
|
21 |
+
const templates = arr.map((template, key) => (
|
22 |
+
<button
|
23 |
+
key={key}
|
24 |
+
name={template[1].name}
|
25 |
+
onClick={() => setTree(template[1])}
|
26 |
+
className="p-2 rounded-sm"
|
27 |
+
>
|
28 |
+
<div className="flex pointer-events-none">
|
29 |
+
<img
|
30 |
+
alt={template[1].name}
|
31 |
+
src={template[1].iconSrc}
|
32 |
+
className="h-9 w-9 rounded-sm"
|
33 |
+
/>
|
34 |
+
<div className="flex flex-col items-start justify-start pl-4">
|
35 |
+
<div>{template[1].name}</div>
|
36 |
+
<div className="text-xs text-gray-400">{template[1].description}</div>
|
37 |
+
</div>
|
38 |
+
</div>
|
39 |
+
</button>
|
40 |
+
));
|
41 |
+
|
42 |
+
return (
|
43 |
+
<motion.div
|
44 |
+
className="sm:mt-44 mx-auto h-full sm:h-auto sm:w-8/12 lg:w-6/12 sm:pb-20 sm:rounded-xl overflow-hidden glassmorphism"
|
45 |
+
variants={modalVariant}
|
46 |
+
initial="initial"
|
47 |
+
animate="animate"
|
48 |
+
exit="exit"
|
49 |
+
transition={{ ease: "easeOut", duration: 0.3 }}
|
50 |
+
onClick={(e) => e.stopPropagation()}
|
51 |
+
>
|
52 |
+
<div
|
53 |
+
style={{ backgroundColor: theme.foreground }}
|
54 |
+
className="border-b border-black h-10 flex items-center px-7"
|
55 |
+
>
|
56 |
+
<h1 className="text-xl">Templates</h1>
|
57 |
+
</div>
|
58 |
+
<div className="pt-6 px-7 flex flex-wrap gap-x-14 gap-y-8">
|
59 |
+
{esbuildStatus.isReady ? templates : <TemplateSelectionSkeleton />}
|
60 |
+
</div>
|
61 |
+
</motion.div>
|
62 |
+
);
|
63 |
+
};
|
64 |
+
|
65 |
+
export default TemplateModal;
|
components/Modals/config.ts
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Variants } from "framer-motion";
|
2 |
+
|
3 |
+
export const modalVariant: Variants = {
|
4 |
+
initial: { scale: 0.95, opacity: 0 },
|
5 |
+
animate: { scale: 1, opacity: 1 },
|
6 |
+
exit: { scale: 0.98, opacity: 0 },
|
7 |
+
};
|
components/Playground/ConsoleLog.tsx
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
import dynamic from "next/dynamic";
|
3 |
+
import { useAppDispatch, useAppSelector } from "../../store/hook";
|
4 |
+
import { clear_logs } from "../../store/features/editorSlice";
|
5 |
+
|
6 |
+
import { theme_state } from "../../store/features/themeSlice";
|
7 |
+
|
8 |
+
const Console = dynamic(import("console-feed/lib/Component"), { ssr: false });
|
9 |
+
|
10 |
+
interface LogsProps {
|
11 |
+
logs: any;
|
12 |
+
}
|
13 |
+
|
14 |
+
const Logs = ({ logs }: LogsProps) => {
|
15 |
+
const { theme } = useAppSelector(theme_state);
|
16 |
+
const dispatch = useAppDispatch();
|
17 |
+
|
18 |
+
return (
|
19 |
+
<div className="text-gray-300 relative h-full">
|
20 |
+
<div className="flex items-center justify-between px-3 h-9">
|
21 |
+
<button
|
22 |
+
onClick={() => dispatch(clear_logs())}
|
23 |
+
className="editor-button h-5 mr-3"
|
24 |
+
>
|
25 |
+
Clear
|
26 |
+
</button>
|
27 |
+
</div>
|
28 |
+
|
29 |
+
<div className="h-80 overflow-auto">
|
30 |
+
<Console
|
31 |
+
styles={{
|
32 |
+
BASE_FONT_FAMILY: '"Rubik", sans-serif;',
|
33 |
+
BASE_FONT_SIZE: 14,
|
34 |
+
BASE_BACKGROUND_COLOR: theme.background,
|
35 |
+
LOG_BORDER: theme.border,
|
36 |
+
}}
|
37 |
+
logs={logs}
|
38 |
+
variant="dark"
|
39 |
+
/>
|
40 |
+
</div>
|
41 |
+
</div>
|
42 |
+
);
|
43 |
+
};
|
44 |
+
|
45 |
+
const ConsoleLog = React.memo(Logs);
|
46 |
+
|
47 |
+
export default ConsoleLog;
|
components/Playground/Footer.tsx
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
import { useAppDispatch, useAppSelector } from "../../store/hook";
|
3 |
+
import {
|
4 |
+
editor_state,
|
5 |
+
toggle_logs_tab,
|
6 |
+
} from "../../store/features/editorSlice";
|
7 |
+
import { compiler_state } from "../../store/features/compilerSlice";
|
8 |
+
import { RiTerminalLine } from "react-icons/ri";
|
9 |
+
|
10 |
+
const Footer = () => {
|
11 |
+
const { logs } = useAppSelector(editor_state);
|
12 |
+
const { isCompiling } = useAppSelector(compiler_state);
|
13 |
+
const dispatch = useAppDispatch();
|
14 |
+
|
15 |
+
return (
|
16 |
+
<div
|
17 |
+
style={{ height: "2rem" }}
|
18 |
+
className="h-full w-full flex items-center justify-between bg-tree-hard border-t-2 border-black text-gray-300 pl-5 pb-0.5 flex-shrink-0 z-50"
|
19 |
+
>
|
20 |
+
<div className="flex items-center">
|
21 |
+
{isCompiling && (
|
22 |
+
<div className="ml-10">
|
23 |
+
<span className="loader --1" />
|
24 |
+
</div>
|
25 |
+
)}
|
26 |
+
</div>
|
27 |
+
|
28 |
+
<div className="flex justify-center items-center">
|
29 |
+
<div
|
30 |
+
onClick={() => dispatch(toggle_logs_tab())}
|
31 |
+
className="flex items-center cursor-pointer text-gray-400"
|
32 |
+
>
|
33 |
+
<RiTerminalLine size={20} />
|
34 |
+
</div>
|
35 |
+
|
36 |
+
<div className="ml-2 w-6">
|
37 |
+
{logs.length > 0 && (
|
38 |
+
<div className="h-5 w-5 flex justify-center items-center">
|
39 |
+
<div className="absolute p-1.5 bg-teal-400 rounded-full animate-ping" />
|
40 |
+
<div className="absolute p-1 bg-teal-400 border-white rounded-full" />
|
41 |
+
</div>
|
42 |
+
)}
|
43 |
+
</div>
|
44 |
+
</div>
|
45 |
+
</div>
|
46 |
+
);
|
47 |
+
};
|
48 |
+
|
49 |
+
export default Footer;
|
components/Playground/Header.tsx
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
import { RiAddFill, RiSettings3Fill } from "react-icons/ri";
|
3 |
+
import { useAppDispatch } from "../../store/hook";
|
4 |
+
import { ModalEnum, open_modal } from "../../store/features/modalSlice";
|
5 |
+
|
6 |
+
const Header = () => {
|
7 |
+
const dispatch = useAppDispatch();
|
8 |
+
|
9 |
+
return (
|
10 |
+
<div style={{ height: "3rem" }} className="flex justify-end px-12">
|
11 |
+
<div className="flex gap-4 items-center ">
|
12 |
+
<RiAddFill
|
13 |
+
size={24}
|
14 |
+
className="cursor-pointer text-gray-400"
|
15 |
+
onClick={() => dispatch(open_modal(ModalEnum.TEMPLATE))}
|
16 |
+
/>
|
17 |
+
<RiSettings3Fill
|
18 |
+
size={20}
|
19 |
+
className="cursor-pointer text-gray-400"
|
20 |
+
onClick={() => dispatch(open_modal(ModalEnum.SETTINGS))}
|
21 |
+
/>
|
22 |
+
</div>
|
23 |
+
</div>
|
24 |
+
);
|
25 |
+
};
|
26 |
+
|
27 |
+
export default Header;
|
components/Playground/Iframe.tsx
ADDED
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useRef, useEffect } from "react";
|
2 |
+
|
3 |
+
import { useAppDispatch } from "../../store/hook";
|
4 |
+
import { update_logs } from "../../store/features/editorSlice";
|
5 |
+
import { getCompileCode } from "../../store/features/compilerSlice";
|
6 |
+
import { createIframeContent } from "../../utils/createIframeContent";
|
7 |
+
import { IframeLoaderScreen } from "./IframeLoaderScreen";
|
8 |
+
import { IframeErrorScreen } from "./IframeErrorScreen";
|
9 |
+
import { LanguagesInterface } from "../../_types/editorTypes";
|
10 |
+
import { CompilerOutput, CompilerStatus } from "../../_types/compilerTypes";
|
11 |
+
|
12 |
+
interface IframeProps {
|
13 |
+
tabs: LanguagesInterface;
|
14 |
+
output: CompilerOutput;
|
15 |
+
isCompiling: boolean;
|
16 |
+
esbuildStatus: CompilerStatus;
|
17 |
+
}
|
18 |
+
|
19 |
+
const IframePanel = ({
|
20 |
+
tabs,
|
21 |
+
output,
|
22 |
+
isCompiling,
|
23 |
+
esbuildStatus,
|
24 |
+
}: IframeProps) => {
|
25 |
+
const iframe = useRef<any>();
|
26 |
+
const dispatch = useAppDispatch();
|
27 |
+
|
28 |
+
const htmlFrameContent = createIframeContent(tabs.css?.data, tabs.html?.data);
|
29 |
+
|
30 |
+
//=== incoming message
|
31 |
+
useEffect(() => {
|
32 |
+
window.onmessage = function (response: MessageEvent) {
|
33 |
+
if (response.data && response.data.source === "iframe") {
|
34 |
+
let errorObject = {
|
35 |
+
method: "error",
|
36 |
+
id: Date.now(),
|
37 |
+
data: [`${response.data.message}`],
|
38 |
+
};
|
39 |
+
dispatch(update_logs(errorObject));
|
40 |
+
}
|
41 |
+
};
|
42 |
+
|
43 |
+
if (tabs.javascript && esbuildStatus.isReady) {
|
44 |
+
setTimeout(async () => {
|
45 |
+
dispatch(
|
46 |
+
getCompileCode(tabs.javascript.data, tabs.javascript.entryPoints)
|
47 |
+
);
|
48 |
+
}, 50);
|
49 |
+
}
|
50 |
+
}, [dispatch, tabs, esbuildStatus.isReady]);
|
51 |
+
|
52 |
+
//=== outgoing massage
|
53 |
+
useEffect(() => {
|
54 |
+
iframe.current.srcdoc = htmlFrameContent;
|
55 |
+
|
56 |
+
setTimeout(async () => {
|
57 |
+
iframe?.current?.contentWindow?.postMessage(output.code, "*");
|
58 |
+
}, 40);
|
59 |
+
}, [htmlFrameContent, output]);
|
60 |
+
|
61 |
+
return (
|
62 |
+
<div className="iframe-container">
|
63 |
+
{/* build error */}
|
64 |
+
{output.error ? <IframeErrorScreen err={output.error} /> : ""}
|
65 |
+
|
66 |
+
{/* Loading screen */}
|
67 |
+
{isCompiling ? (
|
68 |
+
<div className="absolute h-full w-full bg-gray-50 z-40">
|
69 |
+
<IframeLoaderScreen />
|
70 |
+
</div>
|
71 |
+
) : (
|
72 |
+
""
|
73 |
+
)}
|
74 |
+
|
75 |
+
<iframe
|
76 |
+
id="super-iframe"
|
77 |
+
sandbox="allow-downloads allow-forms allow-modals allow-pointer-lock allow-popups allow-presentation allow-same-origin allow-scripts allow-top-navigation-by-user-activation"
|
78 |
+
allow="accelerometer; camera; encrypted-media; geolocation; gyroscope; microphone; midi; clipboard-read; clipboard-write"
|
79 |
+
scrolling="auto"
|
80 |
+
frameBorder="0"
|
81 |
+
ref={iframe}
|
82 |
+
title="previewWindow"
|
83 |
+
srcDoc={htmlFrameContent}
|
84 |
+
onLoad={async () => {
|
85 |
+
const Hook = (await import("console-feed")).Hook;
|
86 |
+
Hook(
|
87 |
+
iframe.current.contentWindow.console,
|
88 |
+
(log) => {
|
89 |
+
dispatch(update_logs(log));
|
90 |
+
},
|
91 |
+
false
|
92 |
+
);
|
93 |
+
}}
|
94 |
+
/>
|
95 |
+
</div>
|
96 |
+
);
|
97 |
+
};
|
98 |
+
|
99 |
+
const Iframe = React.memo(IframePanel);
|
100 |
+
|
101 |
+
export default Iframe;
|
components/Playground/IframeErrorScreen.tsx
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
|
3 |
+
export const IframeErrorScreen = ({ err }: any) => {
|
4 |
+
return (
|
5 |
+
<div className="px-5 pt-10 text-red-600 tracking-wide absolute h-full w-full z-50 backdrop-filter backdrop-blur">
|
6 |
+
{err}
|
7 |
+
</div>
|
8 |
+
);
|
9 |
+
};
|
components/Playground/IframeLoaderScreen.tsx
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
|
3 |
+
export const IframeLoaderScreen = () => {
|
4 |
+
return (
|
5 |
+
<div className="boxes">
|
6 |
+
<div className="box">
|
7 |
+
<div />
|
8 |
+
<div />
|
9 |
+
<div />
|
10 |
+
<div />
|
11 |
+
</div>
|
12 |
+
<div className="box">
|
13 |
+
<div />
|
14 |
+
<div />
|
15 |
+
<div />
|
16 |
+
<div />
|
17 |
+
</div>
|
18 |
+
<div className="box">
|
19 |
+
<div />
|
20 |
+
<div />
|
21 |
+
<div />
|
22 |
+
<div />
|
23 |
+
</div>
|
24 |
+
<div className="box">
|
25 |
+
<div />
|
26 |
+
<div />
|
27 |
+
<div />
|
28 |
+
<div />
|
29 |
+
</div>
|
30 |
+
</div>
|
31 |
+
);
|
32 |
+
};
|
components/Playground/InputCodeTab.tsx
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useEffect, useState } from "react";
|
2 |
+
import Tabs, { TabPane } from "rc-tabs";
|
3 |
+
import { Monaco } from "./Monaco";
|
4 |
+
import { EditorValueInterface } from "../../_types/editorTypes";
|
5 |
+
|
6 |
+
const InputCode = ({ editorValue: parentEditorValue }: { editorValue: EditorValueInterface }) => {
|
7 |
+
const [editorValue, setEditorValue] = useState(parentEditorValue);
|
8 |
+
|
9 |
+
// Update local state when parent state updates
|
10 |
+
useEffect(() => {
|
11 |
+
setEditorValue(parentEditorValue);
|
12 |
+
}, [parentEditorValue]);
|
13 |
+
|
14 |
+
const dataToMap = Object.entries(editorValue.tabs);
|
15 |
+
|
16 |
+
const tabPane = dataToMap.map((item, key) => (
|
17 |
+
<TabPane tab={<div>{item[1].title}</div>} key={key}>
|
18 |
+
<Monaco tab={item[0]} monacoLanguage={item[1].monacoLanguage} />
|
19 |
+
</TabPane>
|
20 |
+
));
|
21 |
+
|
22 |
+
return (
|
23 |
+
<Tabs
|
24 |
+
className="editor-input-tabs"
|
25 |
+
tabPosition="top"
|
26 |
+
tabBarGutter={16}
|
27 |
+
defaultActiveKey="javascript"
|
28 |
+
>
|
29 |
+
{tabPane}
|
30 |
+
</Tabs>
|
31 |
+
);
|
32 |
+
};
|
33 |
+
|
34 |
+
const InputCodeTab = React.memo(InputCode);
|
35 |
+
|
36 |
+
export default InputCodeTab;
|
components/Playground/Monaco.tsx
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useEffect } from "react";
|
2 |
+
import MonacoEditor from "@monaco-editor/react";
|
3 |
+
import { useMonaco } from "../../hooks";
|
4 |
+
import { useAppDispatch, useAppSelector } from "../../store/hook";
|
5 |
+
import {
|
6 |
+
editor_state,
|
7 |
+
update_editor_code,
|
8 |
+
} from "../../store/features/editorSlice";
|
9 |
+
|
10 |
+
type MonacoType = { monacoLanguage: string | undefined; tab: string };
|
11 |
+
|
12 |
+
export const Monaco = ({ monacoLanguage, tab }: MonacoType) => {
|
13 |
+
const dispatch = useAppDispatch();
|
14 |
+
const { monacoInputValue, options } = useAppSelector(editor_state);
|
15 |
+
const { onChange, onMount, code } = useMonaco();
|
16 |
+
|
17 |
+
useEffect(() => {
|
18 |
+
if (code && code?.length >= 1)
|
19 |
+
dispatch(update_editor_code({ type: tab, content: code }));
|
20 |
+
}, [code, dispatch, tab]);
|
21 |
+
|
22 |
+
return (
|
23 |
+
<MonacoEditor
|
24 |
+
onChange={onChange}
|
25 |
+
onMount={onMount}
|
26 |
+
language={monacoLanguage}
|
27 |
+
theme="vs-dark"
|
28 |
+
options={options}
|
29 |
+
className="h-full"
|
30 |
+
// @ts-ignore
|
31 |
+
value={monacoInputValue.tabs[tab]?.data || ''}
|
32 |
+
/>
|
33 |
+
);
|
34 |
+
};
|
components/Playground/Pane.tsx
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { ReactNode } from "react";
|
2 |
+
import { Allotment } from "allotment";
|
3 |
+
import { LayoutPriority } from "allotment/dist/types/src/split-view";
|
4 |
+
|
5 |
+
interface Pane {
|
6 |
+
panelA: ReactNode;
|
7 |
+
panelB: ReactNode;
|
8 |
+
panelC: ReactNode;
|
9 |
+
lastPanelVisibility: boolean;
|
10 |
+
}
|
11 |
+
|
12 |
+
const EditorPanel = ({ panelA, panelB, panelC, lastPanelVisibility }: Pane) => {
|
13 |
+
return (
|
14 |
+
<div style={{ height: "calc(100vh - 8rem)" }}>
|
15 |
+
<Allotment>
|
16 |
+
<Allotment.Pane>{panelA}</Allotment.Pane>
|
17 |
+
|
18 |
+
<Allotment onVisibleChange={function noRefCheck() {}} vertical={true}>
|
19 |
+
<Allotment.Pane
|
20 |
+
priority={"HIGH" as LayoutPriority}
|
21 |
+
preferredSize="70%"
|
22 |
+
visible
|
23 |
+
>
|
24 |
+
{panelB}
|
25 |
+
</Allotment.Pane>
|
26 |
+
|
27 |
+
<Allotment.Pane
|
28 |
+
preferredSize="30%"
|
29 |
+
priority={"LOW" as LayoutPriority}
|
30 |
+
snap
|
31 |
+
visible={lastPanelVisibility}
|
32 |
+
>
|
33 |
+
{panelC}
|
34 |
+
</Allotment.Pane>
|
35 |
+
</Allotment>
|
36 |
+
</Allotment>
|
37 |
+
</div>
|
38 |
+
);
|
39 |
+
};
|
40 |
+
|
41 |
+
export default EditorPanel;
|
components/Playground/index.tsx
ADDED
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useMemo, useRef, useEffect, useState, ChangeEvent, FormEvent } from "react";
|
2 |
+
import { useChat, Message } from "ai/react";
|
3 |
+
import { FunctionCallHandler, nanoid } from "ai";
|
4 |
+
import { useAppDispatch, useAppSelector } from "../../store/hook";
|
5 |
+
import {
|
6 |
+
compiler_state,
|
7 |
+
initEsbuild,
|
8 |
+
} from "../../store/features/compilerSlice";
|
9 |
+
import { editor_state, set_monaco_input_value, update_editor_code } from "../../store/features/editorSlice";
|
10 |
+
import { theme_state } from "../../store/features/themeSlice";
|
11 |
+
import { ModalEnum, open_modal } from "../../store/features/modalSlice";
|
12 |
+
|
13 |
+
import ConsoleLog from "./ConsoleLog";
|
14 |
+
import Iframe from "./Iframe";
|
15 |
+
import InputCodeTab from "./InputCodeTab";
|
16 |
+
import Footer from "./Footer";
|
17 |
+
import Pane from "./Pane";
|
18 |
+
import { SendIcon } from "../../constants/icon";
|
19 |
+
import ReactMarkdown from "react-markdown";
|
20 |
+
|
21 |
+
const Playground = () => {
|
22 |
+
const dispatch = useAppDispatch();
|
23 |
+
const formRef = useRef<HTMLFormElement>(null);
|
24 |
+
const inputRef = useRef<HTMLTextAreaElement>(null);
|
25 |
+
const { theme } = useAppSelector(theme_state);
|
26 |
+
const { esbuildStatus, isCompiling, output } = useAppSelector(compiler_state);
|
27 |
+
const { logs, editorValue, isLogTabOpen } = useAppSelector(editor_state);
|
28 |
+
const [markdownCode, setMarkdownCode] = useState('');
|
29 |
+
const [prevMarkdownCode, setPrevMarkdownCode] = useState(markdownCode);
|
30 |
+
|
31 |
+
const isValidCodeBlock = (markdownCode: string) => {
|
32 |
+
return markdownCode && markdownCode.length > 10 && markdownCode.includes('\n');
|
33 |
+
}
|
34 |
+
|
35 |
+
useEffect(() => {
|
36 |
+
const timer = setInterval(() => {
|
37 |
+
if (isValidCodeBlock(markdownCode) && markdownCode !== prevMarkdownCode) {
|
38 |
+
dispatch(update_editor_code({ type: 'javascript', content: markdownCode }));
|
39 |
+
setPrevMarkdownCode(markdownCode);
|
40 |
+
}
|
41 |
+
}, 2000);
|
42 |
+
|
43 |
+
return () => {
|
44 |
+
clearInterval(timer);
|
45 |
+
};
|
46 |
+
}, [markdownCode, prevMarkdownCode, dispatch]);
|
47 |
+
|
48 |
+
|
49 |
+
const { messages, input, setInput, handleSubmit } = useChat({
|
50 |
+
onError: (error) => {
|
51 |
+
console.error(error);
|
52 |
+
},
|
53 |
+
});
|
54 |
+
|
55 |
+
useEffect(() => {
|
56 |
+
if (!esbuildStatus.isReady) {
|
57 |
+
dispatch(initEsbuild());
|
58 |
+
}
|
59 |
+
}, [dispatch, esbuildStatus]);
|
60 |
+
|
61 |
+
useEffect(() => {
|
62 |
+
dispatch(open_modal(ModalEnum.TEMPLATE));
|
63 |
+
}, [dispatch]);
|
64 |
+
|
65 |
+
useEffect(() => {
|
66 |
+
if(isValidCodeBlock(markdownCode)) {
|
67 |
+
const newEditorValue = {
|
68 |
+
name: "React",
|
69 |
+
description: "By codetree",
|
70 |
+
public: true,
|
71 |
+
iconSrc: "/icons/reactjs.svg",
|
72 |
+
tabs: {
|
73 |
+
javascript: {
|
74 |
+
title: "JS/JSX",
|
75 |
+
entryPoints: "index.js",
|
76 |
+
monacoLanguage: "javascript",
|
77 |
+
data: markdownCode
|
78 |
+
},
|
79 |
+
html: {
|
80 |
+
title: "index.html",
|
81 |
+
entryPoints: "index.html",
|
82 |
+
monacoLanguage: "html",
|
83 |
+
data: ""
|
84 |
+
},
|
85 |
+
css: {
|
86 |
+
title: "main.css",
|
87 |
+
entryPoints: "main.css",
|
88 |
+
monacoLanguage: "css",
|
89 |
+
data: ""
|
90 |
+
}
|
91 |
+
}
|
92 |
+
};
|
93 |
+
|
94 |
+
dispatch(set_monaco_input_value(newEditorValue as any));
|
95 |
+
}
|
96 |
+
}, [markdownCode, dispatch]);
|
97 |
+
|
98 |
+
return (
|
99 |
+
<div style={{ background: theme.background }}>
|
100 |
+
<div className="flex flex-col">
|
101 |
+
<div className="px-4 pb-2 pt-3 shadow-lg sm:pb-3 sm:pt-4">
|
102 |
+
<form
|
103 |
+
ref={formRef}
|
104 |
+
onSubmit={handleSubmit}
|
105 |
+
className="relative w-full"
|
106 |
+
>
|
107 |
+
<textarea ref={inputRef} onChange={(e) => setInput(e.target.value)}
|
108 |
+
placeholder="Chat with GPT-4 and code blocks will automatically render in the editor! Codetree uses WebAssembly and Esbuild to compile in the browser."
|
109 |
+
onKeyDown={(e) => {
|
110 |
+
if (e.key === "Enter" && !e.shiftKey) {
|
111 |
+
formRef.current?.requestSubmit();
|
112 |
+
e.preventDefault();
|
113 |
+
}
|
114 |
+
}}
|
115 |
+
spellCheck={false} className="textarea" value={input} />
|
116 |
+
<button
|
117 |
+
type="submit"
|
118 |
+
className="absolute inset-y-0 right-3 my-auto flex h-8 w-8 items-center justify-center rounded-md transition-all"
|
119 |
+
>
|
120 |
+
<SendIcon
|
121 |
+
className={"h-4 w-4"}
|
122 |
+
/>
|
123 |
+
</button>
|
124 |
+
</form>
|
125 |
+
</div>
|
126 |
+
<div className="flex flex-col items-start space-y-4 overflow-y-auto max-h-[20vh]">
|
127 |
+
{messages?.map((message, index) => (
|
128 |
+
<p key={index} className="messages-text">
|
129 |
+
<ReactMarkdown
|
130 |
+
className="prose mt-1 w-full break-words prose-p:leading-relaxed"
|
131 |
+
components={{
|
132 |
+
a: (props) => (
|
133 |
+
<a {...props} target="_blank" rel="noopener noreferrer" />
|
134 |
+
),
|
135 |
+
// @ts-ignore
|
136 |
+
code: ({node, ...props}) => {
|
137 |
+
const codeValue = props.children[0] || '';
|
138 |
+
setMarkdownCode(codeValue as any);
|
139 |
+
|
140 |
+
return null;
|
141 |
+
}
|
142 |
+
}}
|
143 |
+
>
|
144 |
+
{message.content}
|
145 |
+
</ReactMarkdown>
|
146 |
+
</p>
|
147 |
+
))}
|
148 |
+
</div>
|
149 |
+
</div>
|
150 |
+
<Pane
|
151 |
+
panelA={<InputCodeTab editorValue={editorValue} />}
|
152 |
+
panelB={
|
153 |
+
<Iframe
|
154 |
+
tabs={editorValue.tabs}
|
155 |
+
output={output}
|
156 |
+
isCompiling={isCompiling}
|
157 |
+
esbuildStatus={esbuildStatus}
|
158 |
+
/>
|
159 |
+
}
|
160 |
+
panelC={<ConsoleLog logs={logs} />}
|
161 |
+
lastPanelVisibility={isLogTabOpen}
|
162 |
+
/>
|
163 |
+
|
164 |
+
<Footer />
|
165 |
+
</div>
|
166 |
+
|
167 |
+
);
|
168 |
+
};
|
169 |
+
|
170 |
+
export default Playground;
|
components/Skeleton/TemplateSelectionSkeleton.tsx
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
|
3 |
+
export const TemplateSelectionSkeleton = () => {
|
4 |
+
let items = [];
|
5 |
+
|
6 |
+
for (let i = 0; i < 10; i++) {
|
7 |
+
items.push(
|
8 |
+
<div key={i} className="flex animate-pulse">
|
9 |
+
<div className="h-9 w-9 bg-gray-400 rounded-sm" />
|
10 |
+
<div className="flex flex-col items-start justify-start pl-4 space-y-2.5 mt-1.5">
|
11 |
+
<div className="h-2 w-12 bg-gray-400 rounded-sm" />
|
12 |
+
<div className="h-2 w-20 bg-gray-400 rounded-sm" />
|
13 |
+
</div>
|
14 |
+
</div>
|
15 |
+
);
|
16 |
+
}
|
17 |
+
|
18 |
+
return <>{items}</>;
|
19 |
+
};
|
constants/icon.tsx
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
|
3 |
+
export const SendIcon = ({ className }: { className?: string }) => {
|
4 |
+
return (
|
5 |
+
<svg
|
6 |
+
xmlns="http://www.w3.org/2000/svg"
|
7 |
+
viewBox="0 0 16 16"
|
8 |
+
fill="none"
|
9 |
+
className={className}
|
10 |
+
strokeWidth="2"
|
11 |
+
>
|
12 |
+
<path
|
13 |
+
d="M.5 1.163A1 1 0 0 1 1.97.28l12.868 6.837a1 1 0 0 1 0 1.766L1.969 15.72A1 1 0 0 1 .5 14.836V10.33a1 1 0 0 1 .816-.983L8.5 8 1.316 6.653A1 1 0 0 1 .5 5.67V1.163Z"
|
14 |
+
fill="currentColor"
|
15 |
+
></path>
|
16 |
+
</svg>
|
17 |
+
);
|
18 |
+
};
|
constants/index.ts
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
export * from "./templates";
|
2 |
+
export * from "./monacoOptions";
|
constants/monacoOptions.ts
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { editor } from "monaco-editor";
|
2 |
+
|
3 |
+
export const monacoOptions: editor.IStandaloneEditorConstructionOptions = {
|
4 |
+
fontSize: 12,
|
5 |
+
fontWeight: "500",
|
6 |
+
minimap: {
|
7 |
+
enabled: false,
|
8 |
+
},
|
9 |
+
wordWrap: "on",
|
10 |
+
autoClosingBrackets: "always",
|
11 |
+
showUnused: true,
|
12 |
+
automaticLayout: true,
|
13 |
+
tabSize: 2,
|
14 |
+
renderLineHighlight: "none",
|
15 |
+
scrollbar: { verticalScrollbarSize: 10, verticalSliderSize: 10 },
|
16 |
+
};
|
constants/templates.ts
ADDED
@@ -0,0 +1,271 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export const treeTemplates = {
|
2 |
+
_empty: {
|
3 |
+
name: "Empty",
|
4 |
+
description: "By codetree",
|
5 |
+
public: true,
|
6 |
+
iconSrc: "/icons/vanilla.svg",
|
7 |
+
tabs: {
|
8 |
+
javascript: {
|
9 |
+
title: "app.jsx",
|
10 |
+
entryPoints: "index.js",
|
11 |
+
monacoLanguage: "javascript",
|
12 |
+
data: ``,
|
13 |
+
},
|
14 |
+
html: {
|
15 |
+
title: "index.html",
|
16 |
+
entryPoints: "index.html",
|
17 |
+
monacoLanguage: "html",
|
18 |
+
data: ``,
|
19 |
+
},
|
20 |
+
css: {
|
21 |
+
title: "main.css",
|
22 |
+
entryPoints: "main.css",
|
23 |
+
monacoLanguage: "css",
|
24 |
+
data: ``,
|
25 |
+
},
|
26 |
+
},
|
27 |
+
},
|
28 |
+
_vanilla: {
|
29 |
+
name: "Vanilla",
|
30 |
+
description: "By codetree",
|
31 |
+
public: true,
|
32 |
+
iconSrc: "/icons/vanilla.svg",
|
33 |
+
tabs: {
|
34 |
+
javascript: {
|
35 |
+
title: "JS/JSX",
|
36 |
+
entryPoints: "index.js",
|
37 |
+
monacoLanguage: "javascript",
|
38 |
+
data: `document.getElementById("app").innerHTML = \`
|
39 |
+
<h1>Vanilla</h1>
|
40 |
+
<div>
|
41 |
+
Bare minimal javascript template
|
42 |
+
</div>
|
43 |
+
\`;
|
44 |
+
`,
|
45 |
+
},
|
46 |
+
html: {
|
47 |
+
title: "html",
|
48 |
+
entryPoints: "index.html",
|
49 |
+
monacoLanguage: "html",
|
50 |
+
data: `<div id="app"></div>`,
|
51 |
+
},
|
52 |
+
css: {
|
53 |
+
title: "Css",
|
54 |
+
entryPoints: "main.css",
|
55 |
+
monacoLanguage: "css",
|
56 |
+
data: `body {
|
57 |
+
font-family: sans-serif;
|
58 |
+
}
|
59 |
+
|
60 |
+
#app {
|
61 |
+
display: flex;
|
62 |
+
flex-direction: column;
|
63 |
+
justify-content: center;
|
64 |
+
align-items: center;
|
65 |
+
}
|
66 |
+
`,
|
67 |
+
},
|
68 |
+
},
|
69 |
+
},
|
70 |
+
_typescript: {
|
71 |
+
name: "Typescript",
|
72 |
+
description: "By codetree",
|
73 |
+
public: true,
|
74 |
+
iconSrc: "/icons/typescript.svg",
|
75 |
+
tabs: {
|
76 |
+
javascript: {
|
77 |
+
title: "Ts/Tsx",
|
78 |
+
entryPoints: "index.ts",
|
79 |
+
monacoLanguage: "typescript",
|
80 |
+
data: `function add(x: number, y: number): number {
|
81 |
+
return x + y;
|
82 |
+
}
|
83 |
+
|
84 |
+
const result = add(2,5)
|
85 |
+
|
86 |
+
console.log(result)`,
|
87 |
+
},
|
88 |
+
html: {
|
89 |
+
title: "Html",
|
90 |
+
entryPoints: "index.html",
|
91 |
+
monacoLanguage: "html",
|
92 |
+
data: `<div id="app">
|
93 |
+
<h1>Typescript</h1>
|
94 |
+
<div>Bare minimal Typescript template 🚀</div>
|
95 |
+
</div>
|
96 |
+
`,
|
97 |
+
},
|
98 |
+
css: {
|
99 |
+
title: "Css",
|
100 |
+
entryPoints: "main.css",
|
101 |
+
monacoLanguage: "css",
|
102 |
+
data: `body {
|
103 |
+
font-family: sans-serif;
|
104 |
+
}
|
105 |
+
|
106 |
+
#app {
|
107 |
+
display: flex;
|
108 |
+
flex-direction: column;
|
109 |
+
justify-content: center;
|
110 |
+
align-items: center;
|
111 |
+
}
|
112 |
+
`,
|
113 |
+
},
|
114 |
+
},
|
115 |
+
},
|
116 |
+
_p5: {
|
117 |
+
name: "P5js",
|
118 |
+
description: "By codetree",
|
119 |
+
public: true,
|
120 |
+
iconSrc: "/icons/p5-dot-js.svg",
|
121 |
+
tabs: {
|
122 |
+
javascript: {
|
123 |
+
title: "JS/JSX",
|
124 |
+
entryPoints: "index.js",
|
125 |
+
monacoLanguage: "javascript",
|
126 |
+
data: `import p5 from "p5";
|
127 |
+
|
128 |
+
let sketch = function (p) {
|
129 |
+
p.setup = function () {
|
130 |
+
p.createCanvas(window.innerWidth, window.innerHeight);
|
131 |
+
p.background(0);
|
132 |
+
};
|
133 |
+
p.draw = () => {
|
134 |
+
// sketch.background(100);
|
135 |
+
p.fill(255);
|
136 |
+
p.ellipse(p.mouseX, p.mouseY, 50, 50);
|
137 |
+
};
|
138 |
+
};
|
139 |
+
|
140 |
+
new p5(sketch, window.document.getElementById("container"));
|
141 |
+
`,
|
142 |
+
},
|
143 |
+
html: {
|
144 |
+
title: "Html",
|
145 |
+
entryPoints: "index.html",
|
146 |
+
monacoLanguage: "html",
|
147 |
+
data: ``,
|
148 |
+
},
|
149 |
+
css: {
|
150 |
+
title: "Css",
|
151 |
+
entryPoints: "main.css",
|
152 |
+
monacoLanguage: "css",
|
153 |
+
data: `body {
|
154 |
+
margin: 0px;
|
155 |
+
font-family: sans-serif;
|
156 |
+
}
|
157 |
+
canvas {
|
158 |
+
margin: 0px;
|
159 |
+
}
|
160 |
+
`,
|
161 |
+
},
|
162 |
+
},
|
163 |
+
},
|
164 |
+
_react: {
|
165 |
+
name: "React",
|
166 |
+
description: "By codetree",
|
167 |
+
public: true,
|
168 |
+
iconSrc: "/icons/reactjs.svg",
|
169 |
+
tabs: {
|
170 |
+
javascript: {
|
171 |
+
title: "JS/JSX",
|
172 |
+
entryPoints: "index.js",
|
173 |
+
monacoLanguage: "javascript",
|
174 |
+
data: `import React, { useState } from "react";
|
175 |
+
import ReactDOM from "react-dom";
|
176 |
+
|
177 |
+
function App() {
|
178 |
+
const [count, setCount] = useState(0);
|
179 |
+
|
180 |
+
return (
|
181 |
+
<div className="App">
|
182 |
+
<h1>Hello ReactTree</h1>
|
183 |
+
<h2>You clicked {count} times!</h2>
|
184 |
+
|
185 |
+
<button onClick={() => setCount(count - 1)}>Decrement</button>
|
186 |
+
<button onClick={() => setCount(count + 1)}>Increment</button>
|
187 |
+
</div>
|
188 |
+
);
|
189 |
+
}
|
190 |
+
|
191 |
+
const rootElement = document.getElementById("root");
|
192 |
+
ReactDOM.render(<App />, rootElement);
|
193 |
+
`,
|
194 |
+
},
|
195 |
+
html: {
|
196 |
+
title: "index.html",
|
197 |
+
entryPoints: "index.html",
|
198 |
+
monacoLanguage: "html",
|
199 |
+
data: ``,
|
200 |
+
},
|
201 |
+
css: {
|
202 |
+
title: "main.css",
|
203 |
+
entryPoints: "main.css",
|
204 |
+
monacoLanguage: "css",
|
205 |
+
data: `.App {
|
206 |
+
font-family: sans-serif;
|
207 |
+
text-align: center;
|
208 |
+
}
|
209 |
+
`,
|
210 |
+
},
|
211 |
+
},
|
212 |
+
},
|
213 |
+
_gsap: {
|
214 |
+
name: "Gsap",
|
215 |
+
description: "By codetree",
|
216 |
+
public: true,
|
217 |
+
iconSrc: "/icons/gsap-greensock.svg",
|
218 |
+
tabs: {
|
219 |
+
javascript: {
|
220 |
+
title: "JS/JSX",
|
221 |
+
entryPoints: "index.js",
|
222 |
+
monacoLanguage: "javascript",
|
223 |
+
data: `import gsap from "gsap"
|
224 |
+
|
225 |
+
var tl = gsap.timeline({ repeat: -1 });
|
226 |
+
tl.to("h1", 30, { backgroundPosition: "-960px 0" });
|
227 |
+
`,
|
228 |
+
},
|
229 |
+
html: {
|
230 |
+
title: "Html",
|
231 |
+
entryPoints: "index.html",
|
232 |
+
monacoLanguage: "html",
|
233 |
+
data: `<div class="wrapper">
|
234 |
+
<h1 class="title">Codetree<h1>
|
235 |
+
</div> `,
|
236 |
+
},
|
237 |
+
css: {
|
238 |
+
title: "Css",
|
239 |
+
entryPoints: "main.css",
|
240 |
+
monacoLanguage: "css",
|
241 |
+
data: `body {
|
242 |
+
background-color: #1d1d1d;
|
243 |
+
margin: 0;
|
244 |
+
padding: 0;
|
245 |
+
}
|
246 |
+
|
247 |
+
.wrapper {
|
248 |
+
display: flex;
|
249 |
+
height: 100vh;
|
250 |
+
justify-content: center;
|
251 |
+
align-items: center;
|
252 |
+
}
|
253 |
+
|
254 |
+
h1 {
|
255 |
+
max-width: 75%;
|
256 |
+
font-size: 100px;
|
257 |
+
text-align: center;
|
258 |
+
font-family: "Montserrat", sans-serif;
|
259 |
+
color: #540032;
|
260 |
+
}
|
261 |
+
|
262 |
+
.title {
|
263 |
+
background-image: url(https://cdn.pixabay.com/photo/2017/07/03/20/17/abstract-2468874_960_720.jpg);
|
264 |
+
background-attachment: fixed;
|
265 |
+
-webkit-text-fill-color: transparent;
|
266 |
+
-webkit-background-clip: text;
|
267 |
+
}`,
|
268 |
+
},
|
269 |
+
},
|
270 |
+
},
|
271 |
+
};
|
editor.d.ts
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
declare module "react-split";
|
2 |
+
declare module "monaco-jsx-highlighter";
|
esbuild/plugins/index.ts
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
export * from "./unpkg-fecth-plugin";
|
2 |
+
export * from "./unpkg-path-plugin";
|
esbuild/plugins/unpkg-fecth-plugin.ts
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as esbuild from "esbuild-wasm";
|
2 |
+
import axios from "axios";
|
3 |
+
import localForage from "localforage";
|
4 |
+
|
5 |
+
const fileCache = localForage.createInstance({
|
6 |
+
name: "fileCache",
|
7 |
+
});
|
8 |
+
|
9 |
+
export const unpkgFetchPlugin = (
|
10 |
+
inputCode: string | undefined,
|
11 |
+
entryPoint: string
|
12 |
+
): esbuild.Plugin => {
|
13 |
+
return {
|
14 |
+
name: "unpkg-fetch-plugin",
|
15 |
+
setup(build: esbuild.PluginBuild) {
|
16 |
+
//match entrypoint
|
17 |
+
if (entryPoint === "index.ts") {
|
18 |
+
build.onLoad({ filter: /(^index\.ts$)/ }, () => {
|
19 |
+
return {
|
20 |
+
loader: "tsx",
|
21 |
+
contents: inputCode,
|
22 |
+
};
|
23 |
+
});
|
24 |
+
} else {
|
25 |
+
build.onLoad({ filter: /(^index\.js$)/ }, () => {
|
26 |
+
return {
|
27 |
+
loader: "jsx",
|
28 |
+
contents: inputCode,
|
29 |
+
};
|
30 |
+
});
|
31 |
+
}
|
32 |
+
|
33 |
+
build.onLoad({ filter: /.*/ }, async (args: esbuild.OnLoadArgs) => {
|
34 |
+
const cacheResult = await fileCache.getItem<esbuild.OnLoadResult>(
|
35 |
+
args.path
|
36 |
+
);
|
37 |
+
if (cacheResult) {
|
38 |
+
return cacheResult;
|
39 |
+
}
|
40 |
+
});
|
41 |
+
|
42 |
+
//match css file
|
43 |
+
build.onLoad({ filter: /.css$/ }, async (args: esbuild.OnLoadArgs) => {
|
44 |
+
const { data, request } = await axios.get(args.path);
|
45 |
+
|
46 |
+
const escapedData = data
|
47 |
+
.replace(/\n/g, "")
|
48 |
+
.replace(/"/g, '\\"')
|
49 |
+
.replace(/'/g, "\\'");
|
50 |
+
|
51 |
+
const contents = `const style = document.createElement("style");
|
52 |
+
style.innerText = '${escapedData}';
|
53 |
+
document.head.appendChild(style);`;
|
54 |
+
|
55 |
+
const result: esbuild.OnLoadResult = {
|
56 |
+
loader: "jsx",
|
57 |
+
contents,
|
58 |
+
//specify the place where the content was found
|
59 |
+
resolveDir: new URL("./", request.responseURL).pathname,
|
60 |
+
};
|
61 |
+
//store response in cache
|
62 |
+
await fileCache.setItem(args.path, result);
|
63 |
+
return result;
|
64 |
+
});
|
65 |
+
|
66 |
+
//=================================================
|
67 |
+
|
68 |
+
build.onLoad({ filter: /.*/ }, async (args: esbuild.OnLoadArgs) => {
|
69 |
+
console.log(`...fetching ${args.path}`);
|
70 |
+
const { data, request } = await axios.get(args.path);
|
71 |
+
|
72 |
+
const result: esbuild.OnLoadResult = {
|
73 |
+
loader: "jsx",
|
74 |
+
contents: data,
|
75 |
+
//specify the place where the content was found
|
76 |
+
resolveDir: new URL("./", request.responseURL).pathname,
|
77 |
+
};
|
78 |
+
//store response in cache
|
79 |
+
await fileCache.setItem(args.path, result);
|
80 |
+
console.log("end of fetching");
|
81 |
+
return result;
|
82 |
+
});
|
83 |
+
},
|
84 |
+
};
|
85 |
+
};
|
esbuild/plugins/unpkg-path-plugin.ts
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as esbuild from "esbuild-wasm";
|
2 |
+
|
3 |
+
export const unpkgPathPlugin = (): esbuild.Plugin => {
|
4 |
+
return {
|
5 |
+
name: "unpkg-path-plugin",
|
6 |
+
setup(build: esbuild.PluginBuild) {
|
7 |
+
//
|
8 |
+
build.onResolve({ filter: /.*/ }, (args) => {
|
9 |
+
if (args.kind === "entry-point") {
|
10 |
+
return { path: args.path, namespace: "a" };
|
11 |
+
}
|
12 |
+
});
|
13 |
+
|
14 |
+
//match relative path in a module "./" or "../"
|
15 |
+
build.onResolve({ filter: /^\.+\// }, (args: esbuild.OnResolveArgs) => {
|
16 |
+
return {
|
17 |
+
namespace: "a",
|
18 |
+
path: new URL(args.path, `https://unpkg.com${args.resolveDir}/`).href,
|
19 |
+
};
|
20 |
+
});
|
21 |
+
|
22 |
+
//match main file in a module
|
23 |
+
build.onResolve({ filter: /.*/ }, async (args: esbuild.OnResolveArgs) => {
|
24 |
+
return {
|
25 |
+
namespace: "a",
|
26 |
+
path: `https://unpkg.com/${args.path}`,
|
27 |
+
};
|
28 |
+
});
|
29 |
+
},
|
30 |
+
};
|
31 |
+
};
|
graphql.schema.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
graphql/definitions/auth.definition.ts
ADDED
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { gql } from "@apollo/client";
|
2 |
+
|
3 |
+
export const meQuery = gql(`query Me {
|
4 |
+
me {
|
5 |
+
data {
|
6 |
+
id
|
7 |
+
username
|
8 |
+
email
|
9 |
+
avatar
|
10 |
+
bio
|
11 |
+
website
|
12 |
+
verifiedAt
|
13 |
+
createdAt
|
14 |
+
updatedAt
|
15 |
+
showProfession
|
16 |
+
projects {
|
17 |
+
id
|
18 |
+
title
|
19 |
+
description
|
20 |
+
}
|
21 |
+
firstname
|
22 |
+
lastname
|
23 |
+
jobTitle
|
24 |
+
country
|
25 |
+
}
|
26 |
+
message
|
27 |
+
status
|
28 |
+
}
|
29 |
+
}`);
|
30 |
+
|
31 |
+
export const registerMutation =
|
32 |
+
gql(`mutation Register($input: UserRegisterInput!) {
|
33 |
+
register(input: $input) {
|
34 |
+
data {
|
35 |
+
id
|
36 |
+
email
|
37 |
+
username
|
38 |
+
avatar
|
39 |
+
bio
|
40 |
+
website
|
41 |
+
verifiedAt
|
42 |
+
createdAt
|
43 |
+
updatedAt
|
44 |
+
}
|
45 |
+
token
|
46 |
+
message
|
47 |
+
status
|
48 |
+
}
|
49 |
+
}`);
|
50 |
+
|
51 |
+
export const loginMutation = gql(`mutation Login($input: UserLoginInput!) {
|
52 |
+
login(input: $input) {
|
53 |
+
data {
|
54 |
+
id
|
55 |
+
email
|
56 |
+
username
|
57 |
+
avatar
|
58 |
+
bio
|
59 |
+
website
|
60 |
+
verifiedAt
|
61 |
+
createdAt
|
62 |
+
updatedAt
|
63 |
+
}
|
64 |
+
token
|
65 |
+
message
|
66 |
+
status
|
67 |
+
}
|
68 |
+
}`);
|
69 |
+
|
70 |
+
export const verifiedUserMutation =
|
71 |
+
gql(`mutation VerifyUser($input: UserVerifyInput!) {
|
72 |
+
verifyUser(input: $input) {
|
73 |
+
message
|
74 |
+
status
|
75 |
+
}
|
76 |
+
}`);
|
77 |
+
|
78 |
+
export const googleOauthMutation =
|
79 |
+
gql(`mutation Mutation($input: GoogleAuthInput!) {
|
80 |
+
googleOauth(input: $input) {
|
81 |
+
data {
|
82 |
+
id
|
83 |
+
email
|
84 |
+
username
|
85 |
+
avatar
|
86 |
+
bio
|
87 |
+
website
|
88 |
+
verifiedAt
|
89 |
+
createdAt
|
90 |
+
updatedAt
|
91 |
+
}
|
92 |
+
token
|
93 |
+
message
|
94 |
+
status
|
95 |
+
}
|
96 |
+
}`);
|
97 |
+
|
98 |
+
export const githubOauthMutation =
|
99 |
+
gql(`mutation Mutation($input: GithubAuthInput!) {
|
100 |
+
githubOauth(input: $input) {
|
101 |
+
data {
|
102 |
+
id
|
103 |
+
email
|
104 |
+
username
|
105 |
+
avatar
|
106 |
+
bio
|
107 |
+
website
|
108 |
+
verifiedAt
|
109 |
+
createdAt
|
110 |
+
updatedAt
|
111 |
+
}
|
112 |
+
token
|
113 |
+
message
|
114 |
+
status
|
115 |
+
}
|
116 |
+
}`);
|
117 |
+
|
118 |
+
export const updateProfileMutation =
|
119 |
+
gql(`mutation UpdateProfile($input: UpdateProfileInput) {
|
120 |
+
updateProfile(input: $input) {
|
121 |
+
data {
|
122 |
+
avatar
|
123 |
+
username
|
124 |
+
}
|
125 |
+
message
|
126 |
+
status
|
127 |
+
}
|
128 |
+
}`);
|
129 |
+
|
130 |
+
export const resetPasswordMutation =
|
131 |
+
gql(`mutation ResetPassword($input: UserResetPasswordInput!) {
|
132 |
+
resetPassword(input: $input) {
|
133 |
+
status
|
134 |
+
message
|
135 |
+
data {
|
136 |
+
id
|
137 |
+
email
|
138 |
+
token
|
139 |
+
expiredAt
|
140 |
+
createdAt
|
141 |
+
updatedAt
|
142 |
+
}
|
143 |
+
}
|
144 |
+
}`);
|
145 |
+
|
146 |
+
export const forgotPasswordMutation =
|
147 |
+
gql(`mutation ForgotPassword($input: UserForgotPasswordInput!) {
|
148 |
+
forgotPassword(input: $input) {
|
149 |
+
message
|
150 |
+
status
|
151 |
+
}
|
152 |
+
}`);
|
graphql/definitions/comment.definition.ts
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { gql } from "@apollo/client";
|
2 |
+
|
3 |
+
export const CommentsQuery = gql(`query Comments($input: CommentsInput!) {
|
4 |
+
comments(input: $input) {
|
5 |
+
data {
|
6 |
+
id
|
7 |
+
message
|
8 |
+
parentId
|
9 |
+
authorId
|
10 |
+
createdAt
|
11 |
+
updatedAt
|
12 |
+
author {
|
13 |
+
id
|
14 |
+
email
|
15 |
+
username
|
16 |
+
avatar
|
17 |
+
}
|
18 |
+
}
|
19 |
+
message
|
20 |
+
status
|
21 |
+
}
|
22 |
+
}`);
|
23 |
+
|
24 |
+
export const CreateCommentMutation =
|
25 |
+
gql(`mutation CreateComment($input: CreateCommentInput!) {
|
26 |
+
createComment(input: $input) {
|
27 |
+
data {
|
28 |
+
id
|
29 |
+
message
|
30 |
+
parentId
|
31 |
+
authorId
|
32 |
+
createdAt
|
33 |
+
updatedAt
|
34 |
+
author {
|
35 |
+
id
|
36 |
+
email
|
37 |
+
username
|
38 |
+
avatar
|
39 |
+
}
|
40 |
+
}
|
41 |
+
message
|
42 |
+
status
|
43 |
+
}
|
44 |
+
}`);
|
45 |
+
|
46 |
+
export const UpdateCommentMutation =
|
47 |
+
gql(`mutation UpdateComment($input: UpdateCommentInput!) {
|
48 |
+
updateComment(input: $input) {
|
49 |
+
data {
|
50 |
+
id
|
51 |
+
message
|
52 |
+
parentId
|
53 |
+
authorId
|
54 |
+
createdAt
|
55 |
+
updatedAt
|
56 |
+
author {
|
57 |
+
id
|
58 |
+
email
|
59 |
+
username
|
60 |
+
avatar
|
61 |
+
}
|
62 |
+
}
|
63 |
+
message
|
64 |
+
status
|
65 |
+
}
|
66 |
+
}`);
|
67 |
+
|
68 |
+
export const DeleteCommentMutation =
|
69 |
+
gql(`mutation DeleteComment($input: DeleteCommentInput!) {
|
70 |
+
deleteComment(input: $input) {
|
71 |
+
data {
|
72 |
+
id
|
73 |
+
}
|
74 |
+
message
|
75 |
+
status
|
76 |
+
}
|
77 |
+
}`);
|
78 |
+
|
79 |
+
export const CommentCreatedSubscription = gql(`subscription CommentCreated {
|
80 |
+
commentCreated {
|
81 |
+
type
|
82 |
+
message
|
83 |
+
data {
|
84 |
+
id
|
85 |
+
type
|
86 |
+
receiverId
|
87 |
+
projectId
|
88 |
+
content
|
89 |
+
viewed
|
90 |
+
createdAt
|
91 |
+
sender {
|
92 |
+
avatar
|
93 |
+
username
|
94 |
+
}
|
95 |
+
}
|
96 |
+
}
|
97 |
+
}`);
|
graphql/definitions/notification.definition.ts
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { gql } from "@apollo/client";
|
2 |
+
|
3 |
+
export const NotificationQuery =
|
4 |
+
gql(`query Notifications($input: NotificationInput!) {
|
5 |
+
notifications(input: $input) {
|
6 |
+
message
|
7 |
+
status
|
8 |
+
data {
|
9 |
+
id
|
10 |
+
type
|
11 |
+
receiverId
|
12 |
+
projectId
|
13 |
+
projectSlug
|
14 |
+
content
|
15 |
+
viewed
|
16 |
+
createdAt
|
17 |
+
sender {
|
18 |
+
avatar
|
19 |
+
username
|
20 |
+
}
|
21 |
+
}
|
22 |
+
}
|
23 |
+
}`);
|
24 |
+
|
25 |
+
export const ViewNotificationMutation =
|
26 |
+
gql(`mutation ViewNotification($input: ViewNotificationInput!) {
|
27 |
+
viewNotification(input: $input) {
|
28 |
+
message
|
29 |
+
status
|
30 |
+
}
|
31 |
+
}`);
|
graphql/definitions/pageView.definition.ts
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { gql } from "@apollo/client";
|
2 |
+
|
3 |
+
export const PageViewQuery = gql(`query pageViews {
|
4 |
+
pageViews {
|
5 |
+
data {
|
6 |
+
id
|
7 |
+
ip
|
8 |
+
createdAt
|
9 |
+
updatedAt
|
10 |
+
}
|
11 |
+
message
|
12 |
+
status
|
13 |
+
}
|
14 |
+
}`);
|
graphql/definitions/project.definition.ts
ADDED
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { gql } from "@apollo/client";
|
2 |
+
|
3 |
+
export const ProjectsQuery = gql(`query Projects($input: ProjectsInput!) {
|
4 |
+
projects(input: $input) {
|
5 |
+
data {
|
6 |
+
id
|
7 |
+
createdAt
|
8 |
+
title
|
9 |
+
slug
|
10 |
+
description
|
11 |
+
content
|
12 |
+
updatedAt
|
13 |
+
hasvoted
|
14 |
+
isProjectOwner
|
15 |
+
files
|
16 |
+
tags {
|
17 |
+
id
|
18 |
+
value
|
19 |
+
}
|
20 |
+
_count {
|
21 |
+
votes
|
22 |
+
comments
|
23 |
+
}
|
24 |
+
author {
|
25 |
+
id
|
26 |
+
username
|
27 |
+
avatar
|
28 |
+
}
|
29 |
+
comments {
|
30 |
+
author {
|
31 |
+
id
|
32 |
+
username
|
33 |
+
avatar
|
34 |
+
}
|
35 |
+
}
|
36 |
+
}
|
37 |
+
message
|
38 |
+
status
|
39 |
+
}
|
40 |
+
}`);
|
41 |
+
|
42 |
+
export const ProjectsIDsQuery = gql(`query Projects($input: ProjectsInput!) {
|
43 |
+
projects(input: $input) {
|
44 |
+
data {
|
45 |
+
id
|
46 |
+
}
|
47 |
+
message
|
48 |
+
status
|
49 |
+
}
|
50 |
+
}`);
|
51 |
+
|
52 |
+
export const ProjectQuery = gql(`query Project($input: ProjectInput!) {
|
53 |
+
project(input: $input) {
|
54 |
+
message
|
55 |
+
status
|
56 |
+
data {
|
57 |
+
id
|
58 |
+
title
|
59 |
+
slug
|
60 |
+
description
|
61 |
+
content
|
62 |
+
files
|
63 |
+
author {
|
64 |
+
id
|
65 |
+
username
|
66 |
+
avatar
|
67 |
+
}
|
68 |
+
tags {
|
69 |
+
id
|
70 |
+
value
|
71 |
+
}
|
72 |
+
_count {
|
73 |
+
votes
|
74 |
+
comments
|
75 |
+
}
|
76 |
+
createdAt
|
77 |
+
updatedAt
|
78 |
+
hasvoted
|
79 |
+
isProjectOwner
|
80 |
+
votes {
|
81 |
+
author {
|
82 |
+
id
|
83 |
+
username
|
84 |
+
avatar
|
85 |
+
}
|
86 |
+
}
|
87 |
+
}
|
88 |
+
}
|
89 |
+
usersWhoComment(input: $input) {
|
90 |
+
data {
|
91 |
+
id
|
92 |
+
email
|
93 |
+
username
|
94 |
+
avatar
|
95 |
+
}
|
96 |
+
message
|
97 |
+
status
|
98 |
+
}
|
99 |
+
}`);
|
100 |
+
|
101 |
+
export const HasVotedProjectQuery =
|
102 |
+
gql(`query HasVotedProjectQuery($input: ProjectInput!) {
|
103 |
+
project(input: $input) {
|
104 |
+
data {
|
105 |
+
hasvoted
|
106 |
+
_count {
|
107 |
+
votes
|
108 |
+
}
|
109 |
+
}
|
110 |
+
message
|
111 |
+
status
|
112 |
+
}
|
113 |
+
}`);
|
114 |
+
|
115 |
+
export const CreateProjectMutation =
|
116 |
+
gql(`mutation CreateProject($input: CreateProjectInput!) {
|
117 |
+
createProject(input: $input) {
|
118 |
+
message
|
119 |
+
status
|
120 |
+
data {
|
121 |
+
id
|
122 |
+
title
|
123 |
+
slug
|
124 |
+
description
|
125 |
+
content
|
126 |
+
files
|
127 |
+
}
|
128 |
+
}
|
129 |
+
}`);
|
130 |
+
|
131 |
+
export const UpdateProjectMutation =
|
132 |
+
gql(`mutation UpdateProject($input: UpdateProjectInput!) {
|
133 |
+
updateProject(input: $input) {
|
134 |
+
message
|
135 |
+
status
|
136 |
+
}
|
137 |
+
}`);
|
138 |
+
|
139 |
+
export const DeleteProjectMutation =
|
140 |
+
gql(`mutation DeleteProject($input: DeleteProjectInput!) {
|
141 |
+
deleteProject(input: $input) {
|
142 |
+
data {
|
143 |
+
id
|
144 |
+
}
|
145 |
+
message
|
146 |
+
status
|
147 |
+
}
|
148 |
+
}`);
|
graphql/definitions/tag.definition.ts
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { gql } from "@apollo/client";
|
2 |
+
|
3 |
+
export const TagsQuery = gql(`query Tags {
|
4 |
+
tags {
|
5 |
+
data {
|
6 |
+
id
|
7 |
+
value
|
8 |
+
}
|
9 |
+
message
|
10 |
+
status
|
11 |
+
}
|
12 |
+
}`);
|
graphql/definitions/upload.definition.ts
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { gql } from "@apollo/client";
|
2 |
+
|
3 |
+
export const uploadFileMutation =
|
4 |
+
gql(`mutation UploadFile($input: UploadInput!) {
|
5 |
+
uploadFile(input: $input) {
|
6 |
+
url
|
7 |
+
message
|
8 |
+
status
|
9 |
+
}
|
10 |
+
}`);
|
graphql/definitions/user.definitions.ts
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { gql } from "@apollo/client";
|
2 |
+
|
3 |
+
export const UsersIDsQuery = gql(`query UsersIDsQuery($input: UsersInput!) {
|
4 |
+
users(input: $input) {
|
5 |
+
message
|
6 |
+
status
|
7 |
+
data {
|
8 |
+
username
|
9 |
+
}
|
10 |
+
}
|
11 |
+
}`);
|
12 |
+
|
13 |
+
export const UserQuery = gql(`query User($input: UserInput!) {
|
14 |
+
user(input: $input) {
|
15 |
+
message
|
16 |
+
status
|
17 |
+
data {
|
18 |
+
id
|
19 |
+
email
|
20 |
+
username
|
21 |
+
firstname
|
22 |
+
lastname
|
23 |
+
avatar
|
24 |
+
bio
|
25 |
+
website
|
26 |
+
jobTitle
|
27 |
+
country
|
28 |
+
showProfession
|
29 |
+
points
|
30 |
+
projects {
|
31 |
+
id
|
32 |
+
title
|
33 |
+
slug
|
34 |
+
tags {
|
35 |
+
id
|
36 |
+
value
|
37 |
+
}
|
38 |
+
}
|
39 |
+
}
|
40 |
+
}
|
41 |
+
}`);
|
42 |
+
|
43 |
+
export const MostActiveUsersQuery = gql(`query MostActiveUsers {
|
44 |
+
mostActiveUsers {
|
45 |
+
status
|
46 |
+
message
|
47 |
+
data {
|
48 |
+
id
|
49 |
+
avatar
|
50 |
+
username
|
51 |
+
bio
|
52 |
+
}
|
53 |
+
}
|
54 |
+
}`);
|
graphql/generated/graphql.d.ts
ADDED
@@ -0,0 +1,563 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { gql } from '@apollo/client';
|
2 |
+
export type Maybe<T> = T | null;
|
3 |
+
export type InputMaybe<T> = Maybe<T>;
|
4 |
+
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
5 |
+
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
|
6 |
+
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
|
7 |
+
/** All built-in and custom scalars, mapped to their actual values */
|
8 |
+
export type Scalars = {
|
9 |
+
ID: string;
|
10 |
+
String: string;
|
11 |
+
Boolean: boolean;
|
12 |
+
Int: number;
|
13 |
+
Float: number;
|
14 |
+
DateTime: any;
|
15 |
+
JSON: any;
|
16 |
+
Upload: any;
|
17 |
+
};
|
18 |
+
|
19 |
+
export type Account = {
|
20 |
+
__typename?: 'Account';
|
21 |
+
createdAt: Scalars['DateTime'];
|
22 |
+
email: Scalars['String'];
|
23 |
+
id: Scalars['String'];
|
24 |
+
token: Scalars['String'];
|
25 |
+
updatedAt: Scalars['DateTime'];
|
26 |
+
};
|
27 |
+
|
28 |
+
export type AuthResponse = Response & {
|
29 |
+
__typename?: 'AuthResponse';
|
30 |
+
data?: Maybe<User>;
|
31 |
+
exp?: Maybe<Scalars['DateTime']>;
|
32 |
+
message?: Maybe<Scalars['String']>;
|
33 |
+
status: Scalars['Boolean'];
|
34 |
+
token?: Maybe<Scalars['String']>;
|
35 |
+
};
|
36 |
+
|
37 |
+
export type Comment = {
|
38 |
+
__typename?: 'Comment';
|
39 |
+
author?: Maybe<User>;
|
40 |
+
authorId: Scalars['String'];
|
41 |
+
createdAt?: Maybe<Scalars['DateTime']>;
|
42 |
+
id: Scalars['Int'];
|
43 |
+
message?: Maybe<Scalars['String']>;
|
44 |
+
parentId?: Maybe<Scalars['Int']>;
|
45 |
+
project?: Maybe<Project>;
|
46 |
+
projectId?: Maybe<Scalars['String']>;
|
47 |
+
updatedAt?: Maybe<Scalars['DateTime']>;
|
48 |
+
};
|
49 |
+
|
50 |
+
export type CommentResponse = Response & {
|
51 |
+
__typename?: 'CommentResponse';
|
52 |
+
data?: Maybe<Comment>;
|
53 |
+
message?: Maybe<Scalars['String']>;
|
54 |
+
status: Scalars['Boolean'];
|
55 |
+
};
|
56 |
+
|
57 |
+
export type CommentSubscriptionResponse = {
|
58 |
+
__typename?: 'CommentSubscriptionResponse';
|
59 |
+
data?: Maybe<Notification>;
|
60 |
+
message?: Maybe<Scalars['String']>;
|
61 |
+
type?: Maybe<PushNotificationType>;
|
62 |
+
};
|
63 |
+
|
64 |
+
export type CommentsInput = {
|
65 |
+
projectId: Scalars['String'];
|
66 |
+
};
|
67 |
+
|
68 |
+
export type CommentsResponse = Response & {
|
69 |
+
__typename?: 'CommentsResponse';
|
70 |
+
data?: Maybe<Array<Maybe<Comment>>>;
|
71 |
+
message?: Maybe<Scalars['String']>;
|
72 |
+
status: Scalars['Boolean'];
|
73 |
+
};
|
74 |
+
|
75 |
+
export type CreateCommentInput = {
|
76 |
+
mentions?: InputMaybe<Array<InputMaybe<MentionsInput>>>;
|
77 |
+
message?: InputMaybe<Scalars['String']>;
|
78 |
+
parentId?: InputMaybe<Scalars['Int']>;
|
79 |
+
projectId: Scalars['String'];
|
80 |
+
};
|
81 |
+
|
82 |
+
export type CreatePageViewInput = {
|
83 |
+
ip: Scalars['String'];
|
84 |
+
};
|
85 |
+
|
86 |
+
export type CreateProjectInput = {
|
87 |
+
content?: InputMaybe<Scalars['JSON']>;
|
88 |
+
description?: InputMaybe<Scalars['String']>;
|
89 |
+
file?: InputMaybe<Scalars['Upload']>;
|
90 |
+
tags?: InputMaybe<Array<InputMaybe<TagInput>>>;
|
91 |
+
title?: InputMaybe<Scalars['String']>;
|
92 |
+
};
|
93 |
+
|
94 |
+
export type DeleteCommentInput = {
|
95 |
+
commentId: Scalars['Int'];
|
96 |
+
};
|
97 |
+
|
98 |
+
export type DeleteProjectInput = {
|
99 |
+
projectId: Scalars['String'];
|
100 |
+
};
|
101 |
+
|
102 |
+
export type GithubAuthInput = {
|
103 |
+
code?: InputMaybe<Scalars['String']>;
|
104 |
+
};
|
105 |
+
|
106 |
+
export type GoogleAuthInput = {
|
107 |
+
authuser?: InputMaybe<Scalars['String']>;
|
108 |
+
code?: InputMaybe<Scalars['String']>;
|
109 |
+
prompt?: InputMaybe<Scalars['String']>;
|
110 |
+
scope?: InputMaybe<Scalars['String']>;
|
111 |
+
};
|
112 |
+
|
113 |
+
export type MentionsInput = {
|
114 |
+
email?: InputMaybe<Scalars['String']>;
|
115 |
+
userId?: InputMaybe<Scalars['String']>;
|
116 |
+
};
|
117 |
+
|
118 |
+
export type Mutation = {
|
119 |
+
__typename?: 'Mutation';
|
120 |
+
createComment: CommentResponse;
|
121 |
+
createProject: ProjectResponse;
|
122 |
+
deleteComment: CommentResponse;
|
123 |
+
deleteProject: ProjectResponse;
|
124 |
+
forgotPassword: SimpleAuthResponse;
|
125 |
+
githubOauth: AuthResponse;
|
126 |
+
googleOauth: AuthResponse;
|
127 |
+
login: AuthResponse;
|
128 |
+
register: AuthResponse;
|
129 |
+
resetPassword: ResetPasswordResponse;
|
130 |
+
toggleVote: VoteResponse;
|
131 |
+
updateComment: CommentResponse;
|
132 |
+
updateProfile: UserResponse;
|
133 |
+
updateProject: ProjectResponse;
|
134 |
+
uploadFile: UploadResponse;
|
135 |
+
verifyUser: AuthResponse;
|
136 |
+
viewNotification?: Maybe<Response>;
|
137 |
+
};
|
138 |
+
|
139 |
+
|
140 |
+
export type MutationCreateCommentArgs = {
|
141 |
+
input: CreateCommentInput;
|
142 |
+
};
|
143 |
+
|
144 |
+
|
145 |
+
export type MutationCreateProjectArgs = {
|
146 |
+
input: CreateProjectInput;
|
147 |
+
};
|
148 |
+
|
149 |
+
|
150 |
+
export type MutationDeleteCommentArgs = {
|
151 |
+
input: DeleteCommentInput;
|
152 |
+
};
|
153 |
+
|
154 |
+
|
155 |
+
export type MutationDeleteProjectArgs = {
|
156 |
+
input: DeleteProjectInput;
|
157 |
+
};
|
158 |
+
|
159 |
+
|
160 |
+
export type MutationForgotPasswordArgs = {
|
161 |
+
input: UserForgotPasswordInput;
|
162 |
+
};
|
163 |
+
|
164 |
+
|
165 |
+
export type MutationGithubOauthArgs = {
|
166 |
+
input: GithubAuthInput;
|
167 |
+
};
|
168 |
+
|
169 |
+
|
170 |
+
export type MutationGoogleOauthArgs = {
|
171 |
+
input: GoogleAuthInput;
|
172 |
+
};
|
173 |
+
|
174 |
+
|
175 |
+
export type MutationLoginArgs = {
|
176 |
+
input: UserLoginInput;
|
177 |
+
};
|
178 |
+
|
179 |
+
|
180 |
+
export type MutationRegisterArgs = {
|
181 |
+
input: UserRegisterInput;
|
182 |
+
};
|
183 |
+
|
184 |
+
|
185 |
+
export type MutationResetPasswordArgs = {
|
186 |
+
input: UserResetPasswordInput;
|
187 |
+
};
|
188 |
+
|
189 |
+
|
190 |
+
export type MutationToggleVoteArgs = {
|
191 |
+
input?: InputMaybe<ToggleVoteInput>;
|
192 |
+
};
|
193 |
+
|
194 |
+
|
195 |
+
export type MutationUpdateCommentArgs = {
|
196 |
+
input: UpdateCommentInput;
|
197 |
+
};
|
198 |
+
|
199 |
+
|
200 |
+
export type MutationUpdateProfileArgs = {
|
201 |
+
input?: InputMaybe<UpdateProfileInput>;
|
202 |
+
};
|
203 |
+
|
204 |
+
|
205 |
+
export type MutationUpdateProjectArgs = {
|
206 |
+
input: UpdateProjectInput;
|
207 |
+
};
|
208 |
+
|
209 |
+
|
210 |
+
export type MutationUploadFileArgs = {
|
211 |
+
input: UploadInput;
|
212 |
+
};
|
213 |
+
|
214 |
+
|
215 |
+
export type MutationVerifyUserArgs = {
|
216 |
+
input: UserVerifyInput;
|
217 |
+
};
|
218 |
+
|
219 |
+
|
220 |
+
export type MutationViewNotificationArgs = {
|
221 |
+
input: ViewNotificationInput;
|
222 |
+
};
|
223 |
+
|
224 |
+
export type Notification = {
|
225 |
+
__typename?: 'Notification';
|
226 |
+
content?: Maybe<Scalars['String']>;
|
227 |
+
createdAt?: Maybe<Scalars['DateTime']>;
|
228 |
+
id: Scalars['Int'];
|
229 |
+
projectId?: Maybe<Scalars['String']>;
|
230 |
+
projectSlug?: Maybe<Scalars['String']>;
|
231 |
+
receiverId?: Maybe<Scalars['String']>;
|
232 |
+
sender?: Maybe<User>;
|
233 |
+
senderId?: Maybe<Scalars['String']>;
|
234 |
+
type?: Maybe<Scalars['String']>;
|
235 |
+
updatedAt?: Maybe<Scalars['DateTime']>;
|
236 |
+
viewed?: Maybe<Scalars['Boolean']>;
|
237 |
+
};
|
238 |
+
|
239 |
+
export type NotificationInput = {
|
240 |
+
limit?: InputMaybe<Scalars['Int']>;
|
241 |
+
offset?: InputMaybe<Scalars['Int']>;
|
242 |
+
userId?: InputMaybe<Scalars['String']>;
|
243 |
+
};
|
244 |
+
|
245 |
+
export type NotificationResponse = Response & {
|
246 |
+
__typename?: 'NotificationResponse';
|
247 |
+
data?: Maybe<Notification>;
|
248 |
+
message?: Maybe<Scalars['String']>;
|
249 |
+
status: Scalars['Boolean'];
|
250 |
+
};
|
251 |
+
|
252 |
+
export type NotificationsResponse = Response & {
|
253 |
+
__typename?: 'NotificationsResponse';
|
254 |
+
data?: Maybe<Array<Maybe<Notification>>>;
|
255 |
+
message?: Maybe<Scalars['String']>;
|
256 |
+
status: Scalars['Boolean'];
|
257 |
+
};
|
258 |
+
|
259 |
+
export type PageView = {
|
260 |
+
__typename?: 'PageView';
|
261 |
+
createdAt?: Maybe<Scalars['DateTime']>;
|
262 |
+
id: Scalars['Int'];
|
263 |
+
ip: Scalars['String'];
|
264 |
+
updatedAt?: Maybe<Scalars['DateTime']>;
|
265 |
+
};
|
266 |
+
|
267 |
+
export type PageViewResponse = {
|
268 |
+
__typename?: 'PageViewResponse';
|
269 |
+
data?: Maybe<Array<Maybe<PageView>>>;
|
270 |
+
message?: Maybe<Scalars['String']>;
|
271 |
+
status: Scalars['Boolean'];
|
272 |
+
};
|
273 |
+
|
274 |
+
export type Project = {
|
275 |
+
__typename?: 'Project';
|
276 |
+
_count?: Maybe<ProjectCountPayload>;
|
277 |
+
author?: Maybe<User>;
|
278 |
+
comments?: Maybe<Array<Maybe<Comment>>>;
|
279 |
+
content?: Maybe<Scalars['JSON']>;
|
280 |
+
createdAt?: Maybe<Scalars['DateTime']>;
|
281 |
+
description?: Maybe<Scalars['String']>;
|
282 |
+
files?: Maybe<Array<Maybe<Scalars['String']>>>;
|
283 |
+
hasvoted?: Maybe<Scalars['Boolean']>;
|
284 |
+
id: Scalars['String'];
|
285 |
+
isProjectOwner?: Maybe<Scalars['Boolean']>;
|
286 |
+
rank?: Maybe<Scalars['Int']>;
|
287 |
+
slug?: Maybe<Scalars['String']>;
|
288 |
+
tags?: Maybe<Array<Maybe<Tag>>>;
|
289 |
+
title?: Maybe<Scalars['String']>;
|
290 |
+
updatedAt?: Maybe<Scalars['DateTime']>;
|
291 |
+
votes?: Maybe<Array<Maybe<Vote>>>;
|
292 |
+
};
|
293 |
+
|
294 |
+
export type ProjectCountPayload = {
|
295 |
+
__typename?: 'ProjectCountPayload';
|
296 |
+
comments?: Maybe<Scalars['Int']>;
|
297 |
+
votes?: Maybe<Scalars['Int']>;
|
298 |
+
};
|
299 |
+
|
300 |
+
export type ProjectInput = {
|
301 |
+
id: Scalars['String'];
|
302 |
+
vote_limit?: InputMaybe<Scalars['Int']>;
|
303 |
+
vote_offset?: InputMaybe<Scalars['Int']>;
|
304 |
+
};
|
305 |
+
|
306 |
+
export type ProjectResponse = Response & {
|
307 |
+
__typename?: 'ProjectResponse';
|
308 |
+
data?: Maybe<Project>;
|
309 |
+
message?: Maybe<Scalars['String']>;
|
310 |
+
status: Scalars['Boolean'];
|
311 |
+
};
|
312 |
+
|
313 |
+
export type ProjectsInput = {
|
314 |
+
limit?: InputMaybe<Scalars['Int']>;
|
315 |
+
offset?: InputMaybe<Scalars['Int']>;
|
316 |
+
};
|
317 |
+
|
318 |
+
export type ProjectsResponse = Response & {
|
319 |
+
__typename?: 'ProjectsResponse';
|
320 |
+
data: Array<Project>;
|
321 |
+
message?: Maybe<Scalars['String']>;
|
322 |
+
status: Scalars['Boolean'];
|
323 |
+
};
|
324 |
+
|
325 |
+
export enum PushNotificationType {
|
326 |
+
Comment = 'COMMENT',
|
327 |
+
Mention = 'MENTION',
|
328 |
+
Reply = 'REPLY',
|
329 |
+
Upvote = 'UPVOTE'
|
330 |
+
}
|
331 |
+
|
332 |
+
export type Query = {
|
333 |
+
__typename?: 'Query';
|
334 |
+
comments?: Maybe<CommentsResponse>;
|
335 |
+
me?: Maybe<UserResponse>;
|
336 |
+
mostActiveUsers?: Maybe<UsersResponse>;
|
337 |
+
notifications?: Maybe<NotificationsResponse>;
|
338 |
+
pageViews?: Maybe<PageViewResponse>;
|
339 |
+
project?: Maybe<ProjectResponse>;
|
340 |
+
projects?: Maybe<ProjectsResponse>;
|
341 |
+
tags?: Maybe<TagsResponse>;
|
342 |
+
user?: Maybe<UserResponse>;
|
343 |
+
users?: Maybe<UsersResponse>;
|
344 |
+
usersWhoComment?: Maybe<UsersResponse>;
|
345 |
+
};
|
346 |
+
|
347 |
+
|
348 |
+
export type QueryCommentsArgs = {
|
349 |
+
input: CommentsInput;
|
350 |
+
};
|
351 |
+
|
352 |
+
|
353 |
+
export type QueryNotificationsArgs = {
|
354 |
+
input: NotificationInput;
|
355 |
+
};
|
356 |
+
|
357 |
+
|
358 |
+
export type QueryProjectArgs = {
|
359 |
+
input: ProjectInput;
|
360 |
+
};
|
361 |
+
|
362 |
+
|
363 |
+
export type QueryProjectsArgs = {
|
364 |
+
input: ProjectsInput;
|
365 |
+
};
|
366 |
+
|
367 |
+
|
368 |
+
export type QueryUserArgs = {
|
369 |
+
input: UserInput;
|
370 |
+
};
|
371 |
+
|
372 |
+
|
373 |
+
export type QueryUsersArgs = {
|
374 |
+
input: UsersInput;
|
375 |
+
};
|
376 |
+
|
377 |
+
|
378 |
+
export type QueryUsersWhoCommentArgs = {
|
379 |
+
input: ProjectInput;
|
380 |
+
};
|
381 |
+
|
382 |
+
export type ResetPasswordResponse = Response & {
|
383 |
+
__typename?: 'ResetPasswordResponse';
|
384 |
+
data?: Maybe<Account>;
|
385 |
+
message?: Maybe<Scalars['String']>;
|
386 |
+
status: Scalars['Boolean'];
|
387 |
+
};
|
388 |
+
|
389 |
+
export type Response = {
|
390 |
+
message?: Maybe<Scalars['String']>;
|
391 |
+
status: Scalars['Boolean'];
|
392 |
+
};
|
393 |
+
|
394 |
+
export type SimpleAuthResponse = Response & {
|
395 |
+
__typename?: 'SimpleAuthResponse';
|
396 |
+
message?: Maybe<Scalars['String']>;
|
397 |
+
status: Scalars['Boolean'];
|
398 |
+
};
|
399 |
+
|
400 |
+
export type Subscription = {
|
401 |
+
__typename?: 'Subscription';
|
402 |
+
commentCreated?: Maybe<CommentSubscriptionResponse>;
|
403 |
+
mentionCreated?: Maybe<CommentSubscriptionResponse>;
|
404 |
+
replyCreated?: Maybe<CommentSubscriptionResponse>;
|
405 |
+
upvoteCreated?: Maybe<UpvoteSubscriptionResponse>;
|
406 |
+
};
|
407 |
+
|
408 |
+
export type Tag = {
|
409 |
+
__typename?: 'Tag';
|
410 |
+
id: Scalars['Int'];
|
411 |
+
projects?: Maybe<Array<Maybe<Project>>>;
|
412 |
+
value: Scalars['String'];
|
413 |
+
};
|
414 |
+
|
415 |
+
export type TagInput = {
|
416 |
+
id?: InputMaybe<Scalars['Int']>;
|
417 |
+
value: Scalars['String'];
|
418 |
+
};
|
419 |
+
|
420 |
+
export type TagsResponse = Response & {
|
421 |
+
__typename?: 'TagsResponse';
|
422 |
+
data?: Maybe<Array<Tag>>;
|
423 |
+
message?: Maybe<Scalars['String']>;
|
424 |
+
status: Scalars['Boolean'];
|
425 |
+
};
|
426 |
+
|
427 |
+
export type ToggleVoteInput = {
|
428 |
+
id: Scalars['String'];
|
429 |
+
};
|
430 |
+
|
431 |
+
export type UpdateCommentInput = {
|
432 |
+
commentId: Scalars['Int'];
|
433 |
+
message?: InputMaybe<Scalars['String']>;
|
434 |
+
};
|
435 |
+
|
436 |
+
export type UpdateProfileInput = {
|
437 |
+
banner?: InputMaybe<Scalars['String']>;
|
438 |
+
bio?: InputMaybe<Scalars['String']>;
|
439 |
+
country?: InputMaybe<Scalars['String']>;
|
440 |
+
file?: InputMaybe<Scalars['Upload']>;
|
441 |
+
firstname?: InputMaybe<Scalars['String']>;
|
442 |
+
id: Scalars['String'];
|
443 |
+
jobTitle?: InputMaybe<Scalars['String']>;
|
444 |
+
lastname?: InputMaybe<Scalars['String']>;
|
445 |
+
showProfession?: InputMaybe<Scalars['Boolean']>;
|
446 |
+
username?: InputMaybe<Scalars['String']>;
|
447 |
+
website?: InputMaybe<Scalars['String']>;
|
448 |
+
};
|
449 |
+
|
450 |
+
export type UpdateProjectInput = {
|
451 |
+
content?: InputMaybe<Scalars['JSON']>;
|
452 |
+
description?: InputMaybe<Scalars['String']>;
|
453 |
+
file?: InputMaybe<Scalars['Upload']>;
|
454 |
+
projectId: Scalars['String'];
|
455 |
+
tags?: InputMaybe<Array<InputMaybe<TagInput>>>;
|
456 |
+
title?: InputMaybe<Scalars['String']>;
|
457 |
+
};
|
458 |
+
|
459 |
+
export type UploadInput = {
|
460 |
+
file?: InputMaybe<Scalars['Upload']>;
|
461 |
+
};
|
462 |
+
|
463 |
+
export type UploadResponse = Response & {
|
464 |
+
__typename?: 'UploadResponse';
|
465 |
+
message?: Maybe<Scalars['String']>;
|
466 |
+
status: Scalars['Boolean'];
|
467 |
+
url?: Maybe<Scalars['String']>;
|
468 |
+
};
|
469 |
+
|
470 |
+
export type UpvoteSubscriptionResponse = {
|
471 |
+
__typename?: 'UpvoteSubscriptionResponse';
|
472 |
+
data?: Maybe<Notification>;
|
473 |
+
message?: Maybe<Scalars['String']>;
|
474 |
+
type?: Maybe<PushNotificationType>;
|
475 |
+
};
|
476 |
+
|
477 |
+
export type User = {
|
478 |
+
__typename?: 'User';
|
479 |
+
avatar?: Maybe<Scalars['String']>;
|
480 |
+
banner?: Maybe<Scalars['String']>;
|
481 |
+
bio?: Maybe<Scalars['String']>;
|
482 |
+
country?: Maybe<Scalars['String']>;
|
483 |
+
createdAt?: Maybe<Scalars['DateTime']>;
|
484 |
+
email: Scalars['String'];
|
485 |
+
firstname?: Maybe<Scalars['String']>;
|
486 |
+
id: Scalars['String'];
|
487 |
+
jobTitle?: Maybe<Scalars['String']>;
|
488 |
+
lastname?: Maybe<Scalars['String']>;
|
489 |
+
projects?: Maybe<Array<Maybe<Project>>>;
|
490 |
+
showProfession?: Maybe<Scalars['Boolean']>;
|
491 |
+
updatedAt?: Maybe<Scalars['DateTime']>;
|
492 |
+
username: Scalars['String'];
|
493 |
+
verifiedAt?: Maybe<Scalars['DateTime']>;
|
494 |
+
votes?: Maybe<Array<Maybe<Vote>>>;
|
495 |
+
website?: Maybe<Scalars['String']>;
|
496 |
+
};
|
497 |
+
|
498 |
+
export type UserForgotPasswordInput = {
|
499 |
+
email: Scalars['String'];
|
500 |
+
};
|
501 |
+
|
502 |
+
export type UserInput = {
|
503 |
+
username: Scalars['String'];
|
504 |
+
};
|
505 |
+
|
506 |
+
export type UserLoginInput = {
|
507 |
+
email: Scalars['String'];
|
508 |
+
password: Scalars['String'];
|
509 |
+
};
|
510 |
+
|
511 |
+
export type UserRegisterInput = {
|
512 |
+
email: Scalars['String'];
|
513 |
+
password: Scalars['String'];
|
514 |
+
username: Scalars['String'];
|
515 |
+
};
|
516 |
+
|
517 |
+
export type UserResetPasswordInput = {
|
518 |
+
password: Scalars['String'];
|
519 |
+
token: Scalars['String'];
|
520 |
+
};
|
521 |
+
|
522 |
+
export type UserResponse = Response & {
|
523 |
+
__typename?: 'UserResponse';
|
524 |
+
data?: Maybe<User>;
|
525 |
+
message?: Maybe<Scalars['String']>;
|
526 |
+
status: Scalars['Boolean'];
|
527 |
+
};
|
528 |
+
|
529 |
+
export type UserVerifyInput = {
|
530 |
+
token: Scalars['String'];
|
531 |
+
};
|
532 |
+
|
533 |
+
export type UsersInput = {
|
534 |
+
limit?: InputMaybe<Scalars['Int']>;
|
535 |
+
offset?: InputMaybe<Scalars['Int']>;
|
536 |
+
};
|
537 |
+
|
538 |
+
export type UsersResponse = Response & {
|
539 |
+
__typename?: 'UsersResponse';
|
540 |
+
data?: Maybe<Array<Maybe<User>>>;
|
541 |
+
message?: Maybe<Scalars['String']>;
|
542 |
+
status: Scalars['Boolean'];
|
543 |
+
};
|
544 |
+
|
545 |
+
export type ViewNotificationInput = {
|
546 |
+
notificationId: Scalars['Int'];
|
547 |
+
};
|
548 |
+
|
549 |
+
export type Vote = {
|
550 |
+
__typename?: 'Vote';
|
551 |
+
author?: Maybe<User>;
|
552 |
+
createdAt?: Maybe<Scalars['DateTime']>;
|
553 |
+
id: Scalars['Int'];
|
554 |
+
project?: Maybe<Project>;
|
555 |
+
updatedAt?: Maybe<Scalars['DateTime']>;
|
556 |
+
};
|
557 |
+
|
558 |
+
export type VoteResponse = Response & {
|
559 |
+
__typename?: 'VoteResponse';
|
560 |
+
data?: Maybe<Vote>;
|
561 |
+
message?: Maybe<Scalars['String']>;
|
562 |
+
status: Scalars['Boolean'];
|
563 |
+
};
|
hooks/index.ts
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
export * from "./useMonaco";
|
2 |
+
export * from "./useTree";
|
hooks/useMonaco.ts
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useRef, useState } from "react";
|
2 |
+
import { IKeyboardEvent } from "monaco-editor";
|
3 |
+
import { useDebounce } from "use-debounce";
|
4 |
+
import { OnChange, OnMount } from "@monaco-editor/react";
|
5 |
+
import parserHtml from "prettier/parser-html";
|
6 |
+
import parserCss from "prettier/parser-postcss";
|
7 |
+
import parserBabel from "prettier/parser-babel";
|
8 |
+
import prettier from "prettier";
|
9 |
+
import { useAppSelector } from "../store/hook";
|
10 |
+
import { theme_state } from "../store/features/themeSlice";
|
11 |
+
|
12 |
+
export const useMonaco = () => {
|
13 |
+
const { theme } = useAppSelector(theme_state);
|
14 |
+
const codeEditor = useRef<any>();
|
15 |
+
|
16 |
+
const [input, setInput] = useState<string | undefined>("");
|
17 |
+
const [code] = useDebounce(input, 1000);
|
18 |
+
|
19 |
+
const onChange: OnChange = (value) => {
|
20 |
+
setInput(value);
|
21 |
+
};
|
22 |
+
|
23 |
+
const onMount: OnMount = async (monacoEditor, monaco) => {
|
24 |
+
codeEditor.current = monacoEditor;
|
25 |
+
|
26 |
+
monaco.editor.defineTheme("myTheme", {
|
27 |
+
base: "vs-dark",
|
28 |
+
inherit: true,
|
29 |
+
rules: [{ background: theme.background, token: "" }],
|
30 |
+
colors: {
|
31 |
+
"editor.background": theme.foreground,
|
32 |
+
},
|
33 |
+
});
|
34 |
+
|
35 |
+
monaco.editor.setTheme("myTheme");
|
36 |
+
|
37 |
+
const { default: traverse } = await import("@babel/traverse");
|
38 |
+
const { parse } = await import("@babel/parser");
|
39 |
+
const { default: MonacoJSXHighlighter } = await import(
|
40 |
+
"monaco-jsx-highlighter"
|
41 |
+
);
|
42 |
+
|
43 |
+
//jsx syntax highlight
|
44 |
+
const babelParse = (code: any) =>
|
45 |
+
parse(code, { sourceType: "module", plugins: ["jsx"] });
|
46 |
+
|
47 |
+
const monacoJSXHighlighter = new MonacoJSXHighlighter(
|
48 |
+
//@ts-ignore
|
49 |
+
monaco,
|
50 |
+
babelParse,
|
51 |
+
traverse,
|
52 |
+
monacoEditor
|
53 |
+
);
|
54 |
+
|
55 |
+
monacoJSXHighlighter.highLightOnDidChangeModelContent(
|
56 |
+
0,
|
57 |
+
() => {},
|
58 |
+
() => {},
|
59 |
+
undefined,
|
60 |
+
() => {}
|
61 |
+
);
|
62 |
+
|
63 |
+
//format code
|
64 |
+
function formatOnSave() {
|
65 |
+
const unformattedCode = codeEditor.current.getModel().getValue();
|
66 |
+
const lang = codeEditor.current.getModel()._languageIdentifier.language;
|
67 |
+
|
68 |
+
let config;
|
69 |
+
|
70 |
+
switch (lang) {
|
71 |
+
case "html":
|
72 |
+
config = { parser: "html", plugin: [parserHtml] };
|
73 |
+
break;
|
74 |
+
|
75 |
+
case "css":
|
76 |
+
config = { parser: "css", plugin: [parserCss] };
|
77 |
+
break;
|
78 |
+
|
79 |
+
case "javascript":
|
80 |
+
config = { parser: "babel", plugin: [parserBabel] };
|
81 |
+
break;
|
82 |
+
|
83 |
+
default:
|
84 |
+
break;
|
85 |
+
}
|
86 |
+
|
87 |
+
const formattedCode = prettier.format(unformattedCode, {
|
88 |
+
parser: config && config.parser,
|
89 |
+
plugins: config && config.plugin,
|
90 |
+
useTabs: false,
|
91 |
+
semi: true,
|
92 |
+
});
|
93 |
+
|
94 |
+
codeEditor.current.setValue(formattedCode);
|
95 |
+
}
|
96 |
+
|
97 |
+
//save command
|
98 |
+
let handleOnKeyDown = codeEditor.current.onKeyDown(
|
99 |
+
(event: IKeyboardEvent) => {
|
100 |
+
if (
|
101 |
+
(window.navigator.platform.match("Mac")
|
102 |
+
? event.metaKey
|
103 |
+
: event.ctrlKey) &&
|
104 |
+
event.code === "KeyS"
|
105 |
+
) {
|
106 |
+
event.preventDefault();
|
107 |
+
formatOnSave();
|
108 |
+
}
|
109 |
+
}
|
110 |
+
);
|
111 |
+
|
112 |
+
//cleaning up
|
113 |
+
return () => handleOnKeyDown.dispose();
|
114 |
+
};
|
115 |
+
|
116 |
+
return { onMount, onChange, code };
|
117 |
+
};
|
hooks/useOutsideRef.ts
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useEffect, useState } from "react";
|
2 |
+
|
3 |
+
const UseOutsideRef = (ref: any) => {
|
4 |
+
const [isOutsideRef, setIsOutsideRef] = useState(false);
|
5 |
+
|
6 |
+
useEffect(() => {
|
7 |
+
function handleClickOutside(event: any) {
|
8 |
+
if (ref.current && !ref.current.contains(event.target)) {
|
9 |
+
setIsOutsideRef(true);
|
10 |
+
|
11 |
+
setTimeout(() => {
|
12 |
+
setIsOutsideRef(false);
|
13 |
+
}, 200);
|
14 |
+
}
|
15 |
+
}
|
16 |
+
|
17 |
+
document.addEventListener("mousedown", handleClickOutside);
|
18 |
+
return () => {
|
19 |
+
document.removeEventListener("mousedown", handleClickOutside);
|
20 |
+
};
|
21 |
+
}, [ref]);
|
22 |
+
|
23 |
+
return { isOutsideRef };
|
24 |
+
};
|
25 |
+
|
26 |
+
export default UseOutsideRef;
|