├── .all-contributorsrc ├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .gitlab-ci.yml ├── .npmrc ├── .prettierignore ├── .stylelintrc ├── README.md ├── babel.config.js ├── backend.Dockerfile ├── create-os-roles.sh ├── create-os-token.sh ├── frontend.Dockerfile ├── lerna.json ├── package.json ├── packages ├── slides-frontend │ ├── .eslintrc.json │ ├── .gitignore │ ├── .npmrc │ ├── .prettierignore │ ├── .prettierrc.json │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon-background.ico │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── manifest.json │ │ └── robots.txt │ ├── src │ │ ├── authConfig.js │ │ ├── config.js │ │ ├── configureStore.js │ │ ├── containers │ │ │ ├── Alerts │ │ │ │ ├── AlertForSaving │ │ │ │ │ └── index.js │ │ │ │ ├── fail.js │ │ │ │ ├── success.js │ │ │ │ └── warning.js │ │ │ ├── App │ │ │ │ ├── index.js │ │ │ │ └── selectors.js │ │ │ ├── Editor │ │ │ │ ├── Canvas │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.js │ │ │ │ ├── Container │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.js │ │ │ │ ├── Export │ │ │ │ │ ├── MyExportedSlides.js │ │ │ │ │ ├── alerting.js │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.js │ │ │ │ ├── Homepage │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.js │ │ │ │ ├── LandingPage │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.js │ │ │ │ ├── MySlide │ │ │ │ │ └── index.js │ │ │ │ ├── Presentation │ │ │ │ │ └── index.js │ │ │ │ ├── Settings │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.js │ │ │ │ ├── SideBar │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.js │ │ │ │ ├── components │ │ │ │ │ ├── image │ │ │ │ │ │ ├── Upload │ │ │ │ │ │ │ ├── Dropzone.css │ │ │ │ │ │ │ ├── Dropzone.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ ├── index.css │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── resize_move │ │ │ │ │ │ ├── index.css │ │ │ │ │ │ └── index.js │ │ │ │ │ └── text │ │ │ │ │ │ ├── RenderHtml.css │ │ │ │ │ │ ├── RenderHtml.js │ │ │ │ │ │ ├── TextEditor.css │ │ │ │ │ │ ├── TextEditor.js │ │ │ │ │ │ ├── editorConfig.js │ │ │ │ │ │ ├── index.css │ │ │ │ │ │ └── index.js │ │ │ │ └── styles.js │ │ │ ├── LoadPresentation │ │ │ │ ├── index.css │ │ │ │ └── index.js │ │ │ ├── NotFoundPage │ │ │ │ └── index.js │ │ │ ├── SavePresentation │ │ │ │ └── index.js │ │ │ ├── StyleComponent │ │ │ │ └── index.js │ │ │ ├── ThemeSelector │ │ │ │ └── index.js │ │ │ └── redux-store │ │ │ │ ├── DeckReducer │ │ │ │ ├── actions.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── definitions.ts │ │ │ │ ├── reducer.ts │ │ │ │ └── selectors.ts │ │ │ │ └── PresentationReducer │ │ │ │ ├── actions.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── definitions.ts │ │ │ │ ├── reducer.ts │ │ │ │ └── selectors.ts │ │ ├── global-styles.js │ │ ├── images │ │ │ ├── Logo-Outline-web-White@200.png │ │ │ ├── PageNotFound.jpg │ │ │ └── logoITdep.png │ │ ├── index.js │ │ ├── react-app-env.d.ts │ │ ├── reducers.js │ │ ├── theming │ │ │ ├── StandardSlide.js │ │ │ ├── cern.css │ │ │ ├── cern2.css │ │ │ ├── cern3.css │ │ │ ├── cern4.css │ │ │ ├── cern5.css │ │ │ ├── cern6.css │ │ │ └── theme.js │ │ └── utils │ │ │ ├── helperFunctions.js │ │ │ ├── history.js │ │ │ └── requests.js │ └── tsconfig.json └── slides-server │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc.json │ ├── .travis.yml │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── Logo-Outline-web-White@200.png │ ├── Slides-S.png │ ├── Slides-S_background.png │ ├── intro.html │ └── themes │ │ ├── cern1.png │ │ ├── cern2.png │ │ ├── cern3.png │ │ ├── cern4.png │ │ ├── cern5.png │ │ └── cern6.png │ └── src │ ├── config │ └── index.js │ ├── index.js │ ├── routes │ ├── image.js │ ├── index.js │ ├── presentation.js │ ├── testBackend.js │ └── wopi.js │ └── utils │ ├── log.js │ └── sanitizeState.js ├── tsconfig.json └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "Slides", 3 | "projectOwner": "CERN", 4 | "repoType": "gitlab", 5 | "repoHost": "https://gitlab.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 80, 10 | "commit": true, 11 | "contributors": [ 12 | { 13 | "login": "achionis", 14 | "name": "Aristofanis Chionis", 15 | "avatar_url": "https://avatars.githubusercontent.com/aristofanischionis", 16 | "profile": "https://aristofanischionis.github.io/", 17 | "contributions": [ 18 | "code", 19 | "doc", 20 | "ideas", 21 | "review", 22 | "test" 23 | ] 24 | }, 25 | ], 26 | "contributorsPerLine": 0 27 | } 28 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .git 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | 3 | [*] 4 | indent_style=space 5 | indent_size=4 6 | end_of_line=lf 7 | charset=utf-8 8 | trim_trailing_whitespace=true 9 | insert_final_newline=true 10 | 11 | [{*.yml,*.yaml,*.json}] 12 | indent_size=2 13 | 14 | [{.babelrc,.travis.yml}] 15 | indent_size=2 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes 2 | 3 | # Handle line endings automatically for files detected as text 4 | # and leave all files detected as binary untouched. 5 | * text=auto 6 | 7 | # 8 | # The above will handle all files NOT found below 9 | # 10 | 11 | # 12 | ## These files are text and should be normalized (Convert crlf => lf) 13 | # 14 | 15 | # source code 16 | *.php text 17 | *.css text 18 | *.sass text 19 | *.scss text 20 | *.less text 21 | *.styl text 22 | *.js text eol=lf 23 | *.coffee text 24 | *.json text 25 | *.htm text 26 | *.html text 27 | *.xml text 28 | *.svg text 29 | *.txt text 30 | *.ini text 31 | *.inc text 32 | *.pl text 33 | *.rb text 34 | *.py text 35 | *.scm text 36 | *.sql text 37 | *.sh text 38 | *.bat text 39 | 40 | # templates 41 | *.ejs text 42 | *.hbt text 43 | *.jade text 44 | *.haml text 45 | *.hbs text 46 | *.dot text 47 | *.tmpl text 48 | *.phtml text 49 | 50 | # server config 51 | .htaccess text 52 | .nginx.conf text 53 | 54 | # git config 55 | .gitattributes text 56 | .gitignore text 57 | .gitconfig text 58 | 59 | # code analysis config 60 | .jshintrc text 61 | .jscsrc text 62 | .jshintignore text 63 | .csslintrc text 64 | 65 | # misc config 66 | *.yaml text 67 | *.yml text 68 | .editorconfig text 69 | 70 | # build config 71 | *.npmignore text 72 | *.bowerrc text 73 | 74 | # Heroku 75 | Procfile text 76 | .slugignore text 77 | 78 | # Documentation 79 | *.md text 80 | LICENSE text 81 | AUTHORS text 82 | 83 | 84 | # 85 | ## These files are binary and should be left untouched 86 | # 87 | 88 | # (binary is a macro for -text -diff) 89 | *.png binary 90 | *.jpg binary 91 | *.jpeg binary 92 | *.gif binary 93 | *.ico binary 94 | *.mov binary 95 | *.mp4 binary 96 | *.mp3 binary 97 | *.flv binary 98 | *.fla binary 99 | *.swf binary 100 | *.gz binary 101 | *.zip binary 102 | *.7z binary 103 | *.ttf binary 104 | *.eot binary 105 | *.woff binary 106 | *.pyc binary 107 | *.pdf binary 108 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't check auto-generated stuff into git 2 | coverage 3 | stats.json 4 | # Cruft 5 | .DS_Store 6 | npm-debug.log 7 | .idea/ 8 | .vscode/ 9 | node_modules/ 10 | temp/ 11 | *.log 12 | dump.rdb 13 | .env 14 | *.env 15 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | FRONTEND_NAMESPACE_DEV: slides 3 | BACKEND_NAMESPACE_DEV: slides-backend 4 | # NAMESPACE: slides 5 | OPENSHIFT_SERVER_DEV: https://openshift.cern.ch 6 | # OPENSHIFT_SERVER: https://openshift.cern.ch 7 | BUILD_ENV_DEV: staging 8 | APP_NAME: slides 9 | DEV_TAG: dev 10 | 11 | stages: 12 | - build_docker 13 | - deploy 14 | 15 | .docker_build_template: &docker_definition 16 | stage: build_docker 17 | image: 18 | name: gitlab-registry.cern.ch/ci-tools/docker-image-builder 19 | entrypoint: [""] 20 | script: 21 | - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json 22 | - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/$DOCKER_FILE --destination ${TO} 23 | 24 | .deploy_template: &deploy_definition 25 | stage: deploy 26 | image: gitlab-registry.cern.ch/paas-tools/openshift-client:latest 27 | script: 28 | - oc import-image ${APP_NAME} --from="${CI_REGISTRY_IMAGE}/${DEPLOYMENT_TYPE}:${TAG}" --confirm --token=${TOKEN} --server=${OPENSHIFT_SERVER} -n ${NAMESPACE} || true 29 | - oc tag "${CI_REGISTRY_IMAGE}/${DEPLOYMENT_TYPE}:${TAG}" "${APP_NAME}:latest" --token=${TOKEN} --server=${OPENSHIFT_SERVER} -n ${NAMESPACE} 30 | 31 | build_frontend_docker: 32 | <<: *docker_definition 33 | variables: 34 | TO: ${CI_REGISTRY_IMAGE}/frontend:${DEV_TAG} 35 | DOCKER_FILE: frontend.Dockerfile 36 | only: 37 | - master 38 | 39 | build_backend_docker: 40 | <<: *docker_definition 41 | variables: 42 | TO: ${CI_REGISTRY_IMAGE}/backend:${DEV_TAG} 43 | DOCKER_FILE: backend.Dockerfile 44 | only: 45 | - master 46 | 47 | deploy_frontend: 48 | <<: *deploy_definition 49 | variables: 50 | TOKEN: ${OPENSHIFT_DEV_TOKEN} 51 | OPENSHIFT_SERVER: ${OPENSHIFT_SERVER_DEV} 52 | NAMESPACE: ${FRONTEND_NAMESPACE_DEV} 53 | DEPLOYMENT_TYPE: frontend 54 | TAG: ${DEV_TAG} 55 | environment: 56 | name: staging 57 | url: https://slides.web.cern.ch 58 | only: 59 | - master 60 | 61 | deploy_backend: 62 | <<: *deploy_definition 63 | variables: 64 | # TOKEN: ${OPENSHIFT_DEV_TOKEN} 65 | TOKEN: ${OPENSHIFT_DEV_BACKEND_TOKEN} 66 | OPENSHIFT_SERVER: ${OPENSHIFT_SERVER_DEV} 67 | NAMESPACE: ${BACKEND_NAMESPACE_DEV} 68 | DEPLOYMENT_TYPE: backend 69 | TAG: ${DEV_TAG} 70 | environment: 71 | name: staging 72 | url: https://slides-backend.web.cern.ch 73 | # url: https://backend-slides.app.cern.ch 74 | only: 75 | - master 76 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://cern.ch/cern-nexus/repository/npm-group/ 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | package-lock.json 4 | yarn.lock 5 | package.json 6 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "processors": ["stylelint-processor-styled-components"], 3 | "extends": [ 4 | "stylelint-config-recommended", 5 | "stylelint-config-styled-components" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 2024-12-05 This project has been archived 2 | 3 |
4 | CERN_Slides_App_logo_by_L.Zacharova_and_D.Taborsky 5 |
6 | 7 | [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/) 8 | 9 | **CERN Slides App** is: 10 | * **slides-creator** application 11 | * fully **web-based** 12 | * **Open source** 13 | * **made at CERN**, [the place where the web was born](https://home.cern/science/computing/birth-web)! 14 | 15 | ## Documentation 16 | 17 | - [**Documentation for the Slides App**](https://slides.docs.cern.ch/). 18 | - [**Admin Documentation for the Slides App**](https://slides-admin.docs.cern.ch/). 19 | 20 | 21 | ## Contributors 22 | 23 | 24 | 25 |
Aristofanis Chionis
Aristofanis Chionis

💻 📖
26 | 27 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | modules: false, 7 | }, 8 | ], 9 | '@babel/preset-react', 10 | ], 11 | plugins: [ 12 | 'styled-components', 13 | '@babel/plugin-proposal-class-properties', 14 | '@babel/plugin-syntax-dynamic-import', 15 | ], 16 | env: { 17 | production: { 18 | only: ['app'], 19 | plugins: [ 20 | 'lodash', 21 | 'transform-react-remove-prop-types', 22 | '@babel/plugin-transform-react-inline-elements', 23 | '@babel/plugin-transform-react-constant-elements', 24 | ], 25 | }, 26 | test: { 27 | plugins: [ 28 | '@babel/plugin-transform-modules-commonjs', 29 | 'dynamic-import-node', 30 | ], 31 | }, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /backend.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine 2 | 3 | RUN mkdir -p /home/slides/slides-backend 4 | WORKDIR /home/slides/slides-backend 5 | 6 | ENV NODE_ENV production 7 | COPY ./packages/slides-server . 8 | RUN yarn install 9 | USER 1001 10 | # port for nodejs servers 11 | EXPOSE 8000 12 | 13 | CMD ["node", "-r", "esm", "./src/index.js"] 14 | -------------------------------------------------------------------------------- /create-os-roles.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Takes the project name as a parameter and allows the default account to create and modify the docker registry of the Openshift project 5 | project=$(oc project -q); 6 | echo "Project: ${project}"; 7 | oc policy add-role-to-user admin "system:serviceaccount:${project}:default" 8 | oc policy add-role-to-user view "system:serviceaccount:${project}:default" 9 | echo "Successfully added registry viewer and editor roles to account default in project ${project}" 10 | -------------------------------------------------------------------------------- /create-os-token.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Set the user and token variables with the values provided by GitLab on creation of the Deploy Token. 3 | args=("$@") 4 | 5 | if [ $# -ne 2 ] ; then 6 | echo "usage: ./create-os-token.sh USER PASSWORD"; 7 | exit 1; 8 | fi 9 | 10 | user=${args[0]} 11 | token=${args[1]} 12 | 13 | 14 | # Generate the secret (make sure to `oc login` into your Openshift project first) 15 | auth=$(echo -n "${user}:${token}" | base64 -w 0) 16 | dockercfg=$(echo "{\"auths\": {\"gitlab-registry.cern.ch\": {\"auth\": \"${auth}\"}, \"gitlab.cern.ch\": {\"auth\": \"${auth}\"}}}") 17 | 18 | # To create the actual token: 19 | oc create secret generic gitlab-registry-auth --from-literal=.dockerconfigjson="${dockercfg}" --type=kubernetes.io/dockerconfigjson 20 | 21 | oc secrets link default gitlab-registry-auth --for=pull 22 | echo "Successfully created and linked user secret" 23 | -------------------------------------------------------------------------------- /frontend.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine 2 | 3 | RUN mkdir -p /home/slides/slides-frontend 4 | WORKDIR /home/slides/slides-frontend 5 | 6 | RUN npm install -g serve 7 | ENV NODE_ENV production 8 | COPY ./packages/slides-frontend . 9 | RUN yarn install 10 | RUN yarn build 11 | USER 1001 12 | # port for deployed react 13 | EXPOSE 5000 14 | 15 | CMD ["serve", "-s", "build" ] 16 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "0.0.1", 6 | "npmClient": "yarn", 7 | "useWorkspaces": true 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slides", 3 | "private": true, 4 | "version": "0.0.1", 5 | "author": "CERN", 6 | "workspaces": [ 7 | "packages/*" 8 | ], 9 | "devDependencies": { 10 | "husky": "^4.2.5", 11 | "lerna": "^3.22.1" 12 | }, 13 | "scripts":{ 14 | "start:dev": "lerna run start:dev", 15 | "format": "lerna run format" 16 | }, 17 | "husky": { 18 | "hooks": { 19 | "pre-commit": "yarn format" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/slides-frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parser": "babel-eslint", 8 | "parserOptions": { 9 | "ecmaVersion": 8, 10 | "sourceType": "module", 11 | "ecmaFeatures": { 12 | "jsx": true, 13 | "modules": true 14 | } 15 | }, 16 | "plugins": ["prettier", "react"], 17 | "extends": ["prettier", "eslint:recommended", "plugin:react/recommended"], 18 | "rules": { 19 | "prettier/prettier": "error" 20 | }, 21 | "settings": { 22 | "react": { 23 | "version": "detetect" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/slides-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | build 23 | -------------------------------------------------------------------------------- /packages/slides-frontend/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://cern.ch/cern-nexus/repository/npm-group/ 2 | -------------------------------------------------------------------------------- /packages/slides-frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | package-lock.json 4 | yarn.lock 5 | package.json 6 | -------------------------------------------------------------------------------- /packages/slides-frontend/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "quoteProps": "consistent", 8 | "jsxSingleQuote": false, 9 | "trailingComma": "es5", 10 | "bracketSpacing": false, 11 | "jsxBracketSameLine": false, 12 | "arrowParens": "avoid", 13 | "endOfLine": "lf" 14 | } 15 | -------------------------------------------------------------------------------- /packages/slides-frontend/README.md: -------------------------------------------------------------------------------- 1 | # This is the CERN Slides App frontend code 2 | -------------------------------------------------------------------------------- /packages/slides-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slides-frontend", 3 | "version": "0.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "@authzsvc/keycloak-js-react": "^1.1.0", 7 | "@types/react-redux": "^7.1.9", 8 | "axios": "^0.21.1", 9 | "browser-md5-file": "^1.1.1", 10 | "connected-react-router": "6.8.0", 11 | "core-js": "^3.6.5", 12 | "draft-js": "^0.11.5", 13 | "draftjs-to-html": "^0.9.1", 14 | "eslint-config-react-app": "^5.2.1", 15 | "eslint-plugin-flowtype": "^5.1.0", 16 | "file-saver": "^2.0.2", 17 | "history": "4.10.1", 18 | "html-to-draftjs": "^1.5.0", 19 | "immer": "^8.0.1", 20 | "interactjs": "^1.9.17", 21 | "intl": "1.2.5", 22 | "invariant": "2.2.4", 23 | "jszip": "^3.2.2", 24 | "keycloak-js": "^10.0.1", 25 | "prop-types": "15.7.2", 26 | "react": "^16.13.1", 27 | "react-color": "^2.18.0", 28 | "react-dom": "^16.13.1", 29 | "react-draft-wysiwyg": "^1.14.4", 30 | "react-dropzone": "^11.0.1", 31 | "react-helmet": "6.0.0", 32 | "react-html-parser": "^2.0.2", 33 | "react-interactjs": "^0.1.2", 34 | "react-intl": "2.8.0", 35 | "react-redux": "7.2.0", 36 | "react-router-dom": "5.2.0", 37 | "react-scripts": "3.4.1", 38 | "reactablejs": "^0.2.0", 39 | "redux": "4.0.5", 40 | "redux-devtools-extension": "^2.13.8", 41 | "semantic-ui-css": "^2.4.1", 42 | "semantic-ui-react": "^0.88.2", 43 | "source-map-loader": "^0.2.4", 44 | "spectacle": "^5.7.2", 45 | "styled-components": "^5.1.0", 46 | "sweetalert2": "^9.11.0", 47 | "sweetalert2-react-content": "^3.0.1", 48 | "uuid": "^3.4.0" 49 | }, 50 | "devDependencies": { 51 | "@babel/cli": "7.8.4", 52 | "@babel/core": "7.9.6", 53 | "@babel/plugin-proposal-class-properties": "7.8.3", 54 | "@babel/plugin-syntax-dynamic-import": "7.8.3", 55 | "@babel/plugin-transform-modules-commonjs": "7.9.6", 56 | "@babel/plugin-transform-react-constant-elements": "7.9.0", 57 | "@babel/plugin-transform-react-inline-elements": "7.9.0", 58 | "@babel/preset-env": "7.9.6", 59 | "@babel/preset-react": "7.9.4", 60 | "@babel/register": "7.9.0", 61 | "add-asset-html-webpack-plugin": "3.1.3", 62 | "babel-core": "7.0.0-bridge.0", 63 | "babel-loader": "8.1.0", 64 | "babel-plugin-dynamic-import-node": "2.3.3", 65 | "babel-plugin-lodash": "3.3.4", 66 | "babel-plugin-react-intl": "3.0.1", 67 | "babel-plugin-styled-components": "1.10.7", 68 | "babel-plugin-transform-react-remove-prop-types": "0.4.24", 69 | "circular-dependency-plugin": "5.2.0", 70 | "compare-versions": "3.6.0", 71 | "compression-webpack-plugin": "2.0.0", 72 | "coveralls": "3.1.0", 73 | "css-loader": "2.1.1", 74 | "file-loader": "3.0.1", 75 | "html-loader": "0.5.5", 76 | "html-webpack-plugin": "3.2.0", 77 | "image-webpack-loader": "4.6.0", 78 | "imports-loader": "0.8.0", 79 | "node-plop": "0.18.0", 80 | "null-loader": "0.1.1", 81 | "offline-plugin": "5.0.7", 82 | "plop": "2.6.0", 83 | "pre-commit": "1.2.2", 84 | "prettier": "^2.0.5", 85 | "react-app-polyfill": "0.2.2", 86 | "react-test-renderer": "16.13.1", 87 | "react-testing-library": "6.1.2", 88 | "rimraf": "2.6.3", 89 | "shelljs": "^0.8.3", 90 | "style-loader": "0.23.1", 91 | "stylelint": "10.0.1", 92 | "stylelint-config-recommended": "2.2.0", 93 | "stylelint-config-styled-components": "0.1.1", 94 | "stylelint-processor-styled-components": "1.10.0", 95 | "svg-url-loader": "2.3.2", 96 | "terser-webpack-plugin": "1.2.3", 97 | "ts-loader": "^6.2.1", 98 | "typescript": "^3.9.3", 99 | "url-loader": "1.1.2" 100 | }, 101 | "scripts": { 102 | "start:dev": "react-scripts start", 103 | "build": "react-scripts build", 104 | "start:prod": "serve -s build", 105 | "test": "react-scripts test", 106 | "eject": "react-scripts eject", 107 | "format": "prettier-eslint --write \"$PWD/{,!(node_modules)/**/}*.{js,jsx}\"" 108 | }, 109 | "eslintConfig": { 110 | "extends": "react-app" 111 | }, 112 | "browserslist": { 113 | "production": [ 114 | ">0.2%", 115 | "not dead", 116 | "not op_mini all" 117 | ], 118 | "development": [ 119 | "last 1 chrome version", 120 | "last 1 firefox version", 121 | "last 1 safari version" 122 | ] 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /packages/slides-frontend/public/favicon-background.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CERN/slides/e15e412a3dab997ef1ba95c92a09ffee05e9f396/packages/slides-frontend/public/favicon-background.ico -------------------------------------------------------------------------------- /packages/slides-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CERN/slides/e15e412a3dab997ef1ba95c92a09ffee05e9f396/packages/slides-frontend/public/favicon.ico -------------------------------------------------------------------------------- /packages/slides-frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 17 | 26 | Slides App 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /packages/slides-frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Slides", 3 | "name": "Create beautiful Presentations!", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /packages/slides-frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/authConfig.js: -------------------------------------------------------------------------------- 1 | export const keycloakUrl = 'https://auth.cern.ch/auth'; 2 | export const keycloakRealm = 'cern'; 3 | export const keycloakClientId = 'slides-frontend'; 4 | export const refreshKeycloakToken = true; 5 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | assetsPath: 3 | !process.env.NODE_ENV || process.env.NODE_ENV === 'development' 4 | ? 'http://localhost:8000' 5 | : 'https://slides-backend.web.cern.ch', 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/configureStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create the store with dynamic reducers 3 | */ 4 | 5 | import {createStore, applyMiddleware, compose} from 'redux'; 6 | import {routerMiddleware} from 'connected-react-router'; 7 | import createReducer from './reducers'; 8 | 9 | export default function configureStore(initialState = {}, history) { 10 | let composeEnhancers = compose; 11 | 12 | // If Redux Dev Tools and Saga Dev Tools Extensions are installed, enable them 13 | /* istanbul ignore next */ 14 | if (process.env.NODE_ENV !== 'production' && typeof window === 'object') { 15 | /* eslint-disable no-underscore-dangle */ 16 | if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) 17 | composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}); 18 | } 19 | 20 | // Create the store with two middlewares 21 | // 2. routerMiddleware: Syncs the location/URL path to the state 22 | const middlewares = [routerMiddleware(history)]; 23 | 24 | const enhancers = [applyMiddleware(...middlewares)]; 25 | 26 | const store = createStore(createReducer(), initialState, composeEnhancers(...enhancers)); 27 | 28 | // Extensions 29 | store.injectedReducers = {}; // Reducer registry 30 | 31 | // Make reducers hot reloadable, see http://mxs.is/googmo 32 | /* istanbul ignore next */ 33 | if (module.hot) { 34 | module.hot.accept('./reducers', () => { 35 | store.replaceReducer(createReducer(store.injectedReducers)); 36 | }); 37 | } 38 | 39 | return store; 40 | } 41 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Alerts/AlertForSaving/index.js: -------------------------------------------------------------------------------- 1 | import Swal from 'sweetalert2'; 2 | import withReactContent from 'sweetalert2-react-content'; 3 | 4 | const MySwal = withReactContent(Swal); 5 | 6 | export default async function AlertForSaving(filename) { 7 | const {value: newFileName} = await MySwal.fire({ 8 | titleText: 'Save as...', 9 | input: 'text', 10 | inputAttributes: { 11 | autocapitalize: 'off', 12 | }, 13 | showCancelButton: true, 14 | confirmButtonText: 'Save', 15 | inputValue: filename, 16 | inputValidator: value => { 17 | if (!value) { 18 | return 'You need to provide a filename!'; 19 | } 20 | }, 21 | }); 22 | return newFileName; 23 | } 24 | 25 | /* 26 | even for uploading files consider 27 | */ 28 | /* beautiful toast success 29 | const Toast = Swal.mixin({ 30 | toast: true, 31 | position: 'top-end', 32 | showConfirmButton: false, 33 | timer: 3000, 34 | timerProgressBar: true, 35 | onOpen: (toast) => { 36 | toast.addEventListener('mouseenter', Swal.stopTimer) 37 | toast.addEventListener('mouseleave', Swal.resumeTimer) 38 | } 39 | }) 40 | 41 | Toast.fire({ 42 | icon: 'success', 43 | title: 'Signed in successfully' 44 | }) 45 | */ 46 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Alerts/fail.js: -------------------------------------------------------------------------------- 1 | import Swal from 'sweetalert2'; 2 | import withReactContent from 'sweetalert2-react-content'; 3 | 4 | const MySwal = withReactContent(Swal); 5 | 6 | export default function fail(errorMessage) { 7 | const Toast = MySwal.mixin({ 8 | toast: true, 9 | position: 'top-end', 10 | showConfirmButton: false, 11 | timer: 3000, 12 | timerProgressBar: true, 13 | onOpen: toast => { 14 | toast.addEventListener('mouseenter', MySwal.stopTimer); 15 | toast.addEventListener('mouseleave', MySwal.resumeTimer); 16 | }, 17 | }); 18 | Toast.fire({ 19 | icon: 'error', 20 | title: errorMessage, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Alerts/success.js: -------------------------------------------------------------------------------- 1 | import Swal from 'sweetalert2'; 2 | import withReactContent from 'sweetalert2-react-content'; 3 | 4 | const MySwal = withReactContent(Swal); 5 | 6 | export default function success(successMessage) { 7 | const Toast = MySwal.mixin({ 8 | toast: true, 9 | position: 'top-end', 10 | showConfirmButton: false, 11 | timer: 3000, 12 | timerProgressBar: true, 13 | onOpen: toast => { 14 | toast.addEventListener('mouseenter', MySwal.stopTimer); 15 | toast.addEventListener('mouseleave', MySwal.resumeTimer); 16 | }, 17 | }); 18 | Toast.fire({ 19 | icon: 'success', 20 | title: successMessage, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Alerts/warning.js: -------------------------------------------------------------------------------- 1 | import Swal from 'sweetalert2'; 2 | import withReactContent from 'sweetalert2-react-content'; 3 | 4 | const MySwal = withReactContent(Swal); 5 | 6 | export default function warning(errorMessage) { 7 | const Toast = MySwal.mixin({ 8 | toast: true, 9 | position: 'top-end', 10 | showConfirmButton: false, 11 | timer: 3000, 12 | timerProgressBar: true, 13 | onOpen: toast => { 14 | toast.addEventListener('mouseenter', MySwal.stopTimer); 15 | toast.addEventListener('mouseleave', MySwal.resumeTimer); 16 | }, 17 | }); 18 | Toast.fire({ 19 | icon: 'warning', 20 | title: errorMessage, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/App/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * App.js 4 | * 5 | * This component is the skeleton around the actual pages, and should only 6 | * contain code that should be seen on all pages. (e.g. navigation bar) 7 | * 8 | */ 9 | 10 | import React from 'react'; 11 | import {Switch, Route} from 'react-router-dom'; 12 | import {connect} from 'react-redux'; 13 | import PropTypes from 'prop-types'; 14 | import Container from '../Editor/Container'; 15 | import Presentation from '../Editor/Presentation'; 16 | import Export from '../Editor/Export'; 17 | import Homepage from '../Editor/Homepage'; 18 | import {Loader} from 'semantic-ui-react'; 19 | import GlobalStyle from '../../global-styles'; 20 | import NotFoundPage from '../NotFoundPage'; 21 | 22 | // Migrate to new Version of Spectacle 23 | 24 | function App({authenticated}) { 25 | return ( 26 |
27 | {authenticated ? ( 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | ) : ( 39 | 40 | )} 41 |
42 | ); 43 | } 44 | 45 | App.propTypes = { 46 | authenticated: PropTypes.bool, 47 | }; 48 | 49 | export default connect( 50 | state => ({ 51 | authenticated: state.keycloak.authenticated, 52 | }), 53 | null 54 | )(App); 55 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/App/selectors.js: -------------------------------------------------------------------------------- 1 | import {createSelector} from 'reselect'; 2 | 3 | const selectRouter = state => state.router; 4 | 5 | const makeSelectLocation = () => createSelector(selectRouter, routerState => routerState.location); 6 | 7 | export {makeSelectLocation}; 8 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/Canvas/index.css: -------------------------------------------------------------------------------- 1 | .deck { 2 | display:block; 3 | width: 100%; 4 | height: 100%; 5 | position: relative; 6 | } 7 | /* 8 | .canvas-parent { 9 | background-color: #0053A1; 10 | } */ 11 | 12 | .down { 13 | bottom: 0; 14 | position: absolute; 15 | } 16 | 17 | .center { 18 | position: absolute; 19 | } 20 | 21 | .text-box { 22 | padding-top: 5; 23 | } 24 | 25 | .editor-style { 26 | padding-top: 2em; 27 | } 28 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/Canvas/index.js: -------------------------------------------------------------------------------- 1 | import React, {useRef, useEffect} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {Helmet} from 'react-helmet'; 4 | import {connect} from 'react-redux'; 5 | import {Deck} from 'spectacle'; 6 | import createTheme from 'spectacle/lib/themes/default/index'; 7 | import history from '../../../utils/history'; 8 | import {deletePresentationFolder} from '../../../utils/requests'; 9 | 10 | import { 11 | getTheme, 12 | getTitle, 13 | getAssetsPath, 14 | getBackgroundColor, 15 | } from '../../redux-store/PresentationReducer/selectors'; 16 | 17 | import {getDeck} from '../../redux-store/DeckReducer/selectors'; 18 | 19 | import MySlide from '../MySlide'; 20 | import './index.css'; 21 | import getterTheme from '../../../theming/theme'; 22 | 23 | function Canvas({title, theme, DeckOfSlides, assetsPath, backgroundColor, username, token}) { 24 | const deck = useRef(); 25 | const themeObj = getterTheme(theme); 26 | // change fontconfig from here 27 | const newTheme = { 28 | ...themeObj, 29 | themeConfig: { 30 | ...themeObj.themeConfig, 31 | secondary: backgroundColor, 32 | }, 33 | }; 34 | 35 | const myTheme = createTheme(newTheme.themeConfig, newTheme.fontConfig); 36 | // // now make the check if it is cern 3,4,5 then add intro and end slide 37 | // // use this hook to be able to move to next previous slide in adding removing slides 38 | useEffect(() => { 39 | window.slideCount = deck.current.props.children.length; 40 | }); 41 | 42 | // catch reload event 43 | useEffect(() => { 44 | window.onbeforeunload = e => { 45 | e.preventDefault(); 46 | deletePresentationFolder(assetsPath, username, title, token).then(res => { 47 | // and navigate to home page 48 | if (res.status === 200) { 49 | console.log('Successful deletion in Server'); 50 | history.push(`/`); 51 | } 52 | }); 53 | return 'Are you sure you want to reload?'; 54 | }; 55 | }); 56 | 57 | return ( 58 |
59 | 60 | Edit: {title} 61 | 62 |
63 | 72 | {DeckOfSlides.map(item => ( 73 | 74 | ))} 75 | 76 |
77 |
78 | ); 79 | } 80 | 81 | Canvas.propTypes = { 82 | title: PropTypes.string, 83 | theme: PropTypes.string, 84 | DeckOfSlides: PropTypes.array, 85 | assetsPath: PropTypes.string, 86 | backgroundColor: PropTypes.string, 87 | username: PropTypes.string, 88 | token: PropTypes.string, 89 | }; 90 | 91 | export default connect( 92 | state => ({ 93 | theme: getTheme(state), 94 | title: getTitle(state), 95 | DeckOfSlides: getDeck(state), 96 | assetsPath: getAssetsPath(state), 97 | backgroundColor: getBackgroundColor(state), 98 | username: state.keycloak.userToken.cern_upn, 99 | token: state.keycloak.instance.token, 100 | }), 101 | null 102 | )(Canvas); 103 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/Container/index.css: -------------------------------------------------------------------------------- 1 | .themeSelector { 2 | position: absolute; 3 | height: 100%; 4 | width: 100%; 5 | background-color: #0053A1; 6 | text-align: center; 7 | } 8 | 9 | .inside { 10 | display: inline-block; 11 | } 12 | 13 | .ui.header.white { 14 | color: white; 15 | } 16 | 17 | .parent { 18 | display: grid; 19 | grid-template-columns: 0.7fr 1fr 10fr; 20 | grid-template-rows: 1fr; 21 | /* grid-column-gap: 0px; 22 | grid-row-gap: 0px; */ 23 | background-color: black; 24 | } 25 | 26 | .div1 { grid-area: 1 / 1 / 2 / 2; } 27 | .div2 { grid-area: 1 / 2 / 2 / 3; } 28 | .div3 { grid-area: 1 / 3 / 2 / 4; } -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/Container/index.js: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import './index.css'; 6 | import Settings from '../Settings'; 7 | import Canvas from '../Canvas'; 8 | import SideBar from '../SideBar'; 9 | import SavePresentation from '../../SavePresentation'; 10 | import LoadPresentation from '../../LoadPresentation'; 11 | import {getIsReady} from '../../redux-store/PresentationReducer/selectors'; 12 | import {setPresentationMode, setExportMode} from '../../redux-store/PresentationReducer/actions'; 13 | import Upload from '../components/image/Upload'; 14 | import StyleComponent from '../../StyleComponent'; 15 | import ThemeSelector from '../../ThemeSelector'; 16 | import PageNotFound from '../../NotFoundPage'; 17 | 18 | export function Container({isReady, onSetPresentationMode, onSetExportMode}) { 19 | useEffect(() => { 20 | onSetPresentationMode(); 21 | onSetExportMode(); 22 | }); 23 | 24 | return ( 25 |
26 | {isReady ? ( 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 | ) : ( 38 | 39 | )} 40 |
41 | ); 42 | } 43 | 44 | Container.propTypes = { 45 | isReady: PropTypes.bool, 46 | onSetPresentationMode: PropTypes.func, 47 | onSetExportMode: PropTypes.func, 48 | }; 49 | 50 | function mapDispatchToProps(dispatch) { 51 | return { 52 | onSetPresentationMode: () => dispatch(setPresentationMode(false)), 53 | onSetExportMode: () => dispatch(setExportMode(false)), 54 | }; 55 | } 56 | 57 | export default connect( 58 | state => ({ 59 | isReady: getIsReady(state), 60 | }), 61 | mapDispatchToProps 62 | )(Container); 63 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/Export/MyExportedSlides.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {connect} from 'react-redux'; 4 | import StandardSlide from '../../../theming/StandardSlide'; 5 | import {getTheme, getAssetsPath, getTitle} from '../../redux-store/PresentationReducer/selectors'; 6 | import {getDeck} from '../../redux-store/DeckReducer/selectors'; 7 | import RenderHtml from '../components/text/RenderHtml'; 8 | import {getPixels} from '../../../utils/helperFunctions'; 9 | import './index.css'; 10 | 11 | const Core = ({x, y, width, height, item, assetsPath, username, title}) => ( 12 |
23 | {item.type === 'TEXT' ? ( 24 | // the item is text so use the renderHTML 25 | 26 | ) : ( 27 | // if it is an image 28 |
29 | 30 |
31 | )} 32 |
33 | ); 34 | 35 | function MyExportedSlides({theme, DeckOfSlides, assetsPath, username, title}) { 36 | const StandardSlideTemplate = StandardSlide(theme); 37 | return ( 38 | <> 39 | {DeckOfSlides.map(slide => ( 40 | <> 41 | 42 | {slide.itemsArray.map(item => ( 43 | 54 | ))} 55 | 56 | 57 | ))} 58 | 59 | ); 60 | } 61 | 62 | MyExportedSlides.propTypes = { 63 | theme: PropTypes.string.isRequired, 64 | DeckOfSlides: PropTypes.array, 65 | assetsPath: PropTypes.string, 66 | username: PropTypes.string, 67 | title: PropTypes.string, 68 | }; 69 | 70 | export default connect( 71 | state => ({ 72 | theme: getTheme(state), 73 | DeckOfSlides: getDeck(state), 74 | assetsPath: getAssetsPath(state), 75 | username: state.keycloak.userToken.cern_upn, 76 | title: getTitle(state), 77 | }), 78 | null 79 | )(MyExportedSlides); 80 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/Export/alerting.js: -------------------------------------------------------------------------------- 1 | import Swal from 'sweetalert2'; 2 | import './index.css'; 3 | 4 | export default function exportPDFinfo() { 5 | Swal.fire({ 6 | icon: 'info', 7 | title: 'Export to PDF instructions', 8 | confirmButtonText: 'I am ready!', 9 | html: 10 | "
  1. Use ctrl or cmd + P to open browser's Print dialog
  2. " + 11 | '
  3. Change destination to Save as PDF
  4. ' + 12 | '
  5. Make sure you select the Background graphics option
  6. ' + 13 | "
  7. Click Save, to download your slides' as a PDF file
", 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/Export/index.css: -------------------------------------------------------------------------------- 1 | /* Overwriting this is a hack to show all slides instead of only the first one */ 2 | .spectacle-slide { 3 | position: relative !important; 4 | } 5 | 6 | div.img-style img { 7 | max-width: 100%; 8 | max-height: 100%; 9 | /* height: auto; 10 | width:auto; */ 11 | /* display: flex; 12 | height: 100%; 13 | width: 100%; */ 14 | } 15 | 16 | .swal2-content { 17 | text-align: left !important; 18 | } -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/Export/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {connect} from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import { 5 | getIsReady, 6 | getTitle, 7 | getTheme, 8 | getBackgroundColor, 9 | } from '../../redux-store/PresentationReducer/selectors'; 10 | import {getDeck} from '../../redux-store/DeckReducer/selectors'; 11 | import {Helmet} from 'react-helmet'; 12 | import history from '../../../utils/history'; 13 | import {Deck} from 'spectacle'; 14 | import createTheme from 'spectacle/lib/themes/default/index'; 15 | import getterTheme from '../../../theming/theme'; 16 | import PageNotFound from '../../NotFoundPage'; 17 | import MyExportedSlides from './MyExportedSlides'; 18 | import exportPDFinfo from './alerting'; 19 | import './index.css'; 20 | 21 | /* 22 | Update spectacle 23 | Fix themes 24 | Update the way of exporting PDFs because it works better in the latest version 25 | */ 26 | 27 | function Export({isReady, title, theme, backgroundColor, DeckOfSlides}) { 28 | const themeObj = getterTheme(theme); 29 | // change fontconfig from here 30 | const newTheme = { 31 | ...themeObj, 32 | themeConfig: { 33 | ...themeObj.themeConfig, 34 | secondary: backgroundColor, 35 | }, 36 | }; 37 | 38 | // In history I check if there is a second slide or not 39 | // if there is we need the history in order to get all the slides 40 | // if it's only 1 slide then no history needed 41 | const myTheme = createTheme(newTheme.themeConfig, newTheme.fontConfig); 42 | return ( 43 |
44 | {isReady ? ( 45 |
46 | 47 | Export: {title} 48 | 49 | {exportPDFinfo()} 50 | 1 ? history : null} 55 | disableKeyboardControls={true} 56 | controls={false} 57 | > 58 | {/* Inside here have a function that statically return 59 | the array of slides and array of elements of each slide */} 60 | 61 | 62 |
63 | ) : ( 64 | 65 | )} 66 |
67 | ); 68 | } 69 | 70 | Export.propTypes = { 71 | isReady: PropTypes.bool, 72 | title: PropTypes.string, 73 | theme: PropTypes.string, 74 | backgroundColor: PropTypes.string, 75 | DeckOfSlides: PropTypes.array, 76 | }; 77 | 78 | export default connect( 79 | state => ({ 80 | isReady: getIsReady(state), 81 | title: getTitle(state), 82 | theme: getTheme(state), 83 | backgroundColor: getBackgroundColor(state), 84 | DeckOfSlides: getDeck(state), 85 | }), 86 | null 87 | )(Export); 88 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/Homepage/index.css: -------------------------------------------------------------------------------- 1 | .themeSelector { 2 | position: absolute; 3 | height: 100%; 4 | width: 100%; 5 | background-color: #0053A1; 6 | text-align: center; 7 | } 8 | 9 | .inside { 10 | display: inline-block; 11 | width: 100%; 12 | } 13 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/Homepage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import LandingPage from '../LandingPage'; 4 | import LoadPresentation from '../../LoadPresentation'; 5 | import './index.css'; 6 | 7 | function Homepage() { 8 | return ( 9 |
10 |
11 | 12 | 13 |
14 |
15 | ); 16 | } 17 | 18 | export default Homepage; 19 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/LandingPage/index.css: -------------------------------------------------------------------------------- 1 | .spacing { 2 | margin-bottom: 0.5em !important; 3 | } 4 | 5 | .ui.grid { 6 | margin: 0 !important; 7 | } 8 | 9 | .image { 10 | height: 11%; 11 | width: 11%; 12 | margin-top: 1em; 13 | margin-bottom: 1em; 14 | } 15 | 16 | .landing-page { 17 | background-color: #0053A1 !important; 18 | height: 100%; 19 | width: 100%; 20 | } 21 | 22 | .ui.segment { 23 | background-color: #0053A1 !important; 24 | box-shadow: 0 0 0 0 !important; 25 | border: 0!important; 26 | } 27 | 28 | .ui.divider { 29 | color: white !important; 30 | } 31 | 32 | .footer { 33 | position: fixed; 34 | left: 1%; 35 | bottom: 0; 36 | width: 100%; 37 | color: white; 38 | text-align: left; 39 | } 40 | 41 | .copyright-text { 42 | position: relative; 43 | top: 1.2em; 44 | } 45 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/LandingPage/index.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {connect} from 'react-redux'; 4 | import {titleCheck} from '../../../utils/requests'; 5 | import {Button, Form, Header, Image, Segment, Input, Grid, Divider} from 'semantic-ui-react'; 6 | import config from '../../../config'; 7 | 8 | import { 9 | setTitle, 10 | setLoadRequest, 11 | setIsReady, 12 | setAssetsPath, 13 | } from '../../redux-store/PresentationReducer/actions'; 14 | 15 | import {getTitle, getAssetsPath} from '../../redux-store/PresentationReducer/selectors'; 16 | 17 | import './index.css'; 18 | import history from '../../../utils/history'; 19 | import warning from '../../Alerts/warning'; 20 | 21 | function LandingPage({ 22 | onSetTitle, 23 | currentTitle, 24 | onSetIsReady, 25 | onLoadRequest, 26 | assetsPath, 27 | onSetAssetsPath, 28 | username, 29 | token, 30 | }) { 31 | const [title, setTi] = useState(currentTitle); 32 | const [loadingIndicator, setLoading] = useState(false); 33 | 34 | const clickHandlerNew = () => { 35 | // first check in server if title is good 36 | setLoading(true); 37 | if (title === '') { 38 | // alert for bad title 39 | warning("Title can't be empty!"); 40 | setLoading(false); 41 | return; 42 | } 43 | titleCheck(assetsPath, username, title, token) 44 | .then(res => { 45 | if (res.status === 200) { 46 | // all went good 47 | onSetTitle(title); 48 | const user = username; 49 | history.push(`/edit/${user}/${title}/`); 50 | // ready 51 | onSetIsReady(); 52 | } 53 | setLoading(false); 54 | }) 55 | .catch(err => { 56 | console.log('error in title is', err); 57 | // alert for bad title 58 | warning('Presentation with the same title already exists!'); 59 | setLoading(false); 60 | }); 61 | }; 62 | 63 | const clickHandlerLoad = () => { 64 | // set LoadReq 65 | onLoadRequest(); 66 | // load component will take over and load content 67 | }; 68 | // this .trim() removes trailing whitespaces from both ends of the string 69 | const settingTitle = (e, {value}) => setTi(value.trim()); 70 | 71 | // set the assetsFolder, where images will be, in the redux store 72 | if (assetsPath === '') onSetAssetsPath(config.assetsPath); 73 | // if isAuthenticated render else Loading... 74 | return ( 75 |
76 | CERN_Slides_App_logo_by_L.Zacharova_and_D.Taborsky 82 | 83 | 84 | 85 | 86 | 87 |
88 |
89 | 90 | 96 | 99 | 100 |
101 | 102 | 103 |
104 | 107 | 108 | 109 | 110 | Or 111 | 112 | 113 |
114 | CERN logo 120 | 121 | 128 | CERN Copyright 129 | 130 | 131 |
132 |
133 | ); 134 | } 135 | 136 | LandingPage.propTypes = { 137 | onSetTitle: PropTypes.func, 138 | currentTitle: PropTypes.string, 139 | onSetIsReady: PropTypes.func, 140 | onLoadRequest: PropTypes.func, 141 | assetsPath: PropTypes.string, 142 | onSetAssetsPath: PropTypes.func, 143 | username: PropTypes.string, 144 | token: PropTypes.string, 145 | }; 146 | 147 | export function mapDispatchToProps(dispatch) { 148 | return { 149 | onSetTitle: title => dispatch(setTitle(title)), 150 | onLoadRequest: () => dispatch(setLoadRequest(true)), 151 | onSetIsReady: () => dispatch(setIsReady(true)), 152 | onSetAssetsPath: path => dispatch(setAssetsPath(path)), 153 | }; 154 | } 155 | 156 | export default connect( 157 | state => ({ 158 | currentTitle: getTitle(state), 159 | assetsPath: getAssetsPath(state), 160 | username: state.keycloak.userToken.cern_upn, 161 | token: state.keycloak.instance.token, 162 | }), 163 | mapDispatchToProps 164 | )(LandingPage); 165 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/MySlide/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {connect} from 'react-redux'; 4 | import StandardSlide from '../../../theming/StandardSlide'; 5 | import {getTheme} from '../../redux-store/PresentationReducer/selectors'; 6 | import {getItems} from '../../redux-store/DeckReducer/selectors'; 7 | import MoveResize from '../components/resize_move'; 8 | 9 | function MySlide({theme, itemsArray}) { 10 | const StandardSlideTemplate = StandardSlide(theme); 11 | 12 | return ( 13 | 14 | {itemsArray.map(itm => ( 15 | 16 | ))} 17 | 18 | ); 19 | } 20 | 21 | MySlide.propTypes = { 22 | theme: PropTypes.string.isRequired, 23 | itemsArray: PropTypes.array, 24 | }; 25 | 26 | export default connect( 27 | state => ({ 28 | theme: getTheme(state), 29 | itemsArray: getItems(state), 30 | }), 31 | null 32 | )(MySlide); 33 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/Presentation/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {connect} from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import { 5 | getIsReady, 6 | getTitle, 7 | getTheme, 8 | getBackgroundColor, 9 | } from '../../redux-store/PresentationReducer/selectors'; 10 | import {getDeck} from '../../redux-store/DeckReducer/selectors'; 11 | import {Helmet} from 'react-helmet'; 12 | 13 | import {Deck} from 'spectacle'; 14 | import MySlide from '../MySlide'; 15 | import createTheme from 'spectacle/lib/themes/default/index'; 16 | import getterTheme from '../../../theming/theme'; 17 | 18 | import PageNotFound from '../../NotFoundPage'; 19 | 20 | function Presentation({isReady, DeckOfSlides, title, theme, backgroundColor}) { 21 | const themeObj = getterTheme(theme); 22 | // change fontconfig from here 23 | const newTheme = { 24 | ...themeObj, 25 | themeConfig: { 26 | ...themeObj.themeConfig, 27 | secondary: backgroundColor, 28 | }, 29 | }; 30 | 31 | const myTheme = createTheme(newTheme.themeConfig, newTheme.fontConfig); 32 | return ( 33 |
34 | {isReady ? ( 35 |
36 | 37 | Present: {title} 38 | 39 | 47 | {DeckOfSlides.map(item => ( 48 | 49 | ))} 50 | 51 |
52 | ) : ( 53 | 54 | )} 55 |
56 | ); 57 | } 58 | 59 | Presentation.propTypes = { 60 | isReady: PropTypes.bool, 61 | DeckOfSlides: PropTypes.array, 62 | title: PropTypes.string, 63 | theme: PropTypes.string, 64 | backgroundColor: PropTypes.string, 65 | }; 66 | 67 | export default connect( 68 | state => ({ 69 | isReady: getIsReady(state), 70 | DeckOfSlides: getDeck(state), 71 | title: getTitle(state), 72 | theme: getTheme(state), 73 | backgroundColor: getBackgroundColor(state), 74 | }), 75 | null 76 | )(Presentation); 77 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/Settings/index.css: -------------------------------------------------------------------------------- 1 | .lastitem { 2 | top: auto; 3 | bottom: 0; 4 | position: absolute; 5 | } 6 | 7 | .settings { 8 | min-width: 3em; 9 | /* height: 100vh; */ 10 | height: 100vh; 11 | background-color: #1B1C1D; 12 | /* bottom: 0; 13 | top: 0; */ 14 | } -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/Settings/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {connect} from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import {Menu, Icon, Popup} from 'semantic-ui-react'; 5 | import history from '../../../utils/history'; 6 | import { 7 | setSaveRequest, 8 | setStyleRequest, 9 | setLoadRequest, 10 | themeRequest, 11 | setPresentationMode, 12 | setExportMode, 13 | } from '../../redux-store/PresentationReducer/actions'; 14 | import {getTitle} from '../../redux-store/PresentationReducer/selectors'; 15 | import './index.css'; 16 | 17 | function Settings({ 18 | onSaveRequest, 19 | onStyleRequest, 20 | onLoadRequest, 21 | onThemeRequest, 22 | username, 23 | title, 24 | onSetPresentationMode, 25 | onSetExportMode, 26 | }) { 27 | const onClickHandler = item => { 28 | switch (item) { 29 | case 'cloud upload': 30 | onLoadRequest(); 31 | break; 32 | case 'save': 33 | onSaveRequest(); 34 | break; 35 | case 'paint brush': 36 | onStyleRequest(); 37 | break; 38 | case 'theme': 39 | onThemeRequest(); 40 | break; 41 | case 'eye': 42 | onSetPresentationMode(); 43 | history.push(`/present/${username}/${title}/`); 44 | break; 45 | case 'cloud download': 46 | onSetExportMode(); 47 | // this makes everything stop moving and be deletable, exactly like in presentation Mode 48 | onSetPresentationMode(); 49 | history.push(`/export/${username}/${title}?export&print`); 50 | break; 51 | default: 52 | break; 53 | } 54 | }; 55 | const Item = (item, description) => ( 56 | onClickHandler(item)}> 57 | } 60 | content={description} 61 | position="right center" 62 | /> 63 | 64 | ); 65 | return ( 66 |
67 | 68 | {Item('eye', 'Slideshow')} 69 | {Item('save', 'Save Presentation')} 70 | {/* {Item('play', 'Present (Ctrl+E)')} */} 71 | {/* {Item('setting', 'Presentation Settings')} */} 72 | {Item('paint brush', 'Change Background Color')} 73 | {Item('theme', 'Change Theme')} 74 | {/* {Item('ordered list', 'Arrange Slides')} */} 75 | {Item('cloud upload', 'Upload existing presentation')} 76 | {Item('cloud download', 'Export as PDF (beta)')} 77 | 78 |
79 | ); 80 | } 81 | 82 | Settings.propTypes = { 83 | onSaveRequest: PropTypes.func, 84 | onStyleRequest: PropTypes.func, 85 | onLoadRequest: PropTypes.func, 86 | onThemeRequest: PropTypes.func, 87 | username: PropTypes.string, 88 | title: PropTypes.string, 89 | onSetPresentationMode: PropTypes.func, 90 | onSetExportMode: PropTypes.func, 91 | }; 92 | 93 | export function mapDispatchToProps(dispatch) { 94 | return { 95 | onSaveRequest: () => dispatch(setSaveRequest(true)), 96 | onStyleRequest: () => dispatch(setStyleRequest(true)), 97 | onLoadRequest: () => dispatch(setLoadRequest(true)), 98 | onThemeRequest: () => dispatch(themeRequest(true)), 99 | onSetPresentationMode: () => dispatch(setPresentationMode(true)), 100 | onSetExportMode: () => dispatch(setExportMode(true)), 101 | }; 102 | } 103 | 104 | export default connect( 105 | state => ({ 106 | username: state.keycloak.userToken.cern_upn, 107 | title: getTitle(state), 108 | }), 109 | mapDispatchToProps 110 | )(Settings); 111 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/SideBar/index.css: -------------------------------------------------------------------------------- 1 | .ui.inverted.menu { 2 | background: transparent !important; 3 | } 4 | 5 | .sidebar { 6 | min-width: 5em; 7 | height: 100vh; 8 | background-color: black; 9 | } -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/SideBar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {connect} from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import {Menu, Icon} from 'semantic-ui-react'; 6 | import {addSlide, removeSlide, addItem, cloneSlide} from '../../redux-store/DeckReducer/actions'; 7 | import {getCurrentSlide, getItems} from '../../redux-store/DeckReducer/selectors'; 8 | import {uploadImageRequest} from '../../redux-store/PresentationReducer/actions'; 9 | import {getAssetsPath, getTitle} from '../../redux-store/PresentationReducer/selectors'; 10 | import {deleteImage} from '../../../utils/requests'; 11 | import {ItemTypes} from '../../redux-store/DeckReducer/definitions'; 12 | import './index.css'; 13 | // when i render SideBar onClick they will render something in the middle 14 | 15 | function SideBar({ 16 | onAddSlide, 17 | onRemoveSlide, 18 | onAddText, 19 | currentSlide, 20 | onAddImage, 21 | onCloneSlide, 22 | itemsArray, 23 | assetsPath, 24 | username, 25 | title, 26 | token, 27 | slides 28 | }) { 29 | 30 | const addingSlide = () => { 31 | onAddSlide(); 32 | window.location = `#/${currentSlide + 1}`; 33 | }; 34 | 35 | const removingSlide = () => { 36 | // check if pictures should be deleted from backend 37 | itemsArray.forEach(item => { 38 | if (item.type === 'IMAGE') { 39 | deleteImage(assetsPath, username, title, item.Src, token, slides); 40 | } 41 | }) 42 | onRemoveSlide(); 43 | if (currentSlide === 0) { 44 | window.location = `#/${0}`; 45 | } else window.location = `#/${currentSlide - 1}`; 46 | }; 47 | const cloningSlide = () => { 48 | onCloneSlide(); 49 | window.location = `#/${currentSlide + 1}`; 50 | }; 51 | return ( 52 | 76 | ); 77 | } 78 | 79 | SideBar.propTypes = { 80 | onAddSlide: PropTypes.func, 81 | onRemoveSlide: PropTypes.func, 82 | onAddText: PropTypes.func, 83 | onAddImage: PropTypes.func, 84 | currentSlide: PropTypes.number, 85 | itemsArray: PropTypes.array, 86 | assetsPath: PropTypes.string, 87 | username: PropTypes.string, 88 | title: PropTypes.string, 89 | token: PropTypes.string, 90 | slides: PropTypes.array, 91 | }; 92 | 93 | function mapDispatchToProps(dispatch) { 94 | return { 95 | onAddSlide: () => dispatch(addSlide()), 96 | onRemoveSlide: () => dispatch(removeSlide()), 97 | onAddText: () => dispatch(addItem({type: ItemTypes.TEXT})), 98 | onAddImage: () => dispatch(uploadImageRequest(true)), 99 | onCloneSlide: () => dispatch(cloneSlide()), 100 | }; 101 | } 102 | 103 | export default connect( 104 | state => ({ 105 | currentSlide: getCurrentSlide(state), 106 | itemsArray: getItems(state), 107 | assetsPath: getAssetsPath(state), 108 | title: getTitle(state), 109 | username: state.keycloak.userToken.cern_upn, 110 | token: state.keycloak.instance.token, 111 | slides: state.deck.slides, 112 | }), 113 | mapDispatchToProps 114 | )(SideBar); 115 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/components/image/Upload/Dropzone.css: -------------------------------------------------------------------------------- 1 | .dropzone { 2 | text-align: center; 3 | } 4 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/components/image/Upload/Dropzone.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useMemo} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import {useDropzone} from 'react-dropzone'; 4 | import PropTypes from 'prop-types'; 5 | import {Button, Icon} from 'semantic-ui-react'; 6 | import {uploadImage} from '../../../../../utils/requests'; 7 | import BMF from 'browser-md5-file'; 8 | import {getAssetsPath, getTitle} from '../../../../redux-store/PresentationReducer/selectors'; 9 | import {addItem} from '../../../../redux-store/DeckReducer/actions'; 10 | import {uploadImageRequest} from '../../../../redux-store/PresentationReducer/actions'; 11 | import {ItemTypes} from '../../../../redux-store/DeckReducer/definitions'; 12 | 13 | import './Dropzone.css'; 14 | import { 15 | thumbsContainer, 16 | thumb, 17 | thumbInner, 18 | img, 19 | baseStyle, 20 | activeStyle, 21 | acceptStyle, 22 | rejectStyle, 23 | } from '../../../styles'; 24 | // Add notification and check System 25 | 26 | export function Dropzone({onImageRequest, onAddImage, assetsPath, username, title, token}) { 27 | const [files, setFiles] = useState([]); 28 | const [loadingIndicator, setLoading] = useState(false); 29 | const {getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject} = useDropzone({ 30 | accept: 'image/*', 31 | onDrop: acceptedFiles => { 32 | setFiles( 33 | acceptedFiles.map(file => 34 | Object.assign(file, { 35 | preview: URL.createObjectURL(file), 36 | }) 37 | ) 38 | ); 39 | }, 40 | }); 41 | 42 | const thumbs = files.map(file => ( 43 |
44 |
45 | preview 46 |
47 |
48 | )); 49 | 50 | const style = useMemo( 51 | () => ({ 52 | ...baseStyle, 53 | ...(isDragActive ? activeStyle : {}), 54 | ...(isDragAccept ? acceptStyle : {}), 55 | ...(isDragReject ? rejectStyle : {}), 56 | }), 57 | [isDragActive, isDragAccept, isDragReject] 58 | ); 59 | 60 | const onUploadHandler = e => { 61 | e.preventDefault(); 62 | setLoading(true); 63 | // Upload Files in the server 64 | uploadImage(assetsPath, username, title, files, token).then(res => { 65 | // Save images in Redux Store 66 | // find md5 of the file and append name 67 | files.forEach(f => { 68 | const bmf = new BMF(); 69 | bmf.md5(f, (err, hash) => { 70 | onAddImage(`${hash}_${f.name}`); 71 | }); 72 | }); 73 | // destroy the reference to all of the files 74 | files.forEach(file => URL.revokeObjectURL(file.preview)); 75 | // done! So notify Redux Store 76 | onImageRequest(); 77 | }); 78 | }; 79 | 80 | const onCancelHandler = e => { 81 | e.preventDefault(); 82 | // notify Redux Store we are done with image Uploading the request is not valid anymore 83 | onImageRequest(); 84 | }; 85 | 86 | return ( 87 |
88 |
89 | 90 |

Drag 'n' drop an Image here, or click to select from files

91 |
92 | 93 | 96 | 104 |
105 | ); 106 | } 107 | 108 | Dropzone.propTypes = { 109 | onImageRequest: PropTypes.func, 110 | onAddImage: PropTypes.func, 111 | assetsPath: PropTypes.string, 112 | username: PropTypes.string, 113 | title: PropTypes.string, 114 | token: PropTypes.string, 115 | }; 116 | 117 | function mapDispatchToProps(dispatch) { 118 | return { 119 | onImageRequest: () => dispatch(uploadImageRequest(false)), 120 | onAddImage: Src => dispatch(addItem({type: ItemTypes.IMAGE, Src})), 121 | }; 122 | } 123 | 124 | export default connect( 125 | state => ({ 126 | assetsPath: getAssetsPath(state), 127 | username: state.keycloak.userToken.cern_upn, 128 | title: getTitle(state), 129 | token: state.keycloak.instance.token, 130 | }), 131 | mapDispatchToProps 132 | )(Dropzone); 133 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/components/image/Upload/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {connect} from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import {Modal} from 'semantic-ui-react'; 5 | 6 | import Dropzone from './Dropzone'; 7 | import {getImgUploadRequest} from '../../../../redux-store/PresentationReducer/selectors'; 8 | 9 | function Upload({uploadRequest}) { 10 | return ( 11 | 12 | Upload an Image 13 | 14 | 15 | 16 | 17 | ); 18 | } 19 | 20 | Upload.propTypes = { 21 | uploadRequest: PropTypes.bool, 22 | }; 23 | 24 | export default connect( 25 | state => ({ 26 | uploadRequest: getImgUploadRequest(state), 27 | }), 28 | null 29 | )(Upload); 30 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/components/image/index.css: -------------------------------------------------------------------------------- 1 | /* this ensures that image will be resized according to its div */ 2 | 3 | div.img-style img { 4 | max-width: 100%; 5 | max-height: 100%; 6 | /* height: auto; 7 | width:auto; */ 8 | /* display: flex; 9 | height: 100%; 10 | width: 100%; */ 11 | } -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/components/image/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {connect} from 'react-redux'; 4 | import {getAssetsPath, getTitle} from '../../../redux-store/PresentationReducer/selectors'; 5 | import {getItems} from '../../../redux-store/DeckReducer/selectors'; 6 | import './index.css'; 7 | 8 | const MyImage = ({ID, itemsArray, assetsPath, username, title}) => { 9 | const item = itemsArray.find(itm => itm.ID === ID); 10 | const imageSrc = `${assetsPath}/static/${username}/${title}/assets/${item.Src}`; 11 | 12 | return ( 13 |
14 | 15 |
16 | ); 17 | }; 18 | 19 | MyImage.propTypes = { 20 | ID: PropTypes.string, 21 | itemsArray: PropTypes.array, 22 | assetsPath: PropTypes.string, 23 | username: PropTypes.string, 24 | title: PropTypes.string, 25 | }; 26 | 27 | export default connect(state => ({ 28 | assetsPath: getAssetsPath(state), 29 | username: state.keycloak.userToken.cern_upn, 30 | title: getTitle(state), 31 | itemsArray: getItems(state), 32 | }))(MyImage); 33 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/components/resize_move/index.css: -------------------------------------------------------------------------------- 1 | .item-style:hover { 2 | border-color: #66afe9; 3 | box-shadow: 0 0 8px rgb(0, 140, 255); 4 | } 5 | 6 | .item-style:focus { 7 | border-color: #66afe9; 8 | box-shadow: 0 0 8px rgb(0, 140, 255); 9 | } 10 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/components/resize_move/index.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import reactable from 'reactablejs'; 4 | import interact from 'interactjs'; 5 | import {connect} from 'react-redux'; 6 | import {Label} from 'semantic-ui-react' 7 | import {getItems} from '../../../redux-store/DeckReducer/selectors'; 8 | import Text from '../text'; 9 | import Image from '../image'; 10 | import {deleteImage} from '../../../../utils/requests'; 11 | import {getPercentage, getPixels, getWidth} from '../../../../utils/helperFunctions'; 12 | import {removeItem} from '../../../redux-store/DeckReducer/actions'; 13 | import { 14 | changeItemPosition, 15 | changeItemSize, 16 | setEditMode, 17 | } from '../../../redux-store/DeckReducer/actions'; 18 | import { 19 | getAssetsPath, 20 | getTitle, 21 | getPresentationMode, 22 | } from '../../../redux-store/PresentationReducer/selectors'; 23 | import './index.css'; 24 | 25 | // instead of x, y I should have % percentages 26 | const editMode = (type, edit) => type === 'TEXT' && edit; 27 | // min height, min width 28 | const restrictSizeParameters = type => 29 | type === 'TEXT' 30 | ? { 31 | min: {width: 500, height: 80}, 32 | // max: { width: 800, height: 400 }, 33 | } 34 | : { 35 | min: {width: 100, height: 80}, 36 | // max: { width: 1000, height: 800 }, 37 | }; 38 | 39 | // : x,y: 350, 330 for middle 40 | const Core = ({ 41 | x, 42 | y, 43 | width, 44 | height, 45 | getRef, 46 | item, 47 | assetsPath, 48 | onRemoveItem, 49 | username, 50 | title, 51 | token, 52 | presentationMode, 53 | slides 54 | }) => { 55 | const ItemComponent = item.type === 'TEXT' ? Text : Image; 56 | const [closeIconShown, setCloseIconShown] = useState(false); 57 | const deleter = e => { 58 | // send a delete in Redux 59 | if (presentationMode) return; 60 | if (item.type === 'TEXT' && item.Edit) { 61 | // text in edit mode so don't delete it 62 | return; 63 | } 64 | // send a delete in Server if it is an Image 65 | if (item.type === 'IMAGE') { 66 | deleteImage(assetsPath, username, title, item.Src, token, slides); 67 | } 68 | onRemoveItem(item.ID); 69 | }; 70 | 71 | return ( 72 |
setCloseIconShown(true)} 86 | onMouseLeave={() => setCloseIconShown(false)} 87 | > 88 | {/* presentation mode and edit mode no close icon */} 89 | {/* deleter of icon */} 90 | {closeIconShown && !editMode(item.type, item.Edit) && !presentationMode && ( 91 |
95 | )}; 96 | 97 | const Reactable = reactable(Core); 98 | 99 | function MoveResize({ 100 | ID, 101 | item, 102 | onChangePosition, 103 | onChangeSize, 104 | onSetEditMode, 105 | presentationMode, 106 | assetsPath, 107 | onRemoveItem, 108 | username, 109 | title, 110 | token, 111 | slides 112 | }) { 113 | const [coordinate, setCoordinate] = useState({ 114 | x: getPixels(item.Position.x, getWidth(presentationMode)), 115 | y: getPixels(item.Position.y, window.innerHeight), 116 | width: getPixels(item.Size.width, getWidth(presentationMode)), 117 | height: getPixels(item.Size.height, window.innerHeight), 118 | }); 119 | 120 | // const editMode = (type, edit) => type === 'TEXT' && edit; 121 | 122 | const onDragStop = e => { 123 | console.log('coordinate:', coordinate); 124 | const x = e.client.x - e.clientX0 + coordinate.x; 125 | const y = e.client.y - e.clientY0 + coordinate.y; 126 | console.log('Drag Stopped', x, y); 127 | onChangePosition(ID, { 128 | x: getPercentage(x, getWidth(presentationMode)), 129 | y: getPercentage(y, window.innerHeight), 130 | }); 131 | }; 132 | 133 | const onResizeStop = e => { 134 | const {width, height} = e.rect; 135 | console.log('Resize Stopped', width, height); 136 | onDragStop(e); 137 | onChangeSize(ID, { 138 | width: getPercentage(width, getWidth(presentationMode)), 139 | height: getPercentage(height, window.innerHeight), 140 | }); 141 | }; 142 | 143 | const handler = () => { 144 | // if in presentation Mode you can't change the editing of an item 145 | if (presentationMode) return; 146 | if (item.type === 'TEXT' && !item.Edit) { 147 | onSetEditMode(ID, true); 148 | } 149 | }; 150 | 151 | const movableItemRender = () => ( 152 | { 157 | setCoordinate(prev => ({ 158 | ...prev, 159 | x: prev.x + e.dx, 160 | y: prev.y + e.dy, 161 | })); 162 | }, 163 | onend: e => { 164 | onDragStop(e); 165 | }, 166 | modifiers: [ 167 | interact.modifiers.restrictRect({ 168 | restriction: '.deck', 169 | endOnly: true, 170 | // hold: 1000 171 | }), 172 | ], 173 | }} 174 | resizable={{ 175 | // if I am in presentation Mode don't let user mode items around 176 | enabled: !presentationMode, 177 | edges: {left: true, right: true, bottom: true, top: true}, 178 | // preserveAspectRatio: true, 179 | onmove: e => { 180 | const {width, height} = e.rect; 181 | const {left, top} = e.deltaRect; 182 | setCoordinate(prev => ({ 183 | x: prev.x + left, 184 | y: prev.y + top, 185 | width, 186 | height, 187 | })); 188 | }, 189 | onend: e => { 190 | onResizeStop(e); 191 | }, 192 | modifiers: [ 193 | interact.modifiers.restrictEdges({ 194 | outer: '.deck', 195 | endOnly: true, 196 | // hold: 1000 197 | }), 198 | interact.modifiers.restrictSize(restrictSizeParameters(item.type)), 199 | ], 200 | }} 201 | {...coordinate} 202 | item={item} 203 | assetsPath={assetsPath} 204 | onRemoveItem={onRemoveItem} 205 | username={username} 206 | title={title} 207 | token={token} 208 | presentationMode={presentationMode} 209 | slides={slides} 210 | /> 211 | ); 212 | 213 | const textEditModeRender = () => 214 | ; 225 | 226 | return ( 227 |
228 | {editMode(item.type, item.Edit) ? ( 229 |
{textEditModeRender()}
230 | ) : ( 231 |
232 | {movableItemRender()} 233 |
234 | )} 235 |
236 | ); 237 | } 238 | 239 | Core.propTypes = { 240 | x: PropTypes.number, 241 | y: PropTypes.number, 242 | width: PropTypes.number, 243 | height: PropTypes.number, 244 | getRef: PropTypes.any, 245 | item: PropTypes.object, 246 | assetsPath: PropTypes.string, 247 | onRemoveItem: PropTypes.func, 248 | username: PropTypes.string, 249 | title: PropTypes.string, 250 | token: PropTypes.string, 251 | presentationMode: PropTypes.bool, 252 | slides: PropTypes.array, 253 | }; 254 | 255 | MoveResize.propTypes = { 256 | ID: PropTypes.string, 257 | item: PropTypes.object, 258 | onChangePosition: PropTypes.func, 259 | onChangeSize: PropTypes.func, 260 | onSetEditMode: PropTypes.func, 261 | presentationMode: PropTypes.bool, 262 | assetsPath: PropTypes.string, 263 | onRemoveItem: PropTypes.func, 264 | username: PropTypes.string, 265 | title: PropTypes.string, 266 | token: PropTypes.string, 267 | slides: PropTypes.array, 268 | }; 269 | 270 | export function mapDispatchToProps(dispatch) { 271 | return { 272 | onRemoveItem: id => dispatch(removeItem(id)), 273 | onChangePosition: (id, position) => dispatch(changeItemPosition(id, position)), 274 | onChangeSize: (id, position) => dispatch(changeItemSize(id, position)), 275 | onSetEditMode: (id, edit) => dispatch(setEditMode(id, edit)), 276 | }; 277 | } 278 | 279 | export default connect( 280 | (state, ownProps) => ({ 281 | item: getItems(state).find(itm => itm.ID === ownProps.ID), 282 | presentationMode: getPresentationMode(state), 283 | assetsPath: getAssetsPath(state), 284 | title: getTitle(state), 285 | username: state.keycloak.userToken.cern_upn, 286 | token: state.keycloak.instance.token, 287 | slides: state.deck.slides, 288 | // this makes the string way smaller and so way faster to search for the image name in the deleter 289 | }), 290 | mapDispatchToProps 291 | )(MoveResize); 292 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/components/text/RenderHtml.css: -------------------------------------------------------------------------------- 1 | .bigger-text > h1{ 2 | font-size: 3em; 3 | line-height: 1.5; 4 | margin: 0 0 0; 5 | } 6 | .bigger-text > h2{ 7 | font-size: 2.25em; 8 | line-height: 1.5; 9 | margin: 0 0 0; 10 | } 11 | .bigger-text > h3{ 12 | font-size: 1.755em; 13 | line-height: 1.5; 14 | margin: 0 0 0; 15 | } 16 | .bigger-text > p{ 17 | font-size: 1.3em; 18 | line-height: 1.5; 19 | margin: 0 0 0; 20 | } 21 | /* .bigger-text { */ 22 | /* line-height: 0.3; */ 23 | /* margin: 0 0 0; */ 24 | /* } */ 25 | /* 150% larger text */ 26 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/components/text/RenderHtml.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ReactHtmlParser from 'react-html-parser'; 4 | import './RenderHtml.css'; 5 | 6 | export default function RenderHtml({text}) { 7 | let txt = text; 8 | // how to handle empty text in text editor 9 | const test = txt.split('

\n').join(''); 10 | if (test === '') { 11 | txt = '

Double Click to Edit

'; 12 | } 13 | 14 | return
{ReactHtmlParser(txt)}
; 15 | } 16 | 17 | RenderHtml.propTypes = { 18 | text: PropTypes.string, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/components/text/TextEditor.css: -------------------------------------------------------------------------------- 1 | .tox-notification {display: none !important;} 2 | 3 | 4 | .text-editor { 5 | /* min-height: 7!important; */ 6 | } 7 | 8 | /* overwriting some default values */ 9 | .public-DraftStyleDefault-block { 10 | margin: 0 0 0 !important; 11 | /* line-height: 1.5 !important; */ 12 | line-height: normal !important; 13 | /* height: 10em; */ 14 | /* font-family: 'Courier New'; this changes only the the style in the editor */ 15 | } 16 | 17 | /* editorClassName */ 18 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/components/text/TextEditor.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {connect} from 'react-redux'; 4 | import {Editor} from 'react-draft-wysiwyg'; 5 | import {EditorState, convertToRaw, ContentState} from 'draft-js'; 6 | import draftToHtml from 'draftjs-to-html'; 7 | import htmlToDraft from 'html-to-draftjs'; 8 | import {Button, Popup} from 'semantic-ui-react'; 9 | import {editData, setEditMode} from '../../../redux-store/DeckReducer/actions'; 10 | import {editorConfig} from './editorConfig'; 11 | import './TextEditor.css'; 12 | import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'; 13 | 14 | function SaveButton({saveHandler}) { 15 | // add Custom Save button in the toolbar of draft.js 16 | return ( 17 | 20 | } 21 | mouseEnterDelay={300} // ?! not very important just an effect to delay the showing of the popup 22 | content="Save Text!" 23 | basic 24 | /> 25 | ); 26 | } 27 | SaveButton.propTypes = { 28 | saveHandler: PropTypes.func, 29 | }; 30 | 31 | const TextEditor = ({initialData, ID, onEditData, onSetEditMode}) => { 32 | const saveHandler = () => { 33 | const myhtml = draftToHtml(convertToRaw(editorState.getCurrentContent())); 34 | // save in redux state 35 | onEditData(ID, myhtml); 36 | // turn edit mode off 37 | onSetEditMode(ID, false); 38 | }; 39 | 40 | const handleEditorChange = newState => { 41 | setEditorState(newState); 42 | // const myhtml = draftToHtml(convertToRaw(newState.getCurrentContent())); 43 | // onChange(myhtml); 44 | }; 45 | const contentBlock = htmlToDraft(initialData); 46 | const [editorState, setEditorState] = useState( 47 | EditorState.createWithContent(ContentState.createFromBlockArray(contentBlock.contentBlocks)) 48 | ); 49 | // const defaultEditorState = { 50 | // 'font-family': 'Courier New', 51 | // } 52 | // add support for Ctrl+s or Cmd+s for saving the text 53 | // docs: https://stackoverflow.com/questions/42311815/how-to-create-custom-key-bindings-in-draft-js 54 | return ( 55 |
56 | ]} 65 | onEditorStateChange={handleEditorChange} 66 | /> 67 |
68 | ); 69 | }; 70 | 71 | TextEditor.propTypes = { 72 | initialData: PropTypes.string, 73 | ID: PropTypes.string, 74 | onEditData: PropTypes.func, 75 | onSetEditMode: PropTypes.func, 76 | }; 77 | 78 | function mapDispatchToProps(dispatch) { 79 | return { 80 | onEditData: (id, data) => dispatch(editData(id, data)), 81 | onSetEditMode: (id, edit) => dispatch(setEditMode(id, edit)), 82 | }; 83 | } 84 | 85 | export default connect(null, mapDispatchToProps)(TextEditor); 86 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/components/text/editorConfig.js: -------------------------------------------------------------------------------- 1 | // For more configuration information visit: 2 | // https://jpuri.github.io/react-draft-wysiwyg/#/docs 3 | 4 | export const editorConfig = { 5 | options: [ 6 | 'inline', 7 | 'blockType', 8 | 'fontSize', 9 | 'fontFamily', 10 | 'list', 11 | 'link', 12 | 'textAlign', 13 | 'colorPicker', 14 | // 'embedded', 15 | 'emoji', 16 | 'remove', 17 | 'history', 18 | ], 19 | inline: { 20 | options: ['bold', 'italic', 'underline', 'strikethrough'], 21 | }, 22 | blockType: { 23 | options: ['Normal', 'H1', 'H2', 'H3', 'H4'], 24 | }, 25 | fontSize: { 26 | options: [20, 30, 40, 50, 60, 70, 80, 90], 27 | }, 28 | fontFamily: { 29 | options: [ 30 | 'Arial', 31 | 'Garamond', 32 | 'Helvetica', 33 | 'Roboto', 34 | 'Times New Roman', 35 | 'Times', 36 | 'Courier New', 37 | 'Courier', 38 | 'Verdana', 39 | 'Palatino', 40 | 'Georgia', 41 | 'Impact', 42 | 'Tahoma', 43 | 'Baskerville', 44 | 'Futura', 45 | ], 46 | }, 47 | colorPicker: { 48 | colors: [ 49 | 'rgb(0, 83, 161)', // this is CERN's default color 50 | 'rgb(97,189,109)', 51 | 'rgb(26,188,156)', 52 | 'rgb(84,172,210)', 53 | 'rgb(44,130,201)', 54 | 'rgb(147,101,184)', 55 | 'rgb(71,85,119)', 56 | 'rgb(204,204,204)', 57 | 'rgb(65,168,95)', 58 | 'rgb(0,168,133)', 59 | 'rgb(61,142,185)', 60 | 'rgb(41,105,176)', 61 | 'rgb(85,57,130)', 62 | 'rgb(40,50,78)', 63 | 'rgb(0,0,0)', 64 | 'rgb(247,218,100)', 65 | 'rgb(251,160,38)', 66 | 'rgb(235,107,86)', 67 | 'rgb(226,80,65)', 68 | 'rgb(163,143,132)', 69 | 'rgb(239,239,239)', 70 | 'rgb(255,255,255)', 71 | 'rgb(250,197,28)', 72 | 'rgb(243,121,52)', 73 | 'rgb(209,72,65)', 74 | 'rgb(184,49,47)', 75 | 'rgb(124,112,107)', 76 | 'rgb(209,213,216)', 77 | ], 78 | }, 79 | link: { 80 | defaultTargetOption: '_blank', // this makes default opening the link in another tab 81 | }, 82 | emoji: { 83 | emojis: [ 84 | '😀', 85 | '😁', 86 | '😂', 87 | '😃', 88 | '😉', 89 | '😋', 90 | '😎', 91 | '😍', 92 | '😗', 93 | '🤗', 94 | '🤔', 95 | '😣', 96 | '😫', 97 | '😴', 98 | '😌', 99 | '🤓', 100 | '😛', 101 | '😜', 102 | '😠', 103 | '😇', 104 | '😷', 105 | '😈', 106 | '👻', 107 | '😺', 108 | '😸', 109 | '😹', 110 | '😻', 111 | '😼', 112 | '😽', 113 | '🙀', 114 | '🙈', 115 | '🙉', 116 | '🙊', 117 | '👼', 118 | '👮', 119 | '🕵', 120 | '💂', 121 | '👳', 122 | '🎅', 123 | '👸', 124 | '👰', 125 | '👲', 126 | '🙍', 127 | '🙇', 128 | '🚶', 129 | '🏃', 130 | '💃', 131 | '⛷', 132 | '🏂', 133 | '🏌', 134 | '🏄', 135 | '🚣', 136 | '🏊', 137 | '⛹', 138 | '🏋', 139 | '🚴', 140 | '👫', 141 | '💪', 142 | '👈', 143 | '👉', 144 | '👉', 145 | '👆', 146 | '🖕', 147 | '👇', 148 | '🖖', 149 | '🤘', 150 | '🖐', 151 | '👌', 152 | '👍', 153 | '👎', 154 | '✊', 155 | '👊', 156 | '👏', 157 | '🙌', 158 | '🙏', 159 | '🐵', 160 | '🐶', 161 | '🐇', 162 | '🐥', 163 | '🐸', 164 | '🐌', 165 | '🐛', 166 | '🐜', 167 | '🐝', 168 | '🍉', 169 | '🍄', 170 | '🍔', 171 | '🍤', 172 | '🍨', 173 | '🍪', 174 | '🎂', 175 | '🍰', 176 | '🍾', 177 | '🍷', 178 | '🍸', 179 | '🍺', 180 | '🌍', 181 | '🚑', 182 | '⏰', 183 | '🌙', 184 | '🌝', 185 | '🌞', 186 | '⭐', 187 | '🌟', 188 | '🌠', 189 | '🌨', 190 | '🌩', 191 | '⛄', 192 | '🔥', 193 | '🎄', 194 | '🎈', 195 | '🎉', 196 | '🎊', 197 | '🎁', 198 | '🎗', 199 | '🏀', 200 | '🏈', 201 | '🎲', 202 | '🔇', 203 | '🔈', 204 | '📣', 205 | '🔔', 206 | '🎵', 207 | '🎷', 208 | '💰', 209 | '🖊', 210 | '📅', 211 | '✅', 212 | '❎', 213 | '💯', 214 | ], 215 | }, 216 | }; 217 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/components/text/index.css: -------------------------------------------------------------------------------- 1 | 2 | /* .text-style:hover { 3 | border-color: #66afe9; 4 | box-shadow: 0 0 8px rgb(0, 140, 255); 5 | } */ 6 | 7 | .hidden { 8 | display:none; 9 | } 10 | 11 | .editor { 12 | /* width: 30em; 13 | height: 10em; */ 14 | /* text-align: center; */ 15 | /* margin: 0 auto; */ 16 | } 17 | 18 | /* .render-text :hover { 19 | border-color: #66afe9; 20 | box-shadow: 0 0 8px rgb(0, 140, 255); 21 | } */ 22 | .fit-text { 23 | display: block; 24 | overflow: auto; 25 | } -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/components/text/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {connect} from 'react-redux'; 4 | import TextEditor from './TextEditor'; 5 | import RenderHtml from './RenderHtml'; 6 | import {getItems} from '../../../redux-store/DeckReducer/selectors'; 7 | import {getPresentationMode} from '../../../redux-store/PresentationReducer/selectors'; 8 | import './index.css'; 9 | 10 | const Text = ({ID, itemsArray, presentationMode}) => { 11 | const item = itemsArray.find(itm => itm.ID === ID); 12 | const {Edit, Data} = item; 13 | // in Presentation Mode the text shouldn't be editable any more 14 | return ( 15 |
16 | {presentationMode ? ( 17 | 18 | ) : ( 19 |
20 | {Edit ? : } 21 |
22 | )} 23 |
24 | ); 25 | }; 26 | 27 | Text.propTypes = { 28 | ID: PropTypes.string, 29 | itemsArray: PropTypes.array, 30 | presentationMode: PropTypes.bool, 31 | }; 32 | 33 | export default connect( 34 | state => ({ 35 | itemsArray: getItems(state), 36 | presentationMode: getPresentationMode(state), 37 | }), 38 | null 39 | )(Text); 40 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/Editor/styles.js: -------------------------------------------------------------------------------- 1 | export const thumbsContainer = { 2 | display: 'flex', 3 | flexDirection: 'row', 4 | flexWrap: 'wrap', 5 | marginTop: 16, 6 | }; 7 | 8 | export const thumb = { 9 | display: 'inline-flex', 10 | borderRadius: 2, 11 | border: '1px solid #eaeaea', 12 | marginBottom: 8, 13 | marginRight: 8, 14 | width: 100, 15 | height: 100, 16 | padding: 4, 17 | boxSizing: 'border-box', 18 | }; 19 | 20 | export const thumbInner = { 21 | display: 'flex', 22 | minWidth: 0, 23 | overflow: 'hidden', 24 | }; 25 | 26 | export const img = { 27 | display: 'block', 28 | width: 'auto', 29 | height: '100%', 30 | }; 31 | export const baseStyle = { 32 | flex: 1, 33 | display: 'flex', 34 | flexDirection: 'column', 35 | alignItems: 'center', 36 | padding: '20px', 37 | borderWidth: 2, 38 | borderRadius: 2, 39 | borderColor: '#eeeeee', 40 | borderStyle: 'dashed', 41 | backgroundColor: '#fafafa', 42 | color: '#bdbdbd', 43 | outline: 'none', 44 | transition: 'border .24s ease-in-out', 45 | }; 46 | 47 | export const activeStyle = { 48 | borderColor: '#2196f3', 49 | }; 50 | 51 | export const acceptStyle = { 52 | borderColor: '#00e676', 53 | }; 54 | 55 | export const rejectStyle = { 56 | borderColor: '#ff1744', 57 | }; 58 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/LoadPresentation/index.css: -------------------------------------------------------------------------------- 1 | .dropzone { 2 | text-align: center; 3 | } 4 | 5 | .loadPresentation { 6 | margin-left: 0; 7 | color: black !important; 8 | } -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/LoadPresentation/index.js: -------------------------------------------------------------------------------- 1 | import React, {useMemo, useState} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import {uploadPresentation} from '../../utils/requests'; 5 | import {useDropzone} from 'react-dropzone'; 6 | import {Modal, Button, Icon} from 'semantic-ui-react'; 7 | import {getLoadRequest, getAssetsPath} from '../redux-store/PresentationReducer/selectors'; 8 | import {setLoadRequest, loadState, setIsReady} from '../redux-store/PresentationReducer/actions'; 9 | import {loadDeckState} from '../redux-store/DeckReducer/actions'; 10 | import {baseStyle, activeStyle, acceptStyle, rejectStyle, thumbsContainer} from '../Editor/styles'; 11 | import history from '../../utils/history'; 12 | import './index.css'; 13 | // load will be a post request 14 | // make possible to upload only one presentation at a point 15 | // when i load the state how can i put it in my state? 16 | // I do something wrong with the image extracting check it 17 | function LoadPresentation({ 18 | onLoadRequest, 19 | loadRequest, 20 | onSetIsReady, 21 | assetsPath, 22 | onLoadState, 23 | onLoadDeckState, 24 | token, 25 | username, 26 | }) { 27 | const { 28 | acceptedFiles, 29 | getRootProps, 30 | getInputProps, 31 | isDragActive, 32 | isDragAccept, 33 | isDragReject, 34 | } = useDropzone({ 35 | accept: '.slides', 36 | multiple: false, 37 | }); 38 | const [loadingIndicator, setLoading] = useState(false); 39 | 40 | const style = useMemo( 41 | () => ({ 42 | ...baseStyle, 43 | ...(isDragActive ? activeStyle : {}), 44 | ...(isDragAccept ? acceptStyle : {}), 45 | ...(isDragReject ? rejectStyle : {}), 46 | }), 47 | [isDragActive, isDragAccept, isDragReject] 48 | ); 49 | // use a load endpoint in the server 50 | // so get the .slides 51 | // send it as post in server 52 | // server extracts, saves the images 53 | // send as response the stringified state 54 | // frontend sets the state that it got using a redux dispatch action 55 | const sendLoadRequest = e => { 56 | setLoading(true); 57 | e.preventDefault(); 58 | uploadPresentation(assetsPath, username, acceptedFiles, token) 59 | .then(response => { 60 | // extract the state information and call the loadstate action to copy the whole obj to the current state 61 | onLoadDeckState(response.data.state.deck); 62 | onLoadState(response.data.state.presentation); 63 | // set url 64 | const {title} = response.data.state.presentation; 65 | history.push(`/edit/${username}/${title}/`); 66 | // now that the request is done I can say I am ready for and can move from landing page 67 | onLoadRequest(); 68 | onSetIsReady(); 69 | setLoading(false); 70 | }) 71 | .catch(err => { 72 | console.log('Something went wrong', err); 73 | // fail screen 74 | }); 75 | }; 76 | const onCancelHandler = e => { 77 | e.preventDefault(); 78 | onLoadRequest(); 79 | }; 80 | const acceptedFilesItems = acceptedFiles.map(file => ( 81 | // presentation icon 82 |
83 |

Presentation:

84 | {file.path} - {file.size} bytes 85 |
86 | )); 87 | // return a window to upload a file from local computer 88 | // check that it is .slides and send it to load endpoint and try to open and process it, if it is not processed show an error message 89 | return ( 90 |
91 | 92 | Upload Presentation 93 | 94 |
95 |
96 | 97 |

Drag 'n' drop a presentation file here, or click to select file

98 | (Only *.slides presentations will be accepted) 99 |
100 | 101 |
102 |
103 | 104 | 107 | 115 | 116 |
117 |
118 | ); 119 | } 120 | 121 | LoadPresentation.propTypes = { 122 | onLoadRequest: PropTypes.func, 123 | loadRequest: PropTypes.bool, 124 | onSetIsReady: PropTypes.func, 125 | assetsPath: PropTypes.string, 126 | onLoadState: PropTypes.func, 127 | onLoadDeckState: PropTypes.func, 128 | token: PropTypes.string, 129 | username: PropTypes.string, 130 | }; 131 | 132 | export function mapDispatchToProps(dispatch) { 133 | return { 134 | onLoadRequest: () => dispatch(setLoadRequest(false)), 135 | onLoadState: state => dispatch(loadState(state)), 136 | onLoadDeckState: state => dispatch(loadDeckState(state)), 137 | onSetIsReady: () => dispatch(setIsReady(true)), 138 | }; 139 | } 140 | 141 | export default connect( 142 | state => ({ 143 | loadRequest: getLoadRequest(state), 144 | assetsPath: getAssetsPath(state), 145 | token: state.keycloak.instance.token, 146 | username: state.keycloak.userToken.cern_upn, 147 | }), 148 | mapDispatchToProps 149 | )(LoadPresentation); 150 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/NotFoundPage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Link} from 'react-router-dom'; 3 | import PageNotFound from '../../images/PageNotFound.jpg'; 4 | 5 | class NotFoundPage extends React.Component { 6 | render() { 7 | return ( 8 |
9 | 404-page-not-found 10 |

11 | Go to Home 12 |

13 |
14 | ); 15 | } 16 | } 17 | 18 | export default NotFoundPage; 19 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/SavePresentation/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {connect} from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import {saveAs} from 'file-saver'; 5 | import {setSaveRequest, setTitle} from '../redux-store/PresentationReducer/actions'; 6 | import {getTitle} from '../redux-store/PresentationReducer/selectors'; 7 | import history from '../../utils/history'; 8 | 9 | import AlertForSaving from '../Alerts/AlertForSaving'; 10 | import success from '../Alerts/success'; 11 | import fail from '../Alerts/fail'; 12 | import {savePresentation, renamePresentation} from '../../utils/requests'; 13 | const zip = require('jszip')(); 14 | 15 | // i first need to init and load 16 | 17 | // when i load the state how can i put it in my state? 18 | // DONT STORE IN THE CLIENT SIDE, STORE IN THE SERVER SIDE IN EOS USING FS 19 | 20 | // create the presentation.JSON 21 | // create the folder with the images 22 | // put them all together 23 | // zip it as a blob 24 | // add it in formdata 25 | // give it to phoenix 26 | 27 | /* 28 | SO 29 | when from browser 30 | send request to server 31 | server --> responds with the blob 32 | frontend uses saveas to give the blob to the user 33 | */ 34 | 35 | // returns true or false if everything with the renaming in the background was successful 36 | async function renamePres(assetsPath, username, oldTitle, newTitle, token) { 37 | if (oldTitle === newTitle) { 38 | // then no rename 39 | return true; 40 | } 41 | const renaming = renamePresentation(assetsPath, username, oldTitle, newTitle, token).then(res => { 42 | if (res.data === 'Already Exists') { 43 | // already exists 44 | return false; 45 | } 46 | return true; 47 | }); 48 | return renaming.then(() => true).catch(() => false); 49 | } 50 | 51 | async function sendSaveRequest(assetsPath, stateStringified, newTitle, token, user) { 52 | const url = `${assetsPath}/presentation/save`; 53 | savePresentation(url, stateStringified, user, token) 54 | .then(response => response.status === 200 && response.data) 55 | .then(fileAsBuffer => { 56 | // create the alert so the user can select a filename 57 | // make a toast here that is successful 58 | success('Your work has been saved'); 59 | // here i have an arraybuffer 60 | const blob = new Blob([fileAsBuffer]); 61 | return saveAs(blob, `${newTitle}.slides`); 62 | }) 63 | .catch(error => { 64 | console.log(error); 65 | // i couldn't make the blob in the backend 66 | // create an alert for the user 67 | fail(`${newTitle}.slides file creation failed...`); 68 | }); 69 | } 70 | 71 | function SavePresentation({stateStringified, onSaveRequest, title, onSetTitle, user, token}) { 72 | // use a save endpoint in the server 73 | // title and uuid and savereq can be extracted from state 74 | const obj = JSON.parse(stateStringified); 75 | const {assetsPath, saveRequest} = obj.presentation; 76 | 77 | // it is not working it gives the backend an empty formdata 78 | const sendToPhoenix = () => { 79 | zip.file('presentation.JSON', stateStringified); 80 | zip 81 | .generateAsync({ 82 | type: 'blob', 83 | mimeType: 'application/slides', 84 | }) 85 | .then(content => { 86 | console.log('blob ...', typeof content, content.arrayBuffer()); 87 | const formData = new FormData(); 88 | formData.append('content', content); 89 | 90 | const req = new Map(); 91 | 92 | req.set('event', 'save'); 93 | req.set('slidesFile', formData); 94 | 95 | window.parent.postMessage( 96 | req, 97 | '*' // consider changing '*' to specific target 98 | ); 99 | onSaveRequest(); 100 | }); 101 | }; 102 | 103 | const sendToUser = () => { 104 | // make a blob and save it locally give it to user for download 105 | // I need to make the body of the post request and send a request that includes the params 106 | AlertForSaving(title).then(newTitle => { 107 | if (!newTitle) { 108 | onSaveRequest(); 109 | return; 110 | } 111 | // check if I can rename in the background first 112 | renamePres(assetsPath, user, title, newTitle, token).then(res => { 113 | if (!res) { 114 | // failed 115 | fail('A Presentation with the same name already exists'); 116 | onSaveRequest(); 117 | } else { 118 | // set new filename as title in the presentation 119 | onSetTitle(newTitle); 120 | // state is not gonna update in time 121 | const newObj = JSON.parse(stateStringified); 122 | newObj.presentation.title = newTitle; 123 | newObj.deck.currentSlide = 0; 124 | delete newObj.keycloak; 125 | delete newObj.router; 126 | const newStateStringified = JSON.stringify(newObj); 127 | sendSaveRequest(assetsPath, newStateStringified, newTitle, token, user).then(() => { 128 | onSaveRequest(); 129 | // push the new title in the URL bar 130 | history.push(`/edit/${user}/${newTitle}/`); 131 | }); 132 | } 133 | }); 134 | }); 135 | }; 136 | 137 | const Save = () => { 138 | // First of all, see if I am using Browser of Phoenix 139 | if (JSON.parse(stateStringified).presentation.isPhoenixMode) { 140 | sendToPhoenix(); 141 | } else { 142 | sendToUser(); 143 | } 144 | }; 145 | if (saveRequest) Save(); 146 | 147 | return
; 148 | } 149 | 150 | SavePresentation.propTypes = { 151 | stateStringified: PropTypes.string, 152 | onSaveRequest: PropTypes.func, 153 | onSetTitle: PropTypes.func, 154 | title: PropTypes.string, 155 | user: PropTypes.string, 156 | token: PropTypes.string, 157 | }; 158 | 159 | export function mapDispatchToProps(dispatch) { 160 | return { 161 | onSaveRequest: () => dispatch(setSaveRequest(false)), 162 | onSetTitle: title => dispatch(setTitle(title)), 163 | }; 164 | } 165 | 166 | export default connect( 167 | state => ({ 168 | stateStringified: JSON.stringify(state), 169 | title: getTitle(state), 170 | user: state.keycloak.userToken.cern_upn, 171 | token: state.keycloak.instance.token, 172 | }), 173 | mapDispatchToProps 174 | )(SavePresentation); 175 | 176 | // const sendToUser = () => { 177 | // // I need to make the body of the post request and send a request that includes the params 178 | // const url = `${assetsPath}/save`; 179 | // post( 180 | // url, 181 | // { state: stateStringified }, 182 | // { headers: { 'Content-Type': 'application/json' }, Authorization: `Bearer ${token}` }, 183 | // ) 184 | // .then(response => { 185 | // // console.log('response:', response); 186 | // if (response.status === 200) { 187 | // // notify with success 188 | // addToast(`Saved successfully! 😊`, { 189 | // appearance: 'success', 190 | // autoDismiss: true, 191 | // }); 192 | // } 193 | // }) 194 | // .catch(error => { 195 | // console.log(error); 196 | // addToast('Saving Failed...', { 197 | // appearance: 'error', 198 | // autoDismiss: true, 199 | // }); 200 | // }); 201 | // onSaveRequest(); 202 | // }; 203 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/StyleComponent/index.js: -------------------------------------------------------------------------------- 1 | // it will be listening for styleRequest and will open a modal to select a background color 2 | import React, {useState} from 'react'; 3 | import {connect} from 'react-redux'; 4 | import PropTypes from 'prop-types'; 5 | import {SketchPicker} from 'react-color'; 6 | import {Modal, Button, Icon} from 'semantic-ui-react'; 7 | import {setStyleRequest, setBackgroundColor} from '../redux-store/PresentationReducer/actions'; 8 | import {getStyleRequest, getBackgroundColor} from '../redux-store/PresentationReducer/selectors'; 9 | 10 | const allColors = [ 11 | {color: '#0053A1', title: 'CERN'}, // added CERN's default official color 12 | '#D0021B', 13 | '#F5A623', 14 | '#F8E71C', 15 | '#8B572A', 16 | '#7ED321', 17 | '#417505', 18 | '#BD10E0', 19 | '#9013FE', 20 | '#4A90E2', 21 | '#50E3C2', 22 | '#B8E986', 23 | '#000000', 24 | '#4A4A4A', 25 | '#9B9B9B', 26 | '#FFFFFF', 27 | ]; 28 | 29 | function StyleComponent({styleRequest, onStyleRequest, onColorChange, style}) { 30 | const [backgroundColor, _setBackgroundColor] = useState(style); 31 | const handleChangeColor = color => { 32 | _setBackgroundColor(color.hex); 33 | }; 34 | 35 | const sendStyleRequest = () => { 36 | onColorChange(backgroundColor); 37 | onStyleRequest(); 38 | }; 39 | const onCancelHandler = () => { 40 | onStyleRequest(); 41 | }; 42 | // ask if this with the colored letters is good or not 43 | return ( 44 | 45 | 46 | Choose a background Colour 47 | 48 | 49 | 55 | 56 | 57 | 60 | 63 | 64 | 65 | ); 66 | } 67 | 68 | StyleComponent.propTypes = { 69 | styleRequest: PropTypes.bool, 70 | onStyleRequest: PropTypes.func, 71 | onColorChange: PropTypes.func, 72 | style: PropTypes.string, 73 | }; 74 | 75 | export function mapDispatchToProps(dispatch) { 76 | return { 77 | onStyleRequest: () => dispatch(setStyleRequest(false)), 78 | onColorChange: color => dispatch(setBackgroundColor(color)), 79 | }; 80 | } 81 | 82 | export default connect( 83 | state => ({ 84 | styleRequest: getStyleRequest(state), 85 | style: getBackgroundColor(state), 86 | }), 87 | mapDispatchToProps 88 | )(StyleComponent); 89 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/ThemeSelector/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {connect} from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import {Modal, Card, Image} from 'semantic-ui-react'; 5 | import { 6 | themeRequest, 7 | setTheme, 8 | setBackgroundColor, 9 | } from '../redux-store/PresentationReducer/actions'; 10 | import {getThemeRequest} from '../redux-store/PresentationReducer/selectors'; 11 | import config from '../../config'; 12 | 13 | const baseUrl = `${config.assetsPath}/public/themes`; 14 | 15 | const items = [ 16 | { 17 | name: 'CERN 1', 18 | src: `${baseUrl}/cern1.png`, 19 | }, 20 | { 21 | name: 'CERN 2', 22 | src: `${baseUrl}/cern2.png`, 23 | }, 24 | { 25 | name: 'CERN 3', 26 | src: `${baseUrl}/cern3.png`, 27 | }, 28 | { 29 | name: 'CERN 4', 30 | src: `${baseUrl}/cern4.png`, 31 | }, 32 | { 33 | name: 'CERN 5', 34 | src: `${baseUrl}/cern5.png`, 35 | }, 36 | { 37 | name: 'CERN 6', 38 | src: `${baseUrl}/cern6.png`, 39 | }, 40 | ]; 41 | 42 | function ThemeSelector({themeRequest, onThemeRequest, onSetTheme, onSetBackgroundColor}) { 43 | const handleClickTheme = name => { 44 | onSetTheme(name); 45 | if (name === 'CERN 5' || name === 'CERN 6') { 46 | onSetBackgroundColor('#FFFFFF'); // make background white in theme 5 and 6 47 | } else { 48 | onSetBackgroundColor('#0053A1'); 49 | } 50 | onThemeRequest(); 51 | }; 52 | 53 | const Item = (name, src) => ( 54 | handleClickTheme(name)}> 55 | 56 | 57 | 58 | ); 59 | 60 | return ( 61 | 62 | Select a Theme 63 | 64 | {items.map(i => Item(i.name, i.src))} 65 | 66 | 67 | ); 68 | } 69 | 70 | ThemeSelector.propTypes = { 71 | themeRequest: PropTypes.bool, 72 | onThemeRequest: PropTypes.func, 73 | onSetTheme: PropTypes.func, 74 | onSetBackgroundColor: PropTypes.func, 75 | }; 76 | 77 | export function mapDispatchToProps(dispatch) { 78 | return { 79 | onThemeRequest: () => dispatch(themeRequest(false)), 80 | onSetTheme: theme => dispatch(setTheme(theme)), 81 | onSetBackgroundColor: color => dispatch(setBackgroundColor(color)), 82 | }; 83 | } 84 | 85 | export default connect( 86 | state => ({ 87 | themeRequest: getThemeRequest(state), 88 | }), 89 | mapDispatchToProps 90 | )(ThemeSelector); 91 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/redux-store/DeckReducer/actions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_SLIDE, 3 | REMOVE_SLIDE, 4 | CLONE_SLIDE, 5 | CHANGE_SLIDE, 6 | ADD_ITEM, 7 | REMOVE_ITEM, 8 | CHANGE_ITEM_POSITION, 9 | CHANGE_ITEM_SIZE, 10 | EDIT_DATA, 11 | SET_EDIT_MODE, 12 | LOAD_DECK_STATE, 13 | } from './constants'; 14 | 15 | import { position, size, Item, Deck } from './definitions'; 16 | 17 | export const addSlide = () => ({ 18 | type: ADD_SLIDE, 19 | }) as const; 20 | 21 | export const removeSlide = () =>({ 22 | type: REMOVE_SLIDE, 23 | }) as const; 24 | 25 | export const cloneSlide = () => ({ 26 | type: CLONE_SLIDE, 27 | }) as const; 28 | 29 | export const changeSlide = ( 30 | payload: { 31 | action: string, 32 | isFirstRendering: boolean, 33 | location: { 34 | hash: string, 35 | pathname: string, 36 | search: string, 37 | state: any 38 | } 39 | }) => ({ 40 | type: CHANGE_SLIDE, 41 | payload, 42 | }) as const; 43 | 44 | export const addItem = (item: Item) => ({ 45 | type: ADD_ITEM, 46 | item, 47 | }) as const; 48 | 49 | export const removeItem = (id: string) => ({ 50 | type: REMOVE_ITEM, 51 | id, 52 | }) as const; 53 | 54 | export const changeItemPosition = (id: string, pos: position) => ({ 55 | type: CHANGE_ITEM_POSITION, 56 | id, 57 | position: pos, 58 | }) as const; 59 | 60 | export const changeItemSize = (id: string, siz: size) => ({ 61 | type: CHANGE_ITEM_SIZE, 62 | id, 63 | size: siz, 64 | }) as const; 65 | 66 | export const editData = (id: string, data: string) => ({ 67 | type: EDIT_DATA, 68 | id, 69 | data, 70 | }) as const; 71 | 72 | export const setEditMode = (id: string, edit: boolean) => ({ 73 | type: SET_EDIT_MODE, 74 | id, 75 | edit, 76 | }) as const; 77 | 78 | export const loadDeckState = (state: Deck) => ({ 79 | type: LOAD_DECK_STATE, 80 | state, 81 | }) as const; 82 | 83 | export type Action = ReturnType< 84 | typeof addSlide | typeof removeSlide | typeof cloneSlide | typeof changeSlide | 85 | typeof addItem | typeof removeItem | typeof changeItemPosition | typeof changeItemSize | 86 | typeof editData | typeof setEditMode | typeof loadDeckState 87 | > 88 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/redux-store/DeckReducer/constants.ts: -------------------------------------------------------------------------------- 1 | export const ADD_SLIDE = 'Slides/Presentation/ADD_SLIDE'; 2 | export const REMOVE_SLIDE = 'Slides/Presentation/REMOVE_SLIDE'; 3 | export const CLONE_SLIDE = 'Slides/Presentation/CLONE_SLIDE'; 4 | export const CHANGE_SLIDE = '@@router/LOCATION_CHANGE'; 5 | 6 | export const ADD_ITEM = 'Slides/Presentation/ADD_ITEM'; 7 | export const REMOVE_ITEM = 'Slides/Presentation/REMOVE_ITEM'; 8 | export const CHANGE_ITEM_POSITION = 'Slides/Presentation/CHANGE_ITEM_POSITION'; 9 | export const CHANGE_ITEM_SIZE = 'Slides/Presentation/CHANGE_ITEM_SIZE'; 10 | export const EDIT_DATA = 'Slides/Presentation/EDIT_DATA'; 11 | export const SET_EDIT_MODE = 'Slides/Presentation/SET_EDIT_MODE'; 12 | export const LOAD_DECK_STATE = 'Slides/Presentation/LOAD_DECK_STATE'; 13 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/redux-store/DeckReducer/definitions.ts: -------------------------------------------------------------------------------- 1 | const uuidv4 = require("uuid/v4"); 2 | 3 | export type position = { 4 | x: number 5 | y: number 6 | } 7 | export type size = { 8 | width: number 9 | height: number 10 | } 11 | 12 | export const ItemTypes = { 13 | TEXT: 'TEXT', 14 | IMAGE: 'IMAGE', 15 | } 16 | 17 | class baseItem { 18 | ID: string 19 | Position: position 20 | Size: size 21 | constructor() { 22 | this.ID = uuidv4(); 23 | this.Position= { x: 0, y: 0 }; 24 | this.Size = { width: 0, height: 0 }; 25 | } 26 | } 27 | 28 | export class Text extends baseItem { 29 | readonly type: string 30 | Data: string 31 | Edit: boolean 32 | constructor() { 33 | super(); 34 | this.type = ItemTypes.TEXT 35 | this.Data = "

\n"; 36 | this.Edit = false; 37 | this.Position= { x: 0.30, y: 0.40 }; 38 | this.Size = { width: 0.25, height: 0.05 }; 39 | } 40 | } 41 | 42 | export class Image extends baseItem { 43 | readonly type: string 44 | Src: string 45 | constructor(Src: string) { 46 | super(); 47 | this.type = ItemTypes.IMAGE; 48 | this.Src = Src; 49 | this.Position= { x: 0.30, y: 0.25 }; 50 | this.Size = { width: 0.20, height: 0.55 }; 51 | } 52 | } 53 | 54 | export type Item = Text | Image; 55 | 56 | export type Slide = { 57 | ID: string 58 | itemsArray: Array 59 | } 60 | 61 | export type Deck = { 62 | currentSlide: number 63 | slides: Array 64 | } 65 | 66 | export const newItem = (obj: any): Item => { 67 | switch (obj.type) { 68 | case ItemTypes.TEXT: 69 | return new Text(); 70 | case ItemTypes.IMAGE: 71 | const { Src } = obj; 72 | return new Image(Src); 73 | default: 74 | throw new Error('Object type is not TEXT neither IMAGE'); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/redux-store/DeckReducer/reducer.ts: -------------------------------------------------------------------------------- 1 | import produce from 'immer'; 2 | import { deepCopyFunction } from '../../../utils/helperFunctions'; 3 | import { Action } from './actions'; 4 | import { 5 | ADD_SLIDE, 6 | REMOVE_SLIDE, 7 | CHANGE_SLIDE, 8 | ADD_ITEM, 9 | REMOVE_ITEM, 10 | CHANGE_ITEM_POSITION, 11 | CHANGE_ITEM_SIZE, 12 | EDIT_DATA, 13 | SET_EDIT_MODE, 14 | LOAD_DECK_STATE, 15 | CLONE_SLIDE, 16 | } from './constants'; 17 | import { Item, newItem, ItemTypes, Text, Deck, Slide} from './definitions'; 18 | 19 | const uuidv4 = require("uuid/v4"); 20 | 21 | export let initialDeck:Deck = { 22 | currentSlide: 0, 23 | slides: [], 24 | } 25 | 26 | export const newSlide:Slide = { 27 | ID: uuidv4(), 28 | itemsArray: [], 29 | } 30 | 31 | const initialText:Text = { 32 | type: ItemTypes.TEXT, 33 | ID: uuidv4(), 34 | Position: { x: 0.35, y: 0.33 }, 35 | Size: { width: 0.25, height: 0.27 }, 36 | Data: '

TITLE

Author

Date

', 37 | Edit: false 38 | } 39 | 40 | newSlide.itemsArray.push(initialText); 41 | initialDeck.slides.push(newSlide); 42 | 43 | const addTwoBoxes = (slide:Slide) => { 44 | const TitleBox = { 45 | type: ItemTypes.TEXT, 46 | } 47 | const DescriptionBox = { 48 | type: ItemTypes.TEXT, 49 | } 50 | const itm1: Item = newItem(TitleBox); 51 | const itm2: Item = newItem(DescriptionBox); 52 | 53 | // itm1 - Title 54 | itm1.Position = { 55 | x: 0.30, 56 | y: 0.20, 57 | } 58 | itm1.Size = { 59 | width: 0.25, 60 | height: 0.05, 61 | } 62 | 63 | // itm2 - Description 64 | // this weird number comes from this calculation: title's med is: 30 + 25/2 = 42.5 65 | // to reach this percentage and have the texts inline, the description's x + width/2 = 42.5 66 | // width/2 is 20so x is 22.5 or 0.225 67 | itm2.Position = { 68 | x: 0.225, 69 | y: 0.30, 70 | } 71 | itm2.Size = { 72 | width: 0.40, 73 | height: 0.40, 74 | } 75 | slide.itemsArray.push(itm1, itm2); 76 | } 77 | 78 | const DeckState = (state: Deck = initialDeck, action: Action): Deck => 79 | produce(state, (draft:Deck) => { 80 | // eslint-disable-next-line no-console 81 | switch (action.type) { 82 | case ADD_SLIDE: { 83 | const slide:Slide = { 84 | ID: uuidv4(), 85 | itemsArray: [], // add a box that can be image or text 86 | } 87 | addTwoBoxes(slide); 88 | draft.slides.splice(draft.currentSlide + 1, 0, slide); 89 | break; 90 | } 91 | case REMOVE_SLIDE: { 92 | if (draft.slides.length > 1) { 93 | draft.slides.splice(draft.currentSlide, 1); 94 | // eslint-disable-next-line no-alert 95 | } else alert('Not possible to remove the only slide'); 96 | break; 97 | } 98 | case CLONE_SLIDE: { 99 | // create a new blank slide 100 | let slide:Slide = { 101 | ID: uuidv4(), 102 | itemsArray: [], 103 | } 104 | slide.itemsArray = draft.slides[draft.currentSlide].itemsArray.map(currentItem => { 105 | return deepCopyFunction(currentItem); 106 | }); 107 | // push the cloned slide in the slides array 108 | draft.slides.splice(draft.currentSlide + 1, 0, slide); 109 | break; 110 | } 111 | case CHANGE_SLIDE: { 112 | draft.currentSlide = Number(action.payload.location.hash.substr(2)); 113 | break; 114 | } 115 | case ADD_ITEM: { 116 | // user wants to add text or image 117 | // an object with the properties of an image or text 118 | const itm:Item = newItem(action.item); 119 | draft.slides[draft.currentSlide].itemsArray.push(itm); 120 | break; 121 | } 122 | case REMOVE_ITEM: { 123 | const ind:number = draft.slides[draft.currentSlide].itemsArray.findIndex((itm:Item) => itm.ID === action.id); 124 | if (ind === -1) break; // otherwise it will delete the last item 125 | draft.slides[draft.currentSlide].itemsArray.splice(ind, 1); 126 | break; 127 | } 128 | case CHANGE_ITEM_POSITION: { 129 | const ind:number = draft.slides[draft.currentSlide].itemsArray.findIndex((itm:Item) => itm.ID === action.id); 130 | if (ind === -1) break; 131 | draft.slides[draft.currentSlide].itemsArray[ind].Position = {...action.position}; 132 | break; 133 | } 134 | case CHANGE_ITEM_SIZE: { 135 | const ind:number = draft.slides[draft.currentSlide].itemsArray.findIndex((itm:Item) => itm.ID === action.id); 136 | if (ind === -1) break; 137 | // draft.slides[draft.currentSlide].itemsArray[ind].changeSize(action.size); 138 | draft.slides[draft.currentSlide].itemsArray[ind].Size = {...action.size}; 139 | break; 140 | } 141 | case EDIT_DATA: { 142 | const ind:number = draft.slides[draft.currentSlide].itemsArray.findIndex((itm:Item) => itm.ID === action.id); 143 | if (ind === -1) break; 144 | if(draft.slides[draft.currentSlide].itemsArray[ind].type === ItemTypes.IMAGE){ 145 | throw new Error("I got an editDate for an Image"); 146 | } 147 | const currentText = (draft.slides[draft.currentSlide].itemsArray[ind] as Text); 148 | currentText.Data = action.data; 149 | (draft.slides[draft.currentSlide].itemsArray[ind] as Text) = (currentText as Text); 150 | break; 151 | } 152 | case SET_EDIT_MODE: { 153 | const ind:number = draft.slides[draft.currentSlide].itemsArray.findIndex((itm:Item) => itm.ID === action.id); 154 | if (ind === -1) break; 155 | if(draft.slides[draft.currentSlide].itemsArray[ind].type === ItemTypes.IMAGE){ 156 | throw new Error("I got an editMode for an Image"); 157 | } 158 | const currentText = {...(draft.slides[draft.currentSlide].itemsArray[ind] as Text)}; 159 | currentText.Edit = action.edit; 160 | (draft.slides[draft.currentSlide].itemsArray[ind] as Text) = (currentText as Text); 161 | break; 162 | } 163 | case LOAD_DECK_STATE: { 164 | const newDeckState: Deck = { 165 | ...action.state, 166 | } 167 | Object.assign(draft, newDeckState); 168 | break; 169 | } 170 | } 171 | }); 172 | 173 | export default DeckState; 174 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/redux-store/DeckReducer/selectors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Selectors 3 | */ 4 | import { Deck } from './definitions'; 5 | 6 | const getDeck = (state: { deck: Deck; }) => state.deck.slides; 7 | const getCurrentSlide = (state: { deck: Deck; }) => state.deck.currentSlide; 8 | const getItems = (state: { deck: Deck; }) => state.deck.slides[state.deck.currentSlide].itemsArray; 9 | 10 | export { 11 | getDeck, 12 | getCurrentSlide, 13 | getItems, 14 | }; 15 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/redux-store/PresentationReducer/actions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SET_THEME, 3 | SET_TITLE, 4 | IMAGE_UPLOAD_REQUEST, 5 | SET_ASSETS_PATH, 6 | SAVE_REQUEST, 7 | LOAD_REQUEST, 8 | LOAD_STATE, 9 | IS_READY, 10 | STYLE_REQUEST, 11 | BACKGROUND_COLOR, 12 | THEME_REQUEST, 13 | PRESENTATION_MODE, 14 | EXPORT_MODE 15 | } from './constants'; 16 | 17 | export const setTheme = (theme: string) => ({ 18 | type: SET_THEME, 19 | theme, 20 | }) as const; 21 | 22 | export const setTitle = (title: string) => ({ 23 | type: SET_TITLE, 24 | title, 25 | }) as const; 26 | 27 | export const uploadImageRequest = (request: boolean) => ({ 28 | type: IMAGE_UPLOAD_REQUEST, 29 | request, 30 | }) as const; 31 | 32 | export const setAssetsPath = (path: string) => ({ 33 | type: SET_ASSETS_PATH, 34 | path, 35 | }) as const; 36 | 37 | export const setSaveRequest = (request: boolean) => ({ 38 | type: SAVE_REQUEST, 39 | request, 40 | }) as const; 41 | 42 | export const setLoadRequest = (request: boolean) => ({ 43 | type: LOAD_REQUEST, 44 | request, 45 | }) as const; 46 | 47 | // this is a presentation as an object 48 | export const loadState = (state: any) => ({ 49 | type: LOAD_STATE, 50 | state, 51 | }) as const; 52 | 53 | export const setIsReady = (ready: boolean) => ({ 54 | type: IS_READY, 55 | ready, 56 | }) as const; 57 | 58 | export const setStyleRequest = (request: boolean) => ({ 59 | type: STYLE_REQUEST, 60 | request, 61 | }) as const; 62 | 63 | export const setBackgroundColor = (color: string) => ({ 64 | type: BACKGROUND_COLOR, 65 | color, 66 | }) as const; 67 | 68 | export const themeRequest = (request: boolean) => ({ 69 | type: THEME_REQUEST, 70 | request, 71 | }) as const; 72 | 73 | export const setPresentationMode = (mode: boolean) => ({ 74 | type: PRESENTATION_MODE, 75 | mode, 76 | }) as const; 77 | 78 | export const setExportMode = (mode: boolean) => ({ 79 | type: EXPORT_MODE, 80 | mode, 81 | }) as const; 82 | 83 | 84 | export type Action = ReturnType< 85 | typeof setTheme | typeof setTitle | 86 | typeof uploadImageRequest | typeof setAssetsPath | typeof themeRequest | 87 | typeof setSaveRequest | typeof setLoadRequest | typeof loadState | 88 | typeof setIsReady | typeof setStyleRequest | typeof setBackgroundColor | 89 | typeof setPresentationMode | typeof setExportMode 90 | > 91 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/redux-store/PresentationReducer/constants.ts: -------------------------------------------------------------------------------- 1 | export const SET_THEME = 'Slides/Presentation/SET_THEME'; 2 | export const SET_TITLE = 'Slides/Presentation/SET_TITLE'; 3 | export const IMAGE_UPLOAD_REQUEST = 'Slides/Presentation/IMAGE_UPLOAD_REQUEST'; 4 | export const SET_ASSETS_PATH = 'Slides/Presentation/SET_ASSETS_PATH'; 5 | export const SAVE_REQUEST = 'Slides/Presentation/SAVE_REQUEST'; 6 | export const LOAD_REQUEST = 'Slides/Presentation/LOAD_REQUEST'; 7 | export const IS_READY = 'Slides/Presentation/IS_READY'; 8 | export const LOAD_STATE = 'Slides/Presentation/LOAD_STATE'; 9 | export const STYLE_REQUEST = 'Slides/Presentation/STYLE_REQUEST'; 10 | export const BACKGROUND_COLOR = 'Slides/Presentation/BACKGROUND_COLOR'; 11 | export const THEME_REQUEST = 'Slides/Presentation/THEME_REQUEST'; 12 | export const PRESENTATION_MODE = 'Slides/Presentation/PRESENTATION_MODE'; 13 | export const EXPORT_MODE = 'Slides/Presentation/EXPORT_MODE'; 14 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/redux-store/PresentationReducer/definitions.ts: -------------------------------------------------------------------------------- 1 | export const initialState = { 2 | isPhoenixMode: false, // two modes, so bool, is Phoenix or is Browser, not here is DANGEROUS 3 | presentationMode: false, 4 | exportMode: false, 5 | theme: 'CERN 1', 6 | title: '', 7 | backgroundColor: '#0053A1', 8 | isReady: false, 9 | // 10 | assetsPath: '', 11 | // 12 | imgUploadRequest: false, 13 | saveRequest: false, 14 | loadRequest: false, 15 | styleRequest: false, 16 | themeRequest: false, 17 | } 18 | 19 | export type presentationState = typeof initialState; 20 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/redux-store/PresentationReducer/reducer.ts: -------------------------------------------------------------------------------- 1 | import { initialState, presentationState } from './definitions'; 2 | import produce from "immer"; 3 | import { 4 | SET_THEME, 5 | SET_TITLE, 6 | IMAGE_UPLOAD_REQUEST, 7 | SET_ASSETS_PATH, 8 | SAVE_REQUEST, 9 | LOAD_REQUEST, 10 | IS_READY, 11 | LOAD_STATE, 12 | STYLE_REQUEST, 13 | BACKGROUND_COLOR, 14 | THEME_REQUEST, 15 | PRESENTATION_MODE, 16 | EXPORT_MODE 17 | } from './constants'; 18 | import { Action } from './actions'; 19 | 20 | const PresentationReducer = (state: presentationState=initialState, action: Action): presentationState => 21 | produce(state, (draft: presentationState) => { 22 | // eslint-disable-next-line no-console 23 | switch (action.type) { 24 | case SET_THEME: 25 | // now that the theme is set, push first and last slide in the deck if the theming requires it 26 | draft.theme = action.theme; 27 | break; 28 | case SET_TITLE: 29 | draft.title = action.title; 30 | break; 31 | case IMAGE_UPLOAD_REQUEST: 32 | draft.imgUploadRequest = action.request; 33 | break; 34 | case SAVE_REQUEST: 35 | draft.saveRequest = action.request; 36 | break; 37 | case LOAD_REQUEST: 38 | draft.loadRequest = action.request; 39 | break; 40 | case SET_ASSETS_PATH: 41 | draft.assetsPath = action.path; 42 | break; 43 | case LOAD_STATE: { 44 | // action.state has the information for Presentation reducer 45 | const newPresentationState:presentationState = { 46 | ...action.state, 47 | }; 48 | Object.assign(draft, newPresentationState); 49 | draft.saveRequest = false; 50 | break; 51 | } 52 | case IS_READY: 53 | draft.isReady = action.ready; 54 | break; 55 | case STYLE_REQUEST: 56 | draft.styleRequest = action.request; 57 | break; 58 | case BACKGROUND_COLOR: 59 | draft.backgroundColor = action.color; 60 | break; 61 | case THEME_REQUEST: 62 | draft.themeRequest = action.request; 63 | break; 64 | case PRESENTATION_MODE: 65 | draft.presentationMode = action.mode; 66 | break; 67 | case EXPORT_MODE: 68 | draft.exportMode = action.mode; 69 | break; 70 | } 71 | }); 72 | 73 | export default PresentationReducer; 74 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/containers/redux-store/PresentationReducer/selectors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Selectors 3 | */ 4 | import { presentationState } from './definitions'; 5 | 6 | const getTheme = (state: { presentation: presentationState; }) => state.presentation.theme; 7 | const getTitle = (state: { presentation: presentationState; }) => state.presentation.title; 8 | 9 | const getImgUploadRequest = (state: { presentation: presentationState; }) => state.presentation.imgUploadRequest; 10 | 11 | const getAssetsPath = (state: { presentation: presentationState; }) => state.presentation.assetsPath; 12 | 13 | const getSaveRequest = (state: { presentation: presentationState; }) => state.presentation.saveRequest; 14 | const getLoadRequest = (state: { presentation: presentationState; }) => state.presentation.loadRequest; 15 | 16 | const getIsReady = (state: { presentation: presentationState; }) => state.presentation.isReady; 17 | const getStyleRequest = (state: { presentation: presentationState; }) => state.presentation.styleRequest; 18 | 19 | const getBackgroundColor = (state: { presentation: presentationState; }) => state.presentation.backgroundColor; 20 | const getThemeRequest = (state: { presentation: presentationState; }) => state.presentation.themeRequest; 21 | const getPresentationMode = (state: { presentation: presentationState; }) => state.presentation.presentationMode; 22 | const getExportMode = (state: { presentation: presentationState; }) => state.presentation.exportMode; 23 | 24 | 25 | export { 26 | getTheme, 27 | getTitle, 28 | getImgUploadRequest, 29 | getAssetsPath, 30 | getSaveRequest, 31 | getLoadRequest, 32 | getIsReady, 33 | getStyleRequest, 34 | getBackgroundColor, 35 | getThemeRequest, 36 | getPresentationMode, 37 | getExportMode 38 | }; 39 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/global-styles.js: -------------------------------------------------------------------------------- 1 | import {createGlobalStyle} from 'styled-components'; 2 | 3 | const GlobalStyle = createGlobalStyle` 4 | html, 5 | body { 6 | height: 100%; 7 | width: 100%; 8 | } 9 | 10 | body { 11 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 12 | } 13 | 14 | body.fontLoaded { 15 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 16 | } 17 | 18 | #app { 19 | background-color: #fafafa; 20 | min-height: 100%; 21 | min-width: 100%; 22 | } 23 | 24 | p, 25 | label { 26 | font-family: Georgia, Times, 'Times New Roman', serif; 27 | line-height: 1.5em; 28 | } 29 | `; 30 | 31 | export default GlobalStyle; 32 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/images/Logo-Outline-web-White@200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CERN/slides/e15e412a3dab997ef1ba95c92a09ffee05e9f396/packages/slides-frontend/src/images/Logo-Outline-web-White@200.png -------------------------------------------------------------------------------- /packages/slides-frontend/src/images/PageNotFound.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CERN/slides/e15e412a3dab997ef1ba95c92a09ffee05e9f396/packages/slides-frontend/src/images/PageNotFound.jpg -------------------------------------------------------------------------------- /packages/slides-frontend/src/images/logoITdep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CERN/slides/e15e412a3dab997ef1ba95c92a09ffee05e9f396/packages/slides-frontend/src/images/logoITdep.png -------------------------------------------------------------------------------- /packages/slides-frontend/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * app.js 3 | * 4 | * This is the entry file for the application, only setup and boilerplate 5 | * code. 6 | */ 7 | 8 | // Needed for redux-saga es6 generator support 9 | import 'core-js/stable'; 10 | import 'regenerator-runtime/runtime'; 11 | // Import all the third party stuff 12 | import React from 'react'; 13 | import ReactDOM from 'react-dom'; 14 | import {Provider} from 'react-redux'; 15 | import {ConnectedRouter} from 'connected-react-router'; 16 | import history from './utils/history'; 17 | // Import root app 18 | import App from './containers/App'; 19 | import KeycloakWrapper from '@authzsvc/keycloak-js-react'; 20 | import Keycloak from 'keycloak-js'; 21 | 22 | import configureStore from './configureStore'; 23 | import * as cfg from './authConfig'; 24 | 25 | // Import Semantic-ui styles 26 | import 'semantic-ui-css/semantic.min.css'; 27 | // Create redux store with history 28 | const initialState = {}; 29 | const store = configureStore(initialState, history); 30 | const MOUNT_NODE = document.getElementById('root'); 31 | 32 | ReactDOM.render( 33 | 34 | 35 | 50 | 51 | 52 | 53 | , 54 | MOUNT_NODE 55 | ); 56 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/reducers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Combine all reducers in this file and export the combined reducers. 3 | */ 4 | 5 | import {combineReducers} from 'redux'; 6 | import {connectRouter} from 'connected-react-router'; 7 | 8 | import history from './utils/history'; 9 | import presentationReducer from './containers/redux-store/PresentationReducer/reducer.ts'; 10 | import deckReducer from './containers/redux-store/DeckReducer/reducer.ts'; 11 | import {keycloakReducer as keycloak} from '@authzsvc/keycloak-js-react'; 12 | 13 | /** 14 | * Merges the main reducer with the router state and dynamically injected reducers 15 | */ 16 | export default function createReducer(injectedReducers = {}) { 17 | const rootReducer = combineReducers({ 18 | keycloak, 19 | 20 | presentation: presentationReducer, 21 | deck: deckReducer, 22 | router: connectRouter(history), 23 | ...injectedReducers, 24 | }); 25 | 26 | return rootReducer; 27 | } 28 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/theming/StandardSlide.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/display-name */ 2 | import React, {Component} from 'react'; 3 | import {Slide} from 'spectacle'; 4 | import PropTypes from 'prop-types'; 5 | import getTheme from './theme'; 6 | 7 | export default theme => 8 | class extends Component { 9 | static propTypes = { 10 | children: PropTypes.array.isRequired, 11 | }; 12 | 13 | render() { 14 | const themeObj = getTheme(theme); 15 | return ( 16 | 22 | {React.Children.map(this.props.children, child => { 23 | switch (child.type.displayName) { 24 | case 'Heading': 25 | return React.cloneElement(child, { 26 | textColor: 'tertiary', 27 | }); 28 | case 'Text': 29 | return React.cloneElement(child, { 30 | textColor: 'tertiary', 31 | }); 32 | default: 33 | return child; 34 | } 35 | })} 36 | 37 | ); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/theming/cern.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CERN theme for reveal.js. 3 | * Author: Emmanuel Ormancey 4 | */ 5 | @import url(../../lib/font/league-gothic/league-gothic.css); 6 | @import url(https://framework.web.cern.ch/framework/2.0/fonts/PTSansWeb/PTSansWeb.css); 7 | /** 8 | * CERN Colors 9 | */ 10 | html * { 11 | color-profile: sRGB; 12 | rendering-intent: auto; } 13 | 14 | .reveal ul, 15 | .reveal ol { 16 | color: #cacaca; } 17 | 18 | .reveal ul ul, 19 | .reveal ol ol, 20 | .reveal ul ol, 21 | .reveal ol ul { 22 | color: #abacac; } 23 | 24 | .reveal ul ul ul, 25 | .reveal ol ol ol, 26 | .reveal ul ol ol, 27 | .reveal ul ol ul, 28 | .reveal ol ul ol, 29 | .reveal ol ul ul { 30 | color: #93a1a1; } 31 | 32 | /********************************************* 33 | * GLOBAL STYLES 34 | *********************************************/ 35 | body { 36 | background: #0053A1; 37 | background-color: #0053A1; } 38 | 39 | .reveal { 40 | font-family: "PT Sans", sans-serif !important; 41 | font-size: 32px; 42 | font-weight: normal; 43 | color: #93a1a1; } 44 | 45 | ::selection { 46 | color: #fff; 47 | background: #d33682; 48 | text-shadow: none; } 49 | 50 | ::-moz-selection { 51 | color: #fff; 52 | background: #d33682; 53 | text-shadow: none; } 54 | 55 | .reveal .slides > section, 56 | .reveal .slides > section > section { 57 | line-height: 1.3; 58 | font-weight: inherit; } 59 | 60 | /********************************************* 61 | * HEADERS 62 | *********************************************/ 63 | .reveal h1, 64 | .reveal h2, 65 | .reveal h3, 66 | .reveal h4, 67 | .reveal h5, 68 | .reveal h6 { 69 | margin: 0 0 20px 0; 70 | color: #eee8d5; 71 | font-family: "PT Sans", sans-serif !important; 72 | font-weight: normal; 73 | line-height: 1.2; 74 | letter-spacing: normal; 75 | text-transform: none; 76 | text-shadow: none; 77 | word-wrap: break-word; } 78 | 79 | .reveal h1 { 80 | font-size: 3.77em; } 81 | 82 | .reveal h2 { 83 | font-size: 2.11em; } 84 | 85 | .reveal h3 { 86 | font-size: 1.55em; } 87 | 88 | .reveal h4 { 89 | font-size: 1em; } 90 | 91 | .reveal h1 { 92 | text-shadow: none; } 93 | 94 | /********************************************* 95 | * OTHER 96 | *********************************************/ 97 | .reveal p { 98 | margin: 20px 0; 99 | line-height: 1.3; } 100 | 101 | /* Ensure certain elements are never larger than the slide itself */ 102 | .reveal img, 103 | .reveal video, 104 | .reveal iframe { 105 | max-width: 95%; 106 | max-height: 95%; } 107 | 108 | .reveal strong, 109 | .reveal b { 110 | font-weight: bold; } 111 | 112 | .reveal em { 113 | font-style: italic; } 114 | 115 | .reveal ol, 116 | .reveal dl, 117 | .reveal ul { 118 | display: inline-block; 119 | text-align: left; 120 | margin: 0 0 0 1em; } 121 | 122 | .reveal ol { 123 | list-style-type: decimal; } 124 | 125 | .reveal ul { 126 | list-style-type: disc; } 127 | 128 | .reveal ul ul { 129 | list-style-type: square; } 130 | 131 | .reveal ul ul ul { 132 | list-style-type: circle; } 133 | 134 | .reveal ul ul, 135 | .reveal ul ol, 136 | .reveal ol ol, 137 | .reveal ol ul { 138 | display: block; 139 | margin-left: 40px; } 140 | 141 | .reveal dt { 142 | font-weight: bold; } 143 | 144 | .reveal dd { 145 | margin-left: 40px; } 146 | 147 | .reveal blockquote { 148 | display: block; 149 | position: relative; 150 | width: 70%; 151 | margin: 20px auto; 152 | padding: 5px; 153 | font-style: italic; 154 | background: rgba(255, 255, 255, 0.05); 155 | box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2); } 156 | 157 | .reveal blockquote p:first-child, 158 | .reveal blockquote p:last-child { 159 | display: inline-block; } 160 | 161 | .reveal q { 162 | font-style: italic; } 163 | 164 | .reveal pre { 165 | display: block; 166 | position: relative; 167 | width: 90%; 168 | margin: 20px auto; 169 | text-align: left; 170 | font-size: 0.55em; 171 | font-family: monospace; 172 | line-height: 1.2em; 173 | word-wrap: break-word; 174 | box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); } 175 | 176 | .reveal code { 177 | font-family: monospace; 178 | text-transform: none; } 179 | 180 | .reveal pre code { 181 | display: block; 182 | padding: 5px; 183 | overflow: auto; 184 | max-height: 400px; 185 | word-wrap: normal; } 186 | 187 | .reveal table { 188 | margin: auto; 189 | border-collapse: collapse; 190 | border-spacing: 0; } 191 | 192 | .reveal table th { 193 | font-weight: bold; } 194 | 195 | .reveal table th, 196 | .reveal table td { 197 | text-align: left; 198 | padding: 0.2em 0.5em 0.2em 0.5em; 199 | border-bottom: 1px solid; } 200 | 201 | .reveal table th[align="center"], 202 | .reveal table td[align="center"] { 203 | text-align: center; } 204 | 205 | .reveal table th[align="right"], 206 | .reveal table td[align="right"] { 207 | text-align: right; } 208 | 209 | .reveal table tbody tr:last-child th, 210 | .reveal table tbody tr:last-child td { 211 | border-bottom: none; } 212 | 213 | .reveal sup { 214 | vertical-align: super; } 215 | 216 | .reveal sub { 217 | vertical-align: sub; } 218 | 219 | .reveal small { 220 | display: inline-block; 221 | font-size: 0.6em; 222 | line-height: 1.2em; 223 | vertical-align: top; } 224 | 225 | .reveal small * { 226 | vertical-align: top; } 227 | 228 | /********************************************* 229 | * LINKS 230 | *********************************************/ 231 | .reveal a { 232 | color: #2299ee; 233 | text-decoration: none; 234 | -webkit-transition: color .15s ease; 235 | -moz-transition: color .15s ease; 236 | transition: color .15s ease; } 237 | 238 | .reveal a:hover { 239 | color: #81c5f5; 240 | text-shadow: none; 241 | border: none; } 242 | 243 | .reveal .roll span:after { 244 | color: #fff; 245 | background: #0e70b6; } 246 | 247 | /********************************************* 248 | * IMAGES 249 | *********************************************/ 250 | .reveal section img { 251 | margin: 15px 0px; 252 | background: rgba(255, 255, 255, 0.12); 253 | border: 4px solid #93a1a1; 254 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.15); } 255 | 256 | .reveal section img.plain { 257 | border: 0; 258 | box-shadow: none; } 259 | 260 | .reveal a img { 261 | -webkit-transition: all .15s linear; 262 | -moz-transition: all .15s linear; 263 | transition: all .15s linear; } 264 | 265 | .reveal a:hover img { 266 | background: rgba(255, 255, 255, 0.2); 267 | border-color: #2299ee; 268 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.55); } 269 | 270 | /********************************************* 271 | * NAVIGATION CONTROLS 272 | *********************************************/ 273 | .reveal .controls { 274 | color: #2299ee; } 275 | 276 | /********************************************* 277 | * PROGRESS BAR 278 | *********************************************/ 279 | .reveal .progress { 280 | background: rgba(0, 0, 0, 0.2); 281 | color: #2299ee; } 282 | 283 | .reveal .progress span { 284 | -webkit-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); 285 | -moz-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); 286 | transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); } 287 | 288 | .reveal h6 { 289 | color: orange; } 290 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/theming/cern2.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CERN theme for reveal.js. 3 | * Author: Emmanuel Ormancey 4 | */ 5 | @import url(https://fonts.googleapis.com/css?family=Roboto+Slab:300,700); 6 | @import url(https://fonts.googleapis.com/css?family=Roboto:700); 7 | /** 8 | * CERN Colors 9 | */ 10 | html * { 11 | color-profile: sRGB; 12 | rendering-intent: auto; } 13 | 14 | .reveal ul, 15 | .reveal ol { 16 | color: #cacaca; } 17 | 18 | .reveal ul ul, 19 | .reveal ol ol, 20 | .reveal ul ol, 21 | .reveal ol ul { 22 | color: #abacac; } 23 | 24 | .reveal ul ul ul, 25 | .reveal ol ol ol, 26 | .reveal ul ol ol, 27 | .reveal ul ol ul, 28 | .reveal ol ul ol, 29 | .reveal ol ul ul { 30 | color: #93a1a1; } 31 | 32 | /********************************************* 33 | * GLOBAL STYLES 34 | *********************************************/ 35 | body { 36 | background: #0053A1; 37 | background-color: #0053A1; } 38 | 39 | .reveal { 40 | font-family: "Roboto Slab", serif !important; 41 | font-size: 32px; 42 | font-weight: normal; 43 | color: #93a1a1; } 44 | 45 | ::selection { 46 | color: #fff; 47 | background: #d33682; 48 | text-shadow: none; } 49 | 50 | ::-moz-selection { 51 | color: #fff; 52 | background: #d33682; 53 | text-shadow: none; } 54 | 55 | .reveal .slides > section, 56 | .reveal .slides > section > section { 57 | line-height: 1.3; 58 | font-weight: inherit; } 59 | 60 | /********************************************* 61 | * HEADERS 62 | *********************************************/ 63 | .reveal h1, 64 | .reveal h2, 65 | .reveal h3, 66 | .reveal h4, 67 | .reveal h5, 68 | .reveal h6 { 69 | margin: 0 0 20px 0; 70 | color: #eee8d5; 71 | font-family: "Roboto Slab", serif !important; 72 | font-weight: 700; 73 | line-height: 1.2; 74 | letter-spacing: normal; 75 | text-transform: none; 76 | text-shadow: none; 77 | word-wrap: break-word; } 78 | 79 | .reveal h1 { 80 | font-size: 2.6em; } 81 | 82 | .reveal h2 { 83 | font-size: 2.2em; } 84 | 85 | .reveal h3 { 86 | font-size: 1.7em; } 87 | 88 | .reveal h4 { 89 | font-size: 1.4em; } 90 | 91 | .reveal h1 { 92 | text-shadow: none; } 93 | 94 | /********************************************* 95 | * OTHER 96 | *********************************************/ 97 | .reveal p { 98 | margin: 20px 0; 99 | line-height: 1.3; } 100 | 101 | /* Ensure certain elements are never larger than the slide itself */ 102 | .reveal img, 103 | .reveal video, 104 | .reveal iframe { 105 | max-width: 95%; 106 | max-height: 95%; } 107 | 108 | .reveal strong, 109 | .reveal b { 110 | font-weight: bold; } 111 | 112 | .reveal em { 113 | font-style: italic; } 114 | 115 | .reveal ol, 116 | .reveal dl, 117 | .reveal ul { 118 | display: inline-block; 119 | text-align: left; 120 | margin: 0 0 0 1em; } 121 | 122 | .reveal ol { 123 | list-style-type: decimal; } 124 | 125 | .reveal ul { 126 | list-style-type: disc; } 127 | 128 | .reveal ul ul { 129 | list-style-type: square; } 130 | 131 | .reveal ul ul ul { 132 | list-style-type: circle; } 133 | 134 | .reveal ul ul, 135 | .reveal ul ol, 136 | .reveal ol ol, 137 | .reveal ol ul { 138 | display: block; 139 | margin-left: 40px; } 140 | 141 | .reveal dt { 142 | font-weight: bold; } 143 | 144 | .reveal dd { 145 | margin-left: 40px; } 146 | 147 | .reveal blockquote { 148 | display: block; 149 | position: relative; 150 | width: 70%; 151 | margin: 20px auto; 152 | padding: 5px; 153 | font-style: italic; 154 | background: rgba(255, 255, 255, 0.05); 155 | box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2); } 156 | 157 | .reveal blockquote p:first-child, 158 | .reveal blockquote p:last-child { 159 | display: inline-block; } 160 | 161 | .reveal q { 162 | font-style: italic; } 163 | 164 | .reveal pre { 165 | display: block; 166 | position: relative; 167 | width: 90%; 168 | margin: 20px auto; 169 | text-align: left; 170 | font-size: 0.55em; 171 | font-family: monospace; 172 | line-height: 1.2em; 173 | word-wrap: break-word; 174 | box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); } 175 | 176 | .reveal code { 177 | font-family: monospace; 178 | text-transform: none; } 179 | 180 | .reveal pre code { 181 | display: block; 182 | padding: 5px; 183 | overflow: auto; 184 | max-height: 400px; 185 | word-wrap: normal; } 186 | 187 | .reveal table { 188 | margin: auto; 189 | border-collapse: collapse; 190 | border-spacing: 0; } 191 | 192 | .reveal table th { 193 | font-weight: bold; } 194 | 195 | .reveal table th, 196 | .reveal table td { 197 | text-align: left; 198 | padding: 0.2em 0.5em 0.2em 0.5em; 199 | border-bottom: 1px solid; } 200 | 201 | .reveal table th[align="center"], 202 | .reveal table td[align="center"] { 203 | text-align: center; } 204 | 205 | .reveal table th[align="right"], 206 | .reveal table td[align="right"] { 207 | text-align: right; } 208 | 209 | .reveal table tbody tr:last-child th, 210 | .reveal table tbody tr:last-child td { 211 | border-bottom: none; } 212 | 213 | .reveal sup { 214 | vertical-align: super; } 215 | 216 | .reveal sub { 217 | vertical-align: sub; } 218 | 219 | .reveal small { 220 | display: inline-block; 221 | font-size: 0.6em; 222 | line-height: 1.2em; 223 | vertical-align: top; } 224 | 225 | .reveal small * { 226 | vertical-align: top; } 227 | 228 | /********************************************* 229 | * LINKS 230 | *********************************************/ 231 | .reveal a { 232 | color: #2299ee; 233 | text-decoration: none; 234 | -webkit-transition: color .15s ease; 235 | -moz-transition: color .15s ease; 236 | transition: color .15s ease; } 237 | 238 | .reveal a:hover { 239 | color: #81c5f5; 240 | text-shadow: none; 241 | border: none; } 242 | 243 | .reveal .roll span:after { 244 | color: #fff; 245 | background: #0e70b6; } 246 | 247 | /********************************************* 248 | * IMAGES 249 | *********************************************/ 250 | .reveal section img { 251 | margin: 15px 0px; 252 | background: rgba(255, 255, 255, 0.12); 253 | border: 4px solid #93a1a1; 254 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.15); } 255 | 256 | .reveal section img.plain { 257 | border: 0; 258 | box-shadow: none; } 259 | 260 | .reveal a img { 261 | -webkit-transition: all .15s linear; 262 | -moz-transition: all .15s linear; 263 | transition: all .15s linear; } 264 | 265 | .reveal a:hover img { 266 | background: rgba(255, 255, 255, 0.2); 267 | border-color: #2299ee; 268 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.55); } 269 | 270 | /********************************************* 271 | * NAVIGATION CONTROLS 272 | *********************************************/ 273 | .reveal .controls { 274 | color: #2299ee; } 275 | 276 | /********************************************* 277 | * PROGRESS BAR 278 | *********************************************/ 279 | .reveal .progress { 280 | background: rgba(0, 0, 0, 0.2); 281 | color: #2299ee; } 282 | 283 | .reveal .progress span { 284 | -webkit-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); 285 | -moz-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); 286 | transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); } 287 | 288 | .reveal h6 { 289 | color: orange; } 290 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/theming/theme.js: -------------------------------------------------------------------------------- 1 | const logoPrimaryColor = require('../images/Logo-Outline-web-White@200.png'); 2 | const logoITDEP = require('../images/logoITdep.png'); 3 | 4 | const defaultTheme = { 5 | slideDefaults: { 6 | transition: ['fade'], 7 | bgColor: 'secondary', 8 | textColor: 'primary', 9 | }, 10 | headingDefaults: { 11 | caps: true, 12 | textColor: 'primary', 13 | size: 6, 14 | margin: '0 0 15px 0', 15 | }, 16 | themeConfig: { 17 | primary: '#0053A1', 18 | secondary: '#0053A1', 19 | tertiary: 'white', 20 | quaternary: 'white', 21 | }, 22 | fontConfig: { 23 | primary: 'Montserrat', 24 | secondary: 'Helvetica', 25 | }, 26 | }; 27 | 28 | const defineTheming = selectionTheme => { 29 | switch (selectionTheme) { 30 | case 'CERN 1': 31 | return { 32 | ...defaultTheme, 33 | images: {}, 34 | logoSettings: {}, 35 | headingDefaults: { 36 | caps: true, 37 | textColor: '#93a1a1', 38 | size: '3.77em', 39 | margin: '0 0 15px 0', 40 | }, 41 | themeConfig: { 42 | primary: 'white', 43 | secondary: '#0053A1', 44 | tertiary: '#93a1a1', 45 | quaternary: 'white', // color of slides number 46 | }, 47 | fontConfig: { 48 | primary: 'PT Sans', 49 | secondary: 'sans-serif', 50 | }, 51 | }; 52 | case 'CERN 2': 53 | return { 54 | ...defaultTheme, 55 | images: {}, 56 | logoSettings: {}, 57 | headingDefaults: { 58 | caps: true, 59 | textColor: '#93a1a1', 60 | size: '2.6em', 61 | margin: '0 0 15px 0', 62 | }, 63 | themeConfig: { 64 | primary: 'white', 65 | secondary: '#0053A1', 66 | tertiary: '#93a1a1', 67 | quaternary: 'white', // color of slides number 68 | }, 69 | fontConfig: { 70 | primary: 'Roboto Slab', 71 | secondary: 'serif', 72 | }, 73 | }; 74 | case 'CERN 3': 75 | return { 76 | ...defaultTheme, 77 | images: { 78 | // cernLogo, 79 | // logoSecondaryColor, 80 | // logoPrimaryMono, 81 | logoPrimaryColor, 82 | }, 83 | logoSettings: { 84 | bgImage: logoPrimaryColor, 85 | bgSize: '3em', 86 | bgRepeat: 'no-repeat', 87 | bgPosition: '2% 98%', 88 | }, 89 | headingDefaults: { 90 | caps: true, 91 | textColor: '#93a1a1', 92 | size: '2.6em', 93 | margin: '0 0 15px 0', 94 | }, 95 | themeConfig: { 96 | primary: 'white', 97 | secondary: '#0053A1', 98 | tertiary: '#93a1a1', 99 | quaternary: 'white', // color of slides number 100 | }, 101 | fontConfig: { 102 | primary: 'Roboto Slab', 103 | secondary: 'serif', 104 | }, 105 | }; 106 | case 'CERN 4': 107 | // this one need a blue stripe in the bottom part as background 108 | return { 109 | ...defaultTheme, 110 | images: { 111 | // cernLogo, 112 | // logoSecondaryColor, 113 | // logoPrimaryMono, 114 | logoPrimaryColor, 115 | }, 116 | logoSettings: { 117 | bgImage: logoPrimaryColor, 118 | bgSize: '4em', 119 | bgRepeat: 'no-repeat', 120 | bgPosition: '2% 98%', 121 | }, 122 | headingDefaults: { 123 | caps: true, 124 | textColor: '#5c5c5c', 125 | size: '3.77em', 126 | margin: '0 0 15px 0', 127 | }, 128 | themeConfig: { 129 | primary: '#343434', 130 | secondary: 'white', 131 | tertiary: '#93a1a1', 132 | quaternary: 'white', // color of slides number 133 | }, 134 | fontConfig: { 135 | primary: 'PT Sans', 136 | secondary: 'sans-serif', 137 | }, 138 | }; 139 | case 'CERN 5': 140 | // this one has blue numbers in the right bottom and a picture in the left bottom 141 | return { 142 | ...defaultTheme, 143 | images: { 144 | // cernLogo, 145 | // logoSecondaryColor, 146 | // logoPrimaryMono, 147 | logoITDEP, 148 | }, 149 | logoSettings: { 150 | bgImage: logoITDEP, 151 | bgSize: '18em', 152 | bgRepeat: 'no-repeat', 153 | bgPosition: '0% 100%', 154 | }, 155 | headingDefaults: { 156 | caps: true, 157 | textColor: '#5c5c5c', 158 | size: '3.77em', 159 | margin: '0 0 15px 0', 160 | }, 161 | themeConfig: { 162 | primary: '#343434', 163 | secondary: 'white', 164 | tertiary: '#93a1a1', 165 | quaternary: '#2299ee', // color of slides number 166 | }, 167 | fontConfig: { 168 | primary: 'PT Sans', 169 | secondary: 'sans-serif', 170 | }, 171 | }; 172 | case 'CERN 6': 173 | return { 174 | ...defaultTheme, 175 | images: { 176 | // cernLogo, 177 | // logoSecondaryColor, 178 | // logoPrimaryMono, 179 | logoITDEP, 180 | }, 181 | logoSettings: { 182 | bgImage: logoITDEP, 183 | bgSize: '18em', 184 | bgRepeat: 'no-repeat', 185 | bgPosition: '0% 100%', 186 | }, 187 | headingDefaults: { 188 | caps: true, 189 | textColor: '#5c5c5c', 190 | size: '3.77em', 191 | margin: '0 0 15px 0', 192 | }, 193 | themeConfig: { 194 | primary: '#343434', 195 | secondary: 'white', 196 | tertiary: '#93a1a1', 197 | quaternary: '#2299ee', // color of slides number 198 | }, 199 | fontConfig: { 200 | primary: 'PT Sans', 201 | secondary: 'sans-serif', 202 | }, 203 | }; 204 | default: 205 | return defaultTheme; 206 | } 207 | }; 208 | 209 | const getTheme = selectionTheme => defineTheming(selectionTheme); 210 | export default getTheme; 211 | 212 | /* 213 | THEMES THAT HAVE STARTING SLIDE: 214 | * CERN 3,4,5 215 | */ 216 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/utils/helperFunctions.js: -------------------------------------------------------------------------------- 1 | const canDeleteImageFromBackend = (imageName, slides) => { 2 | let counter = 0; 3 | slides.forEach(slide => 4 | slide.itemsArray.forEach(item => { 5 | if (item.type === "IMAGE" && item.Src === imageName) { 6 | counter = counter + 1; 7 | } 8 | }) 9 | ); 10 | return counter === 1; 11 | } 12 | 13 | const deepCopyFunction = inObject => { 14 | let outObject, value, key; 15 | if (typeof inObject !== "object" || inObject === null) { 16 | return inObject; 17 | } 18 | outObject = Array.isArray(inObject) ? [] : {}; 19 | for (key in inObject) { 20 | value = inObject[key]; 21 | outObject[key] = deepCopyFunction(value); 22 | } 23 | return outObject; 24 | } 25 | 26 | // utils to transform between percentages and pixels 27 | const getPercentage = (px, screenAttribute) => px / screenAttribute; 28 | 29 | const getPixels = (percentage, screenAttribute) => percentage * screenAttribute; 30 | 31 | const getBarsWidth = () => document.getElementById("sidebar").offsetWidth + document.getElementById("settings").offsetWidth; 32 | 33 | const getWidthInEditMode = () => window.innerWidth - getBarsWidth(); 34 | 35 | const getWidth = presentationMode => presentationMode ? window.innerWidth : getWidthInEditMode(); 36 | 37 | export { 38 | canDeleteImageFromBackend, 39 | deepCopyFunction, 40 | getPercentage, 41 | getPixels, 42 | getWidth 43 | }; 44 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/utils/history.js: -------------------------------------------------------------------------------- 1 | import {createBrowserHistory} from 'history'; 2 | const history = createBrowserHistory(); 3 | export default history; 4 | -------------------------------------------------------------------------------- /packages/slides-frontend/src/utils/requests.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { canDeleteImageFromBackend } from './helperFunctions'; 3 | 4 | const deletePresentationFolder = (assetsPath, username, title, token) => { 5 | const url = `${assetsPath}/image/${username}/${title}`; 6 | return axios.delete(url, { 7 | headers: { 8 | Authorization: `Bearer ${token}`, 9 | }, 10 | }); 11 | }; 12 | 13 | const uploadImage = (assetsPath, username, title, files, token) => { 14 | const url = `${assetsPath}/image/upload`; 15 | const formData = new FormData(); 16 | formData.set('username', username); 17 | formData.set('title', title); 18 | files.forEach(f => formData.append('file', f)); 19 | const config = { 20 | headers: { 21 | 'content-type': 'multipart/form-data', 22 | 'Authorization': `Bearer ${token}`, 23 | }, 24 | }; 25 | return axios.post(url, formData, config); 26 | }; 27 | 28 | const deleteImage = (assetsPath, username, title, src, token, slides) => { 29 | const url = `${assetsPath}/image/${username}/${title}/${src}`; 30 | // only if it the function return true then delete from backend 31 | if (canDeleteImageFromBackend(src, slides)){ 32 | return axios.delete(url, { 33 | headers: { 34 | Authorization: `Bearer ${token}`, 35 | }, 36 | }); 37 | } 38 | return; 39 | }; 40 | 41 | // check if title is good?! 42 | const titleCheck = (assetsPath, username, title, token) => { 43 | return axios.post( 44 | `${assetsPath}/presentation/titleCheck`, 45 | { 46 | username, 47 | title, 48 | }, 49 | { 50 | headers: { 51 | Authorization: `Bearer ${token}`, 52 | }, 53 | } 54 | ); 55 | }; 56 | 57 | const uploadPresentation = (assetsPath, username, acceptedFiles, token) => { 58 | const url = `${assetsPath}/presentation/load`; 59 | const formData = new FormData(); 60 | formData.append('username', username); 61 | formData.append('file', acceptedFiles[0]); 62 | const config = { 63 | headers: { 64 | 'content-type': 'multipart/form-data', 65 | 'Authorization': `Bearer ${token}`, 66 | }, 67 | }; 68 | return axios.post(url, formData, config); 69 | }; 70 | 71 | const renamePresentation = (assetsPath, username, oldTitle, newTitle, token) => { 72 | return axios.post( 73 | `${assetsPath}/presentation/rename`, 74 | { 75 | username, 76 | oldTitle, 77 | newTitle, 78 | }, 79 | { 80 | headers: { 81 | Authorization: `Bearer ${token}`, 82 | }, 83 | } 84 | ); 85 | }; 86 | 87 | const savePresentation = (url, stateStringified, user, token) => { 88 | return axios.post( 89 | url, 90 | {state: JSON.parse(stateStringified), username: user}, 91 | { 92 | headers: { 93 | Accept: 'application/slides', 94 | Authorization: `Bearer ${token}`, 95 | }, 96 | responseType: 'arraybuffer', 97 | } 98 | ); 99 | }; 100 | 101 | export { 102 | deletePresentationFolder, 103 | uploadImage, 104 | deleteImage, 105 | titleCheck, 106 | uploadPresentation, 107 | renamePresentation, 108 | savePresentation, 109 | }; 110 | -------------------------------------------------------------------------------- /packages/slides-frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /packages/slides-server/.env.example: -------------------------------------------------------------------------------- 1 | # Replace these values with the secret values 2 | # generated while registering the application for the SSO 3 | # delete these comments and rename this file to .env 4 | KEYCLOAK_REALM="cern" 5 | KEYCLOAK_URL="https://auth.cern.ch/auth" 6 | KEYCLOAK_CLIENT_ID="***" 7 | KEYCLOAK_CLIENT_SECRET="***" 8 | -------------------------------------------------------------------------------- /packages/slides-server/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "ecmaFeatures": { 6 | "modules": true 7 | } 8 | }, 9 | "plugins": ["prettier"], 10 | "extends": ["prettier"], 11 | "rules": { 12 | "prettier/prettier": "error" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/slides-server/.gitignore: -------------------------------------------------------------------------------- 1 | logfile 2 | .env 3 | node_modules/ 4 | Slides-storage 5 | -------------------------------------------------------------------------------- /packages/slides-server/.prettierignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | package-lock.json 4 | yarn.lock 5 | package.json 6 | -------------------------------------------------------------------------------- /packages/slides-server/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "quoteProps": "consistent", 8 | "jsxSingleQuote": false, 9 | "trailingComma": "es5", 10 | "bracketSpacing": false, 11 | "jsxBracketSameLine": false, 12 | "arrowParens": "avoid", 13 | "endOfLine": "lf" 14 | } 15 | -------------------------------------------------------------------------------- /packages/slides-server/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - stable 5 | 6 | install: 7 | - yarn install 8 | 9 | script: 10 | - yarn test 11 | -------------------------------------------------------------------------------- /packages/slides-server/README.md: -------------------------------------------------------------------------------- 1 | # This is the CERN Slides App server code 2 | -------------------------------------------------------------------------------- /packages/slides-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slides-server", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "engines": { 7 | "node": ">=8.10.0" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "scripts": { 13 | "start:dev": "NODE_ENV=development node -r esm ./src/index.js", 14 | "start:prod": "NODE_ENV=production node -r esm ./src/index.js", 15 | "format": "prettier-eslint --write \"$PWD/{,!(node_modules)/**/}*.{js,jsx}\"" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.2.2", 19 | "@babel/node": "^7.2.2", 20 | "@babel/preset-env": "^7.9.6", 21 | "babel-eslint": "^10.1.0", 22 | "eslint": "^7.4.0", 23 | "eslint-config-prettier": "^6.11.0", 24 | "eslint-plugin-node": "^11.1.0", 25 | "eslint-plugin-prettier": "^3.1.4", 26 | "prettier": "^2.0.5", 27 | "prettier-eslint-cli": "^5.0.0" 28 | }, 29 | "dependencies": { 30 | "axios": "^0.21.1", 31 | "body-parser": "^1.18.3", 32 | "cors": "^2.8.5", 33 | "cross-env": "5.2.0", 34 | "dotenv": "^8.2.0", 35 | "esm": "^3.2.25", 36 | "express": "^4.16.4", 37 | "express-fileupload": "^1.1.7-alpha.3", 38 | "extract-zip": "^2.0.0", 39 | "fs": "0.0.1-security", 40 | "fs-extra": "^9.0.0", 41 | "keycloak-connect": "^10.0.1", 42 | "uuid": "^3.3.2", 43 | "zip-folder": "^1.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/slides-server/public/Logo-Outline-web-White@200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CERN/slides/e15e412a3dab997ef1ba95c92a09ffee05e9f396/packages/slides-server/public/Logo-Outline-web-White@200.png -------------------------------------------------------------------------------- /packages/slides-server/public/Slides-S.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CERN/slides/e15e412a3dab997ef1ba95c92a09ffee05e9f396/packages/slides-server/public/Slides-S.png -------------------------------------------------------------------------------- /packages/slides-server/public/Slides-S_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CERN/slides/e15e412a3dab997ef1ba95c92a09ffee05e9f396/packages/slides-server/public/Slides-S_background.png -------------------------------------------------------------------------------- /packages/slides-server/public/intro.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Slides' Backend 6 | 7 | 8 | 11 | 12 | 13 |
14 |
15 |

Available Routes in Slides' Backend:

16 |

Slides' Backend is ONLY accessible by Slides' frontend. Only the 2 logos can be accessed directly with the links above.

17 |

18 |
    19 |
  • /static route contains the images uploaded by a specific user in a presentation.
  • 20 |
  • /public route contains the CERN logo and the CERN Slides App logo.
  • 21 |
  • /image route is protected by Keycloak. Contains code for image handling in presentations (upload and delete).
  • 22 |
  • /presentation route is protected by Keycloak. Contains code for presentation handling (upload and delete).
  • 23 |
  • /test route is protected by Keycloak. Lists all files in the server and in the uploads folder.
  • 24 |
25 |

26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /packages/slides-server/public/themes/cern1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CERN/slides/e15e412a3dab997ef1ba95c92a09ffee05e9f396/packages/slides-server/public/themes/cern1.png -------------------------------------------------------------------------------- /packages/slides-server/public/themes/cern2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CERN/slides/e15e412a3dab997ef1ba95c92a09ffee05e9f396/packages/slides-server/public/themes/cern2.png -------------------------------------------------------------------------------- /packages/slides-server/public/themes/cern3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CERN/slides/e15e412a3dab997ef1ba95c92a09ffee05e9f396/packages/slides-server/public/themes/cern3.png -------------------------------------------------------------------------------- /packages/slides-server/public/themes/cern4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CERN/slides/e15e412a3dab997ef1ba95c92a09ffee05e9f396/packages/slides-server/public/themes/cern4.png -------------------------------------------------------------------------------- /packages/slides-server/public/themes/cern5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CERN/slides/e15e412a3dab997ef1ba95c92a09ffee05e9f396/packages/slides-server/public/themes/cern5.png -------------------------------------------------------------------------------- /packages/slides-server/public/themes/cern6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CERN/slides/e15e412a3dab997ef1ba95c92a09ffee05e9f396/packages/slides-server/public/themes/cern6.png -------------------------------------------------------------------------------- /packages/slides-server/src/config/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const {resolve} = require('path'); 3 | 4 | const prod = { 5 | uploadsFolder: '/mydata/presentations', 6 | }; 7 | const dev = { 8 | uploadsFolder: resolve(__dirname, '../../Slides-storage/presentations'), 9 | }; 10 | 11 | const config = process.env.NODE_ENV === 'development' ? dev : prod; 12 | 13 | module.exports = config; 14 | -------------------------------------------------------------------------------- /packages/slides-server/src/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | import cors from 'cors'; 4 | import bodyParser from 'body-parser'; 5 | import express from 'express'; 6 | 7 | import routes from './routes'; 8 | import path from 'path'; 9 | const fileUpload = require('express-fileupload'); 10 | 11 | /* AUTHENTICATION */ 12 | // Add : ​"keycloak-connect": "10.0.0" to package.json 13 | // const session = require('express-session'); 14 | const Keycloak = require('keycloak-connect'); 15 | const passedAuth = require('./utils/log'); 16 | 17 | if (!process.env.KEYCLOAK_REALM) { 18 | console.log('KEYCLOAK_REALM environment variable has no value'); 19 | process.exit(1); 20 | } 21 | if (!process.env.KEYCLOAK_URL) { 22 | console.log('KEYCLOAK_URL environment variable has no value'); 23 | process.exit(1); 24 | } 25 | if (!process.env.KEYCLOAK_CLIENT_ID) { 26 | console.log('KEYCLOAK_CLIENT_ID environment variable has no value'); 27 | process.exit(1); 28 | } 29 | if (!process.env.KEYCLOAK_CLIENT_SECRET) { 30 | console.log('KEYCLOAK_CLIENT_SECRET environment variable has no value'); 31 | process.exit(1); 32 | } 33 | 34 | const kcUserConf = { 35 | realm: process.env.KEYCLOAK_REALM, 36 | serverUrl: process.env.KEYCLOAK_URL, 37 | clientId: process.env.KEYCLOAK_CLIENT_ID, 38 | clientSecret: process.env.KEYCLOAK_CLIENT_SECRET, 39 | }; 40 | 41 | //user auth 42 | let userKeycloak = new Keycloak({}, kcUserConf); 43 | 44 | // const memoryStore = new session.MemoryStore(); 45 | // const keycloak = new Keycloak({ store: memoryStore }); 46 | /* END AUTH */ 47 | const {uploadsFolder} = require('./config'); 48 | 49 | const app = express(); 50 | 51 | app.use(cors()); 52 | app.use(fileUpload()); 53 | 54 | app.use(bodyParser.json()); 55 | app.use(bodyParser.urlencoded({extended: true})); 56 | // Application-Level Middleware 57 | app.use(userKeycloak.middleware()); 58 | 59 | // static HTML showing the available routes 60 | app.get('/', (req, res) => res.sendFile(path.join(__dirname + '/../public/intro.html'))); 61 | 62 | // serve static images from the uploads folder 63 | // I don't use userKeycloak.protect() here, so the pictures are open to the public 64 | app.use('/static', express.static(uploadsFolder)); 65 | // serve Slides' assets 66 | app.use('/public', express.static(`${__dirname}/../public`)); 67 | 68 | // Routes 69 | app.use('/image', userKeycloak.protect(), passedAuth, routes.image); 70 | app.use('/presentation', userKeycloak.protect(), passedAuth, routes.presentation); 71 | app.use('/test', userKeycloak.protect(), passedAuth, routes.testBackend); 72 | // app.use('/wopi', routes.wopi); 73 | 74 | // Start 75 | const PORT = 8000; 76 | app.listen(PORT, () => console.log(`Server is listening on port ${PORT}!`)); 77 | -------------------------------------------------------------------------------- /packages/slides-server/src/routes/image.js: -------------------------------------------------------------------------------- 1 | import {Router} from 'express'; 2 | const fs = require('fs-extra'); 3 | const {resolve} = require('path'); 4 | const {uploadsFolder} = require('../config'); 5 | 6 | const router = Router(); 7 | 8 | router.post('/upload', (req, res) => { 9 | if (req.files === null) { 10 | return res.status(400).json({ 11 | msg: 'No file uploaded', 12 | }); 13 | } 14 | // uploadsfolder/user/presentationName/assets/hash_filename 15 | const {file} = req.files; 16 | const {username, title} = req.body; 17 | // this makes the dir if it doesn't exist else does nothing 18 | fs.ensureDirSync(`${uploadsFolder}/${username}/${title}/assets`); 19 | const imageNameToStore = `${uploadsFolder}/${username}/${title}/assets/${file.md5}_${file.name}`; 20 | file.mv(imageNameToStore, err => { 21 | if (err) { 22 | console.error(err); 23 | return res.status(500).send(err); 24 | } 25 | 26 | res.json({ 27 | fileName: imageNameToStore, 28 | filePath: resolve(uploadsFolder, imageNameToStore), 29 | }); 30 | }); 31 | }); 32 | 33 | router.delete('/:username/:title/:id', (req, res) => { 34 | const {id, username, title} = req.params; 35 | const imageName = `${uploadsFolder}/${username}/${title}/assets/${id}`; 36 | // delete image file 37 | if (fs.existsSync(imageName)) { 38 | fs.removeSync(imageName); 39 | res.json({ 40 | state: 'Successful', 41 | }); 42 | } else { 43 | res.status(404).send('File does not exist'); 44 | } 45 | }); 46 | 47 | router.delete('/:username/:title', (req, res) => { 48 | const {username, title} = req.params; 49 | const userFolder = `${uploadsFolder}/${username}`; 50 | const presentationFolder = `${userFolder}/${title}`; 51 | // delete the presentation with title: 'title' 52 | if (fs.existsSync(presentationFolder)) { 53 | fs.removeSync(presentationFolder); 54 | // delete the whole folder 'username' (only if there are no more presentations inside) 55 | // fs.readdirSync(userFolder)[0] === '.DS_Store', only for MacOS 56 | if (!fs.readdirSync(userFolder).length) { 57 | fs.removeSync(userFolder); 58 | } 59 | res.json({ 60 | state: 'Successful', 61 | }); 62 | } else { 63 | res.status(404).send('Folder does not exist'); 64 | } 65 | }); 66 | 67 | export default router; 68 | -------------------------------------------------------------------------------- /packages/slides-server/src/routes/index.js: -------------------------------------------------------------------------------- 1 | import image from './image'; 2 | import presentation from './presentation'; 3 | import testBackend from './testBackend'; 4 | import wopi from './wopi'; 5 | 6 | export default { 7 | image, 8 | presentation, 9 | testBackend, 10 | wopi, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/slides-server/src/routes/presentation.js: -------------------------------------------------------------------------------- 1 | import {Router} from 'express'; 2 | const zipFolder = require('zip-folder'); 3 | const extract = require('extract-zip'); 4 | const fs = require('fs-extra'); 5 | const {uploadsFolder} = require('../config'); 6 | 7 | const router = Router(); 8 | 9 | router.post('/save', (req, res) => { 10 | // in this endpoint I need to get, 11 | // username, title, all the state stringified 12 | // I know where the assets are located 13 | // so now in the public/username/ folder put: JSON of state, assets folder 14 | const {state, username} = req.body; 15 | // sanitize state 16 | // get state as obj, JSON parsed from frontend 17 | // FIX STATE SANITIZER 18 | // const newState = stateSanitizer(state); 19 | // 20 | // const obj = JSON.parse(newState); 21 | const obj = {...state}; 22 | const {title} = obj.presentation; 23 | const presentationName = `${uploadsFolder}/${username}/${title}`; 24 | 25 | const tmp = `${uploadsFolder}/${username}/${title}/tmp`; 26 | const presentationFile = `${tmp}/presentation.JSON`; 27 | // 28 | try { 29 | // writes the file and creates the folders if needed 30 | fs.outputJsonSync(presentationFile, obj); 31 | // check if the assets folder exists, if not it is created 32 | fs.ensureDirSync(`${uploadsFolder}/${username}/${title}/assets`); 33 | // copy assets folder 34 | fs.copySync(`${uploadsFolder}/${username}/${title}/assets`, `${tmp}/assets`); 35 | // zip it and rename 36 | zipFolder(`${tmp}`, `${presentationName}.slides`, err => { 37 | if (!err) { 38 | // delete the tmp now that I have the file 39 | fs.removeSync(tmp); 40 | // i need to read the zip 41 | const fileAsBuffer = fs.readFileSync(`${presentationName}.slides`); 42 | // send the file 43 | res.send(fileAsBuffer); 44 | // delete the file from server 45 | fs.removeSync(`${presentationName}.slides`); 46 | } 47 | }); 48 | } catch (e) { 49 | console.log('An error occured ', e); 50 | return res.status(500).send(e); 51 | } 52 | }); 53 | 54 | router.post('/load', async (req, res) => { 55 | if (req.files === null) { 56 | return res.status(400).json({ 57 | msg: 'No file uploaded', 58 | }); 59 | } 60 | if (!req.body.username) { 61 | return res.status(400).json({ 62 | msg: 'No username given', 63 | }); 64 | } 65 | const {file} = req.files; 66 | const username = req.body.username; 67 | const tmpFolder = `${uploadsFolder}/tmp-folder`; 68 | // check if exists, then do nothing otherwise create 69 | fs.ensureDirSync(tmpFolder); 70 | // this name should be unique 71 | const tmpNameForDotSlides = `${tmpFolder}/${file.md5}_${file.name}`; 72 | file.mv(tmpNameForDotSlides, err => { 73 | if (err) { 74 | console.error(err); 75 | return res.status(500).send(err); 76 | } 77 | }); 78 | // unique name for extract folder distinguised by unique file md5 hash 79 | const extractFolder = `${uploadsFolder}/extract-folder_id_${file.md5}`; 80 | // ensure it exists and it is empty 81 | fs.emptyDirSync(`${extractFolder}/assets`); 82 | // get the files 83 | try { 84 | await extract(tmpNameForDotSlides, {dir: extractFolder}); 85 | // read the JSON 86 | const reduxStateOBJ = fs.readJsonSync(`${extractFolder}/presentation.JSON`); 87 | // extract title 88 | const {title} = reduxStateOBJ.presentation; 89 | // move assets in the user's assets folder 90 | fs.emptyDirSync(`${uploadsFolder}/${username}/${title}/assets`); 91 | // copy the images to appropriate folder 92 | fs.copySync(`${extractFolder}/assets`, `${uploadsFolder}/${username}/${title}/assets`); 93 | // return the redux state 94 | // SANITIZE html in load as well 95 | res.json({ 96 | state: reduxStateOBJ, 97 | }); 98 | // delete the extractFolder folder and the file .slides 99 | fs.removeSync(tmpNameForDotSlides); 100 | fs.removeSync(extractFolder); 101 | } catch (err) { 102 | // handle any errors 103 | if (err) { 104 | console.log('An error has occured', err); 105 | return res.status(500).send(err); 106 | } 107 | } 108 | }); 109 | 110 | router.post('/rename', (req, res) => { 111 | // take the old and replace it with the new name 112 | const {username, oldTitle, newTitle} = req.body; 113 | fs.pathExists(`${uploadsFolder}/${username}/${oldTitle}`).then(oldExists => { 114 | if (!oldExists) { 115 | // check this again 116 | res.status(200).send('Success'); 117 | } else { 118 | fs.pathExists(`${uploadsFolder}/${username}/${newTitle}`).then(newExists => { 119 | if (!newExists) { 120 | fs.moveSync( 121 | `${uploadsFolder}/${username}/${oldTitle}`, 122 | `${uploadsFolder}/${username}/${newTitle}` 123 | ); 124 | res.status(200).send('Success'); 125 | } else { 126 | res.status(400).send('Already Exists'); // status 400, means that is obvious client's fault 127 | } 128 | }); 129 | } 130 | }); 131 | }); 132 | 133 | router.post('/titleCheck', (req, res) => { 134 | const {username, title} = req.body; 135 | fs.pathExists(`${uploadsFolder}/${username}/${title}`).then(titleExists => { 136 | if (titleExists) { 137 | res.status(409).send('Conflict: title already exists'); 138 | } else { 139 | res.status(200).send('Success: title can be used'); 140 | } 141 | }); 142 | }); 143 | 144 | export default router; 145 | -------------------------------------------------------------------------------- /packages/slides-server/src/routes/testBackend.js: -------------------------------------------------------------------------------- 1 | import {Router} from 'express'; 2 | const router = Router(); 3 | const {uploadsFolder} = require('../config'); 4 | 5 | const {exec} = require('child_process'); 6 | function OsFunc() { 7 | this.execCommand = function (cmd, callback) { 8 | exec(cmd, (error, stdout, stderr) => { 9 | if (error) { 10 | console.error(`exec error: ${error}`); 11 | callback(error); 12 | return; 13 | } 14 | callback(stdout); 15 | }); 16 | }; 17 | } 18 | const os = new OsFunc(); 19 | router.get('/', (req, res) => { 20 | os.execCommand('ls /', lsroot => { 21 | os.execCommand('ls .', current => 22 | res.status(200).json({ 23 | uploadsFolder, 24 | currentls: current, 25 | lsroot, 26 | }) 27 | ); 28 | }); 29 | }); 30 | 31 | export default router; 32 | -------------------------------------------------------------------------------- /packages/slides-server/src/routes/wopi.js: -------------------------------------------------------------------------------- 1 | import {Router} from 'express'; 2 | const wopiServerFiles = 'http://localhost:8443/wopi/files/'; 3 | const axios = require('axios'); 4 | 5 | const router = Router(); 6 | 7 | router.get('/phoenix/wopi/start', (req, res) => { 8 | const {accessToken, inode, username} = req.query; 9 | console.log('accessToken and inode ', accessToken, inode); 10 | // accessToken, inode, username 11 | // now i have to make the getfile info, getfile wopi requests, and after that i set all the parameters like the redux state and images and im ready to roll 12 | // getfile info: 13 | const getFileInfoURL = `${wopiServerFiles}${inode}?access_token=${accessToken}`; 14 | const getFileURL = `${wopiServerFiles}${inode}/contents?access_token=${accessToken}`; 15 | // lock, uuid, conflict 16 | // put 17 | // unlock 18 | // `X-WOPI-${SessionContext}` 19 | // const wopi_header = 'X-WOPI-Override'; 20 | axios 21 | .get(getFileInfoURL) 22 | .then(resp => { 23 | console.log('getFileInfoURL returned me:', resp.response); 24 | }) 25 | .catch(e => { 26 | console.log('error is', e.response); 27 | }); 28 | }); 29 | 30 | // router.get('/phoenix/wopi/save', (req, res) => { }); 31 | // router.get('/phoenix/wopi/load', (req, res) => { }); 32 | 33 | export default router; 34 | -------------------------------------------------------------------------------- /packages/slides-server/src/utils/log.js: -------------------------------------------------------------------------------- 1 | module.exports = (req, res, next) => { 2 | // console.log("Passed the authentication"); 3 | next(); 4 | }; 5 | -------------------------------------------------------------------------------- /packages/slides-server/src/utils/sanitizeState.js: -------------------------------------------------------------------------------- 1 | const sanitizeHtml = require('sanitize-html'); 2 | 3 | const stateSanitizer = stateObj => 4 | stateObj.deck.slides.forEach(slide => { 5 | slide.itemsArray.forEach(item => { 6 | if (item.type === 'TEXT') { 7 | const newItem = {...item}; 8 | newItem.Data = sanitizeHtml(item.Data); 9 | return newItem; 10 | } 11 | return item; 12 | }); 13 | }); 14 | export default stateSanitizer; 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "ES2016", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 6 | "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | // "outDir": "./", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": false /* Disallow inconsistently-cased references to the same file. */ 65 | } 66 | } 67 | --------------------------------------------------------------------------------