WIP #36
53
README.md
53
README.md
@@ -1,24 +1,59 @@
|
|||||||
# Redwood
|
# CadHub
|
||||||
|
|
||||||
> **WARNING:** RedwoodJS software has not reached a stable version 1.0 and should not be considered suitable for production use. In the "make it work; make it right; make it fast" paradigm, Redwood is in the later stages of the "make it work" phase.
|
CadHub aims to be a community website for javascript based code-cad. Currently trying to integrate [cascadeStudio](https://zalo.github.io/CascadeStudio/), but if successful plan to also integrate [JSCAD](https://openjscad.org/).
|
||||||
|
OpenScad has proven code-cad a much loved formate for cad-modeling. Joining code-cad to a mature language like javascript that has a package manager (npm) plus a community hub for sharing cad models like CadHub, we're going to build a thriving community.
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/Irev-Dev/repo-images/main/images/Parts%20Page.jpg">
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/Irev-Dev/repo-images/main/images/Part%20IDE%20-%20export%20expand%20state.jpg">
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
- [Tutorial](https://redwoodjs.com/tutorial/welcome-to-redwood): getting started and complete overview guide.
|
|
||||||
- [Docs](https://redwoodjs.com/docs/introduction): using the Redwood Router, handling assets and files, list of command-line tools, and more.
|
|
||||||
- [Redwood Community](https://community.redwoodjs.com): get help, share tips and tricks, and collaborate on everything about RedwoodJS.
|
|
||||||
|
|
||||||
### Setup
|
Because we're integrating cascadeStudio, this is done some what crudely for the time being, so you'll need to clone the repo with submodules.
|
||||||
|
|
||||||
We use Yarn as our package manager. To get the dependencies installed, just do this in the root directory:
|
```terminal
|
||||||
|
git clone --recurse-submodules -j8 git@github.com:Irev-Dev/cadhub.git
|
||||||
|
# or
|
||||||
|
git clone --recurse-submodules -j8 https://github.com/Irev-Dev/cadhub.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Install dependencies
|
||||||
```terminal
|
```terminal
|
||||||
yarn install
|
yarn install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Fire it up
|
Initialise the db
|
||||||
|
``` terminal
|
||||||
|
yarn rw db up
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fire up dev
|
||||||
```terminal
|
```terminal
|
||||||
yarn redwood dev
|
yarn rw dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Your browser should open automatically to `http://localhost:8910` to see the web app. Lambda functions run on `http://localhost:8911` and are also proxied to `http://localhost:8910/.redwood/functions/*`.
|
Your browser should open automatically to `http://localhost:8910` to see the web app. Lambda functions run on `http://localhost:8911` and are also proxied to `http://localhost:8910/.redwood/functions/*`.
|
||||||
|
|
||||||
|
You may need to register a account depending on what issue you are trying to tackle, This can be done by clicking the login button on the top right. This will open up netlify's idenitiy modal asking for the websites url, since it will notice you developing locally. Enter `https://cadhub.xyz/` than use you email, verify your email and you should be set.
|
||||||
|
(some routes are protected, but permissions is a big area that needs a lot of work in the near future, so it's in a very incomplete state atm)
|
||||||
|
|
||||||
|
### Note:
|
||||||
|
We're using [RedwoodJS](https://redwoodjs.com/), this is perhaps unwise since they haven't reached 1.0 yet, however with their aim to release 1.0 by the end of the year, it shouldn't be too difficult to port changes over the coming months.
|
||||||
|
If you not familiar with Redwood, never fear the main bit of tech it uses is React, Graphql(apollo) and serverless/lamdas, depending on what part of the app you want to help with, so long as you know you way around these bits of tech you should be fine with some light referencing of the RedWood docs
|
||||||
|
|
||||||
|
### Extra Redwood docs, i.e. getting familiar with the frame work.
|
||||||
|
- [Tutorial](https://redwoodjs.com/tutorial/welcome-to-redwood): getting started and complete overview guide.
|
||||||
|
- [Docs](https://redwoodjs.com/docs/introduction): using the Redwood Router, handling assets and files, list of command-line tools, and more.
|
||||||
|
- [Redwood Community](https://community.redwoodjs.com): get help, share tips and tricks, and collaborate on everything about RedwoodJS.
|
||||||
|
|
||||||
|
## Styles
|
||||||
|
|
||||||
|
We're using tailwind utility classes so please try and use them as much as possible. Again if you not familiar, the [tailwind search](https://tailwindcss.com/) is fantastic, so searching for the css property you want to use will lead you to the correct class 99% of the time.
|
||||||
|
|
||||||
|
## Designs
|
||||||
|
|
||||||
|
In progress, though can be [seen on Figma](https://www.figma.com/file/VUh53RdncjZ7NuFYj0RGB9/CadHub?node-id=0%3A1)
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/Irev-Dev/repo-images/main/images/Part%20Page(1).jpg">
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/Irev-Dev/repo-images/main/images/User%20Page%20Edit.jpg">
|
||||||
|
|||||||
@@ -1,9 +1,52 @@
|
|||||||
import { createUser } from 'src/services/users/users.js'
|
import { createUserInsecure } from 'src/services/users/users.js'
|
||||||
|
|
||||||
export const handler = async (req, _context) => {
|
export const handler = async (req, _context) => {
|
||||||
const body = JSON.parse(req.body)
|
const body = JSON.parse(req.body)
|
||||||
console.log(body)
|
console.log(body)
|
||||||
console.log(_context)
|
console.log(_context)
|
||||||
|
// DUMP FROM THE LOGS ABOVE
|
||||||
|
/*
|
||||||
|
5:09:30 AM: 2020-10-19T18:09:30.011Z 9da27e24-b6ec-404e-8e7d-25b5d323b67a INFO {
|
||||||
|
event: 'signup',
|
||||||
|
instance_id: '403b7d63-17f9-48f1-a85f-3d6b41c7dad1',
|
||||||
|
user: {
|
||||||
|
id: '641222ee-3e61-4253-8c11-9f764779bcc5',
|
||||||
|
aud: '',
|
||||||
|
role: '',
|
||||||
|
email: 'k.hutten@protonmail.ch',
|
||||||
|
confirmation_sent_at: '2020-10-19T18:09:01Z',
|
||||||
|
app_metadata: { provider: 'email' },
|
||||||
|
user_metadata: { full_name: 'sick_dog' },
|
||||||
|
created_at: '2020-10-19T18:09:01Z',
|
||||||
|
updated_at: '2020-10-19T18:09:01Z'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
5:09:30 AM: 2020-10-19T18:09:30.011Z 9da27e24-b6ec-404e-8e7d-25b5d323b67a INFO {
|
||||||
|
callbackWaitsForEmptyEventLoop: [Getter/Setter],
|
||||||
|
succeed: [Function],
|
||||||
|
fail: [Function],
|
||||||
|
done: [Function],
|
||||||
|
functionVersion: '$LATEST',
|
||||||
|
functionName: 'ba7eb4948d1313283ebb91472c689d38444f07ae2f4278da925d3ce7f1d94e3c',
|
||||||
|
memoryLimitInMB: '1024',
|
||||||
|
logGroupName: '/aws/lambda/ba7eb4948d1313283ebb91472c689d38444f07ae2f4278da925d3ce7f1d94e3c',
|
||||||
|
logStreamName: '2020/10/19/[$LATEST]af6ff2c067da44268b4a0c9d1e4ca1ea',
|
||||||
|
clientContext: {
|
||||||
|
custom: {
|
||||||
|
netlify: 'eyJpZGVudGl0eSI6eyJ1cmwiOiJodHRwczovL2FuZ3J5LWRpamtzdHJhLTAzMWExMC5uZXRsaWZ5LmFwcC8ubmV0bGlmeS9pZGVudGl0eSIsInRva2VuIjoiZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SmxlSEFpT2pFMk1ETXhNekV3TWprc0luTjFZaUk2SWpBaWZRLk54Q0hmb0I2aDRpc0V6NnpJREhWbThLTU5hcEZrb3g0dTFXS2dTemhzUncifSwic2l0ZV91cmwiOiJodHRwczovL2FuZ3J5LWRpamtzdHJhLTAzMWExMC5uZXRsaWZ5LmFwcCJ9'
|
||||||
|
},
|
||||||
|
identity: {
|
||||||
|
url: 'https://angry-dijkstra-031a10.netlify.app/.netlify/identity',
|
||||||
|
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDMxMzEwMjksInN1YiI6IjAifQ.NxCHfoB6h4isEz6zIDHVm8KMNapFkox4u1WKgSzhsRw'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
identity: undefined,
|
||||||
|
invokedFunctionArn: 'arn:aws:lambda:us-east-1:012533533302:function:ba7eb4948d1313283ebb91472c689d38444f07ae2f4278da925d3ce7f1d94e3c',
|
||||||
|
awsRequestId: '9da27e24-b6ec-404e-8e7d-25b5d323b67a',
|
||||||
|
getRemainingTimeInMillis: [Function: getRemainingTimeInMillis]
|
||||||
|
}
|
||||||
|
5:09:30 AM: Duration: 5.78 ms Memory Usage: 69 MB Init Duration: 199.35 ms
|
||||||
|
*/
|
||||||
|
|
||||||
const eventType = body.event
|
const eventType = body.event
|
||||||
const user = body.user
|
const user = body.user
|
||||||
@@ -13,15 +56,17 @@ export const handler = async (req, _context) => {
|
|||||||
|
|
||||||
if (eventType === 'signup') {
|
if (eventType === 'signup') {
|
||||||
roles.push('user')
|
roles.push('user')
|
||||||
const hi = {
|
// const hi = {
|
||||||
email: 'kurt.hutten@gmail.com',
|
// email: 'kurt.hutten@gmail.com',
|
||||||
image: '',
|
// image: '',
|
||||||
bio: ''
|
// bio: ''
|
||||||
}
|
// }
|
||||||
const input = {
|
const input = {
|
||||||
email,
|
email,
|
||||||
|
bio: 'default bio'
|
||||||
|
// full_name: user.user_metadata.full_name
|
||||||
}
|
}
|
||||||
createUser({input})
|
await createUserInsecure({input})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ export const user = ({ id }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const createUser = ({ input }) => {
|
export const createUser = ({ input }) => {
|
||||||
console.log(input)
|
|
||||||
console.log(JSON.stringify(input))
|
|
||||||
requireAuth({ role: 'admin' })
|
requireAuth({ role: 'admin' })
|
||||||
|
return createUserInsecure({input})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createUserInsecure = ({ input }) => {
|
||||||
return db.user.create({
|
return db.user.create({
|
||||||
data: input,
|
data: input,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"@redwoodjs/forms": "^0.19.2",
|
"@redwoodjs/forms": "^0.19.2",
|
||||||
"@redwoodjs/router": "^0.19.2",
|
"@redwoodjs/router": "^0.19.2",
|
||||||
"@redwoodjs/web": "^0.19.2",
|
"@redwoodjs/web": "^0.19.2",
|
||||||
|
"cloudinary-react": "^1.6.7",
|
||||||
"controlkit": "^0.1.9",
|
"controlkit": "^0.1.9",
|
||||||
"golden-layout": "^1.5.9",
|
"golden-layout": "^1.5.9",
|
||||||
"jquery": "^3.5.1",
|
"jquery": "^3.5.1",
|
||||||
@@ -28,6 +29,8 @@
|
|||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
|
"react-dropzone": "^11.2.1",
|
||||||
|
"react-image-crop": "^8.6.6",
|
||||||
"rich-markdown-editor": "^11.0.2",
|
"rich-markdown-editor": "^11.0.2",
|
||||||
"styled-components": "^5.2.0",
|
"styled-components": "^5.2.0",
|
||||||
"three": "^0.118.3"
|
"three": "^0.118.3"
|
||||||
|
|||||||
119
web/src/components/PartForm/ImageUploader.js
Normal file
119
web/src/components/PartForm/ImageUploader.js
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import React, { useCallback, useState } from "react";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
import Button from "@material-ui/core/Button";
|
||||||
|
import axios from 'axios'
|
||||||
|
import ReactCrop from 'react-image-crop'
|
||||||
|
import { Dialog } from '@material-ui/core'
|
||||||
|
import { Image as CloudinaryImage } from 'cloudinary-react'
|
||||||
|
import 'react-image-crop/dist/ReactCrop.css'
|
||||||
|
import Svg from 'src/components/Svg/Svg.js'
|
||||||
|
|
||||||
|
const CLOUDINARY_UPLOAD_PRESET = "CadHub_project_images";
|
||||||
|
const CLOUDINARY_UPLOAD_URL = "https://api.cloudinary.com/v1_1/irevdev/upload/?custom_coordinates=10,10,20,20";
|
||||||
|
|
||||||
|
export default function ImageUploader({ onImageUpload, imageUrl }) {
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||||
|
const [file, setFile] = useState()
|
||||||
|
const [cloudinaryId, setCloudinaryId] = useState(imageUrl)
|
||||||
|
const [imageObj, setImageObj] = useState()
|
||||||
|
const [crop, setCrop] = useState({
|
||||||
|
aspect: 16 / 9,
|
||||||
|
unit: '%',
|
||||||
|
width: 100,
|
||||||
|
});
|
||||||
|
async function handleImageUpload() {
|
||||||
|
const croppedFile = await getCroppedImg(imageObj, crop, 'avatar')
|
||||||
|
console.log(croppedFile)
|
||||||
|
const imageData = new FormData();
|
||||||
|
imageData.append('upload_preset', CLOUDINARY_UPLOAD_PRESET);
|
||||||
|
imageData.append('file', croppedFile);
|
||||||
|
let upload = axios.post(CLOUDINARY_UPLOAD_URL, imageData)
|
||||||
|
try {
|
||||||
|
const { data } = await upload
|
||||||
|
if (data && data.public_id !== "") {
|
||||||
|
onImageUpload({cloudinaryPublicId: data.public_id})
|
||||||
|
setCloudinaryId(data.public_id)
|
||||||
|
setIsModalOpen(false)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('ERROR', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Drag and Drop
|
||||||
|
const onDrop = useCallback(acceptedFiles => {
|
||||||
|
setIsModalOpen(true)
|
||||||
|
const fileReader = new FileReader()
|
||||||
|
fileReader.onload = () => {
|
||||||
|
setFile(fileReader.result)
|
||||||
|
}
|
||||||
|
fileReader.readAsDataURL(acceptedFiles[0])
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
|
||||||
|
return (
|
||||||
|
<div className="m-8">
|
||||||
|
<div className="w-full relative" {...getRootProps()}>
|
||||||
|
{cloudinaryId && <button className="absolute z-10 w-full inset-0 bg-indigo-900 opacity-50 flex justify-center items-center">
|
||||||
|
<Svg name="pencil" strokeWidth={2} className="text-gray-300 h-48 w-48" />
|
||||||
|
</button>}
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
{cloudinaryId && <div className="relative">
|
||||||
|
<CloudinaryImage
|
||||||
|
className="object-cover w-full rounded shadow"
|
||||||
|
cloudName="irevdev"
|
||||||
|
publicId={cloudinaryId}
|
||||||
|
width="600"
|
||||||
|
crop="scale"
|
||||||
|
/>
|
||||||
|
</div>}
|
||||||
|
{!cloudinaryId && <button className="absolute inset-0"></button>}
|
||||||
|
{!cloudinaryId && <div className="mt-3 text-indigo-500 border-dashed border border-indigo-500 py-8 text-center rounded-lg w-full">
|
||||||
|
Drop files here ...
|
||||||
|
or <span className="group flex w-full items-center justify-center py-4">
|
||||||
|
<span className="bg-indigo-500 shadow rounded text-gray-200 cursor-pointer p-2 hover:shadow-lg transform hover:-translate-y-1 transition-all duration-150">upload</span>
|
||||||
|
</span>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
<Dialog
|
||||||
|
open={isModalOpen}
|
||||||
|
onClose={() => setIsModalOpen(false)}
|
||||||
|
>
|
||||||
|
<div className="p-4">
|
||||||
|
<ReactCrop src={file} crop={crop} onImageLoaded={(image) => setImageObj(image)} onChange={newCrop => setCrop(newCrop)} />
|
||||||
|
<Button onClick={handleImageUpload} variant="outlined">Upload</Button>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCroppedImg(image, crop, fileName) {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const scaleX = image.naturalWidth / image.width;
|
||||||
|
const scaleY = image.naturalHeight / image.height;
|
||||||
|
canvas.width = crop.width;
|
||||||
|
canvas.height = crop.height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.drawImage(
|
||||||
|
image,
|
||||||
|
crop.x * scaleX,
|
||||||
|
crop.y * scaleY,
|
||||||
|
crop.width * scaleX,
|
||||||
|
crop.height * scaleY,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
crop.width,
|
||||||
|
crop.height
|
||||||
|
);
|
||||||
|
|
||||||
|
// As Base64 string
|
||||||
|
// const base64Image = canvas.toDataURL('image/jpeg');
|
||||||
|
|
||||||
|
// As a blob
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
canvas.toBlob(blob => {
|
||||||
|
blob.name = fileName;
|
||||||
|
resolve(blob);
|
||||||
|
}, 'image/jpeg', 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -4,23 +4,26 @@ import {
|
|||||||
FieldError,
|
FieldError,
|
||||||
Label,
|
Label,
|
||||||
TextField,
|
TextField,
|
||||||
TextAreaField,
|
|
||||||
Submit,
|
Submit,
|
||||||
} from '@redwoodjs/forms'
|
} from '@redwoodjs/forms'
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { navigate, routes } from '@redwoodjs/router'
|
import { navigate, routes } from '@redwoodjs/router'
|
||||||
import { useFlash } from '@redwoodjs/web'
|
import { useFlash } from '@redwoodjs/web'
|
||||||
|
import ImageUploader from './ImageUploader.js'
|
||||||
|
|
||||||
|
|
||||||
import Editor from "rich-markdown-editor";
|
import Editor from "rich-markdown-editor";
|
||||||
|
|
||||||
const PartForm = (props) => {
|
const PartForm = (props) => {
|
||||||
const { addMessage } = useFlash()
|
const { addMessage } = useFlash()
|
||||||
const [description, setDescription] = useState(props?.part?.description)
|
const [description, setDescription] = useState(props?.part?.description)
|
||||||
|
const [imageUrl, setImageUrl] = useState(props?.part?.mainImage)
|
||||||
const onSubmit = async (data, e) => {
|
const onSubmit = async (data, e) => {
|
||||||
|
|
||||||
await props.onSave({
|
await props.onSave({
|
||||||
...data,
|
...data,
|
||||||
description,
|
description,
|
||||||
|
mainImage: imageUrl
|
||||||
}, props?.part?.id)
|
}, props?.part?.id)
|
||||||
const shouldOpenIde = e?.nativeEvent?.submitter?.dataset?.openIde
|
const shouldOpenIde = e?.nativeEvent?.submitter?.dataset?.openIde
|
||||||
if(shouldOpenIde) {
|
if(shouldOpenIde) {
|
||||||
@@ -57,21 +60,8 @@ const PartForm = (props) => {
|
|||||||
/>
|
/>
|
||||||
<FieldError name="title" className="rw-field-error" />
|
<FieldError name="title" className="rw-field-error" />
|
||||||
|
|
||||||
<Label
|
<ImageUploader onImageUpload={({cloudinaryPublicId}) => setImageUrl(cloudinaryPublicId)} />
|
||||||
name="mainImage"
|
|
||||||
className="p-0"
|
|
||||||
errorClassName="rw-label rw-label-error"
|
|
||||||
>
|
|
||||||
Main image
|
|
||||||
</Label>
|
|
||||||
<TextField
|
|
||||||
name="mainImage"
|
|
||||||
defaultValue={props.part?.mainImage}
|
|
||||||
className="rw-input"
|
|
||||||
errorClassName="rw-input rw-input-error"
|
|
||||||
validation={{ required: false }}
|
|
||||||
/>
|
|
||||||
<FieldError name="mainImage" className="rw-field-error" />
|
|
||||||
|
|
||||||
<Label
|
<Label
|
||||||
name="description"
|
name="description"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
import { useMutation, useFlash } from '@redwoodjs/web'
|
||||||
import { Link, routes } from '@redwoodjs/router'
|
import { Link, routes } from '@redwoodjs/router'
|
||||||
|
import { Image as CloudinaryImage } from 'cloudinary-react'
|
||||||
|
|
||||||
import avatar from 'src/assets/harold.jpg'
|
import avatar from 'src/assets/harold.jpg'
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ const PartsList = ({ parts }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto grid gap-8 grid-cols-4">
|
<div className="max-w-xs sm:max-w-sm md:max-w-2xl lg:max-w-5xl xl:max-w-6xl mx-auto grid gap-8 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid- cols-4">
|
||||||
{parts.map((part) => {
|
{parts.map((part) => {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
@@ -64,11 +65,19 @@ const PartsList = ({ parts }) => {
|
|||||||
<div className="rounded-t-2xl bg-gray-900">
|
<div className="rounded-t-2xl bg-gray-900">
|
||||||
<div className="flex items-center p-2 text-indigo-200">
|
<div className="flex items-center p-2 text-indigo-200">
|
||||||
<div className="h-full absolute inset-0 text-6xl flex items-center justify-center text-indigo-700" ><span>?</span></div>
|
<div className="h-full absolute inset-0 text-6xl flex items-center justify-center text-indigo-700" ><span>?</span></div>
|
||||||
<div className="mr-4"><img src={avatar} className="rounded-full h-10 w-10" /></div>
|
<div className="mr-4">
|
||||||
|
<img src={avatar} className="rounded-full h-10 w-10" />
|
||||||
|
</div>
|
||||||
<h3>{part.title}</h3>
|
<h3>{part.title}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-10">
|
<div className="relative z-10">
|
||||||
<img className="h-full" src={part.mainImage}/>
|
<CloudinaryImage
|
||||||
|
className="object-cover w-full rounded shadow"
|
||||||
|
cloudName="irevdev"
|
||||||
|
publicId={part.mainImage}
|
||||||
|
width="300"
|
||||||
|
crop="scale"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
const Svg = ({name, className: className2}) => {
|
const Svg = ({name, className: className2, strokeWidth = 2}) => {
|
||||||
|
|
||||||
const svgs = {
|
const svgs = {
|
||||||
"plus-circle": <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
"plus-circle": <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
</svg>,
|
</svg>,
|
||||||
"plus":<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
"plus":<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||||
</svg>,
|
</svg>,
|
||||||
|
"pencil": <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
||||||
|
</svg>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className={"h-10 w-10 " + className2}>
|
return <div className={"h-10 w-10 " + className2}>
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
@import "tailwindcss/base";
|
@import "tailwindcss/base";
|
||||||
@import "tailwindcss/components";
|
@import "tailwindcss/components";
|
||||||
@import "tailwindcss/utilities";
|
@import "tailwindcss/utilities";
|
||||||
|
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Ropa+Sans&display=swap');
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,500;1,700&display=swap')
|
||||||
/**
|
/**
|
||||||
* END --- TAILWIND GENERATOR EDIT
|
* END --- TAILWIND GENERATOR EDIT
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title>CadHub</title>
|
||||||
<script>
|
<script>
|
||||||
// Install Cascade Studio as a Progressive Web App for Offline Access
|
// Install Cascade Studio as a Progressive Web App for Offline Access
|
||||||
// This needs to be put before ANY HTTP Requests are made, so it can cache them.
|
// This needs to be put before ANY HTTP Requests are made, so it can cache them.
|
||||||
|
|||||||
@@ -11,36 +11,41 @@ const MainLayout = ({ children }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header>
|
<header>
|
||||||
<nav className="flex justify-between h-20 bg-gray-900">
|
<nav className="flex justify-between h-20 px-12 bg-gradient-to-r from-gray-900 to-indigo-900">
|
||||||
<ul className="flex items-center">
|
<ul className="flex items-center">
|
||||||
<li>
|
<li>
|
||||||
<Link to={routes.home()}>
|
<Link to={routes.home()}>
|
||||||
<Tooltip title="We need a logo!" >
|
<Tooltip title="We need a logo!" >
|
||||||
<div className="h-10 w-10 bg-indigo-500 rounded-full ml-12" data-tip="hello world" data-place="bottom"></div>
|
<div className="h-10 w-10 bg-indigo-500 rounded-full" data-tip="hello world" data-place="bottom"></div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Tooltip title="Very alpha, there's lots of work todo" >
|
<Tooltip title="Very alpha, there's lots of work todo" >
|
||||||
<div className="ml-8 flex">
|
<div className="ml-12 flex">
|
||||||
<h2 className="text-indigo-200 text-2xl">CadHub</h2>
|
<h2 className="text-indigo-300 text-5xl font-ropa-sans py-1 tracking-wider" style={{letterSpacing: '0.3em'}}>CadHub</h2>
|
||||||
<div className="text-pink-500 pb-4 text-sm" >pre-alpha</div>
|
<div className="text-pink-400 text-sm font-bold font-ropa-sans" style={{paddingBottom: '2rem', marginLeft: '-1.8rem'}}>pre-alpha</div>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="flex items-center">
|
<ul className="flex items-center">
|
||||||
<li className="mr-8 rounded-full border-2 border-indigo-300">
|
<li className="mr-8 h-10 w-10 rounded-full border-2 border-indigo-300 flex items-center justify-center">
|
||||||
<Link to={routes.newPart()}>
|
<Link to={routes.newPart()}>
|
||||||
<Svg name="plus" className="text-indigo-300" />
|
<Svg name="plus" className="text-indigo-300" />
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="mr-12 p-px border-2 rounded-full border-indigo-300 text-indigo-200">
|
{
|
||||||
<a href="#" onClick={isAuthenticated ? logOut : logIn}>
|
isAuthenticated ?
|
||||||
{isAuthenticated ? 'Log Out' : 'Log In'}
|
<li className="h-10 w-10 border-2 rounded-full border-indigo-300 text-indigo-200">
|
||||||
<img src={avatar} className="rounded-full h-10 w-10" />
|
<a href="#" onClick={logOut}>
|
||||||
</a>
|
<img src={avatar} className="rounded-full object-cover" />
|
||||||
</li>
|
</a>
|
||||||
|
</li>:
|
||||||
|
<li>
|
||||||
|
<a href="#" className='text-indigo-200 font-semibold underline' onClick={logIn}>Sign in/up</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ module.exports = {
|
|||||||
minHeight: {
|
minHeight: {
|
||||||
'md': '28rem'
|
'md': '28rem'
|
||||||
},
|
},
|
||||||
|
fontFamily: {
|
||||||
|
'ropa-sans': ['Ropa Sans', 'Arial', 'sans-serif'],
|
||||||
|
'roboto': ['Roboto', 'Arial', 'sans-serif'],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
variants: {},
|
variants: {},
|
||||||
|
|||||||
52
yarn.lock
52
yarn.lock
@@ -4524,6 +4524,11 @@ atob@^2.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||||
|
|
||||||
|
attr-accept@^2.2.1:
|
||||||
|
version "2.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b"
|
||||||
|
integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==
|
||||||
|
|
||||||
autoprefixer@9.8.6, autoprefixer@^9.4.5, autoprefixer@^9.7.2:
|
autoprefixer@9.8.6, autoprefixer@^9.4.5, autoprefixer@^9.7.2:
|
||||||
version "9.8.6"
|
version "9.8.6"
|
||||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f"
|
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f"
|
||||||
@@ -5842,7 +5847,20 @@ clone-deep@^4.0.1:
|
|||||||
kind-of "^6.0.2"
|
kind-of "^6.0.2"
|
||||||
shallow-clone "^3.0.0"
|
shallow-clone "^3.0.0"
|
||||||
|
|
||||||
clsx@^1.0.4:
|
cloudinary-core@^2.11.3:
|
||||||
|
version "2.11.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/cloudinary-core/-/cloudinary-core-2.11.3.tgz#1440f61c6280485094aac87021b7e10f746dc69e"
|
||||||
|
integrity sha512-ZRnpjSgvx+LbSf+aEz5NKzxDB4Z0436aY/0BSDa90kAHiwAyd84VyEi95I74SE80e15Ri9t5S2xtksTXpzk9Xw==
|
||||||
|
|
||||||
|
cloudinary-react@^1.6.7:
|
||||||
|
version "1.6.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/cloudinary-react/-/cloudinary-react-1.6.7.tgz#13a8c8660a36377edbc2482cdc7f061a12d60b19"
|
||||||
|
integrity sha512-u7Mx2CWEPG7Iy/3jn+JcvYu50uHwem4XokveNDqdnoeBJwit2B/ccfUtOUbpSfKAaEKLxXuPcQR5H8FEpIoavw==
|
||||||
|
dependencies:
|
||||||
|
cloudinary-core "^2.11.3"
|
||||||
|
prop-types "^15.6.2"
|
||||||
|
|
||||||
|
clsx@^1.0.4, clsx@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
|
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
|
||||||
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
|
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
|
||||||
@@ -6159,7 +6177,7 @@ core-js-pure@^3.0.0, core-js-pure@^3.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
|
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
|
||||||
integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
|
integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
|
||||||
|
|
||||||
core-js@3.6.5, core-js@^3.0.1, core-js@^3.0.4, core-js@^3.2.1:
|
core-js@3.6.5, core-js@^3.0.1, core-js@^3.0.4, core-js@^3.2.1, core-js@^3.6.5:
|
||||||
version "3.6.5"
|
version "3.6.5"
|
||||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
|
||||||
integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
|
integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
|
||||||
@@ -7907,6 +7925,13 @@ file-loader@^6.0.0:
|
|||||||
loader-utils "^2.0.0"
|
loader-utils "^2.0.0"
|
||||||
schema-utils "^2.6.5"
|
schema-utils "^2.6.5"
|
||||||
|
|
||||||
|
file-selector@^0.2.2:
|
||||||
|
version "0.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.2.2.tgz#76186ac94ea01a18262a1e9ee36a8815911bc0b4"
|
||||||
|
integrity sha512-tMZc0lkFzhOGlZUAkQ5iljPORvDX+nWEI+9C5nj9KT7Ax8bAUUtI/GYM8JFIjyKfKlQkJRC84D0UgxwDqRGvRQ==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.0.3"
|
||||||
|
|
||||||
file-system-cache@^1.0.5:
|
file-system-cache@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f"
|
resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f"
|
||||||
@@ -13096,6 +13121,15 @@ react-draggable@^4.0.3:
|
|||||||
classnames "^2.2.5"
|
classnames "^2.2.5"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
|
|
||||||
|
react-dropzone@^11.2.1:
|
||||||
|
version "11.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-11.2.1.tgz#7544439ed2e27d1e4ac8efff5c6290b758cc29f5"
|
||||||
|
integrity sha512-AVWKQKKd4M8vIYzRC7QvvyzsGMrz6UAtAYW2WvSlEmstHKXhHL3CAq9LUzALfzMcDd2mxmntSNcpxij0w7U4qA==
|
||||||
|
dependencies:
|
||||||
|
attr-accept "^2.2.1"
|
||||||
|
file-selector "^0.2.2"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
react-error-overlay@^6.0.1, react-error-overlay@^6.0.3:
|
react-error-overlay@^6.0.1, react-error-overlay@^6.0.3:
|
||||||
version "6.0.7"
|
version "6.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
|
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
|
||||||
@@ -13141,6 +13175,15 @@ react-hotkeys@2.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
prop-types "^15.6.1"
|
prop-types "^15.6.1"
|
||||||
|
|
||||||
|
react-image-crop@^8.6.6:
|
||||||
|
version "8.6.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-image-crop/-/react-image-crop-8.6.6.tgz#326f85b4da63c2f39a610f8832abea5d551a338f"
|
||||||
|
integrity sha512-2iQHKp12dYb6Fu2iN/Mg19BeLMgrbdOuKkU9k0RH7CHX6ZZylOlhfCM3/RsECbKnjGJRtGpXniGF+/i9CGis1A==
|
||||||
|
dependencies:
|
||||||
|
clsx "^1.1.1"
|
||||||
|
core-js "^3.6.5"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1:
|
react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
@@ -15204,6 +15247,11 @@ tslib@^1.10.0, tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.2, tslib@^1
|
|||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
|
||||||
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
|
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
|
||||||
|
|
||||||
|
tslib@^2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c"
|
||||||
|
integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==
|
||||||
|
|
||||||
tslib@~2.0.1:
|
tslib@~2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
|
||||||
|
|||||||
Reference in New Issue
Block a user