From 8ac7e8222183b18dc4225edba4442928a79eabe5 Mon Sep 17 00:00:00 2001 From: muzam Date: Mon, 13 Dec 2021 16:21:28 +0530 Subject: [PATCH] Implemented download as zip --- components/EditorNavigation.tsx | 82 +++++++++------------------------ package.json | 3 ++ state.ts | 16 ++++++- utils/helpers.ts | 9 ++++ utils/zip.ts | 32 +++++++++++++ yarn.lock | 78 +++++++++++++++++++++++++++++-- 6 files changed, 156 insertions(+), 64 deletions(-) create mode 100644 utils/helpers.ts create mode 100644 utils/zip.ts diff --git a/components/EditorNavigation.tsx b/components/EditorNavigation.tsx index e3e3dcf..bb44ca6 100644 --- a/components/EditorNavigation.tsx +++ b/components/EditorNavigation.tsx @@ -31,6 +31,7 @@ import { state, syncToGist, updateEditorSettings, + downloadAsZip } from "../state"; import Box from "./Box"; import Button from "./Button"; @@ -129,8 +130,7 @@ const EditorNavigation = () => { // If deleted file is behind active tab // we keep the current state otherwise // select previous file on the list - state.active = - index > snap.active ? snap.active : snap.active - 1; + state.active = index > snap.active ? snap.active : snap.active - 1; }} > @@ -140,28 +140,18 @@ const EditorNavigation = () => { - Create new file - setFilename(e.target.value)} - /> + setFilename(e.target.value)} /> - + @@ -195,9 +185,7 @@ const EditorNavigation = () => { zIndex: 1, }} > - + {status === "authenticated" ? ( @@ -230,15 +218,10 @@ const EditorNavigation = () => { signOut()}> - {session?.user?.username} ( - {session?.user.name}) + {session?.user?.username} ({session?.user.name}) - window.open( - `http://gist.github.com/${session?.user.username}` - ) - } + onClick={() => window.open(`http://gist.github.com/${session?.user.username}`)} > Go to your Gist @@ -252,12 +235,7 @@ const EditorNavigation = () => { ) : ( - )} @@ -296,7 +274,7 @@ const EditorNavigation = () => { }, }} > - - + Download as ZIP { Copy share link to clipboard { syncToGist(session); }} @@ -379,21 +353,15 @@ const EditorNavigation = () => { - {popup && !session ? ( - - ) : null} + {popup && !session ? : null} - setCreateNewAlertOpen(value)} - > + setCreateNewAlertOpen(value)}> Are you sure? - This action will create new public Github Gist from - your current saved files. You can delete gist anytime from your - GitHub Gists page. + This action will create new public Github Gist from your current saved + files. You can delete gist anytime from your GitHub Gists page. @@ -427,8 +395,8 @@ const EditorNavigation = () => { type="number" min="1" value={editorSettings.tabSize} - onChange={(e) => - setEditorSettings((curr) => ({ + onChange={e => + setEditorSettings(curr => ({ ...curr, tabSize: Number(e.target.value), })) @@ -438,18 +406,12 @@ const EditorNavigation = () => { - - diff --git a/package.json b/package.json index 77ccc12..def9980 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "@radix-ui/react-dropdown-menu": "^0.1.1", "@radix-ui/react-id": "^0.1.1", "@stitches/react": "^1.2.6-0", + "file-saver": "^2.0.5", + "jszip": "^3.7.1", "monaco-editor": "^0.29.1", "next": "^12.0.4", "next-auth": "^4.0.0-beta.5", @@ -31,6 +33,7 @@ "valtio": "^1.2.5" }, "devDependencies": { + "@types/file-saver": "^2.0.4", "@types/react": "17.0.31", "eslint": "7.32.0", "eslint-config-next": "11.1.2", diff --git a/state.ts b/state.ts index 8678f3b..dc77a64 100644 --- a/state.ts +++ b/state.ts @@ -5,6 +5,8 @@ import type monaco from 'monaco-editor'; import toast from 'react-hot-toast'; import Router from 'next/router'; import type { Session } from 'next-auth'; +import { createZip } from './utils/zip'; +import { guessZipFileName } from './utils/helpers'; const octokit = new Octokit(); @@ -182,7 +184,9 @@ export const updateEditorSettings = (editorSettings: IState['editorSettings']) = export const saveFile = (value: string) => { const editorModels = state.editorCtx?.getModels(); - const currentModel = editorModels?.find(editorModel => editorModel.uri.path === `/${state.files[state.active].name}`); + const currentModel = editorModels?.find( + editorModel => editorModel.uri.path === `/${state.files[state.active].name}` + ); if (state.files.length > 0) { state.files[state.active].content = currentModel?.getValue() || ''; } @@ -244,6 +248,16 @@ export const compileCode = async (activeId: number) => { } } + +export const downloadAsZip = async () => { + // TODO do something about loading state + const files = state.files.map(({ name, content }) => ({ name, content })); + const zipped = await createZip(files); + const zipFileName = guessZipFileName(files); + zipped.saveFile(zipFileName); +}; + + if (process.env.NODE_ENV !== 'production') { devtools(state, 'Files State'); } diff --git a/utils/helpers.ts b/utils/helpers.ts new file mode 100644 index 0000000..91a82b2 --- /dev/null +++ b/utils/helpers.ts @@ -0,0 +1,9 @@ +interface File { + name: string +} + +export const guessZipFileName = (files: File[]) => { + let parts = (files.filter(f => f.name.endsWith('.c'))[0]?.name || 'hook').split('.') + parts = parts.length > 1 ? parts.slice(0, -1) : parts + return parts.join('') +} \ No newline at end of file diff --git a/utils/zip.ts b/utils/zip.ts new file mode 100644 index 0000000..57c4959 --- /dev/null +++ b/utils/zip.ts @@ -0,0 +1,32 @@ +import JSZip, { JSZipFileOptions } from 'jszip' +import { saveAs } from 'file-saver' + +interface File { + name: string + content: any + options?: JSZipFileOptions +} + +interface Zipped { + saveFile: (filename: string) => void + data: Blob +} + +export const createZip = async (files: File[]): Promise => { + const zip = new JSZip() + + files.forEach(({ name, content, options }) => { + zip.file(name, content, options) + }) + + const data = await zip.generateAsync({ type: "blob" }) + + return { + saveFile: (filename: string) => + saveAs(data, filename), + data + } +} + + + diff --git a/yarn.lock b/yarn.lock index b2c7883..15ac5bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -779,6 +779,11 @@ resolved "https://registry.yarnpkg.com/@types/btoa-lite/-/btoa-lite-1.0.0.tgz#e190a5a548e0b348adb0df9ac7fa5f1151c7cca4" integrity sha512-wJsiX1tosQ+J5+bY5LrSahHxr2wT+uME5UDwdN1kg4frt40euqA+wzECkmq4t5QbveHiJepfdThgQrPw6KiSlg== +"@types/file-saver@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.4.tgz#aaf9b96296150d737b2fefa535ced05ed8013d84" + integrity sha512-sPZYQEIF/SOnLAvaz9lTuydniP+afBMtElRTdYkeV1QtEgvtJ7qolCPjly6O32QI8CbEmP5O/fztMXEDWfEcrg== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -1342,6 +1347,11 @@ core-js-pure@^3.16.0: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.18.3.tgz#7eed77dcce1445ab68fd68715856633e2fb3b90c" integrity sha512-qfskyO/KjtbYn09bn1IPkuhHl5PlJ6IzJ9s9sraJ1EqcuGyLGKzhSM1cY0zgyL9hx42eulQLZ6WaeK5ycJCkqw== +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" @@ -1914,6 +1924,11 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -2204,6 +2219,11 @@ image-size@1.0.0: dependencies: queue "6.0.2" +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -2399,6 +2419,11 @@ is-weakref@^1.0.1: dependencies: call-bind "^1.0.0" +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -2477,6 +2502,16 @@ jsonwebtoken@^8.5.1: array-includes "^3.1.3" object.assign "^4.1.2" +jszip@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" + integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + set-immediate-shim "~1.0.1" + jwa@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -2514,6 +2549,13 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + loader-utils@1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" @@ -2992,7 +3034,7 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -pako@~1.0.5: +pako@~1.0.2, pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== @@ -3121,6 +3163,11 @@ pretty-format@^3.8.0: resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385" integrity sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U= +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + process@0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -3294,6 +3341,19 @@ readable-stream@^3.5.0, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readdirp@~3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" @@ -3382,7 +3442,7 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.1: +safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -3417,6 +3477,11 @@ semver@^7.2.1, semver@^7.3.5: dependencies: lru-cache "^6.0.0" +set-immediate-shim@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= + setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -3590,6 +3655,13 @@ string_decoder@1.3.0, string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -3809,7 +3881,7 @@ use-subscription@1.5.1: dependencies: object-assign "^4.1.1" -util-deprecate@^1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=