├── .envexample ├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── README.md ├── craco.config.js ├── jsconfig.json ├── package.json ├── public ├── _redirects ├── favicon.svg ├── index.html ├── logo192.png ├── manifest.json └── robots.txt ├── src ├── App.css ├── App.js ├── App.test.js ├── assets │ ├── add.svg │ ├── add_icon_only.svg │ ├── avatar.png │ ├── basic_shapes.png │ ├── blue_star.svg │ ├── blur_craftbase_screenshot.jpg │ ├── button.png │ ├── buttonWithIcon.png │ ├── buttonWithIcon.svg │ ├── checkbox.png │ ├── circle.png │ ├── copy.svg │ ├── craftbase_board_screenshot.png │ ├── craftbase_frontend_architecture.png │ ├── cursor.svg │ ├── customize.svg │ ├── divider.png │ ├── dropdown.png │ ├── github_logo.svg │ ├── home_shape_toolbar.png │ ├── imageCard.png │ ├── layers_toolbar_icon.svg │ ├── link_white.svg │ ├── linkwithicon.png │ ├── no_signup.svg │ ├── ooorganize.svg │ ├── overlay.png │ ├── pan.svg │ ├── radio_check.png │ ├── radio_white.svg │ ├── radiobox.png │ ├── rectangle.png │ ├── refresh.svg │ ├── right_arrow.svg │ ├── right_arrow_white.svg │ ├── sticker.svg │ ├── subtract.svg │ ├── text.png │ ├── textinput.png │ ├── toggle.png │ ├── toggle.svg │ └── twitter_logo.svg ├── common.css ├── components │ ├── ProgressiveImageLoader │ │ ├── image.css │ │ ├── image.js │ │ ├── index.css │ │ └── loader.js │ ├── common │ │ ├── button.js │ │ ├── index.css │ │ ├── modal.js │ │ ├── modalContainer.js │ │ ├── portal.js │ │ ├── spinner.js │ │ └── spinnerWithSize.js │ ├── elements │ │ ├── arrowLine.js │ │ ├── avatar.js │ │ ├── button.js │ │ ├── buttonWithIcon.js │ │ ├── checkbox.js │ │ ├── circle.js │ │ ├── divider.js │ │ ├── dropdown.js │ │ ├── frame.js │ │ ├── groupobject.js │ │ ├── imageCard.js │ │ ├── linkWithIcon.js │ │ ├── logo512.png │ │ ├── overlay.js │ │ ├── pencil.js │ │ ├── radiobox.js │ │ ├── rectangle.js │ │ ├── template.js │ │ ├── text.js │ │ ├── textarea.js │ │ ├── textinput.js │ │ ├── toggle.js │ │ └── tooltip.js │ ├── floatingToolbar.js │ ├── oldToolbar.js │ ├── sidebar │ │ ├── elementsDropdown.js │ │ ├── primary.js │ │ ├── shareLinkPopup.js │ │ ├── sidebar.css │ │ ├── tempPopup.js │ │ └── userDetailsPopup.js │ └── utils │ │ ├── borderStyleBox.js │ │ ├── colorPicker.js │ │ ├── dragger.js │ │ ├── editWrapper.js │ │ ├── loader.js │ │ ├── objectSelector.js │ │ ├── opacitySlider.js │ │ ├── toolbarConnector.js │ │ └── zoomer.js ├── constants │ ├── elementSchema.js │ ├── exportHooks.js │ ├── misc.js │ └── properties.js ├── factory │ ├── arrowLine.js │ ├── avatar.js │ ├── button.js │ ├── buttonwithicon.js │ ├── circle.js │ ├── divider.js │ ├── dropdown.js │ ├── frame.js │ ├── imagecard.js │ ├── linkwithicon.js │ ├── main.js │ ├── newArrowLine.js │ ├── overlay.js │ ├── pencil.js │ ├── rectangle.js │ ├── text.js │ ├── textarea.js │ ├── textinput.js │ └── toggle.js ├── hooks │ └── intersectionObserver.js ├── icons │ ├── icon.js │ ├── icons.js │ └── image-gallery-landscape-square-potrait-pic-interface-ui-3 (2).svg ├── index.css ├── index.js ├── logo.svg ├── newCanvas.js ├── routes.js ├── scalingRotate.js ├── schema │ ├── mutations │ │ └── index.js │ ├── queries │ │ └── index.js │ └── subscriptions │ │ └── index.js ├── serviceWorker.js ├── setupTests.js ├── store │ ├── actions │ │ └── main.js │ ├── reducers │ │ ├── index.js │ │ └── main.js │ └── types.js ├── styles │ └── tailwind.css ├── utils │ ├── constants.js │ ├── dragCodeClone.js │ ├── index.js │ ├── misc.js │ └── updateVertices.js ├── views │ ├── Board │ │ ├── board.js │ │ ├── errorBoundary.js │ │ ├── index.css │ │ └── index.js │ └── Home │ │ ├── errorBoundary.js │ │ ├── home.js │ │ ├── index.css │ │ └── index.js └── wireframeAssets │ ├── avatar.svg │ ├── btn.svg │ ├── btnWithIcon.svg │ ├── checkbox.svg │ ├── circle.svg │ ├── cursor.svg │ ├── divider.svg │ ├── frame.svg │ ├── imageCard.svg │ ├── miniAvatar.svg │ ├── pencil.svg │ ├── radiobox.svg │ ├── rectangle.svg │ ├── text.svg │ └── toggle.svg ├── tailwind.config.js ├── temp.js ├── temp.json └── yarn.lock /.envexample: -------------------------------------------------------------------------------- 1 | REACT_APP_HASURA_ADMIN_SECRET=secret 2 | REACT_APP_WS_GRAPHQL_ENDPOINT=ws://localhost:8080/v1/graphql 3 | REACT_APP_GRAPHQL_ENDPOINT=http://localhost:8080/v1/graphql 4 | REACT_APP_GRAPHQL_ERROR_POLICY=all -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .env -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Welcome 2 | 3 | Thanks for reading this README of craftbase app. 4 | 5 | Craftbase is online whiteboarding tool where user(s) can brainstorm using various objects such as shapes, pencil and to name a few, with themselves as well share it with other users. 6 | 7 | Currently, the live sharing feature is internally disabled but I am working on adding feature flag for it. Once it's enabled, you can live share it with your other colleagues/peers/friends and collaborate together. 8 | 9 | My attempt is to make it as simple to spin up a board and start whiteboarding right away. No signup/signin required. 10 | 11 | Please expect some bugs and glitches as I am working on it (mostly on weekends). Feel free to raise GH issue whether is feature, suggestion or bug related to craftbase. 12 | 13 | ## Craftbase and its architecture 14 | 15 | ![Architecture](https://raw.githubusercontent.com/craftbase-org/craftbase/refs/heads/main/src/assets/craftbase_frontend_architecture.png) 16 | 17 | Craftbase underneath uses [two.js](https://github.com/jonobr1/two.js) library to render scene graph which is developed by awesome [Jono brandel](https://github.com/jonobr1) 18 | 19 | The craftbase has "Board" as it's main primary concept which holds the specific responsiblity of rendering the canvas, components and utilities. The heirarchy looks like : Board -> Canvas -> ElementRenderer -> Component Element -> Component Factory 20 | 21 | Board represents the Canvas, primary sidebar and floating toolbar (soon to be). Canvas is where the logic of rendering the components in 2d space is present. Each component has it's factory from which the template code is generated as output and provided to "component element" which hooks with react functional component and it attaches some event listeners to specific component. 22 | 23 | The user interaction controls such as mouse , drag , zoom and pan are part of Canvas. All these interactions are able to make the board lively as if user was whiteboarding on it. 24 | 25 | ## Run/develop locally 26 | 27 | This craftbase repo is containing the frontend code and the other repo under same org is containing the backend code (craftbase-hasura) 28 | 29 | You need to setup both the projects (frontend and backend) locally in order to successfully run craftbase application server in your machine. 30 | 31 | ### Backend 32 | 33 | Head over to the readme.md of the [craftbase-hasura](https://github.com/craftbase-org/craftbase-hasura) and follow the instructions to setup backend of the craftbase locally. 34 | 35 | ### Frontend 36 | 37 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 38 | 39 | So you'll need to clone the repository and run `yarn` first to install the dependencies. You can also run `npm install` depending on your choice since it will have it's own lock file for peer and core dependencies. 40 | 41 | Then, create `.env` file in root of the project directory and copy the contents from .envexample into that. 42 | 43 | Finally, to run the frontend server, you can run `yarn start` which will start the server on default port of 3000. 44 | 45 | #### `yarn start` 46 | 47 | Runs the app in the development mode.
48 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 49 | 50 | The page will reload if you make edits.
51 | You will also see any lint errors in the console. 52 | 53 | #### `yarn build` 54 | 55 | Builds the app for production to the `build` folder.
56 | It correctly bundles React in production mode and optimizes the build for the best performance. 57 | 58 | The build is minified and the filenames include the hashes.
59 | Your app is ready to be deployed! 60 | 61 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 62 | -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | const CracoLessPlugin = require('craco-less') 2 | 3 | module.exports = { 4 | style: {}, 5 | plugins: [ 6 | // { 7 | // plugin: CracoLessPlugin, 8 | // options: { 9 | // lessLoaderOptions: { 10 | // lessOptions: { 11 | // modifyVars: { 12 | // '@primary-color': '#FF4747', 13 | // }, 14 | // javascriptEnabled: true, 15 | // }, 16 | // }, 17 | // }, 18 | // }, 19 | ], 20 | } 21 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src" 4 | }, 5 | "include": ["src"] 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "craftbase", 3 | "version": "0.2.0", 4 | "private": true, 5 | "dependencies": { 6 | "@apollo/client": "^3.10.4", 7 | "@craco/craco": "^7.1.0", 8 | "@tanstack/react-ranger": "^0.0.4", 9 | "@testing-library/jest-dom": "^6.4.5", 10 | "@testing-library/react": "^16.0.0", 11 | "@testing-library/user-event": "^14.5.2", 12 | "classnames": "^2.5.1", 13 | "craco-less": "^3.0.1", 14 | "framer-motion": "^11.2.10", 15 | "graphql": "^16.8.1", 16 | "idx": "^3.0.3", 17 | "immer": "^10.1.1", 18 | "interactjs": "^1.10.27", 19 | "prop-types": "^15.8.1", 20 | "react": "^18.3.1", 21 | "react-dom": "^18.3.1", 22 | "react-loadable": "^5.5.0", 23 | "react-redux": "^9.1.2", 24 | "react-responsive": "10.0.0", 25 | "react-router": "^6.23.1", 26 | "react-router-dom": "^6.23.1", 27 | "react-scripts": "5.0.1", 28 | "redux": "^5.0.1", 29 | "redux-logger": "^3.0.6", 30 | "redux-thunk": "^3.1.0", 31 | "styled-components": "^6.1.11", 32 | "subscriptions-transport-ws": "^0.11.0", 33 | "two.js": "^0.8.13", 34 | "use-immer": "^0.9.0" 35 | }, 36 | "scripts": { 37 | "start": "craco start", 38 | "build": "CI= craco build", 39 | "test": "craco test", 40 | "eject": "react-scripts eject" 41 | }, 42 | "resolutions": { 43 | "react-scripts/**/react-error-overlay": "6.0.9" 44 | }, 45 | "eslintConfig": { 46 | "extends": "react-app" 47 | }, 48 | "browserslist": { 49 | "production": [ 50 | ">0.2%", 51 | "not dead", 52 | "not op_mini all" 53 | ], 54 | "development": [ 55 | "last 1 chrome version", 56 | "last 1 firefox version", 57 | "last 1 safari version" 58 | ] 59 | }, 60 | "devDependencies": { 61 | "@babel/plugin-proposal-private-property-in-object": "^7.14.5", 62 | "tailwindcss": "^3.4.4" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | Sticker -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 21 | 27 | 31 | 35 | 44 | Craftbase 45 | 46 | 50 | 59 | 60 | 80 | 81 | 82 | 83 |
84 | 94 | 95 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/public/logo192.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Craftbase", 3 | "name": "Craftbase | Wireframe tool", 4 | "icons": [ 5 | { 6 | "src": "favicon.svg", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Routes, Route, BrowserRouter } from 'react-router-dom' 3 | 4 | import { 5 | ApolloClient, 6 | ApolloProvider, 7 | InMemoryCache, 8 | HttpLink, 9 | split, 10 | } from '@apollo/client' 11 | import { getMainDefinition } from '@apollo/client/utilities' 12 | import { WebSocketLink } from '@apollo/client/link/ws' 13 | 14 | import BoardViewContainer from 'views/Board' 15 | import HomePageViewContainer from 'views/Home' 16 | 17 | import routes from './routes' 18 | 19 | import './App.css' 20 | 21 | import './common.css' 22 | 23 | function getApolloClient() { 24 | const accessToken = localStorage.getItem('access_token') 25 | // console.log('access_token in client', accessToken) 26 | // const httpLink = new HttpLink({ 27 | // uri: process.env.REACT_APP_GRAPHQL_ENDPOINT, 28 | // headers: { 29 | // authorization: `Bearer ${accessToken}`, 30 | // 'content-type': 'application/json', 31 | // }, 32 | // }) 33 | const httpLink = new HttpLink({ 34 | uri: process.env.REACT_APP_GRAPHQL_ENDPOINT, 35 | headers: { 36 | // authorization: `Bearer ${accessToken}`, 37 | 'content-type': 'application/json', 38 | 'x-hasura-admin-secret': process.env.REACT_APP_HASURA_ADMIN_SECRET, 39 | }, 40 | }) 41 | 42 | const wsLink = new WebSocketLink({ 43 | uri: process.env.REACT_APP_WS_GRAPHQL_ENDPOINT, 44 | 45 | options: { 46 | reconnect: true, 47 | connectionParams: { 48 | headers: { 49 | // authorization: `Bearer ${accessToken}`, 50 | 'content-type': 'application/json', 51 | 'x-hasura-admin-secret': 52 | process.env.REACT_APP_HASURA_ADMIN_SECRET, 53 | }, 54 | }, 55 | }, 56 | }) 57 | 58 | // The split function takes three parameters: 59 | // 60 | // * A function that's called for each operation to execute 61 | // * The Link to use for an operation if the function returns a "truthy" value 62 | // * The Link to use for an operation if the function returns a "falsy" value 63 | const splitLink = split( 64 | ({ query }) => { 65 | const definition = getMainDefinition(query) 66 | return ( 67 | definition.kind === 'OperationDefinition' && 68 | definition.operation === 'subscription' 69 | ) 70 | }, 71 | wsLink, 72 | httpLink 73 | ) 74 | 75 | const client = new ApolloClient({ 76 | link: splitLink, 77 | cache: new InMemoryCache(), 78 | }) 79 | return client 80 | } 81 | 82 | class App extends Component { 83 | constructor(props) { 84 | super(props) 85 | } 86 | 87 | render() { 88 | const apolloClient = getApolloClient() 89 | return ( 90 | 91 | 92 |
93 | 94 | } 97 | /> 98 | } 101 | /> 102 | 103 |
104 |
105 |
106 | ) 107 | } 108 | } 109 | 110 | export default App 111 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/assets/add.svg: -------------------------------------------------------------------------------- 1 | Add -------------------------------------------------------------------------------- /src/assets/add_icon_only.svg: -------------------------------------------------------------------------------- 1 | Add -------------------------------------------------------------------------------- /src/assets/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/avatar.png -------------------------------------------------------------------------------- /src/assets/basic_shapes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/basic_shapes.png -------------------------------------------------------------------------------- /src/assets/blue_star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/blur_craftbase_screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/blur_craftbase_screenshot.jpg -------------------------------------------------------------------------------- /src/assets/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/button.png -------------------------------------------------------------------------------- /src/assets/buttonWithIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/buttonWithIcon.png -------------------------------------------------------------------------------- /src/assets/buttonWithIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | Button 3 | -------------------------------------------------------------------------------- /src/assets/checkbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/checkbox.png -------------------------------------------------------------------------------- /src/assets/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/circle.png -------------------------------------------------------------------------------- /src/assets/copy.svg: -------------------------------------------------------------------------------- 1 | Copy -------------------------------------------------------------------------------- /src/assets/craftbase_board_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/craftbase_board_screenshot.png -------------------------------------------------------------------------------- /src/assets/craftbase_frontend_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/craftbase_frontend_architecture.png -------------------------------------------------------------------------------- /src/assets/cursor.svg: -------------------------------------------------------------------------------- 1 | Cursor -------------------------------------------------------------------------------- /src/assets/customize.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/divider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/divider.png -------------------------------------------------------------------------------- /src/assets/dropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/dropdown.png -------------------------------------------------------------------------------- /src/assets/github_logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/home_shape_toolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/home_shape_toolbar.png -------------------------------------------------------------------------------- /src/assets/imageCard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/imageCard.png -------------------------------------------------------------------------------- /src/assets/layers_toolbar_icon.svg: -------------------------------------------------------------------------------- 1 | Layers -------------------------------------------------------------------------------- /src/assets/link_white.svg: -------------------------------------------------------------------------------- 1 | Link -------------------------------------------------------------------------------- /src/assets/linkwithicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/linkwithicon.png -------------------------------------------------------------------------------- /src/assets/no_signup.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/overlay.png -------------------------------------------------------------------------------- /src/assets/pan.svg: -------------------------------------------------------------------------------- 1 | Pan -------------------------------------------------------------------------------- /src/assets/radio_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/radio_check.png -------------------------------------------------------------------------------- /src/assets/radio_white.svg: -------------------------------------------------------------------------------- 1 | Radio -------------------------------------------------------------------------------- /src/assets/radiobox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/radiobox.png -------------------------------------------------------------------------------- /src/assets/rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/rectangle.png -------------------------------------------------------------------------------- /src/assets/refresh.svg: -------------------------------------------------------------------------------- 1 | Refresh -------------------------------------------------------------------------------- /src/assets/right_arrow.svg: -------------------------------------------------------------------------------- 1 | Arrow Right -------------------------------------------------------------------------------- /src/assets/right_arrow_white.svg: -------------------------------------------------------------------------------- 1 | Arrow Right -------------------------------------------------------------------------------- /src/assets/sticker.svg: -------------------------------------------------------------------------------- 1 | Sticker -------------------------------------------------------------------------------- /src/assets/subtract.svg: -------------------------------------------------------------------------------- 1 | Remove -------------------------------------------------------------------------------- /src/assets/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/text.png -------------------------------------------------------------------------------- /src/assets/textinput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/textinput.png -------------------------------------------------------------------------------- /src/assets/toggle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/assets/toggle.png -------------------------------------------------------------------------------- /src/assets/toggle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/twitter_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/common.css: -------------------------------------------------------------------------------- 1 | .zoomer-container .zoom-in-selector { 2 | position: fixed; 3 | right: 60px; 4 | bottom: 20px; 5 | opacity: 1; 6 | z-index: 1; 7 | } 8 | .zoomer-container .zoom-out-selector { 9 | position: fixed; 10 | right: 15px; 11 | bottom: 20px; 12 | opacity: 1; 13 | z-index: 1; 14 | } 15 | 16 | .dragger-picker { 17 | cursor: pointer; 18 | } 19 | 20 | .rounded-50-percent { 21 | border-radius: 50%; 22 | } 23 | 24 | /* patch - to disable automatically appending iframe */ 25 | iframe { 26 | z-index: -11111 !important; 27 | } 28 | 29 | /* css for text wireframe element's foreign object */ 30 | .foreign-text-container-base { 31 | /* border-radius: 5px; */ 32 | background: transparent; 33 | /* border: 2px solid #ccc; */ 34 | height: 100%; 35 | display: flex; 36 | align-items: center; 37 | justify-content: center; 38 | } 39 | -------------------------------------------------------------------------------- /src/components/ProgressiveImageLoader/image.css: -------------------------------------------------------------------------------- 1 | .image { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | } 8 | .full { 9 | transition: opacity 400ms ease 0ms; 10 | } 11 | .thumb { 12 | filter: blur(20px); 13 | transform: scale(1.1); 14 | transition: visibility 0ms ease 400ms; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/ProgressiveImageLoader/image.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './image.css' 3 | 4 | const Image = (props) => { 5 | const [isLoaded, setIsLoaded] = React.useState(false) 6 | return ( 7 | 8 | {props.alt} 14 | { 16 | setIsLoaded(true) 17 | }} 18 | className="image full" 19 | style={{ opacity: isLoaded ? 1 : 0 }} 20 | alt={props.alt} 21 | src={props.src} 22 | /> 23 | 24 | ) 25 | } 26 | export default Image 27 | -------------------------------------------------------------------------------- /src/components/ProgressiveImageLoader/index.css: -------------------------------------------------------------------------------- 1 | .image-container { 2 | position: relative; 3 | overflow: hidden; 4 | background: rgba(0, 0, 0, 0.05); 5 | } 6 | -------------------------------------------------------------------------------- /src/components/ProgressiveImageLoader/loader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import useIntersectionObserver from 'hooks/intersectionObserver' 3 | import Image from './image' 4 | import './index.css' 5 | 6 | const ImageContainer = (props) => { 7 | const ref = React.useRef() 8 | const [isVisible, setIsVisible] = React.useState(false) 9 | useIntersectionObserver({ 10 | target: ref, 11 | onIntersect: ([{ isIntersecting }], observerElement) => { 12 | if (isIntersecting) { 13 | setIsVisible(true) 14 | observerElement.unobserve(ref.current) 15 | } 16 | }, 17 | }) 18 | const aspectRatio = (props.height / props.width) * 100 19 | return ( 20 |
25 | {isVisible && ( 26 | {props.alt} 27 | )} 28 |
29 | ) 30 | } 31 | export default ImageContainer 32 | -------------------------------------------------------------------------------- /src/components/common/button.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Spinner from 'components/common/spinnerWithSize' 3 | 4 | const Button = (props) => { 5 | let baseClassNames = 6 | ' focus:outline-none rounded-md hover:shadow-lg flex items-center ' 7 | 8 | switch (props.intent) { 9 | case 'primary': // red 10 | baseClassNames += ' bg-primary-blue text-white ' 11 | break 12 | case 'info': // white 13 | baseClassNames += ' bg-neutrals-n500 text-neutrals-n40 ' 14 | break 15 | case 'secondary': // white 16 | baseClassNames += 17 | ' bg-white text-primary-blue border border-primary-blue' 18 | break 19 | default: 20 | baseClassNames += 21 | ' bg-transparent text-white border border-neutral-gray-400' 22 | break 23 | } 24 | 25 | switch (props.size) { 26 | case 'large': // red 27 | baseClassNames += 28 | ' px-4 py-2 2xl:px-6 2xl:py-3 text-sm xl:text-base 2xl:text-lg' 29 | break 30 | case 'small': // white 31 | baseClassNames += ' px-2 py-1 text-xs ' 32 | break 33 | default: 34 | baseClassNames += 35 | ' px-3 py-2 2xl:px-4 2xl:py-2 text-sm 2xl:text-base ' 36 | break 37 | } 38 | 39 | if (props?.customClass) { 40 | baseClassNames = props.customClass 41 | } 42 | 43 | if (props?.disabled) { 44 | baseClassNames += ' cursor-not-allowed opacity-05 ' 45 | } 46 | 47 | if (props?.extendClass) { 48 | baseClassNames = props.extendClass + ' ' + baseClassNames 49 | } 50 | 51 | return ( 52 | <> 53 | 76 | 77 | ) 78 | } 79 | 80 | // Button.defaultProps = { 81 | // disabled: false, 82 | // loading: false, 83 | // onClick: () => {}, 84 | // intent: '', 85 | // size: '', 86 | // label: '', 87 | // customClass: null, 88 | // extendClass: null, 89 | // } 90 | 91 | export default Button 92 | -------------------------------------------------------------------------------- /src/components/common/index.css: -------------------------------------------------------------------------------- 1 | .loaders { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 400px; 6 | height: 200px; 7 | border-radius: 20px; 8 | margin: 10% auto; 9 | background-color: #e0e0e0; 10 | } 11 | .loader { 12 | margin: 20px; 13 | width: 35px; 14 | height: 35px; 15 | border-radius: 35px; 16 | display: block; 17 | /* background-color:green; */ 18 | border: 4px solid; 19 | animation: spinner 1s linear infinite; 20 | } 21 | 22 | .text-red { 23 | color: red; 24 | } 25 | 26 | .lg-loader { 27 | margin: 20px; 28 | display: block; 29 | /* background-color:green; */ 30 | border: 4px solid; 31 | animation: spinner 0.5s linear infinite; 32 | width: 45px; 33 | height: 45px; 34 | border-radius: 45px; 35 | } 36 | .md-loader { 37 | margin: 20px; 38 | display: block; 39 | /* background-color:green; */ 40 | border: 2.5px solid; 41 | animation: spinner 0.4s linear infinite; 42 | width: 20px; 43 | height: 20px; 44 | border-radius: 25px; 45 | } 46 | .sm-loader { 47 | margin: 20px; 48 | display: block; 49 | /* background-color:green; */ 50 | border: 3px solid; 51 | animation: spinner 0.4s linear infinite; 52 | width: 18px; 53 | height: 18px; 54 | border-radius: 25px; 55 | } 56 | .xs-loader { 57 | margin: 20px; 58 | display: block; 59 | /* background-color:green; */ 60 | border: 4px solid; 61 | animation: spinner 0.5s linear infinite; 62 | width: 15px; 63 | height: 15px; 64 | border-radius: 15px; 65 | } 66 | 67 | .loader-1 { 68 | border-color: #fff9c4; 69 | border-top-color: #9575cd; 70 | } 71 | .loader-2 { 72 | border-color: transparent; 73 | border-top-color: #81d4fa; 74 | border-bottom-color: #9575cd; 75 | } 76 | .loader-3 { 77 | border-color: transparent; 78 | border-bottom-color: #9575cd; 79 | } 80 | 81 | @keyframes spinner { 82 | 0% { 83 | transform: rotate(0deg); 84 | } 85 | 100% { 86 | transform: rotate(360deg); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/components/common/modal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | // import "wicg-inert"; 4 | 5 | import Portal from './portal' 6 | 7 | const Backdrop = styled.div` 8 | position: fixed; 9 | top: 0; 10 | right: 0; 11 | bottom: 0; 12 | left: 0; 13 | background-color: rgba(51, 51, 51, 0.3); 14 | backdrop-filter: blur(1px); 15 | opacity: 0; 16 | transition: all 100ms cubic-bezier(0.4, 0, 0.2, 1); 17 | transition-delay: 200ms; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | 22 | & .modal-content { 23 | transform: translateY(100px); 24 | transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1); 25 | opacity: 0; 26 | } 27 | 28 | &.active { 29 | transition-duration: 250ms; 30 | transition-delay: 0ms; 31 | opacity: 1; 32 | 33 | & .modal-content { 34 | transform: translateY(0); 35 | opacity: 1; 36 | transition-delay: 150ms; 37 | transition-duration: 350ms; 38 | } 39 | } 40 | ` 41 | 42 | const Content = styled.div` 43 | position: relative; 44 | padding: 20px; 45 | box-sizing: border-box; 46 | min-height: 50px; 47 | min-width: 50px; 48 | max-height: 80%; 49 | max-width: 80%; 50 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); 51 | background-color: white; 52 | border-radius: 2px; 53 | ` 54 | 55 | export default function Modal(props) { 56 | const [active, setActive] = React.useState(false) 57 | const { open, onClose, locked } = props 58 | const backdrop = React.useRef(null) 59 | 60 | React.useEffect(() => { 61 | const { current } = backdrop 62 | 63 | const transitionEnd = () => setActive(open) 64 | 65 | const keyHandler = (e) => 66 | !locked && [27].indexOf(e.which) >= 0 && onClose() 67 | 68 | const clickHandler = (e) => !locked && e.target === current && onClose() 69 | 70 | if (current) { 71 | current.addEventListener('transitionend', transitionEnd) 72 | current.addEventListener('click', clickHandler) 73 | window.addEventListener('keyup', keyHandler) 74 | } 75 | 76 | if (open) { 77 | window.setTimeout(() => { 78 | document.activeElement.blur() 79 | setActive(open) 80 | // document.querySelector("#root").setAttribute("inert", "true"); 81 | }, 10) 82 | } 83 | 84 | return () => { 85 | if (current) { 86 | current.removeEventListener('transitionend', transitionEnd) 87 | current.removeEventListener('click', clickHandler) 88 | } 89 | 90 | // document.querySelector("#root").removeAttribute("inert"); 91 | window.removeEventListener('keyup', keyHandler) 92 | } 93 | }, [open, locked, onClose]) 94 | 95 | return ( 96 | 97 | {(open || active) && ( 98 | 99 | 103 | 104 | {props.children} 105 | 106 | 107 | 108 | )} 109 | 110 | ) 111 | } 112 | -------------------------------------------------------------------------------- /src/components/common/modalContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Modal from './modal' 4 | 5 | const ModalContainer = ({ closeModal, showModal, children }) => { 6 | return ( 7 | <> 8 | 9 | {children} 10 | 11 | 12 | ) 13 | } 14 | 15 | export default ModalContainer 16 | -------------------------------------------------------------------------------- /src/components/common/portal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | export default function Portal({ children, parent, className }) { 5 | const el = React.useMemo(() => document.createElement('div'), []) 6 | React.useEffect(() => { 7 | const target = parent && parent.appendChild ? parent : document.body 8 | const classList = ['portal-container'] 9 | if (className) 10 | className.split(' ').forEach((item) => classList.push(item)) 11 | classList.forEach((item) => el.classList.add(item)) 12 | target.appendChild(el) 13 | return () => { 14 | target.removeChild(el) 15 | } 16 | }, [el, parent, className]) 17 | return ReactDOM.createPortal(children, el) 18 | } 19 | -------------------------------------------------------------------------------- /src/components/common/spinner.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SpinnerWithSize from './spinnerWithSize' 3 | const Spinner = ({ displayText }) => { 4 | return ( 5 | <> 6 |
7 | {displayText ? ( 8 | displayText 9 | ) : ( 10 | 18 | )} 19 |
20 | 21 | ) 22 | } 23 | 24 | export default Spinner 25 | -------------------------------------------------------------------------------- /src/components/common/spinnerWithSize.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react' 2 | import './index.css' 3 | 4 | const SpinnerWithSize = (props) => { 5 | const { loaderSize, color, customStyles } = props 6 | const renderLoader = () => { 7 | switch (loaderSize) { 8 | case 'lg': 9 | return ( 10 | 16 | ) 17 | case 'md': 18 | return ( 19 | 25 | ) 26 | case 'sm': 27 | return ( 28 | 34 | ) 35 | case 'xs': 36 | return ( 37 | 43 | ) 44 | default: 45 | return 46 | } 47 | } 48 | return {renderLoader()} 49 | } 50 | 51 | // SpinnerWithSize.defaultProps = { 52 | // loaderSize: 'lg', 53 | // color: '#2F98D0', 54 | // customStyles: {}, 55 | // } 56 | 57 | export default SpinnerWithSize 58 | -------------------------------------------------------------------------------- /src/components/elements/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftbase-org/craftbase/235cbd840d18bdcf05ba96ffaca964f1c5748aa7/src/components/elements/logo512.png -------------------------------------------------------------------------------- /src/components/elements/pencil.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import interact from 'interactjs' 3 | import { useImmer } from 'use-immer' 4 | 5 | import { elementOnBlurHandler } from 'utils/misc' 6 | import getEditComponents from 'components/utils/editWrapper' 7 | import PencilFactory from 'factory/pencil' 8 | import Toolbar from 'components/floatingToolbar' 9 | 10 | function Pencil(props) { 11 | const selectedComponents = [] 12 | 13 | const [showToolbar, toggleToolbar] = useState(false) 14 | const [internalState, setInternalState] = useImmer({}) 15 | 16 | const two = props.twoJSInstance 17 | let selectorInstance = null 18 | let toolbarInstance = null 19 | let groupObject = null 20 | 21 | // function onBlurHandler(e) { 22 | // elementOnBlurHandler(e, selectorInstance, two) 23 | // document.getElementById(`${groupObject.id}`) && 24 | // document 25 | // .getElementById(`${groupObject.id}`) 26 | // .removeEventListener('keydown', handleKeyDown) 27 | // } 28 | 29 | // function handleKeyDown(e) { 30 | // if (e.keyCode === 8 || e.keyCode === 46) { 31 | // console.log('handle key down event', e) 32 | // // DELETE/BACKSPACE KEY WAS PRESSED 33 | // props.handleDeleteComponent && 34 | // props.handleDeleteComponent(groupObject) 35 | // two.remove([groupObject]) 36 | // two.update() 37 | // } 38 | // } 39 | 40 | // function onFocusHandler(e) { 41 | // document.getElementById(`${groupObject.id}`).style.outline = 0 42 | // document 43 | // .getElementById(`${groupObject.id}`) 44 | // .addEventListener('keydown', handleKeyDown) 45 | // } 46 | 47 | // Using unmount phase to remove event listeners 48 | useEffect(() => { 49 | // Calculate x and y through dividing width and height by 2 or vice versa 50 | // if x and y are given then multiply width and height into 2 51 | const offsetHeight = 0 52 | 53 | const prevX = props.x 54 | const prevY = props.y 55 | 56 | // Instantiate factory 57 | const elementFactory = new PencilFactory(two, prevX, prevY, { 58 | ...props, 59 | }) 60 | // Get all instances of every sub child element 61 | const { group, path } = elementFactory.createElement() 62 | group.elementData = { ...props.itemData, ...props } 63 | 64 | if (props.parentGroup) { 65 | /** This element will be rendered and scoped in its parent group */ 66 | console.log('properties of pencil', props) 67 | const parentGroup = props.parentGroup 68 | path.translation.x = props.properties.x 69 | path.translation.y = props.properties.y 70 | parentGroup.add(path) 71 | two.update() 72 | } else { 73 | /** This element will render by creating it's own group wrapper */ 74 | groupObject = group 75 | 76 | const { selector } = getEditComponents(two, group, 4) 77 | selectorInstance = selector 78 | 79 | group.children.unshift(path) 80 | two.update() 81 | 82 | // document 83 | // .getElementById(group.id) 84 | // .setAttribute('class', 'dragger-picker') 85 | 86 | document 87 | .getElementById(group.id) 88 | .setAttribute('class', 'avoid-dragging') 89 | 90 | // setting database's id in html attribute of element 91 | document 92 | .getElementById(group.id) 93 | .setAttribute('data-component-id', props.id) 94 | 95 | // console.log('two circle', group.id) 96 | const initialSceneCoords = document 97 | .getElementById(two.scene.id) 98 | .getBoundingClientRect() 99 | console.log('initialSceneCoords', initialSceneCoords) 100 | 101 | setInternalState((draft) => { 102 | draft.element = { 103 | [path.id]: path, 104 | [group.id]: group, 105 | // [selector.id]: selector, 106 | } 107 | draft.group = { 108 | id: group.id, 109 | data: group, 110 | } 111 | draft.shape = { 112 | id: path.id, 113 | data: path, 114 | } 115 | draft.text = { 116 | data: {}, 117 | } 118 | draft.icon = { 119 | data: {}, 120 | } 121 | }) 122 | 123 | const getGroupElementFromDOM = document.getElementById( 124 | `${group.id}` 125 | ) 126 | // getGroupElementFromDOM.addEventListener('focus', onFocusHandler) 127 | // getGroupElementFromDOM.addEventListener('blur', onBlurHandler) 128 | 129 | // If component is in area of selection frame/tool, programmatically enable it's selector 130 | // if (selectedComponents.includes(props.id)) { 131 | // console.log('selectedComponents', selectedComponents) 132 | 133 | // // forcefully 134 | // // document.getElementById(`${group.id}`).focus(); 135 | 136 | // selector.update( 137 | // circle.getBoundingClientRect(true).left - 10, 138 | // circle.getBoundingClientRect(true).right + 10, 139 | // circle.getBoundingClientRect(true).top - 10, 140 | // circle.getBoundingClientRect(true).bottom + 10 141 | // ) 142 | // } 143 | 144 | // const { mousemove, mouseup } = handleDrag(two, group, 'Circle') 145 | 146 | // interact(`#${group.id}`).on('click', () => { 147 | // // two.scene.scale = 1 148 | // console.log('on click circle', group) 149 | // // selector.update( 150 | // // circle.getBoundingClientRect(true).left - 10, 151 | // // circle.getBoundingClientRect(true).right + 10, 152 | // // circle.getBoundingClientRect(true).top - 10, 153 | // // circle.getBoundingClientRect(true).bottom + 10 154 | // // ) 155 | // // two.update() 156 | // toggleToolbar(true) 157 | // }) 158 | } 159 | 160 | return () => { 161 | console.log('UNMOUNTING in Pencil', group) 162 | // clean garbage by removing instance 163 | // two.remove(group) 164 | } 165 | }, []) 166 | 167 | useEffect(() => { 168 | if (internalState?.group?.data) { 169 | let groupInstance = internalState.group.data 170 | groupInstance.translation.x = props.x 171 | groupInstance.translation.y = props.y 172 | two.update() 173 | } 174 | if (internalState?.shape?.data) { 175 | let shapeInstance = internalState.shape.data 176 | 177 | shapeInstance.width = props.width 178 | ? props.width 179 | : shapeInstance.width 180 | shapeInstance.height = props.height 181 | ? props.height 182 | : shapeInstance.height 183 | shapeInstance.fill = props.fill ? props.fill : shapeInstance.fill 184 | 185 | two.update() 186 | } 187 | }, [props.x, props.y, props.fill, props.width, props.height]) 188 | 189 | function closeToolbar() { 190 | toggleToolbar(false) 191 | } 192 | 193 | return ( 194 | 195 |
196 | {/* */} 197 | {showToolbar && ( 198 | { 204 | two.update() 205 | }} 206 | /> 207 | )} 208 |
209 | ) 210 | } 211 | 212 | export default Pencil 213 | -------------------------------------------------------------------------------- /src/components/elements/template.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import interact from 'interactjs'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { useImmer } from 'use-immer'; 5 | 6 | import getEditComponents from 'components/utils/editWrapper'; 7 | import { elementOnBlurHandler } from 'utils/misc'; 8 | import { setPeronsalInformation } from 'store/actions/main'; 9 | import { UPDATE_ELEMENT_DATA } from 'store/types'; 10 | import ElementFactory from 'factory/rectangle'; 11 | import Toolbar from 'components/floatingToolbar'; 12 | 13 | function Rectangle(props) { 14 | const selectedComponents = useSelector( 15 | (state) => state.main.selectedComponents 16 | ); 17 | const [showToolbar, toggleToolbar] = useState(false); 18 | const [internalState, setInternalState] = useImmer({}); 19 | const dispatch = useDispatch(); 20 | const two = props.twoJSInstance; 21 | let selectorInstance = null; 22 | let groupObject = null; 23 | 24 | function onBlurHandler(e) { 25 | elementOnBlurHandler(e, selectorInstance, two); 26 | } 27 | 28 | function onFocusHandler(e) { 29 | document.getElementById(`${groupObject.id}`).style.outline = 0; 30 | } 31 | 32 | // Using unmount phase to remove event listeners 33 | useEffect(() => { 34 | // Calculate x and y through dividing width and height by 2 or vice versa 35 | // if x and y are given then multiply width and height into 2 36 | const offsetHeight = 0; 37 | const prevX = localStorage.getItem('rectangle_coordX'); 38 | const prevY = localStorage.getItem('rectangle_coordY'); 39 | 40 | // Instantiate factory 41 | const elementFactory = new ElementFactory(two, prevX, prevY, {}); 42 | // Get all instances of every sub child element 43 | const { group, rectangle } = elementFactory.createElement(); 44 | 45 | if (props.parentGroup) { 46 | /** This element will be rendered and scoped in its parent group */ 47 | const parentGroup = props.parentGroup; 48 | parentGroup.add(rectangle); 49 | two.update(); 50 | } else { 51 | /** This element will render by creating it's own group wrapper */ 52 | groupObject = group; 53 | 54 | const { selector } = getEditComponents(two, group, 4); 55 | selectorInstance = selector; 56 | group.children.unshift(rectangle); 57 | two.update(); 58 | 59 | setInternalState((draft) => { 60 | draft.element = { 61 | [rectangle.id]: rectangle, 62 | [group.id]: group, 63 | // [selector.id]: selector, 64 | }; 65 | draft.group = { 66 | id: group.id, 67 | data: group, 68 | }; 69 | draft.shape = { 70 | type: 'rectangle', 71 | id: rectangle.id, 72 | data: rectangle, 73 | }; 74 | }); 75 | 76 | const getGroupElementFromDOM = document.getElementById(`${group.id}`); 77 | getGroupElementFromDOM.addEventListener('focus', onFocusHandler); 78 | getGroupElementFromDOM.addEventListener('blur', onBlurHandler); 79 | 80 | // If component is in area of selection frame/tool, programmatically enable it's selector 81 | if (selectedComponents.includes(props.id)) { 82 | console.log('selectedComponents', selectedComponents); 83 | 84 | selector.update( 85 | rectangle.getBoundingClientRect(true).left - 10, 86 | rectangle.getBoundingClientRect(true).right + 10, 87 | rectangle.getBoundingClientRect(true).top - 10, 88 | rectangle.getBoundingClientRect(true).bottom + 10 89 | ); 90 | } 91 | 92 | interact(`#${group.id}`).on('click', () => { 93 | console.log('on click '); 94 | selector.update( 95 | rectangle.getBoundingClientRect(true).left - 10, 96 | rectangle.getBoundingClientRect(true).right + 10, 97 | rectangle.getBoundingClientRect(true).top - 10, 98 | rectangle.getBoundingClientRect(true).bottom + 10 99 | ); 100 | two.update(); 101 | 102 | toggleToolbar(true); 103 | }); 104 | 105 | // RESIZE SHAPE LOGIC 106 | interact(`#${group.id}`).resizable({ 107 | edges: { right: true, left: true, top: true, bottom: true }, 108 | 109 | listeners: { 110 | move(event) { 111 | const target = event.target; 112 | const rect = event.rect; 113 | 114 | const minRectHeight = parseInt(rect.height / 2); 115 | const minRectWidth = parseInt(rect.width / 2); 116 | 117 | if (minRectHeight > 20 && minRectWidth > 20) { 118 | rectangle.width = rect.width; 119 | rectangle.height = rect.height; 120 | 121 | selector.update( 122 | rectangle.getBoundingClientRect(true).left - 10, 123 | rectangle.getBoundingClientRect(true).right + 10, 124 | rectangle.getBoundingClientRect(true).top - 10, 125 | rectangle.getBoundingClientRect(true).bottom + 10 126 | ); 127 | } 128 | 129 | two.update(); 130 | }, 131 | end(event) { 132 | console.log('the end'); 133 | }, 134 | }, 135 | }); 136 | 137 | // DRAG SHAPE LOGIC 138 | interact(`#${group.id}`).draggable({ 139 | // enable inertial throwing 140 | inertia: false, 141 | 142 | listeners: { 143 | start(event) { 144 | // console.log(event.type, event.target); 145 | }, 146 | move(event) { 147 | event.target.style.transform = `translate(${event.pageX}px, ${ 148 | event.pageY - offsetHeight 149 | }px)`; 150 | }, 151 | end(event) { 152 | console.log( 153 | 'event x', 154 | event.target.getBoundingClientRect(), 155 | event.rect.left, 156 | event.pageX, 157 | event.clientX 158 | ); 159 | // alternate -> take event.rect.left for x 160 | localStorage.setItem('rectangle_coordX', parseInt(event.pageX)); 161 | localStorage.setItem( 162 | 'rectangle_coordY', 163 | parseInt(event.pageY - offsetHeight) 164 | ); 165 | group.translation.x = event.pageX; 166 | two.update(); 167 | dispatch( 168 | setPeronsalInformation('COMPLETE', { 169 | data: {}, 170 | shapeObj: { rectangle }, 171 | fill: rectangle.fill, 172 | translationX: group.translation.x, 173 | translationY: group.translation.y, 174 | }) 175 | ); 176 | dispatch( 177 | setPeronsalInformation(UPDATE_ELEMENT_DATA, { 178 | data: { 179 | id: rectangle.id, 180 | property: 'x', 181 | value: group.translation.x, 182 | }, 183 | }) 184 | ); 185 | dispatch( 186 | setPeronsalInformation(UPDATE_ELEMENT_DATA, { 187 | data: { 188 | id: rectangle.id, 189 | property: 'y', 190 | value: group.translation.y, 191 | }, 192 | }) 193 | ); 194 | }, 195 | }, 196 | }); 197 | } 198 | 199 | return () => { 200 | console.log('UNMOUNTING in Rectangle', group); 201 | // clean garbage by removing instance 202 | two.remove(group); 203 | }; 204 | }, []); 205 | 206 | function closeToolbar() { 207 | toggleToolbar(false); 208 | } 209 | 210 | return ( 211 | 212 |
213 | {showToolbar && } 214 | {/* */} 215 | {showToolbar ? ( 216 | { 221 | two.update(); 222 | }} 223 | /> 224 | ) : null} 225 | {/* */} 226 |
227 | ); 228 | } 229 | 230 | export default Rectangle; 231 | -------------------------------------------------------------------------------- /src/components/sidebar/shareLinkPopup.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useEffect } from 'react' 2 | 3 | import Button from 'components/common/button' 4 | import LinkIcon from 'assets/link_white.svg' 5 | import CopyIcon from 'assets/copy.svg' 6 | 7 | const ShareLinkPopup = ({}) => { 8 | const refNode = useRef(null) 9 | const [showLink, setShowLink] = useState(false) 10 | 11 | useEffect(() => { 12 | document.addEventListener('mousedown', handleClick, false) 13 | 14 | // when component will unmount 15 | return () => { 16 | document.removeEventListener('mousedown', handleClick, false) 17 | } 18 | }, []) 19 | 20 | const handleClick = (e) => { 21 | if (refNode && refNode.current.contains(e.target)) { 22 | return 23 | } 24 | 25 | setShowLink(false) 26 | } 27 | 28 | return ( 29 | <> 30 |
31 | { 35 | e.preventDefault() 36 | setShowLink(!showLink) 37 | }} 38 | > 39 | Share 40 | 41 | 42 | 43 |
50 |
57 |
58 | Board Link (Public) 59 |
60 |
61 |
62 | {window.location.href} 63 |
64 |
{ 70 | e.stopPropagation() 71 | navigator?.clipboard?.writeText( 72 | window.location.href 73 | ) 74 | setShowLink(false) 75 | }} 76 | > 77 | 78 |
79 |
80 |
81 |
82 |
83 | 84 | ) 85 | } 86 | 87 | export default ShareLinkPopup 88 | -------------------------------------------------------------------------------- /src/components/sidebar/sidebar.css: -------------------------------------------------------------------------------- 1 | .secondary-sidebar-content .element-image-block { 2 | min-height: 100px; 3 | max-height: 100px; 4 | } 5 | 6 | .secondary-sidebar-content { 7 | font-weight: 600; 8 | } 9 | 10 | .tooltip-child { 11 | opacity: 0; /* define initial transition property */ 12 | -webkit-transition: all 0.2s ease-in-out; /* define transitions */ 13 | transition: all 0.2s ease-in-out; 14 | z-index: -1; 15 | } 16 | 17 | .tooltip-parent:hover .tooltip-child { 18 | opacity: 1; 19 | z-index: 1; 20 | } 21 | -------------------------------------------------------------------------------- /src/components/sidebar/tempPopup.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useEffect } from 'react' 2 | 3 | import Button from 'components/common/button' 4 | import LinkIcon from 'assets/link_white.svg' 5 | import CopyIcon from 'assets/copy.svg' 6 | 7 | const ShareLinkPopup = ({}) => { 8 | const refNode = useRef(null) 9 | const [showLink, setShowLink] = useState(false) 10 | 11 | useEffect(() => { 12 | document.addEventListener('mousedown', handleClick, false) 13 | 14 | // when component will unmount 15 | return () => { 16 | document.removeEventListener('mousedown', handleClick, false) 17 | } 18 | }, []) 19 | 20 | const handleClick = (e) => { 21 | if (refNode && refNode.current.contains(e.target)) { 22 | return 23 | } 24 | 25 | setShowLink(false) 26 | } 27 | 28 | return ( 29 | <> 30 |
31 | { 35 | e.preventDefault() 36 | setShowLink(!showLink) 37 | }} 38 | > 39 | Share 40 | 41 | 42 | 43 |
50 |
56 |
57 | Board Link (Public) 58 |
59 |
60 |
61 | {window.location.href} 62 |
63 |
{ 69 | e.stopPropagation() 70 | navigator?.clipboard?.writeText( 71 | window.location.href 72 | ) 73 | setShowLink(false) 74 | }} 75 | > 76 | 77 |
78 |
79 |
80 |
81 |
82 | 83 | ) 84 | } 85 | 86 | export default ShareLinkPopup 87 | -------------------------------------------------------------------------------- /src/components/sidebar/userDetailsPopup.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useEffect } from 'react' 2 | 3 | import Button from 'components/common/button' 4 | import LinkIcon from 'assets/link_white.svg' 5 | import CopyIcon from 'assets/copy.svg' 6 | 7 | const randomBgColors = [ 8 | '#BF2600', 9 | '#FF8B00', 10 | '#006644', 11 | '#008DA6', 12 | '#0747A6', 13 | '#403294', 14 | '#091E42', 15 | '#FF5630', 16 | '#FFAB00', 17 | '#36B37E', 18 | '#00B8D9', 19 | '#0065FF', 20 | '#6554C0', 21 | ] 22 | 23 | const UserDetailsPopup = ({}) => { 24 | const refNode = useRef(null) 25 | const [showLink, setShowLink] = useState(false) 26 | 27 | useEffect(() => { 28 | document.addEventListener('mousedown', handleClick, false) 29 | 30 | // when component will unmount 31 | return () => { 32 | document.removeEventListener('mousedown', handleClick, false) 33 | } 34 | }, []) 35 | 36 | const handleClick = (e) => { 37 | if (refNode && refNode.current.contains(e.target)) { 38 | return 39 | } 40 | 41 | setShowLink(false) 42 | } 43 | 44 | return ( 45 | <> 46 |
47 |
{ 50 | e.stopPropagation() 51 | setShowLink(!showLink) 52 | }} 53 | > 54 |
60 | M 61 |
62 |
68 | A 69 |
70 |
71 | 72 |
79 |
85 |
86 |
92 | M 93 |
94 |
95 | Meet Zaveri (You) 96 |
97 |
98 | 99 |
100 |
106 | A 107 |
108 |
109 | Arnub G. 110 |
111 |
112 |
113 |
114 |
115 | 116 | ) 117 | } 118 | 119 | export default UserDetailsPopup 120 | -------------------------------------------------------------------------------- /src/components/utils/borderStyleBox.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled, { css } from 'styled-components' 4 | import { allColorShades, essentialShades } from 'utils/constants' 5 | 6 | import { motion } from 'framer-motion' 7 | import ColorPicker from './colorPicker' 8 | 9 | const BorderStyleBoxContainer = styled.div` 10 | width: 250px; 11 | margin: 0 auto; 12 | height: auto; 13 | background: transparent; 14 | text-align: left; 15 | ` 16 | 17 | const BorderStyleBox = ({ 18 | currentType, 19 | currentWidth, 20 | currentColor, 21 | onChangeColor, 22 | onChangeBorderWidth, 23 | }) => { 24 | const renderBorderType = () => { 25 | const types = [ 26 | { value: 'solid', display: '—' }, 27 | { value: 'dashed', display: '- -' }, 28 | ].map((type, index) => { 29 | return ( 30 | 31 | 43 | 44 | ) 45 | }) 46 | return types 47 | } 48 | 49 | const renderBorderWidths = () => { 50 | const widths = [0, 1, 2, 4, 6].map((width, index) => { 51 | return ( 52 | 53 | 63 | 64 | ) 65 | }) 66 | return widths 67 | } 68 | 69 | return ( 70 | 71 | 72 | 73 |
78 | {renderBorderType()} 79 |
80 |
81 | 82 |
87 | {renderBorderWidths()} 88 |
89 |
90 | { 94 | onChangeColor(color) 95 | }} 96 | /> 97 |
98 |
99 | ) 100 | } 101 | 102 | // BorderStyleBox.defaultProps = { 103 | // currentType: 'solid', 104 | // currentWidth: '', 105 | // } 106 | 107 | export default BorderStyleBox 108 | -------------------------------------------------------------------------------- /src/components/utils/colorPicker.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled, { css } from 'styled-components' 4 | import { allColorShades, essentialShades } from 'utils/constants' 5 | import Icon from 'icons/icon' 6 | import { motion, AnimatePresence } from 'framer-motion' 7 | 8 | const ColorPickerContainer = styled.div` 9 | width: 250px; 10 | margin: 0 auto; 11 | height: auto; 12 | background: transparent; 13 | text-align: left; 14 | ` 15 | 16 | const ColorSelector = styled(motion.button)` 17 | border-radius: 50%; 18 | ${(props) => 19 | props.colorpaint && 20 | css` 21 | background: ${props.colorpaint}; 22 | border: ${props.colorpaint === `#FFFFFF` 23 | ? `1px solid #000` 24 | : `1px solid transparent`}; 25 | `} 26 | ` 27 | 28 | const CurrentColorIndicator = styled.div` 29 | border-radius: 50%; 30 | ${(props) => 31 | props.colorpaint && 32 | css` 33 | background: ${props.colorpaint}; 34 | `}; 35 | ` 36 | 37 | const ColorPicker = ({ title, onChangeComplete, currentColor }) => { 38 | const [showAllColors, ToggleShowAllColors] = useState(false) 39 | 40 | const renderColorBtns = () => { 41 | const colorsArr = showAllColors ? allColorShades : essentialShades 42 | const showColors = colorsArr.map((color, index) => { 43 | return ( 44 | 45 | 46 |
53 | { 67 | onChangeComplete(color) 68 | }} 69 | /> 70 |
71 |
72 |
73 | ) 74 | }) 75 | return showColors 76 | } 77 | 78 | return ( 79 | 80 | {title &&
{title}
} 81 | 86 | {/*
87 |
88 | {' '} 89 | { 99 | // onChangeComplete(color); 100 | // }} 101 | /> 102 |
103 | 116 |
*/} 117 | 118 |
119 |
120 | {renderColorBtns()}{' '} 121 | 129 |
130 |
131 |
132 |
133 | ) 134 | } 135 | 136 | // ColorPicker.defaultProps = { 137 | // leftPos: 4, 138 | // currentColor: '#fff', 139 | // } 140 | 141 | ColorPicker.propTypes = { 142 | leftPos: PropTypes.number, 143 | currentColor: PropTypes.string, 144 | onChangeComplete: PropTypes.func, 145 | } 146 | 147 | export default ColorPicker 148 | -------------------------------------------------------------------------------- /src/components/utils/dragger.js: -------------------------------------------------------------------------------- 1 | import Two from 'two.js' 2 | import { ZUI } from 'two.js/extras/jsm/zui' 3 | 4 | import { offsetHeight } from 'constants/misc' 5 | 6 | function handleDrag(twoJSInstance, group, el, cb) { 7 | console.log('group scene', group, twoJSInstance.scene) 8 | // zuifn() 9 | 10 | let domElement = group._renderer.elem 11 | let zui = new ZUI(group, domElement) 12 | document.getElementById(group.id).setAttribute('class', ' dragger-picker') 13 | let mouse = new Two.Vector(group.translation.x, group.translation.y) 14 | let touches = {} 15 | let distance = 0 16 | var dragging = false 17 | 18 | zui.addLimits(0.06, 8) 19 | twoJSInstance.update() 20 | // zui.addLimits(0.06, 8) 21 | console.log('mouse vector', mouse) 22 | domElement.addEventListener('mousedown', mousedown, false) 23 | // domElement.addEventListener('mousewheel', mousewheel, false) 24 | // domElement.addEventListener('wheel', mousewheel, false) 25 | 26 | // domElement.addEventListener('touchstart', touchstart, false) 27 | // domElement.addEventListener('touchmove', touchmove, false) 28 | // domElement.addEventListener('touchend', touchend, false) 29 | // domElement.addEventListener('touchcancel', touchend, false) 30 | 31 | function mousedown(e) { 32 | // console.log( 33 | // 'mouse event 1', 34 | // // e, 35 | // // mouse, 36 | // // group.translation, 37 | // twoJSInstance.scene.translation 38 | // ) 39 | var rect = group.getBoundingClientRect() 40 | dragging = 41 | mouse.x > rect.left && 42 | mouse.x < rect.right && 43 | mouse.y > rect.top && 44 | mouse.y < rect.bottom 45 | mouse.x = e.clientX 46 | mouse.y = e.clientY 47 | window.addEventListener('mousemove', mousemove, false) 48 | window.addEventListener('mouseup', mouseup, false) 49 | } 50 | 51 | function mousemove(e) { 52 | const displacementX = parseInt(localStorage.getItem('displacement_x')) 53 | const displacementY = parseInt(localStorage.getItem('displacement_y')) 54 | 55 | let dx = e.clientX - mouse.x 56 | let dy = e.clientY - mouse.y 57 | console.log( 58 | 'mouse event 2', 59 | e, 60 | group.getBoundingClientRect(), 61 | group.getBoundingClientRect(true), 62 | displacementX, 63 | displacementY 64 | // twoJSInstance.scene.translation.x, 65 | // scale 66 | ) 67 | // console.log('mouse event 2 domEle', domElement.getBoundingClientRect()) 68 | // console.log('mouse event 2 group', group.getBoundingClientRect()) 69 | // zui.translateSurface(dx, dy) 70 | // mouse.set(e.clientX, e.clientY) 71 | // group.translation.set( 72 | // e.clientX - displacementX, 73 | // e.clientY - displacementY 74 | // ) 75 | if (dragging) { 76 | group.position.x += dx / 1 77 | group.position.y += dy / 1 78 | } else { 79 | zui.translateSurface(dx, dy) 80 | } 81 | 82 | twoJSInstance.update() 83 | cb && cb() 84 | } 85 | 86 | function mouseup(e) { 87 | let scale = twoJSInstance.scene.scale 88 | // console.log('mouse event 3', e) 89 | 90 | // setting final data into LS cache 91 | localStorage.setItem( 92 | `${el}_coordX`, 93 | parseInt(e.clientX) + scale * twoJSInstance.scene.translation.x 94 | ) 95 | localStorage.setItem( 96 | `${el}_coordY`, 97 | parseInt( 98 | e.clientY + 99 | offsetHeight + 100 | scale * twoJSInstance.scene.translation.y 101 | ) 102 | ) 103 | 104 | window.removeEventListener('mousemove', mousemove, false) 105 | window.removeEventListener('mouseup', mouseup, false) 106 | twoJSInstance.update() 107 | } 108 | 109 | // function mousewheel(e) { 110 | // let dy = (e.wheelDeltaY || -e.deltaY) / 1000 111 | // zui.zoomBy(dy, e.clientX, e.clientY) 112 | // two.update() 113 | // } 114 | 115 | return { 116 | mousemove: mousemove, 117 | mouseup: mouseup, 118 | } 119 | } 120 | 121 | export default handleDrag 122 | -------------------------------------------------------------------------------- /src/components/utils/editWrapper.js: -------------------------------------------------------------------------------- 1 | import ObjectSelector from "components/utils/objectSelector"; 2 | import ToolBar from "components/utils/toolbarConnector"; 3 | 4 | const getEditComponents = (two, group, constant1) => { 5 | const selector = new ObjectSelector(two, group, 0, 0, 0, 0, constant1); 6 | selector.create(); 7 | 8 | const toolbar = new ToolBar(); 9 | toolbar.create(); 10 | 11 | return { selector, toolbar }; 12 | }; 13 | 14 | export default getEditComponents; 15 | -------------------------------------------------------------------------------- /src/components/utils/loader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const BounceLoader = () => { 4 | return 5 | } 6 | export default BounceLoader 7 | -------------------------------------------------------------------------------- /src/components/utils/objectSelector.js: -------------------------------------------------------------------------------- 1 | import Two from 'two.js' 2 | 3 | export default class Selector { 4 | constructor(instance, group, x1, x2, y1, y2, showCircles) { 5 | this.two = instance 6 | this.group = group 7 | this.area = null 8 | this.circle1 = null 9 | this.circle2 = null 10 | this.circle3 = null 11 | this.circle4 = null 12 | this.circleGroup = null 13 | this.showCircles = showCircles 14 | this.vertices = { 15 | x1, 16 | x2, 17 | y1, 18 | y2, 19 | } 20 | } 21 | create() { 22 | const { x1, x2, y1, y2 } = this.vertices 23 | // console.log("vertices", this.two, x1, x2, y1, y2); 24 | 25 | const area = this.two.makePath(x1, y1, x2, y1, x2, y2, x1, y2) 26 | area.fill = 'rgba(0,0,0,0)' 27 | area.opacity = 1 28 | area.linewidth = 2 29 | area.dashes[0] = 6 30 | area.stroke = '#505F79' 31 | // area.curved = true; 32 | // console.log("area", area); 33 | this.area = area 34 | 35 | let circleGroup = null 36 | if (this.showCircles) { 37 | // console.log("show circles 1", this.showCircles); 38 | switch (this.showCircles) { 39 | case 2: 40 | // console.log("falls in case 2"); 41 | const yAxisMidpoint = (y1 + y2) / 2 42 | const circleLeft = this.two.makeCircle(x1, yAxisMidpoint, 3) 43 | const circleRight = this.two.makeCircle( 44 | x2, 45 | yAxisMidpoint, 46 | 3 47 | ) 48 | // const circleGroup = this.two.makeGroup(circle1, circle2, circle3, circle4); 49 | this.circle1 = circleLeft 50 | this.circle2 = circleRight 51 | this.circle3 = null 52 | this.circle4 = null 53 | circleGroup = this.two.makeGroup(circleLeft, circleRight) 54 | // circleGroup.opacity = 0; 55 | this.circleGroup = circleGroup 56 | 57 | case 4: 58 | const circle1 = this.two.makeCircle(x1, y1, 4) 59 | const circle2 = this.two.makeCircle(x2, y1, 4) 60 | const circle3 = this.two.makeCircle(x2, y2, 4) 61 | const circle4 = this.two.makeCircle(x1, y2, 4) 62 | // const circleGroup = this.two.makeGroup(circle1, circle2, circle3, circle4); 63 | this.circle1 = circle1 64 | this.circle2 = circle2 65 | this.circle3 = circle3 66 | this.circle4 = circle4 67 | circleGroup = this.two.makeGroup( 68 | circle1, 69 | circle2, 70 | circle3, 71 | circle4 72 | ) 73 | circleGroup.linewidth = 1.5 74 | circleGroup.opacity = 0 75 | this.circleGroup = circleGroup 76 | break 77 | 78 | default: 79 | break 80 | } 81 | } 82 | 83 | const areaGroup = this.two.makeGroup(area, circleGroup) 84 | 85 | this.areaGroup = areaGroup 86 | this.group.add(areaGroup) 87 | this.two.update() 88 | 89 | const clearSelector = () => { 90 | this.areaGroup.opacity = 0 91 | } 92 | window.addEventListener('clearSelector', clearSelector, false) 93 | } 94 | 95 | show() { 96 | this.areaGroup.opacity = 1 97 | } 98 | 99 | hide() { 100 | this.areaGroup.opacity = 0 101 | } 102 | 103 | getInstance() { 104 | return this.areaGroup.id 105 | } 106 | 107 | update(x1, x2, y1, y2) { 108 | // console.log("on selector update", x1, x2, y1, y2); 109 | this.vertices = { 110 | x1, 111 | x2, 112 | y1, 113 | y2, 114 | } 115 | 116 | this.area.vertices = [ 117 | new Two.Anchor(x1, y1, null, null, null, null, Two.Commands.line), 118 | new Two.Anchor(x2, y1, null, null, null, null, Two.Commands.line), 119 | 120 | new Two.Anchor(x2, y2, null, null, null, null, Two.Commands.line), 121 | new Two.Anchor(x1, y2, null, null, null, null, Two.Commands.line), 122 | ] 123 | 124 | if (this.showCircles) { 125 | this.circleGroup.opacity = 1 126 | // console.log("show circles 2", this.showCircles); 127 | switch (this.showCircles) { 128 | case 2: 129 | const yAxisMidpoint = (y1 + y2) / 2 130 | this.circle1.translation.set(x1, yAxisMidpoint) 131 | this.circle2.translation.set(x2, yAxisMidpoint) 132 | break 133 | case 4: 134 | this.circle1.translation.set(x1, y1) 135 | this.circle2.translation.set(x2, y1) 136 | this.circle3.translation.set(x2, y2) 137 | this.circle4.translation.set(x1, y2) 138 | break 139 | default: 140 | break 141 | } 142 | } 143 | 144 | this.areaGroup.opacity = 1 145 | this.two.update() 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/components/utils/opacitySlider.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useEffect, useState, useRef } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled, { css } from 'styled-components' 4 | import { useRanger, Ranger } from '@tanstack/react-ranger' 5 | import { motion } from 'framer-motion' 6 | 7 | const SliderContainer = styled.div` 8 | width: 250px; 9 | height: 44px; 10 | border-radius: 6px; 11 | ` 12 | 13 | const Track = styled('div')` 14 | display: inline-block; 15 | height: 4px; 16 | width: 90%; 17 | margin: 0; 18 | ` 19 | 20 | const Tick = styled('div')` 21 | :before { 22 | content: ''; 23 | position: absolute; 24 | left: 0px; 25 | background: #0052cc; 26 | height: 6px; 27 | width: 5px; 28 | border-radius: 50%; 29 | top: -2px; 30 | } 31 | ` 32 | 33 | const TickLabel = styled('div')` 34 | position: absolute; 35 | font-size: 0.6rem; 36 | color: rgba(0, 0, 0, 0.5); 37 | top: 100%; 38 | transform: translate(-50%, 1.2rem); 39 | white-space: nowrap; 40 | ` 41 | 42 | const Segment = styled('div')` 43 | background: ${(props) => 44 | props.index === 0 45 | ? '#0052CC' 46 | : props.index === 1 47 | ? '#0052CC' 48 | : props.index === 2 49 | ? '#f5c200' 50 | : '#ff6050'}; 51 | height: 50%; 52 | ` 53 | 54 | const Handle = styled('div')` 55 | position: absolute; 56 | top: -7px; 57 | background: #0052cc; 58 | width: 0.8rem; 59 | height: 0.8rem; 60 | border-radius: 100%; 61 | left: -2px; 62 | cursor: pointer; 63 | ` 64 | 65 | const OpacitySlider = ({ 66 | title, 67 | handleOnChange, 68 | handleOnDrag, 69 | currentOpacity, 70 | }) => { 71 | // const [values, setValues] = React.useState([1]) 72 | const [values, setValues] = useState([1]) 73 | const rangerRef = useRef(null) 74 | 75 | const rangerInstance = useRanger({ 76 | getRangerElement: () => rangerRef.current, 77 | values, 78 | min: 0, 79 | max: 1, 80 | stepSize: 0.1, 81 | onDrag: (instance) => { 82 | setValues(instance.sortedValues) 83 | handleOnDrag(instance.sortedValues) 84 | }, 85 | onChange: (instance) => handleOnChange(instance.sortedValues), 86 | }) 87 | 88 | // const { getTrackProps, ticks, segments, handles } = useRanger({ 89 | // values, 90 | // onDrag: (e) => { 91 | // console.log('on drag ranger', e) 92 | // setValues(e) 93 | // handleOnDrag(e) 94 | // }, 95 | // onChange: (e) => { 96 | // console.log('on change ranger', e) 97 | // setValues(e) 98 | // handleOnChange(e) 99 | // }, 100 | // min: 0, 101 | // max: 1, 102 | // steps: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1], 103 | // ticks: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], 104 | // }) 105 | 106 | useEffect(() => { 107 | let arr = currentOpacity ? [currentOpacity] : [0] 108 | setValues(arr) 109 | }, []) 110 | 111 | return ( 112 | 113 | 117 | {title &&
{title}
} 118 | {/* 123 | {ticks.map(({ value, getTickProps }) => ( 124 | 128 | 129 | 130 | ))} 131 | {segments.map(({ getSegmentProps }, i) => ( 132 | 137 | ))} 138 | {handles.map(({ value, active, getHandleProps }) => ( 139 |
143 | 147 |
148 | ))} 149 | */} 150 |
162 | {rangerInstance 163 | .handles() 164 | .map( 165 | ( 166 | { 167 | value, 168 | onKeyDownHandler, 169 | onMouseDownHandler, 170 | onTouchStart, 171 | isActive, 172 | }, 173 | i 174 | ) => ( 175 |
204 |
205 |
206 | ) 207 | } 208 | 209 | // OpacitySlider.defaultProps = { 210 | // currentOpacity: 1, 211 | // } 212 | 213 | OpacitySlider.propTypes = { 214 | currentOpacity: PropTypes.number, 215 | handleOnChange: PropTypes.func, 216 | } 217 | 218 | export default OpacitySlider 219 | -------------------------------------------------------------------------------- /src/components/utils/toolbarConnector.js: -------------------------------------------------------------------------------- 1 | import { properties } from "utils/constants"; 2 | 3 | export default class ToolBar { 4 | constructor() { 5 | this.toolBarDOM = document.getElementById("floating-toolbar"); 6 | } 7 | create_color_bg() { 8 | // contains DOM creation and styling part 9 | } 10 | create_color_text() { 11 | // contains DOM creation and styling part 12 | } 13 | create_color_icon() { 14 | // contains DOM creation and styling part 15 | } 16 | create_font_size() { 17 | // contains DOM creation and styling part 18 | } 19 | create_font_weight() { 20 | // contains DOM creation and styling part 21 | } 22 | create_alignment() { 23 | // contains DOM creation and styling part 24 | } 25 | create_border_color() { 26 | // contains DOM creation and styling part 27 | } 28 | create_border_width() { 29 | // contains DOM creation and styling part 30 | } 31 | create_link_url() { 32 | // contains DOM creation and styling part 33 | } 34 | create_opacity() { 35 | // contains DOM creation and styling part 36 | } 37 | create() { 38 | // create bare bones for toolbar 39 | // this.toolBarDOM.style.left = "500px"; 40 | // this.toolBarDOM.style.top = "300px"; 41 | } 42 | hide() { 43 | // this.toolBarDOM.hidden = true; 44 | } 45 | forceHide(callback) { 46 | // this.toolBarDOM.hidden = true; 47 | // this.toolBarDOM.removeEventListener("blur", callback); 48 | } 49 | show() { 50 | // this.toolBarDOM.style.visibility = "visible"; 51 | // console.log("on show toolbar connector"); 52 | // this.toolBarDOM.hidden = false; 53 | } 54 | shift(pageX, pageY) { 55 | // this.toolBarDOM.style.left = `${pageX}px`; 56 | // this.toolBarDOM.style.top = `${ 57 | // pageY - this.toolBarDOM.getBoundingClientRect().height 58 | // }px`; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/utils/zoomer.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { useImmer } from 'use-immer' 3 | 4 | import AddSVG from 'assets/add_icon_only.svg' 5 | import SubtractSVG from 'assets/subtract.svg' 6 | 7 | const CanvasZoomer = (props) => { 8 | const [scale, setScale] = useState(1) 9 | 10 | const handleZoomIn = (e) => { 11 | console.log('props.scene', props.scene) 12 | setScale(scale + 0.2) 13 | props.sceneInstance.scene.scale = scale + 0.2 14 | props.sceneInstance.update() 15 | } 16 | 17 | const handleZoomOut = (e) => { 18 | console.log('props.scene', props.scene) 19 | setScale(scale - 0.2) 20 | props.sceneInstance.scene.scale = scale - 0.2 21 | props.sceneInstance.update() 22 | } 23 | 24 | return ( 25 | 26 | 40 | 41 | ) 42 | } 43 | 44 | export default CanvasZoomer 45 | -------------------------------------------------------------------------------- /src/constants/elementSchema.js: -------------------------------------------------------------------------------- 1 | const avatar = { 2 | id: '', 3 | name: 'avatar', 4 | width: 0, 5 | height: 0, 6 | x: 0, 7 | y: 0, 8 | icon_name: '', 9 | bg_color: '', 10 | icon_color: '', 11 | } 12 | 13 | const button = { 14 | id: '', 15 | name: 'button', 16 | width: 0, 17 | height: 0, 18 | x: 0, 19 | y: 0, 20 | bg_color: '', 21 | text: '', 22 | text_color: '', 23 | } 24 | 25 | const buttonWithIcon = { 26 | id: '', 27 | name: 'buttonWithIcon', 28 | width: 0, 29 | height: 0, 30 | x: 0, 31 | y: 0, 32 | icon_name: '', 33 | bg_color: '', 34 | icon_color: '', 35 | text: '', 36 | text_color: '', 37 | } 38 | 39 | const checkbox = { 40 | id: '', 41 | name: 'checkbox', 42 | width: 0, 43 | height: 0, 44 | x: 0, 45 | y: 0, 46 | text_arr: [], 47 | checked_indices: [], 48 | theme_color: '', 49 | } 50 | 51 | const radiobox = { 52 | id: '', 53 | name: 'radiobox', 54 | width: 0, 55 | height: 0, 56 | x: 0, 57 | y: 0, 58 | text_arr: [], 59 | checked_indices: [], 60 | theme_color: '', 61 | } 62 | 63 | const circle = { 64 | id: '', 65 | name: 'circle', 66 | width: 0, 67 | height: 0, 68 | x: 0, 69 | y: 0, 70 | bg_color: '', 71 | } 72 | 73 | const divider = { 74 | id: '', 75 | name: 'divider', 76 | width: 0, 77 | height: 0, 78 | x: 0, 79 | y: 0, 80 | start_pos: '', 81 | end_pos: '', 82 | stroke_color: '', 83 | } 84 | 85 | const imageCard = { 86 | id: '', 87 | name: 'imageCard', 88 | width: 0, 89 | height: 0, 90 | x: 0, 91 | y: 0, 92 | icon_name: '', 93 | bg_color: '', 94 | icon_color: '', 95 | } 96 | 97 | const linkWithIcon = { 98 | id: '', 99 | name: 'linkWithIcon', 100 | width: 0, 101 | height: 0, 102 | x: 0, 103 | y: 0, 104 | icon_name: '', 105 | icon_color: '', 106 | text_color: '', 107 | text: '', 108 | } 109 | 110 | const overlay = { 111 | id: '', 112 | name: 'overlay', 113 | width: 0, 114 | height: 0, 115 | x: 0, 116 | y: 0, 117 | bg_color: '', 118 | opacity: '', 119 | } 120 | 121 | const rectangle = { 122 | id: '', 123 | name: 'rectangle', 124 | width: 0, 125 | height: 0, 126 | x: 0, 127 | y: 0, 128 | bg_color: '', 129 | } 130 | 131 | const text = { 132 | id: '', 133 | name: 'text', 134 | width: 0, 135 | height: 0, 136 | x: 0, 137 | y: 0, 138 | text: '', 139 | text_color: '', 140 | } 141 | 142 | const textarea = { 143 | id: '', 144 | name: 'textarea', 145 | width: 0, 146 | height: 0, 147 | x: 0, 148 | y: 0, 149 | text: '', 150 | text_color: '', 151 | } 152 | 153 | const textinput = { 154 | id: '', 155 | name: 'textinput', 156 | width: 0, 157 | height: 0, 158 | x: 0, 159 | y: 0, 160 | text: '', 161 | text_color: '', 162 | } 163 | 164 | const toggle = { 165 | id: '', 166 | name: 'toggle', 167 | width: 0, 168 | height: 0, 169 | x: 0, 170 | y: 0, 171 | actives: false, 172 | theme_color: '', 173 | } 174 | -------------------------------------------------------------------------------- /src/constants/exportHooks.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useMediaQuery } from 'react-responsive' 3 | 4 | export const useMediaQueryUtils = () => { 5 | const isDesktop = useMediaQuery({ minWidth: 1280, maxWidth: 1535 }) 6 | const isLaptop = useMediaQuery({ minWidth: 1024, maxWidth: 1279 }) 7 | const isTablet = useMediaQuery({ minWidth: 768, maxWidth: 1023 }) 8 | const isMobile = useMediaQuery({ maxWidth: 767 }) 9 | 10 | return { 11 | isDesktop, 12 | isTablet, 13 | isMobile, 14 | isLaptop, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/constants/misc.js: -------------------------------------------------------------------------------- 1 | export const offsetHeight = 0 2 | export const GROUP_COMPONENT = 'groupobject' 3 | -------------------------------------------------------------------------------- /src/constants/properties.js: -------------------------------------------------------------------------------- 1 | export const WIDTH = 'width' 2 | export const HEIGHT = 'height' 3 | export const TYPE = 'type' 4 | export const STROKE = 'stroke' 5 | export const FILL = 'fill' 6 | export const OPACITY = 'opacity' 7 | export const ROTATION = 'rotation' 8 | export const SCALE = 'scale' 9 | export const TRANSLATION = 'translation' 10 | -------------------------------------------------------------------------------- /src/factory/arrowLine.js: -------------------------------------------------------------------------------- 1 | import Main from './main' 2 | 3 | export default class ArrowLineFactory extends Main { 4 | createElement() { 5 | const two = this.two 6 | const prevX = this.x 7 | const prevY = this.y 8 | const { fill, x1, x2, y1, y2 } = this.properties 9 | 10 | // console.log('arrow line factory x1, y1, x2, y2', x1, y1, x2, y2) 11 | let line = two.makeArrow(x1, y1, x2, y2) 12 | line.linewidth = 2 13 | // line.vertices[1].y = 200 14 | // const centerPointCircle = two.makeEllipse(0, 0, 5, 5) 15 | // centerPointCircle.fill = '#FFF' 16 | // centerPointCircle.stroke = '#0052CC' 17 | // centerPointCircle.linewidth = 2 18 | 19 | const pointCircle1 = two.makeEllipse(0, 0, 5, 5) 20 | pointCircle1.fill = '#FFF' 21 | pointCircle1.stroke = '#0052CC' 22 | pointCircle1.linewidth = 2 23 | // pointCircle1.noStroke() 24 | 25 | const pointCircle2 = two.makeEllipse(0, 0, 5, 5) 26 | pointCircle2.fill = '#FFF' 27 | pointCircle2.stroke = '#0052CC' 28 | pointCircle2.linewidth = 2 29 | // pointCircle2.noStroke() 30 | 31 | // const resizeLine = two.makeGroup(pointCircle1, pointCircle2) 32 | // resizeLine.translation.y = 0 - line.linewidth 33 | // resizeLine.opacity = 0 34 | 35 | let pointCircle1Group = two.makeGroup(pointCircle1) 36 | 37 | let pointCircle2Group = two.makeGroup(pointCircle2) 38 | 39 | let group = two.makeGroup( 40 | line, 41 | pointCircle1Group, 42 | pointCircle2Group 43 | // centerPointCircle 44 | ) 45 | // console.log('main group', group.getBoundingClientRect()) 46 | 47 | // Overriding the circle point group's coordinate and 48 | // manipulating it with line's coordinate 49 | pointCircle1Group.translation.x = line.vertices[0].x 50 | pointCircle1Group.translation.y = line.vertices[0].y 51 | pointCircle2Group.translation.x = line.vertices[1].x 52 | pointCircle2Group.translation.y = line.vertices[1].y 53 | pointCircle1Group.opacity = 0 54 | pointCircle2Group.opacity = 0 55 | // const calcX = parseInt(prevX) + (parseInt(rectangle.width / 2) - 10); 56 | // const calcY = parseInt(prevY) - (parseInt(46) - parseInt(rectangle.height / 2)); 57 | 58 | group.translation.x = parseInt(prevX) 59 | group.translation.y = parseInt(prevY) 60 | 61 | // centerPointCircle.translation.x = line.translation.x 62 | // centerPointCircle.translation.y = line.translation.y 63 | 64 | return { 65 | group, 66 | pointCircle1Group, 67 | pointCircle2Group, 68 | pointCircle1, 69 | pointCircle2, 70 | line, 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/factory/avatar.js: -------------------------------------------------------------------------------- 1 | import Icon from 'icons/icons' 2 | import Main from './main' 3 | import { color_blue } from 'utils/constants' 4 | 5 | export default class AvatarFactory extends Main { 6 | createElement() { 7 | const two = this.two 8 | const prevX = this.x 9 | const prevY = this.y 10 | const { 11 | width = 70, 12 | height = 70, 13 | radius, 14 | fill = color_blue, 15 | 16 | iconStroke, 17 | stroke, 18 | linewidth, 19 | children = {}, 20 | } = this.properties 21 | 22 | const circle = two.makeCircle(0, 0, 0) 23 | circle.width = width 24 | circle.height = height 25 | circle.radius = parseInt(width / 2) 26 | circle.fill = fill 27 | 28 | circle.stroke = stroke ? stroke : '#fff' 29 | circle.linewidth = linewidth ? linewidth : 0 30 | 31 | let iconType = children?.icon?.iconType 32 | ? children?.icon?.iconType 33 | : 'ICON_IMAGE_AVATAR_WHITE' 34 | // creates svg with proper template 35 | const svgImage = new DOMParser().parseFromString( 36 | Icon[iconType].data, 37 | 'text/xml' 38 | ) 39 | 40 | const externalSVG = two.interpret(svgImage.firstChild) 41 | // console.log("svgImage", svgImage, circle.width / 2); 42 | externalSVG.scale = children?.icon?.iconScale 43 | ? children?.icon?.iconScale 44 | : 1 45 | externalSVG.stroke = iconStroke ? iconStroke : '#fff' 46 | externalSVG.fill = 'transparent' 47 | 48 | const externalSVGGroup = two.makeGroup(externalSVG) 49 | externalSVGGroup.center() 50 | const circleSvgGroup = two.makeGroup(circle, externalSVGGroup) 51 | 52 | const group = two.makeGroup(circleSvgGroup) 53 | group.center() 54 | group.translation.x = parseInt(prevX) 55 | group.translation.y = parseInt(prevY) 56 | 57 | return { group, circleSvgGroup, circle, externalSVG, externalSVGGroup } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/factory/button.js: -------------------------------------------------------------------------------- 1 | import Main from './main' 2 | import { color_blue } from 'utils/constants' 3 | 4 | export default class ButtonFactory extends Main { 5 | createElement() { 6 | const two = this.two 7 | const prevX = this.x 8 | const prevY = this.y 9 | const { 10 | width = 70, 11 | height = 70, 12 | fill = color_blue, 13 | textColor, 14 | stroke, 15 | linewidth, 16 | children = {}, 17 | } = this.properties 18 | 19 | const rectangle = two.makeRoundedRectangle(0, 0, 140, 45, 5) 20 | rectangle.width = width 21 | rectangle.height = height 22 | rectangle.fill = fill 23 | 24 | rectangle.stroke = stroke ? stroke : '#fff' 25 | rectangle.linewidth = linewidth ? linewidth : 0 26 | 27 | const text = two.makeText('Button', 10, 0) 28 | 29 | text.value = children?.text?.value || 'Button' 30 | text.size = children?.text?.size || '16' 31 | text.fill = textColor || '#fff' 32 | text.weight = children?.text?.weight || '500' 33 | 34 | const textGroup = two.makeGroup(text) 35 | textGroup.center() 36 | const rectGroup = two.makeGroup(rectangle) 37 | const rectTextGroup = two.makeGroup(rectGroup, textGroup) 38 | 39 | const group = two.makeGroup(rectTextGroup) 40 | group.translation.x = parseInt(prevX) 41 | group.translation.y = parseInt(prevY) 42 | 43 | return { group, rectTextGroup, text, textGroup, rectangle } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/factory/buttonwithicon.js: -------------------------------------------------------------------------------- 1 | import Icon from 'icons/icons' 2 | import Main from './main' 3 | import { color_blue } from 'utils/constants' 4 | 5 | export default class ButtonWithIconFactory extends Main { 6 | createElement() { 7 | const two = this.two 8 | const prevX = this.x 9 | const prevY = this.y 10 | const { 11 | width = 70, 12 | height = 70, 13 | fill = color_blue, 14 | textColor, 15 | stroke, 16 | linewidth, 17 | children = {}, 18 | } = this.properties 19 | 20 | // Implement core element 21 | 22 | const text = two.makeText('Button', -15, 0) 23 | text.value = children?.text?.value || 'Button' 24 | text.size = children?.text?.size || '16' 25 | text.fill = textColor || '#fff' 26 | text.weight = children?.text?.weight || '500' 27 | // text.baseline = "sub"; 28 | text.alignment = 'right' 29 | 30 | let iconType = children?.icon?.iconType 31 | ? children?.icon?.iconType 32 | : 'ICON_IMAGE_PHONE_WHITE' 33 | 34 | // Implement custom svg 35 | const svgImage = new DOMParser().parseFromString( 36 | Icon[iconType].data, 37 | 'text/xml' 38 | ) 39 | // console.log("svgImage", svgImage); 40 | const externalSVG = two.interpret(svgImage.firstChild) 41 | // externalSVG.translation.x = -10 42 | // externalSVG.translation.y = -1 43 | externalSVG.scale = children?.icon?.iconScale 44 | ? children?.icon?.iconScale 45 | : 0.8 46 | // externalSVG.center() 47 | 48 | let externalSVGGroup = two.makeGroup(externalSVG) 49 | externalSVGGroup.center() 50 | 51 | let textGroup = two.makeGroup(text) 52 | // textGroup.center() 53 | // console.log("textGroup", textGroup, textGroup.id); 54 | 55 | const textSvgGroup = two.makeGroup(externalSVGGroup, textGroup) 56 | // textSvgGroup.translation.x = -10 57 | textSvgGroup.center() 58 | 59 | const rectangle = two.makeRoundedRectangle(0, 0, 140, 45, 5) 60 | rectangle.width = width 61 | rectangle.height = height 62 | rectangle.fill = fill 63 | if (stroke && linewidth) { 64 | rectangle.stroke = stroke 65 | rectangle.linewidth = linewidth 66 | } else { 67 | rectangle.noStroke() 68 | } 69 | 70 | const rectTextSvgGroup = two.makeGroup(rectangle, textSvgGroup) 71 | // rectTextSvgGroup.center() 72 | rectangle.noStroke() 73 | const group = two.makeGroup(rectTextSvgGroup) 74 | 75 | // group.center() 76 | group.translation.x = parseInt(prevX) 77 | group.translation.y = parseInt(prevY) 78 | 79 | // Implement external layer of rectangle 80 | // const rectangle = two.makePath( 81 | // group.getBoundingClientRect(true).left - 40, 82 | // group.getBoundingClientRect(true).top - 10, 83 | 84 | // group.getBoundingClientRect(true).right + 10, 85 | // group.getBoundingClientRect(true).top - 10, 86 | 87 | // group.getBoundingClientRect(true).right + 10, 88 | // group.getBoundingClientRect(true).bottom + 10, 89 | 90 | // group.getBoundingClientRect(true).left - 40, 91 | // group.getBoundingClientRect(true).bottom + 10 92 | // ) 93 | // rectangle.fill = fill 94 | 95 | // rectangle.linewidth = 8 96 | // rectangle.join = 'round' 97 | 98 | // if (stroke) { 99 | // rectangle.stroke = stroke 100 | // // rectangle.linewidth = linewidth 101 | // } else { 102 | // rectangle.noStroke() 103 | // } 104 | return { 105 | group, 106 | text, 107 | rectangle, 108 | textSvgGroup, 109 | externalSVG, 110 | rectTextSvgGroup, 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/factory/circle.js: -------------------------------------------------------------------------------- 1 | import Main from './main' 2 | import { color_blue } from 'utils/constants' 3 | 4 | export default class CircleFactory extends Main { 5 | createElement() { 6 | const two = this.two 7 | const prevX = this.x 8 | const prevY = this.y 9 | const { fill, width, height, radius, stroke, linewidth } = 10 | this.properties 11 | 12 | // Implement core element 13 | const circle = two.makeEllipse(0, 0, 0, 0) 14 | circle.width = width || 100 15 | circle.height = height || 100 16 | circle.fill = fill ? fill : color_blue 17 | 18 | circle.stroke = stroke ? stroke : '#fff' 19 | circle.linewidth = linewidth ? linewidth : 0 20 | 21 | this.circle = circle 22 | 23 | // Create group and take children elements as a parameter 24 | const group = two.makeGroup(circle) 25 | group.translation.x = parseInt(prevX) 26 | group.translation.y = parseInt(prevY) 27 | this.group = group 28 | // console.log('group.id circle', group.id) 29 | return { group: this.group, circle: this.circle } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/factory/divider.js: -------------------------------------------------------------------------------- 1 | import Main from './main' 2 | 3 | export default class DividerFactory extends Main { 4 | createElement() { 5 | const two = this.two 6 | const prevX = this.x 7 | const prevY = this.y 8 | const { fill, x1, x2, y1, y2 } = this.properties 9 | 10 | let line = two.makeLine(x1, y1, x2, y2) 11 | line.linewidth = 3 12 | 13 | const pointCircle1 = two.makeEllipse(0, 0, 5, 5) 14 | pointCircle1.fill = '#FFF' 15 | pointCircle1.stroke = '#0052CC' 16 | // pointCircle1.noStroke() 17 | 18 | const pointCircle2 = two.makeEllipse(10, 0, 5, 5) 19 | pointCircle2.fill = '#FFF' 20 | pointCircle2.stroke = '#0052CC' 21 | // pointCircle2.noStroke() 22 | 23 | const resizeLine = two.makeGroup(pointCircle1, pointCircle2) 24 | resizeLine.translation.y = 0 - line.linewidth 25 | resizeLine.opacity = 0 26 | 27 | let group = two.makeGroup(line, resizeLine) 28 | console.log('main group', group.getBoundingClientRect()) 29 | 30 | // Overriding the circle point group's coordinate and 31 | // manipulating it with line's coordinate 32 | pointCircle1.translation.x = line.vertices[0].x - 0 33 | pointCircle1.translation.y = line.vertices[0].y - 0 34 | pointCircle2.translation.x = line.vertices[1].x + 0 35 | pointCircle2.translation.y = line.vertices[1].y - 0 36 | 37 | // const calcX = parseInt(prevX) + (parseInt(rectangle.width / 2) - 10); 38 | // const calcY = parseInt(prevY) - (parseInt(46) - parseInt(rectangle.height / 2)); 39 | group.center() 40 | group.translation.x = parseInt(prevX) 41 | group.translation.y = parseInt(prevY) 42 | 43 | return { group, pointCircle1, pointCircle2, resizeLine, line } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/factory/dropdown.js: -------------------------------------------------------------------------------- 1 | import Main from './main' 2 | 3 | export default class DropdownFactory extends Main { 4 | createElement() { 5 | const two = this.two 6 | const prevX = this.x 7 | const prevY = this.y 8 | const { fill } = this.properties 9 | 10 | // Implement core element 11 | const circle = two.makeEllipse(0, 0, 70, 70) 12 | circle.fill = fill ? fill : '#EBECF0' 13 | circle.noStroke() 14 | this.circle = circle 15 | 16 | // Create group and take children elements as a parameter 17 | const group = two.makeGroup(circle) 18 | group.translation.x = parseInt(prevX) 19 | group.translation.y = parseInt(prevY) 20 | this.group = group 21 | return { group: this.group, circle: this.circle } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/factory/frame.js: -------------------------------------------------------------------------------- 1 | import Main from './main' 2 | import { color_blue } from 'utils/constants' 3 | 4 | export default class FrameFactory extends Main { 5 | createElement() { 6 | const two = this.two 7 | const prevX = this.x 8 | const prevY = this.y 9 | const { fill, width, height, stroke, linewidth } = this.properties 10 | 11 | // Implement core element 12 | 13 | const rectangle = two.makeRectangle(0, 0, width || 210, height || 110) 14 | 15 | if (stroke && linewidth) { 16 | rectangle.stroke = stroke 17 | rectangle.linewidth = linewidth 18 | } else { 19 | rectangle.stroke = '#000' 20 | rectangle.linewidth = 1 21 | // rectangle.noStroke() 22 | } 23 | 24 | rectangle.fill = 'transparent' 25 | rectangle.stroke = '#000' 26 | rectangle.linewidth = 2 27 | 28 | console.log('rectangle', rectangle.getBoundingClientRect()) 29 | 30 | const group = two.makeGroup(rectangle) 31 | 32 | group.translation.x = parseInt(prevX) 33 | group.translation.y = parseInt(prevY) 34 | 35 | return { group, rectangle } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/factory/imagecard.js: -------------------------------------------------------------------------------- 1 | import Icon from 'icons/icons' 2 | import Main from './main' 3 | import { color_blue } from 'utils/constants' 4 | 5 | export default class ImageCardFactory extends Main { 6 | createElement() { 7 | const two = this.two 8 | const prevX = this.x 9 | const prevY = this.y 10 | const { 11 | width = 70, 12 | height = 70, 13 | radius, 14 | fill = color_blue, 15 | 16 | iconStroke, 17 | stroke, 18 | linewidth, 19 | children = {}, 20 | } = this.properties 21 | 22 | const rectangle = two.makeRectangle(0, 0, 60, 60) 23 | rectangle.width = width 24 | rectangle.height = height 25 | rectangle.fill = fill 26 | 27 | rectangle.stroke = stroke ? stroke : '#fff' 28 | rectangle.linewidth = linewidth ? linewidth : 0 29 | 30 | let iconType = children?.icon?.iconType 31 | ? children?.icon?.iconType 32 | : 'ICON_IMAGE_AVATAR_WHITE' 33 | 34 | const svgImage = new DOMParser().parseFromString( 35 | Icon[iconType].data, 36 | 'text/xml' 37 | ) 38 | // console.log("svgImage", svgImage, rectangle.width / 2); 39 | const externalSVG = two.interpret(svgImage.firstChild) 40 | externalSVG.scale = children?.icon?.iconScale 41 | ? children?.icon?.iconScale 42 | : 1 43 | externalSVG.stroke = iconStroke ? iconStroke : '#fff' 44 | 45 | const externalSVGGroup = two.makeGroup(externalSVG) 46 | externalSVGGroup.center() 47 | const rectSvgGroup = two.makeGroup(rectangle, externalSVGGroup) 48 | 49 | const group = two.makeGroup(rectSvgGroup) 50 | group.center() 51 | group.translation.x = parseInt(prevX) 52 | group.translation.y = parseInt(prevY) 53 | 54 | return { group, rectSvgGroup, externalSVG, externalSVGGroup, rectangle } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/factory/linkwithicon.js: -------------------------------------------------------------------------------- 1 | import Icon from 'icons/icons' 2 | import Main from './main' 3 | import { color_blue } from 'utils/constants' 4 | 5 | export default class LinkWithIconFactory extends Main { 6 | createElement() { 7 | const two = this.two 8 | const prevX = this.x 9 | const prevY = this.y 10 | const { 11 | width = 70, 12 | height = 70, 13 | fill = color_blue, 14 | 15 | stroke, 16 | linewidth, 17 | children = {}, 18 | } = this.properties 19 | 20 | // Implement core element 21 | const text = two.makeText('Link', -15, 0) 22 | text.value = children?.text?.value || 'Button' 23 | text.size = children?.text?.size || '16' 24 | text.weight = children?.text?.weight || '500' 25 | text.decoration = 'underline' 26 | text.size = 18 27 | // text.baseline = "sub"; 28 | text.alignment = 'right' 29 | 30 | let iconType = children?.icon?.iconType 31 | ? children?.icon?.iconType 32 | : 'ICON_IMAGE_PHONE_WHITE' 33 | const svgImage = new DOMParser().parseFromString( 34 | Icon[iconType].data, 35 | 'text/xml' 36 | ) 37 | // console.log("svgImage", svgImage); 38 | const externalSVG = two.interpret(svgImage.firstChild) 39 | // externalSVG.translation.x = -3 40 | // externalSVG.translation.y = -1 41 | externalSVG.scale = children?.icon?.iconScale 42 | ? children?.icon?.iconScale 43 | : 1.2 44 | externalSVG.stroke = fill 45 | 46 | let externalSVGGroup = two.makeGroup(externalSVG) 47 | externalSVGGroup.center() 48 | 49 | let textGroup = two.makeGroup(text) 50 | const textSvgGroup = two.makeGroup(externalSVGGroup, textGroup) 51 | textSvgGroup.center() 52 | textSvgGroup.fill = fill 53 | 54 | const rectangle = two.makeRoundedRectangle(0, 0, 140, 45, 5) 55 | rectangle.width = width 56 | rectangle.height = height 57 | rectangle.fill = 'transparent' 58 | if (stroke && linewidth) { 59 | rectangle.stroke = stroke 60 | rectangle.linewidth = linewidth 61 | } else { 62 | rectangle.noStroke() 63 | } 64 | 65 | const rectTextSvgGroup = two.makeGroup(rectangle, textSvgGroup) 66 | // rectTextSvgGroup.center() 67 | rectangle.noStroke() 68 | // rectTextSvgGroup.fill = fill 69 | 70 | const group = two.makeGroup(rectTextSvgGroup) 71 | group.translation.x = parseInt(prevX) 72 | group.translation.y = parseInt(prevY) 73 | return { 74 | group, 75 | externalSVG, 76 | textSvgGroup, 77 | rectangle, 78 | text, 79 | rectTextSvgGroup, 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/factory/main.js: -------------------------------------------------------------------------------- 1 | export default class Main { 2 | constructor(instance, x, y, properties) { 3 | this.two = instance 4 | this.x = x === 0 ? 500 : x 5 | this.y = y === 0 ? 200 : y 6 | this.properties = properties 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/factory/newArrowLine.js: -------------------------------------------------------------------------------- 1 | import Main from './main' 2 | 3 | export default class ArrowLineFactory extends Main { 4 | createElement() { 5 | const two = this.two 6 | const prevX = this.x 7 | const prevY = this.y 8 | const { fill, x1, x2, y1, y2 } = this.properties 9 | 10 | // console.log('arrow line factory x1, y1, x2, y2', x1, y1, x2, y2) 11 | let line = two.makeArrow(x1, y1, x2, y2, 10) 12 | line.linewidth = 4 13 | // line.vertices[1].y = 200 14 | 15 | const pointCircle1 = two.makeEllipse(0, 0, 5, 5) 16 | pointCircle1.fill = '#FFF' 17 | pointCircle1.stroke = '#0052CC' 18 | pointCircle1.linewidth = 2 19 | // pointCircle1.noStroke() 20 | 21 | const pointCircle2 = two.makeEllipse(10, 0, 5, 5) 22 | pointCircle2.fill = '#FFF' 23 | pointCircle2.stroke = '#0052CC' 24 | pointCircle2.linewidth = 2 25 | // pointCircle2.noStroke() 26 | 27 | const resizeLine = two.makeGroup(pointCircle1, pointCircle2) 28 | resizeLine.translation.y = 0 - line.linewidth 29 | resizeLine.opacity = 0 30 | 31 | let group = two.makeGroup(line, resizeLine) 32 | // console.log('main group', group.getBoundingClientRect()) 33 | 34 | // Overriding the circle point group's coordinate and 35 | // manipulating it with line's coordinate 36 | pointCircle1.translation.x = line.vertices[0].x - 0 37 | pointCircle1.translation.y = line.vertices[0].y - 0 38 | pointCircle2.translation.x = line.vertices[1].x + 4 39 | pointCircle2.translation.y = line.vertices[1].y - 0 40 | 41 | // const calcX = parseInt(prevX) + (parseInt(rectangle.width / 2) - 10); 42 | // const calcY = parseInt(prevY) - (parseInt(46) - parseInt(rectangle.height / 2)); 43 | group.center() 44 | group.translation.x = parseInt(prevX) 45 | group.translation.y = parseInt(prevY) 46 | 47 | return { group, pointCircle1, pointCircle2, resizeLine, line } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/factory/overlay.js: -------------------------------------------------------------------------------- 1 | import Main from './main' 2 | 3 | export default class OverlayFactory extends Main { 4 | createElement() { 5 | const two = this.two 6 | const prevX = this.x 7 | const prevY = this.y 8 | const { fill, width, height, stroke, linewidth } = this.properties 9 | 10 | const rectangle = two.makeRectangle(0, 0, width || 210, height || 110) 11 | rectangle.fill = fill 12 | rectangle.opacity = 0.5 13 | 14 | if (stroke && linewidth) { 15 | rectangle.stroke = stroke 16 | rectangle.linewidth = linewidth 17 | } else { 18 | rectangle.noStroke() 19 | } 20 | 21 | // console.log("rectangle", rectangle.getBoundingClientRect()); 22 | 23 | const group = two.makeGroup(rectangle) 24 | 25 | group.translation.x = parseInt(prevX) 26 | group.translation.y = parseInt(prevY) 27 | 28 | return { group, rectangle } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/factory/pencil.js: -------------------------------------------------------------------------------- 1 | import Main from './main' 2 | import { color_blue } from 'utils/constants' 3 | import Two from 'two.js' 4 | 5 | export default class PencilFactory extends Main { 6 | createElement() { 7 | const two = this.two 8 | const prevX = this.x 9 | const prevY = this.y 10 | const { fill, width, height, radius, stroke, linewidth, metadata } = 11 | this.properties 12 | 13 | // let paths = [] 14 | let path = two.makePath() 15 | if (metadata.length > 0) { 16 | metadata.forEach(function (point) { 17 | path.vertices.push( 18 | new Two.Vector(point.x - prevX, point.y - prevY) 19 | ) 20 | }) 21 | path.noFill() 22 | path.stroke = '#000' 23 | path.closed = false 24 | // two.add(path) 25 | // paths.push(path) 26 | } 27 | 28 | // path.fill = fill ? fill : color_blue 29 | 30 | // path.linewidth = linewidth ? linewidth : 0 31 | 32 | this.path = path 33 | // Create group and take children elements as a parameter 34 | const group = two.makeGroup(path) 35 | group.translation.x = parseInt(prevX) 36 | group.translation.y = parseInt(prevY) 37 | this.group = group 38 | console.log('group.id pencil', group.id) 39 | return { group: this.group, path } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/factory/rectangle.js: -------------------------------------------------------------------------------- 1 | import Main from './main' 2 | import { color_blue } from 'utils/constants' 3 | 4 | export default class RectangleFactory extends Main { 5 | createElement() { 6 | const two = this.two 7 | const prevX = this.x 8 | const prevY = this.y 9 | const { fill, width, height, stroke, linewidth } = this.properties 10 | 11 | // Implement core element 12 | 13 | const rectangle = two.makeRoundedRectangle( 14 | 0, 15 | 0, 16 | width || 210, 17 | height || 110, 18 | 5 19 | ) 20 | 21 | rectangle.fill = fill ? fill : color_blue 22 | rectangle.stroke = stroke ? stroke : '#fff' 23 | rectangle.linewidth = linewidth ? linewidth : 0 24 | 25 | // console.trace('rectangle trace') 26 | // console.log('rectangle', rectangle.getBoundingClientRect()) 27 | 28 | const group = two.makeGroup(rectangle) 29 | 30 | group.translation.x = parseInt(prevX) 31 | group.translation.y = parseInt(prevY) 32 | 33 | return { group, rectangle } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/factory/text.js: -------------------------------------------------------------------------------- 1 | import Main from './main' 2 | 3 | export default class TextFactory extends Main { 4 | createElement() { 5 | const two = this.two 6 | const prevX = this.x 7 | const prevY = this.y 8 | const { 9 | fill = 'rgba(0,0,0,0)', 10 | width, 11 | height, 12 | id, 13 | textColor, 14 | } = this.properties || {} 15 | const { content = '' } = this.properties.metadata || {} 16 | 17 | // pass width and height here for transparent rectangle container 18 | const rectangle = two.makeRoundedRectangle(0, 0, width, height, 5) 19 | rectangle.noFill() 20 | rectangle.noStroke() 21 | 22 | const rectTextGroup = two.makeGroup(rectangle) 23 | 24 | const group = two.makeGroup(rectTextGroup) 25 | group.translation.x = parseInt(prevX) 26 | group.translation.y = parseInt(prevY) 27 | 28 | two.update() 29 | 30 | const svgElem = rectTextGroup._renderer.elem 31 | svgElem.innerHTML = ` 32 | 35 |
${content}
36 |
37 | ` 38 | rectTextGroup.center() 39 | 40 | two.update() 41 | 42 | return { group, rectTextGroup, svgElem, rectangle } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/factory/textarea.js: -------------------------------------------------------------------------------- 1 | import Main from './main' 2 | 3 | export default class TextAreaFactory extends Main { 4 | createElement() { 5 | const two = this.two 6 | const prevX = this.x 7 | const prevY = this.y 8 | const { fill = 'rgba(0,0,0,0)', content = '' } = this?.properties || {} 9 | 10 | const text = two.makeText('Enter something here', -30, 0) 11 | text.size = '16' 12 | text.weight = '400' 13 | text.fill = '#B3BAC5' 14 | // text.baseline = "sub"; 15 | text.alignment = 'left' 16 | 17 | let textGroup = two.makeGroup(text) 18 | textGroup.center() 19 | // console.log("textGroup", textGroup, textGroup.id); 20 | 21 | const group = two.makeGroup(textGroup) 22 | // group.center(); 23 | group.translation.x = parseInt(prevX) 24 | group.translation.y = parseInt(prevY) 25 | 26 | // console.log("text bounding initial", text.getBoundingClientRect(true)); 27 | 28 | // Shifting order of objects in group to reflect "z-index alias" mechanism for text box 29 | 30 | const rectangle = two.makePath( 31 | group.getBoundingClientRect(true).left - 10, 32 | group.getBoundingClientRect(true).top - 10, 33 | 34 | group.getBoundingClientRect(true).right + 80, 35 | group.getBoundingClientRect(true).top - 10, 36 | 37 | group.getBoundingClientRect(true).right + 80, 38 | group.getBoundingClientRect(true).bottom + 10, 39 | 40 | group.getBoundingClientRect(true).left - 10, 41 | group.getBoundingClientRect(true).bottom + 10 42 | ) 43 | 44 | rectangle.fill = '#fff' 45 | rectangle.stroke = '#B3BAC5' 46 | rectangle.linewidth = 1 47 | rectangle.join = 'round' 48 | 49 | // rectangle.noStroke(); 50 | 51 | group.add(rectangle) 52 | return { group, rectangle, textGroup, text, rectangle } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/factory/textinput.js: -------------------------------------------------------------------------------- 1 | import Main from './main' 2 | 3 | export default class TextInputFactory extends Main { 4 | createElement() { 5 | const two = this.two 6 | const prevX = this.x 7 | const prevY = this.y 8 | const { fill } = this.properties 9 | 10 | const text = two.makeText('Text input', -30, 0) 11 | text.size = '14' 12 | text.weight = '400' 13 | text.fill = '#B3BAC5' 14 | // text.baseline = "sub"; 15 | text.alignment = 'left' 16 | 17 | let textGroup = two.makeGroup(text) 18 | textGroup.center() 19 | console.log('textGroup', textGroup, textGroup.id) 20 | 21 | const group = two.makeGroup(textGroup) 22 | 23 | // group.center(); 24 | group.translation.x = parseInt(prevX) 25 | group.translation.y = parseInt(prevY) 26 | 27 | console.log('text bounding initial', text.getBoundingClientRect(true)) 28 | 29 | // Shifting order of objects in group to reflect "z-index alias" mechanism for text box 30 | 31 | const rectangle = two.makePath( 32 | group.getBoundingClientRect(true).left - 10, 33 | group.getBoundingClientRect(true).top - 10, 34 | 35 | group.getBoundingClientRect(true).right + 80, 36 | group.getBoundingClientRect(true).top - 10, 37 | 38 | group.getBoundingClientRect(true).right + 80, 39 | group.getBoundingClientRect(true).bottom + 10, 40 | 41 | group.getBoundingClientRect(true).left - 10, 42 | group.getBoundingClientRect(true).bottom + 10 43 | ) 44 | 45 | rectangle.fill = '#fff' 46 | rectangle.stroke = '#B3BAC5' 47 | rectangle.linewidth = 1 48 | rectangle.join = 'round' 49 | 50 | // rectangle.noStroke(); 51 | 52 | const rectTextGroup = two.makeGroup(rectangle, textGroup) 53 | group.add(rectangle) 54 | 55 | return { group, textGroup, rectTextGroup, rectangle, text } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/factory/toggle.js: -------------------------------------------------------------------------------- 1 | import Main from './main' 2 | 3 | export default class ToggleFactory extends Main { 4 | createElement() { 5 | const two = this.two 6 | const prevX = this.x 7 | const prevY = this.y 8 | const { fill, width, height, radius, stroke, linewidth } = 9 | this.properties 10 | 11 | // Implement outer rectangle 12 | const rect = two.makeRoundedRectangle(0, 0, 55, 30, 16) 13 | rect.fill = '#0052CC' 14 | rect.noStroke() 15 | 16 | const calcCirclePointX = parseInt(rect.width / 4) 17 | // Implement circle shape control 18 | const circle = two.makeCircle(calcCirclePointX, 0, 10) 19 | circle.noStroke() 20 | 21 | const rectCircleGroup = two.makeGroup(rect, circle) 22 | const group = two.makeGroup(rectCircleGroup) 23 | 24 | // const calcX = parseInt(prevX) + (parseInt(rect.width / 2) - 10); 25 | // const calcY = parseInt(prevY) - (parseInt(46) - parseInt(rect.height / 2)); 26 | // group.center(); 27 | group.translation.x = parseInt(prevX) 28 | group.translation.y = parseInt(prevY) 29 | 30 | return { group, circle, rectCircleGroup, rect } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/hooks/intersectionObserver.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | const useIntersectionObserver = ({ 3 | target, 4 | onIntersect, 5 | threshold = 0.1, 6 | rootMargin = '0px', 7 | }) => { 8 | React.useEffect(() => { 9 | const observer = new IntersectionObserver(onIntersect, { 10 | rootMargin, 11 | threshold, 12 | }) 13 | const current = target.current 14 | observer.observe(current) 15 | return () => { 16 | observer.unobserve(current) 17 | } 18 | }) 19 | } 20 | export default useIntersectionObserver 21 | -------------------------------------------------------------------------------- /src/icons/icon.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import Icons from './icons' 4 | 5 | class Icon extends Component { 6 | render() { 7 | return ( 8 | 17 | ) 18 | } 19 | 20 | shouldComponentUpdate() { 21 | return false 22 | } 23 | } 24 | 25 | Icon.propTypes = { 26 | width: PropTypes.number, 27 | height: PropTypes.number, 28 | } 29 | 30 | export default Icon 31 | -------------------------------------------------------------------------------- /src/icons/image-gallery-landscape-square-potrait-pic-interface-ui-3 (2).svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import './index.css' 4 | import App from './App' 5 | import * as serviceWorker from './serviceWorker' 6 | 7 | ReactDOM.render(, document.getElementById('root')) 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister() 13 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | home: '/', 3 | board: '/board/:id', 4 | about: '/about', 5 | } 6 | -------------------------------------------------------------------------------- /src/scalingRotate.js: -------------------------------------------------------------------------------- 1 | // import Two from 'https://cdn.skypack.dev/two.js@latest' 2 | 3 | // var two = new Two({ 4 | // type: Two.Types.svg, 5 | // fullscreen: true, 6 | // autostart: true, 7 | // }).appendTo(document.body) 8 | 9 | // var mouse = new Two.Vector() 10 | // var rect 11 | 12 | // var points = generateRandomPoints(two.height * 0.33) 13 | // var path = new Two.Path(points) 14 | // // var path = new Two.Star(0, 0, two.height * 0.0625, two.height * 0.33, 9); 15 | // path.scale = new Two.Vector(1, 1) 16 | // path.position.set(two.width / 2, two.height / 2) 17 | // path.closed = true 18 | // path.curved = true 19 | // path.fill = 'orange' 20 | // path.noStroke() 21 | 22 | // var box = new Two.Rectangle(0, 0, 0, 0) 23 | // box.stroke = '#00AEFF' 24 | // box.noFill() 25 | 26 | // var endpoints = new Two.Points(box.vertices) 27 | // endpoints.size = 6 28 | // endpoints.fill = '#00AEFF' 29 | // endpoints.noStroke() 30 | 31 | // var stage = new Two.Group() 32 | // var ui = new Two.Group() 33 | // var text = new Two.Text(`Operation: none`, 0, 0, { 34 | // size: 17, 35 | // baseline: 'bottom', 36 | // }) 37 | 38 | // stage.add(path) 39 | // ui.add(box, endpoints) 40 | // two.add(stage, ui, text) 41 | 42 | // two.bind('update', update).bind('resize', resize) 43 | 44 | // resize() 45 | 46 | // var domElement = two.renderer.domElement 47 | 48 | // domElement.addEventListener('pointerdown', pointerdown, false) 49 | // domElement.addEventListener('pointermove', pointermove, false) 50 | // domElement.addEventListener('pointerup', pointerup, false) 51 | 52 | // function resize() { 53 | // text.position.x = two.width / 2 54 | // text.position.y = two.height - text.size 55 | // } 56 | 57 | // function update(frameCount) { 58 | // var minX = Infinity 59 | // var minY = Infinity 60 | // var maxX = -Infinity 61 | // var maxY = -Infinity 62 | 63 | // for (var i = 0; i < 1; i += 0.01) { 64 | // var v = path.getPointAt(i) 65 | // minX = Math.min(minX, v.x * path.scale.x) 66 | // maxX = Math.max(maxX, v.x * path.scale.x) 67 | // minY = Math.min(minY, v.y * path.scale.y) 68 | // maxY = Math.max(maxY, v.y * path.scale.y) 69 | // } 70 | 71 | // box.width = maxX - minX 72 | // box.height = maxY - minY 73 | 74 | // ui.position = path.position 75 | // ui.rotation = path.rotation 76 | // } 77 | 78 | // // 79 | 80 | // var dragging = false, 81 | // scaling = false, 82 | // rotating = false 83 | 84 | // function pointerdown(e) { 85 | // var rect = box.getBoundingClientRect() 86 | 87 | // mouse.x = e.clientX - two.scene.position.x 88 | // mouse.y = e.clientY - two.scene.position.y 89 | 90 | // dragging = true 91 | 92 | // rotating = atCorner(box, mouse) 93 | // if (rotating) { 94 | // mouse.theta = Math.atan2(rotating.point.y, rotating.point.x) 95 | // } 96 | 97 | // scaling = !rotating && atCorner(box, mouse, 25) 98 | // if (scaling) { 99 | // mouse.scale = path.scale.clone() 100 | // } 101 | // } 102 | 103 | // function pointermove(e) { 104 | // var rect = box.getBoundingClientRect() 105 | // var dx = e.clientX - mouse.x 106 | // var dy = e.clientY - mouse.y 107 | // var theta = 108 | // Math.atan2(e.clientY - path.position.y, e.clientX - path.position.x) - 109 | // mouse.theta 110 | 111 | // var isRotating = atCorner(box, mouse) 112 | // var isScaling = atCorner(box, mouse, 25) 113 | // var isPositioning = !isRotating && contains(rect, mouse) 114 | 115 | // mouse.x = e.clientX 116 | // mouse.y = e.clientY 117 | 118 | // if (rotating || isRotating) { 119 | // two.renderer.domElement.style.cursor = 'alias' 120 | // text.value = `Operation: rotate` 121 | // } else if (scaling || isScaling) { 122 | // two.renderer.domElement.style.cursor = 'ns-resize' 123 | // text.value = `Operation: scale` 124 | // } else if (isPositioning) { 125 | // two.renderer.domElement.style.cursor = 'grab' 126 | // text.value = `Operation: position` 127 | // } else { 128 | // two.renderer.domElement.style.cursor = 'default' 129 | // text.value = `Operation: none` 130 | // } 131 | 132 | // if (rotating) { 133 | // text.value = 'Operation: rotating' 134 | // path.rotation = theta 135 | // } else if (scaling) { 136 | // text.value = 'Operation: scaling' 137 | // path.scale.x += dx * 0.01 138 | // path.scale.y += dy * 0.01 139 | // } else if (dragging) { 140 | // if (isPositioning) { 141 | // text.value = 'Operation: positioning' 142 | // two.renderer.domElement.style.cursor = 'grabbing' 143 | // path.position.add(dx, dy) 144 | // } 145 | // } 146 | // } 147 | 148 | // function pointerup() { 149 | // dragging = false 150 | // scaling = false 151 | // rotating = false 152 | // } 153 | 154 | // // 155 | 156 | // function contains(rect, point) { 157 | // return ( 158 | // point.x > rect.left && 159 | // point.x < rect.right && 160 | // point.y > rect.top && 161 | // point.y < rect.bottom 162 | // ) 163 | // } 164 | 165 | // function atCorner(object, point, limit) { 166 | // var v 167 | 168 | // if (typeof limit !== 'number') { 169 | // limit = 10 170 | // } 171 | 172 | // limit *= limit 173 | 174 | // var matrix = Two.Utils.getComputedMatrix(object) 175 | 176 | // v = object.vertices[0] 177 | // var tl = matrix.multiply(v.x, v.y, 1) 178 | // v = object.vertices[1] 179 | // var tr = matrix.multiply(v.x, v.y, 1) 180 | // v = object.vertices[2] 181 | // var bl = matrix.multiply(v.x, v.y, 1) 182 | // v = object.vertices[3] 183 | // var br = matrix.multiply(v.x, v.y, 1) 184 | // var dbs = Two.Vector.distanceBetweenSquared 185 | 186 | // if (dbs(point, tl) < limit) { 187 | // return { name: 'nw-resize', point: object.vertices[0] } 188 | // } else if (dbs(point, tr) < limit) { 189 | // return { name: 'ne-resize', point: object.vertices[1] } 190 | // } else if (dbs(point, bl) < limit) { 191 | // return { name: 'sw-resize', point: object.vertices[2] } 192 | // } else if (dbs(point, br) < limit) { 193 | // return { name: 'se-resize', point: object.vertices[3] } 194 | // } else { 195 | // return false 196 | // } 197 | // } 198 | 199 | // function generateRandomPoints(size, count) { 200 | // var points = [] 201 | // var i = 0 202 | // var length = count || 32 203 | // var radius = size / 2 204 | 205 | // while (i < length) { 206 | // var pct = i / length 207 | // var theta = pct * Math.PI * 2 208 | // var r = Math.random() * radius * 0.5 + radius * 0.5 209 | // var x = r * Math.cos(theta) 210 | // var y = r * Math.sin(theta) 211 | // var anchor = new Two.Anchor(x, y) 212 | // points.push(anchor) 213 | // i++ 214 | // } 215 | 216 | // return points 217 | // } 218 | -------------------------------------------------------------------------------- /src/schema/mutations/index.js: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client' 2 | 3 | export const UPDATE_COMPONENT_INFO = gql` 4 | mutation UPDATE_COMPONENT_INFO( 5 | $id: uuid = "" 6 | $updateObj: components_component_set_input = {} 7 | ) { 8 | update_components_component_by_pk( 9 | pk_columns: { id: $id } 10 | _set: $updateObj 11 | ) { 12 | id 13 | } 14 | } 15 | ` 16 | 17 | export const INSERT_COMPONENT = gql` 18 | mutation insertComponent($object: components_component_insert_input = {}) { 19 | component: insert_components_component_one(object: $object) { 20 | id 21 | componentType 22 | } 23 | } 24 | ` 25 | 26 | export const UPDATE_BOARD_COMPONENTS = gql` 27 | mutation updateBoardComponents($id: uuid = "", $components: jsonb = "") { 28 | update_boards_board_by_pk( 29 | pk_columns: { id: $id } 30 | _set: { components: $components } 31 | ) { 32 | id 33 | } 34 | } 35 | ` 36 | 37 | export const DELETE_COMPONENT_BY_ID = gql` 38 | mutation deleteComponentById($id: uuid = "") { 39 | delete_components_component_by_pk(id: $id) { 40 | boardId 41 | } 42 | } 43 | ` 44 | 45 | export const INSERT_BULK_COMPONENTS = gql` 46 | mutation MyMutation($objects: [components_component_insert_input!]! = {}) { 47 | insert_components_component(objects: $objects) { 48 | affected_rows 49 | returning { 50 | boardId 51 | componentType 52 | id 53 | } 54 | } 55 | } 56 | ` 57 | 58 | export const INSERT_USER_ONE = gql` 59 | mutation insertUser($object: users_user_insert_input! = {}) { 60 | user: insert_users_user_one(object: $object) { 61 | id 62 | firstName 63 | } 64 | } 65 | ` 66 | 67 | export const CREATE_BOARD = gql` 68 | mutation createBoard($object: boards_board_insert_input! = {}) { 69 | board: insert_boards_board_one(object: $object) { 70 | id 71 | createdBy 72 | } 73 | } 74 | ` 75 | 76 | export const DELETE_BULK_COMPONENTS = gql` 77 | mutation deleteComponents($_in: [uuid!]! = "") { 78 | deleteComponents: delete_components_component( 79 | where: { id: { _in: $_in } } 80 | ) { 81 | affected_rows 82 | } 83 | } 84 | ` 85 | 86 | export const UPDATE_USER_REVISIT_COUNT = gql` 87 | mutation updateUserRevisitCount($userId: String!) { 88 | update_users_user_revisits_by_pk( 89 | pk_columns: { user_id: $userId } 90 | _inc: { count: "1" } 91 | ) { 92 | count 93 | user_id 94 | } 95 | } 96 | ` 97 | -------------------------------------------------------------------------------- /src/schema/queries/index.js: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client' 2 | 3 | export const GET_USER_DETAILS = gql` 4 | query MyQuery($id: uuid = "") { 5 | users: users_user(where: { id: { _eq: $id } }) { 6 | firstName 7 | id 8 | } 9 | } 10 | ` 11 | 12 | export const GET_COMPONENT_TYPES = gql` 13 | query getComponentTypes { 14 | componentTypes: components_componentType { 15 | label 16 | metadata 17 | logo 18 | width 19 | height 20 | fill 21 | textColor 22 | } 23 | } 24 | ` 25 | 26 | export const GET_COMPONENTS_FOR_BOARD_QUERY = gql` 27 | query getComponentsForBoard($boardId: String = "") { 28 | components: components_component( 29 | where: { boardId: { _eq: $boardId } } 30 | ) { 31 | id 32 | componentType 33 | children 34 | metadata 35 | x 36 | x1 37 | x2 38 | y 39 | y1 40 | y2 41 | fill 42 | width 43 | height 44 | iconStroke 45 | stroke 46 | linewidth 47 | } 48 | } 49 | ` 50 | 51 | export const GET_COMPONENT_INFO_QUERY = gql` 52 | query getComponentInfoQuery($id: uuid = "") { 53 | component: components_component_by_pk(id: $id) { 54 | metadata 55 | width 56 | height 57 | fill 58 | id 59 | stroke 60 | linewidth 61 | x 62 | y 63 | x1 64 | y1 65 | x2 66 | y2 67 | componentType 68 | children 69 | updatedBy 70 | iconStroke 71 | textColor 72 | } 73 | } 74 | ` 75 | 76 | export const GET_BOARD_DATA_QUERY = gql` 77 | query getBoardComponents($boardId: String! = "") { 78 | components: components_component( 79 | where: { boardId: { _eq: $boardId } } 80 | ) { 81 | id 82 | componentType 83 | } 84 | } 85 | ` 86 | -------------------------------------------------------------------------------- /src/schema/subscriptions/index.js: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client' 2 | 3 | export const GET_USER_DETAILS = gql` 4 | subscription MyQuery($id: uuid = "") { 5 | users: users_user(where: { id: { _eq: $id } }) { 6 | firstName 7 | id 8 | } 9 | } 10 | ` 11 | 12 | export const GET_BOARD_DATA_SUBSCRIPTION = gql` 13 | subscription getBoardComponents($boardId: String! = "") { 14 | components: components_component( 15 | where: { boardId: { _eq: $boardId } } 16 | ) { 17 | id 18 | componentType 19 | } 20 | } 21 | ` 22 | 23 | export const GET_COMPONENT_INFO_SUBSCRIPTION = gql` 24 | subscription getComponentInfoSubscription($id: uuid = "") { 25 | component: components_component_by_pk(id: $id) { 26 | metadata 27 | width 28 | height 29 | fill 30 | id 31 | stroke 32 | linewidth 33 | x 34 | y 35 | x1 36 | y1 37 | x2 38 | y2 39 | componentType 40 | children 41 | updatedBy 42 | iconStroke 43 | textColor 44 | } 45 | } 46 | ` 47 | 48 | export const GET_COMPONENTS_FOR_BOARD_SUBSCRIPTION = gql` 49 | subscription getComponentsForBoard($boardId: String = "") { 50 | components: components_component( 51 | where: { boardId: { _eq: $boardId } } 52 | ) { 53 | id 54 | componentType 55 | children 56 | metadata 57 | x 58 | x1 59 | x2 60 | y 61 | y1 62 | y2 63 | fill 64 | width 65 | height 66 | iconStroke 67 | stroke 68 | linewidth 69 | } 70 | } 71 | ` 72 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' } 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /src/store/actions/main.js: -------------------------------------------------------------------------------- 1 | export function setPeronsalInformation(type, data) { 2 | return async function (dispatch) { 3 | try { 4 | await dispatch({ 5 | type, 6 | payload: data, 7 | }); 8 | 9 | // successCallback && successCallback(response); 10 | } catch (e) { 11 | console.error(e); 12 | // errorCallback && errorCallback(e.response); 13 | } 14 | }; 15 | } 16 | 17 | export function getElementsData(type, data) { 18 | return async function (dispatch) { 19 | try { 20 | await dispatch({ 21 | type, 22 | payload: data, 23 | }); 24 | 25 | // successCallback && successCallback(response); 26 | } catch (e) { 27 | console.error(e); 28 | // errorCallback && errorCallback(e.response); 29 | } 30 | }; 31 | } 32 | 33 | export function addElement(type, data) { 34 | return async function (dispatch) { 35 | try { 36 | await dispatch({ 37 | type, 38 | payload: data, 39 | }); 40 | 41 | // successCallback && successCallback(response); 42 | } catch (e) { 43 | console.error(e); 44 | // errorCallback && errorCallback(e.response); 45 | } 46 | }; 47 | } 48 | 49 | export function ungroupElements(type, data) { 50 | return async function (dispatch) { 51 | try { 52 | await dispatch({ 53 | type, 54 | payload: data, 55 | }); 56 | 57 | // successCallback && successCallback(response); 58 | } catch (e) { 59 | console.error(e); 60 | // errorCallback && errorCallback(e.response); 61 | } 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /src/store/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import main from 'store/reducers/main'; 4 | 5 | const appReducer = combineReducers({ 6 | main, 7 | }); 8 | 9 | const rootReducer = (state, action) => { 10 | console.log('action.type in root reducer', action.type); 11 | if (action.type === 'LOGOUT') { 12 | state = undefined; 13 | } 14 | return appReducer(state, action); 15 | }; 16 | 17 | export default rootReducer; 18 | -------------------------------------------------------------------------------- /src/store/types.js: -------------------------------------------------------------------------------- 1 | export const CONSTRUCT = 'CONSTRUCT'; 2 | export const COMPLETE = 'COMPLETE'; 3 | export const ADD_ELEMENT = 'ADD_ELEMENT'; 4 | export const UNGROUP_ELEMENT = 'UNGROUP_ELEMENT'; 5 | export const AREA_SELECTION = 'AREA_SELECTION'; 6 | export const UPDATE_ELEMENT_DATA = 'UPDATE_ELEMENT_DATA'; 7 | -------------------------------------------------------------------------------- /src/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | 3 | @tailwind components; 4 | 5 | @tailwind utilities; 6 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export const removeAllObjects = (obj) => { 2 | let newObj = { ...obj }; 3 | let objKeys = Object.keys(obj); 4 | Object.values(obj).forEach((item, index) => { 5 | if (typeof item === "object") { 6 | delete newObj[objKeys[index]]; 7 | } 8 | }); 9 | return newObj; 10 | }; 11 | 12 | export const calcCoordsFromRect = (rect) => { 13 | const width = rect.width; 14 | const height = rect.height; 15 | const left = rect.left; 16 | const top = rect.top; 17 | 18 | const btnCoordX = parseInt(left) + parseInt(width) / 4; 19 | const btnCoordY = parseInt(top) + parseInt(height) / 4; 20 | return { left: btnCoordX, top: btnCoordY }; 21 | }; 22 | 23 | export const getFourthValue = (x1, x2, y1) => { 24 | const divisor = parseInt(x2 * y1); 25 | const dividend = x1; 26 | const output = divisor / dividend; 27 | return output; 28 | }; 29 | 30 | export const getDiffForTwoValues = (x, y) => { 31 | const result = Math.abs(parseInt(x) - parseInt(y)); 32 | return result; 33 | }; 34 | -------------------------------------------------------------------------------- /src/utils/misc.js: -------------------------------------------------------------------------------- 1 | export const elementOnBlurHandler = (e, selectorInstance, two) => { 2 | // Callback for add and remove event listener for floating showToolbar 3 | const blurListenerCB = (e) => { 4 | if (e?.relatedTarget?.dataset.parent === 'floating-toolbar') { 5 | // no action required 6 | } else { 7 | selectorInstance && selectorInstance.hide() 8 | // toggleToolbar(false); 9 | } 10 | } 11 | 12 | // Check if user interacts with toolbar 13 | if ( 14 | e?.relatedTarget?.id === 'floating-toolbar' || 15 | e?.relatedTarget?.dataset.parent === 'floating-toolbar' 16 | ) { 17 | document 18 | .getElementById('floating-toolbar') 19 | .addEventListener('blur', blurListenerCB) 20 | } else { 21 | selectorInstance && selectorInstance.hide() 22 | // toggleToolbar(false); 23 | } 24 | two.update() 25 | } 26 | 27 | export const generateRandomUsernames = () => { 28 | let names = [ 29 | 'cake_salad', 30 | 'raspberry_waffle', 31 | 'tropical_owl', 32 | 'high_antopera', 33 | 'banestick_watermelon', 34 | 'zephyr_pomegranate', 35 | 'optimus_prime', 36 | 'network_tea', 37 | 'floral_cake', 38 | 'volcano_bee', 39 | 'hurricane_cat', 40 | 'juice_walrus', 41 | 'groundhog_day', 42 | 'spacex_dragon', 43 | 'icecream_fox', 44 | 'astronout_fly', 45 | 'icecoffee_cat', 46 | 'pumpkin_bat', 47 | 'anonymous_galileo', 48 | 'raspberry_cat', 49 | 'water_rabbit', 50 | 'violet_turtle', 51 | ] 52 | 53 | let rB = Math.floor(Math.random() * names.length) 54 | let name = names[rB] 55 | let firstName = name.split('_')[0] 56 | let lastName = name.split('_')[1] 57 | 58 | return { nickname: name, firstName, lastName } 59 | } 60 | 61 | export const generateUUID = () => { 62 | // Public Domain/MIT 63 | let d = new Date().getTime() //Timestamp 64 | let d2 = 65 | (typeof performance !== 'undefined' && 66 | performance.now && 67 | performance.now() * 1000) || 68 | 0 //Time in microseconds since page-load or 0 if unsupported 69 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( 70 | /[xy]/g, 71 | function (c) { 72 | let r = Math.random() * 16 //random number between 0 and 16 73 | if (d > 0) { 74 | //Use timestamp until depleted 75 | r = (d + r) % 16 | 0 76 | d = Math.floor(d / 16) 77 | } else { 78 | //Use microseconds since page-load if supported 79 | r = (d2 + r) % 16 | 0 80 | d2 = Math.floor(d2 / 16) 81 | } 82 | return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16) 83 | } 84 | ) 85 | } 86 | -------------------------------------------------------------------------------- /src/utils/updateVertices.js: -------------------------------------------------------------------------------- 1 | export const updateX1Y1Vertices = (TwoRef, line, x1, y1, pointCircle1, two) => { 2 | // copied code from definition of makeArrow 3 | let headlen = 10 4 | 5 | let angle = Math.atan2(line.vertices[1].y - y1, line.vertices[1].x - x1) 6 | 7 | let vertices = [ 8 | new TwoRef.Anchor( 9 | x1, 10 | y1, 11 | undefined, 12 | undefined, 13 | undefined, 14 | undefined, 15 | TwoRef.Commands.move 16 | ), 17 | new TwoRef.Anchor( 18 | line.vertices[1].x, 19 | line.vertices[1].y, 20 | undefined, 21 | undefined, 22 | undefined, 23 | undefined, 24 | TwoRef.Commands.line 25 | ), 26 | new TwoRef.Anchor( 27 | line.vertices[1].x - headlen * Math.cos(angle - Math.PI / 4), 28 | line.vertices[1].y - headlen * Math.sin(angle - Math.PI / 4), 29 | undefined, 30 | undefined, 31 | undefined, 32 | undefined, 33 | TwoRef.Commands.line 34 | ), 35 | 36 | new TwoRef.Anchor( 37 | line.vertices[1].x, 38 | line.vertices[1].y, 39 | undefined, 40 | undefined, 41 | undefined, 42 | undefined, 43 | TwoRef.Commands.move 44 | ), 45 | new TwoRef.Anchor( 46 | line.vertices[1].x - headlen * Math.cos(angle + Math.PI / 4), 47 | line.vertices[1].y - headlen * Math.sin(angle + Math.PI / 4), 48 | undefined, 49 | undefined, 50 | undefined, 51 | undefined, 52 | TwoRef.Commands.line 53 | ), 54 | ] 55 | line.vertices = vertices 56 | 57 | // old code 58 | // pointCircle1.translation.x = line.vertices[0].x + 0 59 | // pointCircle1.translation.y = line.vertices[0].y + parseInt(line.linewidth) 60 | 61 | pointCircle1.translation.x = line.vertices[0].x 62 | pointCircle1.translation.y = line.vertices[0].y 63 | 64 | two.update() 65 | } 66 | 67 | export const updateX2Y2Vertices = (TwoRef, line, x2, y2, pointCircle2, two) => { 68 | // copied code from definition of makeArrow 69 | let headlen = 10 70 | 71 | let angle = Math.atan2(y2 - line.vertices[0].y, x2 - line.vertices[0].x) 72 | 73 | let vertices = [ 74 | new TwoRef.Anchor( 75 | line.vertices[0].x, 76 | line.vertices[0].y, 77 | undefined, 78 | undefined, 79 | undefined, 80 | undefined, 81 | TwoRef.Commands.move 82 | ), 83 | new TwoRef.Anchor( 84 | x2, 85 | y2, 86 | undefined, 87 | undefined, 88 | undefined, 89 | undefined, 90 | TwoRef.Commands.line 91 | ), 92 | new TwoRef.Anchor( 93 | x2 - headlen * Math.cos(angle - Math.PI / 4), 94 | y2 - headlen * Math.sin(angle - Math.PI / 4), 95 | undefined, 96 | undefined, 97 | undefined, 98 | undefined, 99 | TwoRef.Commands.line 100 | ), 101 | 102 | new TwoRef.Anchor( 103 | x2, 104 | y2, 105 | undefined, 106 | undefined, 107 | undefined, 108 | undefined, 109 | TwoRef.Commands.move 110 | ), 111 | new TwoRef.Anchor( 112 | x2 - headlen * Math.cos(angle + Math.PI / 4), 113 | y2 - headlen * Math.sin(angle + Math.PI / 4), 114 | undefined, 115 | undefined, 116 | undefined, 117 | undefined, 118 | TwoRef.Commands.line 119 | ), 120 | ] 121 | line.vertices = vertices 122 | 123 | // old code 124 | // pointCircle2.translation.x = 125 | // line.vertices[1].x < line.vertices[0].x 126 | // ? line.vertices[1].x 127 | // : line.vertices[1].x + 6 128 | // pointCircle2.translation.y = line.vertices[1].y + parseInt(line.linewidth) 129 | 130 | pointCircle2.translation.x = line.vertices[1].x 131 | pointCircle2.translation.y = line.vertices[1].y 132 | 133 | two.update() 134 | } 135 | -------------------------------------------------------------------------------- /src/views/Board/errorBoundary.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class ErrorBoundaryBoardView extends React.Component { 4 | constructor(props) { 5 | super(props) 6 | this.state = { hasError: false } 7 | } 8 | 9 | static getDerivedStateFromError(error) { 10 | return { hasError: true } 11 | } 12 | 13 | componentDidCatch(error, errorInfo) { 14 | // sentry.post() 15 | } 16 | 17 | render() { 18 | if (this.state.hasError) { 19 | // You can render any custom fallback UI 20 | return

Couldn't load board view. Something went wrong

21 | } 22 | 23 | return this.props.children 24 | } 25 | } 26 | 27 | export default ErrorBoundaryBoardView 28 | -------------------------------------------------------------------------------- /src/views/Board/index.css: -------------------------------------------------------------------------------- 1 | #show-select-any-shape-btn::before { 2 | position: absolute; 3 | content: ''; 4 | width: 10px; 5 | height: 10px; 6 | background: #00875a; 7 | transform: rotate(45deg); 8 | /* border: 10px solid transparent; */ 9 | margin: 0; 10 | left: -5px; 11 | top: 37px; 12 | z-index: -1; 13 | } 14 | 15 | #show-select-any-element-btn::before { 16 | position: absolute; 17 | content: ''; 18 | width: 10px; 19 | height: 10px; 20 | background: #00875a; 21 | transform: rotate(45deg); 22 | /* border: 10px solid transparent; */ 23 | margin: 0; 24 | left: -5px; 25 | top: 37px; 26 | z-index: -1; 27 | } 28 | -------------------------------------------------------------------------------- /src/views/Board/index.js: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react' 2 | 3 | import ErrorBoundary from './errorBoundary' 4 | import Spinner from 'components/common/spinner' 5 | import './index.css' 6 | 7 | const BoardViewPage = React.lazy(() => import('./board')) 8 | 9 | const BoardViewContainer = (props) => ( 10 | }> 11 | 12 | 13 | 14 | 15 | ) 16 | 17 | export default BoardViewContainer 18 | -------------------------------------------------------------------------------- /src/views/Home/errorBoundary.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class ErrorBoundaryHomePageView extends React.Component { 4 | constructor(props) { 5 | super(props) 6 | this.state = { hasError: false } 7 | } 8 | 9 | static getDerivedStateFromError(error) { 10 | return { hasError: true } 11 | } 12 | 13 | componentDidCatch(error, errorInfo) { 14 | // sentry.post() 15 | } 16 | 17 | render() { 18 | if (this.state.hasError) { 19 | // You can render any custom fallback UI 20 | return

Couldn't load home page view. Something went wrong

21 | } 22 | 23 | return this.props.children 24 | } 25 | } 26 | 27 | export default ErrorBoundaryHomePageView 28 | -------------------------------------------------------------------------------- /src/views/Home/index.css: -------------------------------------------------------------------------------- 1 | .animate-fade { 2 | animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1); 3 | } 4 | 5 | .home-arrow-icon { 6 | opacity: 0.4; 7 | transition: all 0.2s ease; 8 | } 9 | .primary-btn-home:hover .home-arrow-icon { 10 | opacity: 1; 11 | transform: translateX(5px); 12 | } 13 | 14 | .home-page-main-video iframe { 15 | z-index: 1 !important; 16 | } 17 | 18 | /* #banner-wave-svg { 19 | transition: opacity 0.3s ease; 20 | animation: fadeIn 0.3s; 21 | } */ 22 | /* 23 | #branding-imgs { 24 | transition: transform 1s ease; 25 | animation: fadeIn 0.3s; 26 | } 27 | 28 | @keyframes fadeIn { 29 | from { 30 | opacity: 0; 31 | transform: scale(0.4); 32 | } 33 | 34 | to { 35 | opacity: 1; 36 | transform: scale(1); 37 | } 38 | } */ 39 | /* 40 | @keyframes fadeIn { 41 | 0% { 42 | opacity: 0; 43 | 44 | } 45 | 46 | 30% { 47 | opacity: 0.2; 48 | 49 | } 50 | 51 | 60% { 52 | opacity: 0.6; 53 | 54 | } 55 | 56 | 100% { 57 | opacity: 1; 58 | } 59 | } */ 60 | -------------------------------------------------------------------------------- /src/views/Home/index.js: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react' 2 | import ErrorBoundary from './errorBoundary' 3 | import Spinner from 'components/common/spinner' 4 | import './index.css' 5 | 6 | const HomePage = React.lazy(() => import('./home')) 7 | 8 | const HomePageViewContainer = (props) => ( 9 | }> 10 | 11 | 12 | 13 | 14 | ) 15 | 16 | export default HomePageViewContainer 17 | -------------------------------------------------------------------------------- /src/wireframeAssets/avatar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/wireframeAssets/btn.svg: -------------------------------------------------------------------------------- 1 | 2 | Button 3 | -------------------------------------------------------------------------------- /src/wireframeAssets/btnWithIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | Button 3 | -------------------------------------------------------------------------------- /src/wireframeAssets/checkbox.svg: -------------------------------------------------------------------------------- 1 | 2 | checkbox 1checkbox 2checkbox 3 3 | -------------------------------------------------------------------------------- /src/wireframeAssets/circle.svg: -------------------------------------------------------------------------------- 1 | Circle -------------------------------------------------------------------------------- /src/wireframeAssets/cursor.svg: -------------------------------------------------------------------------------- 1 | Cursor -------------------------------------------------------------------------------- /src/wireframeAssets/divider.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/wireframeAssets/frame.svg: -------------------------------------------------------------------------------- 1 | Rectangle -------------------------------------------------------------------------------- /src/wireframeAssets/imageCard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/wireframeAssets/miniAvatar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/wireframeAssets/pencil.svg: -------------------------------------------------------------------------------- 1 | Edit -------------------------------------------------------------------------------- /src/wireframeAssets/radiobox.svg: -------------------------------------------------------------------------------- 1 | 2 | radiobox 1radiobox 2radiobox3 3 | -------------------------------------------------------------------------------- /src/wireframeAssets/rectangle.svg: -------------------------------------------------------------------------------- 1 | Rectangle -------------------------------------------------------------------------------- /src/wireframeAssets/text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/wireframeAssets/toggle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /temp.js: -------------------------------------------------------------------------------- 1 | let sampleBoardData = { 2 | data: { 3 | components: [ 4 | { 5 | id: '456e5b31-b7ec-4e1c-bdfc-13bc10aa5eeb', 6 | componentType: 'rectangle', 7 | }, 8 | { 9 | id: 'c0738fa5-7769-406e-aa08-7860fc392cd3', 10 | componentType: 'circle', 11 | }, 12 | { 13 | id: '416b6286-e936-42fd-bce4-fe168216eeef', 14 | componentType: 'rectangle', 15 | }, 16 | ], 17 | }, 18 | } 19 | 20 | /** 21 | * @typedef {Object} componentInfo 22 | * @property {object} metadata 23 | * @property {number} width 24 | * @property {number} height 25 | * @property {text} fill 26 | * @property {text} id 27 | * @property {text} stroke 28 | * @property {number} linewidth 29 | * @property {text} fill 30 | * @property {number} x 31 | * @property {number} y 32 | * @property {number} x1 33 | * @property {number} x2 34 | * @property {number} y1 35 | * @property {number} y2 36 | * @property {text} componentType 37 | * @property {object} children 38 | * @property {text} updatedBy 39 | * @property {text} iconStroke 40 | * @property {text} textColor 41 | */ 42 | 43 | /** @type {componentInfo} */ 44 | let componentInfo = { 45 | metadata: {}, 46 | width: 120, 47 | height: 120, 48 | fill: '#0052CC', 49 | id: null, 50 | stroke: null, 51 | linewidth: null, 52 | x: 0, 53 | y: 0, 54 | x1: 100, 55 | x2: 400, 56 | y1: 100, 57 | y2: 100, 58 | componentType: '', 59 | children: {}, 60 | updatedBy: null, 61 | iconStroke: null, 62 | textColor: null, 63 | } 64 | -------------------------------------------------------------------------------- /temp.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI0ZmIyYjUwNS1jODE1LTRhMDEtOGZhNC03ZDdiMzI0NjhkZTIiLCJpYXQiOjE2MzYyODQzMjN9.Y_ldhZKYyoSzTFarqEg2GzxgIyNHSsf0fGKZNYoNw-8" 3 | } 4 | --------------------------------------------------------------------------------