├── .nvmrc ├── packages ├── client │ ├── .env │ ├── src │ │ ├── react-app-env.d.ts │ │ ├── utils │ │ │ ├── styledUtils.ts │ │ │ ├── unreachableCaseError.ts │ │ │ ├── snippetUtils.ts │ │ │ ├── normalizeArray.ts │ │ │ └── index.ts │ │ ├── missing.d.ts │ │ ├── index.css │ │ ├── context │ │ │ ├── index.ts │ │ │ ├── Preview.tsx │ │ │ ├── Global.tsx │ │ │ └── Auth.tsx │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useInterval.ts │ │ │ ├── useCycle.ts │ │ │ ├── useSearch.ts │ │ │ ├── useConvertKit.ts │ │ │ └── useOAuth.ts │ │ ├── pages │ │ │ ├── Account.tsx │ │ │ ├── NotFound.tsx │ │ │ ├── Gallery.tsx │ │ │ ├── index.ts │ │ │ ├── MySnippets.tsx │ │ │ ├── Embed.tsx │ │ │ ├── Download.tsx │ │ │ ├── Home.tsx │ │ │ └── About.tsx │ │ ├── types.ts │ │ ├── components │ │ │ ├── ScrollToTop.tsx │ │ │ ├── index.ts │ │ │ ├── Page.tsx │ │ │ ├── Toast.tsx │ │ │ ├── PreviewStepSelector.tsx │ │ │ ├── Message.tsx │ │ │ ├── PageRoute.tsx │ │ │ ├── PrivateRoute.tsx │ │ │ ├── SnippetTags.tsx │ │ │ ├── core │ │ │ │ └── index.tsx │ │ │ ├── WindowTitleBar.tsx │ │ │ ├── ColorPicker.tsx │ │ │ ├── StandaloneSnippet.tsx │ │ │ ├── ExportMenu.tsx │ │ │ ├── PreviewContainer.tsx │ │ │ ├── SnippetsList.tsx │ │ │ ├── Hero.tsx │ │ │ ├── Builder.tsx │ │ │ ├── Footer.tsx │ │ │ ├── Nav.tsx │ │ │ ├── TitleToolbar.tsx │ │ │ ├── Preview.tsx │ │ │ └── Autocomplete.tsx │ │ ├── setupTests.ts │ │ ├── templates │ │ │ ├── index.ts │ │ │ ├── blank.ts │ │ │ ├── npmPackage.ts │ │ │ └── default.ts │ │ ├── setupProxy.js │ │ ├── config │ │ │ └── firebase.ts │ │ ├── App.tsx │ │ ├── code-themes │ │ │ ├── windowControls.tsx │ │ │ └── index.ts │ │ ├── index.tsx │ │ ├── theme.tsx │ │ └── serviceWorker.ts │ ├── public │ │ ├── robots.txt │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── wave-snippets-social.jpg │ │ ├── manifest.json │ │ ├── favicon.svg │ │ └── index.html │ ├── tsconfig.json │ └── package.json ├── shared │ ├── src │ │ ├── index.ts │ │ ├── types.ts │ │ └── collections │ │ │ ├── index.ts │ │ │ ├── like.ts │ │ │ ├── user.ts │ │ │ └── snippet.ts │ ├── tsconfig.json │ └── package.json └── functions │ ├── .runtimeconfig.json │ ├── tsconfig.json │ ├── src │ ├── const.ts │ ├── utils │ │ ├── index.ts │ │ ├── store.ts │ │ ├── errors.ts │ │ ├── createGIF.ts │ │ └── validateFirebaseIdToken.ts │ ├── onUserCreate.ts │ ├── index.ts │ ├── queueCreateExport.ts │ └── vendor │ │ ├── timesnap │ │ └── lib │ │ │ ├── capture-canvas.js │ │ │ ├── page-utils.js │ │ │ ├── make-canvas-capturer.js │ │ │ ├── immediate-canvas-handler.js │ │ │ ├── utils.js │ │ │ ├── capture-screenshot.js │ │ │ └── overwrite-random.js │ │ └── timecut │ │ └── index.js │ └── package.json ├── static ├── logo.png ├── intro.gif └── pythonExample.gif ├── .eslintignore ├── .firebaserc ├── .prettierignore ├── .prettierrc ├── .editorconfig ├── .stylelintrc ├── storage.rules ├── lerna.json ├── firebase.json ├── netlify.toml ├── patches ├── react-scripts+3.4.1.patch └── @code-surfer+standalone+3.1.1.patch ├── .gitignore ├── tsconfig.json ├── firestore.indexes.json ├── LICENSE ├── firestore.rules ├── .eslintrc.js ├── package.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v10.21.0 2 | -------------------------------------------------------------------------------- /packages/client/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK="true" 2 | -------------------------------------------------------------------------------- /packages/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './collections' 2 | -------------------------------------------------------------------------------- /packages/shared/src/types.ts: -------------------------------------------------------------------------------- 1 | export type UserID = string 2 | -------------------------------------------------------------------------------- /packages/client/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/client/src/utils/styledUtils.ts: -------------------------------------------------------------------------------- 1 | export { rem, rgba } from 'polished' 2 | -------------------------------------------------------------------------------- /static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwood23/wave-snippets/HEAD/static/logo.png -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | packages/client/public 2 | packages/functions/src/vendor/* 3 | .eslintrc.js 4 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "waves-production" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /static/intro.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwood23/wave-snippets/HEAD/static/intro.gif -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | packages/client/public 2 | packages/functions/src/vendor/* 3 | .eslintrc.js 4 | -------------------------------------------------------------------------------- /static/pythonExample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwood23/wave-snippets/HEAD/static/pythonExample.gif -------------------------------------------------------------------------------- /packages/client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Allow: / 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "semi": false, 5 | "endOfLine": "auto" 6 | } 7 | -------------------------------------------------------------------------------- /packages/client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwood23/wave-snippets/HEAD/packages/client/public/logo192.png -------------------------------------------------------------------------------- /packages/client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwood23/wave-snippets/HEAD/packages/client/public/logo512.png -------------------------------------------------------------------------------- /packages/shared/src/collections/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user' 2 | export * from './snippet' 3 | export * from './like' 4 | -------------------------------------------------------------------------------- /packages/client/src/missing.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'gif.js' 2 | declare module 'gif.js.optimized' 3 | declare module 'dom-to-image-more' 4 | -------------------------------------------------------------------------------- /packages/client/src/index.css: -------------------------------------------------------------------------------- 1 | /* Overrides for the toaster */ 2 | 3 | .Toastify__toast-container--top-right { 4 | top: 80px !important; 5 | } 6 | -------------------------------------------------------------------------------- /packages/client/public/wave-snippets-social.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwood23/wave-snippets/HEAD/packages/client/public/wave-snippets-social.jpg -------------------------------------------------------------------------------- /packages/client/src/context/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Snippet' 2 | export * from './Preview' 3 | export * from './Auth' 4 | export * from './Global' 5 | -------------------------------------------------------------------------------- /packages/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": [ 4 | "src/**/*" 5 | ], 6 | "compilerOptions": {} 7 | } 8 | -------------------------------------------------------------------------------- /packages/client/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useInterval' 2 | export * from './useCycle' 3 | export * from './useRenderGIF' 4 | export * from './useSearch' 5 | -------------------------------------------------------------------------------- /packages/client/src/utils/unreachableCaseError.ts: -------------------------------------------------------------------------------- 1 | export class UnreachableCaseError extends Error { 2 | constructor(val: never) { 3 | super(`Unreachable case: ${val}`) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "processors": [ 3 | "stylelint-processor-styled-components" 4 | ], 5 | "extends": [ 6 | "stylelint-config-recommended", 7 | "stylelint-config-styled-components" 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/functions/.runtimeconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "clienturl": "http://localhost:3000", 4 | "cloudfunctionsurl": "http://localhost:5001/waves-production/us-east1", 5 | "sentrydsn": "" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /storage.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service firebase.storage { 3 | match /b/{bucket}/o { 4 | match /{allPaths=**} { 5 | allow read: if true; 6 | allow write: if false; 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/client/src/pages/Account.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | 3 | import { Page } from '../components' 4 | 5 | export const AccountPage: FC = () => ( 6 | 7 |

Account page

8 |
9 | ) 10 | -------------------------------------------------------------------------------- /packages/client/src/types.ts: -------------------------------------------------------------------------------- 1 | import { SnippetDocument } from '@waves/shared' 2 | import { Optional } from 'utility-types' 3 | 4 | export type BaseSnippet = Optional< 5 | SnippetDocument, 6 | 'owner' | 'createdOn' | 'updatedOn' 7 | > 8 | -------------------------------------------------------------------------------- /packages/client/src/pages/NotFound.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | 3 | import { Page } from '../components' 4 | 5 | export const NotFoundPage: FC = () => ( 6 | 7 |

NotFoundPage page

8 |
9 | ) 10 | -------------------------------------------------------------------------------- /packages/shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*"], 4 | "compilerOptions": { 5 | "module": "es2020", 6 | "outDir": "dist", 7 | "noEmit": false, 8 | "target": "es2020" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*"], 4 | "compilerOptions": { 5 | "module": "commonjs", 6 | "outDir": "dist", 7 | "noEmit": false, 8 | "target": "es2017" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/client/src/utils/snippetUtils.ts: -------------------------------------------------------------------------------- 1 | import { TEMPLATES } from '../templates' 2 | 3 | export const isMatchParamATemplate = (param?: string) => 4 | // @ts-ignore String union to string. That's what we want to check! 5 | param ? TEMPLATES.includes(param) : false 6 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "independent", 3 | "npmClient": "yarn", 4 | "useWorkspaces": true, 5 | "command": {}, 6 | "ignoreChanges": [ 7 | "**/__fixtures__/**", 8 | "**/__tests__/**", 9 | "**/__mocks__/**", 10 | "**/*fixture.js", 11 | "**/*.md" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/functions/src/const.ts: -------------------------------------------------------------------------------- 1 | import { config } from 'firebase-functions' 2 | 3 | export const CLIENT_URL = config().env.clienturl 4 | export const WAVE_DOWNLOAD_URL = `${CLIENT_URL}/download` 5 | export const CLOUD_FUNCTIONS_URL = config().env.cloudfunctionsurl 6 | export const SENTRY_DSN = config().env.sentrydsn 7 | -------------------------------------------------------------------------------- /packages/client/src/pages/Gallery.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | 3 | import { Hero, Page } from '../components' 4 | import { SnippetsList } from '../components/SnippetsList' 5 | 6 | export const GalleryPage: FC = () => ( 7 | 8 | 9 | 10 | 11 | ) 12 | -------------------------------------------------------------------------------- /packages/client/src/pages/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Home' 2 | export * from './Account' 3 | export * from './NotFound' 4 | export * from './Gallery' 5 | export * from './MySnippets' 6 | export * from './Embed' 7 | export * from './Download' 8 | export * from './About' 9 | export * from './PrivacyPolicy' 10 | export * from './Terms' 11 | -------------------------------------------------------------------------------- /packages/client/src/components/ScrollToTop.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useEffect } from 'react' 2 | import { useLocation } from 'react-router-dom' 3 | 4 | export const ScrollToTop: FC = () => { 5 | const { pathname } = useLocation() 6 | 7 | useEffect(() => { 8 | window.scrollTo(0, 0) 9 | }, [pathname]) 10 | 11 | return null 12 | } 13 | -------------------------------------------------------------------------------- /packages/shared/src/collections/like.ts: -------------------------------------------------------------------------------- 1 | import { collection } from 'typesaurus' 2 | 3 | export type LikeCollection = 'snippets' 4 | 5 | export type LikeDocument = { 6 | snippetID: string 7 | userID: string 8 | likedAt: string 9 | collection: LikeCollection 10 | } 11 | 12 | export const likes = collection('likes') 13 | -------------------------------------------------------------------------------- /packages/client/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 3 | // allows you to do things like: 4 | // expect(element).toHaveTextContent(/react/i) 5 | // learn more: https://github.com/testing-library/jest-dom 6 | import '@testing-library/jest-dom/extend-expect' 7 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firestore": { 3 | "rules": "firestore.rules", 4 | "indexes": "firestore.indexes.json" 5 | }, 6 | "functions": { 7 | "source": "packages/functions" 8 | }, 9 | "emulators": { 10 | "firestore": { 11 | "port": "8633" 12 | } 13 | }, 14 | "storage": { 15 | "rules": "storage.rules" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/functions/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export const parseName = (fullName?: string): [string, string] => { 2 | let firstName = '' 3 | let lastName = '' 4 | if (fullName) { 5 | const splitName = fullName.split(/(\s+)/) 6 | firstName = splitName[0] ?? '' 7 | lastName = splitName[2] ?? '' 8 | } 9 | 10 | return [firstName, lastName] 11 | } 12 | -------------------------------------------------------------------------------- /packages/functions/src/utils/store.ts: -------------------------------------------------------------------------------- 1 | import { 2 | auth as admin_auth, 3 | storage as firebaseStorage, 4 | firestore, 5 | initializeApp, 6 | } from 'firebase-admin' 7 | import { config } from 'firebase-functions' 8 | 9 | initializeApp(config().firebase) 10 | 11 | export const db = firestore() 12 | export const auth = admin_auth() 13 | export const storage = firebaseStorage() 14 | -------------------------------------------------------------------------------- /packages/client/src/templates/index.ts: -------------------------------------------------------------------------------- 1 | import { keys } from '../utils' 2 | import { BLANK_TEMPLATE } from './blank' 3 | import { DEFAULT_TEMPLATE } from './default' 4 | // import { NPM_PACKAGE_TEMPLATE } from './npmPackage' 5 | 6 | export const TEMPLATES_DICT = { 7 | default: DEFAULT_TEMPLATE, 8 | 'blank-step': BLANK_TEMPLATE, 9 | // 'npm-package': NPM_PACKAGE_TEMPLATE, 10 | } 11 | 12 | export const TEMPLATES = keys(TEMPLATES_DICT) 13 | -------------------------------------------------------------------------------- /packages/client/src/setupProxy.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | // eslint-disable-next-line import/no-extraneous-dependencies, @typescript-eslint/no-var-requires 3 | const proxy = require('http-proxy-middleware') 4 | 5 | module.exports = function (app) { 6 | app.use( 7 | '/api', 8 | proxy({ 9 | target: process.env.REACT_APP_CLOUD_FUNCTIONS_URL, 10 | changeOrigin: true, 11 | pathRewrite: { '^/api': '' }, 12 | }), 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /packages/client/src/utils/normalizeArray.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Takes an array and returns a dictionary. Types could be way better but where this is 3 | * used will be put into a DB eventually. 4 | */ 5 | export function normalizeArray(array: Array, indexKey: keyof T) { 6 | const normalizedObject: any = {} 7 | for (let i = 0; i < array.length; i++) { 8 | const key = array[i][indexKey] 9 | normalizedObject[key] = array[i] 10 | } 11 | return normalizedObject as { [key: string]: T } 12 | } 13 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "yarn lint:fix && yarn test && yarn build" 3 | publish = "packages/client/build/" 4 | 5 | [context.production] 6 | command = "yarn lint:fix && yarn test && yarn build && yarn firebase deploy --token ${FIREBASE_DEPLOY_TOKEN}" 7 | 8 | [[redirects]] 9 | from = "/api/*" 10 | to = "https://us-east1-waves-production.cloudfunctions.net/:splat" 11 | status = 200 12 | force = true 13 | 14 | [[redirects]] 15 | from = "/*" 16 | to = "/index.html" 17 | status = 200 18 | -------------------------------------------------------------------------------- /packages/client/src/pages/MySnippets.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { where } from 'typesaurus' 3 | 4 | import { Hero, Page } from '../components' 5 | import { SnippetsList } from '../components/SnippetsList' 6 | import { useAuthState } from '../context' 7 | 8 | export const MySnippetsPage: FC = () => { 9 | const { user } = useAuthState() 10 | 11 | return ( 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /packages/shared/src/collections/user.ts: -------------------------------------------------------------------------------- 1 | import { collection } from 'typesaurus' 2 | 3 | export type Tiers = 'free' 4 | 5 | export type UserDocument = { 6 | created: string 7 | displayName: string 8 | email: string 9 | features: {} 10 | firstName: string 11 | isDisabled: boolean 12 | lastLogin: string | null 13 | lastName: string 14 | phoneNumber: string | null 15 | photoURL: string | null 16 | tier: Tiers 17 | userID: string 18 | } 19 | 20 | export const users = collection('users') 21 | -------------------------------------------------------------------------------- /patches/react-scripts+3.4.1.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/react-scripts/scripts/start.js b/node_modules/react-scripts/scripts/start.js 2 | index 2568ab3..270c7f0 100644 3 | --- a/node_modules/react-scripts/scripts/start.js 4 | +++ b/node_modules/react-scripts/scripts/start.js 5 | @@ -171,7 +171,7 @@ checkBrowsers(paths.appPath, isInteractive) 6 | devServer.close(); 7 | process.exit(); 8 | }); 9 | - process.stdin.resume(); 10 | + // process.stdin.resume(); 11 | } 12 | }) 13 | .catch(err => { 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 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 | # production 12 | /packages/*/build 13 | /packages/*/dist 14 | /deploy-functions/dist 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | lerna-debug.log 27 | firebase-debug.log 28 | firestore-debug.log 29 | ui-debug.log 30 | -------------------------------------------------------------------------------- /packages/client/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Preview' 2 | export * from './ScrollToTop' 3 | export * from './Page' 4 | export * from './PageRoute' 5 | export * from './Builder' 6 | export * from './Editor' 7 | export * from './core' 8 | export * from './Autocomplete' 9 | export * from './ColorPicker' 10 | export * from './WindowTitleBar' 11 | export * from './Hero' 12 | export * from './Nav' 13 | export * from './Footer' 14 | export * from './Toast' 15 | export * from './SnippetTags' 16 | export * from './Message' 17 | export * from './PreviewContainer' 18 | export * from './ExportModal' 19 | -------------------------------------------------------------------------------- /packages/client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Wave Snippets", 3 | "name": "Wave Snippets", 4 | "icons": [ 5 | { 6 | "src": "favicon.svg", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#0BC5EA", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es5", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react", 18 | "paths": { 19 | "@waves/shared": ["packages/shared/src"] 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/client/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@waves/shared", 3 | "version": "0.1.0", 4 | "scripts": { 5 | "clean": "run-p clean:*", 6 | "clean:node_modules": "rimraf node_modules", 7 | "clean:build": "rimraf build", 8 | "build": "tsc", 9 | "dev": "tsc -w", 10 | "test": "echo 'N/A'", 11 | "eslint": "eslint \"src/**/*.{js,jsx,ts,tsx}\"", 12 | "eslint:fix": "yarn eslint --fix", 13 | "tsc": "tsc -p tsconfig.json", 14 | "lint": "run-s tsc eslint", 15 | "lint:fix": "yarn tsc && yarn eslint:fix" 16 | }, 17 | "main": "dist/index.js", 18 | "dependencies": { 19 | "firebase": "^7.14.6", 20 | "typesaurus": "^6.0.0" 21 | }, 22 | "private": true 23 | } 24 | -------------------------------------------------------------------------------- /packages/client/src/hooks/useInterval.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | type Callback = () => void 4 | 5 | type UseIntervalOptions = { delay: number; pause: boolean } 6 | 7 | export const useInterval = ( 8 | callback: Callback, 9 | { delay, pause = false }: UseIntervalOptions, 10 | ): void => { 11 | const savedCallback = useRef() 12 | 13 | useEffect(() => { 14 | savedCallback.current = callback 15 | }, [callback]) 16 | 17 | useEffect(() => { 18 | const handler = () => savedCallback.current && savedCallback.current() 19 | 20 | if (!pause) { 21 | const id = setInterval(handler, delay) 22 | return () => clearInterval(id) 23 | } 24 | }, [delay, pause]) 25 | } 26 | -------------------------------------------------------------------------------- /packages/client/src/pages/Embed.tsx: -------------------------------------------------------------------------------- 1 | import { useGet } from '@typesaurus/react' 2 | import { snippets } from '@waves/shared' 3 | import React, { FC } from 'react' 4 | import { RouteComponentProps } from 'react-router-dom' 5 | 6 | import { Box, Spinner } from '../components' 7 | import { StandaloneSnippet } from '../components/StandaloneSnippet' 8 | 9 | type EmbedPageProps = {} & RouteComponentProps<{ snippetID: string }> 10 | 11 | export const EmbedPage: FC = ({ 12 | match: { 13 | params: { snippetID }, 14 | }, 15 | }) => { 16 | const snippet = useGet(snippets, snippetID) 17 | 18 | if (!snippet) { 19 | return 20 | } 21 | 22 | return ( 23 | 24 | 25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /packages/client/src/components/Page.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | 3 | import { Box, BoxProps } from './core' 4 | 5 | export type PageProps = BoxProps & { 6 | /** 7 | * Squeezes the container a bit for word heavy pages to make it easier to read. 8 | * 9 | * @default 10 | * false 11 | */ 12 | wordyPage?: boolean 13 | } 14 | 15 | export const Page: FC = ({ 16 | children, 17 | wordyPage = false, 18 | ...rest 19 | }) => ( 20 | 31 | {children} 32 | 33 | ) 34 | -------------------------------------------------------------------------------- /packages/client/src/components/Toast.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { 3 | ToastContainer as ToastContainerCore, 4 | ToastContainerProps, 5 | ToastContent, 6 | ToastOptions, 7 | toast as toastCore, 8 | useToast as useToastCore, 9 | } from 'react-toastify' 10 | 11 | import { useColorMode } from './core' 12 | 13 | export const useCreateToast = () => { 14 | const { colorMode } = useColorMode() 15 | 16 | return (content: ToastContent, options: Partial = {}) => 17 | toastCore(content, { 18 | // @ts-ignore Dark is missing in types 19 | type: colorMode === 'light' ? 'default' : 'dark', 20 | ...options, 21 | }) 22 | } 23 | 24 | export const ToastContainer: FC = (props) => ( 25 | 26 | ) 27 | 28 | export const useToaster = useToastCore 29 | -------------------------------------------------------------------------------- /packages/client/src/templates/blank.ts: -------------------------------------------------------------------------------- 1 | import { BaseSnippet } from '../types' 2 | 3 | export const BLANK_TEMPLATE: BaseSnippet = { 4 | cycle: false, 5 | steps: [ 6 | { 7 | focus: '', 8 | subtitle: '', 9 | code: '', 10 | title: '', 11 | lang: 'typescript', 12 | id: '1', 13 | }, 14 | ], 15 | backgroundColor: { 16 | g: 197, 17 | r: 11, 18 | b: 234, 19 | a: 100, 20 | }, 21 | name: 'Blank', 22 | theme: 'nightOwl', 23 | tags: ['typescript'], 24 | cycleSpeed: 1500, 25 | immediate: false, 26 | showLineNumbers: false, 27 | showBackground: true, 28 | windowControlsPosition: null, 29 | windowControlsType: null, 30 | startingStep: 0, 31 | springPreset: 'default', 32 | status: 'draft', 33 | defaultWindowTitle: 'hello.ts', 34 | visibility: 'unlisted', 35 | defaultLanguage: 'typescript', 36 | } 37 | -------------------------------------------------------------------------------- /packages/client/src/components/PreviewStepSelector.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | 3 | import { usePreviewDispatch, usePreviewState } from '../context' 4 | import { times } from '../utils' 5 | import { Box, Button } from './core' 6 | 7 | type PreviewStepSelectorProps = { 8 | totalSteps: number 9 | } 10 | 11 | export const PreviewStepSelector: FC = ({ 12 | totalSteps, 13 | }) => { 14 | const dispatch = usePreviewDispatch() 15 | const { currentStep } = usePreviewState() 16 | 17 | return ( 18 | 19 | {times( 20 | (i) => ( 21 | 28 | ), 29 | totalSteps, 30 | )} 31 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /packages/client/src/config/firebase.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-namespace */ 2 | import 'firebase/firestore' 3 | import 'firebase/auth' 4 | import 'firebase/analytics' 5 | 6 | import * as firebase from 'firebase/app' 7 | 8 | const firebaseConfig = { 9 | apiKey: process.env.REACT_APP_FIREBASE_API_KEY, 10 | authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, 11 | databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL, 12 | projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, 13 | storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, 14 | messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID, 15 | appId: process.env.REACT_APP_FIREBASE_APP_ID, 16 | measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID, 17 | } 18 | 19 | // Initialize Firebase 20 | firebase.initializeApp(firebaseConfig) 21 | export const analytics = firebase.analytics() 22 | 23 | export { firebase } 24 | -------------------------------------------------------------------------------- /packages/client/src/hooks/useCycle.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch, SetStateAction, useState } from 'react' 2 | 3 | import { DEFAULT_CYCLE_SPEED } from '../const' 4 | import { useInterval } from './useInterval' 5 | 6 | type UseCycleProps = { 7 | totalSteps: number 8 | initialStep?: number 9 | /** 10 | * Time in milliseconds for the springs to be activated between steps. A lower number is a quicker animation. 11 | */ 12 | cycleSpeed?: number 13 | 14 | pause?: boolean 15 | } 16 | 17 | export const useCycle = ({ 18 | totalSteps, 19 | initialStep = 0, 20 | cycleSpeed = DEFAULT_CYCLE_SPEED, 21 | pause = false, 22 | }: UseCycleProps): [number, Dispatch>] => { 23 | const [currentStep, setCurrentStep] = useState(initialStep) 24 | 25 | useInterval( 26 | () => setCurrentStep(currentStep === totalSteps ? 0 : currentStep + 1), 27 | { delay: cycleSpeed, pause }, 28 | ) 29 | 30 | return [currentStep, setCurrentStep] 31 | } 32 | -------------------------------------------------------------------------------- /packages/client/src/components/Message.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, ReactNode } from 'react' 2 | 3 | import { Box, BoxProps, Icon, Text } from './core' 4 | 5 | type MessageProps = { 6 | title?: ReactNode 7 | content?: ReactNode 8 | icon?: string 9 | } & BoxProps 10 | 11 | export const Message: FC = ({ 12 | title, 13 | content, 14 | icon, 15 | ...rest 16 | }) => ( 17 | 25 | {icon && } 26 | 27 | {title && ( 28 | 34 | {title} 35 | 36 | )} 37 | {content && ( 38 | {content} 39 | )} 40 | 41 | 42 | ) 43 | -------------------------------------------------------------------------------- /packages/client/src/components/PageRoute.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled' 2 | import React, { FC } from 'react' 3 | import { Route, RouteProps } from 'react-router-dom' 4 | 5 | import { Footer } from './Footer' 6 | import { Nav } from './Nav' 7 | 8 | export type PageRouteProps = RouteProps & { 9 | showNav?: boolean 10 | showFooter?: boolean 11 | } 12 | 13 | const Wrapper = styled.div` 14 | display: flex; 15 | flex-direction: column; 16 | min-height: 100vh; 17 | ` 18 | 19 | export const PageRoute: FC = ({ 20 | showNav = true, 21 | showFooter = true, 22 | render, 23 | component: Component, 24 | ...rest 25 | }) => ( 26 | ( 29 | 30 | {showNav &&