diff options
Diffstat (limited to 'blocks')
31 files changed, 569 insertions, 545 deletions
diff --git a/blocks/.dockerignore b/blocks/.dockerignore index 5e6b38b0..6de483dd 100644 --- a/blocks/.dockerignore +++ b/blocks/.dockerignore @@ -9,8 +9,11 @@ env* *.sqlite3 *.swp .DS_Store -**/.env +.env **/.env.* +**/.git* +**/.nvmrc +**/.vscode **/build **/node_modules tags diff --git a/blocks/Dockerfile b/blocks/Dockerfile index f8bfc6ae..f07ce843 100644 --- a/blocks/Dockerfile +++ b/blocks/Dockerfile @@ -105,7 +105,7 @@ FROM base AS build-xcos RUN apt-get update -qq && \ apt-get install -qq --no-install-recommends gawk python3-pip python3-venv wget xz-utils -ARG NODE_VERSION=v20.19.0 +ARG NODE_VERSION=v20.19.3 ARG NODE=node-${NODE_VERSION}-linux-x64 WORKDIR ${HOME} RUN wget -q --no-hsts https://nodejs.org/download/release/${NODE_VERSION}/${NODE}.tar.xz && \ @@ -121,9 +121,6 @@ COPY . . # Configure venv and sqlite3 RUN ./install.sh -# For localhost only -RUN echo 'WDS_SOCKET_PORT=80' > eda-frontend/.env.local - # Cleanup RUN apt-get autoremove -qq --purge gawk python3-pip python3-venv wget xz-utils RUN apt-get clean -qq diff --git a/blocks/Dockerfile.patch b/blocks/Dockerfile.patch index 0a94dd1c..069a62cc 100644 --- a/blocks/Dockerfile.patch +++ b/blocks/Dockerfile.patch @@ -1,5 +1,5 @@ ---- Dockerfile 2025-07-04 16:16:28.040919228 +0530 -+++ Dockerfile.1 2025-07-04 16:19:16.930142599 +0530 +--- Dockerfile 2025-07-08 18:20:34.818712259 +0530 ++++ Dockerfile.1 2025-07-08 18:21:40.192702872 +0530 @@ -119,7 +119,7 @@ COPY . . @@ -7,9 +7,9 @@ -RUN ./install.sh +RUN ./install.sh prod - # For localhost only - RUN echo 'WDS_SOCKET_PORT=80' > eda-frontend/.env.local -@@ -173,4 +173,4 @@ + # Cleanup + RUN apt-get autoremove -qq --purge gawk python3-pip python3-venv wget xz-utils +@@ -170,4 +170,4 @@ WORKDIR ${XCOS_DIR} diff --git a/blocks/authAPI/emails.py b/blocks/authAPI/emails.py new file mode 100644 index 00000000..2f112c74 --- /dev/null +++ b/blocks/authAPI/emails.py @@ -0,0 +1,16 @@ +from djoser import email +from django.conf import settings + + +class CustomActivationEmail(email.ActivationEmail): + def get_context_data(self): + context = super().get_context_data() + context['protocol'] = settings.DEFAULT_PROTOCOL + return context + + +class CustomPasswordResetEmail(email.PasswordResetEmail): + def get_context_data(self): + context = super().get_context_data() + context['protocol'] = settings.DEFAULT_PROTOCOL + return context diff --git a/blocks/authAPI/views.py b/blocks/authAPI/views.py index e1cc173b..8afd799a 100644 --- a/blocks/authAPI/views.py +++ b/blocks/authAPI/views.py @@ -10,7 +10,6 @@ from django.http import HttpResponseNotFound from djoser import utils from djoser.serializers import TokenSerializer from authAPI.serializers import TokenCreateSerializer -from blocks.settings import DOMAIN Token = djoser_settings.TOKEN_MODEL @@ -26,8 +25,7 @@ def activate_user(request, uid, token): Link to this route is sent via email to user for verification """ - protocol = 'https://' if request.is_secure() else 'http://' - web_url = protocol + request.get_host() + '/api/auth/users/activation/' # URL comes from Djoser library + web_url = settings.POST_ACTIVATE_REDIRECT_URL + 'api/auth/users/activation/' # URL comes from Djoser library return render(request, 'activate_user.html', {'uid': uid, 'token': token, @@ -50,8 +48,7 @@ def get_social_user(email, request, callback, service): user.save() token, created = Token.objects.get_or_create(user=user) - protocol = 'https://' if request.is_secure() else 'http://' - web_url = protocol + DOMAIN + '/#/dashboard' + web_url = settings.POST_ACTIVATE_REDIRECT_URL + '#/dashboard' return render(request, callback, {'token': token, diff --git a/blocks/blocks/settings.py b/blocks/blocks/settings.py index 0f95850b..e62c1fda 100644 --- a/blocks/blocks/settings.py +++ b/blocks/blocks/settings.py @@ -13,6 +13,7 @@ https://docs.djangoproject.com/en/3.1/ref/settings/ import os from pathlib import Path from dotenv import load_dotenv +from urllib.parse import urlparse # Loading the .env file load_dotenv() @@ -136,12 +137,14 @@ SESSION_COOKIE_SAMESITE = 'Lax' # use this for console emails EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' -# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' -# EMAIL_HOST = os.environ.get('EMAIL_HOST', 'smtp.gmail.com') -# EMAIL_PORT = os.environ.get('EMAIL_PORT', 587) -# EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', 'email@gmail.com') -# EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', 'gmailpassword') -# EMAIL_USE_TLS = os.environ.get('EMAIL_USE_TLS', True) +EMAIL_HOST = os.environ.get('EMAIL_HOST', '') +EMAIL_PORT = int(os.environ.get('EMAIL_PORT', '587')) +EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '') +EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '') +EMAIL_USE_TLS = os.environ.get('EMAIL_USE_TLS', 'true').lower() == 'true' +DEFAULT_FROM_EMAIL = os.environ.get('FROM_EMAIL', 'webmaster@localhost') +if EMAIL_HOST: + EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = os.environ.get('SOCIAL_AUTH_GOOGLE_OAUTH2_KEY', '') SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = os.environ.get('SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET', '') @@ -153,7 +156,9 @@ GITHUB_OAUTH_REDIRECT_URI = os.environ.get('GITHUB_OAUTH_REDIRECT_URI', 'http://localhost/api/auth/github-callback') POST_ACTIVATE_REDIRECT_URL = os.environ.get('POST_ACTIVATE_REDIRECT_URL', 'http://localhost/') -DOMAIN = os.environ.get('EMAIL_DOMAIN', 'localhost') +parsed_url = urlparse(POST_ACTIVATE_REDIRECT_URL) +DEFAULT_PROTOCOL = parsed_url.scheme or 'http' +DOMAIN = parsed_url.netloc or 'localhost' SITE_NAME = os.environ.get('EMAIL_SITE_NAME', 'Xcos') DJOSER = { @@ -178,6 +183,10 @@ DJOSER = { 'user_delete': 'djoser.serializers.UserDeleteSerializer', 'token_create': 'authAPI.serializers.TokenCreateSerializer', }, + 'EMAIL': { + 'activation': 'authAPI.emails.CustomActivationEmail', + 'password_reset': 'authAPI.emails.CustomPasswordResetEmail', + } } REST_FRAMEWORK = { @@ -210,6 +219,9 @@ TRUSTED_ORIGINS = os.environ.get('TRUSTED_ORIGINS', 'http://localhost') CORS_ALLOWED_ORIGINS = [i for i in ALLOWED_ORIGINS.split(',') if i != ''] CSRF_TRUSTED_ORIGINS = [i for i in TRUSTED_ORIGINS.split(',') if i != ''] CORS_ALLOW_CREDENTIALS = True +CSRF_COOKIE_HTTPONLY = os.environ.get('COOKIE_HTTPONLY', 'false').lower() == 'true' +CSRF_COOKIE_SAMESITE = os.environ.get('COOKIE_SAMESITE', 'Lax') +CSRF_COOKIE_SECURE = os.environ.get('COOKIE_SECURE', 'false').lower() == 'true' # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.1/howto/static-files/ diff --git a/blocks/blocks/xcosblocks/urls.py b/blocks/blocks/xcosblocks/urls.py index a17259d7..6205e437 100644 --- a/blocks/blocks/xcosblocks/urls.py +++ b/blocks/blocks/xcosblocks/urls.py @@ -1,7 +1,7 @@ from django.urls import path from rest_framework.routers import SimpleRouter -from .views import CategoryViewSet, \ +from .views import init_csrf, CategoryViewSet, \ NewBlockViewSet, NewBlockParameterViewSet, \ get_block_images, set_block_parameter @@ -12,7 +12,7 @@ router.register(r'newblocks', NewBlockViewSet) router.register(r'newblockparameters', NewBlockParameterViewSet) urlpatterns = router.urls + [ + path(r'init', init_csrf, name='init-csrf'), path(r'block_images', get_block_images), - path(r'setblockparameter', set_block_parameter), ] diff --git a/blocks/blocks/xcosblocks/views.py b/blocks/blocks/xcosblocks/views.py index 64bc5ff7..2754f915 100644 --- a/blocks/blocks/xcosblocks/views.py +++ b/blocks/blocks/xcosblocks/views.py @@ -1,4 +1,5 @@ from django.http import JsonResponse +from django.views.decorators.csrf import ensure_csrf_cookie from django_filters import FilterSet from django_filters.rest_framework import DjangoFilterBackend import io @@ -13,6 +14,11 @@ from .serializers import CategorySerializer, \ NewBlockSerializer, NewBlockParameterSerializer, NewBlockPortSerializer +@ensure_csrf_cookie +def init_csrf(request): + return JsonResponse({"message": "CSRF cookie set"}) + + class CategoryFilterSet(FilterSet): class Meta: model = Category diff --git a/blocks/eda-frontend/.babelrc.json b/blocks/eda-frontend/.babelrc.json deleted file mode 100644 index 7dd5e9df..00000000 --- a/blocks/eda-frontend/.babelrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["@babel/preset-react"] -} diff --git a/blocks/eda-frontend/package-lock.json b/blocks/eda-frontend/package-lock.json index 3bad5db2..778e966a 100644 --- a/blocks/eda-frontend/package-lock.json +++ b/blocks/eda-frontend/package-lock.json @@ -19,7 +19,7 @@ "highcharts-react-official": "^3.2.2", "mxgraph": "^4.2.2", "prop-types": "^15.8.1", - "query-string": "^9.2.1", + "query-string": "^9.2.2", "react": "^17.0.2", "react-dom": "^17.0.2", "react-loader-spinner": "^6.1.6", @@ -30,14 +30,14 @@ "xml-beautifier": "^0.5.0" }, "devDependencies": { - "@babel/eslint-parser": "^7.27.5", - "eslint": "^9.30.0", + "@babel/eslint-parser": "^7.28.0", + "eslint": "^9.31.0", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-n": "^17.20.0", + "eslint-plugin-n": "^17.21.0", "eslint-plugin-promise": "^7.2.1", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", - "globals": "^16.2.0", + "globals": "^16.3.0", "react-dev-utils": "^12.0.1", "react-scripts": "^5.0.1", "source-map-explorer": "^2.5.2" @@ -120,9 +120,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.27.5.tgz", - "integrity": "sha512-HLkYQfRICudzcOtjGwkPvGc5nF1b4ljLZh1IRDj50lRZ718NAKVgQpIAUX8bfg6u/yuSKY3L7E0YzIV+OxrB8Q==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.0.tgz", + "integrity": "sha512-N4ntErOlKvcbTt01rr5wj3y55xnIdx1ymrfIr8C2WnM1Y9glFgWaGDEULJIazOX3XM9NRzhfJ6zZnQ1sBNWU+w==", "dev": true, "license": "MIT", "dependencies": { @@ -2400,9 +2400,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.30.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.0.tgz", - "integrity": "sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", "dev": true, "license": "MIT", "engines": { @@ -4540,42 +4540,6 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", - "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.33.1", - "@typescript-eslint/types": "^8.33.1", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", - "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/scope-manager": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", @@ -4594,23 +4558,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", - "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, "node_modules/@typescript-eslint/type-utils": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", @@ -4734,161 +4681,6 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", - "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", - "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", - "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", - "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.33.1", - "@typescript-eslint/tsconfig-utils": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", - "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.33.1", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/visitor-keys": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", @@ -6356,9 +6148,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001699", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz", - "integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==", + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", "dev": true, "funding": [ { @@ -8190,9 +7982,9 @@ } }, "node_modules/eslint": { - "version": "9.30.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.0.tgz", - "integrity": "sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8200,9 +7992,9 @@ "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.0", - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.30.0", + "@eslint/js": "9.31.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -8435,14 +8227,13 @@ } }, "node_modules/eslint-plugin-n": { - "version": "17.20.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.20.0.tgz", - "integrity": "sha512-IRSoatgB/NQJZG5EeTbv/iAx1byOGdbbyhQrNvWdCfTnmPxUT0ao9/eGOeG7ljD8wJBsxwE8f6tES5Db0FRKEw==", + "version": "17.21.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.21.0.tgz", + "integrity": "sha512-1+iZ8We4ZlwVMtb/DcHG3y5/bZOdazIpa/4TySo22MLKdwrLcfrX0hbadnCvykSQCCmkAnWmIP8jZVb2AAq29A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.5.0", - "@typescript-eslint/utils": "^8.26.1", "enhanced-resolve": "^5.17.1", "eslint-plugin-es-x": "^7.8.0", "get-tsconfig": "^4.8.1", @@ -8639,6 +8430,19 @@ "node": ">=10" } }, + "node_modules/eslint/node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -9797,9 +9601,9 @@ } }, "node_modules/globals": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz", - "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==", + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", "dev": true, "license": "MIT", "engines": { @@ -16212,9 +16016,9 @@ } }, "node_modules/query-string": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.2.1.tgz", - "integrity": "sha512-3jTGGLRzlhu/1ws2zlr4Q+GVMLCQTLFOj8CMX5x44cdZG9FQE07x2mQhaNxaKVPNmIDu0mvJ/cEwtY7Pim7hqA==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.2.2.tgz", + "integrity": "sha512-pDSIZJ9sFuOp6VnD+5IkakSVf+rICAuuU88Hcsr6AKL0QtxSIfVuKiVP2oahFI7tk3CRSexwV+Ya6MOoTxzg9g==", "license": "MIT", "dependencies": { "decode-uri-component": "^0.4.1", @@ -19572,19 +19376,6 @@ "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", "dev": true }, - "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, "node_modules/ts-declaration-location": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", diff --git a/blocks/eda-frontend/package.json b/blocks/eda-frontend/package.json index 680e9ebb..15755c5b 100644 --- a/blocks/eda-frontend/package.json +++ b/blocks/eda-frontend/package.json @@ -15,7 +15,7 @@ "highcharts-react-official": "^3.2.2", "mxgraph": "^4.2.2", "prop-types": "^15.8.1", - "query-string": "^9.2.1", + "query-string": "^9.2.2", "react": "^17.0.2", "react-dom": "^17.0.2", "react-loader-spinner": "^6.1.6", @@ -47,14 +47,14 @@ ] }, "devDependencies": { - "@babel/eslint-parser": "^7.27.5", - "eslint": "^9.30.0", + "@babel/eslint-parser": "^7.28.0", + "eslint": "^9.31.0", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-n": "^17.20.0", + "eslint-plugin-n": "^17.21.0", "eslint-plugin-promise": "^7.2.1", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", - "globals": "^16.2.0", + "globals": "^16.3.0", "react-dev-utils": "^12.0.1", "react-scripts": "^5.0.1", "source-map-explorer": "^2.5.2" diff --git a/blocks/eda-frontend/public/css/common.css b/blocks/eda-frontend/public/css/common.css new file mode 100644 index 00000000..72dbc130 --- /dev/null +++ b/blocks/eda-frontend/public/css/common.css @@ -0,0 +1,192 @@ +div.mxRubberband { + position: absolute; + overflow: hidden; + border-style: solid; + border-width: 1px; + border-color: #0000FF; + background: #0077FF; +} + +.mxCellEditor { + background: url(data:image/gif;base64,R0lGODlhMAAwAIAAAP///wAAACH5BAEAAAAALAAAAAAwADAAAAIxhI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8egpAAA7); + _background: url('../images/transparent.gif'); + border-color: transparent; + border-style: solid; + display: inline-block; + position: absolute; + overflow: visible; + word-wrap: normal; + border-width: 0; + min-width: 1px; + resize: none; + padding: 0px; + margin: 0px; +} + +.mxPlainTextEditor * { + padding: 0px; + margin: 0px; +} + +div.mxWindow { + -webkit-box-shadow: 3px 3px 12px #C0C0C0; + -moz-box-shadow: 3px 3px 12px #C0C0C0; + box-shadow: 3px 3px 12px #C0C0C0; + background: url(data:image/gif;base64,R0lGODlhGgAUAIAAAOzs7PDw8CH5BAAAAAAALAAAAAAaABQAAAIijI+py70Ao5y02lud3lzhD4ZUR5aPiKajyZbqq7YyB9dhAQA7); + _background: url('../images/window.gif'); + border: 1px solid #c3c3c3; + position: absolute; + overflow: hidden; + z-index: 1; +} + +table.mxWindow { + border-collapse: collapse; + table-layout: fixed; + font-family: Arial; + font-size: 8pt; +} + +td.mxWindowTitle { + background: url(data:image/gif;base64,R0lGODlhFwAXAMQAANfX18rKyuHh4c7OzsDAwMHBwc/Pz+Li4uTk5NHR0dvb2+jo6O/v79/f3/n5+dnZ2dbW1uPj44yMjNPT0+Dg4N3d3ebm5szMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAAAAAALAAAAAAXABcAAAWQICESxWiW5Ck6bOu+MMvMdG3f86LvfO/rlqBwSCwaj8ikUohoOp/QaDNCrVqvWKpgezhsv+AwmEIum89ocmPNbrvf64p8Tq/b5Yq8fs/v5x+AgYKDhIAAh4iJiouHEI6PkJGSjhOVlpeYmZUJnJ2en6CcBqMDpaanqKgXq6ytrq+rAbKztLW2shK5uru8vbkhADs=) repeat-x; + _background: url('../images/window-title.gif') repeat-x; + text-overflow: ellipsis; + white-space: nowrap; + text-align: center; + font-weight: bold; + overflow: hidden; + height: 13px; + padding: 2px; + padding-top: 4px; + padding-bottom: 6px; + color: black; +} + +td.mxWindowPane { + vertical-align: top; + padding: 0px; +} + +div.mxWindowPane { + overflow: hidden; + position: relative; +} + +td.mxWindowPane td { + font-family: Arial; + font-size: 8pt; +} + +td.mxWindowPane input, +td.mxWindowPane select, +td.mxWindowPane textarea, +td.mxWindowPane radio { + border-color: #8C8C8C; + border-style: solid; + border-width: 1px; + font-family: Arial; + font-size: 8pt; + padding: 1px; +} + +td.mxWindowPane button { + background: url(data:image/gif;base64,R0lGODlhCgATALMAAP7+/t7e3vj4+Ojo6OXl5e/v7/n5+fb29vPz8/39/e3t7fHx8e7u7v///wAAAAAAACH5BAAAAAAALAAAAAAKABMAAAQ2MMlJhb0Y6c2X/2AhjiRjnqiirizqMkEsz0Rt30Ou7y8K/ouDcEg0GI9IgHLJbDif0Kh06owAADs=) repeat-x; + _background: url('../images/button.gif') repeat-x; + font-family: Arial; + font-size: 8pt; + padding: 2px; + float: left; +} + +img.mxToolbarItem { + margin-right: 6px; + margin-bottom: 6px; + border-width: 1px; +} + +select.mxToolbarCombo { + vertical-align: top; + border-style: inset; + border-width: 2px; +} + +div.mxToolbarComboContainer { + padding: 2px; +} + +img.mxToolbarMode { + margin: 2px; + margin-right: 4px; + margin-bottom: 4px; + border-width: 0px; +} + +img.mxToolbarModeSelected { + margin: 0px; + margin-right: 2px; + margin-bottom: 2px; + border-width: 2px; + border-style: inset; +} + +div.mxTooltip { + -webkit-box-shadow: 3px 3px 12px #C0C0C0; + -moz-box-shadow: 3px 3px 12px #C0C0C0; + box-shadow: 3px 3px 12px #C0C0C0; + background: #FFFFCC; + border-style: solid; + border-width: 1px; + border-color: black; + font-family: Arial; + font-size: 8pt; + position: absolute; + cursor: default; + padding: 4px; + color: black; +} + +div.mxPopupMenu { + -webkit-box-shadow: 3px 3px 12px #C0C0C0; + -moz-box-shadow: 3px 3px 12px #C0C0C0; + box-shadow: 3px 3px 12px #C0C0C0; + background: url(data:image/gif;base64,R0lGODlhGgAUAIAAAOzs7PDw8CH5BAAAAAAALAAAAAAaABQAAAIijI+py70Ao5y02lud3lzhD4ZUR5aPiKajyZbqq7YyB9dhAQA7); + _background: url('../images/window.gif'); + position: absolute; + border-style: solid; + border-width: 1px; + border-color: black; +} + +table.mxPopupMenu { + border-collapse: collapse; + margin-top: 1px; + margin-bottom: 1px; +} + +tr.mxPopupMenuItem { + color: black; + cursor: pointer; +} + +tr.mxPopupMenuItemHover { + background-color: #000066; + color: #FFFFFF; + cursor: pointer; +} + +td.mxPopupMenuItem { + padding: 2px 30px 2px 10px; + white-space: nowrap; + font-family: Arial; + font-size: 8pt; +} + +td.mxPopupMenuIcon { + background-color: #D0D0D0; + padding: 2px 4px 2px 4px; +} + +.mxDisabled { + opacity: 0.2 !important; + cursor: default !important; +}
\ No newline at end of file diff --git a/blocks/eda-frontend/src/App.js b/blocks/eda-frontend/src/App.js index d28c1296..1ceed868 100644 --- a/blocks/eda-frontend/src/App.js +++ b/blocks/eda-frontend/src/App.js @@ -15,17 +15,13 @@ import NotFound from './pages/NotFound' import SchematicEditor from './pages/SchematicEditor' import SignUp from './pages/signUp' import { loadUser } from './redux/authSlice' +import api from './utils/Api' // Controls Private routes, this are accessible for authenticated users. [ e.g : dashboard ] // and restricted routes disabled for authenticated users. [ e.g : login , signup ] const PrivateRoute = ({ component: Component, ...rest }) => { const isAuthenticated = useSelector(state => state.auth.isAuthenticated) const isLoading = useSelector(state => state.auth.isLoading) - const dispatch = useDispatch() - - useEffect(() => { - dispatch(loadUser()) - }, [dispatch]) return ( <Route @@ -50,11 +46,6 @@ PrivateRoute.propTypes = { const PublicRoute = ({ component: Component, restricted, nav, ...rest }) => { const isAuthenticated = useSelector(state => state.auth.isAuthenticated) const isLoading = useSelector(state => state.auth.isLoading) - const dispatch = useDispatch() - - useEffect(() => { - dispatch(loadUser()) - }, [dispatch]) return ( <Route @@ -80,6 +71,24 @@ PublicRoute.propTypes = { } const App = () => { + const dispatch = useDispatch() + + useEffect(() => { + const initializeCsrf = async () => { + try { + await api.get('init') + } catch (err) { + console.error('Failed to initialize csrf:', err) + } + } + + initializeCsrf() + }, []) + + useEffect(() => { + dispatch(loadUser()) + }, [dispatch]) + return ( // Handles Routing for an application <HashRouter> diff --git a/blocks/eda-frontend/src/components/Dashboard/DashboardSidebar.js b/blocks/eda-frontend/src/components/Dashboard/DashboardSidebar.js index f05203e2..01450f10 100644 --- a/blocks/eda-frontend/src/components/Dashboard/DashboardSidebar.js +++ b/blocks/eda-frontend/src/components/Dashboard/DashboardSidebar.js @@ -1,5 +1,4 @@ -import { useEffect } from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { useSelector } from 'react-redux' import { Link as RouterLink } from 'react-router-dom' import { @@ -16,7 +15,6 @@ import { import { deepPurple } from '@material-ui/core/colors' import { makeStyles } from '@material-ui/core/styles' -import { fetchSchematics } from '../../redux/dashboardSlice' import { getUppercaseInitial } from '../../utils/GalleryUtils' const useStyles = makeStyles((theme) => ({ @@ -54,13 +52,6 @@ export default function DashSidebar (_props) { const user = useSelector(state => state.auth.user) const schematics = useSelector(state => state.dashboard.schematics) - const dispatch = useDispatch() - - // For Fetching Saved Schematics - useEffect(() => { - dispatch(fetchSchematics()) - }, [dispatch]) - const button = 'My ' + process.env.REACT_APP_DIAGRAMS_NAME const placeholder = 'Find your ' + process.env.REACT_APP_SMALL_DIAGRAM_NAME + '...' return ( diff --git a/blocks/eda-frontend/src/components/Dashboard/ProgressPanel.js b/blocks/eda-frontend/src/components/Dashboard/ProgressPanel.js index 908b4054..548727c1 100644 --- a/blocks/eda-frontend/src/components/Dashboard/ProgressPanel.js +++ b/blocks/eda-frontend/src/components/Dashboard/ProgressPanel.js @@ -54,19 +54,21 @@ function a11yProps (index) { export default function ProgressPanel () { const classes = useStyles() const [value, setValue] = useState(0) + const isLoggingOut = useSelector(state => state.auth.isLoggingOut) + const schematics = useSelector(state => state.dashboard.schematics) + + const dispatch = useDispatch() const handleChange = (event, newValue) => { setValue(newValue) } - const schematics = useSelector(state => state.dashboard.schematics) - - const dispatch = useDispatch() - // For Fetching Saved Schematics useEffect(() => { + if (isLoggingOut) return + dispatch(fetchSchematics()) - }, [dispatch]) + }, [dispatch, isLoggingOut]) const tab = 'Recent ' + process.env.REACT_APP_DIAGRAMS_NAME const typography = 'You have not created any ' + process.env.REACT_APP_SMALL_DIAGRAM_NAME diff --git a/blocks/eda-frontend/src/components/Dashboard/SchematicsList.js b/blocks/eda-frontend/src/components/Dashboard/SchematicsList.js index 91c19dc9..55c4504b 100644 --- a/blocks/eda-frontend/src/components/Dashboard/SchematicsList.js +++ b/blocks/eda-frontend/src/components/Dashboard/SchematicsList.js @@ -1,12 +1,9 @@ -import { useEffect } from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { useSelector } from 'react-redux' import { Link as RouterLink } from 'react-router-dom' import { Button, Card, CardActions, CardContent, Grid, Typography } from '@material-ui/core' import { makeStyles } from '@material-ui/core/styles' -import { fetchSchematics } from '../../redux/dashboardSlice' - import SchematicCard from './SchematicCard' const useStyles = makeStyles({ @@ -60,13 +57,6 @@ export default function SchematicsList () { const user = useSelector(state => state.auth.user) const schematics = useSelector(state => state.dashboard.schematics) - const dispatch = useDispatch() - - // For Fetching Saved Schematics - useEffect(() => { - dispatch(fetchSchematics()) - }, [dispatch]) - const typography1 = 'You don\'t have any saved ' + process.env.REACT_APP_SMALL_DIAGRAMS_NAME + '...' return ( <> diff --git a/blocks/eda-frontend/src/components/SchematicEditor/Helper/ToolbarTools.js b/blocks/eda-frontend/src/components/SchematicEditor/Helper/ToolbarTools.js index 73e3a1ac..5db83d44 100644 --- a/blocks/eda-frontend/src/components/SchematicEditor/Helper/ToolbarTools.js +++ b/blocks/eda-frontend/src/components/SchematicEditor/Helper/ToolbarTools.js @@ -5,7 +5,7 @@ import { useSelector } from 'react-redux' import mxGraphFactory from 'mxgraph' -import { styleToObject } from '../../../utils/GalleryUtils' +import { styleToObject, objectToStyle } from '../../../utils/GalleryUtils' import { getPortType, InputPort, OutputPort } from './ComponentDrag' import { portSize, getParameter } from './SvgParser' @@ -111,6 +111,33 @@ export function Rotate () { } } +// vertically +export function Flip () { + const cell = graph.getSelectionCell() + if (cell && cell.CellType === 'Component') { + const model = graph.getModel() + const currentStyle = model.getStyle(cell) || '' + const styleMap = styleToObject(currentStyle) + + styleMap.flip = styleMap.flip === 'true' ? 'false' : 'true' + model.setStyle(cell, objectToStyle(styleMap)) + } +} + +// horizontally +export function Mirror () { + const cell = graph.getSelectionCell() + if (cell && cell.CellType === 'Component') { + const model = graph.getModel() + const currentStyle = model.getStyle(cell) || '' + const styleMap = styleToObject(currentStyle) + + styleMap.mirror = styleMap.mirror === 'true' ? 'false' : 'true' + + model.setStyle(cell, objectToStyle(styleMap)) + } +} + // PRINT PREVIEW OF SCHEMATIC export function PrintPreview () { const title = useSelector(state => state.saveSchematic.title) diff --git a/blocks/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js b/blocks/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js index b992db10..b91b43bd 100644 --- a/blocks/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js +++ b/blocks/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js @@ -24,6 +24,7 @@ import { makeStyles } from '@material-ui/core/styles' import AddBoxOutlinedIcon from '@material-ui/icons/AddBoxOutlined' import ClearAllIcon from '@material-ui/icons/ClearAll' import CloseIcon from '@material-ui/icons/Close' +import CompareArrowsIcon from '@material-ui/icons/CompareArrows' import CreateNewFolderOutlinedIcon from '@material-ui/icons/CreateNewFolderOutlined' import DeleteIcon from '@material-ui/icons/Delete' import DescriptionIcon from '@material-ui/icons/Description' @@ -64,6 +65,8 @@ import { editorZoomIn, editorZoomOut, renderGalleryXML, + Flip, + Mirror, saveXml } from './Helper/ToolbarTools' import { @@ -512,7 +515,10 @@ export default function SchematicToolbar ({ _mobileClose, gridRef }) { 'pipe', { icon: <UndoIcon fontSize='small' />, label: 'Undo', action: editorUndo }, { icon: <RedoIcon fontSize='small' />, label: 'Redo', action: editorRedo }, + 'pipe', { icon: <RotateRightIcon fontSize='small' />, label: 'Rotate', action: Rotate }, + { icon: <CompareArrowsIcon style={{ transform: 'rotate(90deg)' }} fontSize='small' />, label: 'Flip', action: Flip }, + { icon: <CompareArrowsIcon fontSize='small' />, label: 'Mirror', action: Mirror }, 'pipe', { icon: <ZoomInIcon fontSize='small' />, label: 'Zoom In', action: editorZoomIn }, { icon: <ZoomOutIcon fontSize='small' />, label: 'Zoom Out', action: editorZoomOut }, diff --git a/blocks/eda-frontend/src/components/SchematicEditor/ToolbarExtension.js b/blocks/eda-frontend/src/components/SchematicEditor/ToolbarExtension.js index 0c6cc51c..58d758b8 100644 --- a/blocks/eda-frontend/src/components/SchematicEditor/ToolbarExtension.js +++ b/blocks/eda-frontend/src/components/SchematicEditor/ToolbarExtension.js @@ -693,6 +693,37 @@ ImageExportDialog.propTypes = { open: PropTypes.bool.isRequired } +function SchematicRow ({ sch, onClick, details, showDates = false }) { + return ( + <TableRow key={sch.save_id}> + <TableCell align='center'>{sch.name}</TableCell> + <TableCell align='center'> + <Tooltip title={sch.description !== null ? sch.description : 'No description'}> + <span> + {sch.description !== null ? sch.description.slice(0, 30) + (sch.description.length < 30 ? '' : '...') : '-'} + </span> + </Tooltip> + </TableCell> + {showDates && ( + <> + <TableCell align='center'>{getDate(sch.create_time)}</TableCell> + <TableCell align='center'>{getDate(sch.save_time)}</TableCell> + </> + )} + <TableCell align='center'> + <Button + size='small' + color='primary' + onClick={() => onClick(sch.save_id)} + variant={details.save_id === undefined ? 'outlined' : details.save_id !== sch.save_id ? 'outlined' : 'contained'} + > + Launch + </Button> + </TableCell> + </TableRow> + ) +} + // Dialog box to open saved Schematics export function OpenSchDialog (props) { const { open, close, openLocal } = props @@ -753,32 +784,15 @@ export function OpenSchDialog (props) { </TableHead> <TableBody> <> - {GallerySchSample.map( - (sch) => { - return ( - <TableRow key={sch.save_id}> - <TableCell align='center'>{sch.name}</TableCell> - <TableCell align='center'> - <Tooltip title={sch.description !== null ? sch.description : 'No description'}> - <span> - {sch.description !== null ? sch.description.slice(0, 30) + (sch.description.length < 30 ? '' : '...') : '-'} - </span> - </Tooltip> - </TableCell> - <TableCell align='center'> - <Button - size='small' - color='primary' - onClick={() => { dispatch(fetchDiagram(sch.save_id)) }} - variant={details.save_id === undefined ? 'outlined' : details.save_id !== sch.save_id ? 'outlined' : 'contained'} - > - Launch - </Button> - </TableCell> - </TableRow> - ) - } - )} + {GallerySchSample.map((sch) => ( + <SchematicRow + key={sch.save_id} + sch={sch} + onClick={(id) => dispatch(fetchDiagram(id))} + details={details} + showDates={false} + /> + ))} </> </TableBody> </Table> @@ -803,34 +817,15 @@ export function OpenSchDialog (props) { </TableHead> <TableBody> <> - {schematics.map( - (sch) => { - return ( - <TableRow key={sch.save_id}> - <TableCell align='center'>{sch.name}</TableCell> - <TableCell align='center'> - <Tooltip title={sch.description !== null ? sch.description : 'No description'}> - <span> - {sch.description !== null ? sch.description.slice(0, 30) + (sch.description.length < 30 ? '' : '...') : '-'} - </span> - </Tooltip> - </TableCell> - <TableCell align='center'>{getDate(sch.create_time)}</TableCell> - <TableCell align='center'>{getDate(sch.save_time)}</TableCell> - <TableCell align='center'> - <Button - size='small' - color='primary' - onClick={() => { dispatch(fetchSchematic(sch.save_id)) }} - variant={details.save_id === undefined ? 'outlined' : details.save_id !== sch.save_id ? 'outlined' : 'contained'} - > - Launch - </Button> - </TableCell> - </TableRow> - ) - } - )} + {schematics.map((sch) => ( + <SchematicRow + key={sch.save_id} + sch={sch} + onClick={(id) => dispatch(fetchSchematic(id))} + details={details} + showDates={true} + /> + ))} </> </TableBody> </Table> diff --git a/blocks/eda-frontend/src/pages/Login.js b/blocks/eda-frontend/src/pages/Login.js index e7ca5eac..f1aefedd 100644 --- a/blocks/eda-frontend/src/pages/Login.js +++ b/blocks/eda-frontend/src/pages/Login.js @@ -73,14 +73,21 @@ export default function SignIn (props) { } }, [dispatch, props.location.search]) - const [username, setUsername] = useState('') + const rememberedUsername = localStorage.getItem('rememberedUsername') || '' + const [username, setUsername] = useState(rememberedUsername) const [password, setPassword] = useState('') const [showPassword, setShowPassword] = useState(false) + const [rememberMe, setRememberMe] = useState(!!rememberedUsername) const handleClickShowPassword = () => setShowPassword(!showPassword) const handleMouseDownPassword = () => setShowPassword(!showPassword) // Function call for normal user login. const handleLogin = () => { + if (rememberMe) { + localStorage.setItem('rememberedUsername', username) + } else { + localStorage.removeItem('rememberedUsername') + } dispatch(login({ email: username, password, toUrl: url })) } @@ -154,7 +161,13 @@ export default function SignIn (props) { autoComplete='current-password' /> <FormControlLabel - control={<Checkbox value='remember' color='primary' />} + control={ + <Checkbox + color='primary' + checked={rememberMe} + onChange={e => setRememberMe(e.target.checked)} + /> + } label='Remember me' /> <Button diff --git a/blocks/eda-frontend/src/redux/authSlice.js b/blocks/eda-frontend/src/redux/authSlice.js index ee5705c1..21087d52 100644 --- a/blocks/eda-frontend/src/redux/authSlice.js +++ b/blocks/eda-frontend/src/redux/authSlice.js @@ -7,6 +7,7 @@ const tokenKey = process.env.REACT_APP_NAME + '_token' const initialState = { token: localStorage.getItem(tokenKey), isAuthenticated: false, + isLoggingOut: false, isRegistered: false, isLoading: false, user: null, @@ -17,9 +18,9 @@ const initialState = { // Api call for maintaining user login state throughout the application export const loadUser = createAsyncThunk( 'auth/loadUser', - async (_, { getState, rejectWithValue }) => { + async (tokenFromLogin, { getState, rejectWithValue }) => { // Get token from localstorage - const token = getState().auth.token + const token = tokenFromLogin || getState().auth.token if (!token) return rejectWithValue('No token found') // add headers @@ -32,7 +33,7 @@ export const loadUser = createAsyncThunk( try { const res = await api.get('auth/users/me/', config) - if (res.status === 200) { + if ([200, 201, 204].includes(res.status)) { return res.data } return rejectWithValue(res.data || 'Failed to load user') @@ -69,17 +70,18 @@ export const login = createAsyncThunk( email, password }) - if (res.status === 200) { - localStorage.setItem(tokenKey, res.data.auth_token) + if ([200, 201, 204].includes(res.status)) { + const token = res.data.auth_token + localStorage.setItem(tokenKey, token) if (toUrl === '') { - dispatch(loadUser()) + dispatch(loadUser(token)) } else if (!allowedUrls.includes(toUrl)) { console.log('Not redirecting to', toUrl) - dispatch(loadUser()) + dispatch(loadUser(token)) } else { window.open(toUrl, '_self') } - return res.data.auth_token + return token } return loginError(res, rejectWithValue) @@ -117,7 +119,7 @@ export const signUp = createAsyncThunk( password, re_password: reenterPassword }) - if (res.status === 200) { + if ([200, 201, 204].includes(res.status)) { return 'Successfully Signed Up! A verification link has been sent to your email account.' } @@ -165,7 +167,7 @@ export const googleLogin = createAsyncThunk( async (host, { rejectWithValue }) => { try { const res = await api.get(`auth/o/google-oauth2/?redirect_uri=${host}/api/auth/google-callback`) - if (res.status === 200) { + if ([200, 201, 204].includes(res.status)) { // Open google login page window.open(res.data.authorization_url, '_self') return res.data.authorization_url @@ -185,7 +187,7 @@ export const githubLogin = createAsyncThunk( async (host, { rejectWithValue }) => { try { const res = await api.get(`auth/o/github/?redirect_uri=${host}/api/auth/github-callback`) - if (res.status === 200) { + if ([200, 201, 204].includes(res.status)) { // Open GitHub login page window.open(res.data.authorization_url, '_self') return res.data.authorization_url @@ -228,7 +230,6 @@ const authSlice = createSlice({ state.isAuthenticated = false }) .addCase(login.fulfilled, (state, action) => { - state.isLoading = false state.token = action.payload state.errors = '' }) @@ -243,9 +244,6 @@ const authSlice = createSlice({ state.isLoading = true state.isAuthenticated = false }) - .addCase(googleLogin.fulfilled, (state) => { - state.isLoading = false - }) .addCase(googleLogin.rejected, (state, action) => { state.isLoading = false state.token = null @@ -257,9 +255,6 @@ const authSlice = createSlice({ state.isLoading = true state.isAuthenticated = false }) - .addCase(githubLogin.fulfilled, (state) => { - state.isLoading = false - }) .addCase(githubLogin.rejected, (state, action) => { state.isLoading = false state.token = null @@ -281,8 +276,13 @@ const authSlice = createSlice({ state.isRegistered = false state.regErrors = action.payload }) + .addCase(logout.pending, (state) => { + state.isLoading = true + state.isLoggingOut = true + }) .addCase(logout.fulfilled, (state) => { state.isLoading = false + state.isLoggingOut = false state.token = null state.user = null state.isAuthenticated = false diff --git a/blocks/eda-frontend/src/redux/dashboardSlice.js b/blocks/eda-frontend/src/redux/dashboardSlice.js index 753b5287..2c32875a 100644 --- a/blocks/eda-frontend/src/redux/dashboardSlice.js +++ b/blocks/eda-frontend/src/redux/dashboardSlice.js @@ -2,6 +2,8 @@ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' import api from '../utils/Api' +import { logout } from './authSlice' + const initialState = { isLoading: false, schematics: [], @@ -106,6 +108,9 @@ const dashboardSlice = createSlice({ state.isLoading = false state.gallery = [] }) + .addCase(logout.fulfilled, (state) => { + state.schematics = [] + }) } }) diff --git a/blocks/eda-frontend/src/utils/GalleryUtils.js b/blocks/eda-frontend/src/utils/GalleryUtils.js index 7e6a6bf7..37322592 100644 --- a/blocks/eda-frontend/src/utils/GalleryUtils.js +++ b/blocks/eda-frontend/src/utils/GalleryUtils.js @@ -176,6 +176,17 @@ export const styleToObject = (style) => { return styleObject } +export const objectToStyle = (styleObject) => { + let style = styleObject.default + for (const [key, value] of Object.entries(styleObject)) { + if (key === 'default' || value == null || value === '') { + continue + } + style += `;${key}=${value}` + } + return style +} + export const saveToFile = (filename, filetype, data) => { const blob = new Blob([data], { type: filetype + ';charset=utf-8' }) saveAs(blob, filename) diff --git a/blocks/init.sh b/blocks/init.sh index ef98711c..16215a00 100755 --- a/blocks/init.sh +++ b/blocks/init.sh @@ -7,8 +7,8 @@ PASSWORD='' rm -f xcosblocks.sqlite3 -./manage.py migrate -v0 -./manage.py loaddata xcosblocks +python manage.py migrate -v0 +python manage.py loaddata -v0 saveAPI xcosblocks echo "from authAPI.models import User; User.objects.create_superuser('$EMAIL', '$EMAIL', '$PASSWORD')" | - ./manage.py shell + python manage.py shell diff --git a/blocks/simulationAPI/helpers/ngspice_helper.py b/blocks/simulationAPI/helpers/ngspice_helper.py index b41b13a9..1dcd1e00 100644 --- a/blocks/simulationAPI/helpers/ngspice_helper.py +++ b/blocks/simulationAPI/helpers/ngspice_helper.py @@ -1,6 +1,6 @@ import json import os -from os.path import abspath, join, splitext +from os.path import join, splitext import re import subprocess from celery import current_task @@ -17,30 +17,6 @@ from simulationAPI.helpers.scilab_manager import start_scilab, upload, remove, r logger = get_task_logger(__name__) XmlToXcos = join(settings.BASE_DIR, 'Xcos/XmlToXcos.sh') -SCILAB_DIR = abspath(settings.SCILAB_DIR) -SCILAB = join(SCILAB_DIR, 'bin', 'scilab-adv-cli') -# handle scilab startup -SCILAB_START = ( - "try;funcprot(0);lines(0,120);" - "clearfun('messagebox');" - "function messagebox(msg,title,icon,buttons,modal),disp(msg),endfunction;" - "funcprot(1);" - "catch;[error_message,error_number,error_line,error_func]=lasterror();" - "disp(error_message,error_number,error_line,error_func);exit(3);end;" -) - -SCILAB_END = ( - "catch;[error_message,error_number,error_line,error_func]=lasterror();" - "disp(error_message,error_number,error_line,error_func);exit(2);end;exit;" -) -SCILAB_CMD = [SCILAB, - "-noatomsautoload", - "-nogui", - "-nouserstartup", - "-nb", - "-nw", - "-e", SCILAB_START] -LOGFILEFD = 123 START_STATES = ["STARTED"] END_STATES = ["SUCCESS", "FAILURE", "CANCELED"] diff --git a/blocks/simulationAPI/helpers/scilab_manager.py b/blocks/simulationAPI/helpers/scilab_manager.py index ce420eee..528cda5d 100644 --- a/blocks/simulationAPI/helpers/scilab_manager.py +++ b/blocks/simulationAPI/helpers/scilab_manager.py @@ -86,8 +86,7 @@ def secure_filename(filename: str) -> str: def makedirs(dirname, dirtype=None): - if not exists(dirname): - os.makedirs(dirname) + os.makedirs(dirname, exist_ok=True) def rmdir(dirname, dirtype=None): diff --git a/blocks/xcos2xml/blocks/ConstantVoltage.xsl b/blocks/xcos2xml/blocks/ConstantVoltage.xsl index 727bd0f0..34622121 100644 --- a/blocks/xcos2xml/blocks/ConstantVoltage.xsl +++ b/blocks/xcos2xml/blocks/ConstantVoltage.xsl @@ -44,10 +44,8 @@ <xsl:apply-templates select="mxGeometry" /> <Object> <xsl:attribute name="display_parameter"> - <!-- <xsl:value-of select="format-number(0.01 * 1000, '0')" /> - <xsl:text> m</xsl:text> --> <xsl:call-template name="si-format"> - <xsl:with-param name="num" select="number(*[@as='exprs']/data[1]/@value)" /> + <xsl:with-param name="expr" select="*[@as='exprs']/data[1]/@value" /> </xsl:call-template> </xsl:attribute> <xsl:attribute name="as">displayProperties</xsl:attribute> diff --git a/blocks/xcos2xml/blocks/Resistor.xsl b/blocks/xcos2xml/blocks/Resistor.xsl index 925ce561..cda89f2d 100644 --- a/blocks/xcos2xml/blocks/Resistor.xsl +++ b/blocks/xcos2xml/blocks/Resistor.xsl @@ -44,9 +44,8 @@ <xsl:apply-templates select="mxGeometry" /> <Object> <xsl:attribute name="display_parameter"> - <!-- <xsl:value-of select="si-format(number(*[@as='exprs']/data[1]/@value), '0')" /> --> <xsl:call-template name="si-format"> - <xsl:with-param name="num" select="number(*[@as='exprs']/data[1]/@value)" /> + <xsl:with-param name="expr" select="*[@as='exprs']/data[1]/@value" /> </xsl:call-template> </xsl:attribute> <xsl:attribute name="as">displayProperties</xsl:attribute> diff --git a/blocks/xcos2xml/blocks/SUPER_f.xsl b/blocks/xcos2xml/blocks/SUPER_f.xsl index 9bd1a5cc..cca736af 100644 --- a/blocks/xcos2xml/blocks/SUPER_f.xsl +++ b/blocks/xcos2xml/blocks/SUPER_f.xsl @@ -1,8 +1,8 @@ <xsl:template match="SuperBlock"> - <xsl:variable name="explicitInputPorts" select="count(SuperBlockDiagram/mxGraphModel/root/ExplicitOutBlock)" /> - <xsl:variable name="implicitInputPorts" select="count(SuperBlockDiagram/mxGraphModel/root/ImplicitOutBlock)" /> - <xsl:variable name="explicitOutputPorts" select="count(SuperBlockDiagram/mxGraphModel/root/ExplicitInBlock)" /> - <xsl:variable name="implicitOutputPorts" select="count(SuperBlockDiagram/mxGraphModel/root/ImplicitInBlock)" /> + <xsl:variable name="explicitInputPorts" select="count(SuperBlockDiagram/mxGraphModel/root/ExplicitInBlock)" /> + <xsl:variable name="implicitInputPorts" select="count(SuperBlockDiagram/mxGraphModel/root/ImplicitInBlock)" /> + <xsl:variable name="explicitOutputPorts" select="count(SuperBlockDiagram/mxGraphModel/root/ExplicitOutBlock)" /> + <xsl:variable name="implicitOutputPorts" select="count(SuperBlockDiagram/mxGraphModel/root/ImplicitOutBlock)" /> <xsl:variable name="controlPorts" select="count(SuperBlockDiagram/mxGraphModel/root/EventInBlock)" /> <xsl:variable name="commandPorts" select="count(SuperBlockDiagram/mxGraphModel/root/EventOutBlock)" /> <xsl:element name="mxCell"> diff --git a/blocks/xcos2xml/blocks/TEXT_f.xsl b/blocks/xcos2xml/blocks/TEXT_f.xsl index 72e5ca91..9c0ee871 100644 --- a/blocks/xcos2xml/blocks/TEXT_f.xsl +++ b/blocks/xcos2xml/blocks/TEXT_f.xsl @@ -7,7 +7,12 @@ <xsl:variable name="commandPorts">0</xsl:variable> <xsl:element name="mxCell"> <xsl:attribute name="style"> - <xsl:value-of select="@style" /> + <xsl:choose> + <xsl:when test="@style != ''"> + <xsl:value-of select="@style" /> + </xsl:when> + <xsl:otherwise>TEXT_f</xsl:otherwise> + </xsl:choose> </xsl:attribute> <xsl:attribute name="id"> <xsl:value-of select="@id" /> diff --git a/blocks/xcos2xml/head.xsl b/blocks/xcos2xml/head.xsl index 22fb992c..5e58e0c9 100644 --- a/blocks/xcos2xml/head.xsl +++ b/blocks/xcos2xml/head.xsl @@ -20,85 +20,70 @@ </xsl:choose> </xsl:template> - <xsl:template name="si-format"> - <xsl:param name="num" /> + <xsl:template name="eval-rational"> + <xsl:param name="expr"/> - <!-- Compute absolute value manually --> - <xsl:variable name="absNum"> - <xsl:choose> - <xsl:when test="$num < 0"><xsl:value-of select="-$num" /></xsl:when> - <xsl:otherwise><xsl:value-of select="$num" /></xsl:otherwise> - </xsl:choose> - </xsl:variable> + <xsl:choose> + <xsl:when test="contains($expr, '/')"> + <xsl:variable name="num" select="number(substring-before($expr, '/'))" /> + <xsl:variable name="den" select="number(substring-after($expr, '/'))" /> + <xsl:value-of select="$num div $den" /> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="number($expr)" /> + </xsl:otherwise> + </xsl:choose> + </xsl:template> - <!-- Compute exponent --> - <xsl:variable name="exponent"> - <xsl:choose> - <xsl:when test="$absNum = 0">0</xsl:when> - <xsl:otherwise> - <xsl:call-template name="compute-exp"> - <xsl:with-param name="n" select="$absNum" /> - <xsl:with-param name="exp" select="0" /> - </xsl:call-template> - </xsl:otherwise> - </xsl:choose> - </xsl:variable> + <xsl:template name="si-format"> + <xsl:param name="expr" /> - <!-- Define SI prefixes --> - <xsl:choose> - <xsl:when test="$exponent >= -2 and $exponent <= 0"> - <xsl:value-of select="round($num div 1E-3)" /> m - </xsl:when> - <xsl:when test="$exponent >= -5 and $exponent <= -3"> - <xsl:value-of select="round($num div 1E-6)" /> μ <!-- Unicode for μ --> - </xsl:when> - <xsl:when test="$exponent >= -8 and $exponent <= -6"> - <xsl:value-of select="round($num div 1E-9)" /> n - </xsl:when> - <xsl:when test="$exponent >= -11 and $exponent <= -9"> - <xsl:value-of select="round($num div 1E-12)" /> p - </xsl:when> - <xsl:when test="$exponent >= 1 and $exponent <= 3"> - <xsl:value-of select="round($num div 1)" /> - </xsl:when> - <xsl:when test="$exponent >= 4 and $exponent <= 6"> - <xsl:value-of select="round($num div 1E3)" /> k</xsl:when> - <xsl:when test="$exponent >= 7 and $exponent <= 9"> - <xsl:value-of select="round($num div 1E6)" /> M - </xsl:when> - <xsl:when test="$exponent >= 10 and $exponent <= 12"> - <xsl:value-of select="round($num div 1E9)" /> G - </xsl:when> - <xsl:when test="$exponent >= 13 and $exponent <= 15"> - <xsl:value-of select="round($num div 1E12)" /> T - </xsl:when> - <xsl:otherwise> - <xsl:value-of select="$num" /> 10^<xsl:value-of select="$exponent " /> - </xsl:otherwise> - </xsl:choose> - </xsl:template> - <!-- Recursive template to compute the exponent --> - <xsl:template name="compute-exp"> - <xsl:param name="n" /> - <xsl:param name="exp" /> + <xsl:variable name="num"> + <xsl:call-template name="eval-rational"> + <xsl:with-param name="expr" select="$expr" /> + </xsl:call-template> + </xsl:variable> - <xsl:choose> - <xsl:when test="$n < 1"> - <xsl:call-template name="compute-exp"> - <xsl:with-param name="n" select="$n * 10" /> - <xsl:with-param name="exp" select="$exp - 1" /> - </xsl:call-template> - </xsl:when> - <xsl:when test="$n >= 10"> - <xsl:call-template name="compute-exp"> - <xsl:with-param name="n" select="$n div 10" /> - <xsl:with-param name="exp" select="$exp + 1" /> - </xsl:call-template> - </xsl:when> - <xsl:otherwise> - <xsl:value-of select="$exp" /> - </xsl:otherwise> - </xsl:choose> + <xsl:choose> + <xsl:when test="number($num) >= 1000000000000"> + <xsl:value-of select="format-number($num div 1000000000000, '#.##')" /> + <xsl:text> T</xsl:text> + </xsl:when> + <xsl:when test="number($num) >= 1000000000"> + <xsl:value-of select="format-number($num div 1000000000, '#.##')" /> + <xsl:text> G</xsl:text> + </xsl:when> + <xsl:when test="number($num) >= 1000000"> + <xsl:value-of select="format-number($num div 1000000, '#.##')" /> + <xsl:text> M</xsl:text> + </xsl:when> + <xsl:when test="number($num) >= 1000"> + <xsl:value-of select="format-number($num div 1000, '#.##')" /> + <xsl:text> k</xsl:text> + </xsl:when> + <xsl:when test="number($num) >= 1"> + <xsl:value-of select="format-number($num, '#.##')" /> + </xsl:when> + <xsl:when test="number($num) >= 0.001"> + <xsl:value-of select="format-number($num div 0.001, '#.##')" /> + <xsl:text> m</xsl:text> + </xsl:when> + <xsl:when test="number($num) >= 0.000001"> + <xsl:value-of select="format-number($num div 0.000001, '#.##')" /> + <xsl:text> μ</xsl:text> + </xsl:when> + <xsl:when test="number($num) >= 0.000000001"> + <xsl:value-of select="format-number($num div 0.000000001, '#.##')" /> + <xsl:text> n</xsl:text> + </xsl:when> + <xsl:when test="number($num) >= 0.000000000001"> + <xsl:value-of select="format-number($num div 0.000000000001, '#.##')" /> + <xsl:text> p</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="format-number($num, '#.##')" /> + </xsl:otherwise> + </xsl:choose> </xsl:template> <xsl:output method="xml" indent="no" /> @@ -279,59 +264,61 @@ </xsl:template> <xsl:template name="generate-block"> + <xsl:param name="style"/> + <xsl:param name="id"/> <xsl:param name="explicitInputPorts"/> <xsl:param name="implicitInputPorts"/> <xsl:param name="explicitOutputPorts"/> <xsl:param name="implicitOutputPorts"/> <xsl:param name="controlPorts"/> <xsl:param name="commandPorts"/> - <xsl:param name="blockId"/> - <xsl:param name="style"/> <xsl:param name="simulationFunction"/> <xsl:param name="display_parameter"/> <xsl:param name="data"/> <xsl:element name="mxCell"> - <xsl:attribute name="style"><xsl:value-of select="$style"/></xsl:attribute> - <xsl:attribute name="id"><xsl:value-of select="$blockId"/></xsl:attribute> - <xsl:attribute name="vertex">1</xsl:attribute> - <xsl:attribute name="connectable">0</xsl:attribute> - <xsl:attribute name="CellType">Component</xsl:attribute> - <xsl:attribute name="blockprefix">XCOS</xsl:attribute> - <xsl:attribute name="explicitInputPorts"><xsl:value-of select="$explicitInputPorts"/></xsl:attribute> - <xsl:attribute name="implicitInputPorts"><xsl:value-of select="$implicitInputPorts"/></xsl:attribute> - <xsl:attribute name="explicitOutputPorts"><xsl:value-of select="$explicitOutputPorts"/></xsl:attribute> - <xsl:attribute name="implicitOutputPorts"><xsl:value-of select="$implicitOutputPorts"/></xsl:attribute> - <xsl:attribute name="controlPorts"><xsl:value-of select="$controlPorts"/></xsl:attribute> - <xsl:attribute name="commandPorts"><xsl:value-of select="$commandPorts"/></xsl:attribute> - <xsl:attribute name="simulationFunction"><xsl:value-of select="$simulationFunction"/></xsl:attribute> - <xsl:attribute name="sourceVertex">0</xsl:attribute> - <xsl:attribute name="targetVertex">0</xsl:attribute> - <xsl:attribute name="tarx">0</xsl:attribute> - <xsl:attribute name="tary">0</xsl:attribute> + <xsl:attribute name="style"><xsl:value-of select="$style"/></xsl:attribute> + <xsl:attribute name="id"><xsl:value-of select="$id"/></xsl:attribute> + <xsl:attribute name="vertex">1</xsl:attribute> + <xsl:attribute name="connectable">0</xsl:attribute> + <xsl:attribute name="CellType">Component</xsl:attribute> + <xsl:attribute name="blockprefix">XCOS</xsl:attribute> + <xsl:attribute name="explicitInputPorts"><xsl:value-of select="$explicitInputPorts"/></xsl:attribute> + <xsl:attribute name="implicitInputPorts"><xsl:value-of select="$implicitInputPorts"/></xsl:attribute> + <xsl:attribute name="explicitOutputPorts"><xsl:value-of select="$explicitOutputPorts"/></xsl:attribute> + <xsl:attribute name="implicitOutputPorts"><xsl:value-of select="$implicitOutputPorts"/></xsl:attribute> + <xsl:attribute name="controlPorts"><xsl:value-of select="$controlPorts"/></xsl:attribute> + <xsl:attribute name="commandPorts"><xsl:value-of select="$commandPorts"/></xsl:attribute> + <xsl:attribute name="simulationFunction"><xsl:value-of select="$simulationFunction"/></xsl:attribute> + <xsl:attribute name="sourceVertex">0</xsl:attribute> + <xsl:attribute name="targetVertex">0</xsl:attribute> + <xsl:attribute name="tarx">0</xsl:attribute> + <xsl:attribute name="tary">0</xsl:attribute> - <xsl:apply-templates select="mxGeometry"/> - <Object as="displayProperties"> + <xsl:apply-templates select="mxGeometry"/> + + <Object as="displayProperties"> <xsl:attribute name="display_parameter"> - <xsl:value-of select="$display_parameter"/> + <xsl:value-of select="$display_parameter"/> </xsl:attribute> - </Object> - <Object as="parameter_values"> - <xsl:for-each select="$data"> - <xsl:attribute name="{concat('p', format-number(position() - 1, '000'), '_value')}"> - <xsl:value-of select="@value" /> - </xsl:attribute> - </xsl:for-each> - </Object> + </Object> + + <Object as="parameter_values"> + <xsl:for-each select="$data"> + <xsl:attribute name="{concat('p', format-number(position() - 1, '000'), '_value')}"> + <xsl:value-of select="@value" /> + </xsl:attribute> + </xsl:for-each> + </Object> </xsl:element> <xsl:call-template name="port"> - <xsl:with-param name="id" select="$blockId"/> - <xsl:with-param name="explicitInputPorts" select="$explicitInputPorts"/> - <xsl:with-param name="explicitOutputPorts" select="$explicitOutputPorts"/> - <xsl:with-param name="implicitInputPorts" select="$implicitInputPorts"/> - <xsl:with-param name="implicitOutputPorts" select="$implicitOutputPorts"/> - <xsl:with-param name="controlPorts" select="$controlPorts"/> - <xsl:with-param name="commandPorts" select="$commandPorts"/> + <xsl:with-param name="id" select="$id"/> + <xsl:with-param name="explicitInputPorts" select="$explicitInputPorts"/> + <xsl:with-param name="explicitOutputPorts" select="$explicitOutputPorts"/> + <xsl:with-param name="implicitInputPorts" select="$implicitInputPorts"/> + <xsl:with-param name="implicitOutputPorts" select="$implicitOutputPorts"/> + <xsl:with-param name="controlPorts" select="$controlPorts"/> + <xsl:with-param name="commandPorts" select="$commandPorts"/> </xsl:call-template> </xsl:template> |