diff --git a/.eslintignore b/.eslintignore index a0270fb28c682a2460883623adea5089d515ba38..76add878f8dd778c3381fb3da45c8140db7db510 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,2 @@ -node_modules +node_modules dist \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index d901cfd2d724918d3574bc2434409c73050bde5c..130975351dcc41c7a1c9fad92e04f3f39f269fb7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,35 +1,35 @@ -module.exports = { - extends: [ - "plugin:@typescript-eslint/recommended", - "prettier/@typescript-eslint", - "plugin:prettier/recommended", - "plugin:react/recommended", - "plugin:jest/recommended", - ], - parser: "@typescript-eslint/parser", - parserOptions: { - ecmaVersion: 10, - sourceType: "module", - ecmaFeatures: { - jsx: true, - }, - }, - rules: { - "prettier/prettier": "off", - "sort-imports": "warn", - - "@typescript-eslint/array-type": ["warn", "array-simple"], - "@typescript-eslint/camelcase": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-non-null-assertion": ["warn"], - "@typescript-eslint/no-triple-slash-reference": "off", - "@typescript-eslint/prefer-interface": "off", - "react/prop-types": "off", - }, - settings: { - react: { - version: "detect", - }, - }, -}; +module.exports = { + extends: [ + "plugin:@typescript-eslint/recommended", + "prettier/@typescript-eslint", + "plugin:prettier/recommended", + "plugin:react/recommended", + "plugin:jest/recommended", + ], + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: 10, + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + rules: { + "prettier/prettier": "off", + "sort-imports": "warn", + + "@typescript-eslint/array-type": ["warn", "array-simple"], + "@typescript-eslint/camelcase": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-non-null-assertion": ["warn"], + "@typescript-eslint/no-triple-slash-reference": "off", + "@typescript-eslint/prefer-interface": "off", + "react/prop-types": "off", + }, + settings: { + react: { + version: "detect", + }, + }, +}; diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..8ed8c6fa39c94ef5cf17c4bb6310db9072243393 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,39 @@ +# These settings are for any web project + +# Handle line endings automatically for files detected as text +# and leave all files detected as binary untouched. +* text=auto + +# Force the following filetypes to have unix eols, so Windows does not break them +*.* text eol=lf + +# Windows forced line-endings +/.idea/* text eol=crlf + +# +## These files are binary and should be left untouched +# + +# (binary is a macro for -text -diff) +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.mov binary +*.mp4 binary +*.mp3 binary +*.flv binary +*.fla binary +*.swf binary +*.gz binary +*.zip binary +*.7z binary +*.ttf binary +*.eot binary +*.woff binary +*.pyc binary +*.pdf binary +*.ez binary +*.bz2 binary +*.swp binary \ No newline at end of file diff --git a/.gitignore b/.gitignore index 79dd4c79425e1495d338abf5021c2ff705ee349e..7c034dffb50429c002095b4987812305e4bdf8f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -node_modules -dist -.idea +node_modules +dist +.idea tmp \ No newline at end of file diff --git a/.huskyrc.js b/.huskyrc.js index 11c14af8223bc993cd80aa67e571647dc2705df3..bf66d49866df7d9d610b8794f2802617e5366b23 100644 --- a/.huskyrc.js +++ b/.huskyrc.js @@ -1,5 +1,5 @@ -module.exports = { - hooks: { - "pre-commit": "npm run lint-staged" - } -}; +module.exports = { + hooks: { + "pre-commit": "npm run lint-staged" + } +}; diff --git a/.lintstagedrc.js b/.lintstagedrc.js index e1c8b39a823c87cd0655845801c123137077eed6..d893755d710be64a09dd8c7ab5c5ca2b6ee9d1ad 100644 --- a/.lintstagedrc.js +++ b/.lintstagedrc.js @@ -1,3 +1,3 @@ -module.exports = { - "src/**/*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix", "git add"] -}; +module.exports = { + "src/**/*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix", "git add"] +}; diff --git a/.prettierignore b/.prettierignore index 9e6e33c768e8345611435e7a46badc34ca71d287..25c8fdbaba62c31aacfa2307975b06fbfd017485 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,2 @@ -node_modules +node_modules package-lock.json \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js index cade702ac1350db03cd97986f22690b3a26b43b9..bf14dd48068d62c8c13859f8b81d8d6b757ba851 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,6 +1,6 @@ -module.exports = { - tabWidth: 4, - useTabs: true, - printWidth: 120, - trailingComma: "es5" -}; +module.exports = { + tabWidth: 4, + useTabs: true, + printWidth: 120, + trailingComma: "es5" +}; diff --git a/.storybook/addons.js b/.storybook/addons.js index 8c5351a21c393007d3a8e9601ff9ac32213a8668..c2c02ab2f045e758a93672918dc643959642e389 100644 --- a/.storybook/addons.js +++ b/.storybook/addons.js @@ -1,2 +1,2 @@ -import "@storybook/addon-knobs/register"; - +import "@storybook/addon-knobs/register"; + diff --git a/.vscode/settings.json b/.vscode/settings.json index 0d8a488b77b2bccc9c19b1398b8d38a17b362030..2cb5ca0d0af14049a8023c624290700245f19b23 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ -{ - "npm-scripts.showStartNotification": false +{ + "npm-scripts.showStartNotification": false } \ No newline at end of file diff --git a/LICENSE b/LICENSE index fd2fa331e427be08347d3a13134f1481eb0e9705..4b6ea4fe6701002feb5f8e2ee50debdd2e1016fd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2019 Santiago González - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +MIT License + +Copyright (c) 2019 Santiago González + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 0c79476798581186ec3ea5cc98ced801ac7bdc2b..da47566e6e88247aa2d959c9bf091e456c307adc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# openfing-web - +# openfing-web + WIP \ No newline at end of file diff --git a/config/webpack.config.ts b/config/webpack.config.ts index 0ae0e4ad60538889fc4dbf35c77d50b664ae44b5..3055c109d19db16fa1159903776b4e1efe743437 100644 --- a/config/webpack.config.ts +++ b/config/webpack.config.ts @@ -1,145 +1,145 @@ -/// <reference path="../src/typings/create-file-webpack.d.ts" /> -/// <reference path="../src/typings/inline-environment-variables-webpack-plugin.d.ts" /> - -import * as CreateFileWebpack from "create-file-webpack"; -import * as HtmlWebPackPlugin from "html-webpack-plugin"; -import * as InlineEnvironmentVariablesPlugin from "inline-environment-variables-webpack-plugin"; -import * as TerserPlugin from "terser-webpack-plugin"; -import * as path from "path"; -import * as webpack from "webpack"; - -import { CleanWebpackPlugin } from "clean-webpack-plugin"; -import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; - -export const webpackConfigFactory = (env: string, isStorybook: boolean = false) => { - const publicPath = process.env.PUBLIC_URL || "/"; - const outputPath = path.resolve(process.cwd(), "dist"); - - const isProd = env === "production"; - console.log("IS STORYBOOK", isStorybook); - console.log("IS PROD", isProd); - - const minimizers = []; - - if (isProd) - minimizers.push( - new TerserPlugin({ - sourceMap: true, - extractComments: "all", - terserOptions: { - compress: { - drop_console: false, - }, - }, - }) - ); - - const plugins = [ - new HtmlWebPackPlugin({ - template: "./public/index.html", - filename: "./index.html", - }), - new webpack.NoEmitOnErrorsPlugin(), - new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), - new webpack.optimize.OccurrenceOrderPlugin(false), - new InlineEnvironmentVariablesPlugin(), - new CreateFileWebpack({ - path: outputPath, - fileName: ".htaccess", - content: `RewriteEngine On -RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR] -RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d -RewriteRule ^ - [L] -RewriteRule ^ /var/www/html/OpenFING-FW${publicPath !== "/" && publicPath !== "./" ? publicPath : ""}/index.html -`, - }), - ]; - - if (isProd) plugins.push(new CleanWebpackPlugin()); - - const resolve: { extensions: string[]; plugins: any[]; alias?: any } = { - extensions: [".ts", ".tsx", ".js", ".json"], - plugins: [new TsconfigPathsPlugin()], - }; - - if (isStorybook) - resolve.alias = { - src: path.resolve(__dirname, "../src"), - }; - - const modulesToCompile = ["openfing-core"]; - - return { - mode: env, - output: { - path: outputPath, - filename: isProd ? "js/[hash].bundle.js" : "main.js", - publicPath, - }, - module: { - rules: [ - { - test: /\.ts(x?)$/, - use: [ - { - loader: "babel-loader", - options: { - cacheDirectory: true, - }, - }, - { - loader: "ts-loader", - options: { - transpileOnly: true, - }, - }, - ], - }, - { - test: /\.js(x?)$/, - include: new RegExp(`/node_modules\/(${modulesToCompile.join("|")})/`), - use: [ - { - loader: "babel-loader", - options: { - cacheDirectory: true, - }, - }, - ], - }, - { - test: /\.html$/, - use: [ - { - loader: "html-loader", - }, - ], - }, - { - test: /\.(svg|jpg|jpeg)$/, - use: { - loader: "url-loader", - options: isStorybook - ? {} - : { - limit: 1024, - publicPath: (isProd ? publicPath : "") + "/assets", - outputPath: "assets", - }, - }, - }, - ], - }, - resolve, - plugins, - devServer: { - publicPath: "/", - historyApiFallback: true, - host: "0.0.0.0", - }, - devtool: isProd ? undefined : "source-map", - optimization: { - minimizer: minimizers, - }, - }; -}; +/// <reference path="../src/typings/create-file-webpack.d.ts" /> +/// <reference path="../src/typings/inline-environment-variables-webpack-plugin.d.ts" /> + +import * as CreateFileWebpack from "create-file-webpack"; +import * as HtmlWebPackPlugin from "html-webpack-plugin"; +import * as InlineEnvironmentVariablesPlugin from "inline-environment-variables-webpack-plugin"; +import * as TerserPlugin from "terser-webpack-plugin"; +import * as path from "path"; +import * as webpack from "webpack"; + +import { CleanWebpackPlugin } from "clean-webpack-plugin"; +import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; + +export const webpackConfigFactory = (env: string, isStorybook: boolean = false) => { + const publicPath = process.env.PUBLIC_URL || "/"; + const outputPath = path.resolve(process.cwd(), "dist"); + + const isProd = env === "production"; + console.log("IS STORYBOOK", isStorybook); + console.log("IS PROD", isProd); + + const minimizers = []; + + if (isProd) + minimizers.push( + new TerserPlugin({ + sourceMap: true, + extractComments: "all", + terserOptions: { + compress: { + drop_console: false, + }, + }, + }) + ); + + const plugins = [ + new HtmlWebPackPlugin({ + template: "./public/index.html", + filename: "./index.html", + }), + new webpack.NoEmitOnErrorsPlugin(), + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + new webpack.optimize.OccurrenceOrderPlugin(false), + new InlineEnvironmentVariablesPlugin(), + new CreateFileWebpack({ + path: outputPath, + fileName: ".htaccess", + content: `RewriteEngine On +RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR] +RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d +RewriteRule ^ - [L] +RewriteRule ^ /var/www/html/OpenFING-FW${publicPath !== "/" && publicPath !== "./" ? publicPath : ""}/index.html +`, + }), + ]; + + if (isProd) plugins.push(new CleanWebpackPlugin()); + + const resolve: { extensions: string[]; plugins: any[]; alias?: any } = { + extensions: [".ts", ".tsx", ".js", ".json"], + plugins: [new TsconfigPathsPlugin()], + }; + + if (isStorybook) + resolve.alias = { + src: path.resolve(__dirname, "../src"), + }; + + const modulesToCompile = ["openfing-core"]; + + return { + mode: env, + output: { + path: outputPath, + filename: isProd ? "js/[hash].bundle.js" : "main.js", + publicPath, + }, + module: { + rules: [ + { + test: /\.ts(x?)$/, + use: [ + { + loader: "babel-loader", + options: { + cacheDirectory: true, + }, + }, + { + loader: "ts-loader", + options: { + transpileOnly: true, + }, + }, + ], + }, + { + test: /\.js(x?)$/, + include: new RegExp(`/node_modules\/(${modulesToCompile.join("|")})/`), + use: [ + { + loader: "babel-loader", + options: { + cacheDirectory: true, + }, + }, + ], + }, + { + test: /\.html$/, + use: [ + { + loader: "html-loader", + }, + ], + }, + { + test: /\.(svg|jpg|jpeg)$/, + use: { + loader: "url-loader", + options: isStorybook + ? {} + : { + limit: 1024, + publicPath: (isProd ? publicPath : "") + "/assets", + outputPath: "assets", + }, + }, + }, + ], + }, + resolve, + plugins, + devServer: { + publicPath: "/", + historyApiFallback: true, + host: "0.0.0.0", + }, + devtool: isProd ? undefined : "source-map", + optimization: { + minimizer: minimizers, + }, + }; +}; diff --git a/package.json b/package.json index c0fda1b9b4e19ef47d3b31bf6f3089c2117abcf7..fc078b07f21f194ef795b794215385c7ad3ae5c1 100644 --- a/package.json +++ b/package.json @@ -1,103 +1,103 @@ -{ - "name": "openfing-web", - "version": "1.0.0", - "scripts": { - "start": "webpack-dev-server --env development --port 3000", - "build": "webpack --env production", - "lint-staged": "lint-staged", - "ts-check": "tsc --noEmit", - "lint:code": "eslint '*/**/*.{js,ts,tsx}'", - "lint:code:fix": "npm run lint:code -- --fix", - "storybook": "cross-env NODE_PATH=src start-storybook -p 6006 --ci", - "build-storybook": "build-storybook", - "analyze": "npm run build && webpack --profile --json > dist/stats.json && webpack-bundle-analyzer dist/stats.json", - "clean": "rmdir /s /q node_modules && del package-lock.json && npm i" - }, - "repository": { - "type": "git", - "url": "https://gitlab.fing.edu.uy/santiago.gonzalez.pereyra/openfing-web.git" - }, - "author": "Santiago Gonzalez", - "license": "MIT", - "devDependencies": { - "@babel/core": "7.4.5", - "@babel/plugin-proposal-class-properties": "7.4.4", - "@babel/plugin-proposal-decorators": "7.4.4", - "@babel/plugin-proposal-object-rest-spread": "7.4.4", - "@babel/plugin-transform-runtime": "7.4.4", - "@babel/preset-env": "7.4.5", - "@babel/preset-react": "7.0.0", - "@storybook/addon-actions": "5.1.9", - "@storybook/addon-knobs": "^5.1.9", - "@storybook/addon-links": "5.1.9", - "@storybook/addons": "5.1.9", - "@storybook/react": "5.1.9", - "@types/history": "4.7.2", - "@types/html-webpack-plugin": "^3.2.1", - "@types/query-string": "^5.1.0", - "@types/react": "16.8.22", - "@types/react-dom": "16.8.4", - "@types/react-router-dom": "4.3.4", - "@types/storybook__addon-actions": "3.4.3", - "@types/storybook__addon-knobs": "^5.0.3", - "@types/storybook__react": "4.0.2", - "@types/styled-components": "4.1.16", - "@types/terser-webpack-plugin": "^1.2.1", - "@types/url-join": "4.0.0", - "@types/uuid": "^3.4.4", - "@types/webpack": "^4.4.35", - "@typescript-eslint/eslint-plugin": "^1.12.0", - "@typescript-eslint/parser": "^1.12.0", - "babel-core": "6.26.3", - "babel-loader": "8.0.6", - "babel-plugin-styled-components": "1.10.4", - "babel-runtime": "6.26.0", - "clean-webpack-plugin": "3.0.0", - "core-js": "3.1.4", - "create-file-webpack": "1.0.2", - "cross-env": "5.2.0", - "eslint": "^6.0.1", - "eslint-config-prettier": "^6.0.0", - "eslint-loader": "^2.2.1", - "eslint-plugin-jest": "^22.11.1", - "eslint-plugin-prettier": "^3.1.0", - "eslint-plugin-react": "^7.14.2", - "file-loader": "4.0.0", - "html-loader": "0.5.5", - "html-webpack-plugin": "3.2.0", - "husky": "2.7.0", - "inline-environment-variables-webpack-plugin": "1.2.1", - "lint-staged": "^9.2.0", - "node": "^12.5.0", - "prettier": "1.18.2", - "terser-webpack-plugin": "1.3.0", - "ts-loader": "6.0.4", - "ts-node": "^8.3.0", - "tsconfig-paths-webpack-plugin": "3.2.0", - "typescript": "3.5.3", - "url-loader": "2.0.1", - "webpack": "4.35.0", - "webpack-bundle-analyzer": "3.3.2", - "webpack-cli": "3.3.5", - "webpack-dev-server": "3.7.2" - }, - "dependencies": { - "@babel/polyfill": "7.4.4", - "fullscreen-api-polyfill": "1.1.2", - "graphql": "^14.4.2", - "history": "4.9.0", - "mobx": "5.10.1", - "mobx-react-lite": "1.4.1", - "moment": "2.24.0", - "openfing-core": "git+https://gitlab.fing.edu.uy/santiago.gonzalez.pereyra/openfing-core#v1.0.0-rc2", - "path-to-regexp": "3.0.0", - "polished": "3.4.1", - "query-string": "^5.1.1", - "react": "16.8.6", - "react-dom": "16.8.6", - "regenerator-runtime": "0.13.2", - "styled-components": "4.3.2", - "url-join": "4.0.0", - "uuid": "3.3.2" - } -} +{ + "name": "openfing-web", + "version": "1.0.0", + "scripts": { + "start": "webpack-dev-server --env development --port 3000", + "build": "webpack --env production", + "lint-staged": "lint-staged", + "ts-check": "tsc --noEmit", + "lint:code": "eslint '*/**/*.{js,ts,tsx}'", + "lint:code:fix": "npm run lint:code -- --fix", + "storybook": "cross-env NODE_PATH=src start-storybook -p 6006 --ci", + "build-storybook": "build-storybook", + "analyze": "npm run build && webpack --profile --json > dist/stats.json && webpack-bundle-analyzer dist/stats.json", + "clean": "rmdir /s /q node_modules && del package-lock.json && npm i" + }, + "repository": { + "type": "git", + "url": "https://gitlab.fing.edu.uy/santiago.gonzalez.pereyra/openfing-web.git" + }, + "author": "Santiago Gonzalez", + "license": "MIT", + "devDependencies": { + "@babel/core": "7.4.5", + "@babel/plugin-proposal-class-properties": "7.4.4", + "@babel/plugin-proposal-decorators": "7.4.4", + "@babel/plugin-proposal-object-rest-spread": "7.4.4", + "@babel/plugin-transform-runtime": "7.4.4", + "@babel/preset-env": "7.4.5", + "@babel/preset-react": "7.0.0", + "@storybook/addon-actions": "5.1.9", + "@storybook/addon-knobs": "^5.1.9", + "@storybook/addon-links": "5.1.9", + "@storybook/addons": "5.1.9", + "@storybook/react": "5.1.9", + "@types/history": "4.7.2", + "@types/html-webpack-plugin": "^3.2.1", + "@types/query-string": "^5.1.0", + "@types/react": "16.8.22", + "@types/react-dom": "16.8.4", + "@types/react-router-dom": "4.3.4", + "@types/storybook__addon-actions": "3.4.3", + "@types/storybook__addon-knobs": "^5.0.3", + "@types/storybook__react": "4.0.2", + "@types/styled-components": "4.1.16", + "@types/terser-webpack-plugin": "^1.2.1", + "@types/url-join": "4.0.0", + "@types/uuid": "^3.4.4", + "@types/webpack": "^4.4.35", + "@typescript-eslint/eslint-plugin": "^1.12.0", + "@typescript-eslint/parser": "^1.12.0", + "babel-core": "6.26.3", + "babel-loader": "8.0.6", + "babel-plugin-styled-components": "1.10.4", + "babel-runtime": "6.26.0", + "clean-webpack-plugin": "3.0.0", + "core-js": "3.1.4", + "create-file-webpack": "1.0.2", + "cross-env": "5.2.0", + "eslint": "^6.0.1", + "eslint-config-prettier": "^6.0.0", + "eslint-loader": "^2.2.1", + "eslint-plugin-jest": "^22.11.1", + "eslint-plugin-prettier": "^3.1.0", + "eslint-plugin-react": "^7.14.2", + "file-loader": "4.0.0", + "html-loader": "0.5.5", + "html-webpack-plugin": "3.2.0", + "husky": "2.7.0", + "inline-environment-variables-webpack-plugin": "1.2.1", + "lint-staged": "^9.2.0", + "node": "^12.5.0", + "prettier": "1.18.2", + "terser-webpack-plugin": "1.3.0", + "ts-loader": "6.0.4", + "ts-node": "^8.3.0", + "tsconfig-paths-webpack-plugin": "3.2.0", + "typescript": "3.5.3", + "url-loader": "2.0.1", + "webpack": "4.35.0", + "webpack-bundle-analyzer": "3.3.2", + "webpack-cli": "3.3.5", + "webpack-dev-server": "3.7.2" + }, + "dependencies": { + "@babel/polyfill": "7.4.4", + "fullscreen-api-polyfill": "1.1.2", + "graphql": "^14.4.2", + "history": "4.9.0", + "mobx": "5.10.1", + "mobx-react-lite": "1.4.1", + "moment": "2.24.0", + "openfing-core": "git+https://gitlab.fing.edu.uy/santiago.gonzalez.pereyra/openfing-core#v1.0.0-rc2", + "path-to-regexp": "3.0.0", + "polished": "3.4.1", + "query-string": "^5.1.1", + "react": "16.8.6", + "react-dom": "16.8.6", + "regenerator-runtime": "0.13.2", + "styled-components": "4.3.2", + "url-join": "4.0.0", + "uuid": "3.3.2" + } +} diff --git a/src/appstore/NavigationState.ts b/src/appstore/NavigationState.ts index 0f916ed18ce8d2bc35d69119dbbffecea6adfdce..076252e828db26c8de15616a90cde8694c5111c8 100644 --- a/src/appstore/NavigationState.ts +++ b/src/appstore/NavigationState.ts @@ -1,162 +1,162 @@ -// import { History } from "history"; -// import { action, observable } from "mobx"; -// -// export type NavigateOptions = { replace?: boolean; params?: any }; -// -// export type NavigateToHomeOptions = NavigateOptions; -// -// // export type NavigateToCoursePathOptions = { -// // course: Models.Course | string; -// // courseClass?: Models.CourseClass; -// // t?: number; -// // }; -// // export type NavigateToCourseOptions = NavigateOptions & { -// // course: Models.Course | string; -// // courseClass?: Models.CourseClass; -// // }; -// // export type NavigateToCreateCourseClassContext = { -// // course: Models.Course; -// // }; -// // export type NavigateToUpdateCourseContext = { -// // course: Models.Course; -// // }; -// // export type NavigateToUpdateCourseClassContext = { -// // courseClass: Models.CourseClass; -// // course: Models.Course; -// // }; -// // export type NavigateToUpdateFAQContext = { -// // faq: Models.FAQ; -// // }; -// -// export class NavigationState { -// public baseURL: string; -// -// @observable -// public history: History; -// -// public setBaseURL(baseURL: string) { -// this.baseURL = baseURL; -// } -// -// @action -// public setHistory(history: History) { -// this.history = history; -// } -// -// @bind -// public goBack() { -// const { history } = this; -// history.goBack(); -// } -// -// public getHomePath(): string { -// return "/"; -// } -// -// @bind -// public navigateToHome(options: NavigateToHomeOptions = {}) { -// this.navigate(this.getHomePath(), options); -// } -// -// public getCoursePath(options: NavigateToCoursePathOptions) { -// const { course, courseClass, t } = options; -// -// let result = `/courses/${typeof course === "string" ? course : course.code}`; -// -// if (courseClass !== undefined) { -// result += `/${courseClass.number}`; -// -// if (t !== undefined) result += `?t=${t}`; -// } -// -// return result; -// } -// -// @bind -// public navigateToCourse(options: NavigateToCourseOptions) { -// this.navigate(this.getCoursePath(options), options); -// } -// -// public getCreateCoursePath() { -// return `/courses/create`; -// } -// -// @bind -// public navigateToCreateCourse() { -// this.navigate(this.getCreateCoursePath(), {}); -// } -// -// public getCreateCourseClassPath(courseCode: string) { -// return `/courses/${courseCode}/createClass`; -// } -// -// @bind -// public navigateToCreateCourseClass({ course }: NavigateToCreateCourseClassContext, options?: NavigateOptions) { -// this.navigate(this.getCreateCourseClassPath(course.code), options); -// } -// -// public getUpdateCoursePath(courseCode: string) { -// return `/courses/${courseCode}/update`; -// } -// -// @bind -// public navigateToUpdateCourse({ course }: NavigateToUpdateCourseContext, options?: NavigateOptions) { -// this.navigate(this.getUpdateCoursePath(course.code), options); -// } -// -// public getUpdateCourseClassPath(courseCode: string, courseClassNo: string | number) { -// return `/courses/${courseCode}/${courseClassNo}/update`; -// } -// -// @bind -// public navigateToUpdateCourseClass( -// { courseClass, course }: NavigateToUpdateCourseClassContext, -// options?: NavigateOptions -// ) { -// this.navigate(this.getUpdateCourseClassPath(course.code, courseClass.number!), options); -// } -// -// public getCoursesPath() { -// return `/courses`; -// } -// -// @bind -// public navigateToCourses(options?: NavigateOptions) { -// this.navigate(this.getCoursesPath(), options); -// } -// -// public getCreateFAQPath() { -// return `/faqs/create`; -// } -// -// @bind -// public navigateToCreateFAQ(options?: NavigateOptions) { -// this.navigate(this.getCreateFAQPath(), options); -// } -// -// public getFAQsPath() { -// return `/faqs`; -// } -// -// @bind -// public navigateToFAQs(options?: NavigateOptions) { -// this.navigate(this.getFAQsPath(), options); -// } -// -// public getUpdateFAQPath({ faq }: NavigateToUpdateFAQContext) { -// return `/faqs/${faq.id}/update`; -// } -// -// @bind -// public navigateToUpdateFAQ(context: NavigateToUpdateFAQContext, options?: NavigateOptions) { -// this.navigate(this.getUpdateFAQPath(context), options); -// } -// -// @bind -// protected navigate<TParams extends object>(path: string, options: NavigateOptions = {}, params?: TParams) { -// const { history } = this; -// -// if (options.replace) history.replace(path); -// else history.push(path, params); -// } -// } +// import { History } from "history"; +// import { action, observable } from "mobx"; +// +// export type NavigateOptions = { replace?: boolean; params?: any }; +// +// export type NavigateToHomeOptions = NavigateOptions; +// +// // export type NavigateToCoursePathOptions = { +// // course: Models.Course | string; +// // courseClass?: Models.CourseClass; +// // t?: number; +// // }; +// // export type NavigateToCourseOptions = NavigateOptions & { +// // course: Models.Course | string; +// // courseClass?: Models.CourseClass; +// // }; +// // export type NavigateToCreateCourseClassContext = { +// // course: Models.Course; +// // }; +// // export type NavigateToUpdateCourseContext = { +// // course: Models.Course; +// // }; +// // export type NavigateToUpdateCourseClassContext = { +// // courseClass: Models.CourseClass; +// // course: Models.Course; +// // }; +// // export type NavigateToUpdateFAQContext = { +// // faq: Models.FAQ; +// // }; +// +// export class NavigationState { +// public baseURL: string; +// +// @observable +// public history: History; +// +// public setBaseURL(baseURL: string) { +// this.baseURL = baseURL; +// } +// +// @action +// public setHistory(history: History) { +// this.history = history; +// } +// +// @bind +// public goBack() { +// const { history } = this; +// history.goBack(); +// } +// +// public getHomePath(): string { +// return "/"; +// } +// +// @bind +// public navigateToHome(options: NavigateToHomeOptions = {}) { +// this.navigate(this.getHomePath(), options); +// } +// +// public getCoursePath(options: NavigateToCoursePathOptions) { +// const { course, courseClass, t } = options; +// +// let result = `/courses/${typeof course === "string" ? course : course.code}`; +// +// if (courseClass !== undefined) { +// result += `/${courseClass.number}`; +// +// if (t !== undefined) result += `?t=${t}`; +// } +// +// return result; +// } +// +// @bind +// public navigateToCourse(options: NavigateToCourseOptions) { +// this.navigate(this.getCoursePath(options), options); +// } +// +// public getCreateCoursePath() { +// return `/courses/create`; +// } +// +// @bind +// public navigateToCreateCourse() { +// this.navigate(this.getCreateCoursePath(), {}); +// } +// +// public getCreateCourseClassPath(courseCode: string) { +// return `/courses/${courseCode}/createClass`; +// } +// +// @bind +// public navigateToCreateCourseClass({ course }: NavigateToCreateCourseClassContext, options?: NavigateOptions) { +// this.navigate(this.getCreateCourseClassPath(course.code), options); +// } +// +// public getUpdateCoursePath(courseCode: string) { +// return `/courses/${courseCode}/update`; +// } +// +// @bind +// public navigateToUpdateCourse({ course }: NavigateToUpdateCourseContext, options?: NavigateOptions) { +// this.navigate(this.getUpdateCoursePath(course.code), options); +// } +// +// public getUpdateCourseClassPath(courseCode: string, courseClassNo: string | number) { +// return `/courses/${courseCode}/${courseClassNo}/update`; +// } +// +// @bind +// public navigateToUpdateCourseClass( +// { courseClass, course }: NavigateToUpdateCourseClassContext, +// options?: NavigateOptions +// ) { +// this.navigate(this.getUpdateCourseClassPath(course.code, courseClass.number!), options); +// } +// +// public getCoursesPath() { +// return `/courses`; +// } +// +// @bind +// public navigateToCourses(options?: NavigateOptions) { +// this.navigate(this.getCoursesPath(), options); +// } +// +// public getCreateFAQPath() { +// return `/faqs/create`; +// } +// +// @bind +// public navigateToCreateFAQ(options?: NavigateOptions) { +// this.navigate(this.getCreateFAQPath(), options); +// } +// +// public getFAQsPath() { +// return `/faqs`; +// } +// +// @bind +// public navigateToFAQs(options?: NavigateOptions) { +// this.navigate(this.getFAQsPath(), options); +// } +// +// public getUpdateFAQPath({ faq }: NavigateToUpdateFAQContext) { +// return `/faqs/${faq.id}/update`; +// } +// +// @bind +// public navigateToUpdateFAQ(context: NavigateToUpdateFAQContext, options?: NavigateOptions) { +// this.navigate(this.getUpdateFAQPath(context), options); +// } +// +// @bind +// protected navigate<TParams extends object>(path: string, options: NavigateOptions = {}, params?: TParams) { +// const { history } = this; +// +// if (options.replace) history.replace(path); +// else history.push(path, params); +// } +// } diff --git a/src/appstore/StorageState.ts b/src/appstore/StorageState.ts index 6d02e9fdc1c15a8df565756f1cd767f128b03638..1b96c8eb4d7b56f3a22e8aac7bb8bb0f78b64361 100644 --- a/src/appstore/StorageState.ts +++ b/src/appstore/StorageState.ts @@ -1,21 +1,21 @@ -// import localforage from "localforage"; -// -// enum StorageKeys { -// TOKEN = "token" -// } -// -// export class StorageState { -// public async getToken(): Promise<string | null> { -// const token = await localforage.getItem<any>(StorageKeys.TOKEN); -// -// return Promise.resolve(typeof token === "string" ? token : null); -// } -// -// public async setToken(token: string) { -// return localforage.setItem(StorageKeys.TOKEN, token); -// } -// -// public deleteToken() { -// localforage.removeItem(StorageKeys.TOKEN); -// } -// } +// import localforage from "localforage"; +// +// enum StorageKeys { +// TOKEN = "token" +// } +// +// export class StorageState { +// public async getToken(): Promise<string | null> { +// const token = await localforage.getItem<any>(StorageKeys.TOKEN); +// +// return Promise.resolve(typeof token === "string" ? token : null); +// } +// +// public async setToken(token: string) { +// return localforage.setItem(StorageKeys.TOKEN, token); +// } +// +// public deleteToken() { +// localforage.removeItem(StorageKeys.TOKEN); +// } +// } diff --git a/src/appstore/index.ts b/src/appstore/index.ts index 7e2ab7ed101f062edf863393bfbd38fe0cb70dcf..92c98f80a636a60beed505d64efd52108bc4a8bb 100644 --- a/src/appstore/index.ts +++ b/src/appstore/index.ts @@ -1 +1 @@ -export { AppStore } from "./AppStore"; +export { AppStore } from "./AppStore"; diff --git a/src/assets/images/CreativeCommons.svg b/src/assets/images/CreativeCommons.svg index 083c1c76c527672c704fb0629be63c5b4ee59721..f488767d1ddb2c0150044c3cc05516bf08973ec1 100644 --- a/src/assets/images/CreativeCommons.svg +++ b/src/assets/images/CreativeCommons.svg @@ -1,242 +1,242 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://web.resource.org/cc/" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - viewBox="0 0 120 42" - id="svg2759" - sodipodi:version="0.32" - inkscape:version="0.45+devel" - version="1.0" - sodipodi:docname="by-nc-nd.svg" - inkscape:output_extension="org.inkscape.output.svg.inkscape"> - <defs - id="defs2761" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#8b8b8b" - borderopacity="1" - gridtolerance="10000" - guidetolerance="10" - objecttolerance="10" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="1" - inkscape:cx="179" - inkscape:cy="89.569904" - inkscape:document-units="px" - inkscape:current-layer="layer1" - width="120px" - height="42px" - inkscape:showpageshadow="false" - inkscape:window-width="1198" - inkscape:window-height="624" - inkscape:window-x="488" - inkscape:window-y="401" /> - <metadata - id="metadata2764"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1"> - <g - transform="matrix(0.9937728,0,0,0.9936696,-437.11979,0)" - id="g361" - inkscape:export-filename="/mnt/hgfs/Bov/Documents/Work/2007/cc/identity/srr buttons/big/by-nc-nd.png" - inkscape:export-xdpi="300.23013" - inkscape:export-ydpi="300.23013"> - <path - id="path3817_4_" - nodetypes="ccccccc" - d="M 443.28955,0.44873 L 557.35303,0.65185 C 558.94678,0.65185 560.37061,0.41503 560.37061,3.83203 L 560.23096,41.39892 L 440.41064,41.39892 L 440.41064,3.69238 C 440.41064,2.00781 440.57373,0.44873 443.28955,0.44873 z" - style="fill:#aab2ab" /> - - <path - d="M 558.3501,0 L 442.12061,0 C 440.87354,0 439.85889,1.01465 439.85889,2.26123 L 439.85889,41.75732 C 439.85889,42.03906 440.08741,42.26757 440.36963,42.26757 L 560.1001,42.26757 C 560.38233,42.26757 560.61084,42.03905 560.61084,41.75732 L 560.61084,2.26123 C 560.61084,1.01465 559.59619,0 558.3501,0 z M 442.12061,1.02148 L 558.3501,1.02148 C 559.03369,1.02148 559.58936,1.57763 559.58936,2.26123 C 559.58936,2.26123 559.58936,18.15234 559.58936,29.64893 L 476.51612,29.64893 C 473.4712,35.1543 467.60401,38.89258 460.87159,38.89258 C 454.13721,38.89258 448.27198,35.15772 445.22901,29.64893 L 440.88038,29.64893 C 440.88038,18.15235 440.88038,2.26123 440.88038,2.26123 C 440.88037,1.57764 441.43701,1.02148 442.12061,1.02148 z" - id="path364" /> - - <g - id="g5908_4_" - transform="matrix(0.872921,0,0,0.872921,50.12536,143.2144)"> - - <path - id="path5906_4_" - cx="296.35416" - ry="22.939548" - cy="264.3577" - type="arc" - rx="22.939548" - d="M 486.26709,-141.53052 C 486.27271,-132.85028 479.2392,-125.80957 470.55902,-125.80341 C 461.87878,-125.79841 454.83752,-132.83124 454.83191,-141.51148 C 454.83191,-141.51819 454.83191,-141.52436 454.83191,-141.53052 C 454.82629,-150.21186 461.85974,-157.25257 470.53998,-157.25763 C 479.22132,-157.26263 486.26264,-150.22974 486.26709,-141.5495 C 486.26709,-141.54395 486.26709,-141.53723 486.26709,-141.53052 z" - style="fill:#ffffff" /> - - <g - id="g5706_4_" - transform="translate(-289.6157,99.0653)"> - <path - id="path5708_4_" - d="M 772.94281,-253.39801 C 776.42761,-249.9126 778.17059,-245.64465 778.17059,-240.59582 C 778.17059,-235.54644 776.45782,-231.32434 773.03228,-227.92785 C 769.39642,-224.35186 765.10046,-222.56414 760.14227,-222.56414 C 755.2445,-222.56414 751.0224,-224.33672 747.47827,-227.88366 C 743.93188,-231.4295 742.15985,-235.66668 742.15985,-240.59582 C 742.15985,-245.52435 743.93188,-249.79174 747.47827,-253.39801 C 750.93292,-256.88507 755.15497,-258.6275 760.14227,-258.6275 C 765.1911,-258.6275 769.45685,-256.88507 772.94281,-253.39801 z M 749.82422,-251.05371 C 746.8775,-248.07733 745.40412,-244.59082 745.40412,-240.59131 C 745.40412,-236.59302 746.86298,-233.13611 749.77839,-230.22016 C 752.69605,-227.30421 756.16743,-225.84595 760.19599,-225.84595 C 764.22455,-225.84595 767.72614,-227.31873 770.70307,-230.2649 C 773.529,-233.00074 774.94196,-236.44196 774.94196,-240.59132 C 774.94196,-244.70936 773.5055,-248.20485 770.63373,-251.07606 C 767.76306,-253.94673 764.28382,-255.38264 760.19599,-255.38264 C 756.10816,-255.38264 752.64905,-253.93945 749.82422,-251.05371 z M 757.57812,-242.35052 C 757.12841,-243.33221 756.45495,-243.82281 755.55548,-243.82281 C 753.96692,-243.82281 753.17261,-242.75329 753.17261,-240.61425 C 753.17261,-238.47472 753.96692,-237.40575 755.55548,-237.40575 C 756.60486,-237.40575 757.35443,-237.9265 757.80414,-238.97026 L 760.0069,-237.79729 C 758.95642,-235.93181 757.38123,-234.99822 755.28138,-234.99822 C 753.66151,-234.99822 752.36378,-235.49499 751.38935,-236.48784 C 750.41383,-237.48125 749.92603,-238.85057 749.92603,-240.59581 C 749.92603,-242.31139 750.42945,-243.67284 751.43409,-244.68138 C 752.43873,-245.68992 753.69172,-246.19334 755.1919,-246.19334 C 757.4126,-246.19334 759.00117,-245.31907 759.96326,-243.57103 L 757.57812,-242.35052 z M 767.94208,-242.35052 C 767.49121,-243.33221 766.83002,-243.82281 765.95966,-243.82281 C 764.33863,-243.82281 763.52753,-242.75329 763.52753,-240.61425 C 763.52753,-238.47472 764.33863,-237.40575 765.95966,-237.40575 C 767.0113,-237.40575 767.74738,-237.9265 768.16694,-238.97026 L 770.41895,-237.79729 C 769.37067,-235.93181 767.79773,-234.99822 765.70124,-234.99822 C 764.08356,-234.99822 762.78919,-235.49499 761.81477,-236.48784 C 760.8426,-237.48125 760.35487,-238.85057 760.35487,-240.59581 C 760.35487,-242.31139 760.84932,-243.67284 761.83827,-244.68138 C 762.82612,-245.68992 764.08357,-246.19334 765.61177,-246.19334 C 767.82796,-246.19334 769.41542,-245.31907 770.37306,-243.57103 L 767.94208,-242.35052 z" /> - - </g> - - </g> - - <g - enable-background="new " - id="g370"> - <path - d="M 488.25342,32.95605 C 488.5708,32.95605 488.86182,32.98437 489.12354,33.04003 C 489.38526,33.09569 489.60889,33.18749 489.79639,33.31542 C 489.98291,33.44237 490.12744,33.6123 490.23096,33.82323 C 490.3335,34.03514 490.38526,34.29589 490.38526,34.60741 C 490.38526,34.94335 490.30909,35.22264 490.15577,35.44628 C 490.00343,35.67089 489.77784,35.85351 489.47804,35.99706 C 489.89015,36.11522 490.19777,36.32226 490.40089,36.61815 C 490.60401,36.91404 490.70558,37.27049 490.70558,37.68749 C 490.70558,38.02343 490.64015,38.31444 490.50929,38.56054 C 490.37843,38.80566 490.20167,39.00683 489.98097,39.1621 C 489.75929,39.31835 489.50636,39.43358 489.22316,39.5078 C 488.93898,39.583 488.64796,39.6201 488.34816,39.6201 L 485.11183,39.6201 L 485.11183,32.95604 L 488.25342,32.95604 L 488.25342,32.95605 z M 488.06689,35.65137 C 488.32763,35.65137 488.54345,35.58887 488.71142,35.46485 C 488.87939,35.34083 488.96337,35.13965 488.96337,34.86036 C 488.96337,34.70509 488.93505,34.57716 488.87939,34.47852 C 488.82275,34.37891 488.74853,34.30176 488.65478,34.24512 C 488.56103,34.18946 488.45361,34.15039 488.33251,34.12891 C 488.21141,34.10743 488.08446,34.09668 487.9536,34.09668 L 486.58055,34.09668 L 486.58055,35.65137 L 488.06689,35.65137 z M 488.15186,38.47949 C 488.29541,38.47949 488.43213,38.46582 488.56299,38.4375 C 488.69385,38.40918 488.80908,38.3623 488.90967,38.29785 C 489.00928,38.23242 489.08838,38.14355 489.14795,38.03125 C 489.20752,37.91992 489.23682,37.77637 489.23682,37.60254 C 489.23682,37.26074 489.14014,37.0166 488.94678,36.87012 C 488.75342,36.72461 488.49854,36.65137 488.18018,36.65137 L 486.58057,36.65137 L 486.58057,38.47949 L 488.15186,38.47949 z" - id="path372" - style="fill:#ffffff" /> - - <path - d="M 490.96436,32.95605 L 492.60791,32.95605 L 494.16846,35.58789 L 495.71924,32.95605 L 497.35303,32.95605 L 494.8794,37.0625 L 494.8794,39.62012 L 493.41065,39.62012 L 493.41065,37.02539 L 490.96436,32.95605 z" - id="path374" - style="fill:#ffffff" /> - - </g> - - <g - enable-background="new " - id="g376"> - <path - d="M 512.83057,32.95605 L 515.61475,37.42675 L 515.63037,37.42675 L 515.63037,32.95605 L 517.00537,32.95605 L 517.00537,39.62011 L 515.53955,39.62011 L 512.76611,35.1582 L 512.74756,35.1582 L 512.74756,39.62011 L 511.37256,39.62011 L 511.37256,32.95605 L 512.83057,32.95605 z" - id="path378" - style="fill:#ffffff" /> - - <path - d="M 522.56885,34.73145 C 522.48194,34.59083 522.37256,34.46778 522.2417,34.36231 C 522.11084,34.25684 521.96338,34.17383 521.79834,34.11524 C 521.6333,34.05567 521.46045,34.02637 521.28076,34.02637 C 520.95068,34.02637 520.67041,34.08985 520.43994,34.21778 C 520.20947,34.34473 520.02295,34.51563 519.88037,34.73048 C 519.73682,34.94532 519.63232,35.18946 519.56689,35.4629 C 519.50146,35.73634 519.46923,36.01954 519.46923,36.31153 C 519.46923,36.5918 519.50146,36.86426 519.56689,37.12794 C 519.63232,37.39259 519.73681,37.63087 519.88037,37.84181 C 520.02295,38.05372 520.20947,38.22267 520.43994,38.3506 C 520.67041,38.47853 520.95068,38.54201 521.28076,38.54201 C 521.72803,38.54201 522.07861,38.40529 522.33056,38.13088 C 522.58251,37.85744 522.73681,37.49611 522.79247,37.04787 L 524.21142,37.04787 C 524.17431,37.46486 524.07763,37.84182 523.92236,38.17775 C 523.76709,38.51466 523.56103,38.8008 523.30615,39.0381 C 523.05127,39.2754 522.75244,39.45607 522.40967,39.58107 C 522.06787,39.70607 521.69092,39.76857 521.28076,39.76857 C 520.77002,39.76857 520.31103,39.6797 519.90283,39.50197 C 519.4956,39.32521 519.15088,39.08009 518.8706,38.76955 C 518.58935,38.45803 518.37451,38.09182 518.22509,37.67189 C 518.07568,37.25099 518.00048,36.79884 518.00048,36.31251 C 518.00048,35.81446 518.07568,35.35255 518.22509,34.92579 C 518.3745,34.49903 518.58935,34.12696 518.8706,33.80958 C 519.15087,33.4922 519.4956,33.24317 519.90283,33.06251 C 520.31103,32.88185 520.77002,32.792 521.28076,32.792 C 521.64795,32.792 521.99463,32.84473 522.32178,32.95118 C 522.64795,33.05665 522.94092,33.21095 523.19873,33.41407 C 523.45752,33.61622 523.67041,33.86719 523.83838,34.16602 C 524.00635,34.46485 524.11182,34.80762 524.15576,35.19336 L 522.73681,35.19336 C 522.7124,35.02539 522.65576,34.87109 522.56885,34.73145 z" - id="path380" - style="fill:#ffffff" /> - - </g> - - <g - enable-background="new " - id="g382"> - <path - d="M 538.83057,32.95605 L 541.61475,37.42675 L 541.63037,37.42675 L 541.63037,32.95605 L 543.00537,32.95605 L 543.00537,39.62011 L 541.53955,39.62011 L 538.76611,35.1582 L 538.74756,35.1582 L 538.74756,39.62011 L 537.37256,39.62011 L 537.37256,32.95605 L 538.83057,32.95605 z" - id="path384" - style="fill:#ffffff" /> - - <path - d="M 547.16748,32.95605 C 547.59814,32.95605 547.99756,33.02441 548.36865,33.16113 C 548.73974,33.29785 549.06006,33.5039 549.33154,33.77734 C 549.60205,34.05078 549.81396,34.39355 549.96631,34.80371 C 550.11963,35.21484 550.1958,35.69726 550.1958,36.25098 C 550.1958,36.73633 550.1333,37.1836 550.00928,37.59473 C 549.88428,38.00489 549.6958,38.36035 549.44385,38.65821 C 549.19092,38.95704 548.87647,39.19239 548.49951,39.36329 C 548.12255,39.53419 547.6792,39.62013 547.16748,39.62013 L 544.28955,39.62013 L 544.28955,32.95607 L 547.16748,32.95607 L 547.16748,32.95605 z M 547.06494,38.38574 C 547.27685,38.38574 547.48193,38.35156 547.68115,38.2832 C 547.88037,38.21484 548.0581,38.10156 548.21338,37.94238 C 548.36865,37.78418 548.49365,37.57812 548.5874,37.32324 C 548.68017,37.06836 548.72705,36.75683 548.72705,36.39062 C 548.72705,36.05468 548.69482,35.75195 548.62939,35.48144 C 548.56396,35.21093 548.45654,34.97949 548.30712,34.7871 C 548.1577,34.59471 547.96044,34.44628 547.71435,34.34374 C 547.46826,34.2412 547.16455,34.19042 546.80419,34.19042 L 545.75829,34.19042 L 545.75829,38.38573 L 547.06494,38.38573 L 547.06494,38.38574 z" - id="path386" - style="fill:#ffffff" /> - - </g> - - <g - id="g6370_1_" - transform="translate(286.1464,208.0498)"> - <g - id="g7610_1_" - transform="matrix(1.146822,0,0,1.146822,-67.14005,-41.89676)"> - - <path - id="path6372_1_" - cx="475.97119" - ry="29.209877" - cy="252.08646" - type="arc" - rx="29.209877" - d="M 269.61823,-131.7348 C 269.62247,-126.90787 265.71222,-122.99292 260.88486,-122.98907 C 256.05832,-122.98611 252.14295,-126.89593 252.13956,-131.72204 C 252.13956,-131.72714 252.13956,-131.73098 252.13956,-131.7348 C 252.13614,-136.56216 256.04642,-140.47711 260.87293,-140.48095 C 265.69944,-140.48394 269.61481,-136.57409 269.61823,-131.74801 C 269.61823,-131.74374 269.61823,-131.73907 269.61823,-131.7348 z" - style="fill:#ffffff" /> - - <path - id="path6374_1_" - d="M 260.86526,-141.90982 C 263.71875,-141.90982 266.12945,-140.9263 268.09909,-138.95969 C 270.06869,-136.99219 271.05392,-134.58362 271.05392,-131.73481 C 271.05392,-128.88642 270.08572,-126.5034 268.15017,-124.58659 C 266.09539,-122.56843 263.6668,-121.56022 260.86526,-121.56022 C 258.0986,-121.56022 255.71261,-122.56077 253.70892,-124.5619 C 251.70526,-126.56214 250.70385,-128.95368 250.70385,-131.73481 C 250.70385,-134.51637 251.70525,-136.92493 253.70892,-138.95969 C 255.6615,-140.9263 258.04752,-141.90982 260.86526,-141.90982 z M 252.9928,-134.46866 C 252.68964,-133.6099 252.53723,-132.69876 252.53723,-131.7348 C 252.53723,-129.47952 253.36151,-127.5299 255.00839,-125.88431 C 256.65524,-124.23999 258.61636,-123.41742 260.89166,-123.41742 C 263.1661,-123.41742 265.14422,-124.24808 266.82516,-125.91028 C 267.38803,-126.45398 267.85214,-127.04709 268.21572,-127.69 L 264.37954,-129.39776 C 264.11984,-128.10726 262.96941,-127.23571 261.5797,-127.13351 L 261.5797,-125.56457 L 260.41137,-125.56457 L 260.41137,-127.13351 C 259.26946,-127.1463 258.16674,-127.61337 257.32287,-128.35165 L 258.72448,-129.76477 C 259.39892,-129.12952 260.07418,-128.8447 260.99554,-128.8447 C 261.59246,-128.8447 262.25412,-129.07801 262.25412,-129.8559 C 262.25412,-130.13135 262.14767,-130.32297 261.97992,-130.46729 L 261.00919,-130.89817 L 259.8017,-131.43677 C 259.20392,-131.70331 258.69724,-131.92768 258.18973,-132.15418 L 252.9928,-134.46866 z M 260.89166,-140.07861 C 258.58145,-140.07861 256.6297,-139.26495 255.03308,-137.63638 C 254.59878,-137.19784 254.2207,-136.74014 253.90054,-136.26199 L 257.78952,-134.52996 C 258.1412,-135.60931 259.16644,-136.26412 260.41138,-136.33651 L 260.41138,-137.90548 L 261.57971,-137.90548 L 261.57971,-136.33651 C 262.3844,-136.29775 263.2666,-136.07723 264.13601,-135.40365 L 262.7991,-134.02926 C 262.30606,-134.37927 261.68359,-134.62536 261.06027,-134.62536 C 260.55444,-134.62536 259.83999,-134.47036 259.83999,-133.83468 C 259.83999,-133.73803 259.87322,-133.65289 259.93197,-133.57626 L 261.23312,-132.99807 L 262.11361,-132.60549 C 262.67648,-132.35387 263.21465,-132.11544 263.74685,-131.87829 L 268.96084,-129.557 C 269.13284,-130.2395 269.21969,-130.96587 269.21969,-131.7348 C 269.21969,-134.05823 268.40478,-136.02569 266.77493,-137.63638 C 265.16129,-139.26495 263.20102,-140.07861 260.89166,-140.07861 z" /> - - </g> - - </g> - - <g - id="g6394_1_" - transform="matrix(0.624995,0,0,0.624995,312.8511,316.9328)"> - - <path - id="path6396_1_" - cx="475.97119" - ry="29.209877" - cy="252.08646" - type="arc" - rx="29.209877" - d="M 387.83435,-482.97366 C 387.84216,-473.56265 380.2171,-465.92666 370.80609,-465.91885 C 361.39349,-465.91342 353.75751,-473.53689 353.75122,-482.95022 C 353.75122,-482.9573 353.75122,-482.96664 353.75122,-482.97366 C 353.74499,-492.38546 361.36847,-500.02145 370.78107,-500.02853 C 380.19208,-500.03634 387.82807,-492.41128 387.83435,-482.99948 C 387.83435,-482.99008 387.83435,-482.98306 387.83435,-482.97366 z" - style="fill:#ffffff" /> - - <g - id="g6398_1_" - transform="translate(-23.9521,-87.92102)"> - <path - id="path6400_1_" - d="M 394.47845,-413.72311 C 389.30651,-413.72311 384.9284,-411.92001 381.34552,-408.30978 C 377.66895,-404.5762 375.83142,-400.15817 375.83142,-395.05264 C 375.83142,-389.94949 377.66894,-385.56271 381.34552,-381.89084 C 385.02057,-378.21896 389.40027,-376.38297 394.47845,-376.38297 C 399.61914,-376.38297 404.07385,-378.23533 407.84417,-381.93613 C 411.39422,-385.45334 413.17236,-389.82608 413.17236,-395.05265 C 413.17236,-400.28239 411.36456,-404.69962 407.75042,-408.30979 C 404.13635,-411.92001 399.7113,-413.72311 394.47845,-413.72311 z M 394.5238,-410.36453 C 398.76129,-410.36453 402.3598,-408.86996 405.32074,-405.88168 C 408.3114,-402.92617 409.80518,-399.31753 409.80518,-395.05264 C 409.80518,-390.75888 408.34266,-387.19638 405.41449,-384.36508 C 402.32855,-381.31503 398.69879,-379.79239 394.5238,-379.79239 C 390.34875,-379.79239 386.7503,-381.3002 383.72839,-384.31979 C 380.70648,-387.337 379.19555,-390.91592 379.19555,-395.05264 C 379.19555,-399.19333 380.7221,-402.80197 383.77679,-405.88168 C 386.70496,-408.86996 390.28625,-410.36453 394.5238,-410.36453 z" /> - - <g - id="g6402_1_"> - <path - id="path6404_1_" - d="M 401.55505,-399.47849 L 387.98468,-399.47849 L 387.98468,-396.26359 L 401.55505,-396.26359 L 401.55505,-399.47849 z M 401.55505,-393.47763 L 387.98468,-393.47763 L 387.98468,-390.26358 L 401.55505,-390.26358 L 401.55505,-393.47763 z" /> - - </g> - - </g> - - </g> - - <g - id="g398"> - <circle - cx="491.9473" - cy="15.31396" - r="10.80615" - id="circle400" - sodipodi:cx="491.9473" - sodipodi:cy="15.31396" - sodipodi:rx="10.80615" - sodipodi:ry="10.80615" - style="fill:#ffffff" /> - - <g - id="g402"> - <path - d="M 495.07474,12.18701 C 495.07474,11.77051 494.73685,11.43359 494.32083,11.43359 L 489.54837,11.43359 C 489.13235,11.43359 488.79446,11.7705 488.79446,12.18701 L 488.79446,16.95996 L 490.12551,16.95996 L 490.12551,22.6123 L 493.7427,22.6123 L 493.7427,16.95996 L 495.07473,16.95996 L 495.07473,12.18701 L 495.07474,12.18701 z" - id="path404" /> - - <circle - cx="491.9346" - cy="9.1723604" - r="1.63232" - id="circle406" - sodipodi:cx="491.9346" - sodipodi:cy="9.1723604" - sodipodi:rx="1.63232" - sodipodi:ry="1.63232" /> - - </g> - - <path - clip-rule="evenodd" - d="M 491.91946,3.40771 C 488.68801,3.40771 485.95169,4.53515 483.71243,6.7915 C 481.41458,9.12451 480.26614,11.88671 480.26614,15.07568 C 480.26614,18.26465 481.41458,21.00781 483.71243,23.30273 C 486.01028,25.59716 488.74661,26.74462 491.91946,26.74462 C 495.13235,26.74462 497.91751,25.58788 500.27395,23.27294 C 502.49368,21.07616 503.60305,18.34325 503.60305,15.07567 C 503.60305,11.80809 502.47414,9.04735 500.21535,6.79149 C 497.95657,4.53516 495.19193,3.40771 491.91946,3.40771 z M 491.94974,5.50732 C 494.59818,5.50732 496.84622,6.44091 498.69583,8.3081 C 500.56595,10.15527 501.50052,12.41162 501.50052,15.07568 C 501.50052,17.75927 500.58548,19.98681 498.75443,21.75634 C 496.8267,23.6621 494.55814,24.61474 491.94974,24.61474 C 489.33939,24.61474 487.09036,23.67187 485.20169,21.78564 C 483.31302,19.89892 482.36868,17.66259 482.36868,15.07568 C 482.36868,12.48925 483.32278,10.23339 485.23098,8.3081 C 487.06204,6.44092 489.3013,5.50732 491.94974,5.50732 z" - id="path408" - style="fill-rule:evenodd" /> - - </g> - -</g> - </g> -</svg> +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + viewBox="0 0 120 42" + id="svg2759" + sodipodi:version="0.32" + inkscape:version="0.45+devel" + version="1.0" + sodipodi:docname="by-nc-nd.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs2761" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#8b8b8b" + borderopacity="1" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1" + inkscape:cx="179" + inkscape:cy="89.569904" + inkscape:document-units="px" + inkscape:current-layer="layer1" + width="120px" + height="42px" + inkscape:showpageshadow="false" + inkscape:window-width="1198" + inkscape:window-height="624" + inkscape:window-x="488" + inkscape:window-y="401" /> + <metadata + id="metadata2764"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + transform="matrix(0.9937728,0,0,0.9936696,-437.11979,0)" + id="g361" + inkscape:export-filename="/mnt/hgfs/Bov/Documents/Work/2007/cc/identity/srr buttons/big/by-nc-nd.png" + inkscape:export-xdpi="300.23013" + inkscape:export-ydpi="300.23013"> + <path + id="path3817_4_" + nodetypes="ccccccc" + d="M 443.28955,0.44873 L 557.35303,0.65185 C 558.94678,0.65185 560.37061,0.41503 560.37061,3.83203 L 560.23096,41.39892 L 440.41064,41.39892 L 440.41064,3.69238 C 440.41064,2.00781 440.57373,0.44873 443.28955,0.44873 z" + style="fill:#aab2ab" /> + + <path + d="M 558.3501,0 L 442.12061,0 C 440.87354,0 439.85889,1.01465 439.85889,2.26123 L 439.85889,41.75732 C 439.85889,42.03906 440.08741,42.26757 440.36963,42.26757 L 560.1001,42.26757 C 560.38233,42.26757 560.61084,42.03905 560.61084,41.75732 L 560.61084,2.26123 C 560.61084,1.01465 559.59619,0 558.3501,0 z M 442.12061,1.02148 L 558.3501,1.02148 C 559.03369,1.02148 559.58936,1.57763 559.58936,2.26123 C 559.58936,2.26123 559.58936,18.15234 559.58936,29.64893 L 476.51612,29.64893 C 473.4712,35.1543 467.60401,38.89258 460.87159,38.89258 C 454.13721,38.89258 448.27198,35.15772 445.22901,29.64893 L 440.88038,29.64893 C 440.88038,18.15235 440.88038,2.26123 440.88038,2.26123 C 440.88037,1.57764 441.43701,1.02148 442.12061,1.02148 z" + id="path364" /> + + <g + id="g5908_4_" + transform="matrix(0.872921,0,0,0.872921,50.12536,143.2144)"> + + <path + id="path5906_4_" + cx="296.35416" + ry="22.939548" + cy="264.3577" + type="arc" + rx="22.939548" + d="M 486.26709,-141.53052 C 486.27271,-132.85028 479.2392,-125.80957 470.55902,-125.80341 C 461.87878,-125.79841 454.83752,-132.83124 454.83191,-141.51148 C 454.83191,-141.51819 454.83191,-141.52436 454.83191,-141.53052 C 454.82629,-150.21186 461.85974,-157.25257 470.53998,-157.25763 C 479.22132,-157.26263 486.26264,-150.22974 486.26709,-141.5495 C 486.26709,-141.54395 486.26709,-141.53723 486.26709,-141.53052 z" + style="fill:#ffffff" /> + + <g + id="g5706_4_" + transform="translate(-289.6157,99.0653)"> + <path + id="path5708_4_" + d="M 772.94281,-253.39801 C 776.42761,-249.9126 778.17059,-245.64465 778.17059,-240.59582 C 778.17059,-235.54644 776.45782,-231.32434 773.03228,-227.92785 C 769.39642,-224.35186 765.10046,-222.56414 760.14227,-222.56414 C 755.2445,-222.56414 751.0224,-224.33672 747.47827,-227.88366 C 743.93188,-231.4295 742.15985,-235.66668 742.15985,-240.59582 C 742.15985,-245.52435 743.93188,-249.79174 747.47827,-253.39801 C 750.93292,-256.88507 755.15497,-258.6275 760.14227,-258.6275 C 765.1911,-258.6275 769.45685,-256.88507 772.94281,-253.39801 z M 749.82422,-251.05371 C 746.8775,-248.07733 745.40412,-244.59082 745.40412,-240.59131 C 745.40412,-236.59302 746.86298,-233.13611 749.77839,-230.22016 C 752.69605,-227.30421 756.16743,-225.84595 760.19599,-225.84595 C 764.22455,-225.84595 767.72614,-227.31873 770.70307,-230.2649 C 773.529,-233.00074 774.94196,-236.44196 774.94196,-240.59132 C 774.94196,-244.70936 773.5055,-248.20485 770.63373,-251.07606 C 767.76306,-253.94673 764.28382,-255.38264 760.19599,-255.38264 C 756.10816,-255.38264 752.64905,-253.93945 749.82422,-251.05371 z M 757.57812,-242.35052 C 757.12841,-243.33221 756.45495,-243.82281 755.55548,-243.82281 C 753.96692,-243.82281 753.17261,-242.75329 753.17261,-240.61425 C 753.17261,-238.47472 753.96692,-237.40575 755.55548,-237.40575 C 756.60486,-237.40575 757.35443,-237.9265 757.80414,-238.97026 L 760.0069,-237.79729 C 758.95642,-235.93181 757.38123,-234.99822 755.28138,-234.99822 C 753.66151,-234.99822 752.36378,-235.49499 751.38935,-236.48784 C 750.41383,-237.48125 749.92603,-238.85057 749.92603,-240.59581 C 749.92603,-242.31139 750.42945,-243.67284 751.43409,-244.68138 C 752.43873,-245.68992 753.69172,-246.19334 755.1919,-246.19334 C 757.4126,-246.19334 759.00117,-245.31907 759.96326,-243.57103 L 757.57812,-242.35052 z M 767.94208,-242.35052 C 767.49121,-243.33221 766.83002,-243.82281 765.95966,-243.82281 C 764.33863,-243.82281 763.52753,-242.75329 763.52753,-240.61425 C 763.52753,-238.47472 764.33863,-237.40575 765.95966,-237.40575 C 767.0113,-237.40575 767.74738,-237.9265 768.16694,-238.97026 L 770.41895,-237.79729 C 769.37067,-235.93181 767.79773,-234.99822 765.70124,-234.99822 C 764.08356,-234.99822 762.78919,-235.49499 761.81477,-236.48784 C 760.8426,-237.48125 760.35487,-238.85057 760.35487,-240.59581 C 760.35487,-242.31139 760.84932,-243.67284 761.83827,-244.68138 C 762.82612,-245.68992 764.08357,-246.19334 765.61177,-246.19334 C 767.82796,-246.19334 769.41542,-245.31907 770.37306,-243.57103 L 767.94208,-242.35052 z" /> + + </g> + + </g> + + <g + enable-background="new " + id="g370"> + <path + d="M 488.25342,32.95605 C 488.5708,32.95605 488.86182,32.98437 489.12354,33.04003 C 489.38526,33.09569 489.60889,33.18749 489.79639,33.31542 C 489.98291,33.44237 490.12744,33.6123 490.23096,33.82323 C 490.3335,34.03514 490.38526,34.29589 490.38526,34.60741 C 490.38526,34.94335 490.30909,35.22264 490.15577,35.44628 C 490.00343,35.67089 489.77784,35.85351 489.47804,35.99706 C 489.89015,36.11522 490.19777,36.32226 490.40089,36.61815 C 490.60401,36.91404 490.70558,37.27049 490.70558,37.68749 C 490.70558,38.02343 490.64015,38.31444 490.50929,38.56054 C 490.37843,38.80566 490.20167,39.00683 489.98097,39.1621 C 489.75929,39.31835 489.50636,39.43358 489.22316,39.5078 C 488.93898,39.583 488.64796,39.6201 488.34816,39.6201 L 485.11183,39.6201 L 485.11183,32.95604 L 488.25342,32.95604 L 488.25342,32.95605 z M 488.06689,35.65137 C 488.32763,35.65137 488.54345,35.58887 488.71142,35.46485 C 488.87939,35.34083 488.96337,35.13965 488.96337,34.86036 C 488.96337,34.70509 488.93505,34.57716 488.87939,34.47852 C 488.82275,34.37891 488.74853,34.30176 488.65478,34.24512 C 488.56103,34.18946 488.45361,34.15039 488.33251,34.12891 C 488.21141,34.10743 488.08446,34.09668 487.9536,34.09668 L 486.58055,34.09668 L 486.58055,35.65137 L 488.06689,35.65137 z M 488.15186,38.47949 C 488.29541,38.47949 488.43213,38.46582 488.56299,38.4375 C 488.69385,38.40918 488.80908,38.3623 488.90967,38.29785 C 489.00928,38.23242 489.08838,38.14355 489.14795,38.03125 C 489.20752,37.91992 489.23682,37.77637 489.23682,37.60254 C 489.23682,37.26074 489.14014,37.0166 488.94678,36.87012 C 488.75342,36.72461 488.49854,36.65137 488.18018,36.65137 L 486.58057,36.65137 L 486.58057,38.47949 L 488.15186,38.47949 z" + id="path372" + style="fill:#ffffff" /> + + <path + d="M 490.96436,32.95605 L 492.60791,32.95605 L 494.16846,35.58789 L 495.71924,32.95605 L 497.35303,32.95605 L 494.8794,37.0625 L 494.8794,39.62012 L 493.41065,39.62012 L 493.41065,37.02539 L 490.96436,32.95605 z" + id="path374" + style="fill:#ffffff" /> + + </g> + + <g + enable-background="new " + id="g376"> + <path + d="M 512.83057,32.95605 L 515.61475,37.42675 L 515.63037,37.42675 L 515.63037,32.95605 L 517.00537,32.95605 L 517.00537,39.62011 L 515.53955,39.62011 L 512.76611,35.1582 L 512.74756,35.1582 L 512.74756,39.62011 L 511.37256,39.62011 L 511.37256,32.95605 L 512.83057,32.95605 z" + id="path378" + style="fill:#ffffff" /> + + <path + d="M 522.56885,34.73145 C 522.48194,34.59083 522.37256,34.46778 522.2417,34.36231 C 522.11084,34.25684 521.96338,34.17383 521.79834,34.11524 C 521.6333,34.05567 521.46045,34.02637 521.28076,34.02637 C 520.95068,34.02637 520.67041,34.08985 520.43994,34.21778 C 520.20947,34.34473 520.02295,34.51563 519.88037,34.73048 C 519.73682,34.94532 519.63232,35.18946 519.56689,35.4629 C 519.50146,35.73634 519.46923,36.01954 519.46923,36.31153 C 519.46923,36.5918 519.50146,36.86426 519.56689,37.12794 C 519.63232,37.39259 519.73681,37.63087 519.88037,37.84181 C 520.02295,38.05372 520.20947,38.22267 520.43994,38.3506 C 520.67041,38.47853 520.95068,38.54201 521.28076,38.54201 C 521.72803,38.54201 522.07861,38.40529 522.33056,38.13088 C 522.58251,37.85744 522.73681,37.49611 522.79247,37.04787 L 524.21142,37.04787 C 524.17431,37.46486 524.07763,37.84182 523.92236,38.17775 C 523.76709,38.51466 523.56103,38.8008 523.30615,39.0381 C 523.05127,39.2754 522.75244,39.45607 522.40967,39.58107 C 522.06787,39.70607 521.69092,39.76857 521.28076,39.76857 C 520.77002,39.76857 520.31103,39.6797 519.90283,39.50197 C 519.4956,39.32521 519.15088,39.08009 518.8706,38.76955 C 518.58935,38.45803 518.37451,38.09182 518.22509,37.67189 C 518.07568,37.25099 518.00048,36.79884 518.00048,36.31251 C 518.00048,35.81446 518.07568,35.35255 518.22509,34.92579 C 518.3745,34.49903 518.58935,34.12696 518.8706,33.80958 C 519.15087,33.4922 519.4956,33.24317 519.90283,33.06251 C 520.31103,32.88185 520.77002,32.792 521.28076,32.792 C 521.64795,32.792 521.99463,32.84473 522.32178,32.95118 C 522.64795,33.05665 522.94092,33.21095 523.19873,33.41407 C 523.45752,33.61622 523.67041,33.86719 523.83838,34.16602 C 524.00635,34.46485 524.11182,34.80762 524.15576,35.19336 L 522.73681,35.19336 C 522.7124,35.02539 522.65576,34.87109 522.56885,34.73145 z" + id="path380" + style="fill:#ffffff" /> + + </g> + + <g + enable-background="new " + id="g382"> + <path + d="M 538.83057,32.95605 L 541.61475,37.42675 L 541.63037,37.42675 L 541.63037,32.95605 L 543.00537,32.95605 L 543.00537,39.62011 L 541.53955,39.62011 L 538.76611,35.1582 L 538.74756,35.1582 L 538.74756,39.62011 L 537.37256,39.62011 L 537.37256,32.95605 L 538.83057,32.95605 z" + id="path384" + style="fill:#ffffff" /> + + <path + d="M 547.16748,32.95605 C 547.59814,32.95605 547.99756,33.02441 548.36865,33.16113 C 548.73974,33.29785 549.06006,33.5039 549.33154,33.77734 C 549.60205,34.05078 549.81396,34.39355 549.96631,34.80371 C 550.11963,35.21484 550.1958,35.69726 550.1958,36.25098 C 550.1958,36.73633 550.1333,37.1836 550.00928,37.59473 C 549.88428,38.00489 549.6958,38.36035 549.44385,38.65821 C 549.19092,38.95704 548.87647,39.19239 548.49951,39.36329 C 548.12255,39.53419 547.6792,39.62013 547.16748,39.62013 L 544.28955,39.62013 L 544.28955,32.95607 L 547.16748,32.95607 L 547.16748,32.95605 z M 547.06494,38.38574 C 547.27685,38.38574 547.48193,38.35156 547.68115,38.2832 C 547.88037,38.21484 548.0581,38.10156 548.21338,37.94238 C 548.36865,37.78418 548.49365,37.57812 548.5874,37.32324 C 548.68017,37.06836 548.72705,36.75683 548.72705,36.39062 C 548.72705,36.05468 548.69482,35.75195 548.62939,35.48144 C 548.56396,35.21093 548.45654,34.97949 548.30712,34.7871 C 548.1577,34.59471 547.96044,34.44628 547.71435,34.34374 C 547.46826,34.2412 547.16455,34.19042 546.80419,34.19042 L 545.75829,34.19042 L 545.75829,38.38573 L 547.06494,38.38573 L 547.06494,38.38574 z" + id="path386" + style="fill:#ffffff" /> + + </g> + + <g + id="g6370_1_" + transform="translate(286.1464,208.0498)"> + <g + id="g7610_1_" + transform="matrix(1.146822,0,0,1.146822,-67.14005,-41.89676)"> + + <path + id="path6372_1_" + cx="475.97119" + ry="29.209877" + cy="252.08646" + type="arc" + rx="29.209877" + d="M 269.61823,-131.7348 C 269.62247,-126.90787 265.71222,-122.99292 260.88486,-122.98907 C 256.05832,-122.98611 252.14295,-126.89593 252.13956,-131.72204 C 252.13956,-131.72714 252.13956,-131.73098 252.13956,-131.7348 C 252.13614,-136.56216 256.04642,-140.47711 260.87293,-140.48095 C 265.69944,-140.48394 269.61481,-136.57409 269.61823,-131.74801 C 269.61823,-131.74374 269.61823,-131.73907 269.61823,-131.7348 z" + style="fill:#ffffff" /> + + <path + id="path6374_1_" + d="M 260.86526,-141.90982 C 263.71875,-141.90982 266.12945,-140.9263 268.09909,-138.95969 C 270.06869,-136.99219 271.05392,-134.58362 271.05392,-131.73481 C 271.05392,-128.88642 270.08572,-126.5034 268.15017,-124.58659 C 266.09539,-122.56843 263.6668,-121.56022 260.86526,-121.56022 C 258.0986,-121.56022 255.71261,-122.56077 253.70892,-124.5619 C 251.70526,-126.56214 250.70385,-128.95368 250.70385,-131.73481 C 250.70385,-134.51637 251.70525,-136.92493 253.70892,-138.95969 C 255.6615,-140.9263 258.04752,-141.90982 260.86526,-141.90982 z M 252.9928,-134.46866 C 252.68964,-133.6099 252.53723,-132.69876 252.53723,-131.7348 C 252.53723,-129.47952 253.36151,-127.5299 255.00839,-125.88431 C 256.65524,-124.23999 258.61636,-123.41742 260.89166,-123.41742 C 263.1661,-123.41742 265.14422,-124.24808 266.82516,-125.91028 C 267.38803,-126.45398 267.85214,-127.04709 268.21572,-127.69 L 264.37954,-129.39776 C 264.11984,-128.10726 262.96941,-127.23571 261.5797,-127.13351 L 261.5797,-125.56457 L 260.41137,-125.56457 L 260.41137,-127.13351 C 259.26946,-127.1463 258.16674,-127.61337 257.32287,-128.35165 L 258.72448,-129.76477 C 259.39892,-129.12952 260.07418,-128.8447 260.99554,-128.8447 C 261.59246,-128.8447 262.25412,-129.07801 262.25412,-129.8559 C 262.25412,-130.13135 262.14767,-130.32297 261.97992,-130.46729 L 261.00919,-130.89817 L 259.8017,-131.43677 C 259.20392,-131.70331 258.69724,-131.92768 258.18973,-132.15418 L 252.9928,-134.46866 z M 260.89166,-140.07861 C 258.58145,-140.07861 256.6297,-139.26495 255.03308,-137.63638 C 254.59878,-137.19784 254.2207,-136.74014 253.90054,-136.26199 L 257.78952,-134.52996 C 258.1412,-135.60931 259.16644,-136.26412 260.41138,-136.33651 L 260.41138,-137.90548 L 261.57971,-137.90548 L 261.57971,-136.33651 C 262.3844,-136.29775 263.2666,-136.07723 264.13601,-135.40365 L 262.7991,-134.02926 C 262.30606,-134.37927 261.68359,-134.62536 261.06027,-134.62536 C 260.55444,-134.62536 259.83999,-134.47036 259.83999,-133.83468 C 259.83999,-133.73803 259.87322,-133.65289 259.93197,-133.57626 L 261.23312,-132.99807 L 262.11361,-132.60549 C 262.67648,-132.35387 263.21465,-132.11544 263.74685,-131.87829 L 268.96084,-129.557 C 269.13284,-130.2395 269.21969,-130.96587 269.21969,-131.7348 C 269.21969,-134.05823 268.40478,-136.02569 266.77493,-137.63638 C 265.16129,-139.26495 263.20102,-140.07861 260.89166,-140.07861 z" /> + + </g> + + </g> + + <g + id="g6394_1_" + transform="matrix(0.624995,0,0,0.624995,312.8511,316.9328)"> + + <path + id="path6396_1_" + cx="475.97119" + ry="29.209877" + cy="252.08646" + type="arc" + rx="29.209877" + d="M 387.83435,-482.97366 C 387.84216,-473.56265 380.2171,-465.92666 370.80609,-465.91885 C 361.39349,-465.91342 353.75751,-473.53689 353.75122,-482.95022 C 353.75122,-482.9573 353.75122,-482.96664 353.75122,-482.97366 C 353.74499,-492.38546 361.36847,-500.02145 370.78107,-500.02853 C 380.19208,-500.03634 387.82807,-492.41128 387.83435,-482.99948 C 387.83435,-482.99008 387.83435,-482.98306 387.83435,-482.97366 z" + style="fill:#ffffff" /> + + <g + id="g6398_1_" + transform="translate(-23.9521,-87.92102)"> + <path + id="path6400_1_" + d="M 394.47845,-413.72311 C 389.30651,-413.72311 384.9284,-411.92001 381.34552,-408.30978 C 377.66895,-404.5762 375.83142,-400.15817 375.83142,-395.05264 C 375.83142,-389.94949 377.66894,-385.56271 381.34552,-381.89084 C 385.02057,-378.21896 389.40027,-376.38297 394.47845,-376.38297 C 399.61914,-376.38297 404.07385,-378.23533 407.84417,-381.93613 C 411.39422,-385.45334 413.17236,-389.82608 413.17236,-395.05265 C 413.17236,-400.28239 411.36456,-404.69962 407.75042,-408.30979 C 404.13635,-411.92001 399.7113,-413.72311 394.47845,-413.72311 z M 394.5238,-410.36453 C 398.76129,-410.36453 402.3598,-408.86996 405.32074,-405.88168 C 408.3114,-402.92617 409.80518,-399.31753 409.80518,-395.05264 C 409.80518,-390.75888 408.34266,-387.19638 405.41449,-384.36508 C 402.32855,-381.31503 398.69879,-379.79239 394.5238,-379.79239 C 390.34875,-379.79239 386.7503,-381.3002 383.72839,-384.31979 C 380.70648,-387.337 379.19555,-390.91592 379.19555,-395.05264 C 379.19555,-399.19333 380.7221,-402.80197 383.77679,-405.88168 C 386.70496,-408.86996 390.28625,-410.36453 394.5238,-410.36453 z" /> + + <g + id="g6402_1_"> + <path + id="path6404_1_" + d="M 401.55505,-399.47849 L 387.98468,-399.47849 L 387.98468,-396.26359 L 401.55505,-396.26359 L 401.55505,-399.47849 z M 401.55505,-393.47763 L 387.98468,-393.47763 L 387.98468,-390.26358 L 401.55505,-390.26358 L 401.55505,-393.47763 z" /> + + </g> + + </g> + + </g> + + <g + id="g398"> + <circle + cx="491.9473" + cy="15.31396" + r="10.80615" + id="circle400" + sodipodi:cx="491.9473" + sodipodi:cy="15.31396" + sodipodi:rx="10.80615" + sodipodi:ry="10.80615" + style="fill:#ffffff" /> + + <g + id="g402"> + <path + d="M 495.07474,12.18701 C 495.07474,11.77051 494.73685,11.43359 494.32083,11.43359 L 489.54837,11.43359 C 489.13235,11.43359 488.79446,11.7705 488.79446,12.18701 L 488.79446,16.95996 L 490.12551,16.95996 L 490.12551,22.6123 L 493.7427,22.6123 L 493.7427,16.95996 L 495.07473,16.95996 L 495.07473,12.18701 L 495.07474,12.18701 z" + id="path404" /> + + <circle + cx="491.9346" + cy="9.1723604" + r="1.63232" + id="circle406" + sodipodi:cx="491.9346" + sodipodi:cy="9.1723604" + sodipodi:rx="1.63232" + sodipodi:ry="1.63232" /> + + </g> + + <path + clip-rule="evenodd" + d="M 491.91946,3.40771 C 488.68801,3.40771 485.95169,4.53515 483.71243,6.7915 C 481.41458,9.12451 480.26614,11.88671 480.26614,15.07568 C 480.26614,18.26465 481.41458,21.00781 483.71243,23.30273 C 486.01028,25.59716 488.74661,26.74462 491.91946,26.74462 C 495.13235,26.74462 497.91751,25.58788 500.27395,23.27294 C 502.49368,21.07616 503.60305,18.34325 503.60305,15.07567 C 503.60305,11.80809 502.47414,9.04735 500.21535,6.79149 C 497.95657,4.53516 495.19193,3.40771 491.91946,3.40771 z M 491.94974,5.50732 C 494.59818,5.50732 496.84622,6.44091 498.69583,8.3081 C 500.56595,10.15527 501.50052,12.41162 501.50052,15.07568 C 501.50052,17.75927 500.58548,19.98681 498.75443,21.75634 C 496.8267,23.6621 494.55814,24.61474 491.94974,24.61474 C 489.33939,24.61474 487.09036,23.67187 485.20169,21.78564 C 483.31302,19.89892 482.36868,17.66259 482.36868,15.07568 C 482.36868,12.48925 483.32278,10.23339 485.23098,8.3081 C 487.06204,6.44092 489.3013,5.50732 491.94974,5.50732 z" + id="path408" + style="fill-rule:evenodd" /> + + </g> + +</g> + </g> +</svg> diff --git a/src/assets/images/EVA.svg b/src/assets/images/EVA.svg index 1fed00965ede7fb2daf4eb33122fc9c7cf13cb75..812bbe5e5c9f79a876186249de3d06e276122ff0 100644 --- a/src/assets/images/EVA.svg +++ b/src/assets/images/EVA.svg @@ -1,23 +1,23 @@ -<svg id="EVA-OPENFING" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 158.21 148.72"> - <defs> - <style> - .cls-1 { - fill: #005190; - } - - .cls-2 { - fill: #86143b; - } - - .cls-3 { - fill: #b1a322; - } - </style> - </defs> - <title>EVA</title> - <g id="EVA_LOGO" data-name="EVA LOGO"> - <path id="azul" class="cls-1" d="M158.2,74.68c-.46,19-8.67,36.08-23.86,50a23,23,0,0,0-2,1.78c-4.18,4.48-9.42,4.85-15.13,4.34A157.6,157.6,0,0,1,70,119.14,178,178,0,0,1,25.93,92.58,1.13,1.13,0,0,1,25.49,91a.91.91,0,0,1,.17-.23,73.26,73.26,0,0,1,8.6-11c.49-.54.75,0,1,.23A164.89,164.89,0,0,0,60.94,97.77a133.69,133.69,0,0,0,43.9,15.63c4.7.75,9.42,1.48,14.21,1a7.21,7.21,0,0,0,4.1-1.77c6.18-5.27,11.88-10.94,15.72-18.25a45.25,45.25,0,0,0-17.81-60.52,4.22,4.22,0,0,1-2.22-2.76,112.71,112.71,0,0,0-5-13.7c-.28-.62-.21-.86.49-1,4-.83,8-1.32,11.73.9a64.65,64.65,0,0,1,22.85,22.42C155.08,49.94,158.09,61.05,158.2,74.68Z" transform="translate(0.01 -0.03)"/> - <path id="rojo" class="cls-2" d="M119.72,77.7A172.41,172.41,0,0,1,117,108c-.14.78-.45,1-1.26,1a119.43,119.43,0,0,1-13.45-1.71c-.76-.14-1-.36-.81-1.18A158.14,158.14,0,0,0,104,72c-.73-17-4.14-33.41-12.16-48.65A10.41,10.41,0,0,0,85,17.79C78.2,16,71.41,14.56,64.36,14.87c-23.69,1-43.4,18.39-46.72,40.6a8,8,0,0,1-2.54,5.08A136.55,136.55,0,0,0,4.31,72.32c-.5.6-.76.51-1.29.05C.16,69.94-.22,66.77.08,63.28a65.68,65.68,0,0,1,20.57-43A71.87,71.87,0,0,1,61.2.79,74.84,74.84,0,0,1,97.47,4.57a10.14,10.14,0,0,1,5.8,4.86,133,133,0,0,1,13.88,39.78A155.55,155.55,0,0,1,119.72,77.7Z" transform="translate(0.01 -0.03)"/> - <path id="amarillo" class="cls-3" d="M68.31,135.16a52.67,52.67,0,0,0,19.59-3.49,1.5,1.5,0,0,1,1-.11,166,166,0,0,0,20.14,4c.77.1.66.34.32.82a16.23,16.23,0,0,1-5.29,4.84,54.5,54.5,0,0,1-26,7.48c-23.17.73-43.33-6.85-59.94-23C8.23,116.06,2.91,103.89.9,90.26a7.71,7.71,0,0,1,1.56-5.52c9.29-13.78,21-25.23,34.29-35a187.56,187.56,0,0,1,47.56-25c.87-.31,1.34-.18,1.79.6a80.86,80.86,0,0,1,5.51,12.05c.21.56-.15.62-.5.75a220.2,220.2,0,0,0-24,10.34C52.11,56.27,38.55,66,27.31,78.8a51.35,51.35,0,0,0-7.17,9.67,8.7,8.7,0,0,0-.77,6.23c2.12,9.22,5.58,17.76,12.24,24.73a50.07,50.07,0,0,0,30,15.24A45.57,45.57,0,0,0,68.31,135.16Z" transform="translate(0.01 -0.03)"/> - </g> -</svg> +<svg id="EVA-OPENFING" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 158.21 148.72"> + <defs> + <style> + .cls-1 { + fill: #005190; + } + + .cls-2 { + fill: #86143b; + } + + .cls-3 { + fill: #b1a322; + } + </style> + </defs> + <title>EVA</title> + <g id="EVA_LOGO" data-name="EVA LOGO"> + <path id="azul" class="cls-1" d="M158.2,74.68c-.46,19-8.67,36.08-23.86,50a23,23,0,0,0-2,1.78c-4.18,4.48-9.42,4.85-15.13,4.34A157.6,157.6,0,0,1,70,119.14,178,178,0,0,1,25.93,92.58,1.13,1.13,0,0,1,25.49,91a.91.91,0,0,1,.17-.23,73.26,73.26,0,0,1,8.6-11c.49-.54.75,0,1,.23A164.89,164.89,0,0,0,60.94,97.77a133.69,133.69,0,0,0,43.9,15.63c4.7.75,9.42,1.48,14.21,1a7.21,7.21,0,0,0,4.1-1.77c6.18-5.27,11.88-10.94,15.72-18.25a45.25,45.25,0,0,0-17.81-60.52,4.22,4.22,0,0,1-2.22-2.76,112.71,112.71,0,0,0-5-13.7c-.28-.62-.21-.86.49-1,4-.83,8-1.32,11.73.9a64.65,64.65,0,0,1,22.85,22.42C155.08,49.94,158.09,61.05,158.2,74.68Z" transform="translate(0.01 -0.03)"/> + <path id="rojo" class="cls-2" d="M119.72,77.7A172.41,172.41,0,0,1,117,108c-.14.78-.45,1-1.26,1a119.43,119.43,0,0,1-13.45-1.71c-.76-.14-1-.36-.81-1.18A158.14,158.14,0,0,0,104,72c-.73-17-4.14-33.41-12.16-48.65A10.41,10.41,0,0,0,85,17.79C78.2,16,71.41,14.56,64.36,14.87c-23.69,1-43.4,18.39-46.72,40.6a8,8,0,0,1-2.54,5.08A136.55,136.55,0,0,0,4.31,72.32c-.5.6-.76.51-1.29.05C.16,69.94-.22,66.77.08,63.28a65.68,65.68,0,0,1,20.57-43A71.87,71.87,0,0,1,61.2.79,74.84,74.84,0,0,1,97.47,4.57a10.14,10.14,0,0,1,5.8,4.86,133,133,0,0,1,13.88,39.78A155.55,155.55,0,0,1,119.72,77.7Z" transform="translate(0.01 -0.03)"/> + <path id="amarillo" class="cls-3" d="M68.31,135.16a52.67,52.67,0,0,0,19.59-3.49,1.5,1.5,0,0,1,1-.11,166,166,0,0,0,20.14,4c.77.1.66.34.32.82a16.23,16.23,0,0,1-5.29,4.84,54.5,54.5,0,0,1-26,7.48c-23.17.73-43.33-6.85-59.94-23C8.23,116.06,2.91,103.89.9,90.26a7.71,7.71,0,0,1,1.56-5.52c9.29-13.78,21-25.23,34.29-35a187.56,187.56,0,0,1,47.56-25c.87-.31,1.34-.18,1.79.6a80.86,80.86,0,0,1,5.51,12.05c.21.56-.15.62-.5.75a220.2,220.2,0,0,0-24,10.34C52.11,56.27,38.55,66,27.31,78.8a51.35,51.35,0,0,0-7.17,9.67,8.7,8.7,0,0,0-.77,6.23c2.12,9.22,5.58,17.76,12.24,24.73a50.07,50.07,0,0,0,30,15.24A45.57,45.57,0,0,0,68.31,135.16Z" transform="translate(0.01 -0.03)"/> + </g> +</svg> diff --git a/src/assets/images/Logo.svg b/src/assets/images/Logo.svg index 0493e093920347c3320be569fb34304b34cf81ed..33d8090f5d63e4fe0d036684e661c21f3ef5604d 100644 --- a/src/assets/images/Logo.svg +++ b/src/assets/images/Logo.svg @@ -1,35 +1,35 @@ -<svg id="OpenFING" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 2050"> - <defs> - <filter id="sombra"> - <feGaussianBlur in="SourceAlpha" result="desenfoqueSombra" stdDeviation="32"/> - <feOffset in="desenfoqueSombra" dx="0" dy="0" result="desplazamientoSombra"/> - <feFlood flood-color="rgba(0,0,0,0.9)" result="colorSombra"/> - <feComposite in="colorSombra" in2="desplazamientoSombra" operator="in" result="OpenFINGsombra"/> - <feMerge> - <feMergeNode in="OpenFINGsombra" /> - <feMergeNode in="SourceGraphic"/> - </feMerge> - </filter> - <style> - .enlaces { - fill: none; - stroke: #1f4f80; - stroke-width: 50px; - } - .nodos { - fill: #2e77bd; - stroke: #1f4f80; - stroke-width: 20px; - } - </style> - </defs> - <title>OpenFING</title> - <g id="Enlaces" class="enlaces"> - <polygon id="Enlace" points="460,500 995,1435 1530,565"/> - </g> - <g id="Nodos" class="nodos"> - <circle id="NodoChico" cx="1485" cy="605" r="255"/> - <circle id="NodoMedio" cx="462" cy="462" r="392"/> - <circle id="NodoGrande" cx="1000" cy="1462" r="502"/> - </g> +<svg id="OpenFING" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1800 2050"> + <defs> + <filter id="sombra"> + <feGaussianBlur in="SourceAlpha" result="desenfoqueSombra" stdDeviation="32"/> + <feOffset in="desenfoqueSombra" dx="0" dy="0" result="desplazamientoSombra"/> + <feFlood flood-color="rgba(0,0,0,0.9)" result="colorSombra"/> + <feComposite in="colorSombra" in2="desplazamientoSombra" operator="in" result="OpenFINGsombra"/> + <feMerge> + <feMergeNode in="OpenFINGsombra" /> + <feMergeNode in="SourceGraphic"/> + </feMerge> + </filter> + <style> + .enlaces { + fill: none; + stroke: #1f4f80; + stroke-width: 50px; + } + .nodos { + fill: #2e77bd; + stroke: #1f4f80; + stroke-width: 20px; + } + </style> + </defs> + <title>OpenFING</title> + <g id="Enlaces" class="enlaces"> + <polygon id="Enlace" points="460,500 995,1435 1530,565"/> + </g> + <g id="Nodos" class="nodos"> + <circle id="NodoChico" cx="1485" cy="605" r="255"/> + <circle id="NodoMedio" cx="462" cy="462" r="392"/> + <circle id="NodoGrande" cx="1000" cy="1462" r="502"/> + </g> </svg> \ No newline at end of file diff --git a/src/assets/images/LogoTexto.svg b/src/assets/images/LogoTexto.svg index 5b80c753c68d560c1d18552f9bd8d7b23446dae8..25997d80a0b4ee0802f6edd4d5f67ea7210e6d93 100644 --- a/src/assets/images/LogoTexto.svg +++ b/src/assets/images/LogoTexto.svg @@ -1,31 +1,31 @@ -<svg id="_OpenFING_" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2181 314"> - <defs> - <filter id="sombra" y="-40%" height="180%"> - <feGaussianBlur in="SourceAlpha" result="desenfoqueSombra" stdDeviation="20"/> - <feOffset in="desenfoqueSombra" dx="0" dy="0" result="desplazamientoSombra"/> - <feFlood flood-color="rgba(0,0,0,0.5)" result="colorSombra"/> - <feComposite in="colorSombra" in2="desplazamientoSombra" operator="in" result="OpenFINGsombra"/> - <feMerge> - <feMergeNode in="OpenFINGsombra" /> - <feMergeNode in="SourceGraphic"/> - </feMerge> - </filter> - <style> - .formato { - fill:#fff; - } - </style> - </defs> - <title>OpenFING</title> - <g id="texto" class="formato"> - <path id="O" d="M1046.77,359c-38.06-12.33-76.86-12.48-115.21-1.39-44.47,12.85-74.92,41.61-89.78,85.53-13.2,39-13.52,78.94-2.33,118.47,12.88,45.52,42.12,76.58,87.23,91.36,37.9,12.43,76.55,12.49,114.85,1.79,40.67-11.36,69.76-36.78,86.72-75.6,10.13-23.17,13.33-47.72,13.52-72.79-.08-18.43-1.76-36.7-6.89-54.47C1121.56,405.72,1092.7,373.83,1046.77,359Zm14.32,159.76c-1,17-4,33.46-11.73,48.79-14.94,29.72-40.46,40.63-70.6,37.82-32.9-3.06-51-23.43-60.45-53.25-4.76-15-6-30.56-6.09-47.77.21-18.32,2.25-37.87,10.51-56.23,10-22.34,26.35-37.29,51.15-40.81,36.65-5.21,66.38,9.42,80.09,48.24C1061.2,476,1062.33,497.24,1061.09,518.71Z" transform="translate(-831.44 -349.46)"/> - <path id="P" d="M1344.88,416.65a131.35,131.35,0,0,0-42-6.57l-106.1-.07-3.07.21V655.83h63.71V570.48c15.26,0,30.14.47,45-.16a194.22,194.22,0,0,0,32.65-3.8c21.39-4.62,39.39-15,50.84-34.51,8.51-14.5,10.66-30.48,10-47C1394.61,451.42,1376.71,427.49,1344.88,416.65ZM1316,518.93a38.87,38.87,0,0,1-16.2,5.1c-14,.71-28.13.23-42.38.23v-68c.2-.15.39-.43.58-.42,14.6.19,29.26-.3,43.8.8,14.87,1.13,25.12,10.76,28,24.13C1333.33,497.1,1328.24,512.17,1316,518.93Z" transform="translate(-831.44 -349.46)"/> - <polygon id="E" points="673.54 202.24 774.29 202.24 774.29 154.19 673.54 154.19 673.54 108.39 780.37 108.39 780.37 60.71 609.83 60.71 609.83 306.36 784.08 306.36 784.08 258.41 673.54 258.41 673.54 202.24"/> - - <polygon id="N" points="1760.72 211.89 1651.67 5.71 1565.33 5.71 1565.33 306.6 1638.65 306.6 1638.65 100.43 1747.68 306.6 1834.04 306.6 1834.04 5.98 1760.72 5.98 1760.72 211.89"/> - <polygon id="F" points="1129.5 306.36 1206.7 306.36 1206.7 178.95 1330.84 178.95 1330.84 120.42 1206.7 120.42 1206.7 64.13 1338.3 64.13 1338.3 6.08 1129.5 6.08 1129.5 306.36"/> - <rect id="I" x="1411.57" y="5.96" width="76.79" height="300.28"/> - <polygon id="n" points="1000.22 229.11 910.57 60.54 840.02 60.54 840.02 306.54 899.9 306.54 899.9 137.97 989.55 306.54 1060.09 306.54 1060.09 60.54 1000.22 60.54 1000.22 229.11"/> - <path id="G" d="M2891.73,540.65h47l.15,53.63c.08,4.47-1.54,6-5.41,7a133.8,133.8,0,0,1-56.47,2.83c-32.21-5.12-54.95-22.61-65.6-53.75-9.78-28.6-9.65-57.78-.63-86.58,6.69-21.33,20.27-37.36,40.5-47.36,14.27-7.06,29.58-9.73,45.33-10.13,33.23-.85,64.23,7.68,93.65,22.66,3.42,1.74,6.79,3.57,10.73,5.65V372.28l-7.1-2.94c-28.81-12-59-17.86-90-19.09-30.08-1.19-59.74,1.35-88,12.51-42,16.58-70.87,45.88-84.1,89.47-9.37,30.87-9.89,62.34-3.57,93.8,7.69,38.29,27.73,68.63,60.57,90.14,23.56,15.42,49.86,22.78,77.63,24.9,49.68,3.81,97.27-4.6,142.54-25.67,2.53-1.18,3.39-2.48,3.39-5.28V488.94H2891.73Z" transform="translate(-831.44 -349.46)"/> - </g> -</svg> +<svg id="_OpenFING_" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2181 314"> + <defs> + <filter id="sombra" y="-40%" height="180%"> + <feGaussianBlur in="SourceAlpha" result="desenfoqueSombra" stdDeviation="20"/> + <feOffset in="desenfoqueSombra" dx="0" dy="0" result="desplazamientoSombra"/> + <feFlood flood-color="rgba(0,0,0,0.5)" result="colorSombra"/> + <feComposite in="colorSombra" in2="desplazamientoSombra" operator="in" result="OpenFINGsombra"/> + <feMerge> + <feMergeNode in="OpenFINGsombra" /> + <feMergeNode in="SourceGraphic"/> + </feMerge> + </filter> + <style> + .formato { + fill:#fff; + } + </style> + </defs> + <title>OpenFING</title> + <g id="texto" class="formato"> + <path id="O" d="M1046.77,359c-38.06-12.33-76.86-12.48-115.21-1.39-44.47,12.85-74.92,41.61-89.78,85.53-13.2,39-13.52,78.94-2.33,118.47,12.88,45.52,42.12,76.58,87.23,91.36,37.9,12.43,76.55,12.49,114.85,1.79,40.67-11.36,69.76-36.78,86.72-75.6,10.13-23.17,13.33-47.72,13.52-72.79-.08-18.43-1.76-36.7-6.89-54.47C1121.56,405.72,1092.7,373.83,1046.77,359Zm14.32,159.76c-1,17-4,33.46-11.73,48.79-14.94,29.72-40.46,40.63-70.6,37.82-32.9-3.06-51-23.43-60.45-53.25-4.76-15-6-30.56-6.09-47.77.21-18.32,2.25-37.87,10.51-56.23,10-22.34,26.35-37.29,51.15-40.81,36.65-5.21,66.38,9.42,80.09,48.24C1061.2,476,1062.33,497.24,1061.09,518.71Z" transform="translate(-831.44 -349.46)"/> + <path id="P" d="M1344.88,416.65a131.35,131.35,0,0,0-42-6.57l-106.1-.07-3.07.21V655.83h63.71V570.48c15.26,0,30.14.47,45-.16a194.22,194.22,0,0,0,32.65-3.8c21.39-4.62,39.39-15,50.84-34.51,8.51-14.5,10.66-30.48,10-47C1394.61,451.42,1376.71,427.49,1344.88,416.65ZM1316,518.93a38.87,38.87,0,0,1-16.2,5.1c-14,.71-28.13.23-42.38.23v-68c.2-.15.39-.43.58-.42,14.6.19,29.26-.3,43.8.8,14.87,1.13,25.12,10.76,28,24.13C1333.33,497.1,1328.24,512.17,1316,518.93Z" transform="translate(-831.44 -349.46)"/> + <polygon id="E" points="673.54 202.24 774.29 202.24 774.29 154.19 673.54 154.19 673.54 108.39 780.37 108.39 780.37 60.71 609.83 60.71 609.83 306.36 784.08 306.36 784.08 258.41 673.54 258.41 673.54 202.24"/> + + <polygon id="N" points="1760.72 211.89 1651.67 5.71 1565.33 5.71 1565.33 306.6 1638.65 306.6 1638.65 100.43 1747.68 306.6 1834.04 306.6 1834.04 5.98 1760.72 5.98 1760.72 211.89"/> + <polygon id="F" points="1129.5 306.36 1206.7 306.36 1206.7 178.95 1330.84 178.95 1330.84 120.42 1206.7 120.42 1206.7 64.13 1338.3 64.13 1338.3 6.08 1129.5 6.08 1129.5 306.36"/> + <rect id="I" x="1411.57" y="5.96" width="76.79" height="300.28"/> + <polygon id="n" points="1000.22 229.11 910.57 60.54 840.02 60.54 840.02 306.54 899.9 306.54 899.9 137.97 989.55 306.54 1060.09 306.54 1060.09 60.54 1000.22 60.54 1000.22 229.11"/> + <path id="G" d="M2891.73,540.65h47l.15,53.63c.08,4.47-1.54,6-5.41,7a133.8,133.8,0,0,1-56.47,2.83c-32.21-5.12-54.95-22.61-65.6-53.75-9.78-28.6-9.65-57.78-.63-86.58,6.69-21.33,20.27-37.36,40.5-47.36,14.27-7.06,29.58-9.73,45.33-10.13,33.23-.85,64.23,7.68,93.65,22.66,3.42,1.74,6.79,3.57,10.73,5.65V372.28l-7.1-2.94c-28.81-12-59-17.86-90-19.09-30.08-1.19-59.74,1.35-88,12.51-42,16.58-70.87,45.88-84.1,89.47-9.37,30.87-9.89,62.34-3.57,93.8,7.69,38.29,27.73,68.63,60.57,90.14,23.56,15.42,49.86,22.78,77.63,24.9,49.68,3.81,97.27-4.6,142.54-25.67,2.53-1.18,3.39-2.48,3.39-5.28V488.94H2891.73Z" transform="translate(-831.44 -349.46)"/> + </g> +</svg> diff --git a/src/components/BreakpointManager.ts b/src/components/BreakpointManager.ts index e23038ba3fc119626da099a49083871b7bc6a40c..42e2f69e455d2f0776a7be00c55cac181027554c 100644 --- a/src/components/BreakpointManager.ts +++ b/src/components/BreakpointManager.ts @@ -1,21 +1,21 @@ -import { useContext, useEffect } from "react"; -import { getBreakpointForWidth } from "src/helper"; -import { AppStoreContext } from "./AppStoreContext"; - -export const BreakpointManager = () => { - const appStore = useContext(AppStoreContext); - - useEffect(() => { - const handleResize = () => { - const { innerWidth } = window; - - appStore.setBreakpoint(getBreakpointForWidth(innerWidth)); - }; - - window.addEventListener("resize", handleResize); - - return () => window.removeEventListener("resize", handleResize); - }, [appStore]); - - return null; -}; +import { useContext, useEffect } from "react"; +import { getBreakpointForWidth } from "src/helper"; +import { AppStoreContext } from "./AppStoreContext"; + +export const BreakpointManager = () => { + const appStore = useContext(AppStoreContext); + + useEffect(() => { + const handleResize = () => { + const { innerWidth } = window; + + appStore.setBreakpoint(getBreakpointForWidth(innerWidth)); + }; + + window.addEventListener("resize", handleResize); + + return () => window.removeEventListener("resize", handleResize); + }, [appStore]); + + return null; +}; diff --git a/src/components/InputTypeManager.ts b/src/components/InputTypeManager.ts index 5f30d44dcbb3afeac122c45e8437ff6b719da2aa..0ffdb71e376b39c8d42e31bdff0237a6244fa6b6 100644 --- a/src/components/InputTypeManager.ts +++ b/src/components/InputTypeManager.ts @@ -1,46 +1,46 @@ -import React, { useContext, useEffect, useRef } from "react"; -import { InputType } from "src/appstore/AppStore"; -import { AppStoreContext } from "./AppStoreContext"; - -// http://www.javascriptkit.com/dhtmltutors/sticky-hover-issue-solutions.shtml -export const InputTypeManager = () => { - const appStore = useContext(AppStoreContext); - const [currentInput, setCurrentInput] = React.useState<InputType>("pointer"); - - const isTouchTimeoutRef = useRef<number>(); - const isTouchRef = useRef<boolean>(); - - useEffect(() => { - const handleTouchStart = () => { - clearTimeout(isTouchTimeoutRef.current); - isTouchRef.current = true; - - setCurrentInput("touch"); - - isTouchTimeoutRef.current = setTimeout(() => (isTouchRef.current = false), 500); - }; - - const handleMouseOver = () => { - if (isTouchRef.current) return; - - setCurrentInput("pointer"); - }; - - window.addEventListener("touchstart", handleTouchStart); - window.addEventListener("mouseover", handleMouseOver); - - return () => { - window.removeEventListener("touchstart", handleTouchStart); - window.removeEventListener("mouseover", handleMouseOver); - }; - }, [appStore]); - - useEffect(() => { - appStore.setInputType(currentInput); - document.documentElement!.classList.add(currentInput); - - return () => document.documentElement!.classList.remove(currentInput); - }, [currentInput]); - - return null; -}; +import React, { useContext, useEffect, useRef } from "react"; +import { InputType } from "src/appstore/AppStore"; +import { AppStoreContext } from "./AppStoreContext"; + +// http://www.javascriptkit.com/dhtmltutors/sticky-hover-issue-solutions.shtml +export const InputTypeManager = () => { + const appStore = useContext(AppStoreContext); + const [currentInput, setCurrentInput] = React.useState<InputType>("pointer"); + + const isTouchTimeoutRef = useRef<number>(); + const isTouchRef = useRef<boolean>(); + + useEffect(() => { + const handleTouchStart = () => { + clearTimeout(isTouchTimeoutRef.current); + isTouchRef.current = true; + + setCurrentInput("touch"); + + isTouchTimeoutRef.current = setTimeout(() => (isTouchRef.current = false), 500); + }; + + const handleMouseOver = () => { + if (isTouchRef.current) return; + + setCurrentInput("pointer"); + }; + + window.addEventListener("touchstart", handleTouchStart); + window.addEventListener("mouseover", handleMouseOver); + + return () => { + window.removeEventListener("touchstart", handleTouchStart); + window.removeEventListener("mouseover", handleMouseOver); + }; + }, [appStore]); + + useEffect(() => { + appStore.setInputType(currentInput); + document.documentElement!.classList.add(currentInput); + + return () => document.documentElement!.classList.remove(currentInput); + }, [currentInput]); + + return null; +}; diff --git a/src/components/creativecommons/CreativeCommons.styles.ts b/src/components/creativecommons/CreativeCommons.styles.ts index 76841c34c66f542c19b209d73f304bf3905d4240..081af2ed4317ac9f24a6c4375610e0a4e862c157 100644 --- a/src/components/creativecommons/CreativeCommons.styles.ts +++ b/src/components/creativecommons/CreativeCommons.styles.ts @@ -1,60 +1,60 @@ -import styled, { css } from "styled-components"; -import { Breakpoint, Mixins } from "../../style"; - -export const styles = { - Container: styled.div` - padding: 20px; - margin-top: auto; - width: 100%; - - background-color: #fafafa; - - box-shadow: 0 0 5px rgba(67, 52, 52, 0.3); - - ${Mixins.WiderThan( - Breakpoint.md, - css` - flex-direction: row; - ${Mixins.HSpacing(10)}; - align-items: center; - ` - )}; - - ${Mixins.NarrowerThan( - Breakpoint.md, - css` - ${Mixins.VSpacing(10)}; - ` - )}; - `, - - ImageContainer: styled.a` - align-self: flex-start; - `, - - Image: styled.img` - height: 45px; - `, - - Text: styled.span` - display: inline; - flex: 0 0 auto; - - ${Mixins.WiderThan( - Breakpoint.md, - css` - flex: 1 1 0; - ` - )}; - `, - - Link: styled.a` - display: inline; - - &, - &:visited, - &:active { - color: ${p => p.theme.accentColor}; - } - `, -}; +import styled, { css } from "styled-components"; +import { Breakpoint, Mixins } from "../../style"; + +export const styles = { + Container: styled.div` + padding: 20px; + margin-top: auto; + width: 100%; + + background-color: #fafafa; + + box-shadow: 0 0 5px rgba(67, 52, 52, 0.3); + + ${Mixins.WiderThan( + Breakpoint.md, + css` + flex-direction: row; + ${Mixins.HSpacing(10)}; + align-items: center; + ` + )}; + + ${Mixins.NarrowerThan( + Breakpoint.md, + css` + ${Mixins.VSpacing(10)}; + ` + )}; + `, + + ImageContainer: styled.a` + align-self: flex-start; + `, + + Image: styled.img` + height: 45px; + `, + + Text: styled.span` + display: inline; + flex: 0 0 auto; + + ${Mixins.WiderThan( + Breakpoint.md, + css` + flex: 1 1 0; + ` + )}; + `, + + Link: styled.a` + display: inline; + + &, + &:visited, + &:active { + color: ${p => p.theme.accentColor}; + } + `, +}; diff --git a/src/components/creativecommons/CreativeCommons.svg b/src/components/creativecommons/CreativeCommons.svg index 46d4eab5bda43927550e87ee0658730172932ca4..24cb439a1ccdf214f620112779c91a42f792af78 100644 --- a/src/components/creativecommons/CreativeCommons.svg +++ b/src/components/creativecommons/CreativeCommons.svg @@ -1,241 +1,241 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://web.resource.org/cc/" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - viewBox="0 0 120 42" - id="svg2759" - sodipodi:version="0.32" - inkscape:version="0.45+devel" - version="1.0" - sodipodi:docname="by-nc-nd.svg" - inkscape:output_extension="org.inkscape.output.svg.inkscape"> - <defs - id="defs2761" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#8b8b8b" - borderopacity="1" - gridtolerance="10000" - guidetolerance="10" - objecttolerance="10" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="1" - inkscape:cx="179" - inkscape:cy="89.569904" - inkscape:document-units="px" - inkscape:current-layer="layer1" - width="120px" - height="42px" - inkscape:showpageshadow="false" - inkscape:window-width="1198" - inkscape:window-height="624" - inkscape:window-x="488" - inkscape:window-y="401" /> - <metadata - id="metadata2764"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1"> - <g - transform="matrix(0.9937728,0,0,0.9936696,-437.11979,0)" - id="g361" - inkscape:export-filename="/mnt/hgfs/Bov/Documents/Work/2007/cc/identity/srr buttons/big/by-nc-nd.png" - inkscape:export-xdpi="300.23013" - inkscape:export-ydpi="300.23013"> - <path - id="path3817_4_" - nodetypes="ccccccc" - d="M 443.28955,0.44873 L 557.35303,0.65185 C 558.94678,0.65185 560.37061,0.41503 560.37061,3.83203 L 560.23096,41.39892 L 440.41064,41.39892 L 440.41064,3.69238 C 440.41064,2.00781 440.57373,0.44873 443.28955,0.44873 z" - style="fill:#aab2ab" /> - - <path - d="M 558.3501,0 L 442.12061,0 C 440.87354,0 439.85889,1.01465 439.85889,2.26123 L 439.85889,41.75732 C 439.85889,42.03906 440.08741,42.26757 440.36963,42.26757 L 560.1001,42.26757 C 560.38233,42.26757 560.61084,42.03905 560.61084,41.75732 L 560.61084,2.26123 C 560.61084,1.01465 559.59619,0 558.3501,0 z M 442.12061,1.02148 L 558.3501,1.02148 C 559.03369,1.02148 559.58936,1.57763 559.58936,2.26123 C 559.58936,2.26123 559.58936,18.15234 559.58936,29.64893 L 476.51612,29.64893 C 473.4712,35.1543 467.60401,38.89258 460.87159,38.89258 C 454.13721,38.89258 448.27198,35.15772 445.22901,29.64893 L 440.88038,29.64893 C 440.88038,18.15235 440.88038,2.26123 440.88038,2.26123 C 440.88037,1.57764 441.43701,1.02148 442.12061,1.02148 z" - id="path364" /> - - <g - id="g5908_4_" - transform="matrix(0.872921,0,0,0.872921,50.12536,143.2144)"> - - <path - id="path5906_4_" - cx="296.35416" - ry="22.939548" - cy="264.3577" - type="arc" - rx="22.939548" - d="M 486.26709,-141.53052 C 486.27271,-132.85028 479.2392,-125.80957 470.55902,-125.80341 C 461.87878,-125.79841 454.83752,-132.83124 454.83191,-141.51148 C 454.83191,-141.51819 454.83191,-141.52436 454.83191,-141.53052 C 454.82629,-150.21186 461.85974,-157.25257 470.53998,-157.25763 C 479.22132,-157.26263 486.26264,-150.22974 486.26709,-141.5495 C 486.26709,-141.54395 486.26709,-141.53723 486.26709,-141.53052 z" - style="fill:#ffffff" /> - - <g - id="g5706_4_" - transform="translate(-289.6157,99.0653)"> - <path - id="path5708_4_" - d="M 772.94281,-253.39801 C 776.42761,-249.9126 778.17059,-245.64465 778.17059,-240.59582 C 778.17059,-235.54644 776.45782,-231.32434 773.03228,-227.92785 C 769.39642,-224.35186 765.10046,-222.56414 760.14227,-222.56414 C 755.2445,-222.56414 751.0224,-224.33672 747.47827,-227.88366 C 743.93188,-231.4295 742.15985,-235.66668 742.15985,-240.59582 C 742.15985,-245.52435 743.93188,-249.79174 747.47827,-253.39801 C 750.93292,-256.88507 755.15497,-258.6275 760.14227,-258.6275 C 765.1911,-258.6275 769.45685,-256.88507 772.94281,-253.39801 z M 749.82422,-251.05371 C 746.8775,-248.07733 745.40412,-244.59082 745.40412,-240.59131 C 745.40412,-236.59302 746.86298,-233.13611 749.77839,-230.22016 C 752.69605,-227.30421 756.16743,-225.84595 760.19599,-225.84595 C 764.22455,-225.84595 767.72614,-227.31873 770.70307,-230.2649 C 773.529,-233.00074 774.94196,-236.44196 774.94196,-240.59132 C 774.94196,-244.70936 773.5055,-248.20485 770.63373,-251.07606 C 767.76306,-253.94673 764.28382,-255.38264 760.19599,-255.38264 C 756.10816,-255.38264 752.64905,-253.93945 749.82422,-251.05371 z M 757.57812,-242.35052 C 757.12841,-243.33221 756.45495,-243.82281 755.55548,-243.82281 C 753.96692,-243.82281 753.17261,-242.75329 753.17261,-240.61425 C 753.17261,-238.47472 753.96692,-237.40575 755.55548,-237.40575 C 756.60486,-237.40575 757.35443,-237.9265 757.80414,-238.97026 L 760.0069,-237.79729 C 758.95642,-235.93181 757.38123,-234.99822 755.28138,-234.99822 C 753.66151,-234.99822 752.36378,-235.49499 751.38935,-236.48784 C 750.41383,-237.48125 749.92603,-238.85057 749.92603,-240.59581 C 749.92603,-242.31139 750.42945,-243.67284 751.43409,-244.68138 C 752.43873,-245.68992 753.69172,-246.19334 755.1919,-246.19334 C 757.4126,-246.19334 759.00117,-245.31907 759.96326,-243.57103 L 757.57812,-242.35052 z M 767.94208,-242.35052 C 767.49121,-243.33221 766.83002,-243.82281 765.95966,-243.82281 C 764.33863,-243.82281 763.52753,-242.75329 763.52753,-240.61425 C 763.52753,-238.47472 764.33863,-237.40575 765.95966,-237.40575 C 767.0113,-237.40575 767.74738,-237.9265 768.16694,-238.97026 L 770.41895,-237.79729 C 769.37067,-235.93181 767.79773,-234.99822 765.70124,-234.99822 C 764.08356,-234.99822 762.78919,-235.49499 761.81477,-236.48784 C 760.8426,-237.48125 760.35487,-238.85057 760.35487,-240.59581 C 760.35487,-242.31139 760.84932,-243.67284 761.83827,-244.68138 C 762.82612,-245.68992 764.08357,-246.19334 765.61177,-246.19334 C 767.82796,-246.19334 769.41542,-245.31907 770.37306,-243.57103 L 767.94208,-242.35052 z" /> - - </g> - - </g> - - <g - enable-background="new " - id="g370"> - <path - d="M 488.25342,32.95605 C 488.5708,32.95605 488.86182,32.98437 489.12354,33.04003 C 489.38526,33.09569 489.60889,33.18749 489.79639,33.31542 C 489.98291,33.44237 490.12744,33.6123 490.23096,33.82323 C 490.3335,34.03514 490.38526,34.29589 490.38526,34.60741 C 490.38526,34.94335 490.30909,35.22264 490.15577,35.44628 C 490.00343,35.67089 489.77784,35.85351 489.47804,35.99706 C 489.89015,36.11522 490.19777,36.32226 490.40089,36.61815 C 490.60401,36.91404 490.70558,37.27049 490.70558,37.68749 C 490.70558,38.02343 490.64015,38.31444 490.50929,38.56054 C 490.37843,38.80566 490.20167,39.00683 489.98097,39.1621 C 489.75929,39.31835 489.50636,39.43358 489.22316,39.5078 C 488.93898,39.583 488.64796,39.6201 488.34816,39.6201 L 485.11183,39.6201 L 485.11183,32.95604 L 488.25342,32.95604 L 488.25342,32.95605 z M 488.06689,35.65137 C 488.32763,35.65137 488.54345,35.58887 488.71142,35.46485 C 488.87939,35.34083 488.96337,35.13965 488.96337,34.86036 C 488.96337,34.70509 488.93505,34.57716 488.87939,34.47852 C 488.82275,34.37891 488.74853,34.30176 488.65478,34.24512 C 488.56103,34.18946 488.45361,34.15039 488.33251,34.12891 C 488.21141,34.10743 488.08446,34.09668 487.9536,34.09668 L 486.58055,34.09668 L 486.58055,35.65137 L 488.06689,35.65137 z M 488.15186,38.47949 C 488.29541,38.47949 488.43213,38.46582 488.56299,38.4375 C 488.69385,38.40918 488.80908,38.3623 488.90967,38.29785 C 489.00928,38.23242 489.08838,38.14355 489.14795,38.03125 C 489.20752,37.91992 489.23682,37.77637 489.23682,37.60254 C 489.23682,37.26074 489.14014,37.0166 488.94678,36.87012 C 488.75342,36.72461 488.49854,36.65137 488.18018,36.65137 L 486.58057,36.65137 L 486.58057,38.47949 L 488.15186,38.47949 z" - id="path372" - style="fill:#ffffff" /> - - <path - d="M 490.96436,32.95605 L 492.60791,32.95605 L 494.16846,35.58789 L 495.71924,32.95605 L 497.35303,32.95605 L 494.8794,37.0625 L 494.8794,39.62012 L 493.41065,39.62012 L 493.41065,37.02539 L 490.96436,32.95605 z" - id="path374" - style="fill:#ffffff" /> - - </g> - - <g - enable-background="new " - id="g376"> - <path - d="M 512.83057,32.95605 L 515.61475,37.42675 L 515.63037,37.42675 L 515.63037,32.95605 L 517.00537,32.95605 L 517.00537,39.62011 L 515.53955,39.62011 L 512.76611,35.1582 L 512.74756,35.1582 L 512.74756,39.62011 L 511.37256,39.62011 L 511.37256,32.95605 L 512.83057,32.95605 z" - id="path378" - style="fill:#ffffff" /> - - <path - d="M 522.56885,34.73145 C 522.48194,34.59083 522.37256,34.46778 522.2417,34.36231 C 522.11084,34.25684 521.96338,34.17383 521.79834,34.11524 C 521.6333,34.05567 521.46045,34.02637 521.28076,34.02637 C 520.95068,34.02637 520.67041,34.08985 520.43994,34.21778 C 520.20947,34.34473 520.02295,34.51563 519.88037,34.73048 C 519.73682,34.94532 519.63232,35.18946 519.56689,35.4629 C 519.50146,35.73634 519.46923,36.01954 519.46923,36.31153 C 519.46923,36.5918 519.50146,36.86426 519.56689,37.12794 C 519.63232,37.39259 519.73681,37.63087 519.88037,37.84181 C 520.02295,38.05372 520.20947,38.22267 520.43994,38.3506 C 520.67041,38.47853 520.95068,38.54201 521.28076,38.54201 C 521.72803,38.54201 522.07861,38.40529 522.33056,38.13088 C 522.58251,37.85744 522.73681,37.49611 522.79247,37.04787 L 524.21142,37.04787 C 524.17431,37.46486 524.07763,37.84182 523.92236,38.17775 C 523.76709,38.51466 523.56103,38.8008 523.30615,39.0381 C 523.05127,39.2754 522.75244,39.45607 522.40967,39.58107 C 522.06787,39.70607 521.69092,39.76857 521.28076,39.76857 C 520.77002,39.76857 520.31103,39.6797 519.90283,39.50197 C 519.4956,39.32521 519.15088,39.08009 518.8706,38.76955 C 518.58935,38.45803 518.37451,38.09182 518.22509,37.67189 C 518.07568,37.25099 518.00048,36.79884 518.00048,36.31251 C 518.00048,35.81446 518.07568,35.35255 518.22509,34.92579 C 518.3745,34.49903 518.58935,34.12696 518.8706,33.80958 C 519.15087,33.4922 519.4956,33.24317 519.90283,33.06251 C 520.31103,32.88185 520.77002,32.792 521.28076,32.792 C 521.64795,32.792 521.99463,32.84473 522.32178,32.95118 C 522.64795,33.05665 522.94092,33.21095 523.19873,33.41407 C 523.45752,33.61622 523.67041,33.86719 523.83838,34.16602 C 524.00635,34.46485 524.11182,34.80762 524.15576,35.19336 L 522.73681,35.19336 C 522.7124,35.02539 522.65576,34.87109 522.56885,34.73145 z" - id="path380" - style="fill:#ffffff" /> - - </g> - - <g - enable-background="new " - id="g382"> - <path - d="M 538.83057,32.95605 L 541.61475,37.42675 L 541.63037,37.42675 L 541.63037,32.95605 L 543.00537,32.95605 L 543.00537,39.62011 L 541.53955,39.62011 L 538.76611,35.1582 L 538.74756,35.1582 L 538.74756,39.62011 L 537.37256,39.62011 L 537.37256,32.95605 L 538.83057,32.95605 z" - id="path384" - style="fill:#ffffff" /> - - <path - d="M 547.16748,32.95605 C 547.59814,32.95605 547.99756,33.02441 548.36865,33.16113 C 548.73974,33.29785 549.06006,33.5039 549.33154,33.77734 C 549.60205,34.05078 549.81396,34.39355 549.96631,34.80371 C 550.11963,35.21484 550.1958,35.69726 550.1958,36.25098 C 550.1958,36.73633 550.1333,37.1836 550.00928,37.59473 C 549.88428,38.00489 549.6958,38.36035 549.44385,38.65821 C 549.19092,38.95704 548.87647,39.19239 548.49951,39.36329 C 548.12255,39.53419 547.6792,39.62013 547.16748,39.62013 L 544.28955,39.62013 L 544.28955,32.95607 L 547.16748,32.95607 L 547.16748,32.95605 z M 547.06494,38.38574 C 547.27685,38.38574 547.48193,38.35156 547.68115,38.2832 C 547.88037,38.21484 548.0581,38.10156 548.21338,37.94238 C 548.36865,37.78418 548.49365,37.57812 548.5874,37.32324 C 548.68017,37.06836 548.72705,36.75683 548.72705,36.39062 C 548.72705,36.05468 548.69482,35.75195 548.62939,35.48144 C 548.56396,35.21093 548.45654,34.97949 548.30712,34.7871 C 548.1577,34.59471 547.96044,34.44628 547.71435,34.34374 C 547.46826,34.2412 547.16455,34.19042 546.80419,34.19042 L 545.75829,34.19042 L 545.75829,38.38573 L 547.06494,38.38573 L 547.06494,38.38574 z" - id="path386" - style="fill:#ffffff" /> - - </g> - - <g - id="g6370_1_" - transform="translate(286.1464,208.0498)"> - <g - id="g7610_1_" - transform="matrix(1.146822,0,0,1.146822,-67.14005,-41.89676)"> - - <path - id="path6372_1_" - cx="475.97119" - ry="29.209877" - cy="252.08646" - type="arc" - rx="29.209877" - d="M 269.61823,-131.7348 C 269.62247,-126.90787 265.71222,-122.99292 260.88486,-122.98907 C 256.05832,-122.98611 252.14295,-126.89593 252.13956,-131.72204 C 252.13956,-131.72714 252.13956,-131.73098 252.13956,-131.7348 C 252.13614,-136.56216 256.04642,-140.47711 260.87293,-140.48095 C 265.69944,-140.48394 269.61481,-136.57409 269.61823,-131.74801 C 269.61823,-131.74374 269.61823,-131.73907 269.61823,-131.7348 z" - style="fill:#ffffff" /> - - <path - id="path6374_1_" - d="M 260.86526,-141.90982 C 263.71875,-141.90982 266.12945,-140.9263 268.09909,-138.95969 C 270.06869,-136.99219 271.05392,-134.58362 271.05392,-131.73481 C 271.05392,-128.88642 270.08572,-126.5034 268.15017,-124.58659 C 266.09539,-122.56843 263.6668,-121.56022 260.86526,-121.56022 C 258.0986,-121.56022 255.71261,-122.56077 253.70892,-124.5619 C 251.70526,-126.56214 250.70385,-128.95368 250.70385,-131.73481 C 250.70385,-134.51637 251.70525,-136.92493 253.70892,-138.95969 C 255.6615,-140.9263 258.04752,-141.90982 260.86526,-141.90982 z M 252.9928,-134.46866 C 252.68964,-133.6099 252.53723,-132.69876 252.53723,-131.7348 C 252.53723,-129.47952 253.36151,-127.5299 255.00839,-125.88431 C 256.65524,-124.23999 258.61636,-123.41742 260.89166,-123.41742 C 263.1661,-123.41742 265.14422,-124.24808 266.82516,-125.91028 C 267.38803,-126.45398 267.85214,-127.04709 268.21572,-127.69 L 264.37954,-129.39776 C 264.11984,-128.10726 262.96941,-127.23571 261.5797,-127.13351 L 261.5797,-125.56457 L 260.41137,-125.56457 L 260.41137,-127.13351 C 259.26946,-127.1463 258.16674,-127.61337 257.32287,-128.35165 L 258.72448,-129.76477 C 259.39892,-129.12952 260.07418,-128.8447 260.99554,-128.8447 C 261.59246,-128.8447 262.25412,-129.07801 262.25412,-129.8559 C 262.25412,-130.13135 262.14767,-130.32297 261.97992,-130.46729 L 261.00919,-130.89817 L 259.8017,-131.43677 C 259.20392,-131.70331 258.69724,-131.92768 258.18973,-132.15418 L 252.9928,-134.46866 z M 260.89166,-140.07861 C 258.58145,-140.07861 256.6297,-139.26495 255.03308,-137.63638 C 254.59878,-137.19784 254.2207,-136.74014 253.90054,-136.26199 L 257.78952,-134.52996 C 258.1412,-135.60931 259.16644,-136.26412 260.41138,-136.33651 L 260.41138,-137.90548 L 261.57971,-137.90548 L 261.57971,-136.33651 C 262.3844,-136.29775 263.2666,-136.07723 264.13601,-135.40365 L 262.7991,-134.02926 C 262.30606,-134.37927 261.68359,-134.62536 261.06027,-134.62536 C 260.55444,-134.62536 259.83999,-134.47036 259.83999,-133.83468 C 259.83999,-133.73803 259.87322,-133.65289 259.93197,-133.57626 L 261.23312,-132.99807 L 262.11361,-132.60549 C 262.67648,-132.35387 263.21465,-132.11544 263.74685,-131.87829 L 268.96084,-129.557 C 269.13284,-130.2395 269.21969,-130.96587 269.21969,-131.7348 C 269.21969,-134.05823 268.40478,-136.02569 266.77493,-137.63638 C 265.16129,-139.26495 263.20102,-140.07861 260.89166,-140.07861 z" /> - - </g> - - </g> - - <g - id="g6394_1_" - transform="matrix(0.624995,0,0,0.624995,312.8511,316.9328)"> - - <path - id="path6396_1_" - cx="475.97119" - ry="29.209877" - cy="252.08646" - type="arc" - rx="29.209877" - d="M 387.83435,-482.97366 C 387.84216,-473.56265 380.2171,-465.92666 370.80609,-465.91885 C 361.39349,-465.91342 353.75751,-473.53689 353.75122,-482.95022 C 353.75122,-482.9573 353.75122,-482.96664 353.75122,-482.97366 C 353.74499,-492.38546 361.36847,-500.02145 370.78107,-500.02853 C 380.19208,-500.03634 387.82807,-492.41128 387.83435,-482.99948 C 387.83435,-482.99008 387.83435,-482.98306 387.83435,-482.97366 z" - style="fill:#ffffff" /> - - <g - id="g6398_1_" - transform="translate(-23.9521,-87.92102)"> - <path - id="path6400_1_" - d="M 394.47845,-413.72311 C 389.30651,-413.72311 384.9284,-411.92001 381.34552,-408.30978 C 377.66895,-404.5762 375.83142,-400.15817 375.83142,-395.05264 C 375.83142,-389.94949 377.66894,-385.56271 381.34552,-381.89084 C 385.02057,-378.21896 389.40027,-376.38297 394.47845,-376.38297 C 399.61914,-376.38297 404.07385,-378.23533 407.84417,-381.93613 C 411.39422,-385.45334 413.17236,-389.82608 413.17236,-395.05265 C 413.17236,-400.28239 411.36456,-404.69962 407.75042,-408.30979 C 404.13635,-411.92001 399.7113,-413.72311 394.47845,-413.72311 z M 394.5238,-410.36453 C 398.76129,-410.36453 402.3598,-408.86996 405.32074,-405.88168 C 408.3114,-402.92617 409.80518,-399.31753 409.80518,-395.05264 C 409.80518,-390.75888 408.34266,-387.19638 405.41449,-384.36508 C 402.32855,-381.31503 398.69879,-379.79239 394.5238,-379.79239 C 390.34875,-379.79239 386.7503,-381.3002 383.72839,-384.31979 C 380.70648,-387.337 379.19555,-390.91592 379.19555,-395.05264 C 379.19555,-399.19333 380.7221,-402.80197 383.77679,-405.88168 C 386.70496,-408.86996 390.28625,-410.36453 394.5238,-410.36453 z" /> - - <g - id="g6402_1_"> - <path - id="path6404_1_" - d="M 401.55505,-399.47849 L 387.98468,-399.47849 L 387.98468,-396.26359 L 401.55505,-396.26359 L 401.55505,-399.47849 z M 401.55505,-393.47763 L 387.98468,-393.47763 L 387.98468,-390.26358 L 401.55505,-390.26358 L 401.55505,-393.47763 z" /> - - </g> - - </g> - - </g> - - <g - id="g398"> - <circle - cx="491.9473" - cy="15.31396" - r="10.80615" - id="circle400" - sodipodi:cx="491.9473" - sodipodi:cy="15.31396" - sodipodi:rx="10.80615" - sodipodi:ry="10.80615" - style="fill:#ffffff" /> - - <g - id="g402"> - <path - d="M 495.07474,12.18701 C 495.07474,11.77051 494.73685,11.43359 494.32083,11.43359 L 489.54837,11.43359 C 489.13235,11.43359 488.79446,11.7705 488.79446,12.18701 L 488.79446,16.95996 L 490.12551,16.95996 L 490.12551,22.6123 L 493.7427,22.6123 L 493.7427,16.95996 L 495.07473,16.95996 L 495.07473,12.18701 L 495.07474,12.18701 z" - id="path404" /> - - <circle - cx="491.9346" - cy="9.1723604" - r="1.63232" - id="circle406" - sodipodi:cx="491.9346" - sodipodi:cy="9.1723604" - sodipodi:rx="1.63232" - sodipodi:ry="1.63232" /> - - </g> - - <path - clip-rule="evenodd" - d="M 491.91946,3.40771 C 488.68801,3.40771 485.95169,4.53515 483.71243,6.7915 C 481.41458,9.12451 480.26614,11.88671 480.26614,15.07568 C 480.26614,18.26465 481.41458,21.00781 483.71243,23.30273 C 486.01028,25.59716 488.74661,26.74462 491.91946,26.74462 C 495.13235,26.74462 497.91751,25.58788 500.27395,23.27294 C 502.49368,21.07616 503.60305,18.34325 503.60305,15.07567 C 503.60305,11.80809 502.47414,9.04735 500.21535,6.79149 C 497.95657,4.53516 495.19193,3.40771 491.91946,3.40771 z M 491.94974,5.50732 C 494.59818,5.50732 496.84622,6.44091 498.69583,8.3081 C 500.56595,10.15527 501.50052,12.41162 501.50052,15.07568 C 501.50052,17.75927 500.58548,19.98681 498.75443,21.75634 C 496.8267,23.6621 494.55814,24.61474 491.94974,24.61474 C 489.33939,24.61474 487.09036,23.67187 485.20169,21.78564 C 483.31302,19.89892 482.36868,17.66259 482.36868,15.07568 C 482.36868,12.48925 483.32278,10.23339 485.23098,8.3081 C 487.06204,6.44092 489.3013,5.50732 491.94974,5.50732 z" - id="path408" - style="fill-rule:evenodd" /> - - </g> - -</g> - </g> -</svg> +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + viewBox="0 0 120 42" + id="svg2759" + sodipodi:version="0.32" + inkscape:version="0.45+devel" + version="1.0" + sodipodi:docname="by-nc-nd.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs2761" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#8b8b8b" + borderopacity="1" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1" + inkscape:cx="179" + inkscape:cy="89.569904" + inkscape:document-units="px" + inkscape:current-layer="layer1" + width="120px" + height="42px" + inkscape:showpageshadow="false" + inkscape:window-width="1198" + inkscape:window-height="624" + inkscape:window-x="488" + inkscape:window-y="401" /> + <metadata + id="metadata2764"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + transform="matrix(0.9937728,0,0,0.9936696,-437.11979,0)" + id="g361" + inkscape:export-filename="/mnt/hgfs/Bov/Documents/Work/2007/cc/identity/srr buttons/big/by-nc-nd.png" + inkscape:export-xdpi="300.23013" + inkscape:export-ydpi="300.23013"> + <path + id="path3817_4_" + nodetypes="ccccccc" + d="M 443.28955,0.44873 L 557.35303,0.65185 C 558.94678,0.65185 560.37061,0.41503 560.37061,3.83203 L 560.23096,41.39892 L 440.41064,41.39892 L 440.41064,3.69238 C 440.41064,2.00781 440.57373,0.44873 443.28955,0.44873 z" + style="fill:#aab2ab" /> + + <path + d="M 558.3501,0 L 442.12061,0 C 440.87354,0 439.85889,1.01465 439.85889,2.26123 L 439.85889,41.75732 C 439.85889,42.03906 440.08741,42.26757 440.36963,42.26757 L 560.1001,42.26757 C 560.38233,42.26757 560.61084,42.03905 560.61084,41.75732 L 560.61084,2.26123 C 560.61084,1.01465 559.59619,0 558.3501,0 z M 442.12061,1.02148 L 558.3501,1.02148 C 559.03369,1.02148 559.58936,1.57763 559.58936,2.26123 C 559.58936,2.26123 559.58936,18.15234 559.58936,29.64893 L 476.51612,29.64893 C 473.4712,35.1543 467.60401,38.89258 460.87159,38.89258 C 454.13721,38.89258 448.27198,35.15772 445.22901,29.64893 L 440.88038,29.64893 C 440.88038,18.15235 440.88038,2.26123 440.88038,2.26123 C 440.88037,1.57764 441.43701,1.02148 442.12061,1.02148 z" + id="path364" /> + + <g + id="g5908_4_" + transform="matrix(0.872921,0,0,0.872921,50.12536,143.2144)"> + + <path + id="path5906_4_" + cx="296.35416" + ry="22.939548" + cy="264.3577" + type="arc" + rx="22.939548" + d="M 486.26709,-141.53052 C 486.27271,-132.85028 479.2392,-125.80957 470.55902,-125.80341 C 461.87878,-125.79841 454.83752,-132.83124 454.83191,-141.51148 C 454.83191,-141.51819 454.83191,-141.52436 454.83191,-141.53052 C 454.82629,-150.21186 461.85974,-157.25257 470.53998,-157.25763 C 479.22132,-157.26263 486.26264,-150.22974 486.26709,-141.5495 C 486.26709,-141.54395 486.26709,-141.53723 486.26709,-141.53052 z" + style="fill:#ffffff" /> + + <g + id="g5706_4_" + transform="translate(-289.6157,99.0653)"> + <path + id="path5708_4_" + d="M 772.94281,-253.39801 C 776.42761,-249.9126 778.17059,-245.64465 778.17059,-240.59582 C 778.17059,-235.54644 776.45782,-231.32434 773.03228,-227.92785 C 769.39642,-224.35186 765.10046,-222.56414 760.14227,-222.56414 C 755.2445,-222.56414 751.0224,-224.33672 747.47827,-227.88366 C 743.93188,-231.4295 742.15985,-235.66668 742.15985,-240.59582 C 742.15985,-245.52435 743.93188,-249.79174 747.47827,-253.39801 C 750.93292,-256.88507 755.15497,-258.6275 760.14227,-258.6275 C 765.1911,-258.6275 769.45685,-256.88507 772.94281,-253.39801 z M 749.82422,-251.05371 C 746.8775,-248.07733 745.40412,-244.59082 745.40412,-240.59131 C 745.40412,-236.59302 746.86298,-233.13611 749.77839,-230.22016 C 752.69605,-227.30421 756.16743,-225.84595 760.19599,-225.84595 C 764.22455,-225.84595 767.72614,-227.31873 770.70307,-230.2649 C 773.529,-233.00074 774.94196,-236.44196 774.94196,-240.59132 C 774.94196,-244.70936 773.5055,-248.20485 770.63373,-251.07606 C 767.76306,-253.94673 764.28382,-255.38264 760.19599,-255.38264 C 756.10816,-255.38264 752.64905,-253.93945 749.82422,-251.05371 z M 757.57812,-242.35052 C 757.12841,-243.33221 756.45495,-243.82281 755.55548,-243.82281 C 753.96692,-243.82281 753.17261,-242.75329 753.17261,-240.61425 C 753.17261,-238.47472 753.96692,-237.40575 755.55548,-237.40575 C 756.60486,-237.40575 757.35443,-237.9265 757.80414,-238.97026 L 760.0069,-237.79729 C 758.95642,-235.93181 757.38123,-234.99822 755.28138,-234.99822 C 753.66151,-234.99822 752.36378,-235.49499 751.38935,-236.48784 C 750.41383,-237.48125 749.92603,-238.85057 749.92603,-240.59581 C 749.92603,-242.31139 750.42945,-243.67284 751.43409,-244.68138 C 752.43873,-245.68992 753.69172,-246.19334 755.1919,-246.19334 C 757.4126,-246.19334 759.00117,-245.31907 759.96326,-243.57103 L 757.57812,-242.35052 z M 767.94208,-242.35052 C 767.49121,-243.33221 766.83002,-243.82281 765.95966,-243.82281 C 764.33863,-243.82281 763.52753,-242.75329 763.52753,-240.61425 C 763.52753,-238.47472 764.33863,-237.40575 765.95966,-237.40575 C 767.0113,-237.40575 767.74738,-237.9265 768.16694,-238.97026 L 770.41895,-237.79729 C 769.37067,-235.93181 767.79773,-234.99822 765.70124,-234.99822 C 764.08356,-234.99822 762.78919,-235.49499 761.81477,-236.48784 C 760.8426,-237.48125 760.35487,-238.85057 760.35487,-240.59581 C 760.35487,-242.31139 760.84932,-243.67284 761.83827,-244.68138 C 762.82612,-245.68992 764.08357,-246.19334 765.61177,-246.19334 C 767.82796,-246.19334 769.41542,-245.31907 770.37306,-243.57103 L 767.94208,-242.35052 z" /> + + </g> + + </g> + + <g + enable-background="new " + id="g370"> + <path + d="M 488.25342,32.95605 C 488.5708,32.95605 488.86182,32.98437 489.12354,33.04003 C 489.38526,33.09569 489.60889,33.18749 489.79639,33.31542 C 489.98291,33.44237 490.12744,33.6123 490.23096,33.82323 C 490.3335,34.03514 490.38526,34.29589 490.38526,34.60741 C 490.38526,34.94335 490.30909,35.22264 490.15577,35.44628 C 490.00343,35.67089 489.77784,35.85351 489.47804,35.99706 C 489.89015,36.11522 490.19777,36.32226 490.40089,36.61815 C 490.60401,36.91404 490.70558,37.27049 490.70558,37.68749 C 490.70558,38.02343 490.64015,38.31444 490.50929,38.56054 C 490.37843,38.80566 490.20167,39.00683 489.98097,39.1621 C 489.75929,39.31835 489.50636,39.43358 489.22316,39.5078 C 488.93898,39.583 488.64796,39.6201 488.34816,39.6201 L 485.11183,39.6201 L 485.11183,32.95604 L 488.25342,32.95604 L 488.25342,32.95605 z M 488.06689,35.65137 C 488.32763,35.65137 488.54345,35.58887 488.71142,35.46485 C 488.87939,35.34083 488.96337,35.13965 488.96337,34.86036 C 488.96337,34.70509 488.93505,34.57716 488.87939,34.47852 C 488.82275,34.37891 488.74853,34.30176 488.65478,34.24512 C 488.56103,34.18946 488.45361,34.15039 488.33251,34.12891 C 488.21141,34.10743 488.08446,34.09668 487.9536,34.09668 L 486.58055,34.09668 L 486.58055,35.65137 L 488.06689,35.65137 z M 488.15186,38.47949 C 488.29541,38.47949 488.43213,38.46582 488.56299,38.4375 C 488.69385,38.40918 488.80908,38.3623 488.90967,38.29785 C 489.00928,38.23242 489.08838,38.14355 489.14795,38.03125 C 489.20752,37.91992 489.23682,37.77637 489.23682,37.60254 C 489.23682,37.26074 489.14014,37.0166 488.94678,36.87012 C 488.75342,36.72461 488.49854,36.65137 488.18018,36.65137 L 486.58057,36.65137 L 486.58057,38.47949 L 488.15186,38.47949 z" + id="path372" + style="fill:#ffffff" /> + + <path + d="M 490.96436,32.95605 L 492.60791,32.95605 L 494.16846,35.58789 L 495.71924,32.95605 L 497.35303,32.95605 L 494.8794,37.0625 L 494.8794,39.62012 L 493.41065,39.62012 L 493.41065,37.02539 L 490.96436,32.95605 z" + id="path374" + style="fill:#ffffff" /> + + </g> + + <g + enable-background="new " + id="g376"> + <path + d="M 512.83057,32.95605 L 515.61475,37.42675 L 515.63037,37.42675 L 515.63037,32.95605 L 517.00537,32.95605 L 517.00537,39.62011 L 515.53955,39.62011 L 512.76611,35.1582 L 512.74756,35.1582 L 512.74756,39.62011 L 511.37256,39.62011 L 511.37256,32.95605 L 512.83057,32.95605 z" + id="path378" + style="fill:#ffffff" /> + + <path + d="M 522.56885,34.73145 C 522.48194,34.59083 522.37256,34.46778 522.2417,34.36231 C 522.11084,34.25684 521.96338,34.17383 521.79834,34.11524 C 521.6333,34.05567 521.46045,34.02637 521.28076,34.02637 C 520.95068,34.02637 520.67041,34.08985 520.43994,34.21778 C 520.20947,34.34473 520.02295,34.51563 519.88037,34.73048 C 519.73682,34.94532 519.63232,35.18946 519.56689,35.4629 C 519.50146,35.73634 519.46923,36.01954 519.46923,36.31153 C 519.46923,36.5918 519.50146,36.86426 519.56689,37.12794 C 519.63232,37.39259 519.73681,37.63087 519.88037,37.84181 C 520.02295,38.05372 520.20947,38.22267 520.43994,38.3506 C 520.67041,38.47853 520.95068,38.54201 521.28076,38.54201 C 521.72803,38.54201 522.07861,38.40529 522.33056,38.13088 C 522.58251,37.85744 522.73681,37.49611 522.79247,37.04787 L 524.21142,37.04787 C 524.17431,37.46486 524.07763,37.84182 523.92236,38.17775 C 523.76709,38.51466 523.56103,38.8008 523.30615,39.0381 C 523.05127,39.2754 522.75244,39.45607 522.40967,39.58107 C 522.06787,39.70607 521.69092,39.76857 521.28076,39.76857 C 520.77002,39.76857 520.31103,39.6797 519.90283,39.50197 C 519.4956,39.32521 519.15088,39.08009 518.8706,38.76955 C 518.58935,38.45803 518.37451,38.09182 518.22509,37.67189 C 518.07568,37.25099 518.00048,36.79884 518.00048,36.31251 C 518.00048,35.81446 518.07568,35.35255 518.22509,34.92579 C 518.3745,34.49903 518.58935,34.12696 518.8706,33.80958 C 519.15087,33.4922 519.4956,33.24317 519.90283,33.06251 C 520.31103,32.88185 520.77002,32.792 521.28076,32.792 C 521.64795,32.792 521.99463,32.84473 522.32178,32.95118 C 522.64795,33.05665 522.94092,33.21095 523.19873,33.41407 C 523.45752,33.61622 523.67041,33.86719 523.83838,34.16602 C 524.00635,34.46485 524.11182,34.80762 524.15576,35.19336 L 522.73681,35.19336 C 522.7124,35.02539 522.65576,34.87109 522.56885,34.73145 z" + id="path380" + style="fill:#ffffff" /> + + </g> + + <g + enable-background="new " + id="g382"> + <path + d="M 538.83057,32.95605 L 541.61475,37.42675 L 541.63037,37.42675 L 541.63037,32.95605 L 543.00537,32.95605 L 543.00537,39.62011 L 541.53955,39.62011 L 538.76611,35.1582 L 538.74756,35.1582 L 538.74756,39.62011 L 537.37256,39.62011 L 537.37256,32.95605 L 538.83057,32.95605 z" + id="path384" + style="fill:#ffffff" /> + + <path + d="M 547.16748,32.95605 C 547.59814,32.95605 547.99756,33.02441 548.36865,33.16113 C 548.73974,33.29785 549.06006,33.5039 549.33154,33.77734 C 549.60205,34.05078 549.81396,34.39355 549.96631,34.80371 C 550.11963,35.21484 550.1958,35.69726 550.1958,36.25098 C 550.1958,36.73633 550.1333,37.1836 550.00928,37.59473 C 549.88428,38.00489 549.6958,38.36035 549.44385,38.65821 C 549.19092,38.95704 548.87647,39.19239 548.49951,39.36329 C 548.12255,39.53419 547.6792,39.62013 547.16748,39.62013 L 544.28955,39.62013 L 544.28955,32.95607 L 547.16748,32.95607 L 547.16748,32.95605 z M 547.06494,38.38574 C 547.27685,38.38574 547.48193,38.35156 547.68115,38.2832 C 547.88037,38.21484 548.0581,38.10156 548.21338,37.94238 C 548.36865,37.78418 548.49365,37.57812 548.5874,37.32324 C 548.68017,37.06836 548.72705,36.75683 548.72705,36.39062 C 548.72705,36.05468 548.69482,35.75195 548.62939,35.48144 C 548.56396,35.21093 548.45654,34.97949 548.30712,34.7871 C 548.1577,34.59471 547.96044,34.44628 547.71435,34.34374 C 547.46826,34.2412 547.16455,34.19042 546.80419,34.19042 L 545.75829,34.19042 L 545.75829,38.38573 L 547.06494,38.38573 L 547.06494,38.38574 z" + id="path386" + style="fill:#ffffff" /> + + </g> + + <g + id="g6370_1_" + transform="translate(286.1464,208.0498)"> + <g + id="g7610_1_" + transform="matrix(1.146822,0,0,1.146822,-67.14005,-41.89676)"> + + <path + id="path6372_1_" + cx="475.97119" + ry="29.209877" + cy="252.08646" + type="arc" + rx="29.209877" + d="M 269.61823,-131.7348 C 269.62247,-126.90787 265.71222,-122.99292 260.88486,-122.98907 C 256.05832,-122.98611 252.14295,-126.89593 252.13956,-131.72204 C 252.13956,-131.72714 252.13956,-131.73098 252.13956,-131.7348 C 252.13614,-136.56216 256.04642,-140.47711 260.87293,-140.48095 C 265.69944,-140.48394 269.61481,-136.57409 269.61823,-131.74801 C 269.61823,-131.74374 269.61823,-131.73907 269.61823,-131.7348 z" + style="fill:#ffffff" /> + + <path + id="path6374_1_" + d="M 260.86526,-141.90982 C 263.71875,-141.90982 266.12945,-140.9263 268.09909,-138.95969 C 270.06869,-136.99219 271.05392,-134.58362 271.05392,-131.73481 C 271.05392,-128.88642 270.08572,-126.5034 268.15017,-124.58659 C 266.09539,-122.56843 263.6668,-121.56022 260.86526,-121.56022 C 258.0986,-121.56022 255.71261,-122.56077 253.70892,-124.5619 C 251.70526,-126.56214 250.70385,-128.95368 250.70385,-131.73481 C 250.70385,-134.51637 251.70525,-136.92493 253.70892,-138.95969 C 255.6615,-140.9263 258.04752,-141.90982 260.86526,-141.90982 z M 252.9928,-134.46866 C 252.68964,-133.6099 252.53723,-132.69876 252.53723,-131.7348 C 252.53723,-129.47952 253.36151,-127.5299 255.00839,-125.88431 C 256.65524,-124.23999 258.61636,-123.41742 260.89166,-123.41742 C 263.1661,-123.41742 265.14422,-124.24808 266.82516,-125.91028 C 267.38803,-126.45398 267.85214,-127.04709 268.21572,-127.69 L 264.37954,-129.39776 C 264.11984,-128.10726 262.96941,-127.23571 261.5797,-127.13351 L 261.5797,-125.56457 L 260.41137,-125.56457 L 260.41137,-127.13351 C 259.26946,-127.1463 258.16674,-127.61337 257.32287,-128.35165 L 258.72448,-129.76477 C 259.39892,-129.12952 260.07418,-128.8447 260.99554,-128.8447 C 261.59246,-128.8447 262.25412,-129.07801 262.25412,-129.8559 C 262.25412,-130.13135 262.14767,-130.32297 261.97992,-130.46729 L 261.00919,-130.89817 L 259.8017,-131.43677 C 259.20392,-131.70331 258.69724,-131.92768 258.18973,-132.15418 L 252.9928,-134.46866 z M 260.89166,-140.07861 C 258.58145,-140.07861 256.6297,-139.26495 255.03308,-137.63638 C 254.59878,-137.19784 254.2207,-136.74014 253.90054,-136.26199 L 257.78952,-134.52996 C 258.1412,-135.60931 259.16644,-136.26412 260.41138,-136.33651 L 260.41138,-137.90548 L 261.57971,-137.90548 L 261.57971,-136.33651 C 262.3844,-136.29775 263.2666,-136.07723 264.13601,-135.40365 L 262.7991,-134.02926 C 262.30606,-134.37927 261.68359,-134.62536 261.06027,-134.62536 C 260.55444,-134.62536 259.83999,-134.47036 259.83999,-133.83468 C 259.83999,-133.73803 259.87322,-133.65289 259.93197,-133.57626 L 261.23312,-132.99807 L 262.11361,-132.60549 C 262.67648,-132.35387 263.21465,-132.11544 263.74685,-131.87829 L 268.96084,-129.557 C 269.13284,-130.2395 269.21969,-130.96587 269.21969,-131.7348 C 269.21969,-134.05823 268.40478,-136.02569 266.77493,-137.63638 C 265.16129,-139.26495 263.20102,-140.07861 260.89166,-140.07861 z" /> + + </g> + + </g> + + <g + id="g6394_1_" + transform="matrix(0.624995,0,0,0.624995,312.8511,316.9328)"> + + <path + id="path6396_1_" + cx="475.97119" + ry="29.209877" + cy="252.08646" + type="arc" + rx="29.209877" + d="M 387.83435,-482.97366 C 387.84216,-473.56265 380.2171,-465.92666 370.80609,-465.91885 C 361.39349,-465.91342 353.75751,-473.53689 353.75122,-482.95022 C 353.75122,-482.9573 353.75122,-482.96664 353.75122,-482.97366 C 353.74499,-492.38546 361.36847,-500.02145 370.78107,-500.02853 C 380.19208,-500.03634 387.82807,-492.41128 387.83435,-482.99948 C 387.83435,-482.99008 387.83435,-482.98306 387.83435,-482.97366 z" + style="fill:#ffffff" /> + + <g + id="g6398_1_" + transform="translate(-23.9521,-87.92102)"> + <path + id="path6400_1_" + d="M 394.47845,-413.72311 C 389.30651,-413.72311 384.9284,-411.92001 381.34552,-408.30978 C 377.66895,-404.5762 375.83142,-400.15817 375.83142,-395.05264 C 375.83142,-389.94949 377.66894,-385.56271 381.34552,-381.89084 C 385.02057,-378.21896 389.40027,-376.38297 394.47845,-376.38297 C 399.61914,-376.38297 404.07385,-378.23533 407.84417,-381.93613 C 411.39422,-385.45334 413.17236,-389.82608 413.17236,-395.05265 C 413.17236,-400.28239 411.36456,-404.69962 407.75042,-408.30979 C 404.13635,-411.92001 399.7113,-413.72311 394.47845,-413.72311 z M 394.5238,-410.36453 C 398.76129,-410.36453 402.3598,-408.86996 405.32074,-405.88168 C 408.3114,-402.92617 409.80518,-399.31753 409.80518,-395.05264 C 409.80518,-390.75888 408.34266,-387.19638 405.41449,-384.36508 C 402.32855,-381.31503 398.69879,-379.79239 394.5238,-379.79239 C 390.34875,-379.79239 386.7503,-381.3002 383.72839,-384.31979 C 380.70648,-387.337 379.19555,-390.91592 379.19555,-395.05264 C 379.19555,-399.19333 380.7221,-402.80197 383.77679,-405.88168 C 386.70496,-408.86996 390.28625,-410.36453 394.5238,-410.36453 z" /> + + <g + id="g6402_1_"> + <path + id="path6404_1_" + d="M 401.55505,-399.47849 L 387.98468,-399.47849 L 387.98468,-396.26359 L 401.55505,-396.26359 L 401.55505,-399.47849 z M 401.55505,-393.47763 L 387.98468,-393.47763 L 387.98468,-390.26358 L 401.55505,-390.26358 L 401.55505,-393.47763 z" /> + + </g> + + </g> + + </g> + + <g + id="g398"> + <circle + cx="491.9473" + cy="15.31396" + r="10.80615" + id="circle400" + sodipodi:cx="491.9473" + sodipodi:cy="15.31396" + sodipodi:rx="10.80615" + sodipodi:ry="10.80615" + style="fill:#ffffff" /> + + <g + id="g402"> + <path + d="M 495.07474,12.18701 C 495.07474,11.77051 494.73685,11.43359 494.32083,11.43359 L 489.54837,11.43359 C 489.13235,11.43359 488.79446,11.7705 488.79446,12.18701 L 488.79446,16.95996 L 490.12551,16.95996 L 490.12551,22.6123 L 493.7427,22.6123 L 493.7427,16.95996 L 495.07473,16.95996 L 495.07473,12.18701 L 495.07474,12.18701 z" + id="path404" /> + + <circle + cx="491.9346" + cy="9.1723604" + r="1.63232" + id="circle406" + sodipodi:cx="491.9346" + sodipodi:cy="9.1723604" + sodipodi:rx="1.63232" + sodipodi:ry="1.63232" /> + + </g> + + <path + clip-rule="evenodd" + d="M 491.91946,3.40771 C 488.68801,3.40771 485.95169,4.53515 483.71243,6.7915 C 481.41458,9.12451 480.26614,11.88671 480.26614,15.07568 C 480.26614,18.26465 481.41458,21.00781 483.71243,23.30273 C 486.01028,25.59716 488.74661,26.74462 491.91946,26.74462 C 495.13235,26.74462 497.91751,25.58788 500.27395,23.27294 C 502.49368,21.07616 503.60305,18.34325 503.60305,15.07567 C 503.60305,11.80809 502.47414,9.04735 500.21535,6.79149 C 497.95657,4.53516 495.19193,3.40771 491.91946,3.40771 z M 491.94974,5.50732 C 494.59818,5.50732 496.84622,6.44091 498.69583,8.3081 C 500.56595,10.15527 501.50052,12.41162 501.50052,15.07568 C 501.50052,17.75927 500.58548,19.98681 498.75443,21.75634 C 496.8267,23.6621 494.55814,24.61474 491.94974,24.61474 C 489.33939,24.61474 487.09036,23.67187 485.20169,21.78564 C 483.31302,19.89892 482.36868,17.66259 482.36868,15.07568 C 482.36868,12.48925 483.32278,10.23339 485.23098,8.3081 C 487.06204,6.44092 489.3013,5.50732 491.94974,5.50732 z" + id="path408" + style="fill-rule:evenodd" /> + + </g> + +</g> + </g> +</svg> diff --git a/src/components/creativecommons/__stories__/CrativeCommons.stories.tsx b/src/components/creativecommons/__stories__/CrativeCommons.stories.tsx index 223bb2ef261fa273af253b6e1aea3a746eac5be5..1dbb2a677d31e2f4a353eeba0ee22798dfa053a0 100644 --- a/src/components/creativecommons/__stories__/CrativeCommons.stories.tsx +++ b/src/components/creativecommons/__stories__/CrativeCommons.stories.tsx @@ -1,7 +1,7 @@ -import { CreativeCommons } from "../CreativeCommons"; -import React from "react"; -import { storiesOf } from "@storybook/react"; - -const Default = () => <CreativeCommons />; - -storiesOf("CreativeCommons", module).add("Default", () => <Default />); +import { CreativeCommons } from "../CreativeCommons"; +import React from "react"; +import { storiesOf } from "@storybook/react"; + +const Default = () => <CreativeCommons />; + +storiesOf("CreativeCommons", module).add("Default", () => <Default />); diff --git a/src/components/header/__stories__/CrativeCommons.stories.tsx b/src/components/header/__stories__/CrativeCommons.stories.tsx index a1818340d933a4ba48ac414d0dfc123ea3db5aef..c8759b216dbb923230fa6dc6ae34c22c3e6d0d80 100644 --- a/src/components/header/__stories__/CrativeCommons.stories.tsx +++ b/src/components/header/__stories__/CrativeCommons.stories.tsx @@ -1,26 +1,26 @@ -import { boolean, text } from "@storybook/addon-knobs"; - -import { Header } from "../Header"; -import React from "react"; -import { storiesOf } from "@storybook/react"; -import styled from "styled-components"; - -const Container = styled.div` - height: 50px; - flex-direction: row; - width: 100%; - box-shadow: 0 0 5px #ddd; -`; - -const Default = () => { - const showBackButton = boolean("show back button", true); - const title = text("title", "Default story"); - - return ( - <Container> - <Header showBackButton={showBackButton} title={title} /> - </Container> - ); -}; - -storiesOf("Header", module).add("Default", () => <Default />); +import { boolean, text } from "@storybook/addon-knobs"; + +import { Header } from "../Header"; +import React from "react"; +import { storiesOf } from "@storybook/react"; +import styled from "styled-components"; + +const Container = styled.div` + height: 50px; + flex-direction: row; + width: 100%; + box-shadow: 0 0 5px #ddd; +`; + +const Default = () => { + const showBackButton = boolean("show back button", true); + const title = text("title", "Default story"); + + return ( + <Container> + <Header showBackButton={showBackButton} title={title} /> + </Container> + ); +}; + +storiesOf("Header", module).add("Default", () => <Default />); diff --git a/src/components/layer/Layer.styles.ts b/src/components/layer/Layer.styles.ts index bac54c1a412b503d645a48ae2f5ae47312f52975..985fbed8524afe20c55bc8870341f7998644a2ed 100644 --- a/src/components/layer/Layer.styles.ts +++ b/src/components/layer/Layer.styles.ts @@ -1,9 +1,9 @@ -import styled from "styled-components"; - -export const styles = { - Container: styled.div` - position: absolute; - - pointer-events: all; - `, -}; +import styled from "styled-components"; + +export const styles = { + Container: styled.div` + position: absolute; + + pointer-events: all; + `, +}; diff --git a/src/components/layer/Layer.tsx b/src/components/layer/Layer.tsx index e13287ae98de92a08b5703c71ae221d13a00edf2..73f24885eb1ed3bf68d92c9c40980253317a2ad2 100644 --- a/src/components/layer/Layer.tsx +++ b/src/components/layer/Layer.tsx @@ -1,96 +1,96 @@ -import { observer } from "mobx-react-lite"; -import React from "react"; -import { createPortal } from "react-dom"; -import { useLayer } from "src/hooks/useLayer"; -import { styles } from "./Layer.styles"; - -export type LayerPosition = - | { - top: number; - left: number; - } - | { - top: number; - right: number; - } - | { - bottom: number; - left: number; - } - | { - bottom: number; - right: number; - }; - -export type LayerProps = { - calculatePosition: (layersWrapper: HTMLDivElement, layer: HTMLDivElement) => LayerPosition; - updatePosition?: React.MutableRefObject<(() => boolean) | null>; - - onFocus?: React.FocusEventHandler<HTMLDivElement>; - onBlur?: React.FocusEventHandler<HTMLDivElement>; - onMouseEnter?: React.MouseEventHandler<HTMLDivElement>; - onMouseLeave?: React.MouseEventHandler<HTMLDivElement>; - - className?: string; - style?: React.CSSProperties; -}; - -export const Layer: React.FunctionComponent<LayerProps> = observer(props => { - const [shouldRender, setShouldRender] = React.useState(false); - const [coordinates, setCoordinates] = React.useState<LayerPosition>({ left: 0, top: 0 }); - - const layerRef = React.useRef<HTMLDivElement>(null); - const { containerRef: layersWrapper } = useLayer(); - - const updatePosition = (): boolean => { - if (!layersWrapper.current || !layerRef.current) return false; - - const position = props.calculatePosition(layersWrapper.current, layerRef.current); - - setCoordinates(position); - setShouldRender(true); - return true; - }; - - React.useEffect(() => { - if (props.updatePosition) props.updatePosition.current = updatePosition; - - if (shouldRender) return; - - updatePosition(); - }); - - React.useEffect(() => { - const handleFullscreenChange = () => { - setTimeout(() => updatePosition(), 100); - }; - - document.addEventListener("fullscreenchange", handleFullscreenChange); - - return () => document.removeEventListener("fullscreenchange", handleFullscreenChange); - }, []); - - const style: React.CSSProperties = { - opacity: shouldRender ? 1 : 0, - ...coordinates, - ...props.style, - }; - - return ( - layersWrapper.current && - createPortal( - <styles.Container - ref={layerRef} - className={props.className} - style={style} - onFocus={props.onFocus} - onBlur={props.onBlur} - onMouseEnter={props.onMouseEnter} - onMouseLeave={props.onMouseLeave} - > - {props.children} - </styles.Container>, - layersWrapper.current - ) - ); -}); +import { observer } from "mobx-react-lite"; +import React from "react"; +import { createPortal } from "react-dom"; +import { useLayer } from "src/hooks/useLayer"; +import { styles } from "./Layer.styles"; + +export type LayerPosition = + | { + top: number; + left: number; + } + | { + top: number; + right: number; + } + | { + bottom: number; + left: number; + } + | { + bottom: number; + right: number; + }; + +export type LayerProps = { + calculatePosition: (layersWrapper: HTMLDivElement, layer: HTMLDivElement) => LayerPosition; + updatePosition?: React.MutableRefObject<(() => boolean) | null>; + + onFocus?: React.FocusEventHandler<HTMLDivElement>; + onBlur?: React.FocusEventHandler<HTMLDivElement>; + onMouseEnter?: React.MouseEventHandler<HTMLDivElement>; + onMouseLeave?: React.MouseEventHandler<HTMLDivElement>; + + className?: string; + style?: React.CSSProperties; +}; + +export const Layer: React.FunctionComponent<LayerProps> = observer(props => { + const [shouldRender, setShouldRender] = React.useState(false); + const [coordinates, setCoordinates] = React.useState<LayerPosition>({ left: 0, top: 0 }); + + const layerRef = React.useRef<HTMLDivElement>(null); + const { containerRef: layersWrapper } = useLayer(); + + const updatePosition = (): boolean => { + if (!layersWrapper.current || !layerRef.current) return false; + + const position = props.calculatePosition(layersWrapper.current, layerRef.current); + + setCoordinates(position); + setShouldRender(true); + return true; + }; + + React.useEffect(() => { + if (props.updatePosition) props.updatePosition.current = updatePosition; + + if (shouldRender) return; + + updatePosition(); + }); + + React.useEffect(() => { + const handleFullscreenChange = () => { + setTimeout(() => updatePosition(), 100); + }; + + document.addEventListener("fullscreenchange", handleFullscreenChange); + + return () => document.removeEventListener("fullscreenchange", handleFullscreenChange); + }, []); + + const style: React.CSSProperties = { + opacity: shouldRender ? 1 : 0, + ...coordinates, + ...props.style, + }; + + return ( + layersWrapper.current && + createPortal( + <styles.Container + ref={layerRef} + className={props.className} + style={style} + onFocus={props.onFocus} + onBlur={props.onBlur} + onMouseEnter={props.onMouseEnter} + onMouseLeave={props.onMouseLeave} + > + {props.children} + </styles.Container>, + layersWrapper.current + ) + ); +}); diff --git a/src/components/loading/Loading.styles.ts b/src/components/loading/Loading.styles.ts index d4b7bb077f9094332aa7ae9b9eb355b124e152ce..46cc659badcdb966d5704982e354b217a76467e7 100644 --- a/src/components/loading/Loading.styles.ts +++ b/src/components/loading/Loading.styles.ts @@ -1,43 +1,43 @@ -import { transparentize } from "polished"; -import styled, { css, keyframes } from "styled-components"; - -const animation = keyframes` - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } `; - -export const styles = { - Wrapper: styled.div` - position: relative; - margin-bottom: auto; - height: 50px; - width: 50px; - margin: 0 auto auto; - `, - - Container: styled.div` - position: relative; - flex: 1; - margin: 6px; - `, - - Loading: styled.div` - ${p => css` - &, - &:after { - border-radius: 50%; - width: 100%; - height: 100%; - } - - position: relative; - margin: 0.1px; - border: 6px solid ${transparentize(0.8, p.theme.accentColor)}; - border-left: 6px solid ${p.theme.accentColor}; - animation: ${animation} 1.1s infinite linear; - `} - `, -}; +import { transparentize } from "polished"; +import styled, { css, keyframes } from "styled-components"; + +const animation = keyframes` + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } `; + +export const styles = { + Wrapper: styled.div` + position: relative; + margin-bottom: auto; + height: 50px; + width: 50px; + margin: 0 auto auto; + `, + + Container: styled.div` + position: relative; + flex: 1; + margin: 6px; + `, + + Loading: styled.div` + ${p => css` + &, + &:after { + border-radius: 50%; + width: 100%; + height: 100%; + } + + position: relative; + margin: 0.1px; + border: 6px solid ${transparentize(0.8, p.theme.accentColor)}; + border-left: 6px solid ${p.theme.accentColor}; + animation: ${animation} 1.1s infinite linear; + `} + `, +}; diff --git a/src/components/loading/Loading.tsx b/src/components/loading/Loading.tsx index 4180377b700bb049b971848d3d17e2a8e13611fe..c282e93bde8213871fc395dfee2defa77b780bc8 100644 --- a/src/components/loading/Loading.tsx +++ b/src/components/loading/Loading.tsx @@ -1,16 +1,16 @@ -import React from "react"; -import { styles } from "./Loading.styles"; - -export type LoadingProps = { - className?: string; -}; - -export const Loading: React.FunctionComponent<LoadingProps> = ({ className }) => { - return ( - <styles.Wrapper className={className}> - <styles.Container> - <styles.Loading /> - </styles.Container> - </styles.Wrapper> - ); -}; +import React from "react"; +import { styles } from "./Loading.styles"; + +export type LoadingProps = { + className?: string; +}; + +export const Loading: React.FunctionComponent<LoadingProps> = ({ className }) => { + return ( + <styles.Wrapper className={className}> + <styles.Container> + <styles.Loading /> + </styles.Container> + </styles.Wrapper> + ); +}; diff --git a/src/components/modal/Modal.tsx b/src/components/modal/Modal.tsx index 646cb9f885363f9aab91cf89a88e5bf570525eb3..74846fb6b6a5feb8ff18e9fc5c6dca5f27425c5c 100644 --- a/src/components/modal/Modal.tsx +++ b/src/components/modal/Modal.tsx @@ -1,150 +1,150 @@ -import { DefaultTheme, FlattenInterpolation, ThemedStyledProps } from "styled-components"; -import { useContext, useEffect, useRef } from "react"; - -import { AppStoreContext } from "../AppStoreContext"; -import { OverlayContainerContext } from "src/context/OverlayContainer"; -import React from "react"; -import ReactDOM from "react-dom"; -import { observer } from "mobx-react-lite"; -import { styles } from "./Modal.styles"; -import { useFocus } from "src/hooks"; -import { useHistory } from "src/hooks/useHistory"; - -export type ModalProps = { - className?: string; - isVisible: boolean; - autoFocus?: boolean; - onDismiss: () => void; - onCloseAnimationFinish: () => void; - contentContainerCSS?: FlattenInterpolation<ThemedStyledProps<{}, DefaultTheme>>; -}; - -export const Modal: React.FunctionComponent<ModalProps> = observer<ModalProps>(props => { - const overlayContainerContext = useContext(OverlayContainerContext); - const appStore = useContext(AppStoreContext); - const wrapperRef = useRef<HTMLDivElement>(null); - const contentContainerRef = useRef<HTMLDivElement>(null); - - const propsRef = useRef(props); - propsRef.current = props; - - const { history } = useHistory(); - - useEffect(() => { - if (props.isVisible) { - history.push(history.location); - - return history.listen(() => { - if (props.onDismiss) props.onDismiss(); - }); - } - - return; - }, [props.isVisible]); - - useEffect(() => { - if (props.isVisible) - return () => { - if (!propsRef.current.isVisible) propsRef.current.onCloseAnimationFinish(); - }; - - return undefined; - }, [props.isVisible, props.onCloseAnimationFinish]); - - const focusFirstElement = React.useCallback(() => { - const focusableElementsSelector = [ - "a[href]", - "button:not([disabled])", - "area[href]", - "input:not([disabled])", - "select:not([disabled])", - "textarea:not([disabled])", - "iframe", - "object", - "embed", - "*[tabindex]", - "*[contenteditable]", - ].join(); - - if (!contentContainerRef.current) return; - - const firstFocusableElement = contentContainerRef.current.querySelector(focusableElementsSelector); - - if (firstFocusableElement && typeof firstFocusableElement["focus"] === "function") { - firstFocusableElement["focus"](); - return true; - } - - return false; - }, []); - - useEffect(() => { - const autoFocus = props.autoFocus !== false; - - if (props.isVisible && autoFocus) focusFirstElement(); - }, [props.isVisible]); - - let lastIsFocused = React.useRef(appStore.isFocused); - useEffect(() => { - if (!props.isVisible) return; - - if (!appStore.isFocused || lastIsFocused.current === appStore.isFocused) { - lastIsFocused.current = appStore.isFocused; - return; - } - - lastIsFocused.current = appStore.isFocused; - focusFirstElement(); - }, [props.isVisible, appStore.isFocused]); - - const { handleFocus, handleBlur } = useFocus( - { - onBlur: async () => { - await new Promise(r => setTimeout(r(), 15)); - - if (document.activeElement === wrapperRef.current) props.onDismiss(); - else if (!focusFirstElement() && contentContainerRef.current) contentContainerRef.current.focus(); - }, - onFocus: () => {}, - }, - [props.onDismiss, props.isVisible] - ); - - const handleKeyPress = React.useCallback<React.KeyboardEventHandler>(e => { - if (e.isDefaultPrevented()) return; - - if (e.which === 27) props.onDismiss(); - }, []); - - return ( - overlayContainerContext.current && - ReactDOM.createPortal( - props.isVisible && ( - <> - <styles.HiddenButton aria-hidden /> - - <styles.Wrapper - ref={wrapperRef} - tabIndex={-1} - isVisible={props.isVisible} - className={props.className} - onKeyDown={handleKeyPress} - > - <styles.ContentContainer - ref={contentContainerRef} - tabIndex={-1} - css={props.contentContainerCSS} - onFocus={handleFocus} - onBlur={handleBlur} - > - {props.children} - </styles.ContentContainer> - </styles.Wrapper> - - <styles.HiddenButton aria-hidden /> - </> - ), - overlayContainerContext.current - ) - ); -}); +import { DefaultTheme, FlattenInterpolation, ThemedStyledProps } from "styled-components"; +import { useContext, useEffect, useRef } from "react"; + +import { AppStoreContext } from "../AppStoreContext"; +import { OverlayContainerContext } from "src/context/OverlayContainer"; +import React from "react"; +import ReactDOM from "react-dom"; +import { observer } from "mobx-react-lite"; +import { styles } from "./Modal.styles"; +import { useFocus } from "src/hooks"; +import { useHistory } from "src/hooks/useHistory"; + +export type ModalProps = { + className?: string; + isVisible: boolean; + autoFocus?: boolean; + onDismiss: () => void; + onCloseAnimationFinish: () => void; + contentContainerCSS?: FlattenInterpolation<ThemedStyledProps<{}, DefaultTheme>>; +}; + +export const Modal: React.FunctionComponent<ModalProps> = observer<ModalProps>(props => { + const overlayContainerContext = useContext(OverlayContainerContext); + const appStore = useContext(AppStoreContext); + const wrapperRef = useRef<HTMLDivElement>(null); + const contentContainerRef = useRef<HTMLDivElement>(null); + + const propsRef = useRef(props); + propsRef.current = props; + + const { history } = useHistory(); + + useEffect(() => { + if (props.isVisible) { + history.push(history.location); + + return history.listen(() => { + if (props.onDismiss) props.onDismiss(); + }); + } + + return; + }, [props.isVisible]); + + useEffect(() => { + if (props.isVisible) + return () => { + if (!propsRef.current.isVisible) propsRef.current.onCloseAnimationFinish(); + }; + + return undefined; + }, [props.isVisible, props.onCloseAnimationFinish]); + + const focusFirstElement = React.useCallback(() => { + const focusableElementsSelector = [ + "a[href]", + "button:not([disabled])", + "area[href]", + "input:not([disabled])", + "select:not([disabled])", + "textarea:not([disabled])", + "iframe", + "object", + "embed", + "*[tabindex]", + "*[contenteditable]", + ].join(); + + if (!contentContainerRef.current) return; + + const firstFocusableElement = contentContainerRef.current.querySelector(focusableElementsSelector); + + if (firstFocusableElement && typeof firstFocusableElement["focus"] === "function") { + firstFocusableElement["focus"](); + return true; + } + + return false; + }, []); + + useEffect(() => { + const autoFocus = props.autoFocus !== false; + + if (props.isVisible && autoFocus) focusFirstElement(); + }, [props.isVisible]); + + let lastIsFocused = React.useRef(appStore.isFocused); + useEffect(() => { + if (!props.isVisible) return; + + if (!appStore.isFocused || lastIsFocused.current === appStore.isFocused) { + lastIsFocused.current = appStore.isFocused; + return; + } + + lastIsFocused.current = appStore.isFocused; + focusFirstElement(); + }, [props.isVisible, appStore.isFocused]); + + const { handleFocus, handleBlur } = useFocus( + { + onBlur: async () => { + await new Promise(r => setTimeout(r(), 15)); + + if (document.activeElement === wrapperRef.current) props.onDismiss(); + else if (!focusFirstElement() && contentContainerRef.current) contentContainerRef.current.focus(); + }, + onFocus: () => {}, + }, + [props.onDismiss, props.isVisible] + ); + + const handleKeyPress = React.useCallback<React.KeyboardEventHandler>(e => { + if (e.isDefaultPrevented()) return; + + if (e.which === 27) props.onDismiss(); + }, []); + + return ( + overlayContainerContext.current && + ReactDOM.createPortal( + props.isVisible && ( + <> + <styles.HiddenButton aria-hidden /> + + <styles.Wrapper + ref={wrapperRef} + tabIndex={-1} + isVisible={props.isVisible} + className={props.className} + onKeyDown={handleKeyPress} + > + <styles.ContentContainer + ref={contentContainerRef} + tabIndex={-1} + css={props.contentContainerCSS} + onFocus={handleFocus} + onBlur={handleBlur} + > + {props.children} + </styles.ContentContainer> + </styles.Wrapper> + + <styles.HiddenButton aria-hidden /> + </> + ), + overlayContainerContext.current + ) + ); +}); diff --git a/src/components/textinput/TextInput.styles.ts b/src/components/textinput/TextInput.styles.ts index eb26d3fc93976b422f984d76aed09f94a8ca8662..e72597ef7b83cf4525bd800124694597e800d515 100644 --- a/src/components/textinput/TextInput.styles.ts +++ b/src/components/textinput/TextInput.styles.ts @@ -1,15 +1,15 @@ -import styled from "styled-components"; - -export const styles = { - Container: styled.div` - flex-direction: row; - height: ${p => p.theme.rowHeight}px; - flex: 0 0 auto; - `, - - Input: styled.input` - flex: 1 1 100%; - margin: 7px 0px; - padding: 0 5px; - `, -}; +import styled from "styled-components"; + +export const styles = { + Container: styled.div` + flex-direction: row; + height: ${p => p.theme.rowHeight}px; + flex: 0 0 auto; + `, + + Input: styled.input` + flex: 1 1 100%; + margin: 7px 0px; + padding: 0 5px; + `, +}; diff --git a/src/components/textinput/TextInput.tsx b/src/components/textinput/TextInput.tsx index adba2173bd2779df4756bdceec2e3edb9e3b9ac3..4b5c13d1865a64ba1004fdd7dbe89038b30655f4 100644 --- a/src/components/textinput/TextInput.tsx +++ b/src/components/textinput/TextInput.tsx @@ -1,44 +1,44 @@ -import React from "react"; - -import { styles } from "./TextInput.styles"; - -export type TextInputProps = React.InputHTMLAttributes<HTMLInputElement> & { - onChangeText?: (value: string) => void; - focusRef?: React.MutableRefObject<(() => void) | null>; - right?: React.ReactNode; -}; - -export const TextInput = (props: TextInputProps) => { - const { className, focusRef, right, onChange, onChangeText, ...rest } = props; - - const inputRef = React.useRef<HTMLInputElement>(null); - const focusInput = React.useCallback(() => { - if (inputRef.current) inputRef.current.focus(); - }, []); - - React.useEffect(() => { - if (!focusRef) return; - - focusRef.current = focusInput; - - return () => { - focusRef.current = null; - }; - }, [focusRef]); - - const handleOnChange = React.useCallback<React.ChangeEventHandler<HTMLInputElement>>( - e => { - if (onChangeText) onChangeText(e.target.value); - if (onChange) onChange(e); - }, - [onChangeText, onChange] - ); - - return ( - <styles.Container className={className} onClick={focusInput}> - <styles.Input ref={inputRef} {...rest} onChange={handleOnChange} /> - - {right} - </styles.Container> - ); -}; +import React from "react"; + +import { styles } from "./TextInput.styles"; + +export type TextInputProps = React.InputHTMLAttributes<HTMLInputElement> & { + onChangeText?: (value: string) => void; + focusRef?: React.MutableRefObject<(() => void) | null>; + right?: React.ReactNode; +}; + +export const TextInput = (props: TextInputProps) => { + const { className, focusRef, right, onChange, onChangeText, ...rest } = props; + + const inputRef = React.useRef<HTMLInputElement>(null); + const focusInput = React.useCallback(() => { + if (inputRef.current) inputRef.current.focus(); + }, []); + + React.useEffect(() => { + if (!focusRef) return; + + focusRef.current = focusInput; + + return () => { + focusRef.current = null; + }; + }, [focusRef]); + + const handleOnChange = React.useCallback<React.ChangeEventHandler<HTMLInputElement>>( + e => { + if (onChangeText) onChangeText(e.target.value); + if (onChange) onChange(e); + }, + [onChangeText, onChange] + ); + + return ( + <styles.Container className={className} onClick={focusInput}> + <styles.Input ref={inputRef} {...rest} onChange={handleOnChange} /> + + {right} + </styles.Container> + ); +}; diff --git a/src/components/textinput/__stories__/TextInput.stories.tsx b/src/components/textinput/__stories__/TextInput.stories.tsx index 5b838503e70aad50f19c7e0768742c827a67ce60..95f282924c2018b38d3f76b77e9d8d204b25472b 100644 --- a/src/components/textinput/__stories__/TextInput.stories.tsx +++ b/src/components/textinput/__stories__/TextInput.stories.tsx @@ -1,23 +1,23 @@ -import { Icon } from "../../icon/Icon"; -import React from "react"; -import { TextInput } from "../TextInput"; -import { storiesOf } from "@storybook/react"; -import styled from "styled-components"; - -const Container = styled.div` - width: 300px; - max-width: 80%; - margin: auto; -`; - -const InputRight = styled(Icon)` - width: 50px; -`; - -const Default = () => ( - <Container> - <TextInput right={<InputRight name="play" />} /> - </Container> -); - -storiesOf("TextInput", module).add("Default", () => <Default />); +import { Icon } from "../../icon/Icon"; +import React from "react"; +import { TextInput } from "../TextInput"; +import { storiesOf } from "@storybook/react"; +import styled from "styled-components"; + +const Container = styled.div` + width: 300px; + max-width: 80%; + margin: auto; +`; + +const InputRight = styled(Icon)` + width: 50px; +`; + +const Default = () => ( + <Container> + <TextInput right={<InputRight name="play" />} /> + </Container> +); + +storiesOf("TextInput", module).add("Default", () => <Default />); diff --git a/src/hooks/index.ts b/src/hooks/index.ts index b1b597fd59cfa351a06808e8e4d9f3b26e907453..842afc7bf8d451cbcdca21d468816e1010877e05 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1 +1 @@ -export { useFocus, UseFocusOutput, UseFocusProps } from "./useFocus"; +export { useFocus, UseFocusOutput, UseFocusProps } from "./useFocus"; diff --git a/src/hooks/useLayer.tsx b/src/hooks/useLayer.tsx index cd994cdda21aa28101f1cd630aa3000f4152a1d6..7d990d3fd3f68f5678687df0259e98587625c951 100644 --- a/src/hooks/useLayer.tsx +++ b/src/hooks/useLayer.tsx @@ -1,20 +1,20 @@ -import React from "react"; - -// Context -export type LayerContext = { - containerRef: React.RefObject<HTMLDivElement>; -}; - -const context = React.createContext<LayerContext>(undefined as any); - -// Provider -export type LayerProviderProps = LayerContext; - -export const LayerProvider: React.FunctionComponent<LayerProviderProps> = props => ( - <context.Provider value={props}>{props.children}</context.Provider> -); - -// Hook -export type UseLayerOutput = LayerContext; - -export const useLayer: () => UseLayerOutput = () => React.useContext(context); +import React from "react"; + +// Context +export type LayerContext = { + containerRef: React.RefObject<HTMLDivElement>; +}; + +const context = React.createContext<LayerContext>(undefined as any); + +// Provider +export type LayerProviderProps = LayerContext; + +export const LayerProvider: React.FunctionComponent<LayerProviderProps> = props => ( + <context.Provider value={props}>{props.children}</context.Provider> +); + +// Hook +export type UseLayerOutput = LayerContext; + +export const useLayer: () => UseLayerOutput = () => React.useContext(context); diff --git a/src/hooks/useLayerProps copy.tsx b/src/hooks/useLayerProps copy.tsx index 03ac48c62d2bc72f08cc56748f688c4bba2d9881..251cf070ed5f9eab5277dac9b8180d0d1b158d68 100644 --- a/src/hooks/useLayerProps copy.tsx +++ b/src/hooks/useLayerProps copy.tsx @@ -1,93 +1,93 @@ -import React, { DependencyList } from "react"; - -export type DragCoordinates = { - clientX: number; - clientY: number; -}; - -export type UseDragOptions = { - onChange?: (coordinates?: DragCoordinates) => void; -}; - -export type UseDragOutput = { - onMouseDown: React.MouseEventHandler; - onTouchStart: React.TouchEventHandler; - cancel: () => void; - coordinates?: DragCoordinates; -}; - -export const useDrag = (options: UseDragOptions = {}, deps: DependencyList): UseDragOutput => { - const [coordinates, setCoordinates] = React.useState<DragCoordinates | undefined>(); - const [trackingPointer, setTrackingPointer] = React.useState<boolean>(false); - const trackingPointerRef = React.useRef(trackingPointer); - trackingPointerRef.current = trackingPointer; - - const updateCoordinates = React.useCallback((coordinates: DragCoordinates | undefined) => { - const { onChange } = options; - - if (onChange) onChange(coordinates); - setCoordinates(coordinates); - }, deps); - - const onMouseDown = React.useCallback<React.MouseEventHandler>( - e => { - const { clientX, clientY } = e; - - setTrackingPointer(true); - updateCoordinates({ clientX, clientY }); - }, - [updateCoordinates] - ); - - const onTouchStart = React.useCallback<React.TouchEventHandler>( - e => { - const { clientX, clientY } = e.touches[0]; - - setTrackingPointer(true); - updateCoordinates({ clientX, clientY }); - }, - [updateCoordinates] - ); - - React.useEffect(() => { - if (!trackingPointer) return; - - const handleMouseMove = (e: MouseEvent) => { - const { clientX, clientY } = e; - updateCoordinates({ clientX, clientY }); - }; - - const handleTouchMove = (e: TouchEvent) => { - const { clientX, clientY } = e.touches[0]; - updateCoordinates({ clientX, clientY }); - }; - - const stopTracking = () => { - setTrackingPointer(false); - setCoordinates(undefined); - }; - - window.addEventListener("mousemove", handleMouseMove); - window.addEventListener("touchmove", handleTouchMove); - window.addEventListener("mouseup", stopTracking); - window.addEventListener("touchend", stopTracking); - window.addEventListener("touchcancel", stopTracking); - - return () => { - window.removeEventListener("mouseup", stopTracking); - window.removeEventListener("mousemove", handleMouseMove); - window.removeEventListener("touchmove", handleTouchMove); - window.removeEventListener("touchend", stopTracking); - window.removeEventListener("touchcancel", stopTracking); - }; - }, [trackingPointer, updateCoordinates]); - - const cancel = React.useCallback(() => setTrackingPointer(false), []); - - return { - onMouseDown, - onTouchStart, - cancel, - coordinates, - }; -}; +import React, { DependencyList } from "react"; + +export type DragCoordinates = { + clientX: number; + clientY: number; +}; + +export type UseDragOptions = { + onChange?: (coordinates?: DragCoordinates) => void; +}; + +export type UseDragOutput = { + onMouseDown: React.MouseEventHandler; + onTouchStart: React.TouchEventHandler; + cancel: () => void; + coordinates?: DragCoordinates; +}; + +export const useDrag = (options: UseDragOptions = {}, deps: DependencyList): UseDragOutput => { + const [coordinates, setCoordinates] = React.useState<DragCoordinates | undefined>(); + const [trackingPointer, setTrackingPointer] = React.useState<boolean>(false); + const trackingPointerRef = React.useRef(trackingPointer); + trackingPointerRef.current = trackingPointer; + + const updateCoordinates = React.useCallback((coordinates: DragCoordinates | undefined) => { + const { onChange } = options; + + if (onChange) onChange(coordinates); + setCoordinates(coordinates); + }, deps); + + const onMouseDown = React.useCallback<React.MouseEventHandler>( + e => { + const { clientX, clientY } = e; + + setTrackingPointer(true); + updateCoordinates({ clientX, clientY }); + }, + [updateCoordinates] + ); + + const onTouchStart = React.useCallback<React.TouchEventHandler>( + e => { + const { clientX, clientY } = e.touches[0]; + + setTrackingPointer(true); + updateCoordinates({ clientX, clientY }); + }, + [updateCoordinates] + ); + + React.useEffect(() => { + if (!trackingPointer) return; + + const handleMouseMove = (e: MouseEvent) => { + const { clientX, clientY } = e; + updateCoordinates({ clientX, clientY }); + }; + + const handleTouchMove = (e: TouchEvent) => { + const { clientX, clientY } = e.touches[0]; + updateCoordinates({ clientX, clientY }); + }; + + const stopTracking = () => { + setTrackingPointer(false); + setCoordinates(undefined); + }; + + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("touchmove", handleTouchMove); + window.addEventListener("mouseup", stopTracking); + window.addEventListener("touchend", stopTracking); + window.addEventListener("touchcancel", stopTracking); + + return () => { + window.removeEventListener("mouseup", stopTracking); + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("touchmove", handleTouchMove); + window.removeEventListener("touchend", stopTracking); + window.removeEventListener("touchcancel", stopTracking); + }; + }, [trackingPointer, updateCoordinates]); + + const cancel = React.useCallback(() => setTrackingPointer(false), []); + + return { + onMouseDown, + onTouchStart, + cancel, + coordinates, + }; +}; diff --git a/src/hooks/useLayerProps.tsx b/src/hooks/useLayerProps.tsx index 8ba5843362635855ff4daa1ee67c4237c0538dff..daef45b51cd6790610d6b1c3094464d44ad7115b 100644 --- a/src/hooks/useLayerProps.tsx +++ b/src/hooks/useLayerProps.tsx @@ -1,131 +1,131 @@ -import React, { DependencyList, HTMLAttributes, useContext } from "react"; -import { AppStoreContext } from "src/components/AppStoreContext"; -import { LayerPosition, LayerProps } from "src/components/layer/Layer"; -import { useFocus } from "./useFocus"; - -export type UseLayerPropsOptions<T extends HTMLElement> = { - position: "top"; - separation?: number; - - behaviour: "hover" | "press"; - triggerRef: React.RefObject<T | undefined | null>; - updatePosition?: LayerProps["updatePosition"]; -}; - -export type UseLayerPropsOutput<TTrigger extends HTMLElement> = [boolean, LayerProps, HTMLAttributes<TTrigger>]; - -export const useLayerProps = <TTrigger extends HTMLElement>( - options: UseLayerPropsOptions<TTrigger>, - deps: DependencyList -): UseLayerPropsOutput<TTrigger> => { - const { inputType } = useContext(AppStoreContext); - - const [shouldRender, setShouldRender] = React.useState(false); - - const isTriggerFocusedRef = React.useRef(false); - const isTriggerHoveredRef = React.useRef(false); - const isLayerFocusedRef = React.useRef(false); - const isLayerHoveredRef = React.useRef(false); - - const updateShouldRenderTimeoutRef = React.useRef<number>(); - const updateShouldRender = React.useCallback(() => { - clearTimeout(updateShouldRenderTimeoutRef.current); - const nextShouldRender = - options.behaviour === "hover" && inputType === "pointer" - ? isTriggerHoveredRef.current || isLayerHoveredRef.current || isLayerFocusedRef.current - : isTriggerFocusedRef.current || isLayerFocusedRef.current; - - if (shouldRender === nextShouldRender) return; - - if (nextShouldRender) setShouldRender(true); - else updateShouldRenderTimeoutRef.current = setTimeout(() => setShouldRender(false), 300); - }, [shouldRender, inputType]); - - // Trigger - const handleTriggerFocusChange = (isFocused: boolean) => { - isTriggerFocusedRef.current = isFocused; - updateShouldRender(); - }; - const triggerFocusHandler = useFocus( - { - onFocus: () => handleTriggerFocusChange(true), - onBlur: () => handleTriggerFocusChange(false), - }, - [updateShouldRender] - ); - const triggerProps = React.useMemo<HTMLAttributes<TTrigger>>(() => { - const handleTriggerHoverChange = async (isHovered: boolean) => { - isTriggerHoveredRef.current = isHovered; - updateShouldRender(); - }; - - return { - onMouseEnter: () => handleTriggerHoverChange(true), - onMouseLeave: () => handleTriggerHoverChange(false), - onFocus: triggerFocusHandler.handleFocus, - onBlur: triggerFocusHandler.handleBlur, - }; - }, [updateShouldRender]); - - // Layer - const handleLayerFocusChange = (isFocused: boolean) => { - isLayerFocusedRef.current = isFocused; - updateShouldRender(); - }; - const handleLayerHoverChange = (isFocused: boolean) => { - isLayerHoveredRef.current = isFocused; - updateShouldRender(); - }; - const layerFocusHandler = useFocus( - { - onFocus: () => handleLayerFocusChange(true), - onBlur: () => handleLayerFocusChange(false), - }, - [updateShouldRender] - ); - const layerProps = React.useMemo<LayerProps>(() => { - return { - updatePosition: options.updatePosition, - calculatePosition: (layersWrapper, layer) => { - const { triggerRef } = options; - - let result: LayerPosition = { - top: 0, - left: 0, - }; - - if (triggerRef.current) { - const triggerBounds = triggerRef.current.getBoundingClientRect(); - const layersWrapperBounds = layersWrapper.getBoundingClientRect(); - const layerBounds = layer.getBoundingClientRect(); - - if (options.position === "top") { - const top = Math.max(0, triggerBounds.top - layerBounds.height - (options.separation || 0)); - - const triggerXCenterLeft = triggerBounds.left + triggerBounds.width / 2; - const triggerXCenterRight = layersWrapperBounds.width - triggerXCenterLeft; - - if (triggerXCenterLeft > layersWrapperBounds.width / 2) - result = { - top, - right: Math.max(0, triggerXCenterRight - layerBounds.width / 2), - }; - else - result = { - top, - left: Math.max(0, triggerXCenterLeft - layerBounds.width / 2), - }; - } - } - - return result; - }, - onFocus: layerFocusHandler.handleFocus, - onBlur: layerFocusHandler.handleBlur, - onMouseEnter: () => handleLayerHoverChange(true), - onMouseLeave: () => handleLayerHoverChange(false), - }; - }, [options.updatePosition, shouldRender, ...deps]); - - return [shouldRender, layerProps, triggerProps]; -}; +import React, { DependencyList, HTMLAttributes, useContext } from "react"; +import { AppStoreContext } from "src/components/AppStoreContext"; +import { LayerPosition, LayerProps } from "src/components/layer/Layer"; +import { useFocus } from "./useFocus"; + +export type UseLayerPropsOptions<T extends HTMLElement> = { + position: "top"; + separation?: number; + + behaviour: "hover" | "press"; + triggerRef: React.RefObject<T | undefined | null>; + updatePosition?: LayerProps["updatePosition"]; +}; + +export type UseLayerPropsOutput<TTrigger extends HTMLElement> = [boolean, LayerProps, HTMLAttributes<TTrigger>]; + +export const useLayerProps = <TTrigger extends HTMLElement>( + options: UseLayerPropsOptions<TTrigger>, + deps: DependencyList +): UseLayerPropsOutput<TTrigger> => { + const { inputType } = useContext(AppStoreContext); + + const [shouldRender, setShouldRender] = React.useState(false); + + const isTriggerFocusedRef = React.useRef(false); + const isTriggerHoveredRef = React.useRef(false); + const isLayerFocusedRef = React.useRef(false); + const isLayerHoveredRef = React.useRef(false); + + const updateShouldRenderTimeoutRef = React.useRef<number>(); + const updateShouldRender = React.useCallback(() => { + clearTimeout(updateShouldRenderTimeoutRef.current); + const nextShouldRender = + options.behaviour === "hover" && inputType === "pointer" + ? isTriggerHoveredRef.current || isLayerHoveredRef.current || isLayerFocusedRef.current + : isTriggerFocusedRef.current || isLayerFocusedRef.current; + + if (shouldRender === nextShouldRender) return; + + if (nextShouldRender) setShouldRender(true); + else updateShouldRenderTimeoutRef.current = setTimeout(() => setShouldRender(false), 300); + }, [shouldRender, inputType]); + + // Trigger + const handleTriggerFocusChange = (isFocused: boolean) => { + isTriggerFocusedRef.current = isFocused; + updateShouldRender(); + }; + const triggerFocusHandler = useFocus( + { + onFocus: () => handleTriggerFocusChange(true), + onBlur: () => handleTriggerFocusChange(false), + }, + [updateShouldRender] + ); + const triggerProps = React.useMemo<HTMLAttributes<TTrigger>>(() => { + const handleTriggerHoverChange = async (isHovered: boolean) => { + isTriggerHoveredRef.current = isHovered; + updateShouldRender(); + }; + + return { + onMouseEnter: () => handleTriggerHoverChange(true), + onMouseLeave: () => handleTriggerHoverChange(false), + onFocus: triggerFocusHandler.handleFocus, + onBlur: triggerFocusHandler.handleBlur, + }; + }, [updateShouldRender]); + + // Layer + const handleLayerFocusChange = (isFocused: boolean) => { + isLayerFocusedRef.current = isFocused; + updateShouldRender(); + }; + const handleLayerHoverChange = (isFocused: boolean) => { + isLayerHoveredRef.current = isFocused; + updateShouldRender(); + }; + const layerFocusHandler = useFocus( + { + onFocus: () => handleLayerFocusChange(true), + onBlur: () => handleLayerFocusChange(false), + }, + [updateShouldRender] + ); + const layerProps = React.useMemo<LayerProps>(() => { + return { + updatePosition: options.updatePosition, + calculatePosition: (layersWrapper, layer) => { + const { triggerRef } = options; + + let result: LayerPosition = { + top: 0, + left: 0, + }; + + if (triggerRef.current) { + const triggerBounds = triggerRef.current.getBoundingClientRect(); + const layersWrapperBounds = layersWrapper.getBoundingClientRect(); + const layerBounds = layer.getBoundingClientRect(); + + if (options.position === "top") { + const top = Math.max(0, triggerBounds.top - layerBounds.height - (options.separation || 0)); + + const triggerXCenterLeft = triggerBounds.left + triggerBounds.width / 2; + const triggerXCenterRight = layersWrapperBounds.width - triggerXCenterLeft; + + if (triggerXCenterLeft > layersWrapperBounds.width / 2) + result = { + top, + right: Math.max(0, triggerXCenterRight - layerBounds.width / 2), + }; + else + result = { + top, + left: Math.max(0, triggerXCenterLeft - layerBounds.width / 2), + }; + } + } + + return result; + }, + onFocus: layerFocusHandler.handleFocus, + onBlur: layerFocusHandler.handleBlur, + onMouseEnter: () => handleLayerHoverChange(true), + onMouseLeave: () => handleLayerHoverChange(false), + }; + }, [options.updatePosition, shouldRender, ...deps]); + + return [shouldRender, layerProps, triggerProps]; +}; diff --git a/src/hooks/useOverlayProps.ts b/src/hooks/useOverlayProps.ts index 91c1741ba4710504de9ec8a75cecc7345254df56..ac6141f97a078420925d63285e2ef341794b4c71 100644 --- a/src/hooks/useOverlayProps.ts +++ b/src/hooks/useOverlayProps.ts @@ -1,83 +1,83 @@ -import { useEffect, useRef, useState } from "react"; - -import React from "react"; - -export const useOverlayProps = ( - referenceElementRef: React.RefObject<HTMLElement>, - overlayContainerRef?: React.RefObject<HTMLDivElement> -): UseOverlayPropsOutput => { - const [state, setState] = useState({ top: 0, left: 0, isVisible: false }); - let auxOverlayContainerRef = useRef<HTMLDivElement>(null); - - if (overlayContainerRef) auxOverlayContainerRef = overlayContainerRef; - const { current: overlayContainer } = auxOverlayContainerRef; - const { current: referenceElement } = referenceElementRef; - - useEffect(() => { - setTimeout(() => { - if (auxOverlayContainerRef.current !== overlayContainer || referenceElementRef.current !== referenceElement) - setState(prevState => ({ ...prevState })); - }); - }); // TODO: check - - useEffect(() => { - const resizeHandler = async () => { - await new Promise(resolve => setTimeout(resolve, 100)); - - const { current: overlayContainer } = auxOverlayContainerRef; - const { current: referenceElement } = referenceElementRef; - - if (!overlayContainer || !referenceElement) return; - - const { innerHeight, innerWidth } = window; - - const referenceElementBounds = referenceElement.getBoundingClientRect(); - const overlayContainerBounds = overlayContainer.getBoundingClientRect(); - - const bottomDistance = innerHeight - referenceElementBounds.top - referenceElementBounds.height; - - const top = - bottomDistance > overlayContainerBounds.height - ? innerHeight - bottomDistance - : referenceElementBounds.top - overlayContainerBounds.height; - - const left = - innerWidth - referenceElementBounds.left > overlayContainerBounds.width - ? referenceElementBounds.left - : innerWidth - overlayContainerBounds.width - 10; - - setState({ - top, - left, - isVisible: true, - }); - }; - - resizeHandler(); - - window.addEventListener("resize", resizeHandler); - document.addEventListener("fullscreenchange", resizeHandler); - - return () => { - window.removeEventListener("resize", resizeHandler); - document.removeEventListener("fullscreenchange", resizeHandler); - setState(prevState => ({ ...prevState, isVisible: false })); - }; - }, [auxOverlayContainerRef, auxOverlayContainerRef.current, referenceElementRef, referenceElementRef.current]); - - return { - overlayProps: { - ...state, - overlayContainerRef: auxOverlayContainerRef, - }, - }; -}; - -export type UseOverlayPropsOutput = { - overlayProps: { - top: number; - left: number; - isVisible: boolean; - overlayContainerRef: React.RefObject<HTMLDivElement>; - }; -}; +import { useEffect, useRef, useState } from "react"; + +import React from "react"; + +export const useOverlayProps = ( + referenceElementRef: React.RefObject<HTMLElement>, + overlayContainerRef?: React.RefObject<HTMLDivElement> +): UseOverlayPropsOutput => { + const [state, setState] = useState({ top: 0, left: 0, isVisible: false }); + let auxOverlayContainerRef = useRef<HTMLDivElement>(null); + + if (overlayContainerRef) auxOverlayContainerRef = overlayContainerRef; + const { current: overlayContainer } = auxOverlayContainerRef; + const { current: referenceElement } = referenceElementRef; + + useEffect(() => { + setTimeout(() => { + if (auxOverlayContainerRef.current !== overlayContainer || referenceElementRef.current !== referenceElement) + setState(prevState => ({ ...prevState })); + }); + }); // TODO: check + + useEffect(() => { + const resizeHandler = async () => { + await new Promise(resolve => setTimeout(resolve, 100)); + + const { current: overlayContainer } = auxOverlayContainerRef; + const { current: referenceElement } = referenceElementRef; + + if (!overlayContainer || !referenceElement) return; + + const { innerHeight, innerWidth } = window; + + const referenceElementBounds = referenceElement.getBoundingClientRect(); + const overlayContainerBounds = overlayContainer.getBoundingClientRect(); + + const bottomDistance = innerHeight - referenceElementBounds.top - referenceElementBounds.height; + + const top = + bottomDistance > overlayContainerBounds.height + ? innerHeight - bottomDistance + : referenceElementBounds.top - overlayContainerBounds.height; + + const left = + innerWidth - referenceElementBounds.left > overlayContainerBounds.width + ? referenceElementBounds.left + : innerWidth - overlayContainerBounds.width - 10; + + setState({ + top, + left, + isVisible: true, + }); + }; + + resizeHandler(); + + window.addEventListener("resize", resizeHandler); + document.addEventListener("fullscreenchange", resizeHandler); + + return () => { + window.removeEventListener("resize", resizeHandler); + document.removeEventListener("fullscreenchange", resizeHandler); + setState(prevState => ({ ...prevState, isVisible: false })); + }; + }, [auxOverlayContainerRef, auxOverlayContainerRef.current, referenceElementRef, referenceElementRef.current]); + + return { + overlayProps: { + ...state, + overlayContainerRef: auxOverlayContainerRef, + }, + }; +}; + +export type UseOverlayPropsOutput = { + overlayProps: { + top: number; + left: number; + isVisible: boolean; + overlayContainerRef: React.RefObject<HTMLDivElement>; + }; +}; diff --git a/src/hooks/useSetState.ts b/src/hooks/useSetState.ts index 4cc0d95b5dcd005e7df461ac10dab19758e55d46..656b1577c093d670a3d420fdefbd21126807e289 100644 --- a/src/hooks/useSetState.ts +++ b/src/hooks/useSetState.ts @@ -1,27 +1,27 @@ -import React from "react"; - -export type SetState<T> = (state: Partial<T> | ((prevState: T) => Partial<T> | null)) => void; - -export const useSetState = <T>(initialState: T): UseSetStateOutput<T> => { - const [state, setState] = React.useState<T>(initialState); - - const stateRef = React.useRef(state); - stateRef.current = state; - - const dispatch = React.useCallback<SetState<T>>(state => { - const partialState = typeof state === "function" ? state(stateRef.current) : state; - - if (!partialState || Object.keys(partialState).length === 0) return; - - const newState = { - ...stateRef.current, - ...partialState, - }; - - setState(newState); - }, []); - - return [state, dispatch]; -}; - -export type UseSetStateOutput<T> = [T, SetState<T>]; +import React from "react"; + +export type SetState<T> = (state: Partial<T> | ((prevState: T) => Partial<T> | null)) => void; + +export const useSetState = <T>(initialState: T): UseSetStateOutput<T> => { + const [state, setState] = React.useState<T>(initialState); + + const stateRef = React.useRef(state); + stateRef.current = state; + + const dispatch = React.useCallback<SetState<T>>(state => { + const partialState = typeof state === "function" ? state(stateRef.current) : state; + + if (!partialState || Object.keys(partialState).length === 0) return; + + const newState = { + ...stateRef.current, + ...partialState, + }; + + setState(newState); + }, []); + + return [state, dispatch]; +}; + +export type UseSetStateOutput<T> = [T, SetState<T>]; diff --git a/src/hooks/useTrapFocus.tsx b/src/hooks/useTrapFocus.tsx index efa7041451e83cbd9790c3bce68fc28df49597fd..67a3cb6a6d9e9f7ab3958b6a8c6c2f6ebe925f86 100644 --- a/src/hooks/useTrapFocus.tsx +++ b/src/hooks/useTrapFocus.tsx @@ -1,90 +1,90 @@ -import { observable } from "mobx"; -import React, { useEffect } from "react"; -import { useFocus } from "src/hooks"; - -type UseTrapFocusContextValue = { - registerTrap: (ref: React.MutableRefObject<{ requestFocus: () => void; autoFocus: boolean }>) => string; - unregisterTrap: (id: string) => void; - currentValue: string | undefined; -}; - -const UseTrapFocusContext = React.createContext<UseTrapFocusContextValue>(null as any); - -type UseTrapFocusProviderStateItem = { - id: string; - ref: React.MutableRefObject<{ requestFocus: () => void; autoFocus: boolean }>; -}; -export const UseTrapFocusProvider: React.FunctionComponent = props => { - const [value, setValue] = React.useState<UseTrapFocusProviderStateItem[]>([]); - - const focusQueueRef = React.useRef( - observable<UseTrapFocusContextValue>({ - registerTrap: ref => { - const id = uuid(); - const newItem: UseTrapFocusProviderStateItem = { - id, - ref, - }; - - setValue(value => [...value, newItem]); - - return id; - }, - unregisterTrap: id => { - setValue(value => { - const index = value.findIndex(i => i.id === id); - - if (index > 0) return [...value.splice(index, 1)]; - - return value; - }); - }, - currentValue: value.length > 0 ? value[value.length - 1].id : undefined, - }) - ); - - return <UseTrapFocusContext.Provider value={focusQueueRef.current}>{props.children}</UseTrapFocusContext.Provider>; -}; - -// TODO: use -export const useTrapFocus = (requestFocus: () => void, autoFocus: boolean = true) => { - const { registerTrap, unregisterTrap, currentValue } = React.useContext(UseTrapFocusContext); - const isFocusedRef = React.useRef(autoFocus); - const idRef = React.useRef<string>(); - - const registerTrapRef = React.useRef<{ requestFocus: () => void; autoFocus: boolean }>({ - requestFocus: () => { - if (!isFocusedRef.current) requestFocus(); - }, - autoFocus, - }); - - useEffect(() => { - const id = registerTrap(registerTrapRef); - - return () => unregisterTrap(id); - }, []); - - useEffect(() => { - registerTrapRef.current.requestFocus = requestFocus; - registerTrapRef.current.autoFocus = autoFocus; - }, [requestFocus, autoFocus]); - - const { handleFocus, handleBlur } = useFocus( - { - onFocus: () => { - isFocusedRef.current = true; - }, - onBlur: () => { - isFocusedRef.current = false; - - setTimeout(() => { - if (!isFocusedRef.current && idRef.current === currentValue) requestFocus(); - }, 0); - }, - }, - [currentValue] - ); - - return { handleFocus, handleBlur }; -}; +import { observable } from "mobx"; +import React, { useEffect } from "react"; +import { useFocus } from "src/hooks"; + +type UseTrapFocusContextValue = { + registerTrap: (ref: React.MutableRefObject<{ requestFocus: () => void; autoFocus: boolean }>) => string; + unregisterTrap: (id: string) => void; + currentValue: string | undefined; +}; + +const UseTrapFocusContext = React.createContext<UseTrapFocusContextValue>(null as any); + +type UseTrapFocusProviderStateItem = { + id: string; + ref: React.MutableRefObject<{ requestFocus: () => void; autoFocus: boolean }>; +}; +export const UseTrapFocusProvider: React.FunctionComponent = props => { + const [value, setValue] = React.useState<UseTrapFocusProviderStateItem[]>([]); + + const focusQueueRef = React.useRef( + observable<UseTrapFocusContextValue>({ + registerTrap: ref => { + const id = uuid(); + const newItem: UseTrapFocusProviderStateItem = { + id, + ref, + }; + + setValue(value => [...value, newItem]); + + return id; + }, + unregisterTrap: id => { + setValue(value => { + const index = value.findIndex(i => i.id === id); + + if (index > 0) return [...value.splice(index, 1)]; + + return value; + }); + }, + currentValue: value.length > 0 ? value[value.length - 1].id : undefined, + }) + ); + + return <UseTrapFocusContext.Provider value={focusQueueRef.current}>{props.children}</UseTrapFocusContext.Provider>; +}; + +// TODO: use +export const useTrapFocus = (requestFocus: () => void, autoFocus: boolean = true) => { + const { registerTrap, unregisterTrap, currentValue } = React.useContext(UseTrapFocusContext); + const isFocusedRef = React.useRef(autoFocus); + const idRef = React.useRef<string>(); + + const registerTrapRef = React.useRef<{ requestFocus: () => void; autoFocus: boolean }>({ + requestFocus: () => { + if (!isFocusedRef.current) requestFocus(); + }, + autoFocus, + }); + + useEffect(() => { + const id = registerTrap(registerTrapRef); + + return () => unregisterTrap(id); + }, []); + + useEffect(() => { + registerTrapRef.current.requestFocus = requestFocus; + registerTrapRef.current.autoFocus = autoFocus; + }, [requestFocus, autoFocus]); + + const { handleFocus, handleBlur } = useFocus( + { + onFocus: () => { + isFocusedRef.current = true; + }, + onBlur: () => { + isFocusedRef.current = false; + + setTimeout(() => { + if (!isFocusedRef.current && idRef.current === currentValue) requestFocus(); + }, 0); + }, + }, + [currentValue] + ); + + return { handleFocus, handleBlur }; +}; diff --git a/src/index.tsx b/src/index.tsx index bf61aff90b6095bff721f60d4b04298bd142ed2f..b3a779f481455d6ad0092d4341fdb68bca3c3827 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,31 +1,31 @@ -import "whatwg-fetch"; -import "fullscreen-api-polyfill"; - -import * as uuid from "uuid/v4"; - -import { App } from "./App"; -import { GlobalStyle } from "./style/GlobalStyle"; -import { HistoryProvider } from "src/hooks/useHistory"; -import React from "react"; -import ReactDOM from "react-dom"; -import { createBrowserHistory } from "history"; -import { observable } from "mobx"; - -(global as any).uuid = uuid; - -const history = createBrowserHistory({ basename: process.env.PUBLIC_URL }); -const observableHistory = observable(history); -const wrapper = document.getElementById("root"); - -history.listen(location1 => (observableHistory.location = location1)); - -ReactDOM.render( - <> - <GlobalStyle /> - - <HistoryProvider history={observableHistory}> - <App /> - </HistoryProvider> - </>, - wrapper -); +import "whatwg-fetch"; +import "fullscreen-api-polyfill"; + +import * as uuid from "uuid/v4"; + +import { App } from "./App"; +import { GlobalStyle } from "./style/GlobalStyle"; +import { HistoryProvider } from "src/hooks/useHistory"; +import React from "react"; +import ReactDOM from "react-dom"; +import { createBrowserHistory } from "history"; +import { observable } from "mobx"; + +(global as any).uuid = uuid; + +const history = createBrowserHistory({ basename: process.env.PUBLIC_URL }); +const observableHistory = observable(history); +const wrapper = document.getElementById("root"); + +history.listen(location1 => (observableHistory.location = location1)); + +ReactDOM.render( + <> + <GlobalStyle /> + + <HistoryProvider history={observableHistory}> + <App /> + </HistoryProvider> + </>, + wrapper +); diff --git a/src/screens/course/Course.styles.tsx b/src/screens/course/Course.styles.tsx index 89b68581e4a5415f7c50899ef41c006075f54329..bc14471cc02ebbead8e3ac99c2c6c91402c28199 100644 --- a/src/screens/course/Course.styles.tsx +++ b/src/screens/course/Course.styles.tsx @@ -1,72 +1,72 @@ -import { CreativeCommons } from "src/components/creativecommons/CreativeCommons"; -import { CourseClassDetail } from "src/screens/course/components/courseclassdetail/CourseClassDetail"; -import { CourseClassMaster } from "src/screens/course/components/courseclassmaster/CourseClassMaster"; -import { Breakpoint, Mixins } from "src/style"; -import styled, { css } from "styled-components"; -import { CourseURL } from "./components/courseurl/CourseURL"; - -export const styles = { - Wrapper: styled.div` - flex: 1 1 100%; - flex-direction: column; - overflow: auto; - position: relative; - - ${Mixins.WiderThan( - Breakpoint.sm, - css` - flex-direction: row-reverse; - ` - )}; - `, - - CourseClassMaster: styled(CourseClassMaster)` - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - - background-color: white; - box-shadow: 0 0 5px rgba(67, 52, 52, 0.3); - - overflow: auto; - - ${Mixins.WiderThan( - Breakpoint.sm, - css` - order: 1; - position: relative; - width: 100%; - max-width: 350px; - ` - )}; - `, - - CourseClassDetail: styled(CourseClassDetail)` - flex: 0 0 auto; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - - background-color: white; - - ${Mixins.WiderThan( - Breakpoint.sm, - css` - position: static; - flex: 1 1 auto; - - overflow: auto; - ` - )}; - `, - - CreativeCommons: styled(CreativeCommons)` - width: auto; - `, - - CourseButton: styled(CourseURL)``, -}; +import { CreativeCommons } from "src/components/creativecommons/CreativeCommons"; +import { CourseClassDetail } from "src/screens/course/components/courseclassdetail/CourseClassDetail"; +import { CourseClassMaster } from "src/screens/course/components/courseclassmaster/CourseClassMaster"; +import { Breakpoint, Mixins } from "src/style"; +import styled, { css } from "styled-components"; +import { CourseURL } from "./components/courseurl/CourseURL"; + +export const styles = { + Wrapper: styled.div` + flex: 1 1 100%; + flex-direction: column; + overflow: auto; + position: relative; + + ${Mixins.WiderThan( + Breakpoint.sm, + css` + flex-direction: row-reverse; + ` + )}; + `, + + CourseClassMaster: styled(CourseClassMaster)` + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + + background-color: white; + box-shadow: 0 0 5px rgba(67, 52, 52, 0.3); + + overflow: auto; + + ${Mixins.WiderThan( + Breakpoint.sm, + css` + order: 1; + position: relative; + width: 100%; + max-width: 350px; + ` + )}; + `, + + CourseClassDetail: styled(CourseClassDetail)` + flex: 0 0 auto; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + + background-color: white; + + ${Mixins.WiderThan( + Breakpoint.sm, + css` + position: static; + flex: 1 1 auto; + + overflow: auto; + ` + )}; + `, + + CreativeCommons: styled(CreativeCommons)` + width: auto; + `, + + CourseButton: styled(CourseURL)``, +}; diff --git a/src/screens/course/Course.tsx b/src/screens/course/Course.tsx index ef4d2b88e59485720c4df784c1d00d5dd4186a47..d9b1fa6cd7ee555859ac8e506a2f96d9722086a1 100644 --- a/src/screens/course/Course.tsx +++ b/src/screens/course/Course.tsx @@ -1,103 +1,103 @@ -import { CourseContext, CourseProvider } from "./useCourse"; -import { observer, useComputed } from "mobx-react-lite"; -import { useContext, useEffect, useMemo } from "react"; - -import { AppStoreContext } from "src/components/AppStoreContext"; -import { Breakpoint } from "src/style"; -import { LayoutContext } from "../../layout/Layout"; -import { Loading } from "src/components/loading/Loading"; -import { NavigationComponentProps } from "../../navigation/Navigator"; -import React from "react"; -import queryString from "query-string"; -import { styles } from "./Course.styles"; -import { useDocumentTitle } from "../../components/useDocumentTitle"; -import { useFetchCourseByCode } from "openfing-core/lib/hooks/useFetchCourse"; -import { useHistory } from "src/hooks/useHistory"; - -export const Course: React.FunctionComponent<NavigationComponentProps<{ courseCode: string }>> = observer(props => { - const { courseCode } = props.match.params; - - const { history } = useHistory(); - const queryParams = useMemo<{ t?: string; cci?: string; ccn?: string; ccl?: string }>( - () => queryString.parse(history.location.search), - [history.location.search] - ); - const appStore = useContext(AppStoreContext); - const [fetchCourseByCodeState] = useFetchCourseByCode({ courseCode, autoFetch: true, forceFetch: false }); - - const course = fetchCourseByCodeState.success ? fetchCourseByCodeState.course : undefined; - const courseName = course ? course.name : undefined; - const setLayoutOptions = useContext(LayoutContext); - const contextRef = React.useRef<CourseContext>(); - - useDocumentTitle(course && course.name ? `${course.name} - OpenFING` : "Curso - OpenFING"); - - useEffect(() => { - setLayoutOptions({ - header: { - title: courseName ? courseName : "", - right: course && <styles.CourseButton course={course} />, - }, - }); - }, [setLayoutOptions, courseName, course]); - - const courseClassList = - course && course.classLists && course.classLists.length > 0 - ? queryParams.ccl - ? course.classLists.find(ccl => ccl.id.toString() === queryParams.ccl) - : course.classLists[0] - : undefined; - const classes = courseClassList ? courseClassList.classes : undefined; - const courseClass = useComputed(() => { - if (!classes) return undefined; - - if (queryParams.cci) return classes.find(c => c.id.toString() === queryParams.cci); - - if (queryParams.ccn) return classes.find(c => !!c.number && c.number.toString() === queryParams.ccn); - - return undefined; - }, [classes, queryParams]); - useEffect(() => { - const { current } = contextRef; - - if (!current) return; - - current.fetchCourseByCodeState = fetchCourseByCodeState; - current.course = course; - current.courseClassList = courseClassList; - current.courseClass = courseClass; - }, [fetchCourseByCodeState, course, courseClassList, courseClass]); - - useEffect(() => { - if (!contextRef.current) return; - contextRef.current.urlHash = queryParams.t ? "#t=" + queryParams.t : history.location.hash; - }, [queryParams.t, history.location.hash]); - - const detail = (queryParams.ccn || queryParams.cci || appStore.breakpoint > Breakpoint.xs) && ( - <styles.CourseClassDetail key="detail" /> - ); - - const master = <styles.CourseClassMaster key="master" />; - - const content = ( - <styles.Wrapper> - {appStore.breakpoint < Breakpoint.sm ? ( - <> - {master} - {detail} - </> - ) : ( - <> - {detail} - {master} - </> - )} - </styles.Wrapper> - ); - - return ( - <CourseProvider contextRef={contextRef}> - {fetchCourseByCodeState.isLoading ? <Loading /> : fetchCourseByCodeState.success ? content : null} - </CourseProvider> - ); -}); +import { CourseContext, CourseProvider } from "./useCourse"; +import { observer, useComputed } from "mobx-react-lite"; +import { useContext, useEffect, useMemo } from "react"; + +import { AppStoreContext } from "src/components/AppStoreContext"; +import { Breakpoint } from "src/style"; +import { LayoutContext } from "../../layout/Layout"; +import { Loading } from "src/components/loading/Loading"; +import { NavigationComponentProps } from "../../navigation/Navigator"; +import React from "react"; +import queryString from "query-string"; +import { styles } from "./Course.styles"; +import { useDocumentTitle } from "../../components/useDocumentTitle"; +import { useFetchCourseByCode } from "openfing-core/lib/hooks/useFetchCourse"; +import { useHistory } from "src/hooks/useHistory"; + +export const Course: React.FunctionComponent<NavigationComponentProps<{ courseCode: string }>> = observer(props => { + const { courseCode } = props.match.params; + + const { history } = useHistory(); + const queryParams = useMemo<{ t?: string; cci?: string; ccn?: string; ccl?: string }>( + () => queryString.parse(history.location.search), + [history.location.search] + ); + const appStore = useContext(AppStoreContext); + const [fetchCourseByCodeState] = useFetchCourseByCode({ courseCode, autoFetch: true, forceFetch: false }); + + const course = fetchCourseByCodeState.success ? fetchCourseByCodeState.course : undefined; + const courseName = course ? course.name : undefined; + const setLayoutOptions = useContext(LayoutContext); + const contextRef = React.useRef<CourseContext>(); + + useDocumentTitle(course && course.name ? `${course.name} - OpenFING` : "Curso - OpenFING"); + + useEffect(() => { + setLayoutOptions({ + header: { + title: courseName ? courseName : "", + right: course && <styles.CourseButton course={course} />, + }, + }); + }, [setLayoutOptions, courseName, course]); + + const courseClassList = + course && course.classLists && course.classLists.length > 0 + ? queryParams.ccl + ? course.classLists.find(ccl => ccl.id.toString() === queryParams.ccl) + : course.classLists[0] + : undefined; + const classes = courseClassList ? courseClassList.classes : undefined; + const courseClass = useComputed(() => { + if (!classes) return undefined; + + if (queryParams.cci) return classes.find(c => c.id.toString() === queryParams.cci); + + if (queryParams.ccn) return classes.find(c => !!c.number && c.number.toString() === queryParams.ccn); + + return undefined; + }, [classes, queryParams]); + useEffect(() => { + const { current } = contextRef; + + if (!current) return; + + current.fetchCourseByCodeState = fetchCourseByCodeState; + current.course = course; + current.courseClassList = courseClassList; + current.courseClass = courseClass; + }, [fetchCourseByCodeState, course, courseClassList, courseClass]); + + useEffect(() => { + if (!contextRef.current) return; + contextRef.current.urlHash = queryParams.t ? "#t=" + queryParams.t : history.location.hash; + }, [queryParams.t, history.location.hash]); + + const detail = (queryParams.ccn || queryParams.cci || appStore.breakpoint > Breakpoint.xs) && ( + <styles.CourseClassDetail key="detail" /> + ); + + const master = <styles.CourseClassMaster key="master" />; + + const content = ( + <styles.Wrapper> + {appStore.breakpoint < Breakpoint.sm ? ( + <> + {master} + {detail} + </> + ) : ( + <> + {detail} + {master} + </> + )} + </styles.Wrapper> + ); + + return ( + <CourseProvider contextRef={contextRef}> + {fetchCourseByCodeState.isLoading ? <Loading /> : fetchCourseByCodeState.success ? content : null} + </CourseProvider> + ); +}); diff --git a/src/screens/course/__stories__/Course.stories.tsx b/src/screens/course/__stories__/Course.stories.tsx index 5bb52c700b17e01e8e66af286d5535d1935b8633..b2cc583bfa54df7453f9f8426250d918826701a5 100644 --- a/src/screens/course/__stories__/Course.stories.tsx +++ b/src/screens/course/__stories__/Course.stories.tsx @@ -1,22 +1,22 @@ -import { Course } from "../Course"; -import { Layout } from "../../../layout/Layout"; -import { Models } from "openfing-core"; -import React from "react"; -import { assign } from "openfing-core/lib/helpers"; -import moment from "moment"; -import { storiesOf } from "@storybook/react"; - -const courseClass = new Models.CourseClass(1); -assign(courseClass, { - number: 1, - title: "Test", - createdAt: moment("2019-03-19"), -}); - -const Default = () => ( - <Layout routeKey="Course"> - <Course match={{ isExact: false, params: { courseCode: "civ" }, path: "", url: "" }} /> - </Layout> -); - -storiesOf("Course|Course", module).add("Default", () => <Default />); +import { Course } from "../Course"; +import { Layout } from "../../../layout/Layout"; +import { Models } from "openfing-core"; +import React from "react"; +import { assign } from "openfing-core/lib/helpers"; +import moment from "moment"; +import { storiesOf } from "@storybook/react"; + +const courseClass = new Models.CourseClass(1); +assign(courseClass, { + number: 1, + title: "Test", + createdAt: moment("2019-03-19"), +}); + +const Default = () => ( + <Layout routeKey="Course"> + <Course match={{ isExact: false, params: { courseCode: "civ" }, path: "", url: "" }} /> + </Layout> +); + +storiesOf("Course|Course", module).add("Default", () => <Default />); diff --git a/src/screens/course/components/courseclassitem/__stories__/CourseClassItem.stories.tsx b/src/screens/course/components/courseclassitem/__stories__/CourseClassItem.stories.tsx index 73cf68bb9796bbac596e42be25a8e7cc78a80bf1..af51f7b2da4d8d08faaa0a1171aa0c9bf7f7d80f 100644 --- a/src/screens/course/components/courseclassitem/__stories__/CourseClassItem.stories.tsx +++ b/src/screens/course/components/courseclassitem/__stories__/CourseClassItem.stories.tsx @@ -1,24 +1,24 @@ -import { CourseClassItem } from "../CourseClassItem"; -import { Models } from "openfing-core"; -import React from "react"; -import { assign } from "openfing-core/lib/helpers"; -import moment from "moment"; -import { storiesOf } from "@storybook/react"; -import styled from "styled-components"; - -const Container = styled.div` - margin: auto; - max-width: 400px; - width: 100%; -`; - -const courseClass = new Models.CourseClass(1); -assign(courseClass, { - number: 1, - title: "Test", - createdAt: moment("2019-03-19"), -}); - -storiesOf("Course|CourseClassItem", module) - .addDecorator(story => <Container>{story()}</Container>) - .add("Default", () => <CourseClassItem courseClass={courseClass} isLast={false} />); +import { CourseClassItem } from "../CourseClassItem"; +import { Models } from "openfing-core"; +import React from "react"; +import { assign } from "openfing-core/lib/helpers"; +import moment from "moment"; +import { storiesOf } from "@storybook/react"; +import styled from "styled-components"; + +const Container = styled.div` + margin: auto; + max-width: 400px; + width: 100%; +`; + +const courseClass = new Models.CourseClass(1); +assign(courseClass, { + number: 1, + title: "Test", + createdAt: moment("2019-03-19"), +}); + +storiesOf("Course|CourseClassItem", module) + .addDecorator(story => <Container>{story()}</Container>) + .add("Default", () => <CourseClassItem courseClass={courseClass} isLast={false} />); diff --git a/src/screens/course/components/courseclasslist/__stories__/CourseClassList.stories.tsx b/src/screens/course/components/courseclasslist/__stories__/CourseClassList.stories.tsx index ba33840b54867930cbf623ea6ff7b81b5394b5e2..d64c7e8bab152ac98ec6373e5f3219484561479b 100644 --- a/src/screens/course/components/courseclasslist/__stories__/CourseClassList.stories.tsx +++ b/src/screens/course/components/courseclasslist/__stories__/CourseClassList.stories.tsx @@ -1,41 +1,41 @@ -import { CourseClassList } from "../CourseClassList"; -import { CourseProvider } from "src/screens/course/useCourse"; -import { Models } from "openfing-core"; -import React from "react"; -import { assign } from "openfing-core/lib/helpers"; -import moment from "moment"; -import { storiesOf } from "@storybook/react"; -import styled from "styled-components"; - -const Container = styled.div` - margin: auto; - max-width: 400px; - width: 100%; -`; - -const courseClass = new Models.CourseClass(1); -assign(courseClass, { - number: 1, - title: "Test", - createdAt: moment("2019-03-19"), -}); -const courseClasses = [courseClass, courseClass]; - -const courseClassList = new Models.CourseClassList(1); -assign(courseClassList, { - name: "Test", - createdAt: moment("2019-03-19"), - classes: courseClasses, -}); - -const Default = () => ( - <CourseProvider - // value={observable({ courseClassList, courseClassPlayerController: new CourseClassPlayerController() })} TODO - > - <CourseClassList /> - </CourseProvider> -); - -storiesOf("Course|CourseClassList", module) - .addDecorator(story => <Container>{story()}</Container>) - .add("Default", () => <Default />); +import { CourseClassList } from "../CourseClassList"; +import { CourseProvider } from "src/screens/course/useCourse"; +import { Models } from "openfing-core"; +import React from "react"; +import { assign } from "openfing-core/lib/helpers"; +import moment from "moment"; +import { storiesOf } from "@storybook/react"; +import styled from "styled-components"; + +const Container = styled.div` + margin: auto; + max-width: 400px; + width: 100%; +`; + +const courseClass = new Models.CourseClass(1); +assign(courseClass, { + number: 1, + title: "Test", + createdAt: moment("2019-03-19"), +}); +const courseClasses = [courseClass, courseClass]; + +const courseClassList = new Models.CourseClassList(1); +assign(courseClassList, { + name: "Test", + createdAt: moment("2019-03-19"), + classes: courseClasses, +}); + +const Default = () => ( + <CourseProvider + // value={observable({ courseClassList, courseClassPlayerController: new CourseClassPlayerController() })} TODO + > + <CourseClassList /> + </CourseProvider> +); + +storiesOf("Course|CourseClassList", module) + .addDecorator(story => <Container>{story()}</Container>) + .add("Default", () => <Default />); diff --git a/src/screens/course/components/courseclassplayer/CourseClassPlayer.controller.ts b/src/screens/course/components/courseclassplayer/CourseClassPlayer.controller.ts index 44a4f5cdd9f369b3e85f136372df08001d73bf39..a06a4f23ae36ff98b75067fb85acdb8cbbfa8bd7 100644 --- a/src/screens/course/components/courseclassplayer/CourseClassPlayer.controller.ts +++ b/src/screens/course/components/courseclassplayer/CourseClassPlayer.controller.ts @@ -1,471 +1,471 @@ -import { action, computed, observable } from "mobx"; - -export class CourseClassPlayerController { - private video: HTMLVideoElement | null = null; - private videoWrapper: HTMLDivElement | null = null; - private removeListeners: () => void | undefined; - - @observable protected _buffered: TimeRanges | undefined = undefined; - @observable protected _currentTime: number = NaN; - @observable protected _defaultMuted: boolean = false; - @observable protected _defaultPlaybackRate: number = 1; - @observable protected _duration: number = NaN; - @observable protected _ended: boolean = false; - @observable protected _error: MediaError | null = null; - @observable protected _loop: boolean = false; - @observable protected _muted: boolean = false; - @observable protected _networkState: 0 | 1 | 2 | 3 = 0; - @observable protected _paused: boolean = false; - @observable protected _playbackRate: number = 1; - @observable protected _played: TimeRanges | undefined = undefined; - @observable protected _readyState: 0 | 1 | 2 | 3 | 4 = 0; - @observable protected _seeking: boolean = false; - @observable protected _volume: number = 1; - - @observable protected _isFullscreen: boolean = false; - @observable protected blockControlsIds: string[] = []; - - @computed public get buffered() { - return this._buffered; - } - - @computed public get currentTime() { - return this._currentTime; - } - - @action public setCurrentTime(time: number) { - const { video } = this; - if (!video) return; - - time = Math.max(0, Math.min(this.duration, time)); - - video.currentTime = time; - this._currentTime = time; - } - - @computed public get defaultMuted() { - return this._defaultMuted; - } - - @action public setDefaultMuted(defaultMuted: boolean) { - const { video } = this; - - if (!video) return; - - video.defaultMuted = this._defaultMuted = defaultMuted; - } - - @computed public get defaultPlaybackRate() { - return this._defaultPlaybackRate; - } - - @action public setDefaultPlaybackRate(defaultPlaybackRate: number) { - const { video } = this; - - if (!video) return; - - video.defaultPlaybackRate = this._defaultPlaybackRate = defaultPlaybackRate; - } - - @computed public get duration() { - return this._duration; - } - - @computed public get ended() { - return this._ended; - } - - @computed public get error() { - return this._error; - } - - @computed public get loop() { - return this._loop; - } - - @action public setLoop(loop: boolean) { - const { video } = this; - - if (!video) return; - - video.loop = this._loop = loop; - } - - @computed public get muted() { - return this._muted; - } - - @action public setMuted(muted: boolean) { - const { video } = this; - - if (!video) return; - - video.muted = this._muted = muted; - } - - @computed public get networkState() { - return this._networkState; - } - - @computed public get paused() { - return this._paused; - } - - @computed public get playbackRate() { - return this._playbackRate; - } - - @action public setPlaybackRate(playbackRate: number) { - const { video } = this; - - if (!video) return; - - video.playbackRate = this._playbackRate = Math.max(0.25, Math.min(10, playbackRate)); - } - - @computed public get played() { - return this._played; - } - - @computed public get readyState() { - return this._readyState; - } - - @computed public get seeking() { - return this._seeking; - } - - @computed public get volume() { - return this._volume; - } - - @action public setVolume(volume: number) { - const { video } = this; - - if (!video) return; - - video.volume = this._volume = volume; - } - - @computed public get blockControls(): boolean { - return this.blockControlsIds.length > 0; - } - - @action.bound public addBlockControls(id: string) { - this.blockControlsIds.push(id); - } - - @action.bound public removeBlockControls(id: string): boolean { - const index = this.blockControlsIds.indexOf(id); - - if (index < 0) return false; - - this.blockControlsIds.splice(index, 1); - return true; - } - - @computed public get isPlaying(): boolean { - const { paused, readyState, ended, error } = this; - - return !paused && readyState > 2 && !ended && !error; - } - - @action.bound public setIsPlaying(isPlaying: boolean) { - const { video } = this; - if (!video) return; - - if (isPlaying) video.play(); - else video.pause(); - } - - @computed public get loadedPercentage(): number { - const { buffered, currentTime, duration } = this; - - if (!buffered || Number.isNaN(currentTime) || Number.isNaN(duration)) return NaN; - - if (buffered.length === 0) return 0; - - let i; - let bufferedEnd = buffered.end(0); - - for ( - i = 0; - i < buffered.length - 1 && !(buffered.start(i) <= currentTime && currentTime <= buffered.end(i)); - i++ - ) - i++; - if (i < buffered.length) bufferedEnd = buffered.end(i); - - return (bufferedEnd * 100) / duration || 0; - } - - @computed public get isFullscreen() { - return this._isFullscreen; - } - - @action.bound public setFullscreen(fullscreen: boolean) { - if (!fullscreen) document.exitFullscreen(); - else if (this.videoWrapper) this.videoWrapper.requestFullscreen(); - } - - @computed public get showControls(): boolean { - return this.loaded && this.blockControls; - } - - protected setShowControlsTimeout: number | undefined; - - @action public setShowControls(timeout: number): string { - const id = uuid(); - - this.addBlockControls(id); - - this.setShowControlsTimeout = setTimeout(() => this.removeBlockControls(id), timeout); - - return id; - } - - @computed public get loaded() { - return this.readyState > 0; - } - - @computed public get waiting() { - return this.readyState < 3; - } - - protected removeFullscreenListener: (() => void) | undefined; - - @action public setVideoWrapperInstance(videoWrapper: HTMLDivElement | null) { - this.videoWrapper = videoWrapper; - - if (this.removeFullscreenListener) this.removeFullscreenListener(); - - const syncFullscreenState = () => (this._isFullscreen = this.videoWrapper === document.fullscreenElement); - syncFullscreenState(); - - document.addEventListener("fullscreenchange", syncFullscreenState); - - this.removeFullscreenListener = () => document.removeEventListener("fullscreenchange", syncFullscreenState); - } - - @action.bound protected syncVideoState( - video: Pick< - HTMLVideoElement, - | "buffered" - | "currentTime" - | "defaultMuted" - | "defaultPlaybackRate" - | "duration" - | "ended" - | "error" - | "loop" - | "muted" - | "networkState" - | "paused" - | "playbackRate" - | "played" - | "readyState" - | "seeking" - | "volume" - > | null, - eventName?: string - ) { - // if (eventName) console.log(eventName); - - if (!video) { - video = { - buffered: undefined as any, - currentTime: NaN, - defaultMuted: false, - defaultPlaybackRate: 1, - duration: NaN, - ended: false, - error: null, - loop: false, - muted: false, - networkState: 0, - paused: false, - playbackRate: 1, - played: undefined as any, - readyState: 0, - seeking: false, - volume: 1, - }; - } - - const { - currentTime, - defaultMuted, - defaultPlaybackRate, - duration, - ended, - error, - loop, - muted, - networkState, - paused, - playbackRate, - played, - readyState, - seeking, - volume, - } = video; - - this._currentTime = currentTime; - this._defaultMuted = defaultMuted; - this._defaultPlaybackRate = defaultPlaybackRate; - this._duration = duration; - this._ended = ended; - this._error = error; - this._loop = loop; - this._muted = muted; - this._networkState = networkState as any; - this._paused = paused; - this._playbackRate = playbackRate; - this._played = played; - this._readyState = readyState as any; - this._seeking = seeking; - this._volume = volume; - - // this._isPlaying = !paused && readyState > 2 && !ended && !error; - // this._currentTime = currentTime || 0; - // this._duration = duration || 0; - // this._loadedPercentage = 0; // TODO - // this._waiting = false; // TODO - // this._playbackRate = playbackRate || this.playbackRate; - // this._loaded = readyState > 0; - // this._volume = volume; - // this._readyState = readyState; - - // this._duration = Number.isNaN(duration) ? 0 : duration; - // this._currentTime = Number.isNaN(currentTime) ? 0 : currentTime; - - // this._playbackRate = playbackRate; - } - - @action public setVideoInstance(video: HTMLVideoElement | null) { - if (this.removeListeners) this.removeListeners(); - - const eventMap: Partial<Record<keyof HTMLVideoElementEventMap, undefined | ((e: Event) => boolean)>> = { - // MSVideoFormatChanged: undefined, - // MSVideoFrameStepCompleted: undefined, - // MSVideoOptimalLayoutChanged: undefined, - encrypted: undefined, - // msneedkey: undefined, - // waitingforkey: undefined, - fullscreenchange: undefined, - fullscreenerror: undefined, - abort: undefined, - animationcancel: undefined, - animationend: undefined, - animationiteration: undefined, - animationstart: undefined, - auxclick: undefined, - blur: undefined, - cancel: undefined, - canplay: undefined, - canplaythrough: undefined, - change: undefined, - // click: undefined, - close: undefined, - contextmenu: undefined, - cuechange: undefined, - dblclick: undefined, - drag: undefined, - dragend: undefined, - dragenter: undefined, - dragexit: undefined, - dragleave: undefined, - dragover: undefined, - dragstart: undefined, - drop: undefined, - durationchange: undefined, - emptied: undefined, - ended: undefined, - error: undefined, - focus: undefined, - gotpointercapture: undefined, - input: undefined, - invalid: undefined, - keydown: undefined, - keypress: undefined, - keyup: undefined, - load: undefined, - loadeddata: undefined, - loadedmetadata: undefined, - loadend: undefined, - loadstart: undefined, - lostpointercapture: undefined, - // mousedown: undefined, - // mouseenter: undefined, - // mouseleave: undefined, - // mousemove: undefined, - // mouseout: undefined, - // mouseover: undefined, - // mouseup: undefined, - pause: undefined, - play: undefined, - playing: undefined, - // pointercancel: undefined, - // pointerdown: undefined, - // pointerenter: undefined, - // pointerleave: undefined, - // pointermove: undefined, - // pointerout: undefined, - // pointerover: undefined, - // pointerup: undefined, - progress: undefined, - ratechange: undefined, - reset: undefined, - // resize: undefined, - scroll: undefined, - // securitypolicyviolation: undefined, - seeked: undefined, - seeking: undefined, - select: undefined, - selectionchange: undefined, - selectstart: undefined, - stalled: undefined, - submit: undefined, - suspend: undefined, - timeupdate: undefined, - toggle: undefined, - // touchcancel: undefined, - // touchend: undefined, - // touchmove: undefined, - // touchstart: undefined, - transitioncancel: undefined, - transitionend: undefined, - transitionrun: undefined, - transitionstart: undefined, - volumechange: undefined, - waiting: undefined, - wheel: undefined, - copy: undefined, - cut: undefined, - paste: undefined, - }; - - this.video = video; - this.syncVideoState(video); - - if (!video) return; - - const eventListeners: Record<keyof HTMLVideoElementEventMap, (e: Event) => void> = {} as any; - - for (const key in eventMap) { - const typedKey = key as keyof HTMLVideoElementEventMap; - - eventListeners[typedKey] = (e: Event) => { - const value = eventMap[key as keyof HTMLVideoElementEventMap]; - - if (!value || value(e)) this.syncVideoState(e.currentTarget as HTMLVideoElement, typedKey); - }; - } - - for (const key in eventListeners) video.addEventListener(key, eventListeners[key]); - - this.removeListeners = () => { - for (const key in eventListeners) video.removeEventListener(key, eventListeners[key]); - }; - } -} +import { action, computed, observable } from "mobx"; + +export class CourseClassPlayerController { + private video: HTMLVideoElement | null = null; + private videoWrapper: HTMLDivElement | null = null; + private removeListeners: () => void | undefined; + + @observable protected _buffered: TimeRanges | undefined = undefined; + @observable protected _currentTime: number = NaN; + @observable protected _defaultMuted: boolean = false; + @observable protected _defaultPlaybackRate: number = 1; + @observable protected _duration: number = NaN; + @observable protected _ended: boolean = false; + @observable protected _error: MediaError | null = null; + @observable protected _loop: boolean = false; + @observable protected _muted: boolean = false; + @observable protected _networkState: 0 | 1 | 2 | 3 = 0; + @observable protected _paused: boolean = false; + @observable protected _playbackRate: number = 1; + @observable protected _played: TimeRanges | undefined = undefined; + @observable protected _readyState: 0 | 1 | 2 | 3 | 4 = 0; + @observable protected _seeking: boolean = false; + @observable protected _volume: number = 1; + + @observable protected _isFullscreen: boolean = false; + @observable protected blockControlsIds: string[] = []; + + @computed public get buffered() { + return this._buffered; + } + + @computed public get currentTime() { + return this._currentTime; + } + + @action public setCurrentTime(time: number) { + const { video } = this; + if (!video) return; + + time = Math.max(0, Math.min(this.duration, time)); + + video.currentTime = time; + this._currentTime = time; + } + + @computed public get defaultMuted() { + return this._defaultMuted; + } + + @action public setDefaultMuted(defaultMuted: boolean) { + const { video } = this; + + if (!video) return; + + video.defaultMuted = this._defaultMuted = defaultMuted; + } + + @computed public get defaultPlaybackRate() { + return this._defaultPlaybackRate; + } + + @action public setDefaultPlaybackRate(defaultPlaybackRate: number) { + const { video } = this; + + if (!video) return; + + video.defaultPlaybackRate = this._defaultPlaybackRate = defaultPlaybackRate; + } + + @computed public get duration() { + return this._duration; + } + + @computed public get ended() { + return this._ended; + } + + @computed public get error() { + return this._error; + } + + @computed public get loop() { + return this._loop; + } + + @action public setLoop(loop: boolean) { + const { video } = this; + + if (!video) return; + + video.loop = this._loop = loop; + } + + @computed public get muted() { + return this._muted; + } + + @action public setMuted(muted: boolean) { + const { video } = this; + + if (!video) return; + + video.muted = this._muted = muted; + } + + @computed public get networkState() { + return this._networkState; + } + + @computed public get paused() { + return this._paused; + } + + @computed public get playbackRate() { + return this._playbackRate; + } + + @action public setPlaybackRate(playbackRate: number) { + const { video } = this; + + if (!video) return; + + video.playbackRate = this._playbackRate = Math.max(0.25, Math.min(10, playbackRate)); + } + + @computed public get played() { + return this._played; + } + + @computed public get readyState() { + return this._readyState; + } + + @computed public get seeking() { + return this._seeking; + } + + @computed public get volume() { + return this._volume; + } + + @action public setVolume(volume: number) { + const { video } = this; + + if (!video) return; + + video.volume = this._volume = volume; + } + + @computed public get blockControls(): boolean { + return this.blockControlsIds.length > 0; + } + + @action.bound public addBlockControls(id: string) { + this.blockControlsIds.push(id); + } + + @action.bound public removeBlockControls(id: string): boolean { + const index = this.blockControlsIds.indexOf(id); + + if (index < 0) return false; + + this.blockControlsIds.splice(index, 1); + return true; + } + + @computed public get isPlaying(): boolean { + const { paused, readyState, ended, error } = this; + + return !paused && readyState > 2 && !ended && !error; + } + + @action.bound public setIsPlaying(isPlaying: boolean) { + const { video } = this; + if (!video) return; + + if (isPlaying) video.play(); + else video.pause(); + } + + @computed public get loadedPercentage(): number { + const { buffered, currentTime, duration } = this; + + if (!buffered || Number.isNaN(currentTime) || Number.isNaN(duration)) return NaN; + + if (buffered.length === 0) return 0; + + let i; + let bufferedEnd = buffered.end(0); + + for ( + i = 0; + i < buffered.length - 1 && !(buffered.start(i) <= currentTime && currentTime <= buffered.end(i)); + i++ + ) + i++; + if (i < buffered.length) bufferedEnd = buffered.end(i); + + return (bufferedEnd * 100) / duration || 0; + } + + @computed public get isFullscreen() { + return this._isFullscreen; + } + + @action.bound public setFullscreen(fullscreen: boolean) { + if (!fullscreen) document.exitFullscreen(); + else if (this.videoWrapper) this.videoWrapper.requestFullscreen(); + } + + @computed public get showControls(): boolean { + return this.loaded && this.blockControls; + } + + protected setShowControlsTimeout: number | undefined; + + @action public setShowControls(timeout: number): string { + const id = uuid(); + + this.addBlockControls(id); + + this.setShowControlsTimeout = setTimeout(() => this.removeBlockControls(id), timeout); + + return id; + } + + @computed public get loaded() { + return this.readyState > 0; + } + + @computed public get waiting() { + return this.readyState < 3; + } + + protected removeFullscreenListener: (() => void) | undefined; + + @action public setVideoWrapperInstance(videoWrapper: HTMLDivElement | null) { + this.videoWrapper = videoWrapper; + + if (this.removeFullscreenListener) this.removeFullscreenListener(); + + const syncFullscreenState = () => (this._isFullscreen = this.videoWrapper === document.fullscreenElement); + syncFullscreenState(); + + document.addEventListener("fullscreenchange", syncFullscreenState); + + this.removeFullscreenListener = () => document.removeEventListener("fullscreenchange", syncFullscreenState); + } + + @action.bound protected syncVideoState( + video: Pick< + HTMLVideoElement, + | "buffered" + | "currentTime" + | "defaultMuted" + | "defaultPlaybackRate" + | "duration" + | "ended" + | "error" + | "loop" + | "muted" + | "networkState" + | "paused" + | "playbackRate" + | "played" + | "readyState" + | "seeking" + | "volume" + > | null, + eventName?: string + ) { + // if (eventName) console.log(eventName); + + if (!video) { + video = { + buffered: undefined as any, + currentTime: NaN, + defaultMuted: false, + defaultPlaybackRate: 1, + duration: NaN, + ended: false, + error: null, + loop: false, + muted: false, + networkState: 0, + paused: false, + playbackRate: 1, + played: undefined as any, + readyState: 0, + seeking: false, + volume: 1, + }; + } + + const { + currentTime, + defaultMuted, + defaultPlaybackRate, + duration, + ended, + error, + loop, + muted, + networkState, + paused, + playbackRate, + played, + readyState, + seeking, + volume, + } = video; + + this._currentTime = currentTime; + this._defaultMuted = defaultMuted; + this._defaultPlaybackRate = defaultPlaybackRate; + this._duration = duration; + this._ended = ended; + this._error = error; + this._loop = loop; + this._muted = muted; + this._networkState = networkState as any; + this._paused = paused; + this._playbackRate = playbackRate; + this._played = played; + this._readyState = readyState as any; + this._seeking = seeking; + this._volume = volume; + + // this._isPlaying = !paused && readyState > 2 && !ended && !error; + // this._currentTime = currentTime || 0; + // this._duration = duration || 0; + // this._loadedPercentage = 0; // TODO + // this._waiting = false; // TODO + // this._playbackRate = playbackRate || this.playbackRate; + // this._loaded = readyState > 0; + // this._volume = volume; + // this._readyState = readyState; + + // this._duration = Number.isNaN(duration) ? 0 : duration; + // this._currentTime = Number.isNaN(currentTime) ? 0 : currentTime; + + // this._playbackRate = playbackRate; + } + + @action public setVideoInstance(video: HTMLVideoElement | null) { + if (this.removeListeners) this.removeListeners(); + + const eventMap: Partial<Record<keyof HTMLVideoElementEventMap, undefined | ((e: Event) => boolean)>> = { + // MSVideoFormatChanged: undefined, + // MSVideoFrameStepCompleted: undefined, + // MSVideoOptimalLayoutChanged: undefined, + encrypted: undefined, + // msneedkey: undefined, + // waitingforkey: undefined, + fullscreenchange: undefined, + fullscreenerror: undefined, + abort: undefined, + animationcancel: undefined, + animationend: undefined, + animationiteration: undefined, + animationstart: undefined, + auxclick: undefined, + blur: undefined, + cancel: undefined, + canplay: undefined, + canplaythrough: undefined, + change: undefined, + // click: undefined, + close: undefined, + contextmenu: undefined, + cuechange: undefined, + dblclick: undefined, + drag: undefined, + dragend: undefined, + dragenter: undefined, + dragexit: undefined, + dragleave: undefined, + dragover: undefined, + dragstart: undefined, + drop: undefined, + durationchange: undefined, + emptied: undefined, + ended: undefined, + error: undefined, + focus: undefined, + gotpointercapture: undefined, + input: undefined, + invalid: undefined, + keydown: undefined, + keypress: undefined, + keyup: undefined, + load: undefined, + loadeddata: undefined, + loadedmetadata: undefined, + loadend: undefined, + loadstart: undefined, + lostpointercapture: undefined, + // mousedown: undefined, + // mouseenter: undefined, + // mouseleave: undefined, + // mousemove: undefined, + // mouseout: undefined, + // mouseover: undefined, + // mouseup: undefined, + pause: undefined, + play: undefined, + playing: undefined, + // pointercancel: undefined, + // pointerdown: undefined, + // pointerenter: undefined, + // pointerleave: undefined, + // pointermove: undefined, + // pointerout: undefined, + // pointerover: undefined, + // pointerup: undefined, + progress: undefined, + ratechange: undefined, + reset: undefined, + // resize: undefined, + scroll: undefined, + // securitypolicyviolation: undefined, + seeked: undefined, + seeking: undefined, + select: undefined, + selectionchange: undefined, + selectstart: undefined, + stalled: undefined, + submit: undefined, + suspend: undefined, + timeupdate: undefined, + toggle: undefined, + // touchcancel: undefined, + // touchend: undefined, + // touchmove: undefined, + // touchstart: undefined, + transitioncancel: undefined, + transitionend: undefined, + transitionrun: undefined, + transitionstart: undefined, + volumechange: undefined, + waiting: undefined, + wheel: undefined, + copy: undefined, + cut: undefined, + paste: undefined, + }; + + this.video = video; + this.syncVideoState(video); + + if (!video) return; + + const eventListeners: Record<keyof HTMLVideoElementEventMap, (e: Event) => void> = {} as any; + + for (const key in eventMap) { + const typedKey = key as keyof HTMLVideoElementEventMap; + + eventListeners[typedKey] = (e: Event) => { + const value = eventMap[key as keyof HTMLVideoElementEventMap]; + + if (!value || value(e)) this.syncVideoState(e.currentTarget as HTMLVideoElement, typedKey); + }; + } + + for (const key in eventListeners) video.addEventListener(key, eventListeners[key]); + + this.removeListeners = () => { + for (const key in eventListeners) video.removeEventListener(key, eventListeners[key]); + }; + } +} diff --git a/src/screens/course/components/courseclassplayer/CourseClassPlayer.styles.tsx b/src/screens/course/components/courseclassplayer/CourseClassPlayer.styles.tsx index 41dae3252d39f18ab285bede15b3cf0d0bf18f2d..86904a71044a126ae09724cffc870efc1429d6ad 100644 --- a/src/screens/course/components/courseclassplayer/CourseClassPlayer.styles.tsx +++ b/src/screens/course/components/courseclassplayer/CourseClassPlayer.styles.tsx @@ -1,66 +1,66 @@ -import { Loading } from "src/components/loading/Loading"; -import styled, { css } from "styled-components"; -import { Controls } from "./components/controls/Controls"; -import { Video } from "./components/video/Video"; - -export const styles = { - Wrapper: styled.div<{ showCursor: boolean; fullscreen: boolean }>` - position: relative; - width: 100%; - justify-content: center; - - ${({ showCursor }) => - showCursor && - css` - cursor: none; - `}; - - ${({ fullscreen }) => - fullscreen && - css` - &, - &:focus { - background-color: #000; - } - `}; - `, - - Video: styled(Video)` - background-color: transparent; - width: 100%; - max-height: 100%; - flex-shrink: 1; - `, - - Controls: styled(Controls)` - position: absolute; - left: 0; - right: 0; - bottom: 0; - top: 0; - `, - - Loading: styled(Loading)` - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - margin: auto; - align-self: center; - `, - - OverlayContainer: styled.div` - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - pointer-events: none; - z-index: 1; - - * > { - pointer-events: auto; - } - `, -}; +import { Loading } from "src/components/loading/Loading"; +import styled, { css } from "styled-components"; +import { Controls } from "./components/controls/Controls"; +import { Video } from "./components/video/Video"; + +export const styles = { + Wrapper: styled.div<{ showCursor: boolean; fullscreen: boolean }>` + position: relative; + width: 100%; + justify-content: center; + + ${({ showCursor }) => + showCursor && + css` + cursor: none; + `}; + + ${({ fullscreen }) => + fullscreen && + css` + &, + &:focus { + background-color: #000; + } + `}; + `, + + Video: styled(Video)` + background-color: transparent; + width: 100%; + max-height: 100%; + flex-shrink: 1; + `, + + Controls: styled(Controls)` + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 0; + `, + + Loading: styled(Loading)` + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + margin: auto; + align-self: center; + `, + + OverlayContainer: styled.div` + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + z-index: 1; + + * > { + pointer-events: auto; + } + `, +}; diff --git a/src/screens/course/components/courseclassplayer/CourseClassPlayer.tsx b/src/screens/course/components/courseclassplayer/CourseClassPlayer.tsx index 570634ef92b75cc8a068873b4e8bbff93e3a3e58..3f7cfd97eb95498869ea7353644807f82c3ebaf9 100644 --- a/src/screens/course/components/courseclassplayer/CourseClassPlayer.tsx +++ b/src/screens/course/components/courseclassplayer/CourseClassPlayer.tsx @@ -1,169 +1,169 @@ -import { useCallback, useEffect, useRef } from "react"; - -import { LayerProvider } from "src/hooks/useLayer"; -import { OverlayContainerContext } from "src/context/OverlayContainer"; -import React from "react"; -import { observer } from "mobx-react-lite"; -import { styles } from "./CourseClassPlayer.styles"; -import { useCourse } from "src/screens/course/useCourse"; -import { usePress } from "src/components/usePress"; - -export type CourseClassPlayerProps = { - className?: string; -}; - -export const CourseClassPlayer = observer((props: CourseClassPlayerProps) => { - const { courseClassPlayerController: controller, courseClass } = useCourse(); - - const wrapperRef = useRef<HTMLDivElement>(null); - const videoRef = useRef<HTMLVideoElement>(null); - const isTouchRef = useRef<boolean>(false); - const controlsIsFocusRef = useRef(false); - - const showControlsIdRef = React.useRef<string>(); - const addShowControls = () => { - if (showControlsIdRef.current) controller.removeBlockControls(showControlsIdRef.current); - - showControlsIdRef.current = controller.setShowControls(2000); - }; - - const handleMouseMove = useCallback(() => { - if (isTouchRef.current) return; - - addShowControls(); - }, []); - - const isDoubleClickRef = React.useRef(false); - const removeShowControls = () => - !!showControlsIdRef.current && controller.removeBlockControls(showControlsIdRef.current); - const { handleTouch, handleClick } = usePress({ - onTouch: async () => { - console.log("touch"); - isTouchRef.current = true; - - await new Promise(resolve => setTimeout(resolve, 500)); - - if (isDoubleClickRef.current || controlsIsFocusRef.current) return; - - if (!showControlsIdRef.current || !removeShowControls()) addShowControls(); - }, - onClick: async () => { - console.log("click"); - isTouchRef.current = false; - - await new Promise(resolve => setTimeout(resolve, 300)); - - if (controlsIsFocusRef.current || isDoubleClickRef.current) return; - - controller.setIsPlaying(!controller.isPlaying); - }, - }); - - React.useEffect(() => addShowControls(), [controller.loaded]); - - const handleControlsFocus = useCallback(() => { - controlsIsFocusRef.current = true; - }, []); - - const handleControlsBlur = useCallback(() => { - controlsIsFocusRef.current = false; - }, []); - - const isDoubleClickTimeout = React.useRef<number>(); - const handleDoubleClick = useCallback(() => { - if (controlsIsFocusRef.current) return; - - isDoubleClickRef.current = true; - if (isDoubleClickTimeout.current) clearTimeout(isDoubleClickTimeout.current); - - isDoubleClickTimeout.current = setTimeout(() => (isDoubleClickRef.current = false), 1000); - controller.setFullscreen(!controller.isFullscreen); - }, []); - - const handleKeyDown = useCallback<React.KeyboardEventHandler>(e => { - const { key } = e; - - if (e.defaultPrevented) return; - - if (["ArrowLeft", "j"].includes(key)) { - controller.setCurrentTime(controller.currentTime - 10); - e.preventDefault(); - } else if (["ArrowRight", "l"].includes(key)) { - controller.setCurrentTime(controller.currentTime + 10); - e.preventDefault(); - } - if (key === "ArrowUp") { - controller.setVolume(Math.floor(Math.max(0, Math.min(10, controller.volume * 10 + 1))) / 10); - e.preventDefault(); - } else if (key === "ArrowDown") { - controller.setVolume(Math.floor(Math.max(0, Math.min(10, controller.volume * 10 - 1))) / 10); - e.preventDefault(); - } else if ([" ", "k"].includes(key)) { - controller.setIsPlaying(!controller.isPlaying); - e.preventDefault(); - } else if (key === "+") { - controller.setPlaybackRate(controller.playbackRate + 0.25); - e.preventDefault(); - } else if (key === "-") { - controller.setPlaybackRate(controller.playbackRate - 0.25); - e.preventDefault(); - } else if (key === "*") { - controller.setPlaybackRate(1); - e.preventDefault(); - } else if (["Home", "0"].includes(key)) { - controller.setCurrentTime(0); - e.preventDefault(); - } else if (key === "End") { - controller.setCurrentTime(controller.duration || 0); - e.preventDefault(); - } else if (key === "f") { - if (showControlsIdRef.current) removeShowControls(); - else addShowControls(); - - e.preventDefault(); - } else { - const number = Number(key); - - if (!Number.isNaN(number) && controller.duration) - controller.setCurrentTime(number * 10 * (controller.duration / 100)); - } - }, []); - - const overlayContainerRef = React.useRef<HTMLDivElement>(null); - - const qualities = - courseClass && courseClass.videos && courseClass.videos.length > 0 - ? courseClass.videos[0].qualities - : undefined; - - useEffect(() => { - controller.setVideoInstance(qualities ? videoRef.current : null); - controller.setVideoWrapperInstance(wrapperRef.current); - }, [qualities]); - - return ( - <OverlayContainerContext.Provider value={overlayContainerRef}> - <LayerProvider containerRef={overlayContainerRef}> - <styles.Wrapper - ref={wrapperRef} - className={props.className} - tabIndex={0} - fullscreen={controller.isFullscreen} - showCursor={!controller.showControls && controller.isFullscreen} - onTouchStart={handleTouch} - onClick={handleClick} - onMouseMove={handleMouseMove} - onDoubleClick={handleDoubleClick} - onKeyDown={handleKeyDown} - > - {qualities && <styles.Video videoRef={videoRef} quality={qualities[0]} />} - - {controller.loaded && <styles.Controls onFocus={handleControlsFocus} onBlur={handleControlsBlur} />} - {(!controller.loaded || controller.waiting || controller.seeking) && <styles.Loading />} - - <styles.OverlayContainer ref={overlayContainerRef} /> - </styles.Wrapper> - </LayerProvider> - </OverlayContainerContext.Provider> - ); -}); +import { useCallback, useEffect, useRef } from "react"; + +import { LayerProvider } from "src/hooks/useLayer"; +import { OverlayContainerContext } from "src/context/OverlayContainer"; +import React from "react"; +import { observer } from "mobx-react-lite"; +import { styles } from "./CourseClassPlayer.styles"; +import { useCourse } from "src/screens/course/useCourse"; +import { usePress } from "src/components/usePress"; + +export type CourseClassPlayerProps = { + className?: string; +}; + +export const CourseClassPlayer = observer((props: CourseClassPlayerProps) => { + const { courseClassPlayerController: controller, courseClass } = useCourse(); + + const wrapperRef = useRef<HTMLDivElement>(null); + const videoRef = useRef<HTMLVideoElement>(null); + const isTouchRef = useRef<boolean>(false); + const controlsIsFocusRef = useRef(false); + + const showControlsIdRef = React.useRef<string>(); + const addShowControls = () => { + if (showControlsIdRef.current) controller.removeBlockControls(showControlsIdRef.current); + + showControlsIdRef.current = controller.setShowControls(2000); + }; + + const handleMouseMove = useCallback(() => { + if (isTouchRef.current) return; + + addShowControls(); + }, []); + + const isDoubleClickRef = React.useRef(false); + const removeShowControls = () => + !!showControlsIdRef.current && controller.removeBlockControls(showControlsIdRef.current); + const { handleTouch, handleClick } = usePress({ + onTouch: async () => { + console.log("touch"); + isTouchRef.current = true; + + await new Promise(resolve => setTimeout(resolve, 500)); + + if (isDoubleClickRef.current || controlsIsFocusRef.current) return; + + if (!showControlsIdRef.current || !removeShowControls()) addShowControls(); + }, + onClick: async () => { + console.log("click"); + isTouchRef.current = false; + + await new Promise(resolve => setTimeout(resolve, 300)); + + if (controlsIsFocusRef.current || isDoubleClickRef.current) return; + + controller.setIsPlaying(!controller.isPlaying); + }, + }); + + React.useEffect(() => addShowControls(), [controller.loaded]); + + const handleControlsFocus = useCallback(() => { + controlsIsFocusRef.current = true; + }, []); + + const handleControlsBlur = useCallback(() => { + controlsIsFocusRef.current = false; + }, []); + + const isDoubleClickTimeout = React.useRef<number>(); + const handleDoubleClick = useCallback(() => { + if (controlsIsFocusRef.current) return; + + isDoubleClickRef.current = true; + if (isDoubleClickTimeout.current) clearTimeout(isDoubleClickTimeout.current); + + isDoubleClickTimeout.current = setTimeout(() => (isDoubleClickRef.current = false), 1000); + controller.setFullscreen(!controller.isFullscreen); + }, []); + + const handleKeyDown = useCallback<React.KeyboardEventHandler>(e => { + const { key } = e; + + if (e.defaultPrevented) return; + + if (["ArrowLeft", "j"].includes(key)) { + controller.setCurrentTime(controller.currentTime - 10); + e.preventDefault(); + } else if (["ArrowRight", "l"].includes(key)) { + controller.setCurrentTime(controller.currentTime + 10); + e.preventDefault(); + } + if (key === "ArrowUp") { + controller.setVolume(Math.floor(Math.max(0, Math.min(10, controller.volume * 10 + 1))) / 10); + e.preventDefault(); + } else if (key === "ArrowDown") { + controller.setVolume(Math.floor(Math.max(0, Math.min(10, controller.volume * 10 - 1))) / 10); + e.preventDefault(); + } else if ([" ", "k"].includes(key)) { + controller.setIsPlaying(!controller.isPlaying); + e.preventDefault(); + } else if (key === "+") { + controller.setPlaybackRate(controller.playbackRate + 0.25); + e.preventDefault(); + } else if (key === "-") { + controller.setPlaybackRate(controller.playbackRate - 0.25); + e.preventDefault(); + } else if (key === "*") { + controller.setPlaybackRate(1); + e.preventDefault(); + } else if (["Home", "0"].includes(key)) { + controller.setCurrentTime(0); + e.preventDefault(); + } else if (key === "End") { + controller.setCurrentTime(controller.duration || 0); + e.preventDefault(); + } else if (key === "f") { + if (showControlsIdRef.current) removeShowControls(); + else addShowControls(); + + e.preventDefault(); + } else { + const number = Number(key); + + if (!Number.isNaN(number) && controller.duration) + controller.setCurrentTime(number * 10 * (controller.duration / 100)); + } + }, []); + + const overlayContainerRef = React.useRef<HTMLDivElement>(null); + + const qualities = + courseClass && courseClass.videos && courseClass.videos.length > 0 + ? courseClass.videos[0].qualities + : undefined; + + useEffect(() => { + controller.setVideoInstance(qualities ? videoRef.current : null); + controller.setVideoWrapperInstance(wrapperRef.current); + }, [qualities]); + + return ( + <OverlayContainerContext.Provider value={overlayContainerRef}> + <LayerProvider containerRef={overlayContainerRef}> + <styles.Wrapper + ref={wrapperRef} + className={props.className} + tabIndex={0} + fullscreen={controller.isFullscreen} + showCursor={!controller.showControls && controller.isFullscreen} + onTouchStart={handleTouch} + onClick={handleClick} + onMouseMove={handleMouseMove} + onDoubleClick={handleDoubleClick} + onKeyDown={handleKeyDown} + > + {qualities && <styles.Video videoRef={videoRef} quality={qualities[0]} />} + + {controller.loaded && <styles.Controls onFocus={handleControlsFocus} onBlur={handleControlsBlur} />} + {(!controller.loaded || controller.waiting || controller.seeking) && <styles.Loading />} + + <styles.OverlayContainer ref={overlayContainerRef} /> + </styles.Wrapper> + </LayerProvider> + </OverlayContainerContext.Provider> + ); +}); diff --git a/src/screens/course/components/courseclassplayer/components/controls/Controls.styles.ts b/src/screens/course/components/courseclassplayer/components/controls/Controls.styles.ts index 9694b486a8b20eab12fa1a1e611c83788e04805a..9111ecd8ce3519e47d94e689b15830b085f44700 100644 --- a/src/screens/course/components/courseclassplayer/components/controls/Controls.styles.ts +++ b/src/screens/course/components/courseclassplayer/components/controls/Controls.styles.ts @@ -1,87 +1,87 @@ -import { WiderThan } from "src/style/Mixins"; -import styled, { css } from "styled-components"; -import { PlaybackRateButton } from "../playbackratebutton/PlaybackRateButton"; -import { Track } from "../track/Track"; -import { VolumeButton } from "../volumebutton/VolumeButton"; - -export const styles = { - Wrapper: styled.div` - position: relative; - overflow: hidden; - - pointer-events: none; - - > * { - pointer-events: auto; - } - `, - - BottomControlsContainer: styled.div<{ show: boolean; fullscreen: boolean }>` - ${p => css` - margin: auto 0 0; - - background-color: rgba(255, 255, 255, 1); - box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); - - transition: box-shadow ease 0.3s, transform ease 0.3s, opacity ease 0.3s; - - outline: none; - - ${!p.show && - css` - box-shadow: 0 0 0 rgba(0, 0, 0, 0.3); - opacity: 0; - /* pointer-events: none; */ - `}; - - ${WiderThan( - "sm", - css` - ${!p.fullscreen && - css` - margin: auto 10px 10px; - - ${!p.show && - css` - opacity: 0; - `}; - `}; - ` - )}; - - ${WiderThan( - "lg", - css` - ${!p.fullscreen && - css` - margin: auto 30px 20px; - - ${!p.show && - css` - opacity: 0; - `}; - `}; - ` - )}; - `} - `, - - Track: styled(Track)``, - - ButtonsContainer: styled.div` - flex-direction: row; - `, - - LeftButtonsContainer: styled.div` - flex: 1; - flex-direction: row; - `, - - RightButtonsContainer: styled.div` - flex-direction: row; - `, - - PlaybackRateButton: styled(PlaybackRateButton)``, - - VolumeButton: styled(VolumeButton)``, -}; +import { WiderThan } from "src/style/Mixins"; +import styled, { css } from "styled-components"; +import { PlaybackRateButton } from "../playbackratebutton/PlaybackRateButton"; +import { Track } from "../track/Track"; +import { VolumeButton } from "../volumebutton/VolumeButton"; + +export const styles = { + Wrapper: styled.div` + position: relative; + overflow: hidden; + + pointer-events: none; + + > * { + pointer-events: auto; + } + `, + + BottomControlsContainer: styled.div<{ show: boolean; fullscreen: boolean }>` + ${p => css` + margin: auto 0 0; + + background-color: rgba(255, 255, 255, 1); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); + + transition: box-shadow ease 0.3s, transform ease 0.3s, opacity ease 0.3s; + + outline: none; + + ${!p.show && + css` + box-shadow: 0 0 0 rgba(0, 0, 0, 0.3); + opacity: 0; + /* pointer-events: none; */ + `}; + + ${WiderThan( + "sm", + css` + ${!p.fullscreen && + css` + margin: auto 10px 10px; + + ${!p.show && + css` + opacity: 0; + `}; + `}; + ` + )}; + + ${WiderThan( + "lg", + css` + ${!p.fullscreen && + css` + margin: auto 30px 20px; + + ${!p.show && + css` + opacity: 0; + `}; + `}; + ` + )}; + `} + `, + + Track: styled(Track)``, + + ButtonsContainer: styled.div` + flex-direction: row; + `, + + LeftButtonsContainer: styled.div` + flex: 1; + flex-direction: row; + `, + + RightButtonsContainer: styled.div` + flex-direction: row; + `, + + PlaybackRateButton: styled(PlaybackRateButton)``, + + VolumeButton: styled(VolumeButton)``, +}; diff --git a/src/screens/course/components/courseclassplayer/components/controls/Controls.tsx b/src/screens/course/components/courseclassplayer/components/controls/Controls.tsx index 7a30c0d29f5af8045328116c760bd2553f934460..ee853c35fd92f0ffbe3d1127d8c24d24a83bae30 100644 --- a/src/screens/course/components/courseclassplayer/components/controls/Controls.tsx +++ b/src/screens/course/components/courseclassplayer/components/controls/Controls.tsx @@ -1,65 +1,65 @@ -import { ControlsButton } from "../controlsbutton/ControlsButton"; -import React from "react"; -import { observer } from "mobx-react-lite"; -import { styles } from "./Controls.styles"; -import { useCallback } from "react"; -import { useCourse } from "src/screens/course/useCourse"; -import { useFocus } from "src/hooks"; - -export type ControlsProps = { - className?: string; - - onFocus: () => void; - onBlur: () => void; -}; - -export const Controls = observer<ControlsProps>(props => { - const courseClassController = useCourse().courseClassPlayerController; - - const handlePlayClick = useCallback(() => { - courseClassController.setIsPlaying(!courseClassController.isPlaying); - }, []); - - const handleFullscreenClick = useCallback(() => { - courseClassController.setFullscreen(!courseClassController.isFullscreen); - }, []); - - const { handleFocus, handleBlur } = useFocus({ onFocus: props.onFocus, onBlur: props.onBlur }, [ - props.onFocus, - props.onBlur, - ]); - - return ( - <styles.Wrapper className={props.className} onFocus={handleFocus} onBlur={handleBlur}> - <styles.BottomControlsContainer - tabIndex={-1} - show={courseClassController.showControls} - onClick={e => e.preventDefault()} - onDoubleClick={e => e.preventDefault()} - fullscreen={courseClassController.isFullscreen} - > - <styles.Track /> - - <styles.ButtonsContainer> - <styles.LeftButtonsContainer> - <ControlsButton - iconName={courseClassController.isPlaying ? "pause" : "play"} - onPress={handlePlayClick} - /> - - <styles.VolumeButton /> - </styles.LeftButtonsContainer> - - <styles.RightButtonsContainer> - <styles.PlaybackRateButton /> - - <ControlsButton - iconName={courseClassController.isFullscreen ? "exit_fullscreen" : "fullscreen"} - onPress={handleFullscreenClick} - /> - </styles.RightButtonsContainer> - </styles.ButtonsContainer> - </styles.BottomControlsContainer> - </styles.Wrapper> - ); -}); +import { ControlsButton } from "../controlsbutton/ControlsButton"; +import React from "react"; +import { observer } from "mobx-react-lite"; +import { styles } from "./Controls.styles"; +import { useCallback } from "react"; +import { useCourse } from "src/screens/course/useCourse"; +import { useFocus } from "src/hooks"; + +export type ControlsProps = { + className?: string; + + onFocus: () => void; + onBlur: () => void; +}; + +export const Controls = observer<ControlsProps>(props => { + const courseClassController = useCourse().courseClassPlayerController; + + const handlePlayClick = useCallback(() => { + courseClassController.setIsPlaying(!courseClassController.isPlaying); + }, []); + + const handleFullscreenClick = useCallback(() => { + courseClassController.setFullscreen(!courseClassController.isFullscreen); + }, []); + + const { handleFocus, handleBlur } = useFocus({ onFocus: props.onFocus, onBlur: props.onBlur }, [ + props.onFocus, + props.onBlur, + ]); + + return ( + <styles.Wrapper className={props.className} onFocus={handleFocus} onBlur={handleBlur}> + <styles.BottomControlsContainer + tabIndex={-1} + show={courseClassController.showControls} + onClick={e => e.preventDefault()} + onDoubleClick={e => e.preventDefault()} + fullscreen={courseClassController.isFullscreen} + > + <styles.Track /> + + <styles.ButtonsContainer> + <styles.LeftButtonsContainer> + <ControlsButton + iconName={courseClassController.isPlaying ? "pause" : "play"} + onPress={handlePlayClick} + /> + + <styles.VolumeButton /> + </styles.LeftButtonsContainer> + + <styles.RightButtonsContainer> + <styles.PlaybackRateButton /> + + <ControlsButton + iconName={courseClassController.isFullscreen ? "exit_fullscreen" : "fullscreen"} + onPress={handleFullscreenClick} + /> + </styles.RightButtonsContainer> + </styles.ButtonsContainer> + </styles.BottomControlsContainer> + </styles.Wrapper> + ); +}); diff --git a/src/screens/course/components/courseclassplayer/components/playbackratebutton/PlaybackRateButton.styles.tsx b/src/screens/course/components/courseclassplayer/components/playbackratebutton/PlaybackRateButton.styles.tsx index 0b721e6aad53b4e2d3753ae2ad59af264a1cb19d..ae896b94ea2d1084d4bd9cf4c4ab26017ff4e4d8 100644 --- a/src/screens/course/components/courseclassplayer/components/playbackratebutton/PlaybackRateButton.styles.tsx +++ b/src/screens/course/components/courseclassplayer/components/playbackratebutton/PlaybackRateButton.styles.tsx @@ -1,63 +1,63 @@ -import rgba from "polished/lib/color/rgba"; -import styled, { css } from "styled-components"; -import { BaseButton } from "../../../../../../components/basebutton/BaseButton"; -import { Mixins } from "../../../../../../style"; - -export const styles = { - Button: styled(BaseButton)` - align-items: stretch; - justify-content: center; - width: ${({ theme }) => theme.baseLineHeight}px; - height: ${({ theme }) => theme.baseLineHeight}px; - - font-size: 15px; - font-weight: bold; - - ${Mixins.OnHover(css` - background-color: ${rgba(0, 0, 0, 0.1)}; - `)} - `, - - PlaybackRateList: styled.div` - position: absolute; - align-self: center; - bottom: 0; - background-color: white; - box-shadow: 0 0 5px rgba(25, 25, 25, 0.3); - max-height: 200px; - width: 200px; - - overflow: auto; - `, - - PlaybackRateItem: styled(BaseButton)<{ active: boolean }>` - padding: 0 15px; - justify-content: center; - height: ${({ theme }) => theme.baseLineHeight}px; - - ${({ active, theme }) => css` - ${Mixins.OnHover(css` - background-color: ${rgba(0, 0, 0, 0.1)}; - `)} - - ${active && - css` - &, - &:focus { - background-color: ${theme.accentColor}; - color: white; - - ${Mixins.OnHover(css` - background-color: ${theme.accentColor}; - color: white; - `)} - } - - ${Mixins.OnHover(css` - background-color: ${theme.accentColor}; - color: white; - `)} - `} - `} - `, -}; +import rgba from "polished/lib/color/rgba"; +import styled, { css } from "styled-components"; +import { BaseButton } from "../../../../../../components/basebutton/BaseButton"; +import { Mixins } from "../../../../../../style"; + +export const styles = { + Button: styled(BaseButton)` + align-items: stretch; + justify-content: center; + width: ${({ theme }) => theme.baseLineHeight}px; + height: ${({ theme }) => theme.baseLineHeight}px; + + font-size: 15px; + font-weight: bold; + + ${Mixins.OnHover(css` + background-color: ${rgba(0, 0, 0, 0.1)}; + `)} + `, + + PlaybackRateList: styled.div` + position: absolute; + align-self: center; + bottom: 0; + background-color: white; + box-shadow: 0 0 5px rgba(25, 25, 25, 0.3); + max-height: 200px; + width: 200px; + + overflow: auto; + `, + + PlaybackRateItem: styled(BaseButton)<{ active: boolean }>` + padding: 0 15px; + justify-content: center; + height: ${({ theme }) => theme.baseLineHeight}px; + + ${({ active, theme }) => css` + ${Mixins.OnHover(css` + background-color: ${rgba(0, 0, 0, 0.1)}; + `)} + + ${active && + css` + &, + &:focus { + background-color: ${theme.accentColor}; + color: white; + + ${Mixins.OnHover(css` + background-color: ${theme.accentColor}; + color: white; + `)} + } + + ${Mixins.OnHover(css` + background-color: ${theme.accentColor}; + color: white; + `)} + `} + `} + `, +}; diff --git a/src/screens/course/components/courseclassplayer/components/playbackratebutton/PlaybackRateButton.tsx b/src/screens/course/components/courseclassplayer/components/playbackratebutton/PlaybackRateButton.tsx index 6364eb70fe54d8f4b5d55644de6a68604c60d1cb..b18841eb72d2d3437047eb91a8a3104f7aab66fb 100644 --- a/src/screens/course/components/courseclassplayer/components/playbackratebutton/PlaybackRateButton.tsx +++ b/src/screens/course/components/courseclassplayer/components/playbackratebutton/PlaybackRateButton.tsx @@ -1,87 +1,87 @@ -import { Overlay } from "src/components/overlay/Overlay"; -import React from "react"; -import { observer } from "mobx-react-lite"; -import { secondsToString } from "openfing-core/lib/helpers"; -import { styles } from "./PlaybackRateButton.styles"; -import { useCourse } from "src/screens/course/useCourse"; - -type PlaybackRateButtonProps = { - className?: string; -}; - -const playbackRates: number[] = []; - -for (let i = 8; i > 0; i--) { - playbackRates.push(i * 0.25); -} - -export const PlaybackRateButton: React.FunctionComponent<PlaybackRateButtonProps> = observer(({ className }) => { - const { courseClassPlayerController } = useCourse(); - const { playbackRate } = courseClassPlayerController; - const [overlayCoordinates, setOverlayCoordinates] = React.useState<{ top: number; left: number }>(); - - React.useEffect(() => { - const blockControlsId = "playback-rate"; - - if (overlayCoordinates) courseClassPlayerController.addBlockControls(blockControlsId); - else courseClassPlayerController.removeBlockControls(blockControlsId); - }, [!overlayCoordinates]); - - return ( - <> - <styles.Button - className={className} - onClick={e => { - const { top, left, width } = e.currentTarget.getBoundingClientRect(); - setOverlayCoordinates({ - top, - left: left + width / 2, - }); - }} - > - {Math.floor(playbackRate) === playbackRate ? playbackRate + ".0" : playbackRate}x - </styles.Button> - - {overlayCoordinates && ( - <Overlay - isVisible={true} - top={overlayCoordinates.top} - left={overlayCoordinates.left} - onDismiss={() => setOverlayCoordinates(undefined)} - > - <styles.PlaybackRateList> - {playbackRates.map((playbackRate, index) => ( - <styles.PlaybackRateItem - key={playbackRate} - autoFocus={index === 0} - active={courseClassPlayerController.playbackRate === playbackRate} - onClick={() => { - courseClassPlayerController.setPlaybackRate(playbackRate); - setOverlayCoordinates(undefined); - }} - > - <span - style={{ - display: "block", - textAlign: "left", - whiteSpace: "nowrap", - overflow: "hidden", - textOverflow: "ellipsis", - }} - > - <strong style={{ display: "inline", fontWeight: "bold" }}> - {Math.floor(playbackRate) === playbackRate ? playbackRate + ".0" : playbackRate} - x - </strong> - - {courseClassPlayerController.duration && - ` (${secondsToString(courseClassPlayerController.duration / playbackRate)})`} - </span> - </styles.PlaybackRateItem> - ))} - </styles.PlaybackRateList> - </Overlay> - )} - </> - ); -}); +import { Overlay } from "src/components/overlay/Overlay"; +import React from "react"; +import { observer } from "mobx-react-lite"; +import { secondsToString } from "openfing-core/lib/helpers"; +import { styles } from "./PlaybackRateButton.styles"; +import { useCourse } from "src/screens/course/useCourse"; + +type PlaybackRateButtonProps = { + className?: string; +}; + +const playbackRates: number[] = []; + +for (let i = 8; i > 0; i--) { + playbackRates.push(i * 0.25); +} + +export const PlaybackRateButton: React.FunctionComponent<PlaybackRateButtonProps> = observer(({ className }) => { + const { courseClassPlayerController } = useCourse(); + const { playbackRate } = courseClassPlayerController; + const [overlayCoordinates, setOverlayCoordinates] = React.useState<{ top: number; left: number }>(); + + React.useEffect(() => { + const blockControlsId = "playback-rate"; + + if (overlayCoordinates) courseClassPlayerController.addBlockControls(blockControlsId); + else courseClassPlayerController.removeBlockControls(blockControlsId); + }, [!overlayCoordinates]); + + return ( + <> + <styles.Button + className={className} + onClick={e => { + const { top, left, width } = e.currentTarget.getBoundingClientRect(); + setOverlayCoordinates({ + top, + left: left + width / 2, + }); + }} + > + {Math.floor(playbackRate) === playbackRate ? playbackRate + ".0" : playbackRate}x + </styles.Button> + + {overlayCoordinates && ( + <Overlay + isVisible={true} + top={overlayCoordinates.top} + left={overlayCoordinates.left} + onDismiss={() => setOverlayCoordinates(undefined)} + > + <styles.PlaybackRateList> + {playbackRates.map((playbackRate, index) => ( + <styles.PlaybackRateItem + key={playbackRate} + autoFocus={index === 0} + active={courseClassPlayerController.playbackRate === playbackRate} + onClick={() => { + courseClassPlayerController.setPlaybackRate(playbackRate); + setOverlayCoordinates(undefined); + }} + > + <span + style={{ + display: "block", + textAlign: "left", + whiteSpace: "nowrap", + overflow: "hidden", + textOverflow: "ellipsis", + }} + > + <strong style={{ display: "inline", fontWeight: "bold" }}> + {Math.floor(playbackRate) === playbackRate ? playbackRate + ".0" : playbackRate} + x + </strong> + + {courseClassPlayerController.duration && + ` (${secondsToString(courseClassPlayerController.duration / playbackRate)})`} + </span> + </styles.PlaybackRateItem> + ))} + </styles.PlaybackRateList> + </Overlay> + )} + </> + ); +}); diff --git a/src/screens/course/components/courseclassplayer/components/track/Track.styles.ts b/src/screens/course/components/courseclassplayer/components/track/Track.styles.ts index 132ff7eaa57402cb6073f9acf4714d9b6f9f60ce..25d0f53e7009f719a44c82c39b393921c76b4830 100644 --- a/src/screens/course/components/courseclassplayer/components/track/Track.styles.ts +++ b/src/screens/course/components/courseclassplayer/components/track/Track.styles.ts @@ -1,92 +1,92 @@ -import rgba from "polished/lib/color/rgba"; -import { BaseButton } from "src/components/basebutton/BaseButton"; -import { Layer } from "src/components/layer/Layer"; -import styled, { css } from "styled-components"; - -export const styles = { - Wrapper: styled.div` - height: 50px; - justify-content: center; - padding: 0 10px; - `, - - PointerTracker: styled.div` - justify-content: center; - height: 15px; - - cursor: pointer; - `, - - BackgroundTrack: styled.div` - position: relative; - height: 5px; - flex-direction: row; - align-items: stretch; - - background-color: ${rgba(0, 0, 0, 0.15)}; - `, - - BufferedTrack: styled.div` - position: absolute; - height: 5px; - - background-color: ${rgba(0, 0, 0, 0.2)}; - `, - - ProgressTrack: styled.div` - position: absolute; - height: 5px; - - background-color: ${p => p.theme.accentColor}; - `, - - ProgressIndicatorContainer: styled.div` - align-items: center; - justify-content: center; - position: relative; - `, - - ProgressIndicator: styled.div<{ visible: boolean }>` - position: absolute; - background-color: ${p => p.theme.accentColor}; - width: 0; - height: 0; - transition: width ease-in 0.1s, height ease-in 0.1s; - border-radius: 7px; - box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); - - ${({ visible }) => - visible && - css` - width: 14px; - height: 14px; - `}; - `, - - CurrentTrackedTimeLayer: styled(Layer)` - height: ${p => p.theme.rowHeight / 2}px; - padding: 0 10px; - justify-content: center; - - background-color: white; - - box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); - `, - - CurrentTrackedTime: styled.span``, - - TimeWrapper: styled.div` - flex-direction: row; - justify-content: space-between; - margin-top: 8px; - `, - - CurrentTime: styled.span` - font-size: 12px; - line-height: normal; - `, - - Duration: styled(BaseButton)` - font-size: 12px; - `, -}; +import rgba from "polished/lib/color/rgba"; +import { BaseButton } from "src/components/basebutton/BaseButton"; +import { Layer } from "src/components/layer/Layer"; +import styled, { css } from "styled-components"; + +export const styles = { + Wrapper: styled.div` + height: 50px; + justify-content: center; + padding: 0 10px; + `, + + PointerTracker: styled.div` + justify-content: center; + height: 15px; + + cursor: pointer; + `, + + BackgroundTrack: styled.div` + position: relative; + height: 5px; + flex-direction: row; + align-items: stretch; + + background-color: ${rgba(0, 0, 0, 0.15)}; + `, + + BufferedTrack: styled.div` + position: absolute; + height: 5px; + + background-color: ${rgba(0, 0, 0, 0.2)}; + `, + + ProgressTrack: styled.div` + position: absolute; + height: 5px; + + background-color: ${p => p.theme.accentColor}; + `, + + ProgressIndicatorContainer: styled.div` + align-items: center; + justify-content: center; + position: relative; + `, + + ProgressIndicator: styled.div<{ visible: boolean }>` + position: absolute; + background-color: ${p => p.theme.accentColor}; + width: 0; + height: 0; + transition: width ease-in 0.1s, height ease-in 0.1s; + border-radius: 7px; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); + + ${({ visible }) => + visible && + css` + width: 14px; + height: 14px; + `}; + `, + + CurrentTrackedTimeLayer: styled(Layer)` + height: ${p => p.theme.rowHeight / 2}px; + padding: 0 10px; + justify-content: center; + + background-color: white; + + box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); + `, + + CurrentTrackedTime: styled.span``, + + TimeWrapper: styled.div` + flex-direction: row; + justify-content: space-between; + margin-top: 8px; + `, + + CurrentTime: styled.span` + font-size: 12px; + line-height: normal; + `, + + Duration: styled(BaseButton)` + font-size: 12px; + `, +}; diff --git a/src/screens/course/components/courseclassplayer/components/track/Track.tsx b/src/screens/course/components/courseclassplayer/components/track/Track.tsx index 554b45f66d58b0f97552f2f28420f7c0b145e876..e6bd03d73f67a1614c616e76ff9f402627fcbbc6 100644 --- a/src/screens/course/components/courseclassplayer/components/track/Track.tsx +++ b/src/screens/course/components/courseclassplayer/components/track/Track.tsx @@ -1,226 +1,226 @@ -import { useCallback, useEffect, useMemo, useRef } from "react"; - -import { LayerProps } from "src/components/layer/Layer"; -import React from "react"; -import { getValidPercentage } from "src/helper"; -import { observer } from "mobx-react-lite"; -import { secondsToString } from "openfing-core/lib/helpers"; -import { styles } from "./Track.styles"; -import { useCourse } from "src/screens/course/useCourse"; -import { useSetState } from "src/hooks/useSetState"; - -export type TrackProps = { - className?: string; -}; - -type TrackState = { - trackingPointer: boolean; - isClicking: boolean; - positionPercentage: number; -}; - -export const Track = observer<TrackProps>(({ className }) => { - const controller = useCourse().courseClassPlayerController; - - const backgroundTrackRef = useRef<HTMLDivElement>(null); - const wrapperRef = useRef<HTMLDivElement>(null); - - const [state, dispatch] = useSetState<TrackState>({ - trackingPointer: false, - isClicking: false, - positionPercentage: 0, - }); - - const lastPositionRef = useRef(0); - const handleMouseEnter: React.MouseEventHandler = useCallback<React.MouseEventHandler>(e => { - const { clientX } = e; - - if (!backgroundTrackRef.current) return; - const { left, right } = backgroundTrackRef.current.getBoundingClientRect(); - const positionPercentage = getValidPercentage(clientX, left, right); - lastPositionRef.current = positionPercentage; - dispatch({ positionPercentage, trackingPointer: true }); - }, []); - - const handleTouchStart: React.TouchEventHandler = useCallback(() => { - wrapperRef.current!.focus(); - dispatch({ trackingPointer: true, isClicking: true }); - }, []); - - const handleMouseDown: React.MouseEventHandler = useCallback(e => { - wrapperRef.current!.focus(); - const wrapperBounds = wrapperRef.current!.getBoundingClientRect(); - const positionPercentage = getValidPercentage(e.clientX, wrapperBounds.left, wrapperBounds.right); - lastPositionRef.current = positionPercentage; - - dispatch({ - trackingPointer: true, - isClicking: true, - positionPercentage, - }); - }, []); - - const handleMouseLeave: React.MouseEventHandler = useCallback(() => { - if (!state.isClicking) dispatch({ trackingPointer: false, isClicking: false }); - }, [state.isClicking]); - - const touchMoveRef = useRef<number | undefined>(); - - const handleTouchEnd = () => { - controller.setCurrentTime((lastPositionRef.current * controller.duration) / 100); - dispatch({ trackingPointer: false, isClicking: false }); - }; - - const handleMouseUp = () => { - controller.setCurrentTime((lastPositionRef.current * controller.duration) / 100); - dispatch({ trackingPointer: false, isClicking: false }); - }; - - useEffect(() => { - const handleMouseMove = (e: MouseEvent) => { - const { clientX } = e; - - if (!backgroundTrackRef.current) return; - const { left, right } = backgroundTrackRef.current.getBoundingClientRect(); - const positionPercentage = getValidPercentage(clientX, left, right); - lastPositionRef.current = positionPercentage; - dispatch({ positionPercentage }); - }; - - const handleTouchMove = (e: TouchEvent) => { - const { clientX } = e.touches[0]; - clearTimeout(touchMoveRef.current); - - if (!backgroundTrackRef.current) return; - const { left, right } = backgroundTrackRef.current.getBoundingClientRect(); - - const positionPercentage = getValidPercentage(clientX, left, right); - lastPositionRef.current = positionPercentage; - dispatch({ positionPercentage, isClicking: true }); - }; - - const handleTouchCancel = () => { - controller.setCurrentTime((lastPositionRef.current * controller.duration) / 100); - dispatch({ trackingPointer: false, isClicking: false }); - }; - - if (state.trackingPointer) { - window.addEventListener("mouseup", handleMouseUp); - window.addEventListener("mousemove", handleMouseMove); - window.addEventListener("touchmove", handleTouchMove); - window.addEventListener("touchend", handleTouchEnd); - window.addEventListener("touchcancel", handleTouchCancel); - } - - return () => { - window.removeEventListener("mouseup", handleMouseUp); - window.removeEventListener("mousemove", handleMouseMove); - window.removeEventListener("touchmove", handleTouchMove); - window.removeEventListener("touchend", handleTouchEnd); - window.removeEventListener("touchcancel", handleTouchCancel); - }; - }, [state.trackingPointer, controller]); - - const currentTimePercentage = useMemo(() => (controller.currentTime * 100) / controller.duration || 0, [ - controller.currentTime, - controller.duration, - ]); - const progressPercentage = - state.trackingPointer && state.isClicking ? state.positionPercentage : currentTimePercentage; - - const calculateCurrentTrackedTimeLayerPosition = React.useCallback<LayerProps["calculatePosition"]>( - (layersWrapper, layer) => { - const layersWrapperBounds = layersWrapper.getBoundingClientRect(); - const layerBouds = layer.getBoundingClientRect(); - const backgroundBounds = backgroundTrackRef.current && backgroundTrackRef.current.getBoundingClientRect(); - - const pointerLeftRelative = backgroundBounds - ? backgroundBounds.left - - layersWrapperBounds.left + - backgroundBounds.width * (state.positionPercentage / 100) - - layersWrapperBounds.left - : 0; - - const bottom = backgroundBounds ? layersWrapperBounds.height - backgroundBounds.top + 20 : 0; - - return pointerLeftRelative > layersWrapperBounds.width / 2 - ? { - bottom, - right: Math.max(0, layersWrapperBounds.width - pointerLeftRelative - layerBouds.width / 2), - } - : { - bottom, - left: Math.max(0, pointerLeftRelative - layerBouds.width / 2), - }; - }, - [state.positionPercentage] - ); - - const updateCurrentTrackedTimeLayerPositionRef = React.useRef<() => boolean>(null); - - React.useEffect(() => { - if (state.trackingPointer && updateCurrentTrackedTimeLayerPositionRef.current) - updateCurrentTrackedTimeLayerPositionRef.current(); - }, [state.trackingPointer, state.positionPercentage]); - - React.useEffect(() => { - if (state.trackingPointer) controller.addBlockControls("track"); - else controller.removeBlockControls("track"); - }, [state.trackingPointer]); - - const [showTimeLeft, setShowTimeLeft] = React.useState(false); - const handleTimeLeftClick = React.useCallback(() => setShowTimeLeft(value => !value), []); - - return ( - <styles.Wrapper ref={wrapperRef} className={className}> - <styles.PointerTracker - tabIndex={-1} - onMouseEnter={handleMouseEnter} - onTouchStart={handleTouchStart} - onMouseDown={handleMouseDown} - onMouseLeave={handleMouseLeave} - onMouseUp={handleMouseUp} - onTouchEnd={handleTouchEnd} - onTouchCancel={handleTouchEnd} - > - <styles.BackgroundTrack ref={backgroundTrackRef}> - <styles.BufferedTrack style={{ width: `${controller.loadedPercentage}%` }} /> - <styles.ProgressTrack style={{ width: `${progressPercentage}%` }} /> - - {state.trackingPointer && controller.duration && ( - <styles.CurrentTrackedTimeLayer - calculatePosition={calculateCurrentTrackedTimeLayerPosition} - updatePosition={updateCurrentTrackedTimeLayerPositionRef} - > - <styles.CurrentTrackedTime> - {secondsToString((state.positionPercentage * controller.duration) / 100)} - </styles.CurrentTrackedTime> - </styles.CurrentTrackedTimeLayer> - )} - - <styles.ProgressIndicatorContainer - style={{ - left: `${progressPercentage}%`, - }} - > - <styles.ProgressIndicator visible={state.trackingPointer} /> - </styles.ProgressIndicatorContainer> - </styles.BackgroundTrack> - </styles.PointerTracker> - - <styles.TimeWrapper> - <styles.CurrentTime> - {!Number.isNaN(controller.currentTime) ? secondsToString(controller.currentTime) : null} - </styles.CurrentTime> - - <styles.Duration onClick={handleTimeLeftClick}> - {controller.duration && !Number.isNaN(controller.currentTime) - ? showTimeLeft - ? secondsToString(controller.duration - controller.currentTime) - : secondsToString(controller.duration) - : null} - </styles.Duration> - </styles.TimeWrapper> - </styles.Wrapper> - ); -}); +import { useCallback, useEffect, useMemo, useRef } from "react"; + +import { LayerProps } from "src/components/layer/Layer"; +import React from "react"; +import { getValidPercentage } from "src/helper"; +import { observer } from "mobx-react-lite"; +import { secondsToString } from "openfing-core/lib/helpers"; +import { styles } from "./Track.styles"; +import { useCourse } from "src/screens/course/useCourse"; +import { useSetState } from "src/hooks/useSetState"; + +export type TrackProps = { + className?: string; +}; + +type TrackState = { + trackingPointer: boolean; + isClicking: boolean; + positionPercentage: number; +}; + +export const Track = observer<TrackProps>(({ className }) => { + const controller = useCourse().courseClassPlayerController; + + const backgroundTrackRef = useRef<HTMLDivElement>(null); + const wrapperRef = useRef<HTMLDivElement>(null); + + const [state, dispatch] = useSetState<TrackState>({ + trackingPointer: false, + isClicking: false, + positionPercentage: 0, + }); + + const lastPositionRef = useRef(0); + const handleMouseEnter: React.MouseEventHandler = useCallback<React.MouseEventHandler>(e => { + const { clientX } = e; + + if (!backgroundTrackRef.current) return; + const { left, right } = backgroundTrackRef.current.getBoundingClientRect(); + const positionPercentage = getValidPercentage(clientX, left, right); + lastPositionRef.current = positionPercentage; + dispatch({ positionPercentage, trackingPointer: true }); + }, []); + + const handleTouchStart: React.TouchEventHandler = useCallback(() => { + wrapperRef.current!.focus(); + dispatch({ trackingPointer: true, isClicking: true }); + }, []); + + const handleMouseDown: React.MouseEventHandler = useCallback(e => { + wrapperRef.current!.focus(); + const wrapperBounds = wrapperRef.current!.getBoundingClientRect(); + const positionPercentage = getValidPercentage(e.clientX, wrapperBounds.left, wrapperBounds.right); + lastPositionRef.current = positionPercentage; + + dispatch({ + trackingPointer: true, + isClicking: true, + positionPercentage, + }); + }, []); + + const handleMouseLeave: React.MouseEventHandler = useCallback(() => { + if (!state.isClicking) dispatch({ trackingPointer: false, isClicking: false }); + }, [state.isClicking]); + + const touchMoveRef = useRef<number | undefined>(); + + const handleTouchEnd = () => { + controller.setCurrentTime((lastPositionRef.current * controller.duration) / 100); + dispatch({ trackingPointer: false, isClicking: false }); + }; + + const handleMouseUp = () => { + controller.setCurrentTime((lastPositionRef.current * controller.duration) / 100); + dispatch({ trackingPointer: false, isClicking: false }); + }; + + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + const { clientX } = e; + + if (!backgroundTrackRef.current) return; + const { left, right } = backgroundTrackRef.current.getBoundingClientRect(); + const positionPercentage = getValidPercentage(clientX, left, right); + lastPositionRef.current = positionPercentage; + dispatch({ positionPercentage }); + }; + + const handleTouchMove = (e: TouchEvent) => { + const { clientX } = e.touches[0]; + clearTimeout(touchMoveRef.current); + + if (!backgroundTrackRef.current) return; + const { left, right } = backgroundTrackRef.current.getBoundingClientRect(); + + const positionPercentage = getValidPercentage(clientX, left, right); + lastPositionRef.current = positionPercentage; + dispatch({ positionPercentage, isClicking: true }); + }; + + const handleTouchCancel = () => { + controller.setCurrentTime((lastPositionRef.current * controller.duration) / 100); + dispatch({ trackingPointer: false, isClicking: false }); + }; + + if (state.trackingPointer) { + window.addEventListener("mouseup", handleMouseUp); + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("touchmove", handleTouchMove); + window.addEventListener("touchend", handleTouchEnd); + window.addEventListener("touchcancel", handleTouchCancel); + } + + return () => { + window.removeEventListener("mouseup", handleMouseUp); + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("touchmove", handleTouchMove); + window.removeEventListener("touchend", handleTouchEnd); + window.removeEventListener("touchcancel", handleTouchCancel); + }; + }, [state.trackingPointer, controller]); + + const currentTimePercentage = useMemo(() => (controller.currentTime * 100) / controller.duration || 0, [ + controller.currentTime, + controller.duration, + ]); + const progressPercentage = + state.trackingPointer && state.isClicking ? state.positionPercentage : currentTimePercentage; + + const calculateCurrentTrackedTimeLayerPosition = React.useCallback<LayerProps["calculatePosition"]>( + (layersWrapper, layer) => { + const layersWrapperBounds = layersWrapper.getBoundingClientRect(); + const layerBouds = layer.getBoundingClientRect(); + const backgroundBounds = backgroundTrackRef.current && backgroundTrackRef.current.getBoundingClientRect(); + + const pointerLeftRelative = backgroundBounds + ? backgroundBounds.left - + layersWrapperBounds.left + + backgroundBounds.width * (state.positionPercentage / 100) - + layersWrapperBounds.left + : 0; + + const bottom = backgroundBounds ? layersWrapperBounds.height - backgroundBounds.top + 20 : 0; + + return pointerLeftRelative > layersWrapperBounds.width / 2 + ? { + bottom, + right: Math.max(0, layersWrapperBounds.width - pointerLeftRelative - layerBouds.width / 2), + } + : { + bottom, + left: Math.max(0, pointerLeftRelative - layerBouds.width / 2), + }; + }, + [state.positionPercentage] + ); + + const updateCurrentTrackedTimeLayerPositionRef = React.useRef<() => boolean>(null); + + React.useEffect(() => { + if (state.trackingPointer && updateCurrentTrackedTimeLayerPositionRef.current) + updateCurrentTrackedTimeLayerPositionRef.current(); + }, [state.trackingPointer, state.positionPercentage]); + + React.useEffect(() => { + if (state.trackingPointer) controller.addBlockControls("track"); + else controller.removeBlockControls("track"); + }, [state.trackingPointer]); + + const [showTimeLeft, setShowTimeLeft] = React.useState(false); + const handleTimeLeftClick = React.useCallback(() => setShowTimeLeft(value => !value), []); + + return ( + <styles.Wrapper ref={wrapperRef} className={className}> + <styles.PointerTracker + tabIndex={-1} + onMouseEnter={handleMouseEnter} + onTouchStart={handleTouchStart} + onMouseDown={handleMouseDown} + onMouseLeave={handleMouseLeave} + onMouseUp={handleMouseUp} + onTouchEnd={handleTouchEnd} + onTouchCancel={handleTouchEnd} + > + <styles.BackgroundTrack ref={backgroundTrackRef}> + <styles.BufferedTrack style={{ width: `${controller.loadedPercentage}%` }} /> + <styles.ProgressTrack style={{ width: `${progressPercentage}%` }} /> + + {state.trackingPointer && controller.duration && ( + <styles.CurrentTrackedTimeLayer + calculatePosition={calculateCurrentTrackedTimeLayerPosition} + updatePosition={updateCurrentTrackedTimeLayerPositionRef} + > + <styles.CurrentTrackedTime> + {secondsToString((state.positionPercentage * controller.duration) / 100)} + </styles.CurrentTrackedTime> + </styles.CurrentTrackedTimeLayer> + )} + + <styles.ProgressIndicatorContainer + style={{ + left: `${progressPercentage}%`, + }} + > + <styles.ProgressIndicator visible={state.trackingPointer} /> + </styles.ProgressIndicatorContainer> + </styles.BackgroundTrack> + </styles.PointerTracker> + + <styles.TimeWrapper> + <styles.CurrentTime> + {!Number.isNaN(controller.currentTime) ? secondsToString(controller.currentTime) : null} + </styles.CurrentTime> + + <styles.Duration onClick={handleTimeLeftClick}> + {controller.duration && !Number.isNaN(controller.currentTime) + ? showTimeLeft + ? secondsToString(controller.duration - controller.currentTime) + : secondsToString(controller.duration) + : null} + </styles.Duration> + </styles.TimeWrapper> + </styles.Wrapper> + ); +}); diff --git a/src/screens/course/components/courseclassplayer/components/track/__stories__/Track.stories.tsx b/src/screens/course/components/courseclassplayer/components/track/__stories__/Track.stories.tsx index a679e41187f04983291b602464ea76242d74a40b..c637b8cab2c583e2f3bc8d21b3fad543985c0a49 100644 --- a/src/screens/course/components/courseclassplayer/components/track/__stories__/Track.stories.tsx +++ b/src/screens/course/components/courseclassplayer/components/track/__stories__/Track.stories.tsx @@ -1,28 +1,28 @@ -import { CourseContext, CourseProvider } from "src/screens/course/useCourse"; - -import React from "react"; -import { Track } from "../Track"; -import { storiesOf } from "@storybook/react"; -import styled from "styled-components"; - -const Default = () => { - const contextRef = React.useRef<CourseContext>(new CourseContext()); - const StyledTrack = React.useRef( - styled(Track)` - margin-top: auto; - ` - ); - - React.useEffect(() => { - contextRef.current.courseClassPlayerController["_duration"] = 20000; - contextRef.current.courseClassPlayerController["_currentTime"] = 2000; - }, []); - - return ( - <CourseProvider contextRef={contextRef}> - <StyledTrack.current /> - </CourseProvider> - ); -}; - -storiesOf("Track", module).add("Default", () => <Default />); +import { CourseContext, CourseProvider } from "src/screens/course/useCourse"; + +import React from "react"; +import { Track } from "../Track"; +import { storiesOf } from "@storybook/react"; +import styled from "styled-components"; + +const Default = () => { + const contextRef = React.useRef<CourseContext>(new CourseContext()); + const StyledTrack = React.useRef( + styled(Track)` + margin-top: auto; + ` + ); + + React.useEffect(() => { + contextRef.current.courseClassPlayerController["_duration"] = 20000; + contextRef.current.courseClassPlayerController["_currentTime"] = 2000; + }, []); + + return ( + <CourseProvider contextRef={contextRef}> + <StyledTrack.current /> + </CourseProvider> + ); +}; + +storiesOf("Track", module).add("Default", () => <Default />); diff --git a/src/screens/course/components/courseclassplayer/components/volumebutton/VolumeButton.styles.tsx b/src/screens/course/components/courseclassplayer/components/volumebutton/VolumeButton.styles.tsx index b78fdc348c69e741e16ba80e5d6e75723f89e2c3..dcc0329f2c19bd0cf528ac9209017d9a3d305a3c 100644 --- a/src/screens/course/components/courseclassplayer/components/volumebutton/VolumeButton.styles.tsx +++ b/src/screens/course/components/courseclassplayer/components/volumebutton/VolumeButton.styles.tsx @@ -1,79 +1,79 @@ -import rgba from "polished/lib/color/rgba"; -import { Icon } from "src/components/icon/Icon"; -import { Layer } from "src/components/layer/Layer"; -import styled, { css } from "styled-components"; -import { BaseButton } from "../../../../../../components/basebutton/BaseButton"; -import { Mixins } from "../../../../../../style"; - -export const styles = { - Button: styled(BaseButton)` - align-items: center; - justify-content: center; - width: ${({ theme }) => theme.baseLineHeight}px; - height: ${({ theme }) => theme.baseLineHeight}px; - - font-size: 15px; - font-weight: bold; - - ${Mixins.OnHover(css` - background-color: ${rgba(0, 0, 0, 0.1)}; - `)} - `, - - Icon: styled(Icon)` - width: 30px; - height: 30px; - `, - - Layer: styled(Layer)``, - - LayerWrapper: styled.div` - width: ${({ theme }) => theme.rowHeight}px; - height: ${({ theme }) => theme.rowHeight * 3}px; - align-items: center; - - background-color: white; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); - - overscroll-behavior: contain; - `, - - SliderWrapper: styled.div` - margin-top: 15px; - margin-bottom: 15px; - flex: 1; - width: 10px; - padding-right: 3px; - padding-left: 3px; - - cursor: pointer; - `, - - SliderTrack: styled.div` - flex: 1; - width: 4px; - - background-color: rgba(0, 0, 0, 0.15); - `, - - SliderProgress: styled.div` - margin-top: auto; - background-color: ${({ theme }) => theme.accentColor}; - align-items: center; - `, - - SliderThumbContainer: styled.div` - height: 0; - width: 0; - position: relative; - align-items: center; - justify-content: center; - `, - - SliderThumb: styled.div` - width: 14px; - height: 14px; - border-radius: 7px; - background-color: ${({ theme }) => theme.accentColor}; - `, -}; +import rgba from "polished/lib/color/rgba"; +import { Icon } from "src/components/icon/Icon"; +import { Layer } from "src/components/layer/Layer"; +import styled, { css } from "styled-components"; +import { BaseButton } from "../../../../../../components/basebutton/BaseButton"; +import { Mixins } from "../../../../../../style"; + +export const styles = { + Button: styled(BaseButton)` + align-items: center; + justify-content: center; + width: ${({ theme }) => theme.baseLineHeight}px; + height: ${({ theme }) => theme.baseLineHeight}px; + + font-size: 15px; + font-weight: bold; + + ${Mixins.OnHover(css` + background-color: ${rgba(0, 0, 0, 0.1)}; + `)} + `, + + Icon: styled(Icon)` + width: 30px; + height: 30px; + `, + + Layer: styled(Layer)``, + + LayerWrapper: styled.div` + width: ${({ theme }) => theme.rowHeight}px; + height: ${({ theme }) => theme.rowHeight * 3}px; + align-items: center; + + background-color: white; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); + + overscroll-behavior: contain; + `, + + SliderWrapper: styled.div` + margin-top: 15px; + margin-bottom: 15px; + flex: 1; + width: 10px; + padding-right: 3px; + padding-left: 3px; + + cursor: pointer; + `, + + SliderTrack: styled.div` + flex: 1; + width: 4px; + + background-color: rgba(0, 0, 0, 0.15); + `, + + SliderProgress: styled.div` + margin-top: auto; + background-color: ${({ theme }) => theme.accentColor}; + align-items: center; + `, + + SliderThumbContainer: styled.div` + height: 0; + width: 0; + position: relative; + align-items: center; + justify-content: center; + `, + + SliderThumb: styled.div` + width: 14px; + height: 14px; + border-radius: 7px; + background-color: ${({ theme }) => theme.accentColor}; + `, +}; diff --git a/src/screens/course/components/courseclassplayer/components/volumebutton/VolumeButton.tsx b/src/screens/course/components/courseclassplayer/components/volumebutton/VolumeButton.tsx index 067ececa44ddde4c67d173895eafc9f4c289c73b..9731628c1645390409e3f101df675f6a47f837d0 100644 --- a/src/screens/course/components/courseclassplayer/components/volumebutton/VolumeButton.tsx +++ b/src/screens/course/components/courseclassplayer/components/volumebutton/VolumeButton.tsx @@ -1,210 +1,210 @@ -import { AppStoreContext } from "src/components/AppStoreContext"; -import React from "react"; -import { getValidPercentage } from "src/helper"; -import { observer } from "mobx-react-lite"; -import { styles } from "./VolumeButton.styles"; -import { useCourse } from "src/screens/course/useCourse"; -import { useLayerProps } from "src/hooks/useLayerProps"; - -type VolumeButtonProps = { - className?: string; -}; - -type VolumeButtonState = { - trackingPointer: boolean; - isClicking: boolean; - positionPercentage: number; -}; - -export const VolumeButton: React.FunctionComponent<VolumeButtonProps> = observer(({ className }) => { - const controller = useCourse().courseClassPlayerController; - const { inputType } = React.useContext(AppStoreContext); - const buttonRef = React.useRef<HTMLButtonElement>(null); - const sliderRef = React.useRef<HTMLDivElement>(null); - - const [state, setState] = React.useState<VolumeButtonState>({ - trackingPointer: false, - isClicking: false, - positionPercentage: 0, - }); - const lastPositionRef = React.useRef(0); - - const dispatch = (state: Partial<VolumeButtonState>) => setState(prevState => ({ ...prevState, ...state })); - - const handleTouchStart: React.TouchEventHandler = React.useCallback(e => { - sliderRef.current!.focus(); - - e.preventDefault(); - dispatch({ trackingPointer: true, isClicking: true }); - }, []); - - const handleMouseDown: React.MouseEventHandler = React.useCallback(e => { - sliderRef.current!.focus(); - dispatch({ trackingPointer: true }); - const wrapperBounds = sliderRef.current!.getBoundingClientRect(); - - const positionPercentage = getValidPercentage( - e.clientY, - wrapperBounds.top + wrapperBounds.height, - wrapperBounds.top - ); - lastPositionRef.current = positionPercentage; - - dispatch({ - trackingPointer: true, - isClicking: true, - positionPercentage, - }); - }, []); - - const handleMouseLeave: React.MouseEventHandler = React.useCallback(() => { - if (!state.isClicking) dispatch({ trackingPointer: false, isClicking: false }); - }, [state.isClicking]); - - const handleMouseUp = () => { - controller.setVolume(lastPositionRef.current / 100); - dispatch({ trackingPointer: false, isClicking: false }); - }; - - const handleTouchEnd = () => { - controller.setVolume(lastPositionRef.current / 100); - dispatch({ trackingPointer: false, isClicking: false }); - }; - - const progressPercentage = - state.trackingPointer && state.isClicking ? state.positionPercentage : controller.volume * 100; - - const [shouldRender, layerProps, buttonProps] = useLayerProps( - { - behaviour: "hover", - position: "top", - separation: 10, - triggerRef: buttonRef, - }, - [progressPercentage] - ); - const { courseClassPlayerController } = useCourse(); - - const lastVolumeRef = React.useRef(courseClassPlayerController.volume); // TODO: move to controller so m key can be used - - if (courseClassPlayerController.volume && !state.trackingPointer) - lastVolumeRef.current = courseClassPlayerController.volume; - - const handleClick = React.useCallback<React.MouseEventHandler>( - e => { - if (inputType !== "pointer") return; - - e.preventDefault(); - courseClassPlayerController.volume === 0 - ? courseClassPlayerController.setVolume(lastVolumeRef.current || 1) - : courseClassPlayerController.setVolume(0); - }, - [inputType] - ); - - // TODO: mejorar BaseButton y pasar el focus al video en clic - - React.useEffect(() => { - const handleMouseMove = (e: MouseEvent) => { - const { clientY } = e; - - if (!sliderRef.current) return; - const { top, height } = sliderRef.current.getBoundingClientRect(); - const positionPercentage = getValidPercentage(clientY, top + height, top); - lastPositionRef.current = positionPercentage; - controller.setVolume(lastPositionRef.current / 100); - dispatch({ positionPercentage }); - }; - - const handleTouchMove = (e: TouchEvent) => { - const { clientY } = e.touches[0]; - - e.preventDefault(); - - if (!sliderRef.current) return; - const { top, height } = sliderRef.current.getBoundingClientRect(); - - const positionPercentage = getValidPercentage(clientY, top + height, top); - lastPositionRef.current = positionPercentage; - controller.setVolume(lastPositionRef.current / 100); - dispatch({ positionPercentage, isClicking: true }); - }; - - const handleTouchCancel = () => { - controller.setVolume(lastPositionRef.current / 100); - dispatch({ trackingPointer: false, isClicking: false }); - }; - - if (state.trackingPointer) { - window.addEventListener("mouseup", handleMouseUp); - window.addEventListener("mousemove", handleMouseMove); - window.addEventListener("touchmove", handleTouchMove, { passive: false }); - window.addEventListener("touchend", handleTouchEnd); - window.addEventListener("touchcancel", handleTouchCancel); - } - - return () => { - window.removeEventListener("mouseup", handleMouseUp); - window.removeEventListener("mousemove", handleMouseMove); - window.removeEventListener("touchmove", handleTouchMove); - window.removeEventListener("touchend", handleTouchEnd); - window.removeEventListener("touchcancel", handleTouchCancel); - }; - }, [state.trackingPointer, controller]); - - React.useEffect(() => { - const blockControlsId = "volume-button"; - - if (shouldRender) courseClassPlayerController.addBlockControls(blockControlsId); - else courseClassPlayerController.removeBlockControls(blockControlsId); - }, [shouldRender]); - - const { volume } = controller; - - return ( - <> - <styles.Button {...buttonProps} onClick={handleClick} buttonRef={buttonRef} className={className}> - <styles.Icon - name={ - volume === 0 - ? "volume_mute" - : volume < 0.4 - ? "volume_min" - : volume < 0.75 - ? "volume_medium" - : "volume_max" - } - /> - </styles.Button> - - {shouldRender && ( - <styles.Layer {...layerProps}> - <styles.LayerWrapper> - <styles.SliderWrapper - ref={sliderRef} - tabIndex={-1} - onTouchStart={handleTouchStart} - onMouseDown={handleMouseDown} - onMouseLeave={handleMouseLeave} - onMouseUp={handleMouseUp} - onTouchEnd={handleTouchEnd} - onTouchCancel={handleTouchEnd} - > - <styles.SliderTrack> - <styles.SliderProgress - style={{ - height: `${progressPercentage}%`, - }} - > - <styles.SliderThumbContainer> - <styles.SliderThumb /> - </styles.SliderThumbContainer> - </styles.SliderProgress> - </styles.SliderTrack> - </styles.SliderWrapper> - </styles.LayerWrapper> - </styles.Layer> - )} - </> - ); -}); +import { AppStoreContext } from "src/components/AppStoreContext"; +import React from "react"; +import { getValidPercentage } from "src/helper"; +import { observer } from "mobx-react-lite"; +import { styles } from "./VolumeButton.styles"; +import { useCourse } from "src/screens/course/useCourse"; +import { useLayerProps } from "src/hooks/useLayerProps"; + +type VolumeButtonProps = { + className?: string; +}; + +type VolumeButtonState = { + trackingPointer: boolean; + isClicking: boolean; + positionPercentage: number; +}; + +export const VolumeButton: React.FunctionComponent<VolumeButtonProps> = observer(({ className }) => { + const controller = useCourse().courseClassPlayerController; + const { inputType } = React.useContext(AppStoreContext); + const buttonRef = React.useRef<HTMLButtonElement>(null); + const sliderRef = React.useRef<HTMLDivElement>(null); + + const [state, setState] = React.useState<VolumeButtonState>({ + trackingPointer: false, + isClicking: false, + positionPercentage: 0, + }); + const lastPositionRef = React.useRef(0); + + const dispatch = (state: Partial<VolumeButtonState>) => setState(prevState => ({ ...prevState, ...state })); + + const handleTouchStart: React.TouchEventHandler = React.useCallback(e => { + sliderRef.current!.focus(); + + e.preventDefault(); + dispatch({ trackingPointer: true, isClicking: true }); + }, []); + + const handleMouseDown: React.MouseEventHandler = React.useCallback(e => { + sliderRef.current!.focus(); + dispatch({ trackingPointer: true }); + const wrapperBounds = sliderRef.current!.getBoundingClientRect(); + + const positionPercentage = getValidPercentage( + e.clientY, + wrapperBounds.top + wrapperBounds.height, + wrapperBounds.top + ); + lastPositionRef.current = positionPercentage; + + dispatch({ + trackingPointer: true, + isClicking: true, + positionPercentage, + }); + }, []); + + const handleMouseLeave: React.MouseEventHandler = React.useCallback(() => { + if (!state.isClicking) dispatch({ trackingPointer: false, isClicking: false }); + }, [state.isClicking]); + + const handleMouseUp = () => { + controller.setVolume(lastPositionRef.current / 100); + dispatch({ trackingPointer: false, isClicking: false }); + }; + + const handleTouchEnd = () => { + controller.setVolume(lastPositionRef.current / 100); + dispatch({ trackingPointer: false, isClicking: false }); + }; + + const progressPercentage = + state.trackingPointer && state.isClicking ? state.positionPercentage : controller.volume * 100; + + const [shouldRender, layerProps, buttonProps] = useLayerProps( + { + behaviour: "hover", + position: "top", + separation: 10, + triggerRef: buttonRef, + }, + [progressPercentage] + ); + const { courseClassPlayerController } = useCourse(); + + const lastVolumeRef = React.useRef(courseClassPlayerController.volume); // TODO: move to controller so m key can be used + + if (courseClassPlayerController.volume && !state.trackingPointer) + lastVolumeRef.current = courseClassPlayerController.volume; + + const handleClick = React.useCallback<React.MouseEventHandler>( + e => { + if (inputType !== "pointer") return; + + e.preventDefault(); + courseClassPlayerController.volume === 0 + ? courseClassPlayerController.setVolume(lastVolumeRef.current || 1) + : courseClassPlayerController.setVolume(0); + }, + [inputType] + ); + + // TODO: mejorar BaseButton y pasar el focus al video en clic + + React.useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + const { clientY } = e; + + if (!sliderRef.current) return; + const { top, height } = sliderRef.current.getBoundingClientRect(); + const positionPercentage = getValidPercentage(clientY, top + height, top); + lastPositionRef.current = positionPercentage; + controller.setVolume(lastPositionRef.current / 100); + dispatch({ positionPercentage }); + }; + + const handleTouchMove = (e: TouchEvent) => { + const { clientY } = e.touches[0]; + + e.preventDefault(); + + if (!sliderRef.current) return; + const { top, height } = sliderRef.current.getBoundingClientRect(); + + const positionPercentage = getValidPercentage(clientY, top + height, top); + lastPositionRef.current = positionPercentage; + controller.setVolume(lastPositionRef.current / 100); + dispatch({ positionPercentage, isClicking: true }); + }; + + const handleTouchCancel = () => { + controller.setVolume(lastPositionRef.current / 100); + dispatch({ trackingPointer: false, isClicking: false }); + }; + + if (state.trackingPointer) { + window.addEventListener("mouseup", handleMouseUp); + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("touchmove", handleTouchMove, { passive: false }); + window.addEventListener("touchend", handleTouchEnd); + window.addEventListener("touchcancel", handleTouchCancel); + } + + return () => { + window.removeEventListener("mouseup", handleMouseUp); + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("touchmove", handleTouchMove); + window.removeEventListener("touchend", handleTouchEnd); + window.removeEventListener("touchcancel", handleTouchCancel); + }; + }, [state.trackingPointer, controller]); + + React.useEffect(() => { + const blockControlsId = "volume-button"; + + if (shouldRender) courseClassPlayerController.addBlockControls(blockControlsId); + else courseClassPlayerController.removeBlockControls(blockControlsId); + }, [shouldRender]); + + const { volume } = controller; + + return ( + <> + <styles.Button {...buttonProps} onClick={handleClick} buttonRef={buttonRef} className={className}> + <styles.Icon + name={ + volume === 0 + ? "volume_mute" + : volume < 0.4 + ? "volume_min" + : volume < 0.75 + ? "volume_medium" + : "volume_max" + } + /> + </styles.Button> + + {shouldRender && ( + <styles.Layer {...layerProps}> + <styles.LayerWrapper> + <styles.SliderWrapper + ref={sliderRef} + tabIndex={-1} + onTouchStart={handleTouchStart} + onMouseDown={handleMouseDown} + onMouseLeave={handleMouseLeave} + onMouseUp={handleMouseUp} + onTouchEnd={handleTouchEnd} + onTouchCancel={handleTouchEnd} + > + <styles.SliderTrack> + <styles.SliderProgress + style={{ + height: `${progressPercentage}%`, + }} + > + <styles.SliderThumbContainer> + <styles.SliderThumb /> + </styles.SliderThumbContainer> + </styles.SliderProgress> + </styles.SliderTrack> + </styles.SliderWrapper> + </styles.LayerWrapper> + </styles.Layer> + )} + </> + ); +}); diff --git a/src/screens/course/components/courseurl/CourseURL.styles.ts b/src/screens/course/components/courseurl/CourseURL.styles.ts index dc6cfb49397a40748e3137cbd875f1faea49e731..99a784c601f71f52ec5f93139e84a158e581ebc7 100644 --- a/src/screens/course/components/courseurl/CourseURL.styles.ts +++ b/src/screens/course/components/courseurl/CourseURL.styles.ts @@ -1,18 +1,18 @@ -import { rgba } from "polished"; -import { OnHover } from "src/style/Mixins"; -import styled, { css } from "styled-components"; - -export const styles = { - Button: styled.a` - flex-basis: 50px; - padding: 10px; - - ${OnHover(css` - &:hover { - background-color: ${rgba(0, 0, 0, 0.1)}; - } - `)} - `, - - EVA: styled.img``, -}; +import { rgba } from "polished"; +import { OnHover } from "src/style/Mixins"; +import styled, { css } from "styled-components"; + +export const styles = { + Button: styled.a` + flex-basis: 50px; + padding: 10px; + + ${OnHover(css` + &:hover { + background-color: ${rgba(0, 0, 0, 0.1)}; + } + `)} + `, + + EVA: styled.img``, +}; diff --git a/src/screens/course/components/courseurl/CourseURL.tsx b/src/screens/course/components/courseurl/CourseURL.tsx index 38e9557c493b393795f777332086dd1e11dd9c00..368d0a7a624272533ce463602f7bad02cd6d3fcd 100644 --- a/src/screens/course/components/courseurl/CourseURL.tsx +++ b/src/screens/course/components/courseurl/CourseURL.tsx @@ -1,18 +1,18 @@ -import EVA from "src/assets/images/EVA.svg"; -import { Models } from "openfing-core"; -import React from "react"; -import { observer } from "mobx-react-lite"; -import { styles } from "./CourseURL.styles"; - -export type CourseURLProps = { - course: Models.Course; - className?: string; -}; - -export const CourseURL = observer<CourseURLProps>(props => - props.course.eva ? ( - <styles.Button className={props.className} href={props.course.eva}> - <styles.EVA src={EVA} /> - </styles.Button> - ) : null -); +import EVA from "src/assets/images/EVA.svg"; +import { Models } from "openfing-core"; +import React from "react"; +import { observer } from "mobx-react-lite"; +import { styles } from "./CourseURL.styles"; + +export type CourseURLProps = { + course: Models.Course; + className?: string; +}; + +export const CourseURL = observer<CourseURLProps>(props => + props.course.eva ? ( + <styles.Button className={props.className} href={props.course.eva}> + <styles.EVA src={EVA} /> + </styles.Button> + ) : null +); diff --git a/src/screens/updates/components/updateitem/__stories__/UpdateItem.stories.tsx b/src/screens/updates/components/updateitem/__stories__/UpdateItem.stories.tsx index e5c447d5b68a1255e66a4408c8802689c86269f3..29675e4a341752bc6251a92c0291cc0b73f06912 100644 --- a/src/screens/updates/components/updateitem/__stories__/UpdateItem.stories.tsx +++ b/src/screens/updates/components/updateitem/__stories__/UpdateItem.stories.tsx @@ -1,43 +1,43 @@ -import { Models } from "openfing-core"; -import React from "react"; -import { UpdateItem } from "../UpdateItem"; -import { assign } from "openfing-core/lib/helpers"; -import moment from "moment"; -import { storiesOf } from "@storybook/react"; -import styled from "styled-components"; - -const Container = styled.div` - margin: auto; - min-width: 400px; - width: 100px; -`; - -const Content = styled.div` - background-color: palevioletred; -`; - -const courseClass = new Models.CourseClass(1); -assign(courseClass, { - number: 1, - title: "Test", - createdAt: moment("2019-03-19"), -}); - -const courseClassList = new Models.CourseClassList(1); -courseClass.courseClassList = courseClassList; - -const course = new Models.Course(1); -assign(course, { - code: "tst", - name: "Test course", - iconURL: "https://open.fing.edu.uy/api-data/courseicons/defaulticon.svg", -}); -courseClassList.course = course; - -storiesOf("UpdateItem", module) - .addDecorator(story => ( - <Container> - <Content>{story()}</Content> - </Container> - )) - .add("Default", () => <UpdateItem courseClass={courseClass} />); +import { Models } from "openfing-core"; +import React from "react"; +import { UpdateItem } from "../UpdateItem"; +import { assign } from "openfing-core/lib/helpers"; +import moment from "moment"; +import { storiesOf } from "@storybook/react"; +import styled from "styled-components"; + +const Container = styled.div` + margin: auto; + min-width: 400px; + width: 100px; +`; + +const Content = styled.div` + background-color: palevioletred; +`; + +const courseClass = new Models.CourseClass(1); +assign(courseClass, { + number: 1, + title: "Test", + createdAt: moment("2019-03-19"), +}); + +const courseClassList = new Models.CourseClassList(1); +courseClass.courseClassList = courseClassList; + +const course = new Models.Course(1); +assign(course, { + code: "tst", + name: "Test course", + iconURL: "https://open.fing.edu.uy/api-data/courseicons/defaulticon.svg", +}); +courseClassList.course = course; + +storiesOf("UpdateItem", module) + .addDecorator(story => ( + <Container> + <Content>{story()}</Content> + </Container> + )) + .add("Default", () => <UpdateItem courseClass={courseClass} />); diff --git a/src/style/Breakpoint.ts b/src/style/Breakpoint.ts index e3eb2a0be6f462f92bce5c3a82abaf51c2cfc0e1..76b4fed4b79542984259946c7bcc19aab1bef396 100644 --- a/src/style/Breakpoint.ts +++ b/src/style/Breakpoint.ts @@ -1,6 +1,6 @@ -export const Breakpoint = { - xs: 0, - sm: 768, - md: 992, - lg: 1200, -}; +export const Breakpoint = { + xs: 0, + sm: 768, + md: 992, + lg: 1200, +}; diff --git a/src/style/Mixins.ts b/src/style/Mixins.ts index 869636b2f7d00262f51bba261ba1f03fe93bbdd8..5386e930e24a83705e50d40ce9fafdd43281d7c3 100644 --- a/src/style/Mixins.ts +++ b/src/style/Mixins.ts @@ -1,113 +1,113 @@ -import darken from "polished/lib/color/darken"; -import { FlattenSimpleInterpolation, css } from "styled-components"; -import { Breakpoint } from "./Breakpoint"; - -export const EllipsisText = (maxHeight?: string) => css` - overflow: hidden; - ${maxHeight && - css` - max-height: ${maxHeight}; - `}; - display: block; - text-overflow: ellipsis; - white-space: nowrap; - line-height: normal; -`; - -export const OnHover = (inter: FlattenSimpleInterpolation): FlattenSimpleInterpolation => css` - .pointer &:hover { - ${inter}; - } -`; - -export const WiderThan = ( - bp: number | keyof typeof Breakpoint, - inter: FlattenSimpleInterpolation -): FlattenSimpleInterpolation => css` - @media (min-width: ${typeof bp === "string" ? Breakpoint[bp] : bp}px) { - ${inter}; - } -`; - -export const NarrowerThan = (bp: number, inter: FlattenSimpleInterpolation): FlattenSimpleInterpolation => css` - @media (max-width: ${bp}px) { - ${inter}; - } -`; - -export const HSpacing = (spacing: number) => css` - > :not(:last-child) { - margin-right: ${spacing}px; - } -`; - -export const VSpacing = (spacing: number) => css` - > :not(:last-child) { - margin-bottom: ${spacing}px; - } -`; - -export const List = css` - display: flex; - flex-direction: column; - min-height: 0; - min-width: 0; - width: auto; - overflow: auto; - box-sizing: border-box; - - > * { - flex-shrink: 0; - } -`; - -export const ResponsiveList = css` - ${List}; - - ${WiderThan( - Breakpoint.sm, - css` - align-items: center; - - > * { - width: 80%; - max-width: 800px; - box-sizing: border-box; - - &:not(:last-child) { - margin-bottom: 20px; - } - } - ` - )}; -`; - -export const ListItem = css` - flex: 0 0 ${p => p.theme.baseLineHeight}px; - - ${OnHover(css` - background-color: ${darken(0.1, "#eee")}; - `)}; - - padding-right: 20px; - padding-left: 20px; -`; - -export const ResponsiveListItem = css` - ${ListItem}; - - ${WiderThan( - Breakpoint.sm, - css` - flex-basis: auto; - padding: 20px 15px; - border: 1px rgba(67, 52, 52, 0.2) solid; - background-color: #fafafa; - - &:before, - &:after { - content: none; - } - ` - )}; -`; +import darken from "polished/lib/color/darken"; +import { FlattenSimpleInterpolation, css } from "styled-components"; +import { Breakpoint } from "./Breakpoint"; + +export const EllipsisText = (maxHeight?: string) => css` + overflow: hidden; + ${maxHeight && + css` + max-height: ${maxHeight}; + `}; + display: block; + text-overflow: ellipsis; + white-space: nowrap; + line-height: normal; +`; + +export const OnHover = (inter: FlattenSimpleInterpolation): FlattenSimpleInterpolation => css` + .pointer &:hover { + ${inter}; + } +`; + +export const WiderThan = ( + bp: number | keyof typeof Breakpoint, + inter: FlattenSimpleInterpolation +): FlattenSimpleInterpolation => css` + @media (min-width: ${typeof bp === "string" ? Breakpoint[bp] : bp}px) { + ${inter}; + } +`; + +export const NarrowerThan = (bp: number, inter: FlattenSimpleInterpolation): FlattenSimpleInterpolation => css` + @media (max-width: ${bp}px) { + ${inter}; + } +`; + +export const HSpacing = (spacing: number) => css` + > :not(:last-child) { + margin-right: ${spacing}px; + } +`; + +export const VSpacing = (spacing: number) => css` + > :not(:last-child) { + margin-bottom: ${spacing}px; + } +`; + +export const List = css` + display: flex; + flex-direction: column; + min-height: 0; + min-width: 0; + width: auto; + overflow: auto; + box-sizing: border-box; + + > * { + flex-shrink: 0; + } +`; + +export const ResponsiveList = css` + ${List}; + + ${WiderThan( + Breakpoint.sm, + css` + align-items: center; + + > * { + width: 80%; + max-width: 800px; + box-sizing: border-box; + + &:not(:last-child) { + margin-bottom: 20px; + } + } + ` + )}; +`; + +export const ListItem = css` + flex: 0 0 ${p => p.theme.baseLineHeight}px; + + ${OnHover(css` + background-color: ${darken(0.1, "#eee")}; + `)}; + + padding-right: 20px; + padding-left: 20px; +`; + +export const ResponsiveListItem = css` + ${ListItem}; + + ${WiderThan( + Breakpoint.sm, + css` + flex-basis: auto; + padding: 20px 15px; + border: 1px rgba(67, 52, 52, 0.2) solid; + background-color: #fafafa; + + &:before, + &:after { + content: none; + } + ` + )}; +`; diff --git a/src/typings/console.d.ts b/src/typings/console.d.ts index c57302d4ec62db171d9d9837b3b55297f00ffa6a..0af50c4239de658bda6dd731abf7dbc34fa6581e 100644 --- a/src/typings/console.d.ts +++ b/src/typings/console.d.ts @@ -1,3 +1,3 @@ -declare module "console" { - export = typeof import("console"); -} +declare module "console" { + export = typeof import("console"); +} diff --git a/src/typings/create-file-webpack.d.ts b/src/typings/create-file-webpack.d.ts index 85a60676857a5e49adabba2805b7c6a6421420b2..b8d1c1b30b756e8464a0f2ae7d7dd760063ccb15 100644 --- a/src/typings/create-file-webpack.d.ts +++ b/src/typings/create-file-webpack.d.ts @@ -1,17 +1,17 @@ -declare module "create-file-webpack" { - import { Plugin } from "webpack"; - - declare class CreateFileWebpack extends Plugin { - public constructor(options: CreateFileWebpackOptions); - } - - declare namespace CreateFileWebpack { - type CreateFileWebpackOptions = { - path: string; - fileName: string; - content: string; - }; - } - - export = CreateFileWebpack; -} +declare module "create-file-webpack" { + import { Plugin } from "webpack"; + + declare class CreateFileWebpack extends Plugin { + public constructor(options: CreateFileWebpackOptions); + } + + declare namespace CreateFileWebpack { + type CreateFileWebpackOptions = { + path: string; + fileName: string; + content: string; + }; + } + + export = CreateFileWebpack; +} diff --git a/src/typings/inline-environment-variables-webpack-plugin.d.ts b/src/typings/inline-environment-variables-webpack-plugin.d.ts index 7662d42038e669bd243d99d51e9cf46c2ad43d4b..586ed404af7dff6a7ded34f23fa2afbc75c19cdc 100644 --- a/src/typings/inline-environment-variables-webpack-plugin.d.ts +++ b/src/typings/inline-environment-variables-webpack-plugin.d.ts @@ -1,21 +1,21 @@ -declare module "inline-environment-variables-webpack-plugin" { - import { Plugin } from "webpack"; - - declare class InlineEnvironmentVariablesPlugin extends Plugin { - public constructor(); - public constructor(variable: string, options?: InlineEnvironmentVariablesPluginOptions); - public constructor(variables: Record<string, string>, options?: InlineEnvironmentVariablesPluginOptions); - public constructor( - variables: Array<string | Record<string, string>>, - options?: InlineEnvironmentVariablesPluginOptions - ); - } - - declare namespace InlineEnvironmentVariablesPlugin { - type InlineEnvironmentVariablesPluginOptions = { - warnings: boolean; - }; - } - - export = InlineEnvironmentVariablesPlugin; -} +declare module "inline-environment-variables-webpack-plugin" { + import { Plugin } from "webpack"; + + declare class InlineEnvironmentVariablesPlugin extends Plugin { + public constructor(); + public constructor(variable: string, options?: InlineEnvironmentVariablesPluginOptions); + public constructor(variables: Record<string, string>, options?: InlineEnvironmentVariablesPluginOptions); + public constructor( + variables: Array<string | Record<string, string>>, + options?: InlineEnvironmentVariablesPluginOptions + ); + } + + declare namespace InlineEnvironmentVariablesPlugin { + type InlineEnvironmentVariablesPluginOptions = { + warnings: boolean; + }; + } + + export = InlineEnvironmentVariablesPlugin; +}