├── .babelrc ├── .editorconfig ├── .env ├── .eslintrc.js ├── .github └── workflows │ ├── build.yml │ └── lint.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json └── tasks.json ├── License.md ├── README.md ├── jsconfig.json ├── jsconfig.server.json ├── next-sitemap.config.js ├── next.config.js ├── package.json ├── postcss.config.js ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── bg.jpg ├── draco │ ├── decoder.js │ └── draco_decoder_gltf.wasm ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── icons │ ├── android-icon-192x192.png │ ├── android-icon-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── safari-pinned-tab.svg │ └── share.png ├── img │ ├── logo.svg │ └── scores │ │ ├── lighthouse.md │ │ └── lighthouse.svg ├── manifest.json ├── mstile-144x144.png ├── mstile-150x150.png ├── mstile-310x150.png ├── mstile-310x310.png ├── mstile-70x70.png ├── robots.txt ├── safari-pinned-tab.svg ├── share.png ├── site.webmanifest ├── sitemap-0.xml ├── sitemap.xml └── suzanne.gltf ├── server └── dev.js ├── src ├── components │ ├── AddAsset │ │ ├── CreatorSelect.js │ │ ├── Step1.js │ │ ├── Step2.js │ │ ├── Step3.js │ │ ├── TeamSelect.js │ │ └── index.js │ ├── Asset.js │ ├── AssetInfo.js │ ├── Button.js │ ├── Category.js │ ├── CreatorPage.js │ ├── FavoriteButton.js │ ├── Form │ │ ├── FancySelect.js │ │ ├── FileDrop.js │ │ ├── Input.js │ │ └── RadioGroup.js │ ├── Logo.js │ ├── Modal.js │ ├── NextAndPrev.js │ ├── RequestAsset │ │ ├── AssetRequestForm.js │ │ └── Request.js │ ├── Search.js │ ├── Tabs.js │ ├── canvas │ │ ├── Editor │ │ │ ├── ColorPicker.js │ │ │ ├── EditTools.js │ │ │ ├── RangeSlider.js │ │ │ ├── Select.js │ │ │ ├── index.js │ │ │ └── useUpdateScene.js │ │ ├── HDRI.js │ │ ├── Material.js │ │ ├── Model.js │ │ └── controls.js │ ├── comments │ │ ├── Textarea.js │ │ ├── addACommentForm.js │ │ ├── comment.js │ │ ├── editCommentForm.js │ │ ├── index.js │ │ └── submitButton.js │ ├── info │ │ ├── Category.js │ │ ├── CreatorInfo.js │ │ ├── Creators.js │ │ ├── DownloadButton.js │ │ ├── License.js │ │ ├── Size.js │ │ ├── Stats.js │ │ └── rating │ │ │ ├── Star.js │ │ │ ├── StarRating.js │ │ │ └── index.js │ └── layout │ │ ├── PopOverMenu.js │ │ ├── footer.js │ │ ├── index.js │ │ └── nav │ │ ├── UserMenu.js │ │ ├── index.js │ │ └── useNav.js ├── config.js ├── helpers │ ├── api │ │ ├── cleanSupabaseData.js │ │ ├── endpoints │ │ │ └── favorite.js │ │ ├── getSize.js │ │ └── queries │ │ │ ├── cleanSupaBaseData.js │ │ │ └── list.js │ ├── classNames.js │ ├── code │ │ ├── common │ │ │ ├── r3f.js │ │ │ └── three.js │ │ ├── hdri │ │ │ ├── r3f.js │ │ │ └── three.js │ │ ├── matcaps │ │ │ ├── r3f.js │ │ │ └── three.js │ │ ├── model │ │ │ ├── r3f.js │ │ │ └── three.js │ │ └── pbr │ │ │ ├── common.js │ │ │ ├── r3f.js │ │ │ └── three.js │ ├── constants │ │ ├── api.js │ │ └── licenses.js │ ├── getMaterialSize.js │ ├── getNextAndPrev.js │ ├── getRatingsMean.js │ ├── getSize.js │ ├── getStats.js │ ├── hooks │ │ └── useHandleLogin.js │ ├── initSupabase.js │ ├── slugify.js │ └── store │ │ ├── addAsset.js │ │ ├── comments.js │ │ ├── hdri.js │ │ ├── index.js │ │ ├── materials.js │ │ ├── rating.js │ │ ├── requests.js │ │ └── utils.js ├── pages │ ├── 404.jsx │ ├── 500.jsx │ ├── [name] │ │ └── categories │ │ │ ├── [category].js │ │ │ └── index.js │ ├── _app.jsx │ ├── _document.js │ ├── add-asset.js │ ├── admin-dashboard.js │ ├── api │ │ ├── [type] │ │ │ ├── [name] │ │ │ │ ├── [categoryName] │ │ │ │ │ └── index.js │ │ │ │ ├── assets.js │ │ │ │ ├── buffer.js │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── auth.js │ │ └── getUser.js │ ├── creator-dashboard.js │ ├── creator │ │ └── [name].js │ ├── editor │ │ └── [name].js │ ├── favorites.js │ ├── hdri │ │ └── [name].js │ ├── hdris.jsx │ ├── index.jsx │ ├── login.js │ ├── material │ │ └── [name].js │ ├── materials.jsx │ ├── model │ │ └── [name].js │ ├── request.js │ └── team │ │ └── [name].js └── styles │ └── index.css ├── tailwind.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "next/babel", 5 | { 6 | "preset-env": {}, 7 | "transform-runtime": {}, 8 | "styled-jsx": {}, 9 | "class-properties": {} 10 | } 11 | ] 12 | ], 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | 9 | [{*.json,.*.yml}] 10 | indent_style = space 11 | indent_size = 2 -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | TAILWIND_MODE=watch 2 | NEXT_PUBLIC_SUPABASE_URL=https://vazxmixjsiawhamofees.supabase.co 3 | NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxOTg3MDQ2MywiZXhwIjoxOTM1NDQ2NDYzfQ.Zha2ItunG_E6eAQhCFzmu32FHU_WMV_pMZZ6Js_KOMA 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // @js-ignore 2 | module.exports = { 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:react/recommended', 6 | 'plugin:prettier/recommended', 7 | 'react-app', 8 | 'plugin:tailwind/recommended', 9 | 'plugin:react-hooks/recommended', 10 | ], 11 | rules: { 12 | 'react/prop-types': 'off', 13 | 'react/display-name': 'off', 14 | 'react/jsx-indent-props': [2, 'first'], 15 | 'import/prefer-default-export': 'off', 16 | 'import/no-cycle': 'off', 17 | 'no-multi-assign': 'off', 18 | 'react/react-in-jsx-scope': 'off', 19 | // nextjs issue; need to update next 20 | 'jsx-a11y/anchor-is-valid': 'off', 21 | "react/no-unknown-property": 0 22 | }, 23 | settings: {}, 24 | plugins: ['import'], 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [18.x] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - run: yarn 20 | - run: NODE_OPTIONS=--openssl-legacy-provider yarn build 21 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [18.x] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - run: yarn 20 | - run: yarn lint 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # testing 5 | /coverage 6 | 7 | # next.js 8 | /.next/ 9 | /out/ 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | *.pem 17 | 18 | # debug 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # local env files 24 | .env.local 25 | .env.development.local 26 | .env.test.local 27 | .env.production.local 28 | .eslintcache 29 | # vercel 30 | .vercel 31 | 32 | # next-pwa 33 | /public/workbox-*.js 34 | /public/sw.js 35 | 36 | # prefer yarn than npm 37 | package-lock.json 38 | 39 | report.*.json 40 | 41 | # .vscode Debugging 42 | .vscode/*-debug-profile 43 | .env 44 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build/* 2 | dist/* 3 | public/* 4 | .next/* 5 | node_modules/* 6 | .log 7 | package.json 8 | .eslintrc.js 9 | babel.config.js -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "embeddedLanguageFormatting": "auto", 5 | "htmlWhitespaceSensitivity": "css", 6 | "insertPragma": false, 7 | "jsxSingleQuote": true, 8 | "printWidth": 80, 9 | "proseWrap": "preserve", 10 | "quoteProps": "as-needed", 11 | "requirePragma": false, 12 | "semi": false, 13 | "singleQuote": true, 14 | "tabWidth": 2, 15 | "trailingComma": "es5", 16 | "useTabs": false, 17 | "vueIndentScriptAndStyle": false 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "bradlc.vscode-tailwindcss", 6 | "dbaeumer.vscode-eslint", 7 | "editorconfig.editorconfig", 8 | "esbenp.prettier-vscode", 9 | "firefox-devtools.vscode-firefox-debug", 10 | "msjsdiag.debugger-for-chrome" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Next: Node", 11 | "runtimeExecutable": "${workspaceFolder}\\node_modules\\.bin\\next", 12 | "port": 9229, 13 | "console": "integratedTerminal" 14 | }, 15 | { 16 | "type": "chrome", 17 | "request": "launch", 18 | "name": "Next: Chrome", 19 | "url": "http://localhost:3000", 20 | "webRoot": "${workspaceFolder}", 21 | "userDataDir": "${workspaceFolder}/.vscode/chrome-debug-profile" 22 | }, 23 | { 24 | "type": "firefox", 25 | "request": "launch", 26 | "name": "Next: Firefox", 27 | "url": "http://localhost:3000", 28 | "webRoot": "${workspaceFolder}", 29 | "profileDir": "${workspaceFolder}/.vscode/firefox-debug-profile", 30 | "keepProfileChanges": true 31 | } 32 | ], 33 | "compounds": [ 34 | { 35 | "name": "Next: Full", 36 | "configurations": ["Next: Node", "Next: Chrome"] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "build", 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | }, 13 | "problemMatcher": [], 14 | "label": "npm: build", 15 | "detail": "next build" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | 3 | Copyright 2021 pmdrs, contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Market 2 | 3 | 📦 Download CC0 assets ready to use in your next 3D Project 4 | 5 | ## Run Locally 6 | 7 | Clone the project 8 | 9 | ```bash 10 | git clone https://github.com/pmndrs/market 11 | ``` 12 | 13 | Go to the project directory 14 | 15 | ```bash 16 | cd market 17 | ``` 18 | 19 | Install dependencies 20 | 21 | ```bash 22 | yarn 23 | ``` 24 | 25 | Start the server 26 | 27 | ```bash 28 | yarn dev 29 | ``` 30 | 31 | Some needs to know: 32 | 33 | - Login does not work locally 34 | - No environment keys are needed unless you need to do things that change the database 35 | 36 | ## Features 37 | 38 | - 🔎 free minified/ready-to-use CC0 models/textures/hdris 39 | - 🚥 starters for threejs and react-three-fiber 40 | - 👥 accounts 41 | - 📬 request assets 42 | - 🥇 favorites, comments, ratings 43 | - 🔌 api endpoint 44 | - ☁️ CDN with caching 45 | - ⚙️ Material Editor 46 | 47 | ## Roadmap 48 | 49 | - components category 50 | - npm package with this 51 | 52 | ## License 53 | 54 | [MIT](https://choosealicense.com/licenses/mit/) 55 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "allowSyntheticDefaultImports": true, 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["src/*"] 8 | } 9 | }, 10 | "include": ["./src/**/*", "./pages/**/*"], 11 | "exclude": ["node_modules"] 12 | } 13 | -------------------------------------------------------------------------------- /jsconfig.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./jsconfig.json", 3 | "compilerOptions": { 4 | "esModuleInterop": false, 5 | "allowSyntheticDefaultImports": true, 6 | "lib": ["es2017"], 7 | "module": "commonjs", 8 | "noEmit": false, 9 | "outDir": "build", 10 | "skipLibCheck": true, 11 | "target": "es2017" 12 | }, 13 | "include": ["server/**/*.js"] 14 | } 15 | -------------------------------------------------------------------------------- /next-sitemap.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteUrl: 'https://market.pmnd.rs/', 3 | generateRobotsTxt: true, 4 | } 5 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withBundleAnalyzer = require('@next/bundle-analyzer')({ 2 | enabled: process.env.ANALYZE === 'true', 3 | }) 4 | module.exports = withBundleAnalyzer({ 5 | serverRuntimeConfig: { 6 | PROJECT_ROOT: __dirname, 7 | }, 8 | eslint: { 9 | // Warning: This allows production builds to successfully complete even if 10 | // your project has ESLint errors. 11 | ignoreDuringBuilds: true, 12 | }, 13 | webpack5: false, 14 | 15 | webpack: (config, { isServer }) => { 16 | // Fixes draco 17 | if (!isServer) { 18 | config.node = { 19 | fs: 'empty', 20 | } 21 | } 22 | 23 | return config 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-three-next", 3 | "version": "2.0.0", 4 | "authors": [ 5 | "Renaud ROHLINGER " 6 | ], 7 | "private": false, 8 | "scripts": { 9 | "lint": "yarn prettier && yarn eslint", 10 | "eslint": "eslint --fix \"src/**/*.{js,jsx}\" --ext jsconfig.json", 11 | "prettier": "prettier --list-different '**/*.{js,jsx,md}'", 12 | "dev": "next dev", 13 | "build": "next build", 14 | "export": "EXPORT=true next build && EXPORT=true next export", 15 | "analyze": "ANALYZE=true next build", 16 | "start": "next start", 17 | "minify": "node server/minify.js", 18 | "postbuild": "next-sitemap" 19 | }, 20 | "dependencies": { 21 | "@badrap/bar-of-progress": "^0.2.2", 22 | "@gltf-transform/core": "^0.12.7", 23 | "@gltf-transform/functions": "^0.12.7", 24 | "@headlessui/react": "^1.4.0", 25 | "@heroicons/react": "^1.0.3", 26 | "@reach/slider": "^0.16.0", 27 | "@react-spring/web": "^9.2.4", 28 | "@react-three/drei": "^7.3.1", 29 | "@react-three/fiber": "^7.0.6", 30 | "@react-three/gltfjsx": "^4.3.4", 31 | "@supabase/supabase-js": "^1.21.0", 32 | "@tailwindcss/aspect-ratio": "^0.4.2", 33 | "@tailwindcss/forms": "^0.5.7", 34 | "@tippyjs/react": "^4.2.6", 35 | "antd": "^4.16.10", 36 | "axios": "^1.6.7", 37 | "clipboard-copy": "^4.0.1", 38 | "date-fns": "^2.23.0", 39 | "eslint": "^8.56.0", 40 | "file-saver": "^2.0.5", 41 | "jszip": "^3.7.1", 42 | "leva": "^0.9.13", 43 | "next": "^11.0.1", 44 | "nightwind": "^1.1.12", 45 | "react": "^17.0.2", 46 | "react-color": "^2.19.3", 47 | "react-dom": "^17.0.2", 48 | "react-hot-toast": "^2.1.0", 49 | "react-markdown": "^6.0.3", 50 | "tailwindcss": "^3.4.0", 51 | "three": "^0.131.3", 52 | "three-stdlib": "^2.4.0" 53 | }, 54 | "devDependencies": { 55 | "@babel/core": "^7.23.6", 56 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11", 57 | "@next/bundle-analyzer": "^14.0.4", 58 | "autoprefixer": "^10.4.16", 59 | "babel-eslint": "^10.1.0", 60 | "eslint-config-next": "^14.0.4", 61 | "eslint-config-prettier": "^9.1.0", 62 | "eslint-config-react-app": "^7.0.1", 63 | "eslint-plugin-import": "^2.29.1", 64 | "eslint-plugin-jsx-a11y": "^6.8.0", 65 | "eslint-plugin-prettier": "^5.1.0", 66 | "eslint-plugin-react": "^7.33.2", 67 | "eslint-plugin-react-hooks": "^4.6.0", 68 | "eslint-plugin-tailwind": "^0.2.1", 69 | "express": "^4.18.2", 70 | "next-sitemap": "^4.2.3", 71 | "postcss": "^8.4.32", 72 | "prettier": "^3.1.1" 73 | }, 74 | "license": "MIT" 75 | } 76 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/bg.jpg -------------------------------------------------------------------------------- /public/draco/draco_decoder_gltf.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/draco/draco_decoder_gltf.wasm -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/favicon.ico -------------------------------------------------------------------------------- /public/icons/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/icons/android-icon-192x192.png -------------------------------------------------------------------------------- /public/icons/android-icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/icons/android-icon-512x512.png -------------------------------------------------------------------------------- /public/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/icons/favicon.ico -------------------------------------------------------------------------------- /public/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | Created by potrace 1.11, written by Peter Selinger 2001-2013 -------------------------------------------------------------------------------- /public/icons/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/icons/share.png -------------------------------------------------------------------------------- /public/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/scores/lighthouse.md: -------------------------------------------------------------------------------- 1 | npm install -g lighthouse-badges && lighthouse-badges --urls http://r3f-next-starter.vercel.app/ -o public/img/scores 2 | -------------------------------------------------------------------------------- /public/img/scores/lighthouse.svg: -------------------------------------------------------------------------------- 1 | lighthouse: 94%lighthouse94% -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "R3F Next Starter", 3 | "short_name": "R3F Next Starter", 4 | "icons": [ 5 | { 6 | "src": "/icons/favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon", 9 | "purpose": "any maskable" 10 | }, 11 | { 12 | "src": "/icons/android-icon-192x192.png", 13 | "sizes": "192x192", 14 | "type": "image/png", 15 | "purpose": "any maskable" 16 | }, 17 | { 18 | "src": "/icons/android-icon-512x512.png", 19 | "sizes": "512x512", 20 | "type": "image/png", 21 | "purpose": "any maskable" 22 | } 23 | ], 24 | "start_url": "/", 25 | "scope": "/", 26 | "display": "standalone", 27 | "theme_color": "#000000", 28 | "background_color": "#ffffff" 29 | } 30 | -------------------------------------------------------------------------------- /public/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/mstile-144x144.png -------------------------------------------------------------------------------- /public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/mstile-150x150.png -------------------------------------------------------------------------------- /public/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/mstile-310x150.png -------------------------------------------------------------------------------- /public/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/mstile-310x310.png -------------------------------------------------------------------------------- /public/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/mstile-70x70.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # * 2 | User-agent: * 3 | Allow: / 4 | 5 | # Host 6 | Host: https://market.pmnd.rs/ 7 | 8 | # Sitemaps 9 | Sitemap: https://market.pmnd.rs/sitemap.xml 10 | -------------------------------------------------------------------------------- /public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/market/d278b47c6453cc2fdcce410ca81a175957dfdb53/public/share.png -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "background_color": "#000000", 3 | "display": "standalone", 4 | "icons": [ 5 | { 6 | "purpose": "any maskable", 7 | "sizes": "192x192", 8 | "src": "/android-chrome-192x192.png", 9 | "type": "image/png" 10 | }, 11 | { 12 | "purpose": "any maskable", 13 | "sizes": "512x512", 14 | "src": "/android-chrome-512x512.png", 15 | "type": "image/png" 16 | } 17 | ], 18 | "name": "Poimandres", 19 | "short_name": "PMNDRS", 20 | "start_url": "https://pmnd.rs", 21 | "theme_color": "#000000" 22 | } 23 | -------------------------------------------------------------------------------- /public/sitemap-0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://market.pmnd.rs2024-02-21T17:57:29.018Zdaily0.7 4 | https://market.pmnd.rs/add-asset2024-02-21T17:57:29.019Zdaily0.7 5 | https://market.pmnd.rs/admin-dashboard2024-02-21T17:57:29.019Zdaily0.7 6 | https://market.pmnd.rs/creator-dashboard2024-02-21T17:57:29.019Zdaily0.7 7 | https://market.pmnd.rs/favorites2024-02-21T17:57:29.019Zdaily0.7 8 | https://market.pmnd.rs/hdris2024-02-21T17:57:29.019Zdaily0.7 9 | https://market.pmnd.rs/login2024-02-21T17:57:29.019Zdaily0.7 10 | https://market.pmnd.rs/materials2024-02-21T17:57:29.019Zdaily0.7 11 | https://market.pmnd.rs/request2024-02-21T17:57:29.019Zdaily0.7 12 | -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://market.pmnd.rs/sitemap-0.xml 4 | -------------------------------------------------------------------------------- /server/dev.js: -------------------------------------------------------------------------------- 1 | // development server 2 | const express = require('express') 3 | const next = require('next') 4 | 5 | const port = parseInt(process.env.PORT, 10) || 3000 6 | const dev = process.env.NODE_ENV !== 'production' 7 | const app = next({ dev }) 8 | const handle = app.getRequestHandler() 9 | 10 | console.log(`\x1b[1m\x1b[33m%s\x1b[0m`, `-------------------------------------`) 11 | console.log( 12 | `\x1b[1m\x1b[33m%s\x1b[0m`, 13 | `⏱️ - The transpilation of threejs is in process, it can take some time.` 14 | ) 15 | console.log(`\x1b[1m\x1b[33m%s\x1b[0m`, `-------------------------------------`) 16 | 17 | app.prepare().then(() => { 18 | const server = express() 19 | 20 | server.all('*', (req, res) => { 21 | return handle(req, res) 22 | }) 23 | 24 | server.listen(port, (err) => { 25 | if (err) throw err 26 | console.log(`✨ Ready on http://localhost:${port} !`) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/components/AddAsset/Step1.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import VerticalSelect from '@/components/Form/RadioGroup' 3 | import Input from '@/components/Form/Input' 4 | import FileDrop from '@/components/Form/FileDrop' 5 | import { slugify } from '@/helpers/slugify' 6 | import useAddAssetStore from '@/helpers/store/addAsset' 7 | import FancySelect from '../Form/FancySelect' 8 | import { supabase } from '@/helpers/initSupabase' 9 | import Button from '../Button' 10 | 11 | const Step1 = ({ onClick }) => { 12 | const assetState = useAddAssetStore() 13 | 14 | useEffect(() => { 15 | useAddAssetStore.setState({ selectedType: assetState.assetTypes[0] }) 16 | // eslint-disable-next-line react-hooks/exhaustive-deps 17 | }, []) 18 | 19 | useEffect(() => { 20 | if (assetState.name) { 21 | const slug = slugify(assetState.name) 22 | 23 | useAddAssetStore.setState({ slug }) 24 | checkAvailability(slug) 25 | } 26 | // eslint-disable-next-line react-hooks/exhaustive-deps 27 | }, [assetState.name]) 28 | 29 | const checkAvailability = async (slug) => { 30 | const type = assetState.selectedType.url.slice(0, -1) 31 | 32 | const { data } = await supabase 33 | .from(assetState.selectedType.url) 34 | .select('id') 35 | .filter('_id', 'eq', `${type}/${slug}`) 36 | 37 | useAddAssetStore.setState({ slugAvailable: !data.length }) 38 | } 39 | 40 | const updateSlug = (slug) => { 41 | useAddAssetStore.setState({ slug }) 42 | if (slug) { 43 | checkAvailability(slug) 44 | } else { 45 | useAddAssetStore.setState({ slugAvailable: false }) 46 | } 47 | } 48 | 49 | const setAssetType = (type) => { 50 | useAddAssetStore.setState({ selectedType: type }) 51 | if (type.url === 'materials') { 52 | useAddAssetStore.setState({ 53 | category: type.name === 'Matcap' ? 'matcaps' : 'PBR', 54 | }) 55 | } 56 | } 57 | return ( 58 |
59 |
60 |
61 |
62 |

63 | Some info about your asset 64 |

65 |
66 | 72 | useAddAssetStore.setState({ name })} 77 | /> 78 | 79 | { 85 | return !assetState.slugAvailable ? ( 86 |

87 | The slug is not available 88 |

89 | ) : null 90 | }} 91 | /> 92 | 93 | useAddAssetStore.setState({ license: s })} 96 | label='License' 97 | options={assetState.licenses} 98 | /> 99 | 100 | {assetState.selectedType.url !== 'materials' && } 101 | 104 | To create a render a resolution of 420*320px is enough and you 105 | can find the starter blender file we used in most of our shots{' '} 106 | 111 | in the repo 112 | 113 | , please feel free to create your own renders. 114 | 115 | } 116 | label=' Upload your thumbnail' 117 | onChange={assetState.uploadFile} 118 | /> 119 |
120 | 130 |
131 |
132 |
133 |
134 | ) 135 | } 136 | 137 | export default Step1 138 | -------------------------------------------------------------------------------- /src/components/AddAsset/Step2.js: -------------------------------------------------------------------------------- 1 | import FileDrop from '@/components/Form/FileDrop' 2 | import useAddAssetStore from '@/helpers/store/addAsset' 3 | import CreatorSelect from './CreatorSelect' 4 | import TeamSelect from './TeamSelect' 5 | import Button from '../Button' 6 | 7 | const Step2 = ({ onClick }) => { 8 | const assetState = useAddAssetStore() 9 | 10 | return ( 11 |
12 |
13 |
14 |
15 |

16 | Let{"'"}s upload your asset 17 |

18 |
19 | {assetState.selectedType.url === 'models' && ( 20 | 27 | )} 28 | 29 | {assetState.selectedType.url === 'hdris' && ( 30 | 37 | )} 38 | {assetState.selectedType.name === 'Matcap' && ( 39 | 44 | )} 45 | 46 | {assetState.selectedType.name === 'PBR Material' && ( 47 | <> 48 |

52 | Upload your maps, only base color is required 53 |

54 | 58 | useAddAssetStore.setState({ 59 | maps: { 60 | ...assetState.maps, 61 | map: file, 62 | }, 63 | }) 64 | } 65 | label='Base Color' 66 | /> 67 | 71 | useAddAssetStore.setState({ 72 | maps: { 73 | ...assetState.maps, 74 | aoMap: file, 75 | }, 76 | }) 77 | } 78 | label='Ambient Occlusion' 79 | /> 80 | 84 | useAddAssetStore.setState({ 85 | maps: { 86 | ...assetState.maps, 87 | displacementMap: file, 88 | }, 89 | }) 90 | } 91 | label='Height Map' 92 | /> 93 | 97 | useAddAssetStore.setState({ 98 | maps: { 99 | ...assetState.maps, 100 | normalMap: file, 101 | }, 102 | }) 103 | } 104 | label='Normal Map' 105 | /> 106 | 110 | useAddAssetStore.setState({ 111 | maps: { 112 | ...assetState.maps, 113 | roughnessMap: file, 114 | }, 115 | }) 116 | } 117 | label='Roughness Map' 118 | /> 119 | 120 | )} 121 | 122 | 123 | 124 | 125 |
126 | 136 |
137 |
138 |
139 |
140 | ) 141 | } 142 | 143 | export default Step2 144 | -------------------------------------------------------------------------------- /src/components/AddAsset/Step3.js: -------------------------------------------------------------------------------- 1 | import useAddAssetStore from '@/helpers/store/addAsset' 2 | import Link from 'next/link' 3 | 4 | const Step3 = () => { 5 | const { loadingText, createdAsset, uploadingError } = useAddAssetStore() 6 | return ( 7 |
8 |

{loadingText}...

9 | {createdAsset && ( 10 | <> 11 | {uploadingError ? ( 12 | 19 | 25 | 26 | ) : ( 27 | 33 | 38 | 39 | )} 40 | {!uploadingError && ( 41 | <> 42 | 43 | You can go check your asset 44 | 45 |

46 | Your asset will be reviewed to be a part of the website, you can 47 | check the status in{' '} 48 | 49 | your creator dashboard 50 | 51 | . 52 |

53 | 54 | )} 55 | 56 | )} 57 |
58 | ) 59 | } 60 | 61 | export default Step3 62 | -------------------------------------------------------------------------------- /src/components/AddAsset/index.js: -------------------------------------------------------------------------------- 1 | import useAddAssetStore from '@/helpers/store/addAsset' 2 | import { useState } from 'react' 3 | import Step1 from './Step1' 4 | import Step2 from './Step2' 5 | import Step3 from './Step3' 6 | 7 | const AddAsset = () => { 8 | const [step, setStep] = useState(1) 9 | const { createAsset } = useAddAssetStore() 10 | 11 | const submit = () => { 12 | createAsset() 13 | setStep(3) 14 | } 15 | 16 | if (step === 1) { 17 | return setStep(2)} /> 18 | } 19 | if (step === 2) { 20 | return 21 | } 22 | 23 | return 24 | } 25 | 26 | export default AddAsset 27 | -------------------------------------------------------------------------------- /src/components/Asset.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | import FavoriteButton from './FavoriteButton' 4 | import Tippy from '@tippyjs/react' 5 | import { getMaterialSize } from '@/helpers/getMaterialSize' 6 | import useStore from '@/helpers/store' 7 | 8 | const Asset = (asset) => { 9 | const user = useStore((store) => store.user) 10 | const type = asset.id.split('/')[0] + 's' 11 | 12 | return ( 13 |
  • 14 | 18 | 19 |
    20 | {!asset.approved && ( 21 |
    22 | Pending Approval 23 |
    24 | )} 25 | 26 |
    27 | {asset.category} 28 | 29 | 30 |
    38 | {asset.unprocessed ? ( 39 |
    40 | 45 | {asset.name} 54 |
    55 | ) : ( 56 | {asset.name} 62 | )} 63 |
    64 |
    65 |
    66 |

    67 | {asset.name} 68 |

    69 | {user && } 70 |
    71 |

    72 | {asset.highPoly && ( 73 | 74 | 81 | 87 | 88 | 89 | )} 90 | {getMaterialSize(asset)} 91 |

    92 | 93 | 94 |
  • 95 | ) 96 | } 97 | 98 | export default Asset 99 | -------------------------------------------------------------------------------- /src/components/Button.js: -------------------------------------------------------------------------------- 1 | const Button = ({ children, className, ...props }) => { 2 | return ( 3 | 9 | ) 10 | } 11 | 12 | export default Button 13 | -------------------------------------------------------------------------------- /src/components/Category.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | 3 | const Category = (category) => { 4 | const thumb = category[category.type][0].thumbnail 5 | return ( 6 |
  • 7 | 11 | 12 |
    13 | {category.name} 18 |

    19 | {category.name} 20 |

    21 |
    22 |
    23 | 24 |
  • 25 | ) 26 | } 27 | 28 | export default Category 29 | -------------------------------------------------------------------------------- /src/components/CreatorPage.js: -------------------------------------------------------------------------------- 1 | import Asset from './Asset' 2 | import Layout from './layout' 3 | 4 | const Title = ({ children }) => ( 5 |

    6 | {children} 7 |

    8 | ) 9 | 10 | const List = ({ children }) => ( 11 |
      12 | {children} 13 |
    14 | ) 15 | 16 | const CreatorPage = ({ title, creator }) => { 17 | return ( 18 | 19 |
    20 | {creator.logo && ( 21 | {creator.name} 26 | )} 27 |
    28 |

    29 | {title} 30 |

    31 | {creator.link && ( 32 | 37 | 44 | 50 | 51 | Visit Website 52 | 53 | )} 54 | {creator.donateLink && ( 55 | 60 | 67 | 73 | 74 | Support the creator 75 | 76 | )} 77 |
    78 |
    79 | {creator.models.length ? ( 80 | <> 81 | Models 82 | 83 | {creator.models.map((model, i) => ( 84 | 85 | ))} 86 | 87 | 88 | ) : null} 89 | {creator.materials.length ? ( 90 | <> 91 | Materials 92 | 93 | {creator.materials.map((material, i) => ( 94 | 95 | ))} 96 | 97 | 98 | ) : null} 99 | {creator.hdris.length ? ( 100 | <> 101 | HDRI{"'"}s 102 | 103 | {creator.hdris.map((hdri, i) => ( 104 | 105 | ))} 106 | 107 | 108 | ) : null} 109 |
    110 | ) 111 | } 112 | 113 | export default CreatorPage 114 | -------------------------------------------------------------------------------- /src/components/FavoriteButton.js: -------------------------------------------------------------------------------- 1 | import useStore from '@/helpers/store' 2 | import Tippy from '@tippyjs/react' 3 | import { useState } from 'react' 4 | 5 | const FavoriteButton = ({ asset }) => { 6 | const [type, name] = asset.id.split('/') 7 | const user = useStore((state) => state.user) 8 | const toggleFavorite = useStore((state) => state.toggleFavorite) 9 | const liked = 10 | user?.profile?.favorites && 11 | user.profile.favorites.includes(`${type}/${name}`) 12 | const [hover, setHover] = useState() 13 | return ( 14 | 15 | 55 | 56 | ) 57 | } 58 | 59 | export default FavoriteButton 60 | -------------------------------------------------------------------------------- /src/components/Form/FancySelect.js: -------------------------------------------------------------------------------- 1 | import useAddAssetStore from '@/helpers/store/addAsset' 2 | import { useEffect, useState } from 'react' 3 | import 'antd/dist/antd.css' 4 | import { Select } from 'antd' 5 | import { PlusIcon } from '@heroicons/react/solid' 6 | import Input from './Input' 7 | import { supabase } from '@/helpers/initSupabase' 8 | 9 | const { Option } = Select 10 | 11 | const FancySelect = () => { 12 | const assetState = useAddAssetStore() 13 | const [newCat, setNewCat] = useState('') 14 | 15 | const getCategories = async () => { 16 | const allCats = await supabase 17 | .from(assetState.selectedType.url || assetState.assetTypes[0].url) 18 | .select('category') 19 | const cats = Array.from(new Set(allCats.data.map((a) => a.category))) 20 | useAddAssetStore.setState({ availableCats: cats }) 21 | } 22 | 23 | useEffect(() => { 24 | getCategories() 25 | // eslint-disable-next-line react-hooks/exhaustive-deps 26 | }, [assetState.selectedType]) 27 | 28 | return ( 29 |
    30 | 36 | setNewCat(value)} /> 45 |
    46 | 57 | 58 | 59 | )} 60 | > 61 | {assetState.availableCats.map((item) => ( 62 | 63 | ))} 64 | 65 | 66 | ) 67 | } 68 | 69 | export default FancySelect 70 | -------------------------------------------------------------------------------- /src/components/Form/FileDrop.js: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useState } from 'react' 2 | import { useDropzone } from 'react-dropzone' 3 | 4 | function FileDrop({ 5 | onChange, 6 | accept = 'image/*', 7 | showPreview = true, 8 | maxSize = 1000000, 9 | label, 10 | description, 11 | }) { 12 | const [preview, setPreview] = useState(null) 13 | const [success, setSuccess] = useState(false) 14 | const { 15 | getRootProps, 16 | getInputProps, 17 | isDragActive, 18 | isDragAccept, 19 | isDragReject, 20 | } = useDropzone({ 21 | accept, 22 | maxFiles: 1, 23 | maxSize, 24 | multiple: false, 25 | onDrop: (acceptedFiles) => { 26 | setSuccess(true) 27 | if (acceptedFiles && acceptedFiles[0]) { 28 | onChange(acceptedFiles[0]) 29 | showPreview && setPreview(URL.createObjectURL(acceptedFiles[0])) 30 | } 31 | }, 32 | }) 33 | 34 | const style = useMemo( 35 | () => ({ 36 | ...(isDragActive 37 | ? { 38 | borderColor: '#2196f3', 39 | } 40 | : {}), 41 | ...(isDragAccept 42 | ? { 43 | borderColor: '#00e676', 44 | } 45 | : {}), 46 | ...(isDragReject 47 | ? { 48 | borderColor: '#ff1744', 49 | } 50 | : {}), 51 | }), 52 | [isDragActive, isDragReject, isDragAccept] 53 | ) 54 | 55 | const thumbs = preview && ( 56 |
    57 |
    58 | 59 |
    60 |
    61 | ) 62 | 63 | return ( 64 |
    65 | 71 | {description && ( 72 | 73 | {description} 74 | 75 | )} 76 |
    83 | 84 |

    85 | {!success 86 | ? "Drag 'n' drop a file here, or click a file" 87 | : 'Thank you'} 88 |

    89 |
    90 | 91 |
    92 | ) 93 | } 94 | 95 | export default FileDrop 96 | -------------------------------------------------------------------------------- /src/components/Form/Input.js: -------------------------------------------------------------------------------- 1 | const Input = ({ label, id, value, onChange, Error }) => { 2 | return ( 3 |
    4 | {label && ( 5 | 8 | )} 9 |
    10 | onChange(e.target.value)} 17 | /> 18 |
    19 | {Error && } 20 |
    21 | ) 22 | } 23 | 24 | export default Input 25 | -------------------------------------------------------------------------------- /src/components/Form/RadioGroup.js: -------------------------------------------------------------------------------- 1 | import classNames from '@/helpers/classNames' 2 | import { RadioGroup } from '@headlessui/react' 3 | 4 | const VerticalSelect = ({ options, value, onChange, label }) => { 5 | return ( 6 | 7 | 8 | {label} 9 | 10 | 11 |
    12 | {options.map((setting, settingIdx) => ( 13 | 18 | classNames( 19 | settingIdx === 0 ? 'rounded-tl-md rounded-tr-md' : '', 20 | settingIdx === options.length - 1 21 | ? 'rounded-bl-md rounded-br-md' 22 | : '', 23 | checked 24 | ? 'dark:bg-gray-600 dark:border-gray-800 bg-blue-50 border-blue-200 z-10' 25 | : 'border-gray-200', 26 | 'relative border p-4 flex cursor-pointer focus:outline-none', 27 | setting.disabled ? 'opacity-30 cursor-default' : '' 28 | ) 29 | } 30 | > 31 | {({ active, checked }) => ( 32 | <> 33 | 69 | ))} 70 |
    71 |
    72 | ) 73 | } 74 | 75 | export default VerticalSelect 76 | -------------------------------------------------------------------------------- /src/components/Logo.js: -------------------------------------------------------------------------------- 1 | export default function Logo({ color = 'black', darkMode, ...props }) { 2 | const fill = darkMode ? 'white' : 'black' 3 | return ( 4 | 12 | 16 | 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /src/components/NextAndPrev.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | 3 | const NextAndPrev = (asset) => { 4 | return asset.prev || asset.next ? ( 5 |
    6 | {asset.prev && ( 7 |
    8 |
    9 | Previous 10 |
    11 |
    12 | 13 | {asset.prev.name} 14 | 15 |
    16 |
    17 | )} 18 | 19 | {asset.next && ( 20 |
    21 |
    22 | Next 23 |
    24 |
    25 | 26 | {asset.next.name} 27 | 28 |
    29 |
    30 | )} 31 |
    32 | ) : null 33 | } 34 | 35 | export default NextAndPrev 36 | -------------------------------------------------------------------------------- /src/components/RequestAsset/AssetRequestForm.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | const AssetRequestForm = ({ onSubmit }) => { 4 | const [request, setRequest] = useState('') 5 | const [description, setDescription] = useState('') 6 | const [category, setCategory] = useState('') 7 | return ( 8 |
    { 10 | e.preventDefault() 11 | onSubmit({ request, description, category }) 12 | }} 13 | > 14 |
    15 | 21 |
    22 | setRequest(e.target.value)} 25 | type='text' 26 | name='request' 27 | required 28 | id='request' 29 | className='block w-full text-gray-800 bg-white border-gray-300 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md' 30 | placeholder='A spaceship' 31 | /> 32 |
    33 |
    34 | 40 |
    41 | 49 |
    50 |
    51 |
    52 |
    53 | 59 | 71 |
    72 | 78 |
    79 | ) 80 | } 81 | 82 | export default AssetRequestForm 83 | -------------------------------------------------------------------------------- /src/components/RequestAsset/Request.js: -------------------------------------------------------------------------------- 1 | import { supabase } from '@/helpers/initSupabase' 2 | import useStore from '@/helpers/store/' 3 | import useRequestsStore from '@/helpers/store/requests' 4 | import Tippy from '@tippyjs/react' 5 | import Link from 'next/link' 6 | import Button from '../Button' 7 | 8 | const Request = (request) => { 9 | const user = useStore((s) => s.user) 10 | const vote = useRequestsStore((s) => s.vote) 11 | 12 | const closeRequest = async () => { 13 | var id = window.prompt('What is the id?', '') 14 | if (id) { 15 | await supabase 16 | .from('requests') 17 | .update({ closed: true, closedBy: id }) 18 | .match({ id: request.id }) 19 | } 20 | } 21 | 22 | return ( 23 |
  • 24 |
    25 | {request.closed && ( 26 | 27 | 31 | closed by {request.closedBy.split('/')[1]} 32 | 33 | 34 | )} 35 |
    36 |
    39 |
    40 | 48 |
    49 | 69 |
    70 |
    71 |

    72 | {request.upvotes.length} 73 |

    74 |
    75 |
    76 |

    77 | {request.request} 78 |

    79 |

    80 | {request.description} 81 |

    82 |
    83 |
    84 |
    85 |

    90 | {user?.profile && user.profile.admin && !request.closed && ( 91 | 94 | )} 95 | 96 | {request.category || 'Model'} 97 | 98 | {new Date(request.created).toLocaleString('en-US', { 99 | month: 'long', 100 | day: '2-digit', 101 | year: 'numeric', 102 | })} 103 |

    104 |
    105 |
  • 106 | ) 107 | } 108 | 109 | export default Request 110 | -------------------------------------------------------------------------------- /src/components/Search.js: -------------------------------------------------------------------------------- 1 | import Tippy from '@tippyjs/react' 2 | import 'tippy.js/dist/tippy.css' // optional 3 | import { SearchIcon } from '@heroicons/react/solid' 4 | 5 | const Search = ({ 6 | search, 7 | setSearch, 8 | onOrderChange, 9 | onOrderDirectionChange, 10 | assetName, 11 | orderDirection, 12 | }) => { 13 | const placeholder = `Search for ${assetName}` 14 | return ( 15 |
    16 | 19 |
    20 | 29 |
    30 |
    32 |
    33 |
    34 | 37 | 49 | {orderDirection === 'desc' ? ( 50 | 51 | 72 | 73 | ) : ( 74 | 75 | 95 | 96 | )} 97 |
    98 |
    99 | ) 100 | } 101 | 102 | export default Search 103 | -------------------------------------------------------------------------------- /src/components/Tabs.js: -------------------------------------------------------------------------------- 1 | import classNames from '@/helpers/classNames' 2 | 3 | const Tabs = ({ tabs }) => { 4 | return ( 5 |
    6 |
    7 | 10 | 25 |
    26 |
    27 |
    28 | 45 |
    46 |
    47 |
    48 | ) 49 | } 50 | 51 | export default Tabs 52 | -------------------------------------------------------------------------------- /src/components/canvas/Editor/ColorPicker.js: -------------------------------------------------------------------------------- 1 | import { ChromePicker } from 'react-color' 2 | 3 | const ColorPicker = ({ property, value, material, setMaterialsEditor }) => { 4 | const onChange = (val) => { 5 | if (val.hex) { 6 | setMaterialsEditor((m) => ({ 7 | ...m, 8 | [material]: { 9 | ...m[material], 10 | [property]: { 11 | ...m[material][property], 12 | value: val.hex, 13 | }, 14 | }, 15 | })) 16 | } 17 | } 18 | return ( 19 | <> 20 | 21 | {property} 22 | 23 | 29 | 30 | ) 31 | } 32 | 33 | export default ColorPicker 34 | -------------------------------------------------------------------------------- /src/components/canvas/Editor/EditTools.js: -------------------------------------------------------------------------------- 1 | import Button from '@/components/Button' 2 | import classNames from '@/helpers/classNames' 3 | import { useState } from 'react' 4 | 5 | import { saveAs } from 'file-saver' 6 | import { GLTFExporter } from 'three-stdlib' 7 | import RangeSlider from './RangeSlider' 8 | import ColorPicker from './ColorPicker' 9 | import { useUpdateScene } from './useUpdateScene' 10 | import Select from './Select' 11 | 12 | const EditTools = ({ materialsEditor, scene, setMaterialsEditor }) => { 13 | const [activeTab, setActiveTab] = useState() 14 | const exporter = new GLTFExporter() 15 | useUpdateScene({ materialsEditor, scene }) 16 | 17 | const save = () => { 18 | exporter.parse(scene, function (gltf) { 19 | var blob = new Blob([JSON.stringify(gltf)], { 20 | type: 'application/gltf; charset=us-ascii', 21 | }) 22 | saveAs(blob, 'model.gltf') 23 | }) 24 | } 25 | 26 | return ( 27 |
    28 | 49 |
    50 | 67 |
    68 | {activeTab &&

    Edit

    } 69 | {Object.keys(materialsEditor).map((material) => ( 70 |
    74 | {Object.keys(materialsEditor[material]).map((property, i) => { 75 | const val = materialsEditor[material][property] 76 | const props = { 77 | material, 78 | property, 79 | setMaterialsEditor, 80 | value: val.value, 81 | key: i, 82 | } 83 | 84 | if (val.type === 'range') { 85 | return ( 86 | 93 | ) 94 | } 95 | if (val.type === 'color') { 96 | return 97 | } 98 | 99 | if (val.type === 'select') { 100 | return { 16 | setMaterialsEditor((materials) => ({ 17 | ...materials, 18 | [material]: { 19 | ...materials[material], 20 | [property]: { 21 | ...materials[material][property], 22 | value: e.target.value, 23 | }, 24 | }, 25 | })) 26 | }} 27 | > 28 | {options.map((option) => ( 29 | 30 | ))} 31 | 32 |
    33 | ) 34 | } 35 | 36 | export default Select 37 | -------------------------------------------------------------------------------- /src/components/canvas/Editor/index.js: -------------------------------------------------------------------------------- 1 | import { Canvas } from '@react-three/fiber' 2 | import { Html, Loader, OrbitControls, Stage, useGLTF } from '@react-three/drei' 3 | import { Suspense, useRef, useLayoutEffect, useEffect } from 'react' 4 | import { useControls } from 'leva' 5 | import { lightControls, defaultControls } from '../controls' 6 | import { useState } from 'react' 7 | import EditTools from './EditTools' 8 | import Head from 'next/head' 9 | 10 | function rgbToHex({ r, g, b }) { 11 | return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1) 12 | } 13 | 14 | const Model = ({ file, portalRef }) => { 15 | const [materialsEditor, setMaterialsEditor] = useState({}) 16 | const { scene } = useGLTF(file) 17 | 18 | useEffect(() => { 19 | scene.traverse((object) => { 20 | if (object.material && object.isMesh) { 21 | setMaterialsEditor((oldMaterials) => ({ 22 | ...oldMaterials, 23 | [object.material.name]: { 24 | color: { 25 | type: 'color', 26 | value: rgbToHex({ 27 | r: parseInt(object.material.color.r * 255), 28 | g: parseInt(object.material.color.g * 255), 29 | b: parseInt(object.material.color.b * 255), 30 | }), 31 | }, 32 | roughness: { 33 | type: 'range', 34 | value: object.material.roughness, 35 | }, 36 | 37 | metalness: { 38 | type: 'range', 39 | value: object.material.metalness, 40 | }, 41 | opacity: { 42 | type: 'range', 43 | value: object.material.opacity, 44 | }, 45 | side: { 46 | type: 'select', 47 | value: object.material.side, 48 | options: ['FrontSide', 'BackSide', 'DoubleSide'], 49 | }, 50 | emissive: { 51 | type: 'color', 52 | value: rgbToHex({ 53 | r: parseInt(object.material.emissive.r * 255), 54 | g: parseInt(object.material.emissive.g * 255), 55 | b: parseInt(object.material.emissive.b * 255), 56 | }), 57 | }, 58 | emissiveIntensity: { 59 | type: 'range', 60 | value: object.material.emissiveIntensity, 61 | }, 62 | }, 63 | })) 64 | } 65 | }) 66 | }, [scene]) 67 | 68 | useLayoutEffect(() => { 69 | void scene.traverse( 70 | (obj) => obj.isMesh && (obj.castShadow = obj.receiveShadow = true) 71 | ) 72 | }, [scene]) 73 | 74 | return ( 75 | <> 76 | 77 | 78 | 83 | 84 | 85 | ) 86 | } 87 | 88 | export default function ModelComponent(props) { 89 | const portal = useRef() 90 | const ref = useRef() 91 | const controls = useControls({ 92 | ...defaultControls, 93 | ...lightControls, 94 | }) 95 | 96 | return ( 97 |
    98 | 99 | Material Editor - market 100 | 101 |
    102 | 103 | 110 | 111 | 112 | 120 | 121 | 122 | 123 | 124 | 125 |
    126 | ) 127 | } 128 | -------------------------------------------------------------------------------- /src/components/canvas/Editor/useUpdateScene.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { Color } from 'three' 3 | import * as THREE from 'three' 4 | 5 | export const useUpdateScene = ({ materialsEditor, scene }) => { 6 | useEffect(() => { 7 | if (Object.keys(materialsEditor).length) { 8 | scene.traverse((obj) => { 9 | if (obj.isMesh && obj.material && materialsEditor[obj.material.name]) { 10 | const currentMaterial = materialsEditor[obj.material.name] 11 | const color = new Color(currentMaterial.color.value) 12 | obj.material.color = color 13 | obj.material.emissive = new Color(currentMaterial.emissive.value) 14 | obj.material.roughness = currentMaterial.roughness.value 15 | obj.material.metalness = currentMaterial.metalness.value 16 | obj.material.transparent = currentMaterial.opacity !== 1 17 | obj.material.side = THREE[currentMaterial.side.value] 18 | obj.material.opacity = currentMaterial.opacity.value 19 | obj.material.emissiveIntensity = 20 | currentMaterial.emissiveIntensity.value 21 | } 22 | }) 23 | } 24 | }, [materialsEditor, scene]) 25 | } 26 | -------------------------------------------------------------------------------- /src/components/canvas/HDRI.js: -------------------------------------------------------------------------------- 1 | import { Canvas } from '@react-three/fiber' 2 | import { OrbitControls, Environment } from '@react-three/drei' 3 | import React, { Suspense, useRef } from 'react' 4 | import { useControls } from 'leva' 5 | import { defaultControls } from './controls' 6 | 7 | export default function MaterialComponent(props) { 8 | const ref = useRef() 9 | const controls = useControls(defaultControls) 10 | return ( 11 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /src/components/canvas/Material.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import { Canvas } from '@react-three/fiber' 3 | import { 4 | OrbitControls, 5 | useGLTF, 6 | Stage, 7 | useTexture, 8 | Sphere, 9 | } from '@react-three/drei' 10 | import { Suspense, useRef } from 'react' 11 | import { useControls } from 'leva' 12 | import { defaultControls, lightControls } from './controls' 13 | import useStore from '@/helpers/store' 14 | import colors from 'tailwindcss/colors' 15 | 16 | const PBR = ({ maps, displacementScale }) => { 17 | const texturesLoader = useTexture([...Object.values(maps)]) 18 | const textures = Object.keys(maps).reduce((acc, curr, i) => { 19 | acc[curr] = texturesLoader[i] 20 | 21 | return acc 22 | }, {}) 23 | return ( 24 | 25 | 31 | 32 | ) 33 | } 34 | 35 | const MatCap = ({ file }) => { 36 | const [matcap] = useTexture([file]) 37 | const group = useRef() 38 | const { nodes } = useGLTF( 39 | 'https://vazxmixjsiawhamofees.supabase.co/storage/v1/object/public/models/suzanne-high-poly/model.gltf' 40 | ) 41 | return ( 42 | 43 | 49 | 50 | 51 | 52 | ) 53 | } 54 | 55 | const Material = ({ file, category, maps }) => { 56 | const ref = useRef() 57 | const otherControls = 58 | category === 'matcaps' 59 | ? {} 60 | : { 61 | displacement: { 62 | value: 0.02, 63 | min: 0, 64 | max: 1, 65 | step: 0.05, 66 | label: 'displacement scale', 67 | }, 68 | ...lightControls, 69 | } 70 | const controls = useControls({ 71 | ...defaultControls, 72 | ...otherControls, 73 | }) 74 | return ( 75 | <> 76 | 84 | {category === 'matcaps' ? ( 85 | 86 | ) : ( 87 | 88 | )} 89 | 90 | 91 | 92 | ) 93 | } 94 | 95 | export default function MaterialComponent(props) { 96 | const { darkMode } = useStore() 97 | return ( 98 | 104 | 108 | 109 | 110 | 111 | 112 | 113 | ) 114 | } 115 | 116 | useGLTF.preload('/suzanne.gltf') 117 | -------------------------------------------------------------------------------- /src/components/canvas/Model.js: -------------------------------------------------------------------------------- 1 | import { Suspense, useRef, useLayoutEffect, useEffect, useMemo } from 'react' 2 | import { Canvas, useFrame } from '@react-three/fiber' 3 | import { OrbitControls, Stage, useGLTF } from '@react-three/drei' 4 | import { useControls } from 'leva' 5 | import { AnimationMixer } from 'three' 6 | import colors from 'tailwindcss/colors' 7 | import useStore from '@/helpers/store' 8 | import { lightControls, defaultControls } from './controls' 9 | 10 | const Model = ({ file }) => { 11 | const ref = useRef() 12 | const controls = useControls({ 13 | ...defaultControls, 14 | wireframe: false, 15 | ...lightControls, 16 | }) 17 | 18 | const { animations, scene } = useGLTF(file) 19 | 20 | const { animationClips, defaultAnimationsControls, mixer } = useMemo(() => { 21 | const mixer = new AnimationMixer(scene) 22 | const animationClips = [] 23 | let defaultAnimationsControls = {} 24 | 25 | for (let a of animations) { 26 | let action = mixer.clipAction(a) 27 | animationClips[a.name] = action 28 | defaultAnimationsControls[a.name] = false 29 | } 30 | 31 | return { defaultAnimationsControls, animationClips, mixer } 32 | }, [animations, scene]) 33 | 34 | const [animationsControls, setAnimationsControls] = useControls( 35 | 'Animations', 36 | () => defaultAnimationsControls 37 | ) 38 | 39 | useEffect(() => { 40 | for (let clipName in animationClips) { 41 | if (animationsControls[clipName]) { 42 | animationClips[clipName].play() 43 | } else { 44 | animationClips[clipName].stop() 45 | } 46 | } 47 | }, [animationClips, animationsControls, scene]) 48 | 49 | useLayoutEffect(() => { 50 | void scene.traverse( 51 | (obj) => obj.isMesh && (obj.castShadow = obj.receiveShadow = true) 52 | ) 53 | }, [scene]) 54 | 55 | useEffect(() => { 56 | scene.traverse((obj) => { 57 | if (obj.isMesh && obj.material) { 58 | obj.material.wireframe = controls.wireframe 59 | } 60 | }) 61 | 62 | if (animations.length) { 63 | defaultAnimationsControls[animations[0].name] = true 64 | } 65 | setAnimationsControls(defaultAnimationsControls) 66 | }, [ 67 | scene, 68 | controls.wireframe, 69 | animations, 70 | defaultAnimationsControls, 71 | setAnimationsControls, 72 | ]) 73 | 74 | useFrame((state, delta) => { 75 | mixer.update(delta) 76 | }) 77 | 78 | return ( 79 | <> 80 | 88 | 89 | 90 | 91 | 92 | ) 93 | } 94 | 95 | export default function ModelComponent(props) { 96 | const { darkMode } = useStore() 97 | return ( 98 | 104 | 108 | 109 | 110 | 111 | 112 | ) 113 | } 114 | -------------------------------------------------------------------------------- /src/components/canvas/controls.js: -------------------------------------------------------------------------------- 1 | export const defaultControls = { 2 | autoRotate: true, 3 | contactShadow: true, 4 | } 5 | 6 | export const lightControls = { 7 | intensity: { 8 | value: 0.1, 9 | min: 0, 10 | max: 2, 11 | step: 0.1, 12 | label: 'light intensity', 13 | }, 14 | preset: { 15 | value: 'rembrandt', 16 | options: ['rembrandt', 'portrait', 'upfront', 'soft'], 17 | }, 18 | environment: { 19 | value: 'city', 20 | options: [ 21 | '', 22 | 'sunset', 23 | 'dawn', 24 | 'night', 25 | 'warehouse', 26 | 'forest', 27 | 'apartment', 28 | 'studio', 29 | 'city', 30 | 'park', 31 | 'lobby', 32 | ], 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /src/components/comments/Textarea.js: -------------------------------------------------------------------------------- 1 | const Textarea = ({ value, onChange }) => { 2 | return ( 3 |
    4 | 7 |