diff --git a/README.md b/README.md index 109f527..eb4248b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ # CadHub -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/). +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. + + + + ## Getting Started 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. @@ -18,6 +22,11 @@ Install dependencies yarn install ``` +Initialise the db +``` terminal +yarn rw db up +``` + ### Fire up dev ```terminal yarn rw dev @@ -25,7 +34,7 @@ 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/*`. -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. +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: @@ -40,3 +49,11 @@ If you not familiar with Redwood, never fear the main bit of tech it uses is Rea ## 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) + + + + diff --git a/api/src/graphql/users.sdl.js b/api/src/graphql/users.sdl.js index 52c3411..366075d 100644 --- a/api/src/graphql/users.sdl.js +++ b/api/src/graphql/users.sdl.js @@ -15,14 +15,14 @@ export const schema = gql` input CreateUserInput { email: String! - issuer: String! + # issuer: String! image: String bio: String } input UpdateUserInput { email: String - issuer: String + # issuer: String image: String bio: String } diff --git a/web/package.json b/web/package.json index db3c665..a9942bc 100644 --- a/web/package.json +++ b/web/package.json @@ -18,6 +18,7 @@ "@redwoodjs/forms": "^0.19.2", "@redwoodjs/router": "^0.19.2", "@redwoodjs/web": "^0.19.2", + "cloudinary-react": "^1.6.7", "controlkit": "^0.1.9", "golden-layout": "^1.5.9", "jquery": "^3.5.1", @@ -28,6 +29,8 @@ "prop-types": "^15.7.2", "react": "^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", "styled-components": "^5.2.0", "three": "^0.118.3" diff --git a/web/src/cascade b/web/src/cascade index 62f9612..e634591 160000 --- a/web/src/cascade +++ b/web/src/cascade @@ -1 +1 @@ -Subproject commit 62f961293d72558e59cdcbe0707ef15a06d30c12 +Subproject commit e634591e27dd41fec1638b278be3c298c6ab4b5a diff --git a/web/src/components/EmojiReaction/EmojiReaction.js b/web/src/components/EmojiReaction/EmojiReaction.js new file mode 100644 index 0000000..0f2d162 --- /dev/null +++ b/web/src/components/EmojiReaction/EmojiReaction.js @@ -0,0 +1,76 @@ +import { useState } from 'react' +import Fab from '@material-ui/core/Fab' +import IconButton from '@material-ui/core/IconButton' +import Popover from '@material-ui/core/Popover' +import Svg from 'src/components/Svg' + +const emojiMenu = ['🏆', '❤️', '👍', '😊', '😄', '🚀', '👏', '🙌'] + +const EmojiReaction = ({ emotes, callback = () => {} }) => { + const [isOpen, setIsOpen] = useState(false) + const [anchorEl, setAnchorEl] = useState(null) + const [popoverId, setPopoverId] = useState(undefined) + + const openPopover = (target) => { + setAnchorEl(target) + setPopoverId('simple-popover') + setIsOpen(true) + } + + const closePopover = () => { + setAnchorEl(null) + setPopoverId(undefined) + setIsOpen(false) + } + + const togglePopover = ({ currentTarget }) => { + if (isOpen) { + return closePopover() + } + + openPopover(currentTarget) + } + + const handleEmojiClick = (emoji) => { + callback(emoji) + closePopover() + } + + return [ +
+ +
+ +
+
+ +
+ {emotes.map((emote, i) => ( + handleEmojiClick(emote.emoji)}> + {emote.emoji} {emote.count} + + ))} +
+
, + + {emojiMenu.map((emoji, i) => ( + handleEmojiClick(emoji)}>{emoji} + ))} + , + ] +} + +export default EmojiReaction diff --git a/web/src/components/EmojiReaction/EmojiReaction.stories.js b/web/src/components/EmojiReaction/EmojiReaction.stories.js new file mode 100644 index 0000000..1f0c8ab --- /dev/null +++ b/web/src/components/EmojiReaction/EmojiReaction.stories.js @@ -0,0 +1,7 @@ +import EmojiReaction from './EmojiReaction' + +export const generated = () => { + return +} + +export default { title: 'Components/EmojiReaction' } diff --git a/web/src/components/EmojiReaction/EmojiReaction.test.js b/web/src/components/EmojiReaction/EmojiReaction.test.js new file mode 100644 index 0000000..f034533 --- /dev/null +++ b/web/src/components/EmojiReaction/EmojiReaction.test.js @@ -0,0 +1,11 @@ +import { render } from '@redwoodjs/testing' + +import EmojiReaction from './EmojiReaction' + +describe('EmojiReaction', () => { + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/web/src/components/PartForm/ImageUploader.js b/web/src/components/PartForm/ImageUploader.js new file mode 100644 index 0000000..0314a13 --- /dev/null +++ b/web/src/components/PartForm/ImageUploader.js @@ -0,0 +1,120 @@ +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"; + +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 ( +
+
+ {cloudinaryId && } + + {cloudinaryId &&
+ +
} + {!cloudinaryId && } + {!cloudinaryId &&
+ Drop files here ... + or + upload + +
} +
+ setIsModalOpen(false)} + > +
+ setImageObj(image)} onChange={newCrop => setCrop(newCrop)} /> + +
+
+
+ ); +} + +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); + }); +} + diff --git a/web/src/components/PartForm/PartForm.js b/web/src/components/PartForm/PartForm.js index c300d8c..daecc16 100644 --- a/web/src/components/PartForm/PartForm.js +++ b/web/src/components/PartForm/PartForm.js @@ -4,23 +4,26 @@ import { FieldError, Label, TextField, - TextAreaField, Submit, } from '@redwoodjs/forms' import { useState } from 'react'; import { navigate, routes } from '@redwoodjs/router' import { useFlash } from '@redwoodjs/web' +import ImageUploader from './ImageUploader.js' + import Editor from "rich-markdown-editor"; const PartForm = (props) => { const { addMessage } = useFlash() const [description, setDescription] = useState(props?.part?.description) + const [imageUrl, setImageUrl] = useState(props?.part?.mainImage) const onSubmit = async (data, e) => { await props.onSave({ ...data, description, + mainImage: imageUrl }, props?.part?.id) const shouldOpenIde = e?.nativeEvent?.submitter?.dataset?.openIde if(shouldOpenIde) { @@ -57,21 +60,8 @@ const PartForm = (props) => { /> - - - + setImageUrl(cloudinaryPublicId)} /> +