├── .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 |
5 |
6 |
7 | [](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 |
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 | You need to enable JavaScript to run this app.
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 | "Use ctrl or cmd + P to open browser's Print dialog " +
11 | 'Change destination to Save as PDF ' +
12 | 'Make sure you select the Background graphics option ' +
13 | "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 |
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 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
101 |
102 |
103 |
104 |
105 | Upload!
106 |
107 |
108 |
109 |
110 | Or
111 |
112 |
113 |
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 |
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 |
94 | Cancel
95 |
96 |
102 | Upload
103 |
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 |
92 | )}
93 |
94 |
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 |
105 | Cancel
106 |
107 |
113 | Upload
114 |
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 |
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 |
58 | Cancel
59 |
60 |
61 | Save
62 |
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 |
--------------------------------------------------------------------------------