From 6d02e91a2fb96d8dc3fb6951f461a0deb898def7 Mon Sep 17 00:00:00 2001 From: rajatmaheshwari2512 Date: Sun, 2 May 2021 14:46:55 +0530 Subject: [PATCH 01/14] Created API Endpoints for Version Control --- esim-cloud-backend/saveAPI/models.py | 5 ++- esim-cloud-backend/saveAPI/serializers.py | 4 +- esim-cloud-backend/saveAPI/urls.py | 10 ++++- esim-cloud-backend/saveAPI/views.py | 53 +++++++++++++++++++---- 4 files changed, 58 insertions(+), 14 deletions(-) diff --git a/esim-cloud-backend/saveAPI/models.py b/esim-cloud-backend/saveAPI/models.py index 9f8fe3da3..eb6925e5d 100644 --- a/esim-cloud-backend/saveAPI/models.py +++ b/esim-cloud-backend/saveAPI/models.py @@ -10,18 +10,19 @@ class StateSave(models.Model): + id = models.AutoField(primary_key=True) name = models.CharField(max_length=200, null=True) description = models.CharField(max_length=400, null=True) save_time = models.DateTimeField(auto_now=True, db_index=True) create_time = models.DateTimeField(auto_now_add=True) - save_id = models.UUIDField( - primary_key=True, default=uuid.uuid4) + save_id = models.UUIDField(default=uuid.uuid4) data_dump = models.TextField(null=False) shared = models.BooleanField(default=False) owner = models.ForeignKey( get_user_model(), null=True, on_delete=models.CASCADE) base64_image = models.ImageField( upload_to='circuit_images', storage=file_storage, null=True) + version = models.CharField(max_length=20, null=False) is_arduino = models.BooleanField(default=False, null=False) def save(self, *args, **kwargs): diff --git a/esim-cloud-backend/saveAPI/serializers.py b/esim-cloud-backend/saveAPI/serializers.py index 8ae28c98c..68d0f7723 100644 --- a/esim-cloud-backend/saveAPI/serializers.py +++ b/esim-cloud-backend/saveAPI/serializers.py @@ -34,7 +34,7 @@ class StateSaveSerializer(serializers.ModelSerializer): class Meta: model = StateSave fields = ('save_time', 'save_id', 'data_dump', 'name', 'description', - 'owner', 'shared', 'base64_image', 'create_time', + 'owner', 'shared', 'base64_image', 'create_time', "version", 'is_arduino') @@ -44,4 +44,4 @@ class SaveListSerializer(serializers.ModelSerializer): class Meta: model = StateSave fields = ('save_time', 'save_id', 'name', 'description', - 'shared', 'base64_image', 'create_time') + 'shared', 'base64_image', 'create_time', "version") diff --git a/esim-cloud-backend/saveAPI/urls.py b/esim-cloud-backend/saveAPI/urls.py index 45de1d88d..53b458674 100644 --- a/esim-cloud-backend/saveAPI/urls.py +++ b/esim-cloud-backend/saveAPI/urls.py @@ -22,12 +22,18 @@ path('save/arduino/list', saveAPI_views.ArduinoSaveList.as_view(), name='ArduinoSaveList'), - path('save/', + path('save//', saveAPI_views.StateFetchUpdateView.as_view(), name='fetchState'), - path('save//sharing/', + path('save//sharing//', saveAPI_views.StateShareView.as_view(), name='shareState'), + path("save/versions/", + saveAPI_views.StateSaveAllVersions.as_view(), name="listAllVersions"), + + path("save/versions//", + saveAPI_views.GetStateSpecificVersion.as_view(), name="getSpecificVersion") + ] diff --git a/esim-cloud-backend/saveAPI/views.py b/esim-cloud-backend/saveAPI/views.py index 5450a1726..6fca4f5d8 100644 --- a/esim-cloud-backend/saveAPI/views.py +++ b/esim-cloud-backend/saveAPI/views.py @@ -6,6 +6,7 @@ from rest_framework.parsers import FormParser, JSONParser from rest_framework.views import APIView from rest_framework.response import Response +from rest_framework.generics import ListAPIView from rest_framework import status from drf_yasg.utils import swagger_auto_schema from saveAPI.models import StateSave @@ -51,12 +52,13 @@ class StateFetchUpdateView(APIView): methods = ['GET'] @swagger_auto_schema(responses={200: StateSaveSerializer}) - def get(self, request, save_id): + def get(self, request, save_id, version): if isinstance(save_id, uuid.UUID): # Check for permissions and sharing settings here try: - saved_state = StateSave.objects.get(save_id=save_id) + saved_state = StateSave.objects.get( + save_id=save_id, version=version) except StateSave.DoesNotExist: return Response({'error': 'Does not Exist'}, status=status.HTTP_404_NOT_FOUND) @@ -129,11 +131,12 @@ def post(self, request, save_id): status=status.HTTP_400_BAD_REQUEST) @swagger_auto_schema(responses={200: StateSaveSerializer}) - def delete(self, request, save_id): + def delete(self, request, save_id, version): if isinstance(save_id, uuid.UUID): # Check for permissions and sharing settings here try: - saved_state = StateSave.objects.get(save_id=save_id) + saved_state = StateSave.objects.get( + save_id=save_id, version=version) except StateSave.DoesNotExist: return Response({'error': 'Does not Exist'}, status=status.HTTP_404_NOT_FOUND) @@ -162,12 +165,13 @@ class StateShareView(APIView): methods = ['GET'] @swagger_auto_schema(responses={200: StateSaveSerializer}) - def post(self, request, save_id, sharing): + def post(self, request, save_id, sharing, version): if isinstance(save_id, uuid.UUID): # Check for permissions and sharing settings here try: - saved_state = StateSave.objects.get(save_id=save_id) + saved_state = StateSave.objects.get( + save_id=save_id, version=version) except StateSave.DoesNotExist: return Response({'error': 'Does not Exist'}, status=status.HTTP_404_NOT_FOUND) @@ -207,8 +211,8 @@ class UserSavesView(APIView): @swagger_auto_schema(responses={200: StateSaveSerializer}) def get(self, request): - saved_state = StateSave.objects.filter( - owner=self.request.user, is_arduino=False).order_by('-save_time') + saved_state = StateSave.objects.order_by( + "save_id", "-save_time").distinct("save_id") try: serialized = StateSaveSerializer(saved_state, many=True) return Response(serialized.data) @@ -252,6 +256,7 @@ class SaveSearchViewSet(viewsets.ReadOnlyModelViewSet): """ Search Project """ + def get_queryset(self): queryset = StateSave.objects.filter( owner=self.request.user).order_by('-save_time') @@ -259,3 +264,35 @@ def get_queryset(self): serializer_class = SaveListSerializer filter_backends = (filters.DjangoFilterBackend,) filterset_class = SaveSearchFilterSet + + +class StateSaveAllVersions(APIView): + serializer_class = SaveListSerializer + permission_classes = (IsAuthenticated,) + + @swagger_auto_schema(responses={200: SaveListSerializer}) + def get(self, request, save_id): + queryset = StateSave.objects.filter( + owner=self.request.user, save_id=save_id) + try: + serialized = SaveListSerializer( + queryset, many=True, context={'request': request}) + return Response(serialized.data) + except Exception: + return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +class GetStateSpecificVersion(APIView): + serializer_class = StateSaveSerializer + permission_classes = (IsAuthenticated,) + + @swagger_auto_schema(responses={200: StateSaveSerializer}) + def get(self, request, save_id, version): + queryset = StateSave.objects.get( + save_id=save_id, version=version, owner=self.request.user) + try: + serialized = StateSaveSerializer( + queryset) + return Response(serialized.data) + except Exception: + return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) From e14120c57cad86e5511bd4e662ac1f121040046c Mon Sep 17 00:00:00 2001 From: rajatmaheshwari2512 Date: Sun, 2 May 2021 14:51:24 +0530 Subject: [PATCH 02/14] Frontend Integration of Version Control done --- .../src/components/Dashboard/SchematicCard.js | 196 +++--- .../SchematicEditor/PropertiesSidebar.js | 46 +- .../SchematicEditor/SchematicToolbar.js | 641 +++++++++++------- .../SchematicEditor/VersionComponent.js | 30 + eda-frontend/src/pages/SchematiEditor.js | 101 +-- .../src/redux/actions/dashboardActions.js | 4 +- .../src/redux/actions/saveSchematicActions.js | 233 ++++--- esim-cloud-backend/migrations.sh | 6 +- 8 files changed, 765 insertions(+), 492 deletions(-) create mode 100644 eda-frontend/src/components/SchematicEditor/VersionComponent.js diff --git a/eda-frontend/src/components/Dashboard/SchematicCard.js b/eda-frontend/src/components/Dashboard/SchematicCard.js index 72acaa59c..9e8e9495f 100644 --- a/eda-frontend/src/components/Dashboard/SchematicCard.js +++ b/eda-frontend/src/components/Dashboard/SchematicCard.js @@ -1,5 +1,5 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React from "react"; +import PropTypes from "prop-types"; import { Button, Typography, @@ -10,125 +10,152 @@ import { CardMedia, CardHeader, Tooltip, - Snackbar -} from '@material-ui/core' -import ShareIcon from '@material-ui/icons/Share' -import { makeStyles } from '@material-ui/core/styles' -import { Link as RouterLink } from 'react-router-dom' -import DeleteIcon from '@material-ui/icons/Delete' -import { useDispatch } from 'react-redux' -import { deleteSchematic } from '../../redux/actions/index' -import MuiAlert from '@material-ui/lab/Alert' + Snackbar, +} from "@material-ui/core"; +import ShareIcon from "@material-ui/icons/Share"; +import { makeStyles } from "@material-ui/core/styles"; +import { Link as RouterLink } from "react-router-dom"; +import DeleteIcon from "@material-ui/icons/Delete"; +import { useDispatch } from "react-redux"; +import { deleteSchematic } from "../../redux/actions/index"; +import MuiAlert from "@material-ui/lab/Alert"; const useStyles = makeStyles((theme) => ({ media: { height: 0, - paddingTop: '56.25%' // 16:9 + paddingTop: "56.25%", // 16:9 }, rating: { marginTop: theme.spacing(1), - marginLeft: 'auto' - } -})) -function Alert (props) { - return + marginLeft: "auto", + }, +})); +function Alert(props) { + return ; } // Schematic delete snackbar -function SimpleSnackbar ({ open, close, sch }) { - const dispatch = useDispatch() +function SimpleSnackbar({ open, close, sch }) { + const dispatch = useDispatch(); return ( - - - - - } + + + + + } > - {'Delete ' + sch.name + ' ?'} + {"Delete " + sch.name + " ?"} - ) + ); } SimpleSnackbar.propTypes = { open: PropTypes.bool, close: PropTypes.func, - sch: PropTypes.object -} + sch: PropTypes.object, +}; // Display schematic updated status (e.g : updated 2 hours ago...) -function timeSince (jsonDate) { - var json = jsonDate +function timeSince(jsonDate) { + var json = jsonDate; - var date = new Date(json) + var date = new Date(json); - var seconds = Math.floor((new Date() - date) / 1000) + var seconds = Math.floor((new Date() - date) / 1000); - var interval = Math.floor(seconds / 31536000) + var interval = Math.floor(seconds / 31536000); if (interval > 1) { - return interval + ' years' + return interval + " years"; } - interval = Math.floor(seconds / 2592000) + interval = Math.floor(seconds / 2592000); if (interval > 1) { - return interval + ' months' + return interval + " months"; } - interval = Math.floor(seconds / 86400) + interval = Math.floor(seconds / 86400); if (interval > 1) { - return interval + ' days' + return interval + " days"; } - interval = Math.floor(seconds / 3600) + interval = Math.floor(seconds / 3600); if (interval > 1) { - return interval + ' hours' + return interval + " hours"; } - interval = Math.floor(seconds / 60) + interval = Math.floor(seconds / 60); if (interval > 1) { - return interval + ' minutes' + return interval + " minutes"; } - return Math.floor(seconds) + ' seconds' + return Math.floor(seconds) + " seconds"; } // Display schematic created date (e.g : Created On 29 Jun 2020) -function getDate (jsonDate) { - var json = jsonDate - var date = new Date(json) - const dateTimeFormat = new Intl.DateTimeFormat('en', { year: 'numeric', month: 'short', day: '2-digit' }) - const [{ value: month }, , { value: day }, , { value: year }] = dateTimeFormat.formatToParts(date) - return `${day}-${month}-${year}` +function getDate(jsonDate) { + var json = jsonDate; + var date = new Date(json); + const dateTimeFormat = new Intl.DateTimeFormat("en", { + year: "numeric", + month: "short", + day: "2-digit", + }); + const [ + { value: month }, + , + { value: day }, + , + { value: year }, + ] = dateTimeFormat.formatToParts(date); + return `${day}-${month}-${year}`; } // Card displaying overview of onCloud saved schematic. -export default function SchematicCard ({ sch }) { - const classes = useStyles() +export default function SchematicCard({ sch }) { + const classes = useStyles(); // To handel delete schematic snackbar - const [snacOpen, setSnacOpen] = React.useState(false) + const [snacOpen, setSnacOpen] = React.useState(false); const handleSnacClick = () => { - setSnacOpen(true) - } + setSnacOpen(true); + }; const handleSnacClose = (event, reason) => { - if (reason === 'clickaway') { - return + if (reason === "clickaway") { + return; } - setSnacOpen(false) - } + setSnacOpen(false); + }; return ( <> @@ -137,7 +164,9 @@ export default function SchematicCard ({ sch }) { {/* Display updated status */} - + Updated {timeSince(sch.save_time)} ago... @@ -159,7 +193,7 @@ export default function SchematicCard ({ sch }) { {/* Display delete option */} - + { handleSnacClick() }} + style={{ marginLeft: "auto" }} + onClick={() => { + handleSnacClick(); + }} /> {/* Display share status */} - + - ) + ); } SchematicCard.propTypes = { - sch: PropTypes.object -} + sch: PropTypes.object, +}; diff --git a/eda-frontend/src/components/SchematicEditor/PropertiesSidebar.js b/eda-frontend/src/components/SchematicEditor/PropertiesSidebar.js index 4b3800f20..9d83f9f54 100644 --- a/eda-frontend/src/components/SchematicEditor/PropertiesSidebar.js +++ b/eda-frontend/src/components/SchematicEditor/PropertiesSidebar.js @@ -5,6 +5,8 @@ import { makeStyles } from '@material-ui/core/styles' import ComponentProperties from './ComponentProperties' import { useSelector, useDispatch } from 'react-redux' import { setSchDescription } from '../../redux/actions/index' +import api from "../../utils/Api" +import VersionComponent from "./VersionComponent" import './Helper/SchematicEditor.css' @@ -115,13 +117,48 @@ GridProperties.propTypes = { gridRef: PropTypes.object.isRequired } -export default function PropertiesSidebar ({ gridRef, outlineRef }) { +export default function PropertiesSidebar({ gridRef, outlineRef }) { const classes = useStyles() const isOpen = useSelector(state => state.componentPropertiesReducer.isPropertiesWindowOpen) const schSave = useSelector(state => state.saveSchematicReducer) const [description, setDescription] = React.useState(schSave.description) + const [versions, setVersions] = React.useState(null) + React.useEffect(() => { + const config = { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }; + const token = localStorage.getItem("esim_token") + // If token available add to headers + if (token) { + config.headers.Authorization = `Token ${token}` + } + api + .get( + "save/versions/" + + window.location.href.split("?id=")[1].substring(0, 36), + config + ) + .then((resp) => { + console.log(resp.data) + resp.data.forEach(value => { + var d = new Date(value.save_time); + value.date = + d.getDate() + "/"+ + d.getMonth() +"/"+ + d.getFullYear() + value.time = d.getHours() + ":" + d.getMinutes(); + if (d.getMinutes() < 10) + { + value.time = d.getHours() + ":0" + d.getMinutes(); + } + }) + setVersions(resp.data) + }); + }, []) const dispatch = useDispatch() @@ -160,8 +197,13 @@ export default function PropertiesSidebar ({ gridRef, outlineRef }) { - + + +

Versions

+
+ {versions !== null ? <>{versions.map((version) => )} : Loading} +
) } diff --git a/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js b/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js index b3642a221..85fabfae4 100644 --- a/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js +++ b/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js @@ -1,64 +1,88 @@ -import React from 'react' -import PropTypes from 'prop-types' -import Canvg from 'canvg' -import { IconButton, Tooltip, Snackbar } from '@material-ui/core' -import AddBoxOutlinedIcon from '@material-ui/icons/AddBoxOutlined' -import PlayCircleOutlineIcon from '@material-ui/icons/PlayCircleOutline' -import HelpOutlineIcon from '@material-ui/icons/HelpOutline' -import UndoIcon from '@material-ui/icons/Undo' -import RedoIcon from '@material-ui/icons/Redo' -import ZoomInIcon from '@material-ui/icons/ZoomIn' -import ZoomOutIcon from '@material-ui/icons/ZoomOut' -import DeleteIcon from '@material-ui/icons/Delete' -import SettingsOverscanIcon from '@material-ui/icons/SettingsOverscan' -import PrintOutlinedIcon from '@material-ui/icons/PrintOutlined' -import BugReportOutlinedIcon from '@material-ui/icons/BugReportOutlined' -import RotateRightIcon from '@material-ui/icons/RotateRight' -import BorderClearIcon from '@material-ui/icons/BorderClear' -import { makeStyles } from '@material-ui/core/styles' -import CloseIcon from '@material-ui/icons/Close' -import SaveOutlinedIcon from '@material-ui/icons/SaveOutlined' -import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser' -import ClearAllIcon from '@material-ui/icons/ClearAll' -import CreateNewFolderOutlinedIcon from '@material-ui/icons/CreateNewFolderOutlined' -import ImageOutlinedIcon from '@material-ui/icons/ImageOutlined' -import SystemUpdateAltOutlinedIcon from '@material-ui/icons/SystemUpdateAltOutlined' -import { Link as RouterLink } from 'react-router-dom' - -import { NetlistModal, HelpScreen, ImageExportDialog, OpenSchDialog } from './ToolbarExtension' -import { ZoomIn, ZoomOut, ZoomAct, DeleteComp, PrintPreview, ErcCheck, Rotate, GenerateNetList, Undo, Redo, Save, ClearGrid } from './Helper/ToolbarTools' -import { useSelector, useDispatch } from 'react-redux' -import { toggleSimulate, closeCompProperties, setSchXmlData, saveSchematic, openLocalSch } from '../../redux/actions/index' +import React from "react"; +import PropTypes from "prop-types"; +import Canvg from "canvg"; +import { IconButton, Tooltip, Snackbar } from "@material-ui/core"; +import AddBoxOutlinedIcon from "@material-ui/icons/AddBoxOutlined"; +import PlayCircleOutlineIcon from "@material-ui/icons/PlayCircleOutline"; +import HelpOutlineIcon from "@material-ui/icons/HelpOutline"; +import UndoIcon from "@material-ui/icons/Undo"; +import RedoIcon from "@material-ui/icons/Redo"; +import ZoomInIcon from "@material-ui/icons/ZoomIn"; +import ZoomOutIcon from "@material-ui/icons/ZoomOut"; +import DeleteIcon from "@material-ui/icons/Delete"; +import SettingsOverscanIcon from "@material-ui/icons/SettingsOverscan"; +import PrintOutlinedIcon from "@material-ui/icons/PrintOutlined"; +import BugReportOutlinedIcon from "@material-ui/icons/BugReportOutlined"; +import RotateRightIcon from "@material-ui/icons/RotateRight"; +import BorderClearIcon from "@material-ui/icons/BorderClear"; +import { makeStyles } from "@material-ui/core/styles"; +import CloseIcon from "@material-ui/icons/Close"; +import SaveOutlinedIcon from "@material-ui/icons/SaveOutlined"; +import OpenInBrowserIcon from "@material-ui/icons/OpenInBrowser"; +import ClearAllIcon from "@material-ui/icons/ClearAll"; +import CreateNewFolderOutlinedIcon from "@material-ui/icons/CreateNewFolderOutlined"; +import ImageOutlinedIcon from "@material-ui/icons/ImageOutlined"; +import SystemUpdateAltOutlinedIcon from "@material-ui/icons/SystemUpdateAltOutlined"; +import { Link as RouterLink } from "react-router-dom"; + +import { + NetlistModal, + HelpScreen, + ImageExportDialog, + OpenSchDialog, +} from "./ToolbarExtension"; +import { + ZoomIn, + ZoomOut, + ZoomAct, + DeleteComp, + PrintPreview, + ErcCheck, + Rotate, + GenerateNetList, + Undo, + Redo, + Save, + ClearGrid, +} from "./Helper/ToolbarTools"; +import { useSelector, useDispatch } from "react-redux"; +import { + toggleSimulate, + closeCompProperties, + setSchXmlData, + saveSchematic, + openLocalSch, +} from "../../redux/actions/index"; const useStyles = makeStyles((theme) => ({ menuButton: { - marginLeft: 'auto', + marginLeft: "auto", marginRight: theme.spacing(0), padding: theme.spacing(1), - [theme.breakpoints.up('lg')]: { - display: 'none' - } + [theme.breakpoints.up("lg")]: { + display: "none", + }, }, tools: { padding: theme.spacing(1), margin: theme.spacing(0, 0.5), - color: '#262626' + color: "#262626", }, pipe: { - fontSize: '1.45rem', - color: '#d6c4c2', - margin: theme.spacing(0, 1.5) - } -})) + fontSize: "1.45rem", + color: "#d6c4c2", + margin: theme.spacing(0, 1.5), + }, +})); // Notification snackbar to give alert messages -function SimpleSnackbar ({ open, close, message }) { +function SimpleSnackbar({ open, close, message }) { return (
- + } />
- ) + ); } SimpleSnackbar.propTypes = { open: PropTypes.bool, close: PropTypes.func, - message: PropTypes.string -} + message: PropTypes.string, +}; -export default function SchematicToolbar ({ mobileClose, gridRef }) { - const classes = useStyles() - const netfile = useSelector(state => state.netlistReducer) - const auth = useSelector(state => state.authReducer) - const schSave = useSelector(state => state.saveSchematicReducer) +export default function SchematicToolbar({ mobileClose, gridRef }) { + const classes = useStyles(); + const netfile = useSelector((state) => state.netlistReducer); + const auth = useSelector((state) => state.authReducer); + const schSave = useSelector((state) => state.saveSchematicReducer); - const dispatch = useDispatch() + const dispatch = useDispatch(); // Netlist Modal Control - const [open, setOpen] = React.useState(false) - const [netlist, genNetlist] = React.useState('') + const [open, setOpen] = React.useState(false); + const [netlist, genNetlist] = React.useState(""); const handleClickOpen = () => { - var compNetlist = GenerateNetList() - var netlist = netfile.title + '\n\n' + - compNetlist.models + '\n' + - compNetlist.main + '\n' + - netfile.controlLine + '\n' + - netfile.controlBlock + '\n' - genNetlist(netlist) - setOpen(true) - } + var compNetlist = GenerateNetList(); + var netlist = + netfile.title + + "\n\n" + + compNetlist.models + + "\n" + + compNetlist.main + + "\n" + + netfile.controlLine + + "\n" + + netfile.controlBlock + + "\n"; + genNetlist(netlist); + setOpen(true); + }; const handleClose = () => { - setOpen(false) - } + setOpen(false); + }; // Control Help dialog window - const [helpOpen, setHelpOpen] = React.useState(false) + const [helpOpen, setHelpOpen] = React.useState(false); const handleHelpOpen = () => { - setHelpOpen(true) - } + setHelpOpen(true); + }; const handleHelpClose = () => { - setHelpOpen(false) - } + setHelpOpen(false); + }; // Handel Delete component const handleDeleteComp = () => { - DeleteComp() - dispatch(closeCompProperties()) - } + DeleteComp(); + dispatch(closeCompProperties()); + }; // Handel Notification Snackbar - const [snacOpen, setSnacOpen] = React.useState(false) - const [message, setMessage] = React.useState('') + const [snacOpen, setSnacOpen] = React.useState(false); + const [message, setMessage] = React.useState(""); const handleSnacClick = () => { - setSnacOpen(true) - } + setSnacOpen(true); + }; const handleSnacClose = (event, reason) => { - if (reason === 'clickaway') { - return + if (reason === "clickaway") { + return; } - setSnacOpen(false) - } + setSnacOpen(false); + }; // Image Export of Schematic Diagram - async function exportImage (type) { - const svg = document.querySelector('#divGrid > svg').cloneNode(true) - svg.removeAttribute('style') - svg.setAttribute('width', gridRef.current.scrollWidth) - svg.setAttribute('height', gridRef.current.scrollHeight) - const canvas = document.createElement('canvas') - canvas.width = gridRef.current.scrollWidth - canvas.height = gridRef.current.scrollHeight - canvas.style.width = canvas.width + 'px' - canvas.style.height = canvas.height + 'px' - var images = svg.getElementsByTagName('image') + async function exportImage(type) { + const svg = document.querySelector("#divGrid > svg").cloneNode(true); + svg.removeAttribute("style"); + svg.setAttribute("width", gridRef.current.scrollWidth); + svg.setAttribute("height", gridRef.current.scrollHeight); + const canvas = document.createElement("canvas"); + canvas.width = gridRef.current.scrollWidth; + canvas.height = gridRef.current.scrollHeight; + canvas.style.width = canvas.width + "px"; + canvas.style.height = canvas.height + "px"; + var images = svg.getElementsByTagName("image"); for (var image of images) { - const data = await fetch(image.getAttribute('xlink:href')).then((v) => { - return v.text() - }) - image.removeAttribute('xlink:href') + const data = await fetch(image.getAttribute("xlink:href")).then((v) => { + return v.text(); + }); + image.removeAttribute("xlink:href"); image.setAttribute( - 'href', - 'data:image/svg+xml;base64,' + window.btoa(data) - ) + "href", + "data:image/svg+xml;base64," + window.btoa(data) + ); } - var ctx = canvas.getContext('2d') - ctx.mozImageSmoothingEnabled = true - ctx.webkitImageSmoothingEnabled = true - ctx.msImageSmoothingEnabled = true - ctx.imageSmoothingEnabled = true - const pixelRatio = window.devicePixelRatio || 1 - ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0) - return new Promise(resolve => { - if (type === 'SVG') { - var svgdata = new XMLSerializer().serializeToString(svg) - resolve('' + svgdata) - return + var ctx = canvas.getContext("2d"); + ctx.mozImageSmoothingEnabled = true; + ctx.webkitImageSmoothingEnabled = true; + ctx.msImageSmoothingEnabled = true; + ctx.imageSmoothingEnabled = true; + const pixelRatio = window.devicePixelRatio || 1; + ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); + return new Promise((resolve) => { + if (type === "SVG") { + var svgdata = new XMLSerializer().serializeToString(svg); + resolve('' + svgdata); + return; } - var v = Canvg.fromString(ctx, svg.outerHTML) + var v = Canvg.fromString(ctx, svg.outerHTML); v.render().then(() => { - var image = '' - if (type === 'JPG') { - const imgdata = ctx.getImageData(0, 0, canvas.width, canvas.height) + var image = ""; + if (type === "JPG") { + const imgdata = ctx.getImageData(0, 0, canvas.width, canvas.height); for (let i = 0; i < imgdata.data.length; i += 4) { if (imgdata.data[i + 3] === 0) { - imgdata.data[i] = 255 - imgdata.data[i + 1] = 255 - imgdata.data[i + 2] = 255 - imgdata.data[i + 3] = 255 + imgdata.data[i] = 255; + imgdata.data[i + 1] = 255; + imgdata.data[i + 2] = 255; + imgdata.data[i + 3] = 255; } } - ctx.putImageData(imgdata, 0, 0) - image = canvas.toDataURL('image/jpeg', 1.0) + ctx.putImageData(imgdata, 0, 0); + image = canvas.toDataURL("image/jpeg", 1.0); } else { - if (type === 'PNG') { - image = canvas.toDataURL('image/png') + if (type === "PNG") { + image = canvas.toDataURL("image/png"); } } - resolve(image) - }) - }) + resolve(image); + }); + }); } // Download JPEG, PNG exported Image - function downloadImage (data, type) { - var evt = new MouseEvent('click', { + function downloadImage(data, type) { + var evt = new MouseEvent("click", { view: window, bubbles: false, - cancelable: true - }) - var a = document.createElement('a') - const ext = (type === 'PNG') ? '.png' : '.jpg' - a.setAttribute('download', schSave.title + '_eSim_on_cloud' + ext) - a.setAttribute('href', data) - a.setAttribute('target', '_blank') - a.dispatchEvent(evt) + cancelable: true, + }); + var a = document.createElement("a"); + const ext = type === "PNG" ? ".png" : ".jpg"; + a.setAttribute("download", schSave.title + "_eSim_on_cloud" + ext); + a.setAttribute("href", data); + a.setAttribute("target", "_blank"); + a.dispatchEvent(evt); } // Download SVG image - function downloadText (data, options) { - const blob = new Blob(data, options) - const evt = new MouseEvent('click', { + function downloadText(data, options) { + const blob = new Blob(data, options); + const evt = new MouseEvent("click", { view: window, bubbles: false, - cancelable: true - }) - const a = document.createElement('a') - a.setAttribute('download', schSave.title + '_eSim_on_cloud.svg') - a.href = URL.createObjectURL(blob) - a.target = '_blank' - a.setAttribute('target', '_blank') - a.dispatchEvent(evt) + cancelable: true, + }); + const a = document.createElement("a"); + a.setAttribute("download", schSave.title + "_eSim_on_cloud.svg"); + a.href = URL.createObjectURL(blob); + a.target = "_blank"; + a.setAttribute("target", "_blank"); + a.dispatchEvent(evt); } - const [imgopen, setImgOpen] = React.useState(false) + const [imgopen, setImgOpen] = React.useState(false); const handleImgClickOpen = () => { - setImgOpen(true) - } + setImgOpen(true); + }; const handleImgClose = (value) => { - setImgOpen(false) - if (value === 'SVG') { - exportImage('SVG') - .then(v => { - downloadText([v], { - type: 'data:image/svg+xml;charset=utf-8;' - }) - }) - } else if (value === 'PNG') { - exportImage('PNG') - .then(v => { - downloadImage(v, 'PNG') - }) - } else if (value === 'JPG') { - exportImage('JPG') - .then(v => { - downloadImage(v, 'JPG') - }) + setImgOpen(false); + if (value === "SVG") { + exportImage("SVG").then((v) => { + downloadText([v], { + type: "data:image/svg+xml;charset=utf-8;", + }); + }); + } else if (value === "PNG") { + exportImage("PNG").then((v) => { + downloadImage(v, "PNG"); + }); + } else if (value === "JPG") { + exportImage("JPG").then((v) => { + downloadImage(v, "JPG"); + }); } - } + }; // Handel Save Schematic onCloud const handelSchSave = () => { if (auth.isAuthenticated !== true) { - setMessage('You are not Logged In') - handleSnacClick() + setMessage("You are not Logged In"); + handleSnacClick(); } else { - var xml = Save() - dispatch(setSchXmlData(xml)) - var title = schSave.title - var description = schSave.description - exportImage('PNG') - .then(res => { - dispatch(saveSchematic(title, description, xml, res)) - }) - setMessage('Saved Successfully') - handleSnacClick() + var xml = Save(); + dispatch(setSchXmlData(xml)); + var title = schSave.title; + var description = schSave.description; + exportImage("PNG").then((res) => { + dispatch(saveSchematic(title, description, xml, res)); + }); + setMessage("Saved Successfully"); + handleSnacClick(); } - } + }; // Save Schematics Locally const handelLocalSchSave = () => { - var saveLocalData = {} - saveLocalData.data_dump = Save() - saveLocalData.title = schSave.title - saveLocalData.description = schSave.description - var json = JSON.stringify(saveLocalData) - const blob = new Blob([json], { type: 'octet/stream' }) - const evt = new MouseEvent('click', { + var saveLocalData = {}; + saveLocalData.data_dump = Save(); + saveLocalData.title = schSave.title; + saveLocalData.description = schSave.description; + var json = JSON.stringify(saveLocalData); + const blob = new Blob([json], { type: "octet/stream" }); + const evt = new MouseEvent("click", { view: window, bubbles: false, - cancelable: true - }) - const a = document.createElement('a') - a.setAttribute('download', schSave.title + '_eSim_on_cloud.json') - a.href = URL.createObjectURL(blob) - a.target = '_blank' - a.setAttribute('target', '_blank') - a.dispatchEvent(evt) - } + cancelable: true, + }); + const a = document.createElement("a"); + a.setAttribute("download", schSave.title + "_eSim_on_cloud.json"); + a.href = URL.createObjectURL(blob); + a.target = "_blank"; + a.setAttribute("target", "_blank"); + a.dispatchEvent(evt); + }; // Open Locally Saved Schematic const handelLocalSchOpen = () => { - var obj = {} - const fileSelector = document.createElement('input') - fileSelector.setAttribute('type', 'file') - fileSelector.setAttribute('accept', 'application/JSON') - fileSelector.click() - fileSelector.addEventListener('change', function (event) { - var reader = new FileReader() - var filename = event.target.files[0].name - if (filename.slice(filename.length - 4) === 'json') { - reader.onload = onReaderLoad - reader.readAsText(event.target.files[0]) + var obj = {}; + const fileSelector = document.createElement("input"); + fileSelector.setAttribute("type", "file"); + fileSelector.setAttribute("accept", "application/JSON"); + fileSelector.click(); + fileSelector.addEventListener("change", function (event) { + var reader = new FileReader(); + var filename = event.target.files[0].name; + if (filename.slice(filename.length - 4) === "json") { + reader.onload = onReaderLoad; + reader.readAsText(event.target.files[0]); } else { - setMessage('Unsupported file type error ! Select valid file.') - handleSnacClick() + setMessage("Unsupported file type error ! Select valid file."); + handleSnacClick(); } - }) + }); const onReaderLoad = function (event) { - obj = JSON.parse(event.target.result) - if (obj.data_dump === undefined || obj.title === undefined || obj.description === undefined) { - setMessage('Unsupported file error !') - handleSnacClick() + obj = JSON.parse(event.target.result); + if ( + obj.data_dump === undefined || + obj.title === undefined || + obj.description === undefined + ) { + setMessage("Unsupported file error !"); + handleSnacClick(); } else { - dispatch(openLocalSch(obj)) + dispatch(openLocalSch(obj)); } - } - } + }; + }; // Control Help dialog window open and close - const [schOpen, setSchOpen] = React.useState(false) + const [schOpen, setSchOpen] = React.useState(false); const handleSchDialOpen = () => { - setSchOpen(true) - } + setSchOpen(true); + }; const handleSchDialClose = () => { - setSchOpen(false) - } + setSchOpen(false); + }; return ( <> - + - + - + - + - + | - + - + - + | - { dispatch(toggleSimulate()) }}> + { + dispatch(toggleSimulate()); + }} + > - + - + | - + - + - + | - + - + - + | - + - + - + - ) + ); } SchematicToolbar.propTypes = { mobileClose: PropTypes.func, - gridRef: PropTypes.object.isRequired -} + gridRef: PropTypes.object.isRequired, +}; diff --git a/eda-frontend/src/components/SchematicEditor/VersionComponent.js b/eda-frontend/src/components/SchematicEditor/VersionComponent.js new file mode 100644 index 000000000..51255cd8c --- /dev/null +++ b/eda-frontend/src/components/SchematicEditor/VersionComponent.js @@ -0,0 +1,30 @@ +import React from "react" +import Button from "@material-ui/core/Button" +import { Link as RouterLink } from "react-router-dom" + +export default function VersionComponent({ + name, + date, + time, + save_id, + version, +}) { + return ( + <> + + + {(versions&&branchOpen) ? <> diff --git a/eda-frontend/src/redux/actions/saveSchematicActions.js b/eda-frontend/src/redux/actions/saveSchematicActions.js index c8493cc56..42de64e10 100644 --- a/eda-frontend/src/redux/actions/saveSchematicActions.js +++ b/eda-frontend/src/redux/actions/saveSchematicActions.js @@ -34,7 +34,7 @@ export const setSchXmlData = (xmlData) => (dispatch) => { }; // Api call to save new schematic or updating saved schematic. -export const saveSchematic = (title, description, xml, base64,newBranch=false) => ( +export const saveSchematic = (title, description, xml, base64,newBranch=false,branchName=null) => ( dispatch, getState ) => { @@ -100,9 +100,7 @@ export const saveSchematic = (title, description, xml, base64,newBranch=false) = else { console.log("New Branch not Version") body.save_id = schSave.details.save_id; - body.branch = randomstring.generate({ - length: 20, - }) + body.branch = branchName body.version = schSave.details.version api .post("save", queryString.stringify(body), config) @@ -179,7 +177,7 @@ export const setSchShared = (share) => (dispatch, getState) => { api .post( - "save/" + schSave.details.save_id + "/sharing/" + isShared+"/"+schSave.details.version, + "save/" + schSave.details.save_id + "/sharing/" + isShared+"/"+schSave.details.version+"/"+schSave.details.branch, {}, config ) From 7a935838c9daf47d39c05671b19d7a6a2be8fc5c Mon Sep 17 00:00:00 2001 From: rajatmaheshwari2512 Date: Wed, 2 Jun 2021 17:22:13 +0530 Subject: [PATCH 12/14] Auto render new branches --- .../SchematicEditor/PropertiesSidebar.js | 24 +++++++--- .../src/redux/actions/saveSchematicActions.js | 44 +++++++++++-------- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/eda-frontend/src/components/SchematicEditor/PropertiesSidebar.js b/eda-frontend/src/components/SchematicEditor/PropertiesSidebar.js index a76a0be42..87485c99d 100644 --- a/eda-frontend/src/components/SchematicEditor/PropertiesSidebar.js +++ b/eda-frontend/src/components/SchematicEditor/PropertiesSidebar.js @@ -246,8 +246,9 @@ export default function PropertiesSidebar({ gridRef, outlineRef }) { const handleBranch = (branchName) => { setDialogOpen(false) exportImage("PNG").then((res) => { - dispatch(saveSchematic(schSave.title,schSave.description,schSave.xmlData,res,true,branchName)) + dispatch(saveSchematic(schSave.title,schSave.description,schSave.xmlData,res,true,branchName,setVersions,versions)) }) + setBranchName("") } const handleClick = (index) => { @@ -307,7 +308,7 @@ export default function PropertiesSidebar({ gridRef, outlineRef }) { -

Versions

+

History

- Create new branch + Create new Sub-Feature
+
@@ -341,7 +351,7 @@ export default function PropertiesSidebar({ gridRef, outlineRef }) { return ( <> handleClick(index)}> - + { @@ -363,7 +373,7 @@ export default function PropertiesSidebar({ gridRef, outlineRef }) { ) } - : Loading + : No History Available }
diff --git a/eda-frontend/src/redux/actions/saveSchematicActions.js b/eda-frontend/src/redux/actions/saveSchematicActions.js index 42de64e10..c17318c6c 100644 --- a/eda-frontend/src/redux/actions/saveSchematicActions.js +++ b/eda-frontend/src/redux/actions/saveSchematicActions.js @@ -34,7 +34,7 @@ export const setSchXmlData = (xmlData) => (dispatch) => { }; // Api call to save new schematic or updating saved schematic. -export const saveSchematic = (title, description, xml, base64,newBranch=false,branchName=null) => ( +export const saveSchematic = (title, description, xml, base64,newBranch=false,branchName=null,setVersions,versions) => ( dispatch, getState ) => { @@ -80,9 +80,7 @@ export const saveSchematic = (title, description, xml, base64,newBranch=false,br console.error(err); }); } else { - body.branch = randomstring.generate({ - length: 20, - }) + body.branch = "master" // saving new schematic api .post("save", queryString.stringify(body), config) @@ -99,20 +97,30 @@ export const saveSchematic = (title, description, xml, base64,newBranch=false,br } else { console.log("New Branch not Version") - body.save_id = schSave.details.save_id; - body.branch = branchName - body.version = schSave.details.version - api - .post("save", queryString.stringify(body), config) - .then((res) => { - dispatch({ - type: actions.SET_SCH_SAVED, - payload: res.data, - }); - }) - .catch((err) => { - console.error(err); - }) + var flag = 0 + for (var i = 0; i < versions.length; i++){ + if (branchName === versions[i][0]) + flag=1 + } + if (!flag) { + body.save_id = schSave.details.save_id; + body.branch = branchName + body.version = schSave.details.version + api + .post("save", queryString.stringify(body), config) + .then((res) => { + var temp = versions + temp.push([res.data.branch, [res.data]]) + setVersions(temp) + dispatch({ + type: actions.SET_SCH_SAVED, + payload: res.data, + }); + }) + .catch((err) => { + console.error(err); + }) + } } }; From f97da8db82ed55c4d4e66cb896d57663c0f57cfc Mon Sep 17 00:00:00 2001 From: rajatmaheshwari2512 Date: Mon, 7 Jun 2021 10:50:38 +0530 Subject: [PATCH 13/14] Finished versioning and page reloading --- .../SchematicEditor/PropertiesSidebar.js | 19 ++++++++-------- .../SchematicEditor/SchematicToolbar.js | 15 +++++++++++-- .../SchematicEditor/VersionComponent.js | 11 ++++++---- .../src/redux/actions/saveSchematicActions.js | 11 +++++++++- esim-cloud-backend/saveAPI/serializers.py | 4 ++-- esim-cloud-backend/saveAPI/views.py | 22 ++++++++++--------- 6 files changed, 53 insertions(+), 29 deletions(-) diff --git a/eda-frontend/src/components/SchematicEditor/PropertiesSidebar.js b/eda-frontend/src/components/SchematicEditor/PropertiesSidebar.js index 87485c99d..f62f69c74 100644 --- a/eda-frontend/src/components/SchematicEditor/PropertiesSidebar.js +++ b/eda-frontend/src/components/SchematicEditor/PropertiesSidebar.js @@ -162,7 +162,7 @@ export default function PropertiesSidebar({ gridRef, outlineRef }) { } versionsAccordingFreq[value.branch]?versionsAccordingFreq[value.branch].push(value):versionsAccordingFreq[value.branch]=[value] }); - setVersions(Object.entries(versionsAccordingFreq)) + setVersions(Object.entries(versionsAccordingFreq).reverse()) var temp=[]; for(var i=0;i - Create new Sub-Feature + Create new Variation @@ -331,16 +331,15 @@ export default function PropertiesSidebar({ gridRef, outlineRef }) { variant="contained" color="primary" onClick={() => handleBranch(branchName) }> - Set name and create sub-feature + Create Variation @@ -351,7 +350,7 @@ export default function PropertiesSidebar({ gridRef, outlineRef }) { return ( <> handleClick(index)}> - + { diff --git a/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js b/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js index 602acc75d..1aaa2623d 100644 --- a/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js +++ b/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js @@ -121,7 +121,18 @@ export default function SchematicToolbar({ mobileClose, gridRef }) { // Netlist Modal Control const [open, setOpen] = React.useState(false); - const [netlist, genNetlist] = React.useState(""); + const [netlist, genNetlist] = React.useState("") + + const handleSave = (version, newSave,save_id) => { + if (!newSave) { + window.location = "#/editor?id=" + window.location.href.split("id=")[1].substr(0, 36) + "&version=" + version + "&branch=" + window.location.href.split("branch=")[1].substr(0) + window.location.reload() + } + else { + window.location = "#/editor?id=" + save_id + "&version=" + version + "&branch=master" + window.location.reload() + } + } const handleClickOpen = () => { var compNetlist = GenerateNetList(); @@ -303,7 +314,7 @@ export default function SchematicToolbar({ mobileClose, gridRef }) { var title = schSave.title; var description = schSave.description; exportImage("PNG").then((res) => { - dispatch(saveSchematic(title, description, xml, res, false)); + dispatch(saveSchematic(title, description, xml, res, false, null, handleSave)); }); setMessage("Saved Successfully"); handleSnacClick(); diff --git a/eda-frontend/src/components/SchematicEditor/VersionComponent.js b/eda-frontend/src/components/SchematicEditor/VersionComponent.js index 5dc0ce18e..0cc38ba07 100644 --- a/eda-frontend/src/components/SchematicEditor/VersionComponent.js +++ b/eda-frontend/src/components/SchematicEditor/VersionComponent.js @@ -10,16 +10,19 @@ export default function VersionComponent({ version, branch }) { + const handleClick = (e) => { + e.preventDefault() + window.location = "#/editor?id=" + save_id + "&version=" + version + "&branch=" + branch + window.location.reload() + } return ( <>