├── todo-list ├── after │ ├── src │ │ ├── vite-env.d.ts │ │ ├── styles.css │ │ └── main.ts │ ├── package.json │ ├── .gitignore │ ├── index.html │ ├── tsconfig.json │ └── package-lock.json └── before │ ├── styles.css │ └── index.html ├── converting-js-to-ts ├── after │ ├── src │ │ ├── vite-env.d.ts │ │ ├── styles.css │ │ ├── minesweeper.ts │ │ └── script.ts │ ├── .gitignore │ ├── package.json │ ├── index.html │ ├── tsconfig.json │ └── package-lock.json └── before │ ├── package.json │ ├── .gitignore │ ├── index.html │ ├── styles.css │ ├── script.js │ ├── minesweeper.js │ └── package-lock.json └── google-calendar-clone ├── after ├── src │ ├── vite-env.d.ts │ ├── utils │ │ ├── cc.ts │ │ ├── types.ts │ │ └── formatDate.ts │ ├── main.tsx │ ├── App.tsx │ ├── context │ │ ├── useEvent.ts │ │ └── Events.tsx │ ├── components │ │ ├── Modal.tsx │ │ ├── OverflowContainer.tsx │ │ └── Calendar.tsx │ └── styles.css ├── vite.config.ts ├── tsconfig.node.json ├── .gitignore ├── .eslintrc.cjs ├── index.html ├── tsconfig.json ├── package.json └── public │ └── vite.svg └── before ├── README.md ├── styles.css └── index.html /todo-list/after/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /converting-js-to-ts/after/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /google-calendar-clone/after/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /google-calendar-clone/after/src/utils/cc.ts: -------------------------------------------------------------------------------- 1 | export function cc(...classes: unknown[]) { 2 | return classes.filter(c => typeof c === "string").join(" ") 3 | } 4 | -------------------------------------------------------------------------------- /google-calendar-clone/after/src/utils/types.ts: -------------------------------------------------------------------------------- 1 | export type UnionOmit = T extends unknown 2 | ? Omit 3 | : never 4 | -------------------------------------------------------------------------------- /google-calendar-clone/after/src/utils/formatDate.ts: -------------------------------------------------------------------------------- 1 | export function formatDate(date: Date, options?: Intl.DateTimeFormatOptions) { 2 | return new Intl.DateTimeFormat(undefined, options).format(date) 3 | } 4 | -------------------------------------------------------------------------------- /google-calendar-clone/after/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /google-calendar-clone/after/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /google-calendar-clone/after/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | import App from "./App.tsx" 4 | 5 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 6 | 7 | 8 | 9 | ) 10 | -------------------------------------------------------------------------------- /google-calendar-clone/after/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Calendar } from "./components/Calendar" 2 | import { EventsProvider } from "./context/Events" 3 | import "./styles.css" 4 | 5 | export default function App() { 6 | return ( 7 | 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /todo-list/after/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "after", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "typescript": "^5.0.2", 13 | "vite": "^4.4.5" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /todo-list/after/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /converting-js-to-ts/before/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "before", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite": "^4.4.5" 13 | }, 14 | "dependencies": { 15 | "lodash": "^4.17.21" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /converting-js-to-ts/after/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /converting-js-to-ts/before/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /google-calendar-clone/after/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /google-calendar-clone/after/src/context/useEvent.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from "react" 2 | import { Context } from "./Events" 3 | 4 | export const EVENT_COLORS = ["red", "green", "blue"] as const 5 | 6 | export function useEvents() { 7 | const value = useContext(Context) 8 | if (value == null) { 9 | throw new Error("useEvents must be used within an EventsProvider") 10 | } 11 | 12 | return value 13 | } 14 | -------------------------------------------------------------------------------- /converting-js-to-ts/after/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "before", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "@types/lodash": "^4.14.197", 13 | "typescript": "^5.2.2", 14 | "vite": "^4.4.5" 15 | }, 16 | "dependencies": { 17 | "lodash": "^4.17.21" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /todo-list/after/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Advanced Todo List 5 | 6 | 7 | 8 | 9 | 10 | 11 | New Todo 12 | 13 | Add Todo 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /google-calendar-clone/after/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { browser: true, es2020: true }, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:react-hooks/recommended', 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 10 | plugins: ['react-refresh'], 11 | rules: { 12 | 'react-refresh/only-export-components': 'warn', 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /google-calendar-clone/after/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /converting-js-to-ts/before/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Minesweeper 9 | 10 | 11 | Minesweeper 12 | Mines Left: 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /converting-js-to-ts/after/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Minesweeper 9 | 10 | 11 | Minesweeper 12 | Mines Left: 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /todo-list/after/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true 21 | }, 22 | "include": ["src"] 23 | } 24 | -------------------------------------------------------------------------------- /converting-js-to-ts/after/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true 21 | }, 22 | "include": ["src"] 23 | } 24 | -------------------------------------------------------------------------------- /todo-list/after/src/styles.css: -------------------------------------------------------------------------------- 1 | #new-todo-form { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | #new-todo-form > * { 7 | margin: 0.15rem; 8 | } 9 | 10 | #list { 11 | padding: 0; 12 | } 13 | 14 | .list-item { 15 | list-style: none; 16 | } 17 | 18 | .list-item-label:hover, 19 | .label-input:hover { 20 | cursor: pointer; 21 | } 22 | 23 | .list-item-label:hover > .label-text { 24 | color: #333; 25 | text-decoration: line-through; 26 | } 27 | 28 | .label-input:checked ~ .label-text { 29 | text-decoration: line-through; 30 | color: #aaa; 31 | } 32 | 33 | .delete-btn { 34 | margin-left: 0.5rem; 35 | } 36 | 37 | button { 38 | cursor: pointer; 39 | } 40 | -------------------------------------------------------------------------------- /todo-list/before/styles.css: -------------------------------------------------------------------------------- 1 | #new-todo-form { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | #new-todo-form > * { 7 | margin: 0.15rem; 8 | } 9 | 10 | #list { 11 | padding: 0; 12 | } 13 | 14 | .list-item { 15 | list-style: none; 16 | } 17 | 18 | .list-item-label:hover, 19 | .label-input:hover { 20 | cursor: pointer; 21 | } 22 | 23 | .list-item-label:hover > .label-text { 24 | color: #333; 25 | text-decoration: line-through; 26 | } 27 | 28 | .label-input:checked ~ .label-text { 29 | text-decoration: line-through; 30 | color: #aaa; 31 | } 32 | 33 | .delete-btn { 34 | margin-left: 0.5rem; 35 | } 36 | 37 | button { 38 | cursor: pointer; 39 | } 40 | -------------------------------------------------------------------------------- /google-calendar-clone/after/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /todo-list/before/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Advanced Todo List 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Placeholder Task 13 | 14 | Delete 15 | 16 | 17 | 18 | 19 | New Todo 20 | 21 | Add Todo 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /google-calendar-clone/after/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "after", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "date-fns": "^2.30.0", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^18.0.37", 19 | "@types/react-dom": "^18.0.11", 20 | "@typescript-eslint/eslint-plugin": "^5.59.0", 21 | "@typescript-eslint/parser": "^5.59.0", 22 | "@vitejs/plugin-react-swc": "^3.0.0", 23 | "eslint": "^8.38.0", 24 | "eslint-plugin-react-hooks": "^4.6.0", 25 | "eslint-plugin-react-refresh": "^0.3.4", 26 | "typescript": "^5.0.2", 27 | "vite": "^4.3.9" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /converting-js-to-ts/after/src/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | background-color: #333; 8 | display: flex; 9 | align-items: center; 10 | font-size: 3rem; 11 | flex-direction: column; 12 | color: white; 13 | } 14 | 15 | .title { 16 | margin: 20px; 17 | } 18 | 19 | .subtext { 20 | color: #CCC; 21 | font-size: 1.5rem; 22 | margin-bottom: 10px; 23 | } 24 | 25 | .board { 26 | display: inline-grid; 27 | padding: 10px; 28 | grid-template-columns: repeat(var(--size), 60px); 29 | grid-template-rows: repeat(var(--size), 60px); 30 | gap: 4px; 31 | background-color: #777; 32 | } 33 | 34 | .board > * { 35 | width: 100%; 36 | height: 100%; 37 | display: flex; 38 | justify-content: center; 39 | align-items: center; 40 | color: white; 41 | border: 2px solid #BBB; 42 | user-select: none; 43 | } 44 | 45 | .board > [data-status="hidden"] { 46 | background-color: #BBB; 47 | cursor: pointer; 48 | } 49 | 50 | .board > [data-status="mine"] { 51 | background-color: red; 52 | } 53 | 54 | .board > [data-status="number"] { 55 | background-color: none; 56 | } 57 | 58 | .board > [data-status="marked"] { 59 | background-color: yellow; 60 | } -------------------------------------------------------------------------------- /converting-js-to-ts/before/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | background-color: #333; 8 | display: flex; 9 | align-items: center; 10 | font-size: 3rem; 11 | flex-direction: column; 12 | color: white; 13 | } 14 | 15 | .title { 16 | margin: 20px; 17 | } 18 | 19 | .subtext { 20 | color: #CCC; 21 | font-size: 1.5rem; 22 | margin-bottom: 10px; 23 | } 24 | 25 | .board { 26 | display: inline-grid; 27 | padding: 10px; 28 | grid-template-columns: repeat(var(--size), 60px); 29 | grid-template-rows: repeat(var(--size), 60px); 30 | gap: 4px; 31 | background-color: #777; 32 | } 33 | 34 | .board > * { 35 | width: 100%; 36 | height: 100%; 37 | display: flex; 38 | justify-content: center; 39 | align-items: center; 40 | color: white; 41 | border: 2px solid #BBB; 42 | user-select: none; 43 | } 44 | 45 | .board > [data-status="hidden"] { 46 | background-color: #BBB; 47 | cursor: pointer; 48 | } 49 | 50 | .board > [data-status="mine"] { 51 | background-color: red; 52 | } 53 | 54 | .board > [data-status="number"] { 55 | background-color: none; 56 | } 57 | 58 | .board > [data-status="marked"] { 59 | background-color: yellow; 60 | } -------------------------------------------------------------------------------- /google-calendar-clone/after/src/components/Modal.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, useEffect, useLayoutEffect, useRef, useState } from "react" 2 | import { createPortal } from "react-dom" 3 | import { cc } from "../utils/cc" 4 | 5 | export type ModalProps = { 6 | children: ReactNode 7 | isOpen: boolean 8 | onClose: () => void 9 | } 10 | 11 | export function Modal({ children, isOpen, onClose }: ModalProps) { 12 | const [isClosing, setIsClosing] = useState(false) 13 | const prevIsOpen = useRef() 14 | 15 | useEffect(() => { 16 | function handler(e: KeyboardEvent) { 17 | if (e.key === "Escape") onClose() 18 | } 19 | 20 | document.addEventListener("keydown", handler) 21 | 22 | return () => { 23 | document.removeEventListener("keydown", handler) 24 | } 25 | }, [onClose]) 26 | 27 | useLayoutEffect(() => { 28 | if (!isOpen && prevIsOpen.current) { 29 | setIsClosing(true) 30 | } 31 | 32 | prevIsOpen.current = isOpen 33 | }, [isOpen]) 34 | 35 | if (!isOpen && !isClosing) return null 36 | 37 | return createPortal( 38 | setIsClosing(false)} 40 | className={cc("modal", isClosing && "closing")} 41 | > 42 | 43 | {children} 44 | , 45 | document.querySelector("#modal-container") as HTMLElement 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /google-calendar-clone/after/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /todo-list/after/src/main.ts: -------------------------------------------------------------------------------- 1 | import "./styles.css" 2 | 3 | type Todo = { 4 | id: string 5 | name: string 6 | complete: boolean 7 | } 8 | 9 | const form = document.querySelector("#new-todo-form")! 10 | const todoInput = document.querySelector("#todo-input")! 11 | const list = document.querySelector("#list")! 12 | 13 | let todos = loadTodos() 14 | todos.forEach(renderNewTodo) 15 | 16 | form.addEventListener("submit", e => { 17 | e.preventDefault() 18 | 19 | const todoName = todoInput.value 20 | if (todoName === "") return 21 | const newTodo = { 22 | id: crypto.randomUUID(), 23 | name: todoName, 24 | complete: false, 25 | } 26 | 27 | todos.push(newTodo) 28 | renderNewTodo(newTodo) 29 | saveTodos() 30 | todoInput.value = "" 31 | }) 32 | 33 | function renderNewTodo(todo: Todo) { 34 | const listItem = document.createElement("li") 35 | listItem.classList.add("list-item") 36 | 37 | const label = document.createElement("label") 38 | label.classList.add("list-item-label") 39 | 40 | const checkbox = document.createElement("input") 41 | checkbox.type = "checkbox" 42 | checkbox.checked = todo.complete 43 | checkbox.classList.add("label-input") 44 | checkbox.addEventListener("change", () => { 45 | todo.complete = checkbox.checked 46 | saveTodos() 47 | }) 48 | 49 | const textElement = document.createElement("span") 50 | textElement.classList.add("label-text") 51 | textElement.innerText = todo.name 52 | 53 | const deleteButton = document.createElement("button") 54 | deleteButton.classList.add("delete-btn") 55 | deleteButton.innerText = "Delete" 56 | deleteButton.addEventListener("click", () => { 57 | listItem.remove() 58 | todos = todos.filter(t => t.id !== todo.id) 59 | saveTodos() 60 | }) 61 | 62 | label.append(checkbox, textElement) 63 | listItem.append(label, deleteButton) 64 | list.append(listItem) 65 | } 66 | 67 | function saveTodos() { 68 | localStorage.setItem("todos", JSON.stringify(todos)) 69 | } 70 | 71 | function loadTodos() { 72 | const value = localStorage.getItem("todos") 73 | if (value == null) return [] 74 | return JSON.parse(value) as Todo[] 75 | } 76 | -------------------------------------------------------------------------------- /google-calendar-clone/after/src/components/OverflowContainer.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, Key, useState, useRef, useLayoutEffect } from "react" 2 | 3 | type OverflowContainerProps = { 4 | items: T[] 5 | renderItem: (item: T) => ReactNode 6 | renderOverflow: (overflowAmount: number) => ReactNode 7 | getKey: (item: T) => Key 8 | className?: string 9 | } 10 | 11 | export function OverflowContainer({ 12 | items, 13 | getKey, 14 | renderItem, 15 | renderOverflow, 16 | className, 17 | }: OverflowContainerProps) { 18 | const containerRef = useRef(null) 19 | const [overflowAmount, setOverflowAmount] = useState(0) 20 | 21 | useLayoutEffect(() => { 22 | if (containerRef.current == null) return 23 | 24 | const observer = new ResizeObserver(entries => { 25 | const containerElement = entries[0]?.target 26 | if (containerElement == null) return 27 | const children = 28 | containerElement.querySelectorAll("[data-item]") 29 | const overflowElement = 30 | containerElement.parentElement?.querySelector( 31 | "[data-overflow]" 32 | ) 33 | 34 | if (overflowElement != null) overflowElement.style.display = "none" 35 | children.forEach(child => child.style.removeProperty("display")) 36 | let amount = 0 37 | for (let i = children.length - 1; i >= 0; i--) { 38 | const child = children[i] 39 | if (containerElement.scrollHeight <= containerElement.clientHeight) { 40 | break 41 | } 42 | amount = children.length - i 43 | child.style.display = "none" 44 | overflowElement?.style.removeProperty("display") 45 | } 46 | setOverflowAmount(amount) 47 | }) 48 | 49 | observer.observe(containerRef.current) 50 | 51 | return () => observer.disconnect() 52 | }, [items]) 53 | 54 | return ( 55 | <> 56 | 57 | {items.map(item => ( 58 | 59 | {renderItem(item)} 60 | 61 | ))} 62 | 63 | {renderOverflow(overflowAmount)} 64 | > 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /google-calendar-clone/after/src/context/Events.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, createContext, useEffect, useState } from "react" 2 | import { UnionOmit } from "../utils/types" 3 | import { EVENT_COLORS } from "./useEvent" 4 | 5 | export type Event = { 6 | id: string 7 | name: string 8 | color: (typeof EVENT_COLORS)[number] 9 | date: Date 10 | } & ( 11 | | { allDay: false; startTime: string; endTime: string } 12 | | { allDay: true; startTime?: never; endTime?: never } 13 | ) 14 | 15 | type EventsContext = { 16 | events: Event[] 17 | addEvent: (event: UnionOmit) => void 18 | updateEvent: (id: string, eventDetails: UnionOmit) => void 19 | deleteEvent: (id: string) => void 20 | } 21 | 22 | export const Context = createContext(null) 23 | 24 | type EventsProviderProps = { 25 | children: ReactNode 26 | } 27 | 28 | export function EventsProvider({ children }: EventsProviderProps) { 29 | const [events, setEvents] = useLocalStorage("EVENTS", []) 30 | 31 | function addEvent(eventDetails: UnionOmit) { 32 | setEvents(e => [...e, { ...eventDetails, id: crypto.randomUUID() }]) 33 | } 34 | 35 | function updateEvent(id: string, eventDetails: UnionOmit) { 36 | setEvents(e => { 37 | return e.map(event => { 38 | return event.id === id ? { id, ...eventDetails } : event 39 | }) 40 | }) 41 | } 42 | 43 | function deleteEvent(id: string) { 44 | setEvents(e => e.filter(event => event.id !== id)) 45 | } 46 | 47 | return ( 48 | 49 | {children} 50 | 51 | ) 52 | } 53 | 54 | function useLocalStorage(key: string, initialValue: Event[]) { 55 | const [value, setValue] = useState(() => { 56 | const jsonValue = localStorage.getItem(key) 57 | if (jsonValue == null) return initialValue 58 | 59 | return (JSON.parse(jsonValue) as Event[]).map(event => { 60 | if (event.date instanceof Date) return event 61 | return { ...event, date: new Date(event.date) } 62 | }) 63 | }) 64 | 65 | useEffect(() => { 66 | localStorage.setItem(key, JSON.stringify(value)) 67 | }, [value, key]) 68 | 69 | return [value, setValue] as const 70 | } 71 | -------------------------------------------------------------------------------- /converting-js-to-ts/before/script.js: -------------------------------------------------------------------------------- 1 | // Display/UI 2 | 3 | import { 4 | TILE_STATUSES, 5 | createBoard, 6 | markTile, 7 | revealTile, 8 | checkWin, 9 | checkLose, 10 | positionMatch, 11 | markedTilesCount, 12 | } from "./minesweeper.js" 13 | 14 | const BOARD_SIZE = 10 15 | const NUMBER_OF_MINES = 10 16 | 17 | let board = createBoard( 18 | BOARD_SIZE, 19 | getMinePositions(BOARD_SIZE, NUMBER_OF_MINES) 20 | ) 21 | const boardElement = document.querySelector(".board") 22 | const minesLeftText = document.querySelector("[data-mine-count]") 23 | const messageText = document.querySelector(".subtext") 24 | 25 | function render() { 26 | boardElement.innerHTML = "" 27 | checkGameEnd() 28 | 29 | getTileElements().forEach(element => { 30 | boardElement.append(element) 31 | }) 32 | 33 | listMinesLeft() 34 | } 35 | 36 | function getTileElements() { 37 | return board.flatMap(row => { 38 | return row.map(tileToElement) 39 | }) 40 | } 41 | 42 | function tileToElement(tile) { 43 | const element = document.createElement("div") 44 | element.dataset.status = tile.status 45 | element.dataset.x = tile.x 46 | element.dataset.y = tile.y 47 | element.textContent = tile.adjacentMinesCount || "" 48 | return element 49 | } 50 | 51 | boardElement.addEventListener("click", e => { 52 | if (!e.target.matches("[data-status]")) return 53 | 54 | board = revealTile(board, { 55 | x: parseInt(e.target.dataset.x), 56 | y: parseInt(e.target.dataset.y), 57 | }) 58 | render() 59 | }) 60 | 61 | boardElement.addEventListener("contextmenu", e => { 62 | if (!e.target.matches("[data-status]")) return 63 | 64 | e.preventDefault() 65 | board = markTile(board, { 66 | x: parseInt(e.target.dataset.x), 67 | y: parseInt(e.target.dataset.y), 68 | }) 69 | render() 70 | }) 71 | 72 | boardElement.style.setProperty("--size", BOARD_SIZE) 73 | render() 74 | 75 | function listMinesLeft() { 76 | minesLeftText.textContent = NUMBER_OF_MINES - markedTilesCount(board) 77 | } 78 | 79 | function checkGameEnd() { 80 | const win = checkWin(board) 81 | const lose = checkLose(board) 82 | 83 | if (win || lose) { 84 | boardElement.addEventListener("click", stopProp, { capture: true }) 85 | boardElement.addEventListener("contextmenu", stopProp, { capture: true }) 86 | } 87 | 88 | if (win) { 89 | messageText.textContent = "You Win" 90 | } 91 | if (lose) { 92 | messageText.textContent = "You Lose" 93 | board.forEach(row => { 94 | row.forEach(tile => { 95 | if (tile.status === TILE_STATUSES.MARKED) board = markTile(board, tile) 96 | if (tile.mine) board = revealTile(board, tile) 97 | }) 98 | }) 99 | } 100 | } 101 | 102 | function stopProp(e) { 103 | e.stopImmediatePropagation() 104 | } 105 | 106 | function getMinePositions(boardSize, numberOfMines) { 107 | const positions = [] 108 | 109 | while (positions.length < numberOfMines) { 110 | const position = { 111 | x: randomNumber(boardSize), 112 | y: randomNumber(boardSize), 113 | } 114 | 115 | if (!positions.some(positionMatch.bind(null, position))) { 116 | positions.push(position) 117 | } 118 | } 119 | 120 | return positions 121 | } 122 | 123 | function randomNumber(size) { 124 | return Math.floor(Math.random() * size) 125 | } 126 | -------------------------------------------------------------------------------- /converting-js-to-ts/before/minesweeper.js: -------------------------------------------------------------------------------- 1 | import { times, range } from "lodash/fp" 2 | 3 | export const TILE_STATUSES = { 4 | HIDDEN: "hidden", 5 | MINE: "mine", 6 | NUMBER: "number", 7 | MARKED: "marked", 8 | } 9 | 10 | export function createBoard(boardSize, minePositions) { 11 | return times(x => { 12 | return times(y => { 13 | return { 14 | x, 15 | y, 16 | mine: minePositions.some(positionMatch.bind(null, { x, y })), 17 | status: TILE_STATUSES.HIDDEN, 18 | } 19 | }, boardSize) 20 | }, boardSize) 21 | } 22 | 23 | export function markedTilesCount(board) { 24 | return board.reduce((count, row) => { 25 | return ( 26 | count + row.filter(tile => tile.status === TILE_STATUSES.MARKED).length 27 | ) 28 | }, 0) 29 | } 30 | 31 | export function markTile(board, { x, y }) { 32 | const tile = board[x][y] 33 | if ( 34 | tile.status !== TILE_STATUSES.HIDDEN && 35 | tile.status !== TILE_STATUSES.MARKED 36 | ) { 37 | return board 38 | } 39 | 40 | if (tile.status === TILE_STATUSES.MARKED) { 41 | return replaceTile( 42 | board, 43 | { x, y }, 44 | { ...tile, status: TILE_STATUSES.HIDDEN } 45 | ) 46 | } else { 47 | return replaceTile( 48 | board, 49 | { x, y }, 50 | { ...tile, status: TILE_STATUSES.MARKED } 51 | ) 52 | } 53 | } 54 | 55 | function replaceTile(board, position, newTile) { 56 | return board.map((row, x) => { 57 | return row.map((tile, y) => { 58 | if (positionMatch(position, { x, y })) { 59 | return newTile 60 | } 61 | return tile 62 | }) 63 | }) 64 | } 65 | 66 | export function revealTile(board, { x, y }) { 67 | const tile = board[x][y] 68 | if (tile.status !== TILE_STATUSES.HIDDEN) { 69 | return board 70 | } 71 | 72 | if (tile.mine) { 73 | return replaceTile(board, { x, y }, { ...tile, status: TILE_STATUSES.MINE }) 74 | } 75 | 76 | const adjacentTiles = nearbyTiles(board, tile) 77 | const mines = adjacentTiles.filter(t => t.mine) 78 | const newBoard = replaceTile( 79 | board, 80 | { x, y }, 81 | { ...tile, status: TILE_STATUSES.NUMBER, adjacentMinesCount: mines.length } 82 | ) 83 | if (mines.length === 0) { 84 | return adjacentTiles.reduce((b, t) => { 85 | return revealTile(b, t) 86 | }, newBoard) 87 | } 88 | return newBoard 89 | } 90 | 91 | export function checkWin(board) { 92 | return board.every(row => { 93 | return row.every(tile => { 94 | return ( 95 | tile.status === TILE_STATUSES.NUMBER || 96 | (tile.mine && 97 | (tile.status === TILE_STATUSES.HIDDEN || 98 | tile.status === TILE_STATUSES.MARKED)) 99 | ) 100 | }) 101 | }) 102 | } 103 | 104 | export function checkLose(board) { 105 | return board.some(row => { 106 | return row.some(tile => { 107 | return tile.status === TILE_STATUSES.MINE 108 | }) 109 | }) 110 | } 111 | 112 | export function positionMatch(a, b) { 113 | return a.x === b.x && a.y === b.y 114 | } 115 | 116 | function nearbyTiles(board, { x, y }) { 117 | const offsets = range(-1, 2) 118 | 119 | return offsets 120 | .flatMap(xOffset => { 121 | return offsets.map(yOffset => { 122 | return board[x + xOffset]?.[y + yOffset] 123 | }) 124 | }) 125 | .filter(tile => tile != null) 126 | } 127 | -------------------------------------------------------------------------------- /converting-js-to-ts/after/src/minesweeper.ts: -------------------------------------------------------------------------------- 1 | import { times, range } from "lodash/fp" 2 | 3 | type TileStatus = (typeof TILE_STATUSES)[number] 4 | export type Position = { 5 | x: number 6 | y: number 7 | } 8 | export type Tile = Position & { 9 | mine: boolean 10 | adjacentMinesCount?: number 11 | status: TileStatus 12 | } 13 | type Board = Tile[][] 14 | 15 | const TILE_STATUSES = ["hidden", "mine", "number", "marked"] as const 16 | 17 | export function createBoard( 18 | boardSize: number, 19 | minePositions: Position[] 20 | ): Board { 21 | return times(x => { 22 | return times(y => { 23 | return { 24 | x, 25 | y, 26 | mine: minePositions.some(minePos => { 27 | return positionMatch(minePos, { x, y }) 28 | }), 29 | status: "hidden", 30 | } 31 | }, boardSize) 32 | }, boardSize) 33 | } 34 | 35 | export function markedTilesCount(board: Board) { 36 | return board.reduce((count, row) => { 37 | return count + row.filter(tile => tile.status === "marked").length 38 | }, 0) 39 | } 40 | 41 | export function markTile(board: Board, { x, y }: Position) { 42 | const tile = board[x][y] 43 | if (tile.status !== "hidden" && tile.status !== "marked") { 44 | return board 45 | } 46 | 47 | if (tile.status === "marked") { 48 | return replaceTile(board, { x, y }, { ...tile, status: "hidden" }) 49 | } else { 50 | return replaceTile(board, { x, y }, { ...tile, status: "marked" }) 51 | } 52 | } 53 | 54 | function replaceTile(board: Board, position: Position, newTile: Tile) { 55 | return board.map((row, x) => { 56 | return row.map((tile, y) => { 57 | if (positionMatch(position, { x, y })) { 58 | return newTile 59 | } 60 | return tile 61 | }) 62 | }) 63 | } 64 | 65 | export function revealTile(board: Board, { x, y }: Position): Board { 66 | const tile = board[x][y] 67 | if (tile.status !== "hidden") { 68 | return board 69 | } 70 | 71 | if (tile.mine) { 72 | return replaceTile(board, { x, y }, { ...tile, status: "mine" }) 73 | } 74 | 75 | const adjacentTiles = nearbyTiles(board, tile) 76 | const mines = adjacentTiles.filter(t => t.mine) 77 | const newBoard = replaceTile( 78 | board, 79 | { x, y }, 80 | { ...tile, status: "number", adjacentMinesCount: mines.length } 81 | ) 82 | if (mines.length === 0) { 83 | return adjacentTiles.reduce((b, t) => { 84 | return revealTile(b, t) 85 | }, newBoard) 86 | } 87 | return newBoard 88 | } 89 | 90 | export function checkWin(board: Board) { 91 | return board.every(row => { 92 | return row.every(tile => { 93 | return ( 94 | tile.status === "number" || 95 | (tile.mine && (tile.status === "hidden" || tile.status === "marked")) 96 | ) 97 | }) 98 | }) 99 | } 100 | 101 | export function checkLose(board: Board) { 102 | return board.some(row => { 103 | return row.some(tile => { 104 | return tile.status === "mine" 105 | }) 106 | }) 107 | } 108 | 109 | export function positionMatch(a: Position, b: Position) { 110 | return a.x === b.x && a.y === b.y 111 | } 112 | 113 | function nearbyTiles(board: Board, { x, y }: Position) { 114 | const offsets = range(-1, 2) 115 | 116 | return offsets 117 | .flatMap(xOffset => { 118 | return offsets.map(yOffset => { 119 | return board[x + xOffset]?.[y + yOffset] 120 | }) 121 | }) 122 | .filter(tile => tile != null) 123 | } 124 | -------------------------------------------------------------------------------- /google-calendar-clone/before/README.md: -------------------------------------------------------------------------------- 1 | # Before Getting Started 2 | 3 | This is a quite complex project and is rather large so do not be surprised if this takes you a few days/weeks to complete. Some of the more complex parts of this project took me many hours to build and I spent nearly a full week on this project so don't worry if it takes you awhile. 4 | 5 | Just like all the other projects in this course I have all the starting code you need for this project. The `styles.css` file contains all the styles you need for the project. The `index.html` file has an example calendar with all different types of events included. Make sure you look through each of the CSS classes being applied so you full understand what classes need to be applied in each situation. _Also, you can ignore the `#root` div element that wraps the entire calendar. This is just there so I can emulate how React will render the content inside its own `#root` div and make sure all styles are properly applied._ Lastly, to render the modals you can just uncomment them in the `index.html` to see what they look like. 6 | 7 | The main goal of this project is to create a simple calendar application similar to Google Calendar. While this is a vastly simplified version of Google Calendar it is still quite complicated and will require lots of unique and interesting code. This project should also be written entirely in TypeScript if you are familiar with TypeScript as it is really good practice for most of the React specific TypeScript concepts. 8 | 9 | # Instructions 10 | 11 | 1. Create a `Calendar` component that renders the current month by default. 12 | - This calendar should have buttons for going back/forward a month as well as for jumping to the current month. 13 | 2. Add a `+` button that allows you to create a new event for the specific day. 14 | - This should open a modal form for adding a new event. 15 | - The form should include a `name`, `allDay`, `startTime`, `endTime`, and `color` field. 16 | - The `name` field is required. 17 | - The `allDay` field should be a checkbox that when checked will disable the `startTime` and `endTime` fields. 18 | - The `startTime` must be before the `endTime` and are required if `allDay` is not checked. 19 | - The `color` field should have the options `red`, `blue`, and `green`. 20 | 3. Render events in the calendar view. 21 | - Events should be sorted with all day events first and then by start date. 22 | 4. Clicking on an event should open an edit modal. 23 | - This modal should have the same fields as the add event modal but should be pre-populated with the event data. 24 | - This modal should also have a delete button for removing an event. 25 | 26 | ## Bonus: 27 | 28 | 1. Store events in LocalStorage so they persist on page refresh. 29 | 2. If the number of events overflows the calendar day then render a `+X More` button at the bottom of the day that shows the number of events that overflow. 30 | - These overflow events should be rendered in a modal when the `+X More` button is clicked. 31 | - Clicking on one of the events in this modal should open the edit modal. 32 | - The overflow events should be hidden and not rendered at all in the DOM (except in the modal). 33 | - When the calendar day is resized the overflow events should be recalculated and rendered again. 34 | - When the number of events changes the overflow events should be recalculated and rendered again. 35 | - This is by far the hardest part of this project and will require a lot of thought and planning to get right so don't feel bad if you struggle here. This took me a full day to get done properly on my own. 36 | 3. Add a closing animation to the modals. 37 | - A `closing` class can be added to the modal to trigger the closing animation. 38 | - The modal should only be removed from the DOM after the animation finishes to ensure if has a nice smooth transition out. 39 | -------------------------------------------------------------------------------- /converting-js-to-ts/after/src/script.ts: -------------------------------------------------------------------------------- 1 | // Display/UI 2 | 3 | import { 4 | createBoard, 5 | markTile, 6 | revealTile, 7 | checkWin, 8 | checkLose, 9 | positionMatch, 10 | markedTilesCount, 11 | Tile, 12 | Position, 13 | } from "./minesweeper.ts" 14 | 15 | const BOARD_SIZE = 10 16 | const NUMBER_OF_MINES = 10 17 | 18 | let board = createBoard( 19 | BOARD_SIZE, 20 | getMinePositions(BOARD_SIZE, NUMBER_OF_MINES) 21 | ) 22 | const boardElement = document.querySelector(".board")! 23 | const minesLeftText = 24 | document.querySelector("[data-mine-count]")! 25 | const messageText = document.querySelector(".subtext")! 26 | 27 | function render() { 28 | boardElement.innerHTML = "" 29 | checkGameEnd() 30 | 31 | getTileElements().forEach(element => { 32 | boardElement.append(element) 33 | }) 34 | 35 | listMinesLeft() 36 | } 37 | 38 | function getTileElements() { 39 | return board.flatMap(row => { 40 | return row.map(tileToElement) 41 | }) 42 | } 43 | 44 | function tileToElement(tile: Tile) { 45 | const element = document.createElement("div") 46 | element.dataset.status = tile.status 47 | element.dataset.x = tile.x.toString() 48 | element.dataset.y = tile.y.toString() 49 | element.textContent = tile.adjacentMinesCount 50 | ? tile.adjacentMinesCount.toString() 51 | : "" 52 | return element 53 | } 54 | 55 | boardElement.addEventListener("click", e => { 56 | if ( 57 | !(e.target instanceof HTMLElement) || 58 | !e.target.matches("[data-status]") || 59 | e.target.dataset.x == null || 60 | e.target.dataset.y == null 61 | ) { 62 | return 63 | } 64 | 65 | board = revealTile(board, { 66 | x: parseInt(e.target.dataset.x), 67 | y: parseInt(e.target.dataset.y), 68 | }) 69 | render() 70 | }) 71 | 72 | boardElement.addEventListener("contextmenu", e => { 73 | if ( 74 | !(e.target instanceof HTMLElement) || 75 | !e.target.matches("[data-status]") || 76 | e.target.dataset.x == null || 77 | e.target.dataset.y == null 78 | ) { 79 | return 80 | } 81 | 82 | e.preventDefault() 83 | board = markTile(board, { 84 | x: parseInt(e.target.dataset.x), 85 | y: parseInt(e.target.dataset.y), 86 | }) 87 | render() 88 | }) 89 | 90 | boardElement.style.setProperty("--size", BOARD_SIZE.toString()) 91 | render() 92 | 93 | function listMinesLeft() { 94 | minesLeftText.textContent = `${NUMBER_OF_MINES - markedTilesCount(board)}` 95 | } 96 | 97 | function checkGameEnd() { 98 | const win = checkWin(board) 99 | const lose = checkLose(board) 100 | 101 | if (win || lose) { 102 | boardElement.addEventListener("click", stopProp, { capture: true }) 103 | boardElement.addEventListener("contextmenu", stopProp, { capture: true }) 104 | } 105 | 106 | if (win) { 107 | messageText.textContent = "You Win" 108 | } 109 | if (lose) { 110 | messageText.textContent = "You Lose" 111 | board.forEach(row => { 112 | row.forEach(tile => { 113 | if (tile.status === "marked") board = markTile(board, tile) 114 | if (tile.mine) board = revealTile(board, tile) 115 | }) 116 | }) 117 | } 118 | } 119 | 120 | function stopProp(e: Event) { 121 | e.stopImmediatePropagation() 122 | } 123 | 124 | function getMinePositions(boardSize: number, numberOfMines: number) { 125 | const positions: Position[] = [] 126 | 127 | while (positions.length < numberOfMines) { 128 | const position = { 129 | x: randomNumber(boardSize), 130 | y: randomNumber(boardSize), 131 | } 132 | 133 | if (!positions.some(positionMatch.bind(null, position))) { 134 | positions.push(position) 135 | } 136 | } 137 | 138 | return positions 139 | } 140 | 141 | function randomNumber(size: number) { 142 | return Math.floor(Math.random() * size) 143 | } 144 | -------------------------------------------------------------------------------- /google-calendar-clone/before/styles.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: border-box; 5 | font-family: sans-serif; 6 | } 7 | 8 | :root { 9 | --blue-background: hsl(200, 80%, 50%); 10 | --red-background: hsl(0, 75%, 60%); 11 | --green-background: hsl(150, 80%, 30%); 12 | --border-color: #dadce0; 13 | --border-size: 1px; 14 | --day-padding: 0.25rem; 15 | } 16 | 17 | body { 18 | margin: 0; 19 | } 20 | 21 | #root { 22 | height: 100vh; 23 | max-width: 1500px; 24 | margin: 0 auto; 25 | } 26 | 27 | .calendar { 28 | height: 100%; 29 | display: flex; 30 | flex-direction: column; 31 | color: #333; 32 | } 33 | 34 | .header { 35 | padding: 1rem; 36 | width: 100%; 37 | display: flex; 38 | align-items: center; 39 | } 40 | 41 | .header > * { 42 | margin-right: 0.5rem; 43 | } 44 | 45 | .header > :last-child { 46 | margin-right: 0; 47 | } 48 | 49 | .btn { 50 | background: none; 51 | border: 1px solid var(--border-color); 52 | border-radius: 0.25rem; 53 | padding: 0.5rem 1rem; 54 | font-size: 1rem; 55 | cursor: pointer; 56 | transition: background-color 250ms; 57 | color: #333; 58 | } 59 | 60 | .btn:hover { 61 | background-color: #f1f3f4; 62 | } 63 | 64 | .btn.btn-delete { 65 | border-color: hsl(0, 75%, 60%); 66 | background-color: hsl(0, 75%, 95%); 67 | color: hsl(0, 75%, 10%); 68 | } 69 | 70 | .btn.btn-delete:hover { 71 | background-color: hsl(0, 75%, 90%); 72 | } 73 | 74 | .btn.btn-success { 75 | border-color: hsl(150, 80%, 30%); 76 | background-color: hsl(150, 80%, 95%); 77 | color: hsl(150, 80%, 10%); 78 | } 79 | 80 | .btn.btn-success:hover { 81 | background-color: hsl(150, 80%, 90%); 82 | } 83 | 84 | .close-btn { 85 | cursor: pointer; 86 | background: none; 87 | border: none; 88 | font-size: 1.75rem; 89 | width: 2rem; 90 | padding: 0; 91 | height: 2rem; 92 | text-align: center; 93 | vertical-align: middle; 94 | border-radius: 100%; 95 | transition: background-color 250ms; 96 | color: #333; 97 | } 98 | 99 | .close-btn:hover { 100 | background-color: #eaeaea; 101 | } 102 | 103 | .month-change-btn { 104 | cursor: pointer; 105 | background: none; 106 | border: none; 107 | font-size: 1.25rem; 108 | width: 2rem; 109 | padding: 0; 110 | height: 2rem; 111 | text-align: center; 112 | vertical-align: middle; 113 | border-radius: 100%; 114 | transition: background-color 250ms; 115 | color: #333; 116 | } 117 | 118 | .month-change-btn:hover { 119 | background-color: #f1f3f4; 120 | } 121 | 122 | .month-change-btn:first-child { 123 | margin-right: -0.5rem; 124 | } 125 | 126 | .month-title { 127 | font-size: 1.5rem; 128 | font-weight: bold; 129 | } 130 | 131 | .days { 132 | flex-grow: 1; 133 | overflow-y: auto; 134 | display: grid; 135 | grid-template-columns: repeat(7, minmax(0, 1fr)); 136 | grid-auto-rows: minmax(100px, 1fr); 137 | background-color: var(--border-color); 138 | gap: var(--border-size); 139 | padding: var(--border-size); 140 | } 141 | 142 | .day { 143 | background-color: white; 144 | padding: var(--day-padding); 145 | overflow: hidden; 146 | display: flex; 147 | flex-direction: column; 148 | } 149 | 150 | .non-month-day { 151 | opacity: 0.75; 152 | } 153 | 154 | .old-month-day .events, 155 | .old-month-day .day-header { 156 | opacity: 0.5; 157 | } 158 | 159 | .day-header { 160 | margin-bottom: 0.25rem; 161 | display: flex; 162 | flex-direction: column; 163 | align-items: center; 164 | position: relative; 165 | } 166 | 167 | .week-name { 168 | text-transform: uppercase; 169 | font-size: 0.75rem; 170 | font-weight: bold; 171 | color: #777; 172 | } 173 | 174 | .day-number { 175 | font-size: 0.9rem; 176 | width: 1.5rem; 177 | height: 1.5rem; 178 | display: flex; 179 | justify-content: center; 180 | align-items: center; 181 | } 182 | 183 | .day-number.today { 184 | background-color: var(--blue-background); 185 | border-radius: 50%; 186 | color: white; 187 | } 188 | 189 | .day:hover .add-event-btn, 190 | .add-event-btn:focus { 191 | opacity: 1; 192 | } 193 | 194 | .add-event-btn { 195 | opacity: 0; 196 | position: absolute; 197 | background: none; 198 | border: none; 199 | border-radius: 50%; 200 | width: 1.5rem; 201 | height: 1.5rem; 202 | display: flex; 203 | justify-content: center; 204 | align-items: center; 205 | right: 0; 206 | top: 0; 207 | font-size: 1.25rem; 208 | cursor: pointer; 209 | color: #333; 210 | } 211 | 212 | .add-event-btn:hover { 213 | background-color: #f1f3f4; 214 | } 215 | 216 | .event { 217 | display: flex; 218 | align-items: center; 219 | overflow: hidden; 220 | white-space: nowrap; 221 | cursor: pointer; 222 | flex-shrink: 0; 223 | background: none; 224 | width: 100%; 225 | border: none; 226 | font-size: 1rem; 227 | padding: 0; 228 | } 229 | 230 | .all-day-event { 231 | color: white; 232 | padding: 0.15rem 0.25rem; 233 | border-radius: 0.25rem; 234 | } 235 | 236 | .all-day-event .event-name { 237 | overflow: hidden; 238 | } 239 | 240 | .event > * { 241 | margin-right: 0.5rem; 242 | } 243 | 244 | .event > :last-child { 245 | margin-right: 0; 246 | } 247 | 248 | .event-time { 249 | color: #777; 250 | } 251 | 252 | .color-dot { 253 | border-radius: 50%; 254 | width: 0.5rem; 255 | height: 0.5rem; 256 | flex-shrink: 0; 257 | } 258 | 259 | .color-dot.blue, 260 | .all-day-event.blue { 261 | background-color: var(--blue-background); 262 | } 263 | 264 | .color-dot.red, 265 | .all-day-event.red { 266 | background-color: var(--red-background); 267 | } 268 | 269 | .color-dot.green, 270 | .all-day-event.green { 271 | background-color: var(--green-background); 272 | } 273 | 274 | .events { 275 | display: flex; 276 | flex-direction: column; 277 | gap: 0.5rem; 278 | flex-grow: 1; 279 | overflow: hidden; 280 | } 281 | 282 | .events-view-more-btn { 283 | border: none; 284 | background: none; 285 | font-weight: bold; 286 | color: #555; 287 | cursor: pointer; 288 | width: 100%; 289 | } 290 | 291 | .modal { 292 | position: fixed; 293 | top: 0; 294 | left: 0; 295 | bottom: 0; 296 | right: 0; 297 | display: flex; 298 | justify-content: center; 299 | align-items: center; 300 | } 301 | 302 | .modal .overlay { 303 | background-color: transparent; 304 | width: 100%; 305 | height: 100%; 306 | position: fixed; 307 | animation: fade-in 250ms forwards; 308 | } 309 | 310 | .modal.closing .overlay { 311 | animation: fade-in 250ms forwards reverse; 312 | } 313 | 314 | @keyframes fade-in { 315 | 100% { 316 | background-color: rgba(0, 0, 0, 0.5); 317 | } 318 | } 319 | 320 | .modal .modal-body { 321 | background-color: white; 322 | border-radius: 0.5rem; 323 | padding: 1rem; 324 | z-index: 1; 325 | min-width: 300px; 326 | max-width: 95%; 327 | animation: pop-in 250ms forwards; 328 | scale: 0; 329 | } 330 | 331 | .modal.closing .modal-body { 332 | animation: pop-in 250ms forwards reverse; 333 | } 334 | 335 | @keyframes pop-in { 336 | 100% { 337 | scale: 1; 338 | } 339 | } 340 | 341 | .modal-title { 342 | font-size: 1.5rem; 343 | margin-bottom: 1.5rem; 344 | display: flex; 345 | justify-content: space-between; 346 | align-items: center; 347 | } 348 | 349 | .modal-title > * { 350 | margin-right: 0.25rem; 351 | } 352 | 353 | .modal-title > :last-child { 354 | margin-right: 0; 355 | } 356 | 357 | .modal-title > small { 358 | color: #555; 359 | } 360 | 361 | .form-group { 362 | display: flex; 363 | flex-direction: column; 364 | margin-bottom: 1rem; 365 | } 366 | 367 | .form-group.checkbox { 368 | flex-direction: row; 369 | align-items: center; 370 | } 371 | 372 | .form-group.checkbox input { 373 | cursor: pointer; 374 | margin-right: 0; 375 | } 376 | 377 | .form-group.checkbox label { 378 | padding-left: 0.5rem; 379 | cursor: pointer; 380 | } 381 | 382 | .form-group:last-child { 383 | margin-bottom: 0; 384 | } 385 | 386 | .form-group label { 387 | font-weight: bold; 388 | font-size: 0.8rem; 389 | color: #777; 390 | } 391 | 392 | .form-group input { 393 | padding: 0.25rem 0.5rem; 394 | } 395 | 396 | .row { 397 | display: flex; 398 | } 399 | 400 | .row > * { 401 | flex-grow: 1; 402 | flex-basis: 0; 403 | margin-right: 0.5rem; 404 | } 405 | 406 | .row > :last-child { 407 | margin-right: 0; 408 | } 409 | 410 | .row.left > * { 411 | flex-grow: 0; 412 | } 413 | 414 | .sr-only { 415 | visibility: hidden; 416 | height: 0; 417 | width: 0; 418 | display: block; 419 | } 420 | 421 | .color-radio { 422 | position: absolute; 423 | opacity: 0; 424 | left: -9999px; 425 | } 426 | 427 | .color-radio + label::before { 428 | content: ""; 429 | display: block; 430 | width: 1.75rem; 431 | height: 1.75rem; 432 | border-radius: 0.25rem; 433 | cursor: pointer; 434 | opacity: 0.25; 435 | } 436 | 437 | .color-radio:checked + label::before { 438 | opacity: 1; 439 | } 440 | 441 | .color-radio:focus + label::before { 442 | outline: 1px solid black; 443 | } 444 | 445 | .color-radio[value="blue"] + label::before { 446 | background-color: var(--blue-background); 447 | } 448 | 449 | .color-radio[value="red"] + label::before { 450 | background-color: var(--red-background); 451 | } 452 | 453 | .color-radio[value="green"] + label::before { 454 | background-color: var(--green-background); 455 | } 456 | -------------------------------------------------------------------------------- /google-calendar-clone/after/src/styles.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: border-box; 5 | font-family: sans-serif; 6 | } 7 | 8 | :root { 9 | --blue-background: hsl(200, 80%, 50%); 10 | --red-background: hsl(0, 75%, 60%); 11 | --green-background: hsl(150, 80%, 30%); 12 | --border-color: #dadce0; 13 | --border-size: 1px; 14 | --day-padding: 0.25rem; 15 | } 16 | 17 | body { 18 | margin: 0; 19 | } 20 | 21 | #root { 22 | height: 100vh; 23 | max-width: 1500px; 24 | margin: 0 auto; 25 | } 26 | 27 | .calendar { 28 | height: 100%; 29 | display: flex; 30 | flex-direction: column; 31 | color: #333; 32 | } 33 | 34 | .header { 35 | padding: 1rem; 36 | width: 100%; 37 | display: flex; 38 | align-items: center; 39 | } 40 | 41 | .header > * { 42 | margin-right: 0.5rem; 43 | } 44 | 45 | .header > :last-child { 46 | margin-right: 0; 47 | } 48 | 49 | .btn { 50 | background: none; 51 | border: 1px solid var(--border-color); 52 | border-radius: 0.25rem; 53 | padding: 0.5rem 1rem; 54 | font-size: 1rem; 55 | cursor: pointer; 56 | transition: background-color 250ms; 57 | color: #333; 58 | } 59 | 60 | .btn:hover { 61 | background-color: #f1f3f4; 62 | } 63 | 64 | .btn.btn-delete { 65 | border-color: hsl(0, 75%, 60%); 66 | background-color: hsl(0, 75%, 95%); 67 | color: hsl(0, 75%, 10%); 68 | } 69 | 70 | .btn.btn-delete:hover { 71 | background-color: hsl(0, 75%, 90%); 72 | } 73 | 74 | .btn.btn-success { 75 | border-color: hsl(150, 80%, 30%); 76 | background-color: hsl(150, 80%, 95%); 77 | color: hsl(150, 80%, 10%); 78 | } 79 | 80 | .btn.btn-success:hover { 81 | background-color: hsl(150, 80%, 90%); 82 | } 83 | 84 | .close-btn { 85 | cursor: pointer; 86 | background: none; 87 | border: none; 88 | font-size: 1.75rem; 89 | width: 2rem; 90 | padding: 0; 91 | height: 2rem; 92 | text-align: center; 93 | vertical-align: middle; 94 | border-radius: 100%; 95 | transition: background-color 250ms; 96 | color: #333; 97 | } 98 | 99 | .close-btn:hover { 100 | background-color: #eaeaea; 101 | } 102 | 103 | .month-change-btn { 104 | cursor: pointer; 105 | background: none; 106 | border: none; 107 | font-size: 1.25rem; 108 | width: 2rem; 109 | padding: 0; 110 | height: 2rem; 111 | text-align: center; 112 | vertical-align: middle; 113 | border-radius: 100%; 114 | transition: background-color 250ms; 115 | color: #333; 116 | } 117 | 118 | .month-change-btn:hover { 119 | background-color: #f1f3f4; 120 | } 121 | 122 | .month-change-btn:first-child { 123 | margin-right: -0.5rem; 124 | } 125 | 126 | .month-title { 127 | font-size: 1.5rem; 128 | font-weight: bold; 129 | } 130 | 131 | .days { 132 | flex-grow: 1; 133 | overflow-y: auto; 134 | display: grid; 135 | grid-template-columns: repeat(7, minmax(0, 1fr)); 136 | grid-auto-rows: minmax(100px, 1fr); 137 | background-color: var(--border-color); 138 | gap: var(--border-size); 139 | padding: var(--border-size); 140 | } 141 | 142 | .day { 143 | background-color: white; 144 | padding: var(--day-padding); 145 | overflow: hidden; 146 | display: flex; 147 | flex-direction: column; 148 | } 149 | 150 | .non-month-day { 151 | opacity: 0.75; 152 | } 153 | 154 | .old-month-day .events, 155 | .old-month-day .day-header { 156 | opacity: 0.5; 157 | } 158 | 159 | .day-header { 160 | margin-bottom: 0.25rem; 161 | display: flex; 162 | flex-direction: column; 163 | align-items: center; 164 | position: relative; 165 | } 166 | 167 | .week-name { 168 | text-transform: uppercase; 169 | font-size: 0.75rem; 170 | font-weight: bold; 171 | color: #777; 172 | } 173 | 174 | .day-number { 175 | font-size: 0.9rem; 176 | width: 1.5rem; 177 | height: 1.5rem; 178 | display: flex; 179 | justify-content: center; 180 | align-items: center; 181 | } 182 | 183 | .day-number.today { 184 | background-color: var(--blue-background); 185 | border-radius: 50%; 186 | color: white; 187 | } 188 | 189 | .day:hover .add-event-btn, 190 | .add-event-btn:focus { 191 | opacity: 1; 192 | } 193 | 194 | .add-event-btn { 195 | opacity: 0; 196 | position: absolute; 197 | background: none; 198 | border: none; 199 | border-radius: 50%; 200 | width: 1.5rem; 201 | height: 1.5rem; 202 | display: flex; 203 | justify-content: center; 204 | align-items: center; 205 | right: 0; 206 | top: 0; 207 | font-size: 1.25rem; 208 | cursor: pointer; 209 | color: #333; 210 | } 211 | 212 | .add-event-btn:hover { 213 | background-color: #f1f3f4; 214 | } 215 | 216 | .event { 217 | display: flex; 218 | align-items: center; 219 | overflow: hidden; 220 | white-space: nowrap; 221 | cursor: pointer; 222 | flex-shrink: 0; 223 | background: none; 224 | width: 100%; 225 | border: none; 226 | font-size: 1rem; 227 | padding: 0; 228 | } 229 | 230 | .all-day-event { 231 | color: white; 232 | padding: 0.15rem 0.25rem; 233 | border-radius: 0.25rem; 234 | } 235 | 236 | .all-day-event .event-name { 237 | overflow: hidden; 238 | } 239 | 240 | .event > * { 241 | margin-right: 0.5rem; 242 | } 243 | 244 | .event > :last-child { 245 | margin-right: 0; 246 | } 247 | 248 | .event-time { 249 | color: #777; 250 | } 251 | 252 | .color-dot { 253 | border-radius: 50%; 254 | width: 0.5rem; 255 | height: 0.5rem; 256 | flex-shrink: 0; 257 | } 258 | 259 | .color-dot.blue, 260 | .all-day-event.blue { 261 | background-color: var(--blue-background); 262 | } 263 | 264 | .color-dot.red, 265 | .all-day-event.red { 266 | background-color: var(--red-background); 267 | } 268 | 269 | .color-dot.green, 270 | .all-day-event.green { 271 | background-color: var(--green-background); 272 | } 273 | 274 | .events { 275 | display: flex; 276 | flex-direction: column; 277 | gap: 0.5rem; 278 | flex-grow: 1; 279 | overflow: hidden; 280 | } 281 | 282 | .events-view-more-btn { 283 | border: none; 284 | background: none; 285 | font-weight: bold; 286 | color: #555; 287 | cursor: pointer; 288 | width: 100%; 289 | } 290 | 291 | .modal { 292 | position: fixed; 293 | top: 0; 294 | left: 0; 295 | bottom: 0; 296 | right: 0; 297 | display: flex; 298 | justify-content: center; 299 | align-items: center; 300 | } 301 | 302 | .modal .overlay { 303 | background-color: transparent; 304 | width: 100%; 305 | height: 100%; 306 | position: fixed; 307 | animation: fade-in 250ms forwards; 308 | } 309 | 310 | .modal.closing .overlay { 311 | animation: fade-in 250ms forwards reverse; 312 | } 313 | 314 | @keyframes fade-in { 315 | 100% { 316 | background-color: rgba(0, 0, 0, 0.5); 317 | } 318 | } 319 | 320 | .modal .modal-body { 321 | background-color: white; 322 | border-radius: 0.5rem; 323 | padding: 1rem; 324 | z-index: 1; 325 | min-width: 300px; 326 | max-width: 95%; 327 | animation: pop-in 250ms forwards; 328 | scale: 0; 329 | } 330 | 331 | .modal.closing .modal-body { 332 | animation: pop-in 250ms forwards reverse; 333 | } 334 | 335 | @keyframes pop-in { 336 | 100% { 337 | scale: 1; 338 | } 339 | } 340 | 341 | .modal-title { 342 | font-size: 1.5rem; 343 | margin-bottom: 1.5rem; 344 | display: flex; 345 | justify-content: space-between; 346 | align-items: center; 347 | } 348 | 349 | .modal-title > * { 350 | margin-right: 0.25rem; 351 | } 352 | 353 | .modal-title > :last-child { 354 | margin-right: 0; 355 | } 356 | 357 | .modal-title > small { 358 | color: #555; 359 | } 360 | 361 | .form-group { 362 | display: flex; 363 | flex-direction: column; 364 | margin-bottom: 1rem; 365 | } 366 | 367 | .form-group.checkbox { 368 | flex-direction: row; 369 | align-items: center; 370 | } 371 | 372 | .form-group.checkbox input { 373 | cursor: pointer; 374 | margin-right: 0; 375 | } 376 | 377 | .form-group.checkbox label { 378 | padding-left: 0.5rem; 379 | cursor: pointer; 380 | } 381 | 382 | .form-group:last-child { 383 | margin-bottom: 0; 384 | } 385 | 386 | .form-group label { 387 | font-weight: bold; 388 | font-size: 0.8rem; 389 | color: #777; 390 | } 391 | 392 | .form-group input { 393 | padding: 0.25rem 0.5rem; 394 | } 395 | 396 | .row { 397 | display: flex; 398 | } 399 | 400 | .row > * { 401 | flex-grow: 1; 402 | flex-basis: 0; 403 | margin-right: 0.5rem; 404 | } 405 | 406 | .row > :last-child { 407 | margin-right: 0; 408 | } 409 | 410 | .row.left > * { 411 | flex-grow: 0; 412 | } 413 | 414 | .sr-only { 415 | visibility: hidden; 416 | height: 0; 417 | width: 0; 418 | display: block; 419 | } 420 | 421 | .color-radio { 422 | position: absolute; 423 | opacity: 0; 424 | left: -9999px; 425 | } 426 | 427 | .color-radio + label::before { 428 | content: ""; 429 | display: block; 430 | width: 1.75rem; 431 | height: 1.75rem; 432 | border-radius: 0.25rem; 433 | cursor: pointer; 434 | opacity: 0.25; 435 | } 436 | 437 | .color-radio:checked + label::before { 438 | opacity: 1; 439 | } 440 | 441 | .color-radio:focus + label::before { 442 | outline: 1px solid black; 443 | } 444 | 445 | .color-radio[value="blue"] + label::before { 446 | background-color: var(--blue-background); 447 | } 448 | 449 | .color-radio[value="red"] + label::before { 450 | background-color: var(--red-background); 451 | } 452 | 453 | .color-radio[value="green"] + label::before { 454 | background-color: var(--green-background); 455 | } 456 | -------------------------------------------------------------------------------- /google-calendar-clone/after/src/components/Calendar.tsx: -------------------------------------------------------------------------------- 1 | import { FormEvent, Fragment, useId, useMemo, useRef, useState } from "react" 2 | import { 3 | startOfWeek, 4 | startOfMonth, 5 | endOfWeek, 6 | endOfMonth, 7 | eachDayOfInterval, 8 | isSameMonth, 9 | isBefore, 10 | endOfDay, 11 | isToday, 12 | subMonths, 13 | addMonths, 14 | isSameDay, 15 | parse, 16 | } from "date-fns" 17 | import { formatDate } from "../utils/formatDate" 18 | import { cc } from "../utils/cc" 19 | import { EVENT_COLORS, useEvents } from "../context/useEvent" 20 | import { Modal, ModalProps } from "./Modal" 21 | import { UnionOmit } from "../utils/types" 22 | import { Event } from "../context/Events" 23 | import { OverflowContainer } from "./OverflowContainer" 24 | 25 | export function Calendar() { 26 | const [selectedMonth, setSelectedMonth] = useState(new Date()) 27 | 28 | const calendarDays = useMemo(() => { 29 | const firstWeekStart = startOfWeek(startOfMonth(selectedMonth)) 30 | const lastWeekEnd = endOfWeek(endOfMonth(selectedMonth)) 31 | return eachDayOfInterval({ start: firstWeekStart, end: lastWeekEnd }) 32 | }, [selectedMonth]) 33 | 34 | const { events } = useEvents() 35 | 36 | return ( 37 | 38 | 39 | setSelectedMonth(new Date())}> 40 | Today 41 | 42 | 43 | { 46 | setSelectedMonth(m => subMonths(m, 1)) 47 | }} 48 | > 49 | < 50 | 51 | { 54 | setSelectedMonth(m => addMonths(m, 1)) 55 | }} 56 | > 57 | > 58 | 59 | 60 | 61 | {formatDate(selectedMonth, { month: "long", year: "numeric" })} 62 | 63 | 64 | 65 | {calendarDays.map((day, index) => ( 66 | isSameDay(day, event.date))} 70 | showWeekName={index < 7} 71 | selectedMonth={selectedMonth} 72 | /> 73 | ))} 74 | 75 | 76 | ) 77 | } 78 | 79 | type CalendarDayProps = { 80 | day: Date 81 | showWeekName: boolean 82 | selectedMonth: Date 83 | events: Event[] 84 | } 85 | 86 | function CalendarDay({ 87 | day, 88 | showWeekName, 89 | selectedMonth, 90 | events, 91 | }: CalendarDayProps) { 92 | const [isNewEventModalOpen, setIsNewEventModalOpen] = useState(false) 93 | const [isViewMoreEventModalOpen, setIsViewMoreEventModalOpen] = 94 | useState(false) 95 | const { addEvent } = useEvents() 96 | 97 | const sortedEvents = useMemo(() => { 98 | const timeToNumber = (time: string) => parseFloat(time.replace(":", ".")) 99 | 100 | return [...events].sort((a, b) => { 101 | if (a.allDay && b.allDay) { 102 | return 0 103 | } else if (a.allDay) { 104 | return -1 105 | } else if (b.allDay) { 106 | return 1 107 | } else { 108 | return timeToNumber(a.startTime) - timeToNumber(b.startTime) 109 | } 110 | }) 111 | }, [events]) 112 | 113 | return ( 114 | 121 | 122 | {showWeekName && ( 123 | 124 | {formatDate(day, { weekday: "short" })} 125 | 126 | )} 127 | 128 | {formatDate(day, { day: "numeric" })} 129 | 130 | setIsNewEventModalOpen(true)} 133 | > 134 | + 135 | 136 | 137 | {sortedEvents.length > 0 && ( 138 | event.id} 142 | renderItem={event => } 143 | renderOverflow={amount => ( 144 | <> 145 | setIsViewMoreEventModalOpen(true)} 147 | className="events-view-more-btn" 148 | > 149 | +{amount} More 150 | 151 | setIsViewMoreEventModalOpen(false)} 155 | /> 156 | > 157 | )} 158 | /> 159 | )} 160 | 161 | setIsNewEventModalOpen(false)} 165 | onSubmit={addEvent} 166 | /> 167 | 168 | ) 169 | } 170 | 171 | type ViewMoreCalendarEventsModalProps = { 172 | events: Event[] 173 | } & Omit 174 | 175 | function ViewMoreCalendarEventsModal({ 176 | events, 177 | ...modalProps 178 | }: ViewMoreCalendarEventsModalProps) { 179 | if (events.length === 0) return null 180 | 181 | return ( 182 | 183 | 184 | {formatDate(events[0].date, { dateStyle: "short" })} 185 | 186 | × 187 | 188 | 189 | 190 | {events.map(event => ( 191 | 192 | ))} 193 | 194 | 195 | ) 196 | } 197 | 198 | function CalendarEvent({ event }: { event: Event }) { 199 | const [isEditModalOpen, setIsEditModalOpen] = useState(false) 200 | const { updateEvent, deleteEvent } = useEvents() 201 | 202 | return ( 203 | <> 204 | setIsEditModalOpen(true)} 206 | className={cc("event", event.color, event.allDay && "all-day-event")} 207 | > 208 | {event.allDay ? ( 209 | {event.name} 210 | ) : ( 211 | <> 212 | 213 | 214 | {formatDate(parse(event.startTime, "HH:mm", event.date), { 215 | timeStyle: "short", 216 | })} 217 | 218 | {event.name} 219 | > 220 | )} 221 | 222 | setIsEditModalOpen(false)} 226 | onSubmit={e => updateEvent(event.id, e)} 227 | onDelete={() => deleteEvent(event.id)} 228 | /> 229 | > 230 | ) 231 | } 232 | 233 | type EventFormModalProps = { 234 | onSubmit: (event: UnionOmit) => void 235 | } & ( 236 | | { onDelete: () => void; event: Event; date?: never } 237 | | { onDelete?: never; event?: never; date: Date } 238 | ) & 239 | Omit 240 | 241 | function EventFormModal({ 242 | onSubmit, 243 | onDelete, 244 | event, 245 | date, 246 | ...modalProps 247 | }: EventFormModalProps) { 248 | const isNew = event == null 249 | const formId = useId() 250 | const [selectedColor, setSelectedColor] = useState( 251 | event?.color || EVENT_COLORS[0] 252 | ) 253 | const [isAllDayChecked, setIsAllDayChecked] = useState(event?.allDay || false) 254 | const [startTime, setStartTime] = useState(event?.startTime || "") 255 | const endTimeRef = useRef(null) 256 | const nameRef = useRef(null) 257 | 258 | function handleSubmit(e: FormEvent) { 259 | e.preventDefault() 260 | 261 | const name = nameRef.current?.value 262 | const endTime = endTimeRef.current?.value 263 | 264 | if (name == null || name === "") return 265 | 266 | const commonProps = { 267 | name, 268 | date: date || event?.date, 269 | color: selectedColor, 270 | } 271 | let newEvent: UnionOmit 272 | 273 | if (isAllDayChecked) { 274 | newEvent = { 275 | ...commonProps, 276 | allDay: true, 277 | } 278 | } else { 279 | if ( 280 | startTime == null || 281 | startTime === "" || 282 | endTime == null || 283 | endTime === "" 284 | ) { 285 | return 286 | } 287 | newEvent = { 288 | ...commonProps, 289 | allDay: false, 290 | startTime, 291 | endTime, 292 | } 293 | } 294 | 295 | modalProps.onClose() 296 | onSubmit(newEvent) 297 | } 298 | 299 | return ( 300 | 301 | 302 | {isNew ? "Add" : "Edit"} Event 303 | {formatDate(date || event.date, { dateStyle: "short" })} 304 | 305 | × 306 | 307 | 308 | 309 | 310 | Name 311 | 318 | 319 | 320 | setIsAllDayChecked(e.target.checked)} 323 | type="checkbox" 324 | id={`${formId}-all-day`} 325 | /> 326 | All Day? 327 | 328 | 329 | 330 | Start Time 331 | setStartTime(e.target.value)} 334 | required={!isAllDayChecked} 335 | disabled={isAllDayChecked} 336 | type="time" 337 | id={`${formId}-start-time`} 338 | /> 339 | 340 | 341 | End Time 342 | 351 | 352 | 353 | 354 | Color 355 | 356 | {EVENT_COLORS.map(color => ( 357 | 358 | setSelectedColor(color)} 365 | className="color-radio" 366 | /> 367 | 368 | {color} 369 | 370 | 371 | ))} 372 | 373 | 374 | 375 | 376 | {isNew ? "Add" : "Edit"} 377 | 378 | {onDelete != null && ( 379 | 380 | Delete 381 | 382 | )} 383 | 384 | 385 | 386 | ) 387 | } 388 | -------------------------------------------------------------------------------- /google-calendar-clone/before/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Calendar 9 | 10 | 11 | 12 | 13 | 14 | Today 15 | 16 | < 17 | > 18 | 19 | June 2023 20 | 21 | 22 | 23 | 24 | Sun 25 | 28 26 | + 27 | 28 | 29 | 30 | Short 31 | 32 | 33 | 34 | Long Event Name That Just Keeps Going 35 | 36 | 37 | 38 | 39 | 7am 40 | Event Name 41 | 42 | 43 | 44 | 45 | 46 | Mon 47 | 29 48 | + 49 | 50 | 51 | 52 | 53 | Tue 54 | 30 55 | + 56 | 57 | 58 | 59 | 60 | Wed 61 | 31 62 | + 63 | 64 | 65 | 66 | 67 | Thu 68 | 1 69 | + 70 | 71 | 72 | 73 | 74 | Fri 75 | 2 76 | + 77 | 78 | 79 | 80 | 81 | Sat 82 | 3 83 | + 84 | 85 | 86 | 87 | 88 | 4 89 | + 90 | 91 | 92 | 93 | 94 | 5 95 | + 96 | 97 | 98 | 99 | 100 | 6 101 | + 102 | 103 | 104 | 105 | 106 | 7 107 | + 108 | 109 | 110 | 111 | 112 | 8 113 | + 114 | 115 | 116 | 117 | Short 118 | 119 | 120 | 121 | Long Event Name That Just Keeps Going 122 | 123 | 124 | 125 | 126 | 7am 127 | Event Name 128 | 129 | 130 | 131 | 132 | 133 | 9 134 | + 135 | 136 | 137 | 138 | Short 139 | 140 | 141 | 142 | 7am 143 | Event Name 144 | 145 | 146 | 147 | 8am 148 | Event Name 149 | 150 | 151 | 152 | 9am 153 | Event Name 154 | 155 | 156 | 157 | 10am 158 | Event Name 159 | 160 | 161 | 162 | 11am 163 | Event Name 164 | 165 | 166 | +2 More 167 | 168 | 169 | 170 | 10 171 | + 172 | 173 | 174 | 175 | 176 | 11 177 | + 178 | 179 | 180 | 181 | 182 | 12 183 | + 184 | 185 | 186 | 187 | 188 | 13 189 | + 190 | 191 | 192 | 193 | 194 | 14 195 | + 196 | 197 | 198 | 199 | 200 | 15 201 | + 202 | 203 | 204 | 205 | 206 | 16 207 | + 208 | 209 | 210 | 211 | 212 | 17 213 | + 214 | 215 | 216 | 217 | 218 | 18 219 | + 220 | 221 | 222 | 223 | 224 | 19 225 | + 226 | 227 | 228 | 229 | Short 230 | 231 | 232 | 233 | Long Event Name That Just Keeps Going 234 | 235 | 236 | 237 | 238 | 7am 239 | Event Name 240 | 241 | 242 | 243 | 244 | 245 | 20 246 | + 247 | 248 | 249 | 250 | 251 | 21 252 | + 253 | 254 | 255 | 256 | 257 | 22 258 | + 259 | 260 | 261 | 262 | 263 | 23 264 | 265 | 266 | 267 | 268 | 24 269 | + 270 | 271 | 272 | 273 | 274 | 25 275 | + 276 | 277 | 278 | 279 | 280 | 26 281 | + 282 | 283 | 284 | 285 | 286 | 27 287 | + 288 | 289 | 290 | 291 | 292 | 28 293 | + 294 | 295 | 296 | 297 | 298 | 29 299 | + 300 | 301 | 302 | 303 | 304 | 30 305 | + 306 | 307 | 308 | 309 | 310 | 1 311 | + 312 | 313 | 314 | 315 | 316 | 317 | 351 | 352 | 416 | 417 | 418 | 419 | -------------------------------------------------------------------------------- /converting-js-to-ts/before/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "before", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "before", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "lodash": "^4.17.21" 12 | }, 13 | "devDependencies": { 14 | "vite": "^4.4.5" 15 | } 16 | }, 17 | "node_modules/@esbuild/android-arm": { 18 | "version": "0.18.20", 19 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", 20 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", 21 | "cpu": [ 22 | "arm" 23 | ], 24 | "dev": true, 25 | "optional": true, 26 | "os": [ 27 | "android" 28 | ], 29 | "engines": { 30 | "node": ">=12" 31 | } 32 | }, 33 | "node_modules/@esbuild/android-arm64": { 34 | "version": "0.18.20", 35 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", 36 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", 37 | "cpu": [ 38 | "arm64" 39 | ], 40 | "dev": true, 41 | "optional": true, 42 | "os": [ 43 | "android" 44 | ], 45 | "engines": { 46 | "node": ">=12" 47 | } 48 | }, 49 | "node_modules/@esbuild/android-x64": { 50 | "version": "0.18.20", 51 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", 52 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", 53 | "cpu": [ 54 | "x64" 55 | ], 56 | "dev": true, 57 | "optional": true, 58 | "os": [ 59 | "android" 60 | ], 61 | "engines": { 62 | "node": ">=12" 63 | } 64 | }, 65 | "node_modules/@esbuild/darwin-arm64": { 66 | "version": "0.18.20", 67 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", 68 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", 69 | "cpu": [ 70 | "arm64" 71 | ], 72 | "dev": true, 73 | "optional": true, 74 | "os": [ 75 | "darwin" 76 | ], 77 | "engines": { 78 | "node": ">=12" 79 | } 80 | }, 81 | "node_modules/@esbuild/darwin-x64": { 82 | "version": "0.18.20", 83 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", 84 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", 85 | "cpu": [ 86 | "x64" 87 | ], 88 | "dev": true, 89 | "optional": true, 90 | "os": [ 91 | "darwin" 92 | ], 93 | "engines": { 94 | "node": ">=12" 95 | } 96 | }, 97 | "node_modules/@esbuild/freebsd-arm64": { 98 | "version": "0.18.20", 99 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", 100 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", 101 | "cpu": [ 102 | "arm64" 103 | ], 104 | "dev": true, 105 | "optional": true, 106 | "os": [ 107 | "freebsd" 108 | ], 109 | "engines": { 110 | "node": ">=12" 111 | } 112 | }, 113 | "node_modules/@esbuild/freebsd-x64": { 114 | "version": "0.18.20", 115 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", 116 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", 117 | "cpu": [ 118 | "x64" 119 | ], 120 | "dev": true, 121 | "optional": true, 122 | "os": [ 123 | "freebsd" 124 | ], 125 | "engines": { 126 | "node": ">=12" 127 | } 128 | }, 129 | "node_modules/@esbuild/linux-arm": { 130 | "version": "0.18.20", 131 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", 132 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", 133 | "cpu": [ 134 | "arm" 135 | ], 136 | "dev": true, 137 | "optional": true, 138 | "os": [ 139 | "linux" 140 | ], 141 | "engines": { 142 | "node": ">=12" 143 | } 144 | }, 145 | "node_modules/@esbuild/linux-arm64": { 146 | "version": "0.18.20", 147 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", 148 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", 149 | "cpu": [ 150 | "arm64" 151 | ], 152 | "dev": true, 153 | "optional": true, 154 | "os": [ 155 | "linux" 156 | ], 157 | "engines": { 158 | "node": ">=12" 159 | } 160 | }, 161 | "node_modules/@esbuild/linux-ia32": { 162 | "version": "0.18.20", 163 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", 164 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", 165 | "cpu": [ 166 | "ia32" 167 | ], 168 | "dev": true, 169 | "optional": true, 170 | "os": [ 171 | "linux" 172 | ], 173 | "engines": { 174 | "node": ">=12" 175 | } 176 | }, 177 | "node_modules/@esbuild/linux-loong64": { 178 | "version": "0.18.20", 179 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", 180 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", 181 | "cpu": [ 182 | "loong64" 183 | ], 184 | "dev": true, 185 | "optional": true, 186 | "os": [ 187 | "linux" 188 | ], 189 | "engines": { 190 | "node": ">=12" 191 | } 192 | }, 193 | "node_modules/@esbuild/linux-mips64el": { 194 | "version": "0.18.20", 195 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", 196 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", 197 | "cpu": [ 198 | "mips64el" 199 | ], 200 | "dev": true, 201 | "optional": true, 202 | "os": [ 203 | "linux" 204 | ], 205 | "engines": { 206 | "node": ">=12" 207 | } 208 | }, 209 | "node_modules/@esbuild/linux-ppc64": { 210 | "version": "0.18.20", 211 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", 212 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", 213 | "cpu": [ 214 | "ppc64" 215 | ], 216 | "dev": true, 217 | "optional": true, 218 | "os": [ 219 | "linux" 220 | ], 221 | "engines": { 222 | "node": ">=12" 223 | } 224 | }, 225 | "node_modules/@esbuild/linux-riscv64": { 226 | "version": "0.18.20", 227 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", 228 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", 229 | "cpu": [ 230 | "riscv64" 231 | ], 232 | "dev": true, 233 | "optional": true, 234 | "os": [ 235 | "linux" 236 | ], 237 | "engines": { 238 | "node": ">=12" 239 | } 240 | }, 241 | "node_modules/@esbuild/linux-s390x": { 242 | "version": "0.18.20", 243 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", 244 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", 245 | "cpu": [ 246 | "s390x" 247 | ], 248 | "dev": true, 249 | "optional": true, 250 | "os": [ 251 | "linux" 252 | ], 253 | "engines": { 254 | "node": ">=12" 255 | } 256 | }, 257 | "node_modules/@esbuild/linux-x64": { 258 | "version": "0.18.20", 259 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", 260 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", 261 | "cpu": [ 262 | "x64" 263 | ], 264 | "dev": true, 265 | "optional": true, 266 | "os": [ 267 | "linux" 268 | ], 269 | "engines": { 270 | "node": ">=12" 271 | } 272 | }, 273 | "node_modules/@esbuild/netbsd-x64": { 274 | "version": "0.18.20", 275 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", 276 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", 277 | "cpu": [ 278 | "x64" 279 | ], 280 | "dev": true, 281 | "optional": true, 282 | "os": [ 283 | "netbsd" 284 | ], 285 | "engines": { 286 | "node": ">=12" 287 | } 288 | }, 289 | "node_modules/@esbuild/openbsd-x64": { 290 | "version": "0.18.20", 291 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", 292 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", 293 | "cpu": [ 294 | "x64" 295 | ], 296 | "dev": true, 297 | "optional": true, 298 | "os": [ 299 | "openbsd" 300 | ], 301 | "engines": { 302 | "node": ">=12" 303 | } 304 | }, 305 | "node_modules/@esbuild/sunos-x64": { 306 | "version": "0.18.20", 307 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", 308 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", 309 | "cpu": [ 310 | "x64" 311 | ], 312 | "dev": true, 313 | "optional": true, 314 | "os": [ 315 | "sunos" 316 | ], 317 | "engines": { 318 | "node": ">=12" 319 | } 320 | }, 321 | "node_modules/@esbuild/win32-arm64": { 322 | "version": "0.18.20", 323 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", 324 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", 325 | "cpu": [ 326 | "arm64" 327 | ], 328 | "dev": true, 329 | "optional": true, 330 | "os": [ 331 | "win32" 332 | ], 333 | "engines": { 334 | "node": ">=12" 335 | } 336 | }, 337 | "node_modules/@esbuild/win32-ia32": { 338 | "version": "0.18.20", 339 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", 340 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", 341 | "cpu": [ 342 | "ia32" 343 | ], 344 | "dev": true, 345 | "optional": true, 346 | "os": [ 347 | "win32" 348 | ], 349 | "engines": { 350 | "node": ">=12" 351 | } 352 | }, 353 | "node_modules/@esbuild/win32-x64": { 354 | "version": "0.18.20", 355 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", 356 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", 357 | "cpu": [ 358 | "x64" 359 | ], 360 | "dev": true, 361 | "optional": true, 362 | "os": [ 363 | "win32" 364 | ], 365 | "engines": { 366 | "node": ">=12" 367 | } 368 | }, 369 | "node_modules/esbuild": { 370 | "version": "0.18.20", 371 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", 372 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", 373 | "dev": true, 374 | "hasInstallScript": true, 375 | "bin": { 376 | "esbuild": "bin/esbuild" 377 | }, 378 | "engines": { 379 | "node": ">=12" 380 | }, 381 | "optionalDependencies": { 382 | "@esbuild/android-arm": "0.18.20", 383 | "@esbuild/android-arm64": "0.18.20", 384 | "@esbuild/android-x64": "0.18.20", 385 | "@esbuild/darwin-arm64": "0.18.20", 386 | "@esbuild/darwin-x64": "0.18.20", 387 | "@esbuild/freebsd-arm64": "0.18.20", 388 | "@esbuild/freebsd-x64": "0.18.20", 389 | "@esbuild/linux-arm": "0.18.20", 390 | "@esbuild/linux-arm64": "0.18.20", 391 | "@esbuild/linux-ia32": "0.18.20", 392 | "@esbuild/linux-loong64": "0.18.20", 393 | "@esbuild/linux-mips64el": "0.18.20", 394 | "@esbuild/linux-ppc64": "0.18.20", 395 | "@esbuild/linux-riscv64": "0.18.20", 396 | "@esbuild/linux-s390x": "0.18.20", 397 | "@esbuild/linux-x64": "0.18.20", 398 | "@esbuild/netbsd-x64": "0.18.20", 399 | "@esbuild/openbsd-x64": "0.18.20", 400 | "@esbuild/sunos-x64": "0.18.20", 401 | "@esbuild/win32-arm64": "0.18.20", 402 | "@esbuild/win32-ia32": "0.18.20", 403 | "@esbuild/win32-x64": "0.18.20" 404 | } 405 | }, 406 | "node_modules/fsevents": { 407 | "version": "2.3.3", 408 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 409 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 410 | "dev": true, 411 | "hasInstallScript": true, 412 | "optional": true, 413 | "os": [ 414 | "darwin" 415 | ], 416 | "engines": { 417 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 418 | } 419 | }, 420 | "node_modules/lodash": { 421 | "version": "4.17.21", 422 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 423 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 424 | }, 425 | "node_modules/nanoid": { 426 | "version": "3.3.6", 427 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", 428 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", 429 | "dev": true, 430 | "funding": [ 431 | { 432 | "type": "github", 433 | "url": "https://github.com/sponsors/ai" 434 | } 435 | ], 436 | "bin": { 437 | "nanoid": "bin/nanoid.cjs" 438 | }, 439 | "engines": { 440 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 441 | } 442 | }, 443 | "node_modules/picocolors": { 444 | "version": "1.0.0", 445 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 446 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 447 | "dev": true 448 | }, 449 | "node_modules/postcss": { 450 | "version": "8.4.29", 451 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz", 452 | "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==", 453 | "dev": true, 454 | "funding": [ 455 | { 456 | "type": "opencollective", 457 | "url": "https://opencollective.com/postcss/" 458 | }, 459 | { 460 | "type": "tidelift", 461 | "url": "https://tidelift.com/funding/github/npm/postcss" 462 | }, 463 | { 464 | "type": "github", 465 | "url": "https://github.com/sponsors/ai" 466 | } 467 | ], 468 | "dependencies": { 469 | "nanoid": "^3.3.6", 470 | "picocolors": "^1.0.0", 471 | "source-map-js": "^1.0.2" 472 | }, 473 | "engines": { 474 | "node": "^10 || ^12 || >=14" 475 | } 476 | }, 477 | "node_modules/rollup": { 478 | "version": "3.28.1", 479 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", 480 | "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", 481 | "dev": true, 482 | "bin": { 483 | "rollup": "dist/bin/rollup" 484 | }, 485 | "engines": { 486 | "node": ">=14.18.0", 487 | "npm": ">=8.0.0" 488 | }, 489 | "optionalDependencies": { 490 | "fsevents": "~2.3.2" 491 | } 492 | }, 493 | "node_modules/source-map-js": { 494 | "version": "1.0.2", 495 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 496 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 497 | "dev": true, 498 | "engines": { 499 | "node": ">=0.10.0" 500 | } 501 | }, 502 | "node_modules/vite": { 503 | "version": "4.4.9", 504 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", 505 | "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", 506 | "dev": true, 507 | "dependencies": { 508 | "esbuild": "^0.18.10", 509 | "postcss": "^8.4.27", 510 | "rollup": "^3.27.1" 511 | }, 512 | "bin": { 513 | "vite": "bin/vite.js" 514 | }, 515 | "engines": { 516 | "node": "^14.18.0 || >=16.0.0" 517 | }, 518 | "funding": { 519 | "url": "https://github.com/vitejs/vite?sponsor=1" 520 | }, 521 | "optionalDependencies": { 522 | "fsevents": "~2.3.2" 523 | }, 524 | "peerDependencies": { 525 | "@types/node": ">= 14", 526 | "less": "*", 527 | "lightningcss": "^1.21.0", 528 | "sass": "*", 529 | "stylus": "*", 530 | "sugarss": "*", 531 | "terser": "^5.4.0" 532 | }, 533 | "peerDependenciesMeta": { 534 | "@types/node": { 535 | "optional": true 536 | }, 537 | "less": { 538 | "optional": true 539 | }, 540 | "lightningcss": { 541 | "optional": true 542 | }, 543 | "sass": { 544 | "optional": true 545 | }, 546 | "stylus": { 547 | "optional": true 548 | }, 549 | "sugarss": { 550 | "optional": true 551 | }, 552 | "terser": { 553 | "optional": true 554 | } 555 | } 556 | } 557 | } 558 | } 559 | -------------------------------------------------------------------------------- /todo-list/after/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "after", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "after", 9 | "version": "0.0.0", 10 | "devDependencies": { 11 | "typescript": "^5.0.2", 12 | "vite": "^4.4.5" 13 | } 14 | }, 15 | "node_modules/@esbuild/android-arm": { 16 | "version": "0.18.20", 17 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", 18 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", 19 | "cpu": [ 20 | "arm" 21 | ], 22 | "dev": true, 23 | "optional": true, 24 | "os": [ 25 | "android" 26 | ], 27 | "engines": { 28 | "node": ">=12" 29 | } 30 | }, 31 | "node_modules/@esbuild/android-arm64": { 32 | "version": "0.18.20", 33 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", 34 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", 35 | "cpu": [ 36 | "arm64" 37 | ], 38 | "dev": true, 39 | "optional": true, 40 | "os": [ 41 | "android" 42 | ], 43 | "engines": { 44 | "node": ">=12" 45 | } 46 | }, 47 | "node_modules/@esbuild/android-x64": { 48 | "version": "0.18.20", 49 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", 50 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", 51 | "cpu": [ 52 | "x64" 53 | ], 54 | "dev": true, 55 | "optional": true, 56 | "os": [ 57 | "android" 58 | ], 59 | "engines": { 60 | "node": ">=12" 61 | } 62 | }, 63 | "node_modules/@esbuild/darwin-arm64": { 64 | "version": "0.18.20", 65 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", 66 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", 67 | "cpu": [ 68 | "arm64" 69 | ], 70 | "dev": true, 71 | "optional": true, 72 | "os": [ 73 | "darwin" 74 | ], 75 | "engines": { 76 | "node": ">=12" 77 | } 78 | }, 79 | "node_modules/@esbuild/darwin-x64": { 80 | "version": "0.18.20", 81 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", 82 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", 83 | "cpu": [ 84 | "x64" 85 | ], 86 | "dev": true, 87 | "optional": true, 88 | "os": [ 89 | "darwin" 90 | ], 91 | "engines": { 92 | "node": ">=12" 93 | } 94 | }, 95 | "node_modules/@esbuild/freebsd-arm64": { 96 | "version": "0.18.20", 97 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", 98 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", 99 | "cpu": [ 100 | "arm64" 101 | ], 102 | "dev": true, 103 | "optional": true, 104 | "os": [ 105 | "freebsd" 106 | ], 107 | "engines": { 108 | "node": ">=12" 109 | } 110 | }, 111 | "node_modules/@esbuild/freebsd-x64": { 112 | "version": "0.18.20", 113 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", 114 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", 115 | "cpu": [ 116 | "x64" 117 | ], 118 | "dev": true, 119 | "optional": true, 120 | "os": [ 121 | "freebsd" 122 | ], 123 | "engines": { 124 | "node": ">=12" 125 | } 126 | }, 127 | "node_modules/@esbuild/linux-arm": { 128 | "version": "0.18.20", 129 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", 130 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", 131 | "cpu": [ 132 | "arm" 133 | ], 134 | "dev": true, 135 | "optional": true, 136 | "os": [ 137 | "linux" 138 | ], 139 | "engines": { 140 | "node": ">=12" 141 | } 142 | }, 143 | "node_modules/@esbuild/linux-arm64": { 144 | "version": "0.18.20", 145 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", 146 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", 147 | "cpu": [ 148 | "arm64" 149 | ], 150 | "dev": true, 151 | "optional": true, 152 | "os": [ 153 | "linux" 154 | ], 155 | "engines": { 156 | "node": ">=12" 157 | } 158 | }, 159 | "node_modules/@esbuild/linux-ia32": { 160 | "version": "0.18.20", 161 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", 162 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", 163 | "cpu": [ 164 | "ia32" 165 | ], 166 | "dev": true, 167 | "optional": true, 168 | "os": [ 169 | "linux" 170 | ], 171 | "engines": { 172 | "node": ">=12" 173 | } 174 | }, 175 | "node_modules/@esbuild/linux-loong64": { 176 | "version": "0.18.20", 177 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", 178 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", 179 | "cpu": [ 180 | "loong64" 181 | ], 182 | "dev": true, 183 | "optional": true, 184 | "os": [ 185 | "linux" 186 | ], 187 | "engines": { 188 | "node": ">=12" 189 | } 190 | }, 191 | "node_modules/@esbuild/linux-mips64el": { 192 | "version": "0.18.20", 193 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", 194 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", 195 | "cpu": [ 196 | "mips64el" 197 | ], 198 | "dev": true, 199 | "optional": true, 200 | "os": [ 201 | "linux" 202 | ], 203 | "engines": { 204 | "node": ">=12" 205 | } 206 | }, 207 | "node_modules/@esbuild/linux-ppc64": { 208 | "version": "0.18.20", 209 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", 210 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", 211 | "cpu": [ 212 | "ppc64" 213 | ], 214 | "dev": true, 215 | "optional": true, 216 | "os": [ 217 | "linux" 218 | ], 219 | "engines": { 220 | "node": ">=12" 221 | } 222 | }, 223 | "node_modules/@esbuild/linux-riscv64": { 224 | "version": "0.18.20", 225 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", 226 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", 227 | "cpu": [ 228 | "riscv64" 229 | ], 230 | "dev": true, 231 | "optional": true, 232 | "os": [ 233 | "linux" 234 | ], 235 | "engines": { 236 | "node": ">=12" 237 | } 238 | }, 239 | "node_modules/@esbuild/linux-s390x": { 240 | "version": "0.18.20", 241 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", 242 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", 243 | "cpu": [ 244 | "s390x" 245 | ], 246 | "dev": true, 247 | "optional": true, 248 | "os": [ 249 | "linux" 250 | ], 251 | "engines": { 252 | "node": ">=12" 253 | } 254 | }, 255 | "node_modules/@esbuild/linux-x64": { 256 | "version": "0.18.20", 257 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", 258 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", 259 | "cpu": [ 260 | "x64" 261 | ], 262 | "dev": true, 263 | "optional": true, 264 | "os": [ 265 | "linux" 266 | ], 267 | "engines": { 268 | "node": ">=12" 269 | } 270 | }, 271 | "node_modules/@esbuild/netbsd-x64": { 272 | "version": "0.18.20", 273 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", 274 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", 275 | "cpu": [ 276 | "x64" 277 | ], 278 | "dev": true, 279 | "optional": true, 280 | "os": [ 281 | "netbsd" 282 | ], 283 | "engines": { 284 | "node": ">=12" 285 | } 286 | }, 287 | "node_modules/@esbuild/openbsd-x64": { 288 | "version": "0.18.20", 289 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", 290 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", 291 | "cpu": [ 292 | "x64" 293 | ], 294 | "dev": true, 295 | "optional": true, 296 | "os": [ 297 | "openbsd" 298 | ], 299 | "engines": { 300 | "node": ">=12" 301 | } 302 | }, 303 | "node_modules/@esbuild/sunos-x64": { 304 | "version": "0.18.20", 305 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", 306 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", 307 | "cpu": [ 308 | "x64" 309 | ], 310 | "dev": true, 311 | "optional": true, 312 | "os": [ 313 | "sunos" 314 | ], 315 | "engines": { 316 | "node": ">=12" 317 | } 318 | }, 319 | "node_modules/@esbuild/win32-arm64": { 320 | "version": "0.18.20", 321 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", 322 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", 323 | "cpu": [ 324 | "arm64" 325 | ], 326 | "dev": true, 327 | "optional": true, 328 | "os": [ 329 | "win32" 330 | ], 331 | "engines": { 332 | "node": ">=12" 333 | } 334 | }, 335 | "node_modules/@esbuild/win32-ia32": { 336 | "version": "0.18.20", 337 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", 338 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", 339 | "cpu": [ 340 | "ia32" 341 | ], 342 | "dev": true, 343 | "optional": true, 344 | "os": [ 345 | "win32" 346 | ], 347 | "engines": { 348 | "node": ">=12" 349 | } 350 | }, 351 | "node_modules/@esbuild/win32-x64": { 352 | "version": "0.18.20", 353 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", 354 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", 355 | "cpu": [ 356 | "x64" 357 | ], 358 | "dev": true, 359 | "optional": true, 360 | "os": [ 361 | "win32" 362 | ], 363 | "engines": { 364 | "node": ">=12" 365 | } 366 | }, 367 | "node_modules/esbuild": { 368 | "version": "0.18.20", 369 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", 370 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", 371 | "dev": true, 372 | "hasInstallScript": true, 373 | "bin": { 374 | "esbuild": "bin/esbuild" 375 | }, 376 | "engines": { 377 | "node": ">=12" 378 | }, 379 | "optionalDependencies": { 380 | "@esbuild/android-arm": "0.18.20", 381 | "@esbuild/android-arm64": "0.18.20", 382 | "@esbuild/android-x64": "0.18.20", 383 | "@esbuild/darwin-arm64": "0.18.20", 384 | "@esbuild/darwin-x64": "0.18.20", 385 | "@esbuild/freebsd-arm64": "0.18.20", 386 | "@esbuild/freebsd-x64": "0.18.20", 387 | "@esbuild/linux-arm": "0.18.20", 388 | "@esbuild/linux-arm64": "0.18.20", 389 | "@esbuild/linux-ia32": "0.18.20", 390 | "@esbuild/linux-loong64": "0.18.20", 391 | "@esbuild/linux-mips64el": "0.18.20", 392 | "@esbuild/linux-ppc64": "0.18.20", 393 | "@esbuild/linux-riscv64": "0.18.20", 394 | "@esbuild/linux-s390x": "0.18.20", 395 | "@esbuild/linux-x64": "0.18.20", 396 | "@esbuild/netbsd-x64": "0.18.20", 397 | "@esbuild/openbsd-x64": "0.18.20", 398 | "@esbuild/sunos-x64": "0.18.20", 399 | "@esbuild/win32-arm64": "0.18.20", 400 | "@esbuild/win32-ia32": "0.18.20", 401 | "@esbuild/win32-x64": "0.18.20" 402 | } 403 | }, 404 | "node_modules/fsevents": { 405 | "version": "2.3.3", 406 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 407 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 408 | "dev": true, 409 | "hasInstallScript": true, 410 | "optional": true, 411 | "os": [ 412 | "darwin" 413 | ], 414 | "engines": { 415 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 416 | } 417 | }, 418 | "node_modules/nanoid": { 419 | "version": "3.3.6", 420 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", 421 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", 422 | "dev": true, 423 | "funding": [ 424 | { 425 | "type": "github", 426 | "url": "https://github.com/sponsors/ai" 427 | } 428 | ], 429 | "bin": { 430 | "nanoid": "bin/nanoid.cjs" 431 | }, 432 | "engines": { 433 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 434 | } 435 | }, 436 | "node_modules/picocolors": { 437 | "version": "1.0.0", 438 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 439 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 440 | "dev": true 441 | }, 442 | "node_modules/postcss": { 443 | "version": "8.4.29", 444 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz", 445 | "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==", 446 | "dev": true, 447 | "funding": [ 448 | { 449 | "type": "opencollective", 450 | "url": "https://opencollective.com/postcss/" 451 | }, 452 | { 453 | "type": "tidelift", 454 | "url": "https://tidelift.com/funding/github/npm/postcss" 455 | }, 456 | { 457 | "type": "github", 458 | "url": "https://github.com/sponsors/ai" 459 | } 460 | ], 461 | "dependencies": { 462 | "nanoid": "^3.3.6", 463 | "picocolors": "^1.0.0", 464 | "source-map-js": "^1.0.2" 465 | }, 466 | "engines": { 467 | "node": "^10 || ^12 || >=14" 468 | } 469 | }, 470 | "node_modules/rollup": { 471 | "version": "3.28.1", 472 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", 473 | "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", 474 | "dev": true, 475 | "bin": { 476 | "rollup": "dist/bin/rollup" 477 | }, 478 | "engines": { 479 | "node": ">=14.18.0", 480 | "npm": ">=8.0.0" 481 | }, 482 | "optionalDependencies": { 483 | "fsevents": "~2.3.2" 484 | } 485 | }, 486 | "node_modules/source-map-js": { 487 | "version": "1.0.2", 488 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 489 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 490 | "dev": true, 491 | "engines": { 492 | "node": ">=0.10.0" 493 | } 494 | }, 495 | "node_modules/typescript": { 496 | "version": "5.2.2", 497 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", 498 | "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", 499 | "dev": true, 500 | "bin": { 501 | "tsc": "bin/tsc", 502 | "tsserver": "bin/tsserver" 503 | }, 504 | "engines": { 505 | "node": ">=14.17" 506 | } 507 | }, 508 | "node_modules/vite": { 509 | "version": "4.4.9", 510 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", 511 | "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", 512 | "dev": true, 513 | "dependencies": { 514 | "esbuild": "^0.18.10", 515 | "postcss": "^8.4.27", 516 | "rollup": "^3.27.1" 517 | }, 518 | "bin": { 519 | "vite": "bin/vite.js" 520 | }, 521 | "engines": { 522 | "node": "^14.18.0 || >=16.0.0" 523 | }, 524 | "funding": { 525 | "url": "https://github.com/vitejs/vite?sponsor=1" 526 | }, 527 | "optionalDependencies": { 528 | "fsevents": "~2.3.2" 529 | }, 530 | "peerDependencies": { 531 | "@types/node": ">= 14", 532 | "less": "*", 533 | "lightningcss": "^1.21.0", 534 | "sass": "*", 535 | "stylus": "*", 536 | "sugarss": "*", 537 | "terser": "^5.4.0" 538 | }, 539 | "peerDependenciesMeta": { 540 | "@types/node": { 541 | "optional": true 542 | }, 543 | "less": { 544 | "optional": true 545 | }, 546 | "lightningcss": { 547 | "optional": true 548 | }, 549 | "sass": { 550 | "optional": true 551 | }, 552 | "stylus": { 553 | "optional": true 554 | }, 555 | "sugarss": { 556 | "optional": true 557 | }, 558 | "terser": { 559 | "optional": true 560 | } 561 | } 562 | } 563 | } 564 | } 565 | -------------------------------------------------------------------------------- /converting-js-to-ts/after/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "before", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "before", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "lodash": "^4.17.21" 12 | }, 13 | "devDependencies": { 14 | "@types/lodash": "^4.14.197", 15 | "typescript": "^5.2.2", 16 | "vite": "^4.4.5" 17 | } 18 | }, 19 | "node_modules/@esbuild/android-arm": { 20 | "version": "0.18.20", 21 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", 22 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", 23 | "cpu": [ 24 | "arm" 25 | ], 26 | "dev": true, 27 | "optional": true, 28 | "os": [ 29 | "android" 30 | ], 31 | "engines": { 32 | "node": ">=12" 33 | } 34 | }, 35 | "node_modules/@esbuild/android-arm64": { 36 | "version": "0.18.20", 37 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", 38 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", 39 | "cpu": [ 40 | "arm64" 41 | ], 42 | "dev": true, 43 | "optional": true, 44 | "os": [ 45 | "android" 46 | ], 47 | "engines": { 48 | "node": ">=12" 49 | } 50 | }, 51 | "node_modules/@esbuild/android-x64": { 52 | "version": "0.18.20", 53 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", 54 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", 55 | "cpu": [ 56 | "x64" 57 | ], 58 | "dev": true, 59 | "optional": true, 60 | "os": [ 61 | "android" 62 | ], 63 | "engines": { 64 | "node": ">=12" 65 | } 66 | }, 67 | "node_modules/@esbuild/darwin-arm64": { 68 | "version": "0.18.20", 69 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", 70 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", 71 | "cpu": [ 72 | "arm64" 73 | ], 74 | "dev": true, 75 | "optional": true, 76 | "os": [ 77 | "darwin" 78 | ], 79 | "engines": { 80 | "node": ">=12" 81 | } 82 | }, 83 | "node_modules/@esbuild/darwin-x64": { 84 | "version": "0.18.20", 85 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", 86 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", 87 | "cpu": [ 88 | "x64" 89 | ], 90 | "dev": true, 91 | "optional": true, 92 | "os": [ 93 | "darwin" 94 | ], 95 | "engines": { 96 | "node": ">=12" 97 | } 98 | }, 99 | "node_modules/@esbuild/freebsd-arm64": { 100 | "version": "0.18.20", 101 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", 102 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", 103 | "cpu": [ 104 | "arm64" 105 | ], 106 | "dev": true, 107 | "optional": true, 108 | "os": [ 109 | "freebsd" 110 | ], 111 | "engines": { 112 | "node": ">=12" 113 | } 114 | }, 115 | "node_modules/@esbuild/freebsd-x64": { 116 | "version": "0.18.20", 117 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", 118 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", 119 | "cpu": [ 120 | "x64" 121 | ], 122 | "dev": true, 123 | "optional": true, 124 | "os": [ 125 | "freebsd" 126 | ], 127 | "engines": { 128 | "node": ">=12" 129 | } 130 | }, 131 | "node_modules/@esbuild/linux-arm": { 132 | "version": "0.18.20", 133 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", 134 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", 135 | "cpu": [ 136 | "arm" 137 | ], 138 | "dev": true, 139 | "optional": true, 140 | "os": [ 141 | "linux" 142 | ], 143 | "engines": { 144 | "node": ">=12" 145 | } 146 | }, 147 | "node_modules/@esbuild/linux-arm64": { 148 | "version": "0.18.20", 149 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", 150 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", 151 | "cpu": [ 152 | "arm64" 153 | ], 154 | "dev": true, 155 | "optional": true, 156 | "os": [ 157 | "linux" 158 | ], 159 | "engines": { 160 | "node": ">=12" 161 | } 162 | }, 163 | "node_modules/@esbuild/linux-ia32": { 164 | "version": "0.18.20", 165 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", 166 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", 167 | "cpu": [ 168 | "ia32" 169 | ], 170 | "dev": true, 171 | "optional": true, 172 | "os": [ 173 | "linux" 174 | ], 175 | "engines": { 176 | "node": ">=12" 177 | } 178 | }, 179 | "node_modules/@esbuild/linux-loong64": { 180 | "version": "0.18.20", 181 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", 182 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", 183 | "cpu": [ 184 | "loong64" 185 | ], 186 | "dev": true, 187 | "optional": true, 188 | "os": [ 189 | "linux" 190 | ], 191 | "engines": { 192 | "node": ">=12" 193 | } 194 | }, 195 | "node_modules/@esbuild/linux-mips64el": { 196 | "version": "0.18.20", 197 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", 198 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", 199 | "cpu": [ 200 | "mips64el" 201 | ], 202 | "dev": true, 203 | "optional": true, 204 | "os": [ 205 | "linux" 206 | ], 207 | "engines": { 208 | "node": ">=12" 209 | } 210 | }, 211 | "node_modules/@esbuild/linux-ppc64": { 212 | "version": "0.18.20", 213 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", 214 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", 215 | "cpu": [ 216 | "ppc64" 217 | ], 218 | "dev": true, 219 | "optional": true, 220 | "os": [ 221 | "linux" 222 | ], 223 | "engines": { 224 | "node": ">=12" 225 | } 226 | }, 227 | "node_modules/@esbuild/linux-riscv64": { 228 | "version": "0.18.20", 229 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", 230 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", 231 | "cpu": [ 232 | "riscv64" 233 | ], 234 | "dev": true, 235 | "optional": true, 236 | "os": [ 237 | "linux" 238 | ], 239 | "engines": { 240 | "node": ">=12" 241 | } 242 | }, 243 | "node_modules/@esbuild/linux-s390x": { 244 | "version": "0.18.20", 245 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", 246 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", 247 | "cpu": [ 248 | "s390x" 249 | ], 250 | "dev": true, 251 | "optional": true, 252 | "os": [ 253 | "linux" 254 | ], 255 | "engines": { 256 | "node": ">=12" 257 | } 258 | }, 259 | "node_modules/@esbuild/linux-x64": { 260 | "version": "0.18.20", 261 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", 262 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", 263 | "cpu": [ 264 | "x64" 265 | ], 266 | "dev": true, 267 | "optional": true, 268 | "os": [ 269 | "linux" 270 | ], 271 | "engines": { 272 | "node": ">=12" 273 | } 274 | }, 275 | "node_modules/@esbuild/netbsd-x64": { 276 | "version": "0.18.20", 277 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", 278 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", 279 | "cpu": [ 280 | "x64" 281 | ], 282 | "dev": true, 283 | "optional": true, 284 | "os": [ 285 | "netbsd" 286 | ], 287 | "engines": { 288 | "node": ">=12" 289 | } 290 | }, 291 | "node_modules/@esbuild/openbsd-x64": { 292 | "version": "0.18.20", 293 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", 294 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", 295 | "cpu": [ 296 | "x64" 297 | ], 298 | "dev": true, 299 | "optional": true, 300 | "os": [ 301 | "openbsd" 302 | ], 303 | "engines": { 304 | "node": ">=12" 305 | } 306 | }, 307 | "node_modules/@esbuild/sunos-x64": { 308 | "version": "0.18.20", 309 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", 310 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", 311 | "cpu": [ 312 | "x64" 313 | ], 314 | "dev": true, 315 | "optional": true, 316 | "os": [ 317 | "sunos" 318 | ], 319 | "engines": { 320 | "node": ">=12" 321 | } 322 | }, 323 | "node_modules/@esbuild/win32-arm64": { 324 | "version": "0.18.20", 325 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", 326 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", 327 | "cpu": [ 328 | "arm64" 329 | ], 330 | "dev": true, 331 | "optional": true, 332 | "os": [ 333 | "win32" 334 | ], 335 | "engines": { 336 | "node": ">=12" 337 | } 338 | }, 339 | "node_modules/@esbuild/win32-ia32": { 340 | "version": "0.18.20", 341 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", 342 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", 343 | "cpu": [ 344 | "ia32" 345 | ], 346 | "dev": true, 347 | "optional": true, 348 | "os": [ 349 | "win32" 350 | ], 351 | "engines": { 352 | "node": ">=12" 353 | } 354 | }, 355 | "node_modules/@esbuild/win32-x64": { 356 | "version": "0.18.20", 357 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", 358 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", 359 | "cpu": [ 360 | "x64" 361 | ], 362 | "dev": true, 363 | "optional": true, 364 | "os": [ 365 | "win32" 366 | ], 367 | "engines": { 368 | "node": ">=12" 369 | } 370 | }, 371 | "node_modules/@types/lodash": { 372 | "version": "4.14.197", 373 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.197.tgz", 374 | "integrity": "sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==", 375 | "dev": true 376 | }, 377 | "node_modules/esbuild": { 378 | "version": "0.18.20", 379 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", 380 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", 381 | "dev": true, 382 | "hasInstallScript": true, 383 | "bin": { 384 | "esbuild": "bin/esbuild" 385 | }, 386 | "engines": { 387 | "node": ">=12" 388 | }, 389 | "optionalDependencies": { 390 | "@esbuild/android-arm": "0.18.20", 391 | "@esbuild/android-arm64": "0.18.20", 392 | "@esbuild/android-x64": "0.18.20", 393 | "@esbuild/darwin-arm64": "0.18.20", 394 | "@esbuild/darwin-x64": "0.18.20", 395 | "@esbuild/freebsd-arm64": "0.18.20", 396 | "@esbuild/freebsd-x64": "0.18.20", 397 | "@esbuild/linux-arm": "0.18.20", 398 | "@esbuild/linux-arm64": "0.18.20", 399 | "@esbuild/linux-ia32": "0.18.20", 400 | "@esbuild/linux-loong64": "0.18.20", 401 | "@esbuild/linux-mips64el": "0.18.20", 402 | "@esbuild/linux-ppc64": "0.18.20", 403 | "@esbuild/linux-riscv64": "0.18.20", 404 | "@esbuild/linux-s390x": "0.18.20", 405 | "@esbuild/linux-x64": "0.18.20", 406 | "@esbuild/netbsd-x64": "0.18.20", 407 | "@esbuild/openbsd-x64": "0.18.20", 408 | "@esbuild/sunos-x64": "0.18.20", 409 | "@esbuild/win32-arm64": "0.18.20", 410 | "@esbuild/win32-ia32": "0.18.20", 411 | "@esbuild/win32-x64": "0.18.20" 412 | } 413 | }, 414 | "node_modules/fsevents": { 415 | "version": "2.3.3", 416 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 417 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 418 | "dev": true, 419 | "hasInstallScript": true, 420 | "optional": true, 421 | "os": [ 422 | "darwin" 423 | ], 424 | "engines": { 425 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 426 | } 427 | }, 428 | "node_modules/lodash": { 429 | "version": "4.17.21", 430 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 431 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 432 | }, 433 | "node_modules/nanoid": { 434 | "version": "3.3.6", 435 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", 436 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", 437 | "dev": true, 438 | "funding": [ 439 | { 440 | "type": "github", 441 | "url": "https://github.com/sponsors/ai" 442 | } 443 | ], 444 | "bin": { 445 | "nanoid": "bin/nanoid.cjs" 446 | }, 447 | "engines": { 448 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 449 | } 450 | }, 451 | "node_modules/picocolors": { 452 | "version": "1.0.0", 453 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 454 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 455 | "dev": true 456 | }, 457 | "node_modules/postcss": { 458 | "version": "8.4.29", 459 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz", 460 | "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==", 461 | "dev": true, 462 | "funding": [ 463 | { 464 | "type": "opencollective", 465 | "url": "https://opencollective.com/postcss/" 466 | }, 467 | { 468 | "type": "tidelift", 469 | "url": "https://tidelift.com/funding/github/npm/postcss" 470 | }, 471 | { 472 | "type": "github", 473 | "url": "https://github.com/sponsors/ai" 474 | } 475 | ], 476 | "dependencies": { 477 | "nanoid": "^3.3.6", 478 | "picocolors": "^1.0.0", 479 | "source-map-js": "^1.0.2" 480 | }, 481 | "engines": { 482 | "node": "^10 || ^12 || >=14" 483 | } 484 | }, 485 | "node_modules/rollup": { 486 | "version": "3.28.1", 487 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", 488 | "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", 489 | "dev": true, 490 | "bin": { 491 | "rollup": "dist/bin/rollup" 492 | }, 493 | "engines": { 494 | "node": ">=14.18.0", 495 | "npm": ">=8.0.0" 496 | }, 497 | "optionalDependencies": { 498 | "fsevents": "~2.3.2" 499 | } 500 | }, 501 | "node_modules/source-map-js": { 502 | "version": "1.0.2", 503 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 504 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 505 | "dev": true, 506 | "engines": { 507 | "node": ">=0.10.0" 508 | } 509 | }, 510 | "node_modules/typescript": { 511 | "version": "5.2.2", 512 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", 513 | "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", 514 | "dev": true, 515 | "bin": { 516 | "tsc": "bin/tsc", 517 | "tsserver": "bin/tsserver" 518 | }, 519 | "engines": { 520 | "node": ">=14.17" 521 | } 522 | }, 523 | "node_modules/vite": { 524 | "version": "4.4.9", 525 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", 526 | "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", 527 | "dev": true, 528 | "dependencies": { 529 | "esbuild": "^0.18.10", 530 | "postcss": "^8.4.27", 531 | "rollup": "^3.27.1" 532 | }, 533 | "bin": { 534 | "vite": "bin/vite.js" 535 | }, 536 | "engines": { 537 | "node": "^14.18.0 || >=16.0.0" 538 | }, 539 | "funding": { 540 | "url": "https://github.com/vitejs/vite?sponsor=1" 541 | }, 542 | "optionalDependencies": { 543 | "fsevents": "~2.3.2" 544 | }, 545 | "peerDependencies": { 546 | "@types/node": ">= 14", 547 | "less": "*", 548 | "lightningcss": "^1.21.0", 549 | "sass": "*", 550 | "stylus": "*", 551 | "sugarss": "*", 552 | "terser": "^5.4.0" 553 | }, 554 | "peerDependenciesMeta": { 555 | "@types/node": { 556 | "optional": true 557 | }, 558 | "less": { 559 | "optional": true 560 | }, 561 | "lightningcss": { 562 | "optional": true 563 | }, 564 | "sass": { 565 | "optional": true 566 | }, 567 | "stylus": { 568 | "optional": true 569 | }, 570 | "sugarss": { 571 | "optional": true 572 | }, 573 | "terser": { 574 | "optional": true 575 | } 576 | } 577 | } 578 | } 579 | } 580 | --------------------------------------------------------------------------------