diff --git a/.angular-cli.json b/.angular-cli.json new file mode 100644 index 0000000000000000000000000000000000000000..ce654106465498153093c13f88c34696eaa0252b --- /dev/null +++ b/.angular-cli.json @@ -0,0 +1,68 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "project": { + "name": "cli-stable" + }, + "apps": [ + { + "root": "src", + "outDir": "dist", + "assets": [ + "assets", + "favicon.ico", + ".htaccess" + ], + "index": "index.html", + "main": "main.ts", + "polyfills": "polyfills.ts", + "test": "test.ts", + "tsconfig": "tsconfig.app.json", + "testTsconfig": "tsconfig.spec.json", + "prefix": "app", + "styles": [ + "../node_modules/font-awesome/css/font-awesome.css", + "styles/app.scss", + "styles/console.css", + "../node_modules/tippy.js/dist/tippy.css", + "../node_modules/codemirror/lib/codemirror.css", + "../node_modules/codemirror/addon/hint/show-hint.css", + "../node_modules/codemirror/addon/dialog/dialog.css", + "../node_modules/codemirror/addon/search/matchesonscrollbar.css" + ], + "scripts": [ + "../node_modules/jquery/dist/jquery.min.js", + "../node_modules/jq-console/lib/jqconsole.js" + ], + "environmentSource": "environments/environment.ts", + "environments": { + "dev": "environments/environment.ts", + "prod": "environments/environment.prod.ts" + } + } + ], + "e2e": { + "protractor": { + "config": "./protractor.conf.js" + } + }, + "lint": [ + { + "project": "src/tsconfig.app.json" + }, + { + "project": "src/tsconfig.spec.json" + }, + { + "project": "e2e/tsconfig.e2e.json" + } + ], + "test": { + "karma": { + "config": "./karma.conf.js" + } + }, + "defaults": { + "styleExt": "scss", + "component": {} + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..9b7352176acfa09b8f83d19dea54e34a8089cd5f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..537939b80542401d5f9011cd662d0fbe7e7092d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +# /dist +/tmp +/out-tsc + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +testem.log +/typings + +# e2e +/e2e/*.js +/e2e/*.map + +# System Files +.DS_Store +Thumbs.db diff --git a/README.md b/README.md index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..73593638e277094c600c4a55edad6a3ff3a8482e 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,42 @@ +Luego de ejecutar npm install, copiar los archivos ubicados en la carpeta "custom codemirror" en "node_modules/codemirror/addon/search" + + +Detalles del template utilizado: + +# SB Admin rewritten in Angular4 and Bootstrap 4 + +Simple Dashboard Admin App built using Angular 4 and Bootstrap 4 + +This project is a port of the famous Free Admin Bootstrap Theme [SB Admin v4.0](http://startbootstrap.com/template-overviews/sb-admin-2/) to Angular4 Theme. + +Powered by [StartAngular](http://startangular.com/) & [StrapUI](http://strapui.com/) + +## [Demo](http://rawgit.com/start-angular/SB-Admin-BS4-Angular-4/master/dist/) + + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0. + +### Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +### Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`. + +### Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. + +### Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +### Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). +Before running the tests make sure you are serving the app via `ng serve`. + +### Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/custom codemirror/jump-to-line.js b/custom codemirror/jump-to-line.js new file mode 100644 index 0000000000000000000000000000000000000000..696bbd40a1d6c9c8fa9f5235ed14f27ed590182e --- /dev/null +++ b/custom codemirror/jump-to-line.js @@ -0,0 +1,49 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// Defines jumpToLine command. Uses dialog.js if present. + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../dialog/dialog")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../dialog/dialog"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + function dialog(cm, text, shortText, deflt, f) { + if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true}); + else f(prompt(shortText, deflt)); + } + + var jumpDialog = + 'Ir a la lÃnea: <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint"></span>'; + + function interpretLine(cm, string) { + var num = Number(string) + if (/^[-+]/.test(string)) return cm.getCursor().line + num + else return num - 1 + } + + CodeMirror.commands.jumpToLine = function(cm) { + var cur = cm.getCursor(); + dialog(cm, jumpDialog, "Jump to line:", (cur.line + 1) + ":" + cur.ch, function(posStr) { + if (!posStr) return; + + var match; + if (match = /^\s*([\+\-]?\d+)\s*\:\s*(\d+)\s*$/.exec(posStr)) { + cm.setCursor(interpretLine(cm, match[1]), Number(match[2])) + } else if (match = /^\s*([\+\-]?\d+(\.\d+)?)\%\s*/.exec(posStr)) { + var line = Math.round(cm.lineCount() * Number(match[1]) / 100); + if (/^[-+]/.test(match[1])) line = cur.line + line + 1; + cm.setCursor(line - 1, cur.ch); + } else if (match = /^\s*\:?\s*([\+\-]?\d+)\s*/.exec(posStr)) { + cm.setCursor(interpretLine(cm, match[1]), cur.ch); + } + }); + }; + + CodeMirror.keyMap["default"]["Alt-G"] = "jumpToLine"; +}); diff --git a/custom codemirror/search.js b/custom codemirror/search.js new file mode 100644 index 0000000000000000000000000000000000000000..08b24d265f1d2ff73856b40ebe071bc8c4ecf035 --- /dev/null +++ b/custom codemirror/search.js @@ -0,0 +1,252 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// Define search commands. Depends on dialog.js or another +// implementation of the openDialog method. + +// Replace works a little oddly -- it will do the replace on the next +// Ctrl-G (or whatever is bound to findNext) press. You prevent a +// replace by making sure the match is no longer selected when hitting +// Ctrl-G. + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + function searchOverlay(query, caseInsensitive) { + if (typeof query == "string") + query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g"); + else if (!query.global) + query = new RegExp(query.source, query.ignoreCase ? "gi" : "g"); + + return {token: function(stream) { + query.lastIndex = stream.pos; + var match = query.exec(stream.string); + if (match && match.index == stream.pos) { + stream.pos += match[0].length || 1; + return "searching"; + } else if (match) { + stream.pos = match.index; + } else { + stream.skipToEnd(); + } + }}; + } + + function SearchState() { + this.posFrom = this.posTo = this.lastQuery = this.query = null; + this.overlay = null; + } + + function getSearchState(cm) { + return cm.state.search || (cm.state.search = new SearchState()); + } + + function queryCaseInsensitive(query) { + return typeof query == "string" && query == query.toLowerCase(); + } + + function getSearchCursor(cm, query, pos) { + // Heuristic: if the query string is all lowercase, do a case insensitive search. + return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true}); + } + + function persistentDialog(cm, text, deflt, onEnter, onKeyDown) { + cm.openDialog(text, onEnter, { + value: deflt, + selectValueOnOpen: true, + closeOnEnter: false, + onClose: function() { clearSearch(cm); }, + onKeyDown: onKeyDown + }); + } + + function dialog(cm, text, shortText, deflt, f) { + if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true}); + else f(prompt(shortText, deflt)); + } + + function confirmDialog(cm, text, shortText, fs) { + if (cm.openConfirm) cm.openConfirm(text, fs); + else if (confirm(shortText)) fs[0](); + } + + function parseString(string) { + return string.replace(/\\(.)/g, function(_, ch) { + if (ch == "n") return "\n" + if (ch == "r") return "\r" + return ch + }) + } + + function parseQuery(query) { + var isRE = query.match(/^\/(.*)\/([a-z]*)$/); + if (isRE) { + try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); } + catch(e) {} // Not a regular expression after all, do a string search + } else { + query = parseString(query) + } + if (typeof query == "string" ? query == "" : query.test("")) + query = /x^/; + return query; + } + + var queryDialog = + '<span class="CodeMirror-search-label">Buscar:</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint"></span>'; + + function startSearch(cm, state, query) { + state.queryText = query; + state.query = parseQuery(query); + cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); + state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); + cm.addOverlay(state.overlay); + if (cm.showMatchesOnScrollbar) { + if (state.annotate) { state.annotate.clear(); state.annotate = null; } + state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query)); + } + } + + function doSearch(cm, rev, persistent, immediate) { + var state = getSearchState(cm); + if (state.query) return findNext(cm, rev); + var q = cm.getSelection() || state.lastQuery; + if (persistent && cm.openDialog) { + var hiding = null + var searchNext = function(query, event) { + CodeMirror.e_stop(event); + if (!query) return; + if (query != state.queryText) { + startSearch(cm, state, query); + state.posFrom = state.posTo = cm.getCursor(); + } + if (hiding) hiding.style.opacity = 1 + findNext(cm, event.shiftKey, function(_, to) { + var dialog + if (to.line < 3 && document.querySelector && + (dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) && + dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top) + (hiding = dialog).style.opacity = .4 + }) + }; + persistentDialog(cm, queryDialog, q, searchNext, function(event, query) { + var keyName = CodeMirror.keyName(event) + var cmd = CodeMirror.keyMap[cm.getOption("keyMap")][keyName] + if (!cmd) cmd = cm.getOption('extraKeys')[keyName] + if (cmd == "findNext" || cmd == "findPrev" || + cmd == "findPersistentNext" || cmd == "findPersistentPrev") { + CodeMirror.e_stop(event); + startSearch(cm, getSearchState(cm), query); + cm.execCommand(cmd); + } else if (cmd == "find" || cmd == "findPersistent") { + CodeMirror.e_stop(event); + searchNext(query, event); + } + }); + if (immediate && q) { + startSearch(cm, state, q); + findNext(cm, rev); + } + } else { + dialog(cm, queryDialog, "Buscar por:", q, function(query) { + if (query && !state.query) cm.operation(function() { + startSearch(cm, state, query); + state.posFrom = state.posTo = cm.getCursor(); + findNext(cm, rev); + }); + }); + } + } + + function findNext(cm, rev, callback) {cm.operation(function() { + var state = getSearchState(cm); + var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo); + if (!cursor.find(rev)) { + cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0)); + if (!cursor.find(rev)) return; + } + cm.setSelection(cursor.from(), cursor.to()); + cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20); + state.posFrom = cursor.from(); state.posTo = cursor.to(); + if (callback) callback(cursor.from(), cursor.to()) + });} + + function clearSearch(cm) {cm.operation(function() { + var state = getSearchState(cm); + state.lastQuery = state.query; + if (!state.query) return; + state.query = state.queryText = null; + cm.removeOverlay(state.overlay); + if (state.annotate) { state.annotate.clear(); state.annotate = null; } + });} + + var replaceQueryDialog = + ' <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint"></span>'; + var replacementQueryDialog = '<span class="CodeMirror-search-label">Con:</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/>'; + var doReplaceConfirm = '<span class="CodeMirror-search-label">Reemplazar?</span> <button>Si</button> <button>No</button> <button>Todos</button> <button>Cancelar</button>'; + + function replaceAll(cm, query, text) { + cm.operation(function() { + for (var cursor = getSearchCursor(cm, query); cursor.findNext();) { + if (typeof query != "string") { + var match = cm.getRange(cursor.from(), cursor.to()).match(query); + cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];})); + } else cursor.replace(text); + } + }); + } + + function replace(cm, all) { + if (cm.getOption("readOnly")) return; + var query = cm.getSelection() || getSearchState(cm).lastQuery; + var dialogText = '<span class="CodeMirror-search-label">' + (all ? 'Reemplazar todo:' : 'Reemplazar:') + '</span>'; + dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) { + if (!query) return; + query = parseQuery(query); + dialog(cm, replacementQueryDialog, "Reemplazar con:", "", function(text) { + text = parseString(text) + if (all) { + replaceAll(cm, query, text) + } else { + clearSearch(cm); + var cursor = getSearchCursor(cm, query, cm.getCursor("from")); + var advance = function() { + var start = cursor.from(), match; + if (!(match = cursor.findNext())) { + cursor = getSearchCursor(cm, query); + if (!(match = cursor.findNext()) || + (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; + } + cm.setSelection(cursor.from(), cursor.to()); + cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); + confirmDialog(cm, doReplaceConfirm, "Reemplazar?", + [function() {doReplace(match);}, advance, + function() {replaceAll(cm, query, text)}]); + }; + var doReplace = function(match) { + cursor.replace(typeof query == "string" ? text : + text.replace(/\$(\d)/g, function(_, i) {return match[i];})); + advance(); + }; + advance(); + } + }); + }); + } + + CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);}; + CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);}; + CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);}; + CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);}; + CodeMirror.commands.findNext = doSearch; + CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; + CodeMirror.commands.clearSearch = clearSearch; + CodeMirror.commands.replace = replace; + CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);}; +}); diff --git a/e2e/app.e2e-spec.ts b/e2e/app.e2e-spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..0bcefd88f860c2a231cb0841b5f1f8fc3db1630a --- /dev/null +++ b/e2e/app.e2e-spec.ts @@ -0,0 +1,14 @@ +import { CliStablePage } from './app.po'; + +describe('cli-stable App', () => { + let page: CliStablePage; + + beforeEach(() => { + page = new CliStablePage(); + }); + + it('should display message saying app works', () => { + page.navigateTo(); + expect(page.getParagraphText()).toEqual('app works!'); + }); +}); diff --git a/e2e/app.po.ts b/e2e/app.po.ts new file mode 100644 index 0000000000000000000000000000000000000000..11ce66a9f934629452998aafe0037f0a4de6ab89 --- /dev/null +++ b/e2e/app.po.ts @@ -0,0 +1,11 @@ +import { browser, element, by } from 'protractor'; + +export class CliStablePage { + navigateTo() { + return browser.get('/'); + } + + getParagraphText() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/e2e/tsconfig.e2e.json b/e2e/tsconfig.e2e.json new file mode 100644 index 0000000000000000000000000000000000000000..ac7a37325798f692729aa27a75a1b6c86db3502b --- /dev/null +++ b/e2e/tsconfig.e2e.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/e2e", + "module": "commonjs", + "target": "es5", + "types":[ + "jasmine", + "node" + ] + } +} diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000000000000000000000000000000000000..84b4cd5acab29968044fc5748fa53dd97d21229d --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,44 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/0.13/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular/cli'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular/cli/plugins/karma') + ], + client:{ + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + files: [ + { pattern: './src/test.ts', watched: false } + ], + preprocessors: { + './src/test.ts': ['@angular/cli'] + }, + mime: { + 'text/x-typescript': ['ts','tsx'] + }, + coverageIstanbulReporter: { + reports: [ 'html', 'lcovonly' ], + fixWebpackSourcePaths: true + }, + angularCli: { + environment: 'dev' + }, + reporters: config.angularCli && config.angularCli.codeCoverage + ? ['progress', 'coverage-istanbul'] + : ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..37280096f01533ab49f88e2282716a7e27a9f91d --- /dev/null +++ b/package.json @@ -0,0 +1,55 @@ +{ + "name": "sb-admin-angular4-bootstrap4", + "version": "1.0.0", + "license": "MIT", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular/common": "^4.0.0", + "@angular/compiler": "^4.0.0", + "@angular/core": "^4.1.3", + "@angular/forms": "^4.0.0", + "@angular/http": "^4.0.0", + "@angular/platform-browser": "^4.0.0", + "@angular/platform-browser-dynamic": "^4.0.0", + "@angular/router": "^4.0.0", + "@ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.22", + "core-js": "^2.4.1", + "font-awesome": "^4.7.0", + "ionicons": "^3.0.0", + "jq-console": "^2.13.2", + "jquery": "^3.2.1", + "ng2-bootstrap-modal": "^1.0.1", + "ng2-codemirror": "^1.1.1", + "rxjs": "^5.1.0", + "tippy.js": "^1.2.0", + "zone.js": "^0.8.4" + }, + "devDependencies": { + "@angular/cli": "1.0.0", + "@angular/compiler-cli": "^4.0.0", + "@types/jasmine": "2.5.38", + "@types/jquery": "^2.0.45", + "@types/node": "~6.0.60", + "codelyzer": "~2.0.0", + "jasmine-core": "~2.5.2", + "jasmine-spec-reporter": "~3.2.0", + "karma": "~1.4.1", + "karma-chrome-launcher": "~2.0.0", + "karma-cli": "~1.0.1", + "karma-coverage-istanbul-reporter": "^0.2.0", + "karma-jasmine": "~1.1.0", + "karma-jasmine-html-reporter": "^0.2.2", + "protractor": "~5.1.0", + "ts-node": "~2.0.0", + "tslint": "~4.5.0", + "typescript": "~2.2.0" + } +} diff --git a/protractor.conf.js b/protractor.conf.js new file mode 100644 index 0000000000000000000000000000000000000000..fc13da6c6586a873cead1d9c6864a6787ad81b3b --- /dev/null +++ b/protractor.conf.js @@ -0,0 +1,30 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './e2e/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome' + }, + directConnect: true, + baseUrl: 'http://0.0.0.0:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + beforeLaunch: function() { + require('ts-node').register({ + project: 'e2e/tsconfig.e2e.json' + }); + }, + onPrepare() { + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; diff --git a/src/.htaccess b/src/.htaccess new file mode 100644 index 0000000000000000000000000000000000000000..e906ce034d9b3a4bbd1dc4a8bdca08481f5fd927 --- /dev/null +++ b/src/.htaccess @@ -0,0 +1,6 @@ +RewriteEngine On +RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR] +RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d +RewriteRule ^ - [L] + +RewriteRule ^ /index.html [L] diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..d3972a1895d806d37dbf2b6e7144d2ba5cd13092 --- /dev/null +++ b/src/app/app-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { AuthGuard } from './shared/guards/auth.guard'; + +const routes: Routes = [ + { path: '', loadChildren: './layout/layout.module#LayoutModule', canActivate: [AuthGuard] }, + { path: 'login', loadChildren: './login/login.module#LoginModule' }, + { path: 'not-found', loadChildren: './not-found/not-found.module#NotFoundModule' }, + { path: '**', redirectTo: 'not-found' } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes, { useHash: true })], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/src/app/app.component.html b/src/app/app.component.html new file mode 100644 index 0000000000000000000000000000000000000000..0680b43f9c6ae05df91c576141f20ed411d07c7d --- /dev/null +++ b/src/app/app.component.html @@ -0,0 +1 @@ +<router-outlet></router-outlet> diff --git a/src/app/app.component.scss b/src/app/app.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..2fee2a8011f1505e0a6cf68f6dc12f162e128530 --- /dev/null +++ b/src/app/app.component.spec.ts @@ -0,0 +1,36 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule + ], + declarations: [ + AppComponent + ], + }).compileComponents(); + })); + + it('should create the app', async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + })); + + it(`should have as title 'app works!'`, async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('app works!'); + })); + + it('should render title in a h1 tag', async(() => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('app works!'); + })); +}); diff --git a/src/app/app.component.ts b/src/app/app.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..aa1c15aa845343c53a856188c8938fa69b42f654 --- /dev/null +++ b/src/app/app.component.ts @@ -0,0 +1,14 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +export class AppComponent implements OnInit { + constructor(public router: Router) { } + ngOnInit() { + //this.router.navigate(['/login']); + } +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..084f634a68ef5c67a0b66e483b4fa78d954826aa --- /dev/null +++ b/src/app/app.module.ts @@ -0,0 +1,25 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { AuthGuard } from './shared/guards/auth.guard'; +import { SessionService } from './shared/services/session.service'; +import { NotificacionService } from './shared/services/notificacion.service'; +import {NotificacionModule} from './notificacion/notificacion.module' +@NgModule({ + declarations: [ + AppComponent, + ], + imports: [ + NotificacionModule, + BrowserModule, + FormsModule, + HttpModule, + AppRoutingModule + ], + providers: [AuthGuard, SessionService,NotificacionService], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/src/app/layout/archivos/archivos-routing.module.ts b/src/app/layout/archivos/archivos-routing.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..47cc7eab4298205c636aab78669e16deac50c9b9 --- /dev/null +++ b/src/app/layout/archivos/archivos-routing.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { ArchivosComponent } from './archivos.component'; + +const routes: Routes = [ + { path: '', component: ArchivosComponent } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class ArchivosRoutingModule { } \ No newline at end of file diff --git a/src/app/layout/archivos/archivos.component.html b/src/app/layout/archivos/archivos.component.html new file mode 100644 index 0000000000000000000000000000000000000000..6df5a10f81854ae0c8b10b5f89db033375702fca --- /dev/null +++ b/src/app/layout/archivos/archivos.component.html @@ -0,0 +1,152 @@ +<notificacion></notificacion> +<div class="container-fluid"> + <div class="row"> + <div class="col-lg-5"> + <label for="search">Nombre del archivo:</label> + <div class="input-group"> + <input type="text" class="form-control" id="search" [(ngModel)]=filtroNombre> + <span class="input-group-addon fa fa-search"> + </span> + </div> + + </div> + + </div> + <div class="row" style="margin-top: 20px"> + <div class="col-lg-5"> + <ngb-tabset [destroyOnHide]=false> + <ngb-tab title="Mis archivos"> + <ng-template ngbTabContent> + + <div class="card"> + <div> + <div class="btn-group pull-right" style="cursor: pointer; margin-top: -9px;"> + <button ngbPopover="Atras" triggers="mouseenter:mouseleave" data-placement="bottom" class="btn btn-sm btn-secondary pull-right" *ngIf="directorioActual.padreId!=-1" style="cursor: pointer; margin-top: -33.5px; margin-right: 73px; height: 30px;"(click)="navBack()"> + <i class="fa fa-arrow-up"></i> + </button> + </div> + <!-- <button class="btn btn-sm btn-secondary pull-right" style="cursor: pointer; margin-top: -35px;" > + <i class="fa fa-plus"></i> + </buton> --> + <div class="btn-group pull-right" style="cursor: pointer; margin-top: -42px; height: 30px; width: 29px;"> + <button ngbPopover="Nuevo" triggers="mouseenter:mouseleave" data-placement="bottom" style="cursor: pointer;border-radius: 3px;" type="button" class="btn btn-sm btn-secondary" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + <i class="fa fa-plus"></i> + </button> + <div class="dropdown-menu"> + <a class="dropdown-item"(click)="mkFile()" >Archivo</a> + <div class="dropdown-divider"></div> + <a class="dropdown-item" (click)="mkdir()">Carpeta</a> + </div> + </div> + + <div class="btn-group pull-right" style="cursor: pointer; margin-top: -42px; height: 30px; width: 29px; margin-right: 36px;"> + <button ngbPopover="Ordenar" triggers="mouseenter:mouseleave" data-placement="bottom" style="cursor: pointer;border-radius: 3px;width: 36px;" type="button" class="btn btn-sm btn-secondary" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + <i class="fa fa-sort"></i> + </button> + <div class="dropdown-menu"> + <a class="dropdown-item"(click)="ordenarPorTipo()" >Carpetas primero</a> + <div class="dropdown-divider"></div> + <a class="dropdown-item" (click)="ordenarPorFecha()">Fecha de creación</a> + </div> + </div> + + + </div> + <div class="card-block"> + <div class="row listado-archivos" style="min-height: 100px; overflow-y: scroll;"> + <div class="loading" *ngIf="loading"> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + </div> + <div *ngFor="let arch of directorioActual.archivos | filter: 'nombre': filtroNombre " (click)="seleccionarArchivo(arch)" class="col-sm-3 col-4" style="text-align: center;"> + <i *ngIf="arch.directorio" class="fa fa-folder" style="font-size: 3em; cursor: pointer;color:#f95e5e" aria-hidden="true"></i> + <i *ngIf="!arch.directorio" class="fa fa-file-text" style="font-size: 3em; cursor: pointer;color:#ff8383" aria-hidden="true"></i> + <p style="cursor: pointer;">{{arch.nombre}}</p> + </div> + </div> + </div> + </div> + + </ng-template> + </ngb-tab> + <ngb-tab title="Compartidos"> + <ng-template ngbTabContent> + + <div class="card" *ngIf="esAlumno"> + <div class="card-block"> + <div class="row listado-archivos" style="min-height: 100px; overflow-y: scroll;"> + <div class="loading" *ngIf="loadingCompartidos"> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + </div> + <div *ngFor="let arch of archivosCompartidosSinDuplicados | filter: 'nombre': filtroNombre " (click)="seleccionarArchivo(arch)" class="col-sm-3 col-4" style="text-align: center;"> + <i class="fa fa-file-text" style="font-size: 3em; cursor: pointer;color:#ff8383" aria-hidden="true"></i> + + <p style="cursor: pointer;">{{arch.nombre}}</p> + </div> + </div> + </div> + </div> + </ng-template> + </ngb-tab> + </ngb-tabset> + </div> + <div class="col-lg-7"> + <div class="card"> + <div class="card-header"> + <button ngbPopover="Cargar/Editar" data-placement="bottom" triggers="mouseenter:mouseleave" class="btn btn-sm btn-secondary pull-left mr-2" (click)="cargarArchivo()"> + <i class="fa fa-pencil"></i> + </button> + <button ngbPopover="Eliminar" data-placement="bottom" triggers="mouseenter:mouseleave" class="btn btn-sm btn-secondary pull-left mr-2" (click)="mostrarEliminarDialogo()"> + <i class="fa fa-remove"></i> + </button> + <button ngbPopover="Mover Archivo"data-placement="bottom" triggers="mouseenter:mouseleave" class="btn btn-sm btn-secondary pull-left mr-2" (click)="seleccionarDirectorioAMover()"> + <i class="fa fa-cut"></i> + </button> + + <button class="btn btn-sm btn-secondary pull-left mr-2" *ngIf="esAlumno && archivoSeleccionado && hayArchivoOriginal()" (click)="seleccionarArchivoOriginal()">Ver original</button> + + <button class="btn btn-sm btn-secondary pull-left mr-2" *ngIf="esAlumno && archivoSeleccionado && hayArchivoMio()" (click)="seleccionarArchivoMio()">Ver mio</button> + + <button class="btn btn-sm btn-secondary pull-left mr-4" *ngIf="esAlumno && archivoSeleccionado && !archivoSeleccionado.directorio && archivoSeleccionado.archivoOrigenId != -1 && (archivoSeleccionado.estado == 'Edicion' || archivoSeleccionado.estado == 'Devuelto')" (click)="confirmarEntrega()">Entregar</button> + + <div class="pull-left mr-2" *ngIf="esAlumno && (archivoSeleccionado?.estado == 'Entregado')">{{archivoSeleccionado.estado}} - </div> + + <button *ngIf="esAlumno && archivoSeleccionado?.estado == 'Corregido'" class="btn btn-sm btn-secondary pull-left mr-2" (click)="verCalificacion()">Ver calificacion</button> + + <button *ngIf="!esAlumno" class="btn btn-sm btn-secondary pull-left mr-2" (click)="compartirArchivo()">Compartir</button> + + <div class="pull-left" *ngIf="archivoSeleccionado"> + Nombre: {{archivoSeleccionado?.nombre}} - Creado: {{archivoSeleccionado?.fechaCreacion | date}} + </div> + <div class="pull-left" *ngIf="!archivoSeleccionado">Seleccione un archivo para previsualizarlo</div> + <div class="pull-right" *ngIf="archivoSeleccionado"> + <label class="custom-control custom-checkbox" *ngIf="!esAlumno"> + <input type="checkbox" *ngIf="archivoSeleccionado.editable" (click)="setSoloLectura()" class="custom-control-input"> + <input type="checkbox" *ngIf="!archivoSeleccionado.editable" checked (click)="setSoloLectura()" class="custom-control-input"> + <span class="custom-control-indicator"></span> + <span class="custom-control-description">Solo lectura</span> + </label> + </div> + </div> + <div *ngIf="!archivoSeleccionado" class="card-block"> + <div class="previewArchivoNoSeleccionado" style="width: 100%; text-align: center;"> + <i style="color: rgb(220,220,220); font-size: 10em; padding: 0.1em" class="fa fa-file-text"></i> + </div> + </div> + <codemirror class="codemirrorArchivo" *ngIf="archivoSeleccionado" [(ngModel)]="preview" [config]="configCodeMirror" [ngStyle]="{'font-size': configCodeMirror.fontSize+'px'}"> + </codemirror> + </div> + + </div> + +</div> +</div> + + + + diff --git a/src/app/layout/archivos/archivos.component.ts b/src/app/layout/archivos/archivos.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..09ceca704024f9f7ef76acbde016818648a8df64 --- /dev/null +++ b/src/app/layout/archivos/archivos.component.ts @@ -0,0 +1,535 @@ +import { Component,ViewChild } from '@angular/core'; +import { Archivo } from '../../shared/objects/archivo'; +import { AuthenticationService } from '../../shared/services/authentication.service'; +import { HaskellService } from '../../shared/services/haskell.service'; +import { SessionService } from '../../shared/services/session.service'; +import { NotificacionService } from '../../shared/services/notificacion.service'; +import { Router } from '@angular/router'; +import { NuevoArchivo } from './nuevoArchivo.component'; +import { VerCalificacionComponent } from './verCalificacion.component'; +import { CompartirArchivoComponent } from './compartirArchivo.component'; +import { ConfirmarEliminar } from './confirmarEliminar.component'; +import { DialogService } from "ng2-bootstrap-modal"; +import { ConfirmComponent } from '../../shared/modal/confirm.component'; +import { SeleccionarDirectorioMove } from './seleccionarDirectorio.component'; +import { CodemirrorComponent } from 'ng2-codemirror'; +import { NgbPopoverConfig, NgbPopover} from '@ng-bootstrap/ng-bootstrap'; + +import 'codemirror/mode/haskell/haskell'; +import 'codemirror/addon/display/panel'; +import 'codemirror/addon/hint/show-hint'; +import 'codemirror/addon/hint/anyword-hint'; +import 'codemirror/mode/markdown/markdown'; + +@Component({ + moduleId: module.id, + selector: 'archivos', + templateUrl: './archivos.component.html' +}) + +export class ArchivosComponent { + archivos : Archivo[] = []; + archivosCompartidos: Archivo[] = []; + archivosCompartidosSinDuplicados: Archivo [] = []; + archivoSeleccionado: Archivo; + loading: boolean = false; + loadingCompartidos:boolean = false; + filtroNombre: string = ''; + idRecorridos :any = []; + esAlumno :boolean; + tree: any; + preview: string = ''; + directorioActual:any; + sortFunction:any; + configCodeMirror = JSON.parse(sessionStorage.getItem('codeMirrorConfig')) ; + + constructor( + private router: Router, + private notifService: NotificacionService, + private authService: AuthenticationService, + private haskellService: HaskellService, + private sessionService: SessionService, + private dialogService:DialogService + ){ + this.esAlumno = JSON.parse(sessionStorage.getItem("currentUser")).tipo ==="alumno"; + this.directorioActual = {}; + this.directorioActual.archivos = []; + this.configCodeMirror.readOnly = true; + } + @ViewChild(CodemirrorComponent) codemirror: CodemirrorComponent; + + ngOnInit(){ + this.sortFunction = 'tipo' + let cedula = this.authService.getUser().cedula; + this.loading = true; + this.haskellService.getArchivos(cedula) + .subscribe( + archivos => { + this.archivos = archivos; + this.loading = false; + this.buildTreeFromList(); + + }, + error => console.log(error) + ); + + if(this.esAlumno){ + this.loadingCompartidos = true; + this.haskellService.getArchivosCompartidosAlumno(cedula) + .subscribe( + archivos => { + this.archivosCompartidos = archivos; + this.archivosCompartidosSinDuplicados = archivos.filter(arch => arch.archivoOrigenId != -1 || !archivos.some(a => a.archivoOrigenId == arch.id)); + this.loadingCompartidos = false; + }, + error => console.log(error) + ); + + } + } + ordenarMixto(){ + //1. primero las carpetas. + this.archivosCompartidosSinDuplicados = this.archivosCompartidosSinDuplicados.sort(this.ordenarTipo); + this.directorioActual.archivos= this.directorioActual.archivos.sort(this.ordenarTipo); + + var archs1 = this.directorioActual.archivos; + var archs2 = this.archivosCompartidosSinDuplicados; + + var archs1_directorios = archs1.filter( + function (a) { + return a.directorio; + } + ); + + var archs1_archivos =archs1.filter( + function(a){ + return !a.directorio; + } + + ); + + var archs2_directorios = archs2.filter( + function (a) { + return a.directorio; + } + ); + + var archs2_archivos =archs2.filter( + function(a){ + return !a.directorio; + } + + ); + //2. dentro de cada categorÃa ordeno alfabéticamente. + + archs1_archivos = archs1_archivos.sort(this.ordenarAlph); + archs1_directorios = archs1_directorios.sort(this.ordenarAlph); + archs2_archivos = archs2_archivos.sort(this.ordenarAlph); + archs2_directorios = archs2_directorios.sort(this.ordenarAlph); + + for(var i in archs1_archivos){ + archs1_directorios.push(archs1_archivos[i]) + } + for(var i in archs2_archivos){ + archs2_directorios.push(archs2_archivos[i]) + } + + this.directorioActual.archivos = archs1_directorios; + this.archivosCompartidosSinDuplicados = archs2_directorios; + } + + ordenarAlph(a,b){ + if(a.nombre.toLowerCase() < b.nombre.toLowerCase()) return -1; + if(a.nombre.toLowerCase() > b.nombre.toLowerCase()) return 1; + return 0; + } + ordenarFecha(a, b){ + if(a.fechaCreacion < b.fechaCreacion) return -1; + if(a.fechaCreacion > b.fechaCreacion) return 1; + return 0; + } + ordenarTipo(a,b){ + if(a.directorio && !b.directorio) return -1; + if(!a.directorio && b.directorio) return 1; + return 0; + } + ordenarPorTipo(){ + this.sortFunction='tipo'; + this.ordenarArchivos(); + } + ordenarPorFecha(){ + this.sortFunction='fecha'; + this.ordenarArchivos(); + } + ordenarFechaCreacion(){ + this.archivosCompartidosSinDuplicados = this.archivosCompartidosSinDuplicados.sort(this.ordenarFecha); + this.directorioActual.archivos= this.directorioActual.archivos.sort(this.ordenarFecha); + } + + ordenarArchivos(){ + var tipo = this.sortFunction; + if(tipo==='tipo'){ + this.ordenarMixto(); + } else if(tipo==='fecha'){ + this.ordenarFechaCreacion(); + } + } + + mostrarEliminarDialogo(){ + if(this.archivoSeleccionado){ + //Si el archivo es del alumno lo puedo eliminar. + //(No se controla por creador dado que los compartidos mantienen este atributo) + if(this.archivos.some(arch => arch.id == this.archivoSeleccionado.id)){ + var that = this; + let disposable = this.dialogService.addDialog(ConfirmarEliminar, { + nombreArchivo:that.archivoSeleccionado.nombre, + esDirectorio:that.archivoSeleccionado.directorio, + parentContext :that}) + .subscribe((isConfirmed)=>{ + + if(isConfirmed) { + + + //codeMirrorRef.options.readOnly = false; + //componentRef.editDialogFired = true; + } + }); + }else{ + this.notifService.warning("Sin permisos para eliminar el archivo"); + } + }else{ + this.notifService.warning("Archivo no seleccionado"); + } + } + seleccionarDirectorioAMover(){ + if(this.archivoSeleccionado){ + //Si el archivo es del alumno lo puedo eliminar. + //(No se controla por creador dado que los compartidos mantienen este atributo) + if(this.archivos.some(arch => arch.id == this.archivoSeleccionado.id)){ + var that = this; + let disposable = this.dialogService.addDialog(SeleccionarDirectorioMove, { + archivos:that.tree, + directorioActual:that.directorioActual, + nombre:that.archivoSeleccionado.nombre, + directorio:that.archivoSeleccionado.directorio, + parent :that}) + .subscribe((isConfirmed)=>{ + + if(isConfirmed) { + + + } + }); + }else{ + this.notifService.warning("Sin permisos para mover el archivo"); + } + }else{ + this.notifService.warning("Archivo no seleccionado"); + } + } + + recargarArchivos(idDirectorioActual){ + let cedula = this.authService.getUser().cedula; + this.loading = true; + this.haskellService.getArchivos(cedula) + .subscribe( + archivos => { + this.archivos = archivos; + this.loading = false; + this.buildTreeFromList_setearDirectorioActual(idDirectorioActual); + + }, + error => console.log(error) + ); + } + + + navBack(){ + var that =this; + if(this.directorioActual.padreId!==-1){ + var padre = this.archivos.filter(function(a){return a.id===that.directorioActual.padreId})[0]; + this.directorioActual = padre; + } + + } + + setSoloLectura = function(arch: Archivo){ + + this.archivoSeleccionado.editable = !this.archivoSeleccionado.editable; + this.haskellService.editarArchivo(this.archivoSeleccionado.id,this.archivoSeleccionado) + .subscribe( + archivo => { + console.log("Archivo modificado"); + + }, + error => { + this.notifService.error(error); + }); + + } + + cargarArchivo(){ + if(this.archivoSeleccionado){ + if(this.archivoSeleccionado.directorio){ + this.notifService.warning('No se seleccionó ningún archivo',false); + }else{ + //Si el archivo es compartido con el grupo, editabe y no lo he editado, lo voy a buscar al servidor. + if(this.archivosCompartidos.some(arch => arch.id == this.archivoSeleccionado.id) && this.archivoSeleccionado.editable && this.archivoSeleccionado.archivoOrigenId == -1){ + if(this.hayArchivoMio()){ + this.seleccionarArchivoMio(); + this.sessionService.setArchivo(this.archivoSeleccionado); + this.router.navigate(['/matefun']); + }else{ + let cedula = this.authService.getUser().cedula; + this.haskellService.getCopiaArchivoCompartidoGrupo(cedula,this.archivoSeleccionado.id).subscribe( + archivo => { + this.sessionService.setArchivo(archivo); + this.router.navigate(['/matefun']); + }, + error =>{ + console.log(error); + }); + } + }else{ + this.sessionService.setArchivo(this.archivoSeleccionado); + this.router.navigate(['/matefun']); + } + } + }else{ + this.notifService.warning("Archivo no seleccionado"); + } + } + + confirmarEntrega(){ + let disposable = this.dialogService.addDialog(ConfirmComponent, { + title:'Entregar archivo', + message:'¿Desea entregar el archivo "'+this.archivoSeleccionado.nombre+'"?\nNo se podrá editar luego de la entrega.', + confirmText: 'Entregar', + cancelText: 'Cancelar' + }) + .subscribe((isConfirmed)=>{ + if(isConfirmed) { + this.entregarArchivo(); + } + }); + } + + entregarArchivo(){ + this.archivoSeleccionado.estado = 'Entregado'; + this.haskellService.editarArchivo(this.archivoSeleccionado.id, this.archivoSeleccionado) + .subscribe( + archivo => { + this.archivoSeleccionado = archivo; + }, + error => { + this.notifService.error(error); + }); + } + + buildTreeFromList_setearDirectorioActual(idDirectorioActual){ + var archivos = this.archivos; + this.sessionService.setArchivosList(archivos); + var root :Archivo; + + for(var a in archivos){ + var arch = archivos[a]; + if(arch.padreId===-1){ + root = arch; + } + } + this.idRecorridos = [root.id]; + var archivos2 = archivos.filter( + function(a){ + return a.id!==root.id; + } + ); + var directorioActual = this.archivos.filter(function(a){return a.id===idDirectorioActual})[0]; + var tree = this.buildTree(archivos2,root); + this.tree = tree; + this.directorioActual = directorioActual; + this.ordenarArchivos(); + this.sessionService.setArchivosTree(tree); + } + + buildTreeFromList (){ + + var archivos = this.archivos; + this.sessionService.setArchivosList(archivos); + var root :Archivo; + + for(var a in archivos){ + var arch = archivos[a]; + if(arch.padreId===-1){ + root = arch; + } + } + this.idRecorridos = [root.id]; + var archivos2 = archivos.filter( + function(a){ + return a.id!==root.id; + } + ); + var tree = this.buildTree(archivos2,root); + this.tree = tree; + this.directorioActual = tree; + this.ordenarArchivos(); + this.sessionService.setArchivosTree(tree); + } + + + buildTree(archivos, root){ + root.archivos = this.getArchivos(root.id,archivos); + for(var a in root.archivos){ + if(root.archivos[a].directorio && this.idRecorridos[root.archivos[a].id] === undefined){ + var id = root.archivos[a].id; + var archivos2 = archivos.filter(function(a){return a.id!==id}); + root.archivos[a] = this.buildTree(archivos2 ,root.archivos[a]); + } + } + return root; + } + + getArchivos(id,archivos){ + return archivos.filter( + function(a){ + return a.padreId === id; + } + ); + } + + cantArchivos(idPadre,archivos){ + return archivos.filter(function(a){a.padreId ===idPadre;}).length; + } + + elem(id,archivos){ + if(archivos===[]){ + return false; + }else { + return archivos.filter( + function(a){ + a.id ===id; + }).length>0; + } + } + + mkdir(){ + var that = this; + let disposable = this.dialogService.addDialog(NuevoArchivo, { + nombre:'', + descripcion:'', + esDirectorio:true, + parentContext :that}) + .subscribe((isConfirmed)=>{ + + if(isConfirmed) { + + + //codeMirrorRef.options.readOnly = false; + //componentRef.editDialogFired = true; + } + }); + } + mkFile(){ + var that = this; + let disposable = this.dialogService.addDialog(NuevoArchivo, { + nombre:'', + descripcion:'', + esDirectorio:false, + parentContext :that}) + .subscribe((isConfirmed)=>{ + + if(isConfirmed) { + + + //codeMirrorRef.options.readOnly = false; + //componentRef.editDialogFired = true; + } + }); + } + + seleccionarArchivo = function(arch: Archivo){ + if(arch.directorio){ + this.directorioActual=arch; + }else { + this.sessionService.setDirectorioActual(this.directorioActual); + this.sessionService.cargarDependencias(arch); + } + this.archivoSeleccionado = arch; + this.preview = arch.contenido; + this.ordenarArchivos(); + } + + compartirArchivo(){ + if(this.archivoSeleccionado){ + var grupos = this.sessionService.getGrupos(); + if(grupos == undefined){ + this.haskellService.getGrupos(this.authService.getUser().cedula) + .subscribe( + gruposRest => { + this.sessionService.setGrupos(grupos); + this.dialogService.addDialog(CompartirArchivoComponent, { + grupos:gruposRest, + archivo:this.archivoSeleccionado, + parent:this + }) + .subscribe((isConfirmed)=>{ + if(isConfirmed) { + this.notifService.success("confirmado?"); + } + }); + + + }, + error => { + + }) + }else{ + this.dialogService.addDialog(CompartirArchivoComponent, { + grupos:grupos, + archivo:this.archivoSeleccionado, + parent:this + }) + .subscribe((isConfirmed)=>{ + if(isConfirmed) { + this.notifService.success("confirmado?"); + } + }); + } + }else{ + this.notifService.warning("Archivo no seleccionado"); + } + + } + + hayArchivoOriginal(){ + return !this.archivoSeleccionado.directorio && this.archivosCompartidos.some(a => a.id == this.archivoSeleccionado.archivoOrigenId); + } + + seleccionarArchivoOriginal(){ + this.archivoSeleccionado = this.archivosCompartidos.find(arch => arch.id == this.archivoSeleccionado.archivoOrigenId); + this.preview = this.archivoSeleccionado.contenido; + } + + hayArchivoMio(){ + return !this.archivoSeleccionado.directorio && this.archivosCompartidos.some(a => a.archivoOrigenId == this.archivoSeleccionado.id); + } + + seleccionarArchivoMio(){ + this.archivoSeleccionado = this.archivosCompartidos.find(a => a.archivoOrigenId == this.archivoSeleccionado.id); + this.preview = this.archivoSeleccionado.contenido; + } + + verCalificacion(){ + this.dialogService.addDialog(VerCalificacionComponent, { + archivo:this.archivoSeleccionado + }).subscribe((isConfirmed)=>{ + if(isConfirmed) { + this.notifService.success("confirmado?"); + } + }); + } + + + + +} diff --git a/src/app/layout/archivos/archivos.module.ts b/src/app/layout/archivos/archivos.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..757246bce89ac97b6005a9447db97e71147f85a2 --- /dev/null +++ b/src/app/layout/archivos/archivos.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ArchivosComponent } from './archivos.component'; +import { CommonModule } from '@angular/common'; +import { ArchivosRoutingModule } from './archivos-routing.module'; +import { FilterPipe } from '../../shared/pipes/filter.pipe'; +import { ConfirmComponent } from '../../shared/modal/confirm.component'; +import { NuevoArchivo } from './nuevoArchivo.component'; +import { VerCalificacionComponent } from './verCalificacion.component'; +import { CompartirArchivoComponent } from './compartirArchivo.component'; +import { SeleccionarDirectorioMove } from './seleccionarDirectorio.component'; +import { ConfirmarEliminar } from './confirmarEliminar.component'; +import { DialogService } from "ng2-bootstrap-modal"; +import { BootstrapModalModule } from 'ng2-bootstrap-modal'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { CodemirrorModule } from 'ng2-codemirror'; +import { NotificacionModule } from '../../notificacion/notificacion.module'; + +@NgModule({ + imports: [CommonModule, ArchivosRoutingModule, FormsModule,BootstrapModalModule, NgbModule, CodemirrorModule,NotificacionModule], + declarations: [ArchivosComponent, FilterPipe,NuevoArchivo, VerCalificacionComponent, ConfirmComponent, CompartirArchivoComponent,ConfirmarEliminar, SeleccionarDirectorioMove], + entryComponents: [ + NuevoArchivo, + VerCalificacionComponent, + ConfirmComponent, + CompartirArchivoComponent, + ConfirmarEliminar, + SeleccionarDirectorioMove + ], + exports: [ArchivosComponent] +}) + +export class ArchivosModule { } diff --git a/src/app/layout/archivos/compartirArchivo.component.ts b/src/app/layout/archivos/compartirArchivo.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..02b5187edc781c1f383b9887da049e1cc53e6210 --- /dev/null +++ b/src/app/layout/archivos/compartirArchivo.component.ts @@ -0,0 +1,64 @@ +import { Component } from '@angular/core'; +import { DialogComponent, DialogService } from "ng2-bootstrap-modal"; +import { Archivo } from '../../shared/objects/archivo'; +import { Grupo } from '../../shared/objects/grupo'; + +export interface ConfirmModel { + archivo: Archivo; + grupos:any; + parent:any; +} +@Component({ + selector: 'confirm', + template: `<div class="modal-dialog" style="margin-top:100px;"> + <div class="modal-content"> + <div class="modal-header"> + <h6 class="modal-title pull-lefth">Compartir "{{archivo.nombre}}" con:</h6> + <button type="button" class="close" (click)="close()" style="margin-rigth:8px;">×</button> + </div> + <div class="modal-body" style="height:350px;overflow-y: scroll;"> + <div> + <div class="list-group"> + <button *ngFor="let g of grupos" type="button" (click)="seleccionarGrupo(g)" style="cursor:pointer" class="list-group-item list-group-item-action" [ngClass]="{'active':grupo!=undefined && g.grado == grupo.grado && g.grupo == grupo.grupo && g.anio == grupo.anio && g.liceoId == grupo.liceoId}"> + <i class="fa fa-group" style="margin-right:10px; font-size: 3em; cursor: pointer;" aria-hidden="true" ></i> + {{g.grado+"°"+g.grupo+" - "+g.anio}} + </button> + </div> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" (click)="compartir()">Compartir</button> + </div> + </div> + </div>` +}) +export class CompartirArchivoComponent extends DialogComponent<ConfirmModel, boolean> implements ConfirmModel { + archivo: Archivo; + grupos:any; + grupo: Grupo; + parent: any; + + constructor(dialogService: DialogService) { + super(dialogService); + } + + seleccionarGrupo(grupo){ + this.grupo = grupo; + } + + compartir(){ + if(this.grupo){ + this.parent.haskellService.compartirArchivoGrupo(this.grupo, this.archivo.id) + .subscribe( + success => { + this.parent.notifService.success("Archivo compartido"); + this.close(); + }, + error => { + this.parent.notifService.error(error); + }); + }else{ + this.parent.notifService.error("Seleccione un grupo"); + } + } +} \ No newline at end of file diff --git a/src/app/layout/archivos/confirmarEliminar.component.ts b/src/app/layout/archivos/confirmarEliminar.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..1cb8755e497f4ecedc0064e2acd48ec16f5c1402 --- /dev/null +++ b/src/app/layout/archivos/confirmarEliminar.component.ts @@ -0,0 +1,64 @@ +import { Component } from '@angular/core'; +import { DialogComponent, DialogService } from "ng2-bootstrap-modal"; +import { Archivo } from '../../shared/objects/archivo'; +export interface ConfirmModel { + nombreArchivo:string; + esDirectorio:boolean; + parentContext: any; + +} +@Component({ + selector: 'confirm', + template: `<div class="modal-dialog" style="margin-top:100px;"> + <div class="modal-content"> + <div class="modal-header"> + <h6 class="modal-title" *ngIf="!esDirectorio">Eliminar archivo</h6> + <h6 class="modal-title" *ngIf="esDirectorio">Eliminar carpeta</h6> + <button type="button" class="close" (click)="close()" style="margin-left:8px;">×</button> + </div> + <div class="modal-body"> + <p *ngIf="!esDirectorio">¿Está seguro que desea eliminar el archivo {{nombreArchivo}}?</p> + <p *ngIf="esDirectorio">¿Está seguro que desea eliminar la carpeta {{nombreArchivo}}?</p> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" (click)="close()">Cancelar</button> + <button type="button" class="btn btn-success" (click)="confirmarEliminar()">Eliminar</button> + </div> + </div> + </div>` +}) +export class ConfirmarEliminar extends DialogComponent<ConfirmModel, boolean> implements ConfirmModel { + nombreArchivo: string; + esDirectorio: boolean; + parentContext: any; + constructor(dialogService: DialogService) { + super(dialogService); + } + confirmarEliminar(){ + var that = this.parentContext; + var directorio = this.parentContext.archivoSeleccionado.directorio; + this.parentContext.archivoSeleccionado.eliminado = true; + if(directorio){ + delete this.parentContext.archivoSeleccionado['archivos']; + } + this.parentContext.haskellService.editarArchivo(this.parentContext.archivoSeleccionado.id,this.parentContext.archivoSeleccionado) + .subscribe( + archivo => { + console.log("Archivo eliminado"); + if(directorio){ + var idDirActual = that.directorioActual.padreId; + }else { + var idDirActual = that.directorioActual.id; + } + that.recargarArchivos(idDirActual); + that.archivoSeleccionado = null; + + }, + error => { + this.parentContext.notifService.error(error); + }); + this.close(); + + } + +} \ No newline at end of file diff --git a/src/app/layout/archivos/index.ts b/src/app/layout/archivos/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..ca9d5018ad6489d2ad15d7e146a5b152c6138aff --- /dev/null +++ b/src/app/layout/archivos/index.ts @@ -0,0 +1,4 @@ +/** + * This barrel file provides the export for the lazy loaded BlankpageComponent. + */ +export * from './archivos.component'; \ No newline at end of file diff --git a/src/app/layout/archivos/nuevoArchivo.component.ts b/src/app/layout/archivos/nuevoArchivo.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..d51363e99c4abb73e8d37bfed3804858bab34ed0 --- /dev/null +++ b/src/app/layout/archivos/nuevoArchivo.component.ts @@ -0,0 +1,85 @@ +import { Component } from '@angular/core'; +import { DialogComponent, DialogService } from "ng2-bootstrap-modal"; +import { Archivo } from '../../shared/objects/archivo'; +export interface ConfirmModel { + nombre:string; + descripcion:string; + parentContext: any; + esDirectorio:boolean; + +} +@Component({ + selector: 'confirm', + template: `<div class="modal-dialog" style="margin-top:100px;"> + <div class="modal-content"> + + <div class="modal-header"> + <h6 class="modal-title" *ngIf="esDirectorio">Nueva carpeta</h6> + <h6 class="modal-title" *ngIf="!esDirectorio">Nuevo archivo</h6> + <button type="button" class="close" (click)="close()" style="margin-left:8px;">×</button> + </div> + + <div class="modal-body"> + <form> + <div class="form-group"> + <label for="recipient-name" class="form-control-label">Nombre:</label> + <input type="text" class="form-control" id="recipient-name" [(ngModel)]="nombre" [ngModelOptions]="{standalone: true}" > + </div> + <div class="form-group" *ngIf="esDirectorio"> + <label for="message-text" class="form-control-label">Descripción:</label> + <textarea class="form-control" id="message-text" [(ngModel)]="descripcion" [ngModelOptions]="{standalone: true}" ></textarea> + </div> + </form> + </div> + + <div class="modal-footer"> + <button type="button" class="btn btn-success" (click)="confirm()">Crear</button> + </div> + + </div> + </div>` +}) +export class NuevoArchivo extends DialogComponent<ConfirmModel, boolean> implements ConfirmModel { + nombre: string; + esDirectorio: boolean; + descripcion: string; + parentContext: any; + constructor(dialogService: DialogService) { + super(dialogService); + } + confirm() { + var nombre = this.nombre; + var desc = this.descripcion; + var archivo : Archivo; + archivo = new Archivo(); + archivo.cedulaCreador = this.parentContext.directorioActual.cedulaCreador; + if(this.esDirectorio){ + archivo.contenido = desc; + } else { + archivo.contenido = ''; + } + archivo.directorio = this.esDirectorio; + archivo.editable = true; + archivo.fechaCreacion = new Date(); + archivo.nombre = nombre; + archivo.padreId = this.parentContext.directorioActual.id; + var that = this.parentContext; + var regex = /^[A-Z]/ + if(regex.test(nombre)){ + this.parentContext.haskellService.crearArchivo(archivo).subscribe( + archivo => { + var id = that.directorioActual.id; + that.recargarArchivos(id); + }, + error => { + this.parentContext.notifService.error(error); + }); + + + this.close(); + }else{ + alert("Nombre de archivo debe iniciar con mayusula.") + } + } + +} \ No newline at end of file diff --git a/src/app/layout/archivos/seleccionarDirectorio.component.ts b/src/app/layout/archivos/seleccionarDirectorio.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..9ca43a236fd7d186c80f8a4a36451e61485ec3c6 --- /dev/null +++ b/src/app/layout/archivos/seleccionarDirectorio.component.ts @@ -0,0 +1,85 @@ +import { Component } from '@angular/core'; +import { DialogComponent, DialogService } from "ng2-bootstrap-modal"; +import { Archivo } from '../../shared/objects/archivo'; +export interface ConfirmModel { + directorio: boolean; + archivos:any; + directorioActual:any; + parent:any; + nombre:string; + +} +@Component({ + selector: 'confirm', + template: `<div class="modal-dialog" style="margin-top:100px;"> + <div class="modal-content"> + <div class="modal-header"> + <h6 class="modal-title pull-lefth">¿Dónde quieres mover el archivo?</h6> + <button type="button" class="close" (click)="close()" style="margin-rigth:8px;">×</button> + </div> + <div class="modal-body" style="height:350px;overflow-y: scroll;"> + <div> + <div class="list-group" > + <button *ngFor="let arch of directorioActual.archivos" type="button" (click)="navToDir(arch)" style="cursor:pointer" class="list-group-item list-group-item-action"> + <i *ngIf="arch.directorio" class="fa fa-folder" style="margin-right:10px; font-size: 3em; cursor: pointer;" aria-hidden="true" ></i> + <i *ngIf="!arch.directorio" class="fa fa-file-text" style="margin-right:10px;font-size: 3em; cursor: pointer;" aria-hidden="true"></i> + {{arch.nombre}} + </button> + </div> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" (click)="navBack()">Atras</button> + <button type="button" class="btn btn-primary" (click)="move()">Mover aquÃ</button> + </div> + </div> + </div>` +}) +export class SeleccionarDirectorioMove extends DialogComponent<ConfirmModel, boolean> implements ConfirmModel { + directorio: boolean; + archivos:any; + directorioActual:any; + parent:any; + nombre:string; + + constructor(dialogService: DialogService) { + super(dialogService); + } + move() { + // we set dialog result as true on click on confirm button, + // then we can get dialog result from caller code + var that = this; + if(this.nombre!==undefined && this.nombre!==""){ + this.parent.archivoSeleccionado.padreId = this.directorioActual.id; + if(this.parent.archivoSeleccionado.directorio){ + delete this.parent.archivoSeleccionado['archivos']; + } + this.parent.haskellService.editarArchivo(this.parent.archivoSeleccionado.id,this.parent.archivoSeleccionado) + .subscribe( + archivo => { + + that.parent.recargarArchivos(this.directorioActual.id); + that.parent.archivoSeleccionado = null; + + }, + error => { + this.parent.notifService.error(error); + }); + + } + this.close(); + } + + navToDir(arch){ + if(arch.directorio){ + this.directorioActual = arch; + } + } + + navBack(){ + var idPadre = this.directorioActual.padreId; + var archivosList = this.parent.sessionService.getArchivosList(); + var nuevoDirectorioActual = archivosList.filter(function(a){return a.id===idPadre})[0]; + this.directorioActual=nuevoDirectorioActual; + } +} \ No newline at end of file diff --git a/src/app/layout/archivos/verCalificacion.component.ts b/src/app/layout/archivos/verCalificacion.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..d8a438dee6482b284204af452ee57aa593478bb3 --- /dev/null +++ b/src/app/layout/archivos/verCalificacion.component.ts @@ -0,0 +1,35 @@ +import { Component } from '@angular/core'; +import { DialogComponent, DialogService } from "ng2-bootstrap-modal"; +import { Archivo, Evaluacion } from '../../shared/objects/archivo'; + +export interface ConfirmModel { + archivo: Archivo; +} +@Component({ + selector: 'confirm', + template: `<div class="modal-dialog" style="margin-top:100px;"> + <div class="modal-content"> + <div class="modal-header"> + <h6 class="modal-title pull-lefth">Calificación "{{archivo.nombre}}":</h6> + <button type="button" class="close" (click)="close()" style="margin-rigth:8px;">×</button> + </div> + <div class="modal-body"> + <div> + <label><b>Fecha: </b></label> {{archivo.evaluacion.fecha | date}}<br> + <label><b>Nota (1-100): </b></label> {{archivo.evaluacion.nota}}<br> + <label><b>Detalle: </b></label> {{archivo.evaluacion.descripcion}} + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" (click)="close()">Cerrar</button> + </div> + </div> + </div>` +}) +export class VerCalificacionComponent extends DialogComponent<ConfirmModel, boolean> implements ConfirmModel { + archivo: Archivo; + + constructor(dialogService: DialogService) { + super(dialogService); + } +} \ No newline at end of file diff --git a/src/app/layout/canvas/canvas.component.html b/src/app/layout/canvas/canvas.component.html new file mode 100644 index 0000000000000000000000000000000000000000..7495fcb82f1093ee84279f510a36f9fb776b4ec6 --- /dev/null +++ b/src/app/layout/canvas/canvas.component.html @@ -0,0 +1,49 @@ +<div class="card"> +<!-- <div class="card-header"> + <label>Gráficas</label> + </div> --> + <div class="card-block contenedor-canvas" > + <button placement="bottom" [ngbPopover]=popoverTipoZoom triggers="mouseenter:mouseleave:click" data-placement="bottom" class="btn btn-sm btn-secondary" style="float:right; margin-right: 198px; margin-top: -55px" (click)=cambiarTipoZoom() >{{tipoZoom}}</button> + <ng-template #popoverTipoZoom> + {{tipoZoomDesc}} + </ng-template> + <button ngbPopover="Zoom +" triggers="mouseenter:mouseleave" data-placement="bottom" class="btn btn-sm btn-secondary" style="float:right; margin-right: 165px; margin-top: -55px" (click)=zoomMas() ><i class="fa fa-plus"></i></button> + <button ngbPopover="Zoom -" data-placement="bottom" triggers="mouseenter:mouseleave" class="btn btn-sm btn-secondary" style="float:right; margin-right: 132px; margin-top: -55px" (click)=zoomMenos() ><i class="fa fa-minus"></i></button> + <button ngbPopover="Centrar" triggers="mouseenter:mouseleave" data-placement="bottom" class="btn btn-sm btn-secondary" style=" float:right; margin-right: 99px; margin-top: -55px" (click)="centrarCanvas()" ><i class="fa fa-arrows"></i></button> + <button ngbPopover="Borrar" triggers="mouseenter:mouseleave" data-placement="bottom" class="btn btn-sm btn-secondary" style=" float:right; margin-right: 66px; margin-top: -55px" (click)="limpiarCanvas()" ><i class="fa fa-trash"></i></button> + + <button ngbPopover="Descargar PNG" triggers="mouseenter:mouseleave" data-placement="bottom" class="btn btn-sm btn-secondary" style="float:right; margin-right: 33px; margin-top: -55px" (click)=exportImg() ><i class="fa fa-download"></i></button> + <a id="dl" download="Canvas.png" style="display: none">Download Canvas</a> + + <button class="btn btn-sm btn-secondary" style="float:right; margin-top: -55px" id="popover" placement="bottom" [ngbPopover]=popoverCanvas popoverTitle="Configuración" #popover="ngbPopover" tiggers="click"> + <i class="fa fa-gear"></i> + </button> + <ng-template #popoverCanvas style="width: 15em"> + <div style="width: 8em"> + <div class="form-group"> + <label> + <input type="checkbox" style="width: 15px; display: inline-block;" name="evaluacionVertical" class="form-control form-control-sm" [checked]=evaluacionVertical (click)="mostrarEvaluacionVertical()"> + Evaluacion + </label> + <br> + <label> + <input type="checkbox" style="width: 15px; display: inline-block;" name="mostrarGrilla" class="form-control form-control-sm" [checked]=mostrarGrilla (click)="mostrarOcultarGrilla()"> + Grilla + </label> + <br> + <label> + <input type="checkbox" style="width: 15px; display: inline-block;" name="mostrarEjes" class="form-control form-control-sm" [checked]=mostrarEjes (click)="mostrarOcultarEjes()"> + Ejes + </label> + </div> + </div> + </ng-template> + + <button class="btn btn-sm btn-secondary" data-placement="bottom" *ngIf="!animando && elementosAnimacion.length>0" style=" float:left; margin-top: -5px; margin-right: 5px" (click)="play()" ><i class="fa fa-play"></i></button> + <button class="btn btn-sm btn-secondary" data-placement="bottom" *ngIf="animando && elementosAnimacion.length>0" style=" float:left; margin-top: -5px; margin-right: 5px" (click)="pause()" ><i class="fa fa-pause"></i></button> + <ngb-progressbar style="float: none" *ngIf="elementosAnimacion.length>0" type="info" [value]="((frameAnimacion+1)/elementosAnimacion.length)*100"></ngb-progressbar> + <canvas #canvasElement width="2000" height="2000" style="max-width: 100%;" (mousemove)="moveGraph($event)" (touchmove)="moveGraph($event)" (touchstart)="moveGraph($event)" (touchend)="moveGraph($event)" (mouseleave)="leaveCanvas($event)" (mousewheel)="zoomGraph($event)" (DOMMouseScroll)="zoomGraph($event)" id="myCanvas"> + </canvas> + + </div> +</div> \ No newline at end of file diff --git a/src/app/layout/canvas/canvas.component.ts b/src/app/layout/canvas/canvas.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab6563203bded6b9192412fe7f2d23b8dda77d99 --- /dev/null +++ b/src/app/layout/canvas/canvas.component.ts @@ -0,0 +1,1063 @@ +import { Component, ViewChild, HostListener, ComponentRef, ElementRef, Input, Output} from '@angular/core'; +import { NgbPopoverConfig, NgbPopover} from '@ng-bootstrap/ng-bootstrap'; +import { NgbPopoverWindow } from '@ng-bootstrap/ng-bootstrap/popover/popover'; +import { GHCIService } from '../../shared/services/ghci.service'; +@Component({ + moduleId: module.id, + selector: 'canvas-component', + templateUrl: './canvas.component.html', + host: { + '(window:resize)': 'onResize($event)' + } +}) + +export class CanvasComponent { + //addEventListener('mousemove', tellPos, false); + public constructor(private ghciService: GHCIService) { + ghciService.messages.subscribe( + canvas=>{ + this.limpiarCanvas(); + if(canvas.tipo == 'canvas'){ + this.objetos = JSON.parse(canvas.resultado); + this.elementosAnimacion = []; + this.frameAnimacion = 0; + + this.dibujarObjetos(); + }else if (canvas.tipo == 'animacion'){ + this.elementosAnimacion = canvas.resultado.map(res => JSON.parse(res)); + this.frameAnimacion = 0; + this.animando = true; + this.animar(); + }else if (canvas.tipo == 'graph'){ + var jsonCanvas = JSON.parse(canvas.resultado); + var fun = this.generarFuncion(jsonCanvas); + this.objetos = [{tipo:'grafica',ecuacion: eval(fun), color:'black',thickness: 2}] + this.dibujarObjetos(); + } + } + , + error=>{ + this.objetos = []; + }) + } + + objetos:any = []; + evaluacionVertical:boolean = true; + elementosAnimacion:any = []; + frameAnimacion:number = 0; + animando: boolean = true; + mostrarEjes: boolean = true; + mostrarGrilla: boolean = true; + //Todo, abscisa y ordenada + tipoZoom = "Todo"; + tipoZoomDesc = "Zoom en ambos ejes"; + + + + @ViewChild('canvasElement') canvasRef: ElementRef; + + @ViewChild('popover') popover: NgbPopover; + + onResize(event){ + if(this.canvasRef.nativeElement.offsetParent){ + var pixelRatio = window.devicePixelRatio || 1; + if(pixelRatio > 2){ + pixelRatio = 2; + }else if (pixelRatio < 1){ + pixelRatio = 1; + } + + this.canvasRef.nativeElement.width = this.canvasRef.nativeElement.offsetParent.offsetWidth * pixelRatio * 0.94; + this.canvasRef.nativeElement.height = this.canvasRef.nativeElement.offsetParent.offsetHeight * pixelRatio * 0.94; + var relacionAspecto = this.canvasRef.nativeElement.width/this.canvasRef.nativeElement.height; + this.Graph(relacionAspecto); + this.dibujarObjetos(); + } + } + + exportImg(){ + function dlCanvas() { + var canvas :any; + canvas = document.getElementById('myCanvas'); + var dt = canvas.toDataURL('image/png'); + /* Change MIME type to trick the browser to downlaod the file instead of displaying it */ + dt = dt.replace(/^data:image\/[^;]*/, 'data:application/octet-stream'); + + /* In addition to <a>'s "download" attribute, you can define HTTP-style headers */ + dt = dt.replace(/^data:application\/octet-stream/, 'data:application/octet-stream;headers=Content-Disposition%3A%20attachment%3B%20filename=Canvas.png'); + + this.href = dt; + }; + document.getElementById("dl").addEventListener('click', dlCanvas, false); + document.getElementById("dl").click(); + } + ngAfterViewInit() { + + // Make it visually fill the positioned parent + this.canvasRef.nativeElement.width = this.canvasRef.nativeElement.offsetParent.offsetWidth * 0.94; + this.canvasRef.nativeElement.height = this.canvasRef.nativeElement.offsetParent.offsetHeight * 0.94; + var relacionAspecto = this.canvasRef.nativeElement.width/this.canvasRef.nativeElement.height; + this.Graph(relacionAspecto); + + // this.objetos.push({tipo:'rect',x:0,y:0,w:5,h:1, d:90}); + // this.objetos.push({tipo:'circulo',x:1,y:1,r:4}); + // this.objetos.push({tipo:'circulo',x:1,y:0,r:2}); + // this.objetos.push({tipo:'grafica',ecuacion:(x)=>Math.exp(x), color:'red',thickness: 2}); + // this.objetos.push({tipo:'grafica',ecuacion:(x)=>x*x*x/20, color:'green',thickness: 2}); + + // this.objetos.push({tipo:'grafica',ecuacion:(x)=>Math.sin(1/x), color:'blue',thickness: 2}); + // this.objetos.push({tipo:'rect',x:8,y:0,w:5,h:1, d:45}); + // this.drawCircle(1,1,4); + // this.drawCircle(1,1,2); + // this.drawEquation((x)=>x*x,"black",2); + this.dibujarObjetos(); + } + + @HostListener('document:click', ['$event']) + private documentClicked(event: MouseEvent): void { + + // Popover is open + if (this.popover && this.popover.isOpen()) { + + // Not clicked on self element + if (!(this.popover as any)._elementRef.nativeElement.contains(event.target)) { + + // Hacking typescript to access private member + const popoverWindowRef: ComponentRef<NgbPopoverWindow> = (this.popover as any)._windowRef; + + // If clicked outside popover window + if (!popoverWindowRef.location.nativeElement.contains(event.target)) { + this.popover.close(); + } + } + } + } + + play(){ + this.animando = true; + this.animar(); + } + + pause(){ + this.animando = false; + } + + private mostrarOcultarEjes = function(){ + this.mostrarEjes = !this.mostrarEjes; + this.context.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); + this.dibujarObjetos(); + } + + private mostrarOcultarGrilla = function(){ + this.mostrarGrilla = !this.mostrarGrilla; + this.context.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); + this.dibujarObjetos(); + } + + private mostrarEvaluacionVertical = function(){ + this.evaluacionVertical = !this.evaluacionVertical; + this.context.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); + this.dibujarObjetos(); + } + + public limpiarCanvas = function(){ + this.objetos = []; + this.context.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); + if(this.mostrarEjes || this.mostrarGrilla){ + this.drawXAxis(this.context); + this.drawYAxis(this.context); + } + } + + public centrarCanvas = function(){ + this.maxX = 10; + this.maxY = 10/this.aspectRatio; + this.minX = -10; + this.minY = -10/this.aspectRatio; + + this.rangeX = this.maxX - this.minX; + this.rangeY = this.maxY - this.minY; + + this.unitsPerTickX = 1; + this.unitsPerTickY = 1; + + this.unitX = this.canvasRef.nativeElement.width / this.rangeX; + this.unitY = this.canvasRef.nativeElement.height / this.rangeY; + this.centerY = (-this.minY / this.rangeY) * this.canvasRef.nativeElement.height; + this.centerX = (-this.minX / this.rangeX) * this.canvasRef.nativeElement.width; + this.iteration = (this.maxX - this.minX) / 1000; + this.scaleX = this.canvasRef.nativeElement.width / this.rangeX; + this.scaleY = this.canvasRef.nativeElement.height / this.rangeY; + + this.context.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); + this.dibujarObjetos(); + } + + public cambiarTipoZoom = function(){ + //Todo, Abscisa y Ordenada + if(this.tipoZoom == "Todo"){ + this.tipoZoom = "Abscisa"; + this.tipoZoomDesc = "Zoom en abscisa"; + }else if (this.tipoZoom == "Abscisa"){ + this.tipoZoom = "Ordenada"; + this.tipoZoomDesc = "Zoom en ordenada"; + }else{ + this.tipoZoom = "Todo"; + this.tipoZoomDesc = "Zoom en ambos ejes"; + } + } + /* + Se genera algo de la forma: + +GRAPH:{ "graph": <nombre de funcion a graficar>, "funs" = [ <la función a graficar y todas las que usa> ] } + +Por cada función se genera: + +{ "fun" = <nombre de funcion>, "args" : [<nombres de los parámetros>], "bdy" : <expresión> } + + +Las expresiones pueden ser de distintos tipos, eso lo especifica el "kind": +*/ + +private generarFuncion = function(graph:any){ + var funcionString=""; + var grafica; + for(var fun of graph.funs){ + funcionString = "var "+fun.fun + " = function("+fun.args.join()+"){\n return "+this.generarExpresion(fun.bdy)+"}\n"+funcionString; + + if(fun.fun == graph.graph){ + funcionString += "return "+fun.fun+"("+fun.args.join()+");\n" + grafica = fun; + } + } + funcionString = "("+grafica.args.join()+",delta,hayPunto)=>{\n"+funcionString+"}"; + return funcionString; +} + +/* +-condicionales (ej: cnd?exp1:exp2) +{ "kind" : "cnd", "cond" : <expresión>, "exp1" : <expresión>, "exp2" : <expresión>} +- operadores binarios (ej: exp1 + exp2) +{ "kind" : "bop", "op" : <operador>, "exp1" : <expresión>, "exp2" : <expresión> } +-operadores unarios (ej: - exp) +{ "kind" : "uop", "op" : <operador>, "exp" : <expresión> } +-aplicación de funciones (ej: f(3,4)) +{ "kind" : "app", "fun" : <nombre función>, "args" : [ <expresiones> ] } +-tupla (ej:(2,3,4)) +{ "kind" : "tup", "exps" : [ <expresiones> ] } +-literales (ej: 1, "tito", etc.) +{ "kind" : "lit", "val" : <valor> } +-variables (ej: x) +{ "kind" : "var", "var" : <nombre de la variable> } +*/ + +private generarExpresion = function(exp:any){ + var expresion = ""; + if(exp.kind == "cnd"){ + expresion = " ("+this.generarExpresion(exp.cond)+"?"+this.generarExpresion(exp.exp1)+":"+this.generarExpresion(exp.exp2)+") "; + }else if(exp.kind == "bop"){ + if(exp.op == "=="){ + expresion = " Math.abs(("+this.generarExpresion(exp.exp1)+") - ("+this.generarExpresion(exp.exp2)+")) < delta && hayPunto() "; + }else if (exp.op == "/="){ + expresion = " Math.abs(("+this.generarExpresion(exp.exp1)+") - ("+this.generarExpresion(exp.exp2)+")) > delta || Math.abs(("+this.generarExpresion(exp.exp1)+") - ("+this.generarExpresion(exp.exp2)+")) < delta && !hayPunto() "; + }else{ + expresion = " ("+this.generarExpresion(exp.exp1)+")"+exp.op+"("+this.generarExpresion(exp.exp2)+") "; + } + }else if(exp.kind == "uop"){ + expresion = " "+exp.op+" "+this.generarExpresion(exp.exp)+" "; + }else if(exp.kind == "app"){ + if(exp.fun == 'cos'){ + exp.fun = 'Math.cos' + }else if(exp.fun == 'sen'){ + exp.fun = 'Math.sin' + }else if(exp.fun == 'red'){ + exp.fun = 'Math.round' + } + expresion = " "+exp.fun+"("+exp.args.map(e => this.generarExpresion(e)).join()+") "; + }else if(exp.kind == "tup"){ + expresion = " ("+exp.exps.map(e => this.generarExpresion(e)).join()+") "; + }else if(exp.kind == "lit"){ + expresion = " "+exp.val+" "; + }else if(exp.kind == "var"){ + expresion = " "+exp.var+" "; + }else{ + expresion = " undefined "; + } + + return expresion; +} + +private animar = function(){ + if(this.mostrarEjes || this.mostrarGrilla){ + this.drawXAxis(this.context); + this.drawYAxis(this.context); + } + this.limpiarCanvas(); + this.objetos = this.elementosAnimacion[this.frameAnimacion]; + this.dibujarObjetos(); + if(this.animando){ + setTimeout(function(){ + if(this.animando){ + this.frameAnimacion ++; + if(this.frameAnimacion>=this.elementosAnimacion.length){ + this.frameAnimacion = 0; + } + this.animar(); + } + }.bind(this),1000); + } +} + +private dibujarObjetos = function(){ + if(this.mostrarEjes || this.mostrarGrilla){ + this.drawXAxis(this.context); + this.drawYAxis(this.context); + } + for(let obj of this.objetos){ + if(obj.tipo == 'circulo'){ + this.drawCircle(obj.x, obj.y, obj.r, obj.color,obj.rotacion); + }else if(obj.tipo == 'grafica'){ + this.drawEquation(obj.ecuacion, obj.color,obj.thickness) + }else if (obj.tipo == 'rectangulo'){ + this.drawRect(obj.x,obj.y,obj.w,obj.h,obj.color,obj.rotacion); + }else if (obj.tipo == 'texto'){ + this.drawText(obj.x, obj.y, obj.text, obj.size, obj.color,obj.rotacion); + }else if (obj.tipo == 'poligono'){ + this.drawPolyline(true,obj.puntos,obj.color, obj.rotacion); + }else if (obj.tipo == 'lineas'){ + this.drawPolyline(false,obj.puntos,obj.color, obj.rotacion); + } + } +} + +public Graph = function(relacionAspecto) { + this.config = { + canvasId: 'myCanvas', + minX: -10, + minY: -10/relacionAspecto, + maxX: 10, + maxY: 10/relacionAspecto, + unitsPerTickX: 1, + unitsPerTickY: 1 + } + this.aspectRatio = relacionAspecto; + this.canvas = this.canvasRef; + // this.canvas = document.getElementById(config.canvasId); + this.minX = this.config.minX; + this.minY = this.config.minY; + this.maxX = this.config.maxX; + this.maxY = this.config.maxY; + this.unitsPerTickX = this.config.unitsPerTickX; + this.unitsPerTickY = this.config.unitsPerTickY; + // constants + this.axisColor = '#aaa'; + this.font = '8pt Calibri'; + this.tickSize = 10; + + // relationships + this.context = this.canvasRef.nativeElement.getContext('2d'); + + this.rangeX = this.maxX - this.minX; + this.rangeY = this.maxY - this.minY; + + + this.unitX = this.canvasRef.nativeElement.width / this.rangeX; + this.unitY = this.canvasRef.nativeElement.height / this.rangeY; + this.centerY = Math.round(Math.abs(this.minY / this.rangeY) * this.canvasRef.nativeElement.height); + this.centerX = Math.round(Math.abs(this.minX / this.rangeX) * this.canvasRef.nativeElement.width); + this.iteration = (this.maxX - this.minX) / 1000; + this.scaleX = this.canvasRef.nativeElement.width / this.rangeX; + this.scaleY = this.canvasRef.nativeElement.height / this.rangeY; +}; + + + +private drawXAxis = function(context: any) { + context.save(); + + // draw tick marks + let xPosIncrement = this.unitsPerTickX * this.unitX; + //let xPos, unit; + context.font = this.font; + context.textAlign = 'center'; + context.textBaseline = 'top'; + + // draw left tick marks + let xPos = this.centerX - xPosIncrement; + let unit = -1 * this.unitsPerTickX; + if(this.mostrarGrilla){ + context.beginPath(); + context.strokeStyle = "#EEEEEE";; + context.lineWidth = 1; + context.moveTo(this.centerX, 0); + context.lineTo(this.centerX, this.canvasRef.nativeElement.height); + context.stroke(); + } + + while (xPos > 0) { + if(this.mostrarGrilla){ + context.beginPath(); + context.strokeStyle = "#EEEEEE";; + context.lineWidth = 1; + context.moveTo(xPos, 0); + context.lineTo(xPos, this.canvasRef.nativeElement.height); + context.stroke(); + + } + if(this.mostrarEjes){ + context.beginPath(); + context.strokeStyle = this.axisColor; + context.lineWidth = 2; + context.moveTo(xPos, this.centerY - this.tickSize / 2); + context.lineTo(xPos, this.centerY + this.tickSize / 2); + context.stroke(); + context.fillText(unit + '', xPos, this.centerY + this.tickSize / 2 + 3); + } + unit = parseFloat((unit-this.unitsPerTickX).toFixed(2)); + xPos = Math.round(xPos - xPosIncrement); + } + + // draw right tick marks + xPos = this.centerX + xPosIncrement; + unit = this.unitsPerTickX; + while (xPos < this.canvas.nativeElement.width) { + if(this.mostrarGrilla){ + context.beginPath(); + context.strokeStyle = "#EEEEEE";; + context.lineWidth = 1; + context.moveTo(xPos, 0); + context.lineTo(xPos, this.canvasRef.nativeElement.height); + context.stroke(); + + } + if(this.mostrarEjes){ + context.beginPath(); + context.strokeStyle = this.axisColor; + context.lineWidth = 2; + context.moveTo(xPos, this.centerY - this.tickSize / 2); + context.lineTo(xPos, this.centerY + this.tickSize / 2); + context.stroke(); + context.fillText(unit + '', xPos, this.centerY + this.tickSize / 2 + 3); + } + unit = parseFloat((unit+this.unitsPerTickX).toFixed(2)); + xPos = Math.round(xPos + xPosIncrement); + } + if(this.mostrarEjes){ + context.beginPath(); + context.strokeStyle = this.axisColor; + context.lineWidth = 2; + context.moveTo(0, this.centerY); + context.lineTo(this.canvasRef.nativeElement.width, this.centerY); + context.stroke(); + context.moveTo(this.canvasRef.nativeElement.width, this.centerY); + context.lineTo(this.canvasRef.nativeElement.width-12, this.centerY-5); + context.stroke(); + context.moveTo(this.canvasRef.nativeElement.width, this.centerY); + context.lineTo(this.canvasRef.nativeElement.width-12, this.centerY+5); + context.stroke(); + } + context.restore(); +}; + +private drawYAxis = function(context: any) { + context.save(); + + // draw tick marks + let yPosIncrement = this.unitsPerTickY * this.unitY; + //var yPos, unit; + context.font = this.font; + context.textAlign = 'right'; + context.textBaseline = 'middle'; + + // draw top tick marks + let yPos = this.centerY - yPosIncrement; + let unit = this.unitsPerTickY; + if(this.mostrarGrilla){ + context.beginPath(); + context.strokeStyle = "#EEEEEE";; + context.lineWidth = 1; + context.moveTo(0, this.centerY); + context.lineTo(this.canvasRef.nativeElement.width, this.centerY); + context.stroke(); + } + while (yPos > 0) { + if(this.mostrarGrilla){ + context.beginPath(); + context.strokeStyle = "#EEEEEE";; + context.lineWidth = 1; + context.moveTo(0, yPos); + context.lineTo(this.canvasRef.nativeElement.width, yPos); + context.stroke(); + } + if(this.mostrarEjes){ + context.beginPath(); + context.strokeStyle = this.axisColor; + context.lineWidth = 2; + context.moveTo(this.centerX - this.tickSize / 2, yPos); + context.lineTo(this.centerX + this.tickSize / 2, yPos); + context.stroke(); + context.fillText(unit, this.centerX - this.tickSize / 2 - 3, yPos); + } + unit = parseFloat((unit+this.unitsPerTickY).toFixed(2)); + yPos = Math.round(yPos - yPosIncrement); + } + + // draw bottom tick marks + yPos = this.centerY + yPosIncrement; + unit = -1 * this.unitsPerTickY; + while (yPos < this.canvasRef.nativeElement.height) { + if(this.mostrarGrilla){ + + context.beginPath(); + context.strokeStyle = "#EEEEEE";; + context.lineWidth = 1; + context.moveTo(0, yPos); + context.lineTo(this.canvasRef.nativeElement.width, yPos); + context.stroke(); + } + if(this.mostrarEjes){ + context.beginPath(); + context.strokeStyle = this.axisColor; + context.lineWidth = 2; + context.moveTo(this.centerX - this.tickSize / 2, yPos); + context.lineTo(this.centerX + this.tickSize / 2, yPos); + context.stroke(); + context.fillText(unit, this.centerX - this.tickSize / 2 - 3, yPos); + } + unit = parseFloat((unit-this.unitsPerTickY).toFixed(2)); + yPos = Math.round(yPos + yPosIncrement); + } + if(this.mostrarEjes){ + context.beginPath(); + context.strokeStyle = this.axisColor; + context.lineWidth = 2; + context.moveTo(this.centerX, 0); + context.lineTo(this.centerX, this.canvasRef.nativeElement.height); + context.stroke(); + context.moveTo(this.centerX, 0); + context.lineTo(this.centerX+5, 12); + context.stroke(); + context.moveTo(this.centerX, 0); + context.lineTo(this.centerX-5, 12); + context.stroke(); + } + context.restore(); +}; + +private transformContext = function(context: any) { + + context.translate(this.centerX, this.centerY); + // stretch grid to fit the canvas window, and + // invert the y scale so that that increments + // as you move upwards + + context.scale(this.scaleX, -this.scaleY); +} + +public hayGraficas = function(){ + for(let obj of this.objetos){ + if(obj.tipo == 'grafica'){ + return true; + } + } + return false; +} + +public verticalLine = function(x:number, y:number){ + if(this.hayGraficas()){ + this.context.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); + this.dibujarObjetos(); + var context = this.context; + + this.context.moveTo(x, 0); + this.context.lineTo(x, this.canvasRef.nativeElement.height); + + var minimoX = -(this.centerX / this.scaleX); + var minimoY = -(this.centerY / this.scaleY); + + var relativeX = (x/this.canvasRef.nativeElement.width)*this.rangeX + minimoX; + + for(let obj of this.objetos){ + if(obj.tipo == 'grafica'){ + var relativeX = Math.trunc(relativeX*100)/100; + var interseccion = obj.ecuacion(relativeX,this.rangeX/500, ()=>{return true;}); + var realY = - ((interseccion+minimoY)/this.rangeY)*this.canvasRef.nativeElement.height; + if(obj.color){ + this.context.fillStyle = obj.color; + } + this.context.fillText("("+relativeX.toFixed(2)+","+interseccion.toFixed(2)+")",x+10,realY); + this.context.fillStyle = 'black'; + this.context.fillRect(x-2.5,realY-2.5,5,5); + } + } + + this.context.stroke(); + } +} + +public leaveCanvas = function(e:any){ + this.context.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); + this.dibujarObjetos(); +} + +public moveGraph = function(e: any) { + + + if (e.buttons === 1 && e.type == 'mousemove') { + this.centerX += e.offsetX - this.lastPositionX; + this.centerY += e.offsetY - this.lastPositionY; + this.minX = -(this.centerX / this.scaleX); + this.maxY = (this.centerY / this.scaleY); + this.maxX = (this.canvasRef.nativeElement.width / this.scaleX) - (this.centerX / this.scaleX); + this.minY = -((this.canvasRef.nativeElement.height / this.scaleY) - (this.centerY / this.scaleY)); + this.lastPositionX = e.offsetX; + this.lastPositionY = e.offsetY; + this.context.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); + this.dibujarObjetos(); + } else if(e.type == 'touchend'){ + if(e.touches.length==1){ + this.lastPositionX = e.touches[0].clientX; + this.lastPositionY = e.touches[0].clientY; + } + } else if(e.type == 'touchmove') { + if(e.touches.length==1){ + this.centerX += e.touches[0].clientX - this.lastPositionX; + this.centerY += e.touches[0].clientY - this.lastPositionY; + this.minX = -(this.centerX / this.scaleX); + this.maxY = (this.centerY / this.scaleY); + this.maxX = (this.canvasRef.nativeElement.width / this.scaleX) - (this.centerX / this.scaleX); + this.minY = -((this.canvasRef.nativeElement.height / this.scaleY) - (this.centerY / this.scaleY)); + this.lastPositionX = e.touches[0].clientX; + this.lastPositionY = e.touches[0].clientY; + this.context.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); + this.dibujarObjetos(); + } else if(this.lastZoom && e.touches.length>1){ + this.lastPositionX = e.touches[0].clientX; + this.lastPositionY = e.touches[0].clientY; + var x = e.touches[1].clientX; + var y = e.touches[1].clientY; + var newZoom = Math.sqrt(Math.pow(this.lastPositionX - x,2)+Math.pow(this.lastPositionY - y,2)); + if(Math.abs(newZoom - this.lastZoom)>2){ + //15 es un factor para que el zoom en el dispositivo sea natural. + this.zoom((newZoom-this.lastZoom)/15); + } + this.lastZoom = newZoom; + } + } else if(e.type == 'touchstart'){ + this.lastPositionX = e.touches[0].clientX; + this.lastPositionY = e.touches[0].clientY; + if(e.touches.length>1){ + var x = e.touches[1].clientX; + var y = e.touches[1].clientY; + this.lastZoom = Math.sqrt(Math.pow(this.lastPositionX - x,2)+Math.pow(this.lastPositionY - y,2)); + }else{ + this.lastZoom = undefined; + } + } else { + this.lastPositionX = e.offsetX; + this.lastPositionY = e.offsetY; + } + + if(this.evaluacionVertical){ + var rect = this.canvasRef.nativeElement.getBoundingClientRect(); + var x,y; + if(e instanceof MouseEvent){ + x = e.clientX - rect.left; + y = e.clientY - rect.top; + }else if(e instanceof TouchEvent){ + x = e.touches[0].clientX - rect.left; + y = e.touches[0].clientY - rect.top; + } + this.verticalLine(x,y); + } + + if(e instanceof TouchEvent){ + e.preventDefault(); + } + +} + +public zoomGraph = function(e: any) { + // cross-browser wheel delta + var e = window.event || e; // old IE support + var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail))); + this.zoom(delta, e.clientX, e.clientY); + return false; +} + +public zoomMas = function(){ + this.zoom(1); +} + +public zoomMenos = function(){ + this.zoom(-1); +} + +private zoom = function(delta:number, clientX?:number, clientY?:number){ + var deltaX = delta*this.rangeX / 20; + var deltaY = delta*this.rangeY / 20; + + if ((this.tipoZoom == "Todo" && ((this.rangeX < 0.1 && deltaX > 0 || this.rangeX > 10000 && deltaX < 0) + || (this.rangeY < 0.1 && deltaY > 0 || this.rangeY > 10000 && deltaY < 0))) + || this.tipoZoom == "Abscisa" && (this.rangeX < 0.1 && deltaX > 0 || this.rangeX > 10000 && deltaX < 0) + || this.tipoZoom == "Ordenada" && (this.rangeY < 0.1 && deltaY > 0 || this.rangeY > 10000 && deltaY < 0)) { + return; + } + + if(clientX && clientY){ + var rect = this.canvasRef.nativeElement.getBoundingClientRect(); + var x = clientX - rect.left; + var y = clientY - rect.top; + var minimoX = -(this.centerX / this.scaleX); + var minimoY = -((this.canvasRef.nativeElement.height / this.scaleY) - (this.centerY / this.scaleY)); + var relativeX = (x/this.canvasRef.nativeElement.width)*this.rangeX + minimoX; + var relativeY = (1-y/this.canvasRef.nativeElement.height)*this.rangeY + minimoY; + var distRelX = (Math.abs(relativeX-this.minX)/Math.abs(this.maxX-this.minX)); + var distRelY = (Math.abs(relativeY-this.minY)/Math.abs(this.maxY-this.minY)); + + if(this.tipoZoom == "Todo"){ + this.maxX -= deltaX*(1-distRelX); + this.maxY -= deltaY*(1-distRelY); + this.minX += deltaX*distRelX; + this.minY += deltaY*distRelY; + //this.minY = this.maxY - (this.maxX-this.minX)/this.aspectRatio; + }else if(this.tipoZoom == "Abscisa"){ + this.maxX -= deltaX*(1-distRelX); + this.minX += deltaX*distRelX; + }else{ + this.maxY -= deltaY*(1-distRelY); + this.minY += deltaY*distRelY; + } + }else{ + if(this.tipoZoom == "Todo"){ + this.maxX -= deltaX; + this.maxY -= deltaY; + this.minX += deltaX; + this.minY += deltaY; + //this.minY = this.maxY - (this.maxX-this.minX)/this.aspectRatio; + }else if(this.tipoZoom == "Abscisa"){ + this.maxX -= deltaX; + this.minX += deltaX; + }else{ + this.maxY -= deltaY; + this.minY += deltaY; + } + } + + this.rangeX = this.maxX - this.minX; + this.rangeY = this.maxY - this.minY; + + //this.tickSize = (20 / this.rangeX) * 10; + if(this.rangeX >15){ + this.unitsPerTickX = Math.round(this.rangeX/15); + }else if (this.rangeX > 4){ + this.unitsPerTickX = 1; + }else if (this.rangeX > 1.5){ + this.unitsPerTickX = Math.round((this.rangeX/15)*10)/10; + }else if (this.rangeX > 0.4){ + this.unitsPerTickX = 0.1; + }else if (this.rangeX > 0.15){ + this.unitsPerTickX = Math.round((this.rangeX/15)*100)/100; + }else{ + this.unitsPerTickX = 0.01; + } + + + if(this.rangeY >15){ + this.unitsPerTickY = Math.round(this.rangeY/15); + }else if(this.rangeY > 4){ + this.unitsPerTickY = 1; + }else if (this.rangeY > 1.5){ + this.unitsPerTickY = Math.round((this.rangeY/15)*10)/10; + }else if (this.rangeY > 0.4){ + this.unitsPerTickY = 0.1; + }else if (this.rangeY > 0.15){ + this.unitsPerTickY = Math.round((this.rangeY/15)*100)/100; + }else { + this.unitsPerTickY = 0.01; + } + + this.unitX = this.canvasRef.nativeElement.width / this.rangeX; + this.unitY = this.canvasRef.nativeElement.height / this.rangeY; + this.centerY = (this.maxY / this.rangeY) * this.canvasRef.nativeElement.height; + this.centerX = (-this.minX / this.rangeX) * this.canvasRef.nativeElement.width; + this.iteration = (this.maxX - this.minX) / 1000; + this.scaleX = this.canvasRef.nativeElement.width / this.rangeX; + this.scaleY = this.canvasRef.nativeElement.height / this.rangeY; + + this.context.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height); + this.dibujarObjetos(); + +} + +private drawCircle: any = function(x: number, y: number, radius: number, color:string, rotation: number) { + + let context = this.context; + context.save(); + context.save(); + this.transformContext(context); + + context.beginPath(); + try { + context.translate(0, 0); + var degree = rotation * Math.PI / 180; + var nuevoX = Math.cos(degree)*x-Math.sin(degree)*y; + var nuevoY = Math.sin(degree)*x+Math.cos(degree)*y; + + context.rotate(-degree); + + this.context.arc(nuevoX, nuevoY, radius, 0, 2 * Math.PI, false); + if(color){ + context.fillStyle = color; + context.fill(); + } + + } catch (e) { + this.limpiarCanvas(); + } + + context.restore(); + context.lineJoin = 'round'; + context.lineWidth = this.thickness; + context.strokeStyle = this.color; + context.stroke(); + context.restore(); + +} + +private drawText: any = function(x: number, y: number, text: string, size:number, color:string, rotation: number) { + + this.context.save(); + var minimoX = -(this.centerX / this.scaleX); + var minimoY = -(this.centerY / this.scaleY); + + var realY = - ((y+minimoY)/this.rangeY)*this.canvasRef.nativeElement.height; + var realX = ((x-minimoX)/this.rangeX)*this.canvasRef.nativeElement.width; + if(color){ + this.context.fillStyle = color; + } + + this.context.translate(realX, realY); + var degree = rotation * Math.PI / 180; + + this.context.rotate(degree); + + this.context.font = (size*100)/this.rangeX+"pt Arial"; + this.context.textBaseline="middle"; + this.context.textAlign="center"; + this.context.fillText(text,0,0); + this.context.restore(); +} + +private drawRect: any = function(x: number, y: number, width: number, height:number,color:string, rotation:number) { + + let context = this.context; + context.save(); + context.save(); + this.transformContext(context); + + context.beginPath(); + try { + context.translate(0, 0); + var degree = rotation * Math.PI / 180; + var nuevoX = Math.cos(degree)*x-Math.sin(degree)*y; + var nuevoY = Math.sin(degree)*x+Math.cos(degree)*y; + + context.rotate(-degree); + + context.rect(nuevoX-width/2,nuevoY-height/2,width,height); + if(color){ + context.fillStyle = color; + context.fill(); + } + + context.translate(this.centerX, this.centerY); + } catch (e) { + this.limpiarCanvas(); + } + + context.restore(); + context.lineJoin = 'round'; + context.lineWidth = this.thickness; + context.strokeStyle = this.color; + context.stroke(); + context.restore(); +} + +private drawElipse: any = function(x: number, y: number, radiusX: number, radiusY: number, rotation: number) { + + let context = this.context; + this.color = 'green'; + this.thickness = 3; + + context.save(); + context.save(); + this.transformContext(context); + + context.beginPath(); + try { + this.context.ellipse(x, y, radiusX, radiusY, rotation * Math.PI / 180, 0, 2 * Math.PI); + } catch (e) { + this.limpiarCanvas(); + } + context.restore(); + context.lineJoin = 'round'; + context.lineWidth = this.thickness; + context.strokeStyle = this.color; + context.stroke(); + context.restore(); +} + +private drawEquation: any = function(equation: any, color: string, thickness: number) { + let context = this.context; + context.save(); + context.save(); + this.transformContext(context); + + context.beginPath(); + context.lineWidth = thickness; + try { + context.moveTo(this.minX, equation(this.minX)); + var move = true; + var _x = undefined; + var _y = undefined; + var pendiente = undefined; + var delta = 0.50; + var nuevoY = undefined; + var h = 1/1000; + var delta = this.rangeX/1000; + var anchoPunto = this.rangeX/200; + for (var x = this.minX + this.iteration; x <= this.maxX; x += this.iteration) { + try{ + + var punto = false; + var hayPunto = function(){ + punto = true; + return true; + } + // var punto = esPunto(x, this.rangeX/1000); + // if(punto != undefined){ + // this.context.fillRect(x-anchoPunto/2,punto-anchoPunto/2,anchoPunto,anchoPunto); + // move = true; + // }else{ + var y = equation(x,delta,hayPunto); + if(punto){ + this.context.fillRect(x-anchoPunto/2,y-anchoPunto/2,anchoPunto,anchoPunto); + move = true; + punto = false; + }else{ + // if(nuevoY){ + // var diff = Math.abs(Math.abs(nuevoY)-Math.abs(y)); + // if(diff>delta){ + // move = true; + // } + // } + // if(_x){ + // pendiente = (y -_y)/(x-_x); + // nuevoY = y + pendiente*h; + // } + + //###################################################### + //POSIBLE CODIGO QUE MANEJA UBICACION DEL NUEVO PUNTO + //SEGUN EL ANGULO DE LA PENDIENTE Y SU UBICACION. + // var y = equation(x); + if(pendiente != undefined){ + var diff = Math.abs(Math.abs(nuevoY)-Math.abs(y)); + + var pendienteMas1 = Math.tan(Math.atan(pendiente)+Math.PI/8); + var pendienteMenos1 = Math.tan(Math.atan(pendiente)-Math.PI/8); + + if(pendiente > 0 && pendienteMas1 < 0){ + pendienteMas1 = 1000000; + } + + if(pendiente < 0 && pendienteMenos1 > 0){ + pendienteMenos1 = -1000000; + } + + var max = (x - _x)*pendienteMas1 - (y-_y); + var min = (x - _x)*pendienteMenos1 -(y-_y); + + //tiene que ser o pendienteMas1 o max negativo, por eso se multiplica. + // if(Math.sign(pendienteMas1)*Math.sign(pendiente)*max < 0 || Math.sign(pendienteMenos1)*Math.sign(pendiente)*min > 0){ + if(max < 0 || min > 0){ + move = true; + } + } + if(_x){ + pendiente = (y -_y)/(x-_x); + } + //###################################################### + + + if(y>10e6){ + y = 10e6; + }else if (y<-10e6){ + y = -10e6; + } + // if(Math.abs(x)<10e-5){ + // y = 0; + // } + // if(Math.abs(x)<10e-5){ + // x = 0; + // } + if(move){ + context.moveTo(x,y); + move = false; + }else{ + context.lineTo(x, y); + } + } + _x = x; + _y = y; + }catch(e){ + move = true; + } + } + } catch (e) { + this.limpiarCanvas(); + } + + context.restore(); + context.lineJoin = 'bevel'; + context.lineWidth = thickness; + context.strokeStyle = color; + context.stroke(); + context.restore(); +}; + +private drawPolyline: any = function(polygon:boolean, puntos: any, color: string, rotation:number) { + let context = this.context; + context.save(); + context.save(); + this.transformContext(context); + + context.beginPath(); + try { + if (puntos.length > 1){ + var inicio = puntos[0]; + context.moveTo(inicio[0], inicio[1]); + for (let punto of puntos) { + context.lineTo(punto[0], punto[1]); + } + if(polygon){ + context.lineTo(inicio[0], inicio[1]); + } + } + } catch (e) { + this.limpiarCanvas(); + } + + context.restore(); + context.lineJoin = 'round'; + context.strokeStyle = color; + if(color){ + context.fillStyle = color; + context.fill(); + } + context.strokeStyle = 'black'; + context.stroke(); + context.restore(); +}; + +} diff --git a/src/app/layout/canvas/canvas.module.ts b/src/app/layout/canvas/canvas.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..931d67ff30e27b86d8603f88738370614f064386 --- /dev/null +++ b/src/app/layout/canvas/canvas.module.ts @@ -0,0 +1,14 @@ +import { NgModule} from '@angular/core'; +import { CommonModule } from '@angular/common' +import { RouterModule } from '@angular/router'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { FormsModule } from '@angular/forms'; +import { CanvasComponent } from './canvas.component'; + +@NgModule({ + imports: [FormsModule, RouterModule, CommonModule, NgbModule], + declarations: [CanvasComponent], + exports: [CanvasComponent] +}) + +export class CanvasModule { } diff --git a/src/app/layout/canvas/canvas.routes.ts b/src/app/layout/canvas/canvas.routes.ts new file mode 100644 index 0000000000000000000000000000000000000000..38e1ce5815db6590d55a9fd5a0d63348079e6a65 --- /dev/null +++ b/src/app/layout/canvas/canvas.routes.ts @@ -0,0 +1,9 @@ +import { Route } from '@angular/router'; +import { CanvasComponent } from './index'; + +export const CanvasRoutes: Route[] = [ + { + path: 'canvas', + component: CanvasComponent + } +]; diff --git a/src/app/layout/canvas/index.ts b/src/app/layout/canvas/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..ec27135c63c8edede3c80ac96a285e1ba8692e60 --- /dev/null +++ b/src/app/layout/canvas/index.ts @@ -0,0 +1,6 @@ +/** + * This barrel file provides the export for the lazy loaded BlankpageComponent. + */ +export * from './canvas.component'; + +export * from './canvas.routes'; diff --git a/src/app/layout/functions/function.services.result.ts b/src/app/layout/functions/function.services.result.ts new file mode 100644 index 0000000000000000000000000000000000000000..eb4d8fb3715ff3b7e227c1f698007a48e4d90c13 --- /dev/null +++ b/src/app/layout/functions/function.services.result.ts @@ -0,0 +1,4 @@ +export class FunctionServicesResult { + code: number; + msg: string; +} \ No newline at end of file diff --git a/src/app/layout/functions/function.ts b/src/app/layout/functions/function.ts new file mode 100644 index 0000000000000000000000000000000000000000..764863fd9a8465e33813b39a09df4bb2ae64e3b8 --- /dev/null +++ b/src/app/layout/functions/function.ts @@ -0,0 +1,5 @@ +export class Function { + id: number; + name: string; + str: string; +} \ No newline at end of file diff --git a/src/app/layout/functions/functions.component.html b/src/app/layout/functions/functions.component.html new file mode 100644 index 0000000000000000000000000000000000000000..c8a5596b905e00a961645ddbb7ce4852bafd02e4 --- /dev/null +++ b/src/app/layout/functions/functions.component.html @@ -0,0 +1,50 @@ + + + <div class="panel panel-success"> + + <div class="card"> + <h4 class="card-header">Mis funciones</h4> + <div class="card-block"> + <ul class="list-group list-group-flush"> + + <li *ngFor="let f of _funcs" class="list-group-item"> + <div class="card-block"> + <div class="checkbox"> + <label for="checkbox"> + {{f.str}} + </label> + </div> + <div class="pull-right action-buttons"> + <button (click)="modifyFunc(f.name)" class="btn btn-success"> + <i class="fa fa-pencil" ></i> + </button> + <button (click)="removeFunc(f.name)" class="btn btn-success"> + <i class="fa fa-remove" ></i> + </button> + </div> + </div> + </li> + + + + </ul> + </div> + </div> + + + + <div class="panel-footer"> + <div class="row"> + <div class="col-md-12"> + <div class="input-group input-group"> + <input [(ngModel)]="_newFunction.str" placeholder="nueva función" class="form-control" > + <span class="input-group-btn"> + <button class="btn btn-primary" [disabled]="this._newFunction.str==''" type="button" (click)="handleAddFunction()">Cargar función</button> + </span> + </div> + </div> + + </div> + <!-- <button (click)="popToast()">pop toast</button> --> + </div> + </div> diff --git a/src/app/layout/functions/functions.component.ts b/src/app/layout/functions/functions.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..aec0c51f8c4cd4d66787c389d6512264b24b0097 --- /dev/null +++ b/src/app/layout/functions/functions.component.ts @@ -0,0 +1,52 @@ +import { Component, OnInit } from '@angular/core'; +import { Function } from './function'; +import { FunctionServices } from './functions.service'; +import { FunctionServicesResult } from './function.services.result'; +//import {ToasterModule, ToasterService,ToasterConfig} from 'angular2-toaster'; + +@Component({ + selector: 'functions', + templateUrl: './functions.component.html', + styleUrls: ['./functions.css'], + providers: [] +}) +export class FunctionsComponent implements OnInit { + title = 'Módulo de funciones'; + _funcs: Function[]; + _newFunction :Function = {'id':2,'name':'g','str':'g(x)=x+41'}; + //_toasterService: ToasterService; + // + constructor(private functionServices: FunctionServices) { + // this._toasterService = toasterService; + } + getFuncs(): void { + this.functionServices.getAll().then(result => this._funcs = result); + } + removeFunc(name:string): void{ + this.functionServices.delete(name); + this.getFuncs(); + } + modifyFunc(name:string): void{ + var f = this.functionServices.get(name); + console.log(f); + this._newFunction = f; + + } + + ngOnInit(): void { + this.getFuncs(); + } + + handleAddFunction(): void{ + if(this._newFunction.str.length>4){ + this._newFunction.id =1; + } + var _f:Function = {'id':this._newFunction.id,'name':this._newFunction.str.split('(')[0], 'str':this._newFunction.str}; + var response = this.functionServices.add(_f); + this._newFunction.id=0; + this._newFunction.name=''; + this._newFunction.str = ''; + + } + +} diff --git a/src/app/layout/functions/functions.css b/src/app/layout/functions/functions.css new file mode 100644 index 0000000000000000000000000000000000000000..7f08fa988273ceed7bebc1a77209bd4b1f0642bd --- /dev/null +++ b/src/app/layout/functions/functions.css @@ -0,0 +1,13 @@ +.trash { + color:rgb(209, 91, 71); +} +.flag { + color:rgb(248, 148, 6); +} +.panel-body { padding:0px; } +.panel-footer .pagination { margin: 0; } +.panel .glyphicon,.list-group-item .glyphicon { margin-right:5px; } +.panel-body .radio, .checkbox { display:inline-block;margin:0px; } +.panel-body input[type=checkbox]:checked + label { text-decoration: line-through;color: rgb(128, 144, 160); } +.list-group-item:hover, a.list-group-item:focus {text-decoration: none;background-color: rgb(245, 245, 245);} +.list-group { margin-bottom:0px; } \ No newline at end of file diff --git a/src/app/layout/functions/functions.module.ts b/src/app/layout/functions/functions.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..46bbe37e1e2fb9aefaae7edcef4394996a4d2c73 --- /dev/null +++ b/src/app/layout/functions/functions.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; + +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { FunctionsComponent } from './functions.component'; +//import {ToasterModule, ToasterService} from 'angular2-toaster'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule + //,ToasterModule + ], + declarations: [ + FunctionsComponent + ], + exports:[FunctionsComponent] +}) +export class FunctionsModule { } diff --git a/src/app/layout/functions/functions.routes.ts b/src/app/layout/functions/functions.routes.ts new file mode 100644 index 0000000000000000000000000000000000000000..7ebc6616a48ee803e6d48297915e9495dec64f12 --- /dev/null +++ b/src/app/layout/functions/functions.routes.ts @@ -0,0 +1,10 @@ +import { Route } from '@angular/router'; + +import { FunctionsComponent } from './index'; + +export const FunctionsRoutes: Route[] = [ + { + path: 'functions', + component: FunctionsComponent + } +]; diff --git a/src/app/layout/functions/functions.service.ts b/src/app/layout/functions/functions.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..4d5cffa7be96e1acb0272f6fa3a01189d3b60f4b --- /dev/null +++ b/src/app/layout/functions/functions.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@angular/core'; + +import { Function } from './function'; +import { FunctionServicesResult }from './function.services.result'; + + +const regex = /^\w+\(((\w+(\s)*)?|((\w+(\s)*),(\s)*)*(\w+(\s)*))\)(\s)*=(\s)*$/g; + +@Injectable() +export class FunctionServices { + getAll(): Promise<Function[]>{ + return Promise.resolve(this.allfunctions); + }; + delete(functionName:String):void{ + console.log("deleting" + functionName); + this.allfunctions = this.allfunctions.filter(function(f){return f.name!=functionName}); + }; + add(f: Function): FunctionServicesResult{ + console.log(f); + if(f.id!=0){ + this.allfunctions.push(f); + var result : FunctionServicesResult = {'code':200,'msg':'' }; + } else { + var result : FunctionServicesResult = {'code':500,'msg':'Función no válida' }; + } + return result; + + }; + get(name:string){ + try{ + return this.allfunctions.filter(function(f){return f.name===name})[0]; + } catch(err){ + return null; + } + }; + getToPlot(name:string){ + console.log("Functions: "); + console.log(this.allfunctions); + try{ + return this.allfunctions.filter(function(f){return f.name===name})[0].str.split("=")[1]; + } catch(err){ + return null; + } + }; + + private allfunctions : Function[] = [{'id':12,'name':'f', 'str':'f(x)=x+5'}]; +} \ No newline at end of file diff --git a/src/app/layout/functions/index.ts b/src/app/layout/functions/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..46cafb423a5f0e921ee3a6a1761f9a422d8ab924 --- /dev/null +++ b/src/app/layout/functions/index.ts @@ -0,0 +1,5 @@ +/** + * This barrel file provides the export for the lazy loaded BlankpageComponent. + */ +export * from './functions.component'; +export * from './functions.routes'; diff --git a/src/app/layout/grupos/calificarEntrega.component.ts b/src/app/layout/grupos/calificarEntrega.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..32bf6eade13def9868c8aefd232451d909a4df76 --- /dev/null +++ b/src/app/layout/grupos/calificarEntrega.component.ts @@ -0,0 +1,86 @@ +import { Component } from '@angular/core'; +import { DialogComponent, DialogService } from "ng2-bootstrap-modal"; +import { Archivo, Evaluacion } from '../../shared/objects/archivo'; + +export interface ConfirmModel { + cedula:string; + archivo: Archivo; + parentContext: any; +} +@Component({ + selector: 'confirm', + template: `<div class="modal-dialog" style="margin-top:100px;"> + <div class="modal-content"> + + <div class="modal-header"> + <h5 class="modal-title">Calificar entrega</h5> + <button type="button" class="close" (click)="close()" style="margin-left:8px;">×</button> + </div> + + <div class="modal-body"> + <form> + <div class="form-group"> + <label for="message-text" class="form-control-label">Calificacion (1-100):</label> + <input type="number" class="form-control" [(ngModel)]="nota" min=1 max=100 [ngModelOptions]="{standalone: true}" > + </div> + <div class="form-group"> + <label for="message-text" class="form-control-label">Detalle:</label> + <textarea class="form-control" id="message-text" [(ngModel)]="descripcion" [ngModelOptions]="{standalone: true}" ></textarea> + </div> + </form> + </div> + + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" (click)="cancel()">Cancelar</button> + <button type="button" class="btn btn-success" (click)="confirm()">Calificar</button> + </div> + + </div> + </div>` +}) +export class CalificarEntrega extends DialogComponent<ConfirmModel, boolean> implements ConfirmModel { + descripcion: string = ""; + cedula: string; + archivo: Archivo; + nota: number = 1; + + parentContext: any; + + constructor(dialogService: DialogService) { + super(dialogService); + + } + + ngOnInit() { + if(this.archivo.evaluacion){ + this.descripcion = this.archivo.evaluacion.descripcion; + this.nota = this.archivo.evaluacion.nota; + } + } + + confirm() { + var evaluacion = new Evaluacion(); + evaluacion.cedulaDocente = this.cedula; + evaluacion.descripcion = this.descripcion; + evaluacion.nota = this.nota; + if(this.nota>0 && this.nota<100){ + this.parentContext.haskellService.calificarArchivo(this.archivo.id,evaluacion ) + .subscribe( + evaluacion => { + this.parentContext.notifService.success("Archivo evaluado"); + this.archivo.evaluacion = evaluacion; + this.close(); + }, + error => { + this.parentContext.notifService.error(error); + }); + }else{ + this.parentContext.notifService.error("Calificacion fuera de rango"); + } + } + + cancel(){ + this.close(); + } + +} \ No newline at end of file diff --git a/src/app/layout/grupos/grupos-routing.module.ts b/src/app/layout/grupos/grupos-routing.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b7c8a8b3cb84d218e25a2f5f8178fc8cf36e9bc --- /dev/null +++ b/src/app/layout/grupos/grupos-routing.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { GruposComponent } from './grupos.component'; + +const routes: Routes = [ + { path: '', component: GruposComponent } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class GruposRoutingModule { } \ No newline at end of file diff --git a/src/app/layout/grupos/grupos.component.html b/src/app/layout/grupos/grupos.component.html new file mode 100644 index 0000000000000000000000000000000000000000..df1f27cb7efa30b930059138b30056a442b95b64 --- /dev/null +++ b/src/app/layout/grupos/grupos.component.html @@ -0,0 +1,121 @@ +<notificacion></notificacion> +<div class="container-fluid"> + <div class="row"> + <div class="col-lg-5"> + <label for="search">Nombre del archivo:</label> + <div class="input-group"> + <!--[(ngModel)]=filtroNombre--> + <input type="text" class="form-control" id="search" > + <span class="input-group-addon fa fa-search"> + </span> + </div> + </div> + </div> + <div class="row" style="margin-top: 20px"> + <div class="col-lg-5"> + <div class="card" *ngIf="grupoSeleccionado == undefined"> + <div class="card-header"> + <div *ngIf="grupoSeleccionado==undefined">Grupos</div> + </div> + <div class="card-block" *ngIf="grupoSeleccionado == undefined"> + <div class="row listado-grupos" style="min-height: 100px; overflow-y: scroll;"> + <div class="loading" *ngIf="loading"> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + </div> + <div *ngFor="let grupo of grupos" (click)="seleccionarGrupo(grupo)" class="col-sm-3 col-4 " style="text-align: center;"> + <i class="fa fa-users" style="font-size: 3em; cursor: pointer;color: #f95e5e;" aria-hidden="true"></i> + <p style="cursor: pointer;">{{grupo.grado + '°' + grupo.grupo+" - "+grupo.anio}}</p> + </div> + </div> + </div> + </div> + <ngb-tabset *ngIf="grupoSeleccionado" [destroyOnHide]=false> + <ngb-tab title="Alumnos"> + <ng-template ngbTabContent> + <div class="card"> + <div> + <button class="btn btn-sm btn-secondary pull-right" style="cursor: pointer; margin-top: -35px; margin-right: 105px;" (click)="desseleccionarGrupo()" ngbPopover="Atras" data-placement="bottom" triggers="mouseenter:mouseleave"> + <i class="fa fa-arrow-up"></i> + </button> + <p class="pull-right" style="margin-top: -34px; margin-right: 5px;">{{grupoSeleccionado.grado+ '°' + grupoSeleccionado.grupo+" - "+grupoSeleccionado.anio}}</p> + </div> + <div class="card-block"> + <div class="row listado-grupos" style="min-height: 100px; overflow-y: scroll;"> + <div *ngFor="let alumno of grupoSeleccionado.alumnos" (click)="seleccionarAlumno(alumno)" class="col-sm-3 " style="text-align: center;"> + <i class="fa fa-user" style="font-size: 3em; cursor: pointer;color: #f95e5e;" aria-hidden="true"></i> + <p style="cursor: pointer;">{{alumno.apellido +', ' + alumno.nombre}}</p> + </div> + </div> + </div> + </div> + </ng-template> + </ngb-tab> + <ngb-tab title="Archivos"> + <ng-template ngbTabContent> + <div class="card"> + <div> + <button class="btn btn-sm btn-secondary pull-right" style="cursor: pointer; margin-top: -35px; margin-right: 105px;" (click)="desseleccionarGrupo()" ngbPopover="Atras" data-placement="bottom" triggers="mouseenter:mouseleave"> + <i class="fa fa-arrow-up"></i> + </button> + <p class="pull-right" style="margin-top: -34px; margin-right: 5px;">{{grupoSeleccionado.grado+ '°' + grupoSeleccionado.grupo+" - "+grupoSeleccionado.anio}}</p> + </div> + <div class="card-block"> + <div class="row listado-grupos" style="min-height: 100px; overflow-y: scroll;"> + <div *ngFor="let archivo of grupoSeleccionado.archivos " (click)="seleccionarArchivo(archivo)" class="col-sm-3 col-4" style="text-align: center;"> + <i class="fa fa-file-text" style="font-size: 3em; cursor: pointer;color: #ff8383" aria-hidden="true"></i> + <p style="cursor: pointer;">{{archivo.nombre}}</p> + </div> + </div> + </div> + </div> + </ng-template> + </ngb-tab> + </ngb-tabset> + </div> + <div class="col-lg-7"> + <div class="card" *ngIf="alumnoSeleccionado"> + <div class="card-block"> + <div class="row listadoEntregasAlumnoGrupos" style="min-height: 100px; overflow-y: scroll;" > + <div *ngFor="let entrega of alumnoSeleccionado.archivos" (click)="seleccionarEntrega(entrega)" class="col-sm-3 col-4" style="text-align: center;"> + <i [ngStyle]="" class="fa fa-file-text" style="font-size: 3em; cursor: pointer;" aria-hidden="true"></i> + <p style="cursor: pointer;">{{entrega.nombre}}</p> + </div> + <div *ngIf="alumnoSeleccionado.archivos.length == 0" style="width: 100%; text-align: center;"> + <i style="color: rgb(220,220,220); font-size: 10em; padding: 0.1em" class="fa fa-file-text"></i> + <p>No hay entregas del alumno: {{alumnoSeleccionado.nombre +' '+alumnoSeleccionado.apellido }}</p> + </div> + </div> + + </div> + </div> + <div class="card" *ngIf="alumnoSeleccionado == undefined && archivoSeleccionado == undefined"> + <div class="card-block"> + <div class="row previewArchivoNoSeleccionadoGrupos" style="min-height: 100px" > + <div style="width: 100%; text-align: center;"> + <i style="color: rgb(220,220,220); font-size: 10em; padding: 0.1em" class="fa fa-file-text"></i> + </div> + </div> + </div> + </div> + + <div class="card" *ngIf="archivoSeleccionado" > + <div class="card-header"> + <button *ngIf="tipoArchivo == 'entrega'" class="btn btn-sm btn-secondary pull-left mr-2" (click)="calificarEntrega()"> + Calificar + </button> + <button *ngIf="esArchivoGrupo()" ngbPopover="Cargar/Editar" data-placement="bottom" triggers="mouseenter:mouseleave" class="btn btn-sm btn-secondary pull-left mr-2" (click)="cargarArchivoCompartido()"> + <i class="fa fa-pencil"></i> + </button> + <div class="pull-left" > + Nombre: {{archivoSeleccionado?.nombre}} - Creado: {{archivoSeleccionado?.fechaCreacion | date}} + </div> + </div> + <codemirror class="codemirrorGrupos" [(ngModel)]="archivoSeleccionado.contenido" [config]="configCodeMirror" [ngStyle]="{'font-size': configCodeMirror.fontSize+'px'}"> + </codemirror> + </div> + </div> + </div> +</div> diff --git a/src/app/layout/grupos/grupos.component.ts b/src/app/layout/grupos/grupos.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..29c81563d73cb8267e939a82904111d691e3a44c --- /dev/null +++ b/src/app/layout/grupos/grupos.component.ts @@ -0,0 +1,177 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Archivo } from '../../shared/objects/archivo'; +import { Grupo } from '../../shared/objects/grupo'; +import { Usuario } from '../../shared/objects/usuario'; +import { AuthenticationService } from '../../shared/services/authentication.service'; +import { HaskellService } from '../../shared/services/haskell.service'; +import { SessionService } from '../../shared/services/session.service'; +import { DialogService } from "ng2-bootstrap-modal"; +import { CalificarEntrega } from './calificarEntrega.component'; +import { NgbPopoverConfig, NgbPopover} from '@ng-bootstrap/ng-bootstrap'; +import { NotificacionService } from '../../shared/services/notificacion.service'; + + + +@Component({ + moduleId: module.id, + selector: 'grupos', + templateUrl: './grupos.component.html' +}) + +export class GruposComponent { + archivos : Archivo[] = []; + grupos : Grupo [] = []; + grupoSeleccionado: Grupo = undefined; + + alumnoSeleccionado: Usuario = undefined; + + archivoSeleccionado: Archivo = undefined; + + tipoArchivo: string = undefined; + + loading: boolean = false; + idRecorridos :any = []; + tree: any; + directorioActual:any; + configCodeMirror = JSON.parse(sessionStorage.getItem('codeMirrorConfig')); + + constructor( + private router: Router, + private authService: AuthenticationService, + private haskellService: HaskellService, + private notifService: NotificacionService, + private sessionService: SessionService, + private dialogService:DialogService + ){ + this.directorioActual = {}; + this.directorioActual.archivos = []; + this.configCodeMirror.readOnly = true; + } + + ngOnInit(){ + let cedula = this.authService.getUser().cedula; + this.loading = true; + this.haskellService.getGrupos(cedula) + .subscribe( + grupos => { + this.grupos = grupos; + this.ordenarGrupos(); + this.loading = false; + }, + error => console.log(error) + ); + } + + //ordenar archivos del grupo seleccionado. + ordenarAlph(a, b){ + if(a.nombre.toLowerCase() < b.nombre.toLowerCase()) return -1; + if(a.nombre.toLowerCase() > b.nombre.toLowerCase()) return 1; + return 0; + } + + ordenarArchivos(){ + this.grupoSeleccionado.archivos = this.grupoSeleccionado.archivos.sort(this.ordenarAlph); + } + + //ordeno los archivos del alumno (los archivos entregados.) + ordenarArchivosAlumno(){ + if(this.archivoSeleccionado.archivos){ + this.archivoSeleccionado.archivos = this.archivoSeleccionado.archivos.sort(this.ordenarAlph); + } + } + + //ordenar grupos + ordenarGrupoF(a,b){ + if(a.grado>b.grado) return 1; + if(a.grado<b.grado) return -1; + if(a.grupo.toLowerCase()>b.grupo.toLowerCase()) return 1; + if(a.grupo.toLowerCase()<b.grupo.toLowerCase()) return -1; + return 0; + } + + ordenarGrupos(){ + this.grupos = this.grupos.sort(this.ordenarGrupoF); + } + + //ordenar alumnos + ordenarAlumnosF(a,b){ + if(a.apellido.toLowerCase()>b.apellido.toLowerCase()) return 1; + if(a.apellido.toLowerCase()<b.apellido.toLowerCase()) return -1; + return 0; + } + + ordenarAlumnos(){ + this.grupoSeleccionado.alumnos = this.grupoSeleccionado.alumnos.sort(this.ordenarAlumnosF); + } + + seleccionarGrupo(grupo){ + this.grupoSeleccionado = grupo; + this.ordenarAlumnos(); + this.ordenarArchivos(); + this.archivoSeleccionado = undefined; + this.alumnoSeleccionado = undefined; + } + + desseleccionarGrupo(){ + this.grupoSeleccionado = undefined; + this.archivoSeleccionado = undefined; + this.alumnoSeleccionado = undefined; + } + + seleccionarAlumno(alumno){ + this.alumnoSeleccionado = alumno; + this.ordenarArchivosAlumno(); + this.archivoSeleccionado = undefined; + } + + seleccionarArchivo(archivo){ + this.archivoSeleccionado = archivo; + this.alumnoSeleccionado = undefined; + this.tipoArchivo = "compartido"; + } + + seleccionarEntrega(entrega){ + this.archivoSeleccionado = entrega; + this.alumnoSeleccionado = undefined; + this.tipoArchivo = "entrega"; + } + + calificarEntrega(){ + let disposable = this.dialogService.addDialog(CalificarEntrega, + { + cedula: JSON.parse(sessionStorage.currentUser).cedula+'', + archivo: this.archivoSeleccionado, + parentContext: this + }) + .subscribe((isConfirmed)=>{ + if(isConfirmed) { + //codeMirrorRef.options.readOnly = false; + //componentRef.editDialogFired = true; + } + }); + } + + esArchivoGrupo(){ + if(this.archivoSeleccionado && this.grupoSeleccionado && this.grupoSeleccionado.archivos.some(arch => arch.id == this.archivoSeleccionado.id)){ + return true; + }else{ + return false; + } + } + + cargarArchivoCompartido(){ + if(this.archivoSeleccionado){ + if(this.archivoSeleccionado.directorio){ + this.notifService.warning('No se seleccionó ningún archivo',false); + }else{ + this.sessionService.setArchivo(this.archivoSeleccionado); + this.router.navigate(['/matefun']); + } + }else{ + this.notifService.warning("Archivo no seleccionado"); + } + } + +} \ No newline at end of file diff --git a/src/app/layout/grupos/grupos.module.ts b/src/app/layout/grupos/grupos.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..184ff83d5e7a6daabd1d418d1b9ab67fd4e44e71 --- /dev/null +++ b/src/app/layout/grupos/grupos.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { GruposComponent } from './grupos.component'; +import { CommonModule } from '@angular/common'; +import { GruposRoutingModule } from './grupos-routing.module'; +import { DialogService } from "ng2-bootstrap-modal"; +import { NotificacionService } from '../../shared/services/notificacion.service'; +import { BootstrapModalModule } from 'ng2-bootstrap-modal'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { CodemirrorModule } from 'ng2-codemirror'; +import { CalificarEntrega } from './calificarEntrega.component'; +import { NotificacionModule } from '../../notificacion/notificacion.module'; + +@NgModule({ + imports: [CommonModule, GruposRoutingModule, FormsModule,BootstrapModalModule, NgbModule, CodemirrorModule,NotificacionModule], + declarations: [GruposComponent, CalificarEntrega], + exports: [GruposComponent], + entryComponents: [CalificarEntrega] +}) + +export class GruposModule { } diff --git a/src/app/layout/layout-routing.module.ts b/src/app/layout/layout-routing.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..0a40b0676a0578af59cb53142b10dc9120881710 --- /dev/null +++ b/src/app/layout/layout-routing.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { LayoutComponent } from './layout.component'; + +const routes: Routes = [ + { + path: '', component: LayoutComponent, + children: [ + { path: 'matefun', loadChildren: './matefun/matefun.module#MateFunModule' }, + { path: 'archivos', loadChildren: './archivos/archivos.module#ArchivosModule' }, + { path: 'grupos', loadChildren: './grupos/grupos.module#GruposModule' } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class LayoutRoutingModule { } diff --git a/src/app/layout/layout.component.html b/src/app/layout/layout.component.html new file mode 100644 index 0000000000000000000000000000000000000000..33251e486fc88b7ae2527cda5d878c8b44c331be --- /dev/null +++ b/src/app/layout/layout.component.html @@ -0,0 +1,5 @@ +<app-header></app-header> +<app-sidebar></app-sidebar> +<section class="main-container"> + <router-outlet></router-outlet> +</section> diff --git a/src/app/layout/layout.component.scss b/src/app/layout/layout.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..b82aa23034df301fe032e08c27af23c69bc472bf --- /dev/null +++ b/src/app/layout/layout.component.scss @@ -0,0 +1,17 @@ +.main-container{ + margin-top: 60px; + margin-left: 235px; + padding: 15px; + -ms-overflow-x: hidden; + overflow-x: hidden; + overflow-y: scroll; + position: relative; + overflow: hidden; + +} +// @media screen and (max-width: 982px) { + .main-container { + margin-left: 0px !important; + } +// } + diff --git a/src/app/layout/layout.component.spec.ts b/src/app/layout/layout.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c4d6365d70259780c4656f6382416ca8418ff674 --- /dev/null +++ b/src/app/layout/layout.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LayoutComponent } from './layout.component'; + +describe('LayoutComponent', () => { + let component: LayoutComponent; + let fixture: ComponentFixture<LayoutComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LayoutComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LayoutComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/layout/layout.component.ts b/src/app/layout/layout.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..b81e4b8d12417f6fd627094fc7105f75cf784224 --- /dev/null +++ b/src/app/layout/layout.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { GHCIService } from '../shared/services/ghci.service'; +import { NotificacionService } from '../shared/services/notificacion.service'; +@Component({ + selector: 'app-layout', + templateUrl: './layout.component.html', + styleUrls: ['./layout.component.scss'], + providers: [GHCIService] +}) +export class LayoutComponent implements OnInit { + constructor(public router: Router) { } + ngOnInit() { + if (this.router.url === '/') { + this.router.navigate(['/login']); + } + } +} diff --git a/src/app/layout/layout.module.ts b/src/app/layout/layout.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..9777481aaa12a918ec469c807b12d4c1338c7cc5 --- /dev/null +++ b/src/app/layout/layout.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { FormsModule } from '@angular/forms'; + +import { LayoutRoutingModule } from './layout-routing.module'; +import { LayoutComponent } from './layout.component'; +import { HeaderComponent, SidebarComponent } from '../shared'; +import { AuthenticationService } from '../shared/services/authentication.service'; +import { HaskellService } from '../shared/services/haskell.service'; +import { CodemirrorModule } from 'ng2-codemirror'; +import { NotificacionModule } from '../notificacion/notificacion.module'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + NgbModule.forRoot(), + LayoutRoutingModule, + CodemirrorModule, + NotificacionModule + ], + declarations: [ + LayoutComponent, + HeaderComponent, + SidebarComponent + ], + providers: [AuthenticationService, HaskellService] +}) +export class LayoutModule { } diff --git a/src/app/layout/matefun/confirm.component.ts b/src/app/layout/matefun/confirm.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..f2c1c727ed9168f37172b43190eca8766b7f072d --- /dev/null +++ b/src/app/layout/matefun/confirm.component.ts @@ -0,0 +1,37 @@ +import { Component } from '@angular/core'; +import { DialogComponent, DialogService } from "ng2-bootstrap-modal"; +export interface ConfirmModel { + title:string; + message:string; +} +@Component({ + selector: 'confirm', + template: `<div class="modal-dialog" style="margin-top:100px;"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" (click)="close()" >×</button> + <!-- <h4 class="modal-title">{{title || 'Confirm'}}</h4> --> + </div> + <div class="modal-body"> + <p>{{message || ''}}</p> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" (click)="confirm()">Editar</button> + <button type="button" class="btn btn-default" (click)="close()" >Cancelar</button> + </div> + </div> + </div>` +}) +export class ConfirmComponent extends DialogComponent<ConfirmModel, boolean> implements ConfirmModel { + title: string; + message: string; + constructor(dialogService: DialogService) { + super(dialogService); + } + confirm() { + // we set dialog result as true on click on confirm button, + // then we can get dialog result from caller code + this.result = true; + this.close(); + } +} \ No newline at end of file diff --git a/src/app/layout/matefun/index.ts b/src/app/layout/matefun/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..4676de0be9833a3554428a3549bd8dac2b3d2da8 --- /dev/null +++ b/src/app/layout/matefun/index.ts @@ -0,0 +1,5 @@ +/** + * This barrel file provides the export for the lazy loaded BlankpageComponent. + */ +export * from './matefun.component'; +export * from './matefun.routes'; diff --git a/src/app/layout/matefun/matefun-routing.module.ts b/src/app/layout/matefun/matefun-routing.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..4ff3fac0188bdba52797adf1e33277716babfafb --- /dev/null +++ b/src/app/layout/matefun/matefun-routing.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { MateFunComponent } from './matefun.component'; + +const routes: Routes = [ + { path: '', component: MateFunComponent } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class MateFunRoutingModule { } \ No newline at end of file diff --git a/src/app/layout/matefun/matefun.component.html b/src/app/layout/matefun/matefun.component.html new file mode 100644 index 0000000000000000000000000000000000000000..0e2031ad57a826aa1ffd9c923bae9fc0a9e87ef9 --- /dev/null +++ b/src/app/layout/matefun/matefun.component.html @@ -0,0 +1,122 @@ +<notificacion></notificacion> +<div class="container-fluid" style=" padding-left: 0px; padding-right: 0px; margin-top: -13px; margin-left: -6px; margin-right: -6px;"> + <div class="row"> + <div class="col-md-6"> + <ngb-tabset [destroyOnHide]=false> + <ngb-tab id="ProgramBtn"title="Programa"> + <ng-template ngbTabContent> + + <div class="card"> + <div class="card-header"> + <form> + <input type="text" name="archivo" class="nomArchivoInp form-control form-control-sm" + [disabled]="!archivo.editable || archivo.estado=='Corregido' || archivo.estado == 'Entregado'" + *ngIf="archivo" [(ngModel)]="archivo.nombre" (keyup)="archivoModificado()" placeholder="nombre del archivo" /> + + <button id="downloadFileButton" (click)="downloadFile()" style="margin-left: 10px; float: right;" class="btn btn-sm btn-secondary" placement="bottom" ngbPopover="Exportar (Ctrl+E)" triggers="mouseenter:mouseleave" tiggers="click"> + <i class="fa fa-download "></i> + </button> + + <button style="margin-left: 10px; float: right;" id="popover" class="btn btn-sm btn-secondary" placement="bottom" [ngbPopover]=popoverContent #popover="ngbPopover" popoverTitle="Configuración" tiggers="click"> + <i class="fa fa-gear"></i> + </button> + <div style="margin-left: 10px; float: right;" ngbPopover="Guardar archivo (Ctrl+G)" triggers="mouseenter:mouseleave" placement="bottom" > + <button [disabled]="!modificado" (click)="guardarArchivo()" class="btn btn-sm btn-secondary" > + <i class="fa fa-save"></i> + </button> + </div> + <button style="margin-left: 10px; float: right;" (click)="reiniciarInterprete()" class="btn btn-sm btn-secondary" ngbPopover="Reiniciar intérprete (Ctrl+R)" triggers="mouseenter:mouseleave" placement="bottom"> + <i class="fa fa-refresh"></i> + </button> + <button style="margin-left: 10px; float: right;" (click)="runCode()" class="btn btn-sm btn-secondary" ngbPopover="Cargar programa (Ctrl+P)" triggers="mouseenter:mouseleave" placement="bottom"> + <i class="fa fa-play"></i> + </button> + <button style="float: right;" (click)="seleccionarDirectorio()" class="btn btn-sm btn-secondary" ngbPopover="Nuevo archivo (Ctrl+A)" triggers="mouseenter:mouseleave" placement="bottom"> + <i class="fa fa-plus"></i> + </button> + <ng-template #popoverContent style="width: 15em"> + <div style="width: 12em"> + <div class="form-group"> + <label>Tema:</label> + <select name="theme" class="form-control form-control-sm" #selectTheme (change)=updateConfig(selectTheme.value)> + <option *ngFor="let theme of themes" [selected]="theme==configCodeMirror.theme" value='{{theme}}'>{{theme}}</option> + </select> + </div> + <div class="form-group"> + <label>Tamaño de fuente:</label> + <div> + <button class="btn btn-sm btn-secondary" (click)="aumentarFuente()">Aâº</button> + <button class="btn btn-sm btn-secondary" (click)="disminuirFuente()">Aâ»</button> + {{configCodeMirror.fontSize}}px + </div> + </div> + <div class="form-group"> + <label> + <input type="checkbox" style="width: 15px; display: inline-block;" name="argumentoF" class="form-control form-control-sm" [(ngModel)]=argumentoF> + Mostrar advertencias de uso de funciones + </label> + <br> + <label> + <input type="checkbox" style="width: 15px; display: inline-block;" name="argumentoI" class="form-control form-control-sm" [(ngModel)]=argumentoI> + Mostrar advertencias de uso de operadores infijos + </label> + </div> + <div class="form-group"> + <button class="btn btn-secondary" (click)="saveConfig()">Guardar</button> + </div> + </div> + </ng-template> + </form> + </div> + <codemirror class="codemirrorPrograma" [(ngModel)]="archivo.contenido" (keyup)="archivoModificado()" [config]="configCodeMirror" [ngStyle]="{'font-size': configCodeMirror.fontSize+'px'}"> + </codemirror> + </div> + + </ng-template> + </ngb-tab> + <ngb-tab id="FigurasBtn" title="Figuras"> + <ng-template ngbTabContent> + <canvas-component (canvasComp)=canvasC></canvas-component> + </ng-template> + </ngb-tab> + </ngb-tabset> + + </div> + + <div class="col-md-6"> + <!-- + <ngb-tabset [destroyOnHide]=false> + <ngb-tab title="Programa"> + <ng-template ngbTabContent> + + <div class="card"> + <div id="console" > </div> + </div> + + </ng-template> + </ngb-tab> + <ngb-tab title="Figuras"> + <ng-template ngbTabContent> + <canvas-component (canvasComp)=canvasC></canvas-component> + </ng-template> + </ngb-tab> + </ngb-tabset> + --> + + + + <div class="card"> + <div id="console"> </div> + </div> + <!-- + <canvas-component (canvasComp)=canvasC></canvas-component> + + <div class="card"> + <div id="svgHaskell"> + </div> + </div> --> + + </div> + </div> +</div> + diff --git a/src/app/layout/matefun/matefun.component.scss b/src/app/layout/matefun/matefun.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..53f3612a50baea080c6c5c35f7a34f68c66212d0 --- /dev/null +++ b/src/app/layout/matefun/matefun.component.scss @@ -0,0 +1,5 @@ +#svgHaskell svg{ + width: 100% !important; + height: 100% !important; +} + diff --git a/src/app/layout/matefun/matefun.component.ts b/src/app/layout/matefun/matefun.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..837dc7c54c6c800ef20500e38391447a8f81588c --- /dev/null +++ b/src/app/layout/matefun/matefun.component.ts @@ -0,0 +1,621 @@ +import { Component, NgModule, ViewChild, HostListener, ElementRef, ComponentRef, TemplateRef } from '@angular/core'; +import { CanvasModule} from '../canvas/canvas.module'; +import { CanvasComponent } from '../canvas/canvas.component'; +import { FunctionsModule } from '../functions/functions.module'; +import { FunctionsComponent } from '../functions/functions.component'; +import { Http, JsonpModule } from '@angular/http'; +import { Headers, RequestOptions } from '@angular/http'; +import { HaskellService } from '../../shared/services/haskell.service'; +import { FunctionServices } from '../functions/functions.service'; +import { WebsocketService } from '../../shared/services/websocket.service'; +import { UsuarioService } from '../../shared/services/usuario.service'; +import { SessionService } from '../../shared/services/session.service'; +import { GHCIService } from '../../shared/services/ghci.service'; +import { AuthenticationService } from '../../shared/services/authentication.service'; +import { GHCI_URL } from '../../shared/config'; +import { Archivo } from '../../shared/objects/archivo'; +import { Configuracion } from '../../shared/objects/usuario'; +import { ConfirmComponent } from './confirm.component'; +import { SeleccionarDirectorioComp } from './seleccionarDirectorio.component'; +import { DialogService } from "ng2-bootstrap-modal"; +import { CodemirrorComponent } from 'ng2-codemirror'; +import { NgbPopoverConfig, NgbPopover} from '@ng-bootstrap/ng-bootstrap'; +import { NgbPopoverWindow } from '@ng-bootstrap/ng-bootstrap/popover/popover'; +import { NotificacionService } from '../../shared/services/notificacion.service'; + +import 'rxjs/add/operator/catch'; +import 'rxjs/add/operator/map'; +import 'codemirror/mode/haskell/haskell'; +import 'codemirror/addon/display/panel'; +import 'codemirror/addon/hint/show-hint'; +import 'codemirror/addon/hint/anyword-hint'; +import 'codemirror/mode/markdown/markdown'; +import 'codemirror/lib/codemirror'; +import 'codemirror/addon/search/search'; + + +import 'codemirror/addon/dialog/dialog'; +import 'codemirror/addon/search/search'; +import 'codemirror/addon/search/matchesonscrollbar'; +import 'codemirror/addon/search/jump-to-line'; + +var codeMirrorRef:any; +var componentRef : any; +var focus: any; +@Component({ + moduleId: module.id, + selector: 'matefun', + templateUrl: './matefun.component.html', + styleUrls: ['./matefun.component.scss'], + providers: [ WebsocketService, FunctionServices, NgbPopoverConfig, UsuarioService ] +}) + + +export class MateFunComponent { + + consoleDisable: boolean = false; + consolaVisible: boolean = true; + cursorPanel: any; + cursorPanelLabel: any; + cursorLabelInit : boolean = false; + entrada : string = ''; + archivo : Archivo; + copiaNombreArchivo:string; + copiaContenidoArchivo:string; + modificado = false; + argumentoI = false; + argumentoF = false; + editableLoaded = false; + editDialogFired = false; + archivosTree :any; + idRecorridos: any; + code: string =''; + configCodeMirror = { + readOnly: false, + lineNumbers: true, + lineWrapping : true, + extraKeys: {"Ctrl-Space": "autocomplete"}, + mode: { + name: "haskell", + globalVars: true + }, + gutters: ["CodeMirror-linenumbers", "breakpoints"], + theme: 'dracula', + fontSize: 12 + }; + themes = ['3024-day', '3024-night', 'abcdef', 'ambiance-mobile', 'ambiance', 'base16-dark', 'base16-light', 'bespin', 'blackboard', 'cobalt', 'colorforth', 'dracula', 'duotone-dark', 'duotone-light', 'eclipse', 'elegant', 'erlang-dark', 'hopscotch', 'icecoder', 'isotope', 'lesser-dark', 'liquibyte', 'material', 'mbo', 'mdn-like', 'midnight', 'monokai', 'neat', 'neo', 'night', 'panda-syntax', 'paraiso-dark', 'paraiso-light', 'pastel-on-dark', 'railscasts', 'rubyblue', 'seti', 'solarized', 'the-matrix', 'tomorrow-night-bright', 'tomorrow-night-eighties', 'ttcn', 'twilight', 'vibrant-ink', 'xq-dark', 'xq-light', 'yeti', 'zenburn'] + + + constructor( + private haskellService: HaskellService, + private authService: AuthenticationService, + private ghciService: GHCIService, + private elRef: ElementRef, + private notifService: NotificacionService, + private functionServices: FunctionServices, + private sessionService: SessionService, + private dialogService:DialogService, + private usuarioService: UsuarioService) { + //si el archivo fue seteado en la session. + this.archivo = sessionService.getArchivo(); + if(!this.archivo || !this.archivo.id){ + this.newFile(); + } + this.copiaContenidoArchivo = this.archivo.contenido; + this.copiaNombreArchivo = this.archivo.nombre; + if(authService.getUser().configuracion){ + var config: Configuracion = authService.getUser().configuracion; + if(config.fontSizeEditor<=30 && config.fontSizeEditor>=8){ + this.configCodeMirror.fontSize = config.fontSizeEditor; + } + if(this.themes.some(theme => theme==config.themeEditor)){ + this.configCodeMirror.theme = config.themeEditor; + } + sessionStorage.setItem('codeMirrorConfig',JSON.stringify(this.configCodeMirror)); + this.argumentoI = config.argumentoI; + this.argumentoF = config.argumentoF; + + } + this.code = "my code"; + let svg : string = ''; + + } + + + @ViewChild(CodemirrorComponent) codemirror: CodemirrorComponent; + // @ViewChild(NgbPopover) popover: NgbPopover; + @ViewChild('popover') popover: NgbPopover; + + updateConfig(theme){ + this.configCodeMirror.theme = theme; + this.codemirror.instance.setOption('theme', theme); + sessionStorage.setItem('codeMirrorConfig',JSON.stringify(this.configCodeMirror)); + } + + lockSaveButton (){ + this.copiaNombreArchivo = this.archivo.nombre; + this.copiaContenidoArchivo = this.archivo.contenido; + this.modificado = false; + } + + + showConfirm() { + let disposable = this.dialogService.addDialog(ConfirmComponent, { + title:'Está intentando editar un archivo de solo lectura', + message:'Está editando un archivo de solo lectura, desea continuar?'}) + .subscribe((isConfirmed)=>{ + + if(isConfirmed) { + codeMirrorRef.options.readOnly = false; + componentRef.editDialogFired = true; + } + }); + //We can close dialog calling disposable.unsubscribe(); + //If dialog was not closed manually close it by timeout + /* setTimeout(()=>{ + disposable.unsubscribe(); + },10000);*/ + } + + /* Panel para la posición del cursor */ + makePanel() { + + var node = document.createElement("div"); + node.id = "cursorpos-panel"; + node.className = "panel bottom"; + this.cursorPanelLabel = node.appendChild(document.createElement("span")); + var cm = this.codemirror.instance; + var x = cm.getCursor().line; + var y = cm.getCursor().ch; + x = (Number(x) + 1).toString(); + y = (Number(y) + 1).toString(); + this.cursorPanelLabel.textContent = "Posición del cursor: (" + x + "," + y + ")"; + + + this.cursorPanel = this.codemirror.instance.addPanel(node, {position: "bottom", stable: true}); + var that = this; + //agregamos el evento que setea la posición + this.codemirror.instance.on("cursorActivity",function(cm){ + var x = cm.getCursor().line; + var y = cm.getCursor().ch; + x = (Number(x) + 1).toString(); + y = (Number(y) + 1).toString(); + that.cursorPanel.node.innerText = "Posición del cursor: (" + x + "," + y + ")"; + }); + + this.codemirror.instance.on("keyHandled",function(cm,name,evt){ + if(name.code==="Digit1" && name.ctrlKey && name.shiftKey){ + that.seleccionarDirectorio(); + } else if(name.code==="Digit2" && name.ctrlKey && name.shiftKey){ + that.saveConfig(); + } + }); + /* + this.codemirror.instance.on("gutterClick", function(cm, n) { + var info = cm.lineInfo(n); + var makeMarker = function() { + var marker = document.createElement("div"); + marker.style.width = "15px"; + marker.style.height = "15px"; + marker.style.marginLeft = "-5px"; + marker.style.cursor = "pointer"; + marker.style["background-image"] = "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=')"; + marker.innerHTML = "<a href='@' title='cuidado , advertencia matefun'></a>"; + return marker; + } + cm.setGutterMarker(n, "breakpoints", info.gutterMarkers ? null : makeMarker()); + });*/ + + this.codemirror.instance.on("keypress",function(cm,name,evt){ + + if(!that.editDialogFired && JSON.parse(sessionStorage.currentUser).tipo === "docente" && cm.options.readOnly){ + codeMirrorRef = that.codemirror.instance; + componentRef = that; + that.showConfirm(); + } + + }); + } + + saveConfig(){ + var config = new Configuracion(); + config.themeEditor = this.configCodeMirror.theme; + config.fontSizeEditor = this.configCodeMirror.fontSize; + var confUser = this.authService.getUserConfig(); + var reiniciar = confUser.argumentoF != this.argumentoF || confUser.argumentoI != this.argumentoI; + config.argumentoF = this.argumentoF; + config.argumentoI = this.argumentoI; + this.usuarioService.actualizarConfiguracion(this.authService.getUser().cedula,config) + .subscribe( + success=> { + //this.ghciService.consoleRef.Write("Configuración guardada" + "\n"); + this.popover.close(); + this.authService.setUserConfig(success); + if(reiniciar){ + this.reiniciarInterprete(); + } + + }, + error=> { + this.notifService.error(error); + this.popover.close(); + } + ); + } + + aumentarFuente(){ + if(this.configCodeMirror.fontSize<30){ + this.configCodeMirror.fontSize++; + } + } + + disminuirFuente(){ + if(this.configCodeMirror.fontSize>8){ + this.configCodeMirror.fontSize--; + } + } + + @HostListener('document:click', ['$event']) + private documentClicked(event: MouseEvent): void { + + // Popover is open + if (this.popover && this.popover.isOpen()) { + + // Not clicked on self element + if (!(this.popover as any)._elementRef.nativeElement.contains(event.target)) { + + // Hacking typescript to access private member + const popoverWindowRef: ComponentRef<NgbPopoverWindow> = (this.popover as any)._windowRef; + + // If clicked outside popover window + if (!popoverWindowRef.location.nativeElement.contains(event.target)) { + this.popover.close(); + } + } + } + } + + ngOnInit() { + + this.ghciService.rendered(); + + + this.haskellService.getArchivos(this.authService.getUser().cedula) + .subscribe( + archivos => { + //.filter(function(a){return !a.eliminado}) + this.buildTreeFromList(archivos); + + }, + error => console.log("Error al obtener los archivos del alumno") + ); + + function KeyPress(e) { + + var evtobj = window.event? event : e + if (evtobj.keyCode == 90 && evtobj.ctrlKey){ + //alert("Ctrl+z") + }; + if(evtobj.key.toLowerCase() ==="a" && evtobj.ctrlKey){ + componentRef.seleccionarDirectorio(); + return false; + }else if(evtobj.key.toLowerCase() ==="e" && evtobj.ctrlKey){ + componentRef.downloadFile(); + return false; + } else if(evtobj.key.toLowerCase() ==="r" && evtobj.ctrlKey){ + componentRef.reiniciarInterprete(); + return false; + } else if(evtobj.key.toLowerCase() ==="g" && evtobj.ctrlKey){ + componentRef.guardarArchivo(); + return false; + } else if(evtobj.key.toLowerCase() ==="o" && evtobj.ctrlKey){ + document.getElementById("popover").click(); + return false; + } else if(evtobj.ctrlKey && evtobj.altKey && evtobj.key.toLowerCase() ==="p"){ + document.getElementById("ProgramBtn").click(); + var that = componentRef; + setTimeout(function() { + that.codemirror.instance.focus(); + },250); + componentRef.codemirror.instance.focus(); + focus ="program"; + return false; + } else if(evtobj.ctrlKey && evtobj.altKey && evtobj.key.toLowerCase() ==="c"){ + componentRef.ghciService.focusConsole(); + focus = "consola"; + return false; + } else if(evtobj.ctrlKey && evtobj.altKey && evtobj.key.toLowerCase() ==="f"){ + document.getElementById("FigurasBtn").click() + componentRef.ghciService.focusConsole(); + focus = "graficas"; + return false; + } else if(evtobj.key.toLowerCase() ==="p" && evtobj.ctrlKey && !evtobj.altKey){ + componentRef.runCode(); + return false; + } + } + document.onkeydown = KeyPress; + } + ngAfterViewInit() { + componentRef = this; + if(this.codemirror.instance!=null && !this.cursorLabelInit){ + this.cursorLabelInit = true; + this.codemirror.instance.setOption('theme', this.configCodeMirror.theme); + this.makePanel(); + } + if(!this.editableLoaded && this.codemirror.instance!=null &&(this.sessionService.archivo.editable!==undefined)){ + try{ + var editable = this.sessionService.archivo.editable && (this.sessionService.archivo.estado == 'Edicion' || this.sessionService.archivo.estado == 'Devuelto'); + this.codemirror.instance.options.readOnly = !editable; + this.editableLoaded = true; + + + } catch(err) { + return; + + } + } + + } + htmlEncode(value:string){ + return value + .replace('Prelude> ','') + .replace(/&/g, '&') + .replace(/\s/g, ' ') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/</g, '<') + .replace(/>/g, '>'); + } + + @ViewChild(CanvasComponent) canvasC: CanvasComponent; + + funcionSTR: string = 'Math.sin(x)*x*x-20'; + consola: string = ''; + command: string = ''; + tipo: number = 1; + + private onKey = function (value: string) { + this.funcionSTR = value; + this.archivo.contenido = value; + } + + private writeCommand = function (value: string){ + this.command = value.split("\n")[value.split("\n").length - 1]; + } + + private selectFunction = function() { + this.tipo = 1; + this.funcionSTR = "Math.sin(x)*x*x-20"; + } + + private selectElipse = function() { + this.tipo = 2; + this.funcionSTR = "elipse(x,y,radioX, radioY, rotacion_en_grados)"; + } + + private selectCircle = function() { + this.tipo = 3; + this.funcionSTR = "circulo(x,y,radio)"; + } + + private elipse = function(x: number, y: number, radiusX: number, radiusY: number, rotation: number) { + return [x, y, radiusX, radiusY, rotation]; + } + + private circulo = function(x: number, y: number, radius: number) { + return [x, y, radius]; + } + + inputConsola(text:any){ + this.entrada = text; + } + newFile(){ + this.archivo = new Archivo(); + this.archivo.cedulaCreador = this.authService.getUser().cedula; + this.archivo.contenido = ""; + this.archivo.nombre = ""; + this.copiaNombreArchivo = ''; + this.copiaContenidoArchivo = ''; + } + + archivoModificado(){ + if(this.copiaNombreArchivo!=this.archivo.nombre || this.copiaContenidoArchivo != this.archivo.contenido){ + this.modificado = true; + }else{ + this.modificado = false; + } + } + + guardarArchivo(){ + var regex = /^[A-Z]/ + if(this.archivo.nombre.trim() == ""){ + this.notifService.error("Nombre de archivo sin especificar"); + }else if (!regex.test(this.archivo.nombre)){ + this.notifService.error("Nombre de archivo debe iniciar con mayusula.") + }else{ + if(this.archivo.id){ + this.haskellService.editarArchivo(this.archivo.id, this.archivo) + .subscribe( + archivo => { + //this.ghciService.consoleRef.Write("Editar archivo: " + this.archivo.nombre + "\n"); + this.archivo = archivo; + this.lockSaveButton(); + }, + error => { + this.notifService.error(error); + }); + }else{ + this.haskellService.crearArchivo(this.archivo) + .subscribe( + archivo => { + //this.ghciService.consoleRef.Write("Archivo creado: " + this.archivo.nombre + "\n"); + this.archivo = archivo; + this.lockSaveButton(); + }, + error => { + this.notifService.error(error); + }); + + } + } + } + runCode(){ + + this.ghciService.setCodemirrorRef(this.codemirror.instance); + this.ghciService.resetGutters(); + var regex = /^[A-Z]/ + if(this.archivo.nombre.trim() == ""){ + this.notifService.error("Nombre de archivo sin especificar"); + }else if (!regex.test(this.archivo.nombre)){ + this.notifService.error("Nombre de archivo debe iniciar con mayusula.") + }else{ + + var resultado = this.sessionService.cargarDependencias(this.archivo); + if(resultado["status"]==="miss"){ + this.ghciService.outputConsole("Error: No se encuentra el archivo " + resultado["nombre"] + "\n"); + return; + } + if(this.archivo.id){ + if(this.archivo.editable || this.authService.getUser().tipo == 'docente'){ + this.haskellService.editarArchivo(this.archivo.id, this.archivo) + .subscribe( + archivo => { + this.archivo = archivo; + var list = this.sessionService.getDependencias(), + idList = []; + for(var l in list){ + idList.push(list[l].id); + } + if(!idList.some(id => id ==archivo.id)){ + idList.push(archivo.id); + } + this.lockSaveButton(); + this.ghciService.loadFile(archivo.id,idList); + }, + error => { + this.notifService.error(error); + }); + }else{ + var list = this.sessionService.getDependencias(), + idList = []; + for(var l in list){ + idList.push(list[l].id); + } + if(!idList.some(id => id ==this.archivo.id)){ + idList.push(this.archivo.id); + } + this.ghciService.loadFile(this.archivo.id,idList); + } + }else{ + this.haskellService.crearArchivo(this.archivo) + .subscribe( + archivo => { + this.archivo = archivo; + this.lockSaveButton(); + this.ghciService.loadFile(archivo.id,[]); + }, + error => { + this.notifService.error(error); + }); + } + } + this.ghciService.focusConsole(); + + } + download(filename, text) { + var element = document.createElement('a'); + element.setAttribute('href', 'data:application/octet-stream,' + encodeURIComponent(text)); + element.setAttribute('download', filename+ ".mf"); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); + } + downloadFile(){ + var nom = this.archivo.nombre; + var content = this.archivo.contenido; + if(nom!= undefined && nom!="" && content!= undefined && content !=""){ + this.download(nom , content); + } + + } + reiniciarInterprete(){ + this.ghciService.reiniciarInterprete(); + } + + toggleConsole(){ + this.consolaVisible = !this.consolaVisible; + } + + seleccionarDirectorio(){ + this.archivosTree = this.sessionService.getArchivos(undefined); + var that = this; + let disposable = this.dialogService.addDialog(SeleccionarDirectorioComp, { + title:'', + message:'', + archivos:this.archivosTree, + directorioActual:this.archivosTree, + nombre:'', + parent:this}) + .subscribe((isConfirmed)=>{ + + if(isConfirmed) { + + + //codeMirrorRef.options.readOnly = false; + //componentRef.editDialogFired = true; + } + }); + } + + + buildTreeFromList (archivos){ + + + this.sessionService.setArchivosList(archivos); + var root :Archivo; + + for(var a in archivos){ + var arch = archivos[a]; + if(arch.padreId===-1){ + root = arch; + } + } + this.idRecorridos = [root.id]; + var archivos2 = archivos.filter( + function(a){ + return a.id!==root.id; + } + ); + var tree = this.buildTree(archivos2,root); + this.archivosTree = tree; + this.sessionService.setArchivosTree(tree); + } + + + buildTree(archivos, root){ + root.archivos = this.getArchivos(root.id,archivos); + for(var a in root.archivos){ + if(root.archivos[a].directorio && this.idRecorridos[root.archivos[a].id] === undefined){ + var id = root.archivos[a].id; + var archivos2 = archivos.filter(function(a){return a.id!==id}); + root.archivos[a] = this.buildTree(archivos2 ,root.archivos[a]); + } + } + return root; + } + + getArchivos(id,archivos){ + return archivos.filter( + function(a){ + return a.padreId === id; + }); + } + + + + } diff --git a/src/app/layout/matefun/matefun.module.ts b/src/app/layout/matefun/matefun.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..838a75b5583601ee96c5d6dc3886739cbc39518b --- /dev/null +++ b/src/app/layout/matefun/matefun.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { CanvasModule } from '../canvas/canvas.module' +import { MateFunComponent } from './matefun.component'; +import { FunctionsModule } from '../functions/functions.module' +import { BootstrapModalModule } from 'ng2-bootstrap-modal'; +import { ConfirmComponent } from './confirm.component'; +import { SeleccionarDirectorioComp } from './seleccionarDirectorio.component'; +import { CommonModule } from '@angular/common'; +import { MateFunRoutingModule } from './matefun-routing.module'; +import { CodemirrorModule } from 'ng2-codemirror'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NotificacionModule } from '../../notificacion/notificacion.module'; +@NgModule({ + imports: [ + CommonModule, + FormsModule, + CanvasModule, + NotificacionModule, + FunctionsModule, + MateFunRoutingModule, + CodemirrorModule, + NgbModule, + BootstrapModalModule + ], + entryComponents: [ + ConfirmComponent, + SeleccionarDirectorioComp + ], + declarations: [MateFunComponent,ConfirmComponent,SeleccionarDirectorioComp], + exports: [MateFunComponent] +}) + +export class MateFunModule { } diff --git a/src/app/layout/matefun/matefun.routes.ts b/src/app/layout/matefun/matefun.routes.ts new file mode 100644 index 0000000000000000000000000000000000000000..222b5fdaef865844401e09f21c86af1e385cdb22 --- /dev/null +++ b/src/app/layout/matefun/matefun.routes.ts @@ -0,0 +1,8 @@ +import { Route } from '@angular/router'; +import { MateFunComponent } from './index'; +export const MateFunRoutes: Route[] = [ + { + path: 'funciones', + component: MateFunComponent + } +]; diff --git a/src/app/layout/matefun/seleccionarDirectorio.component.ts b/src/app/layout/matefun/seleccionarDirectorio.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..90625e8808784168852459fff0454f3ad6181bce --- /dev/null +++ b/src/app/layout/matefun/seleccionarDirectorio.component.ts @@ -0,0 +1,99 @@ +import { Component } from '@angular/core'; +import { DialogComponent, DialogService } from "ng2-bootstrap-modal"; +import { Archivo } from '../../shared/objects/archivo'; +export interface ConfirmModel { + title:string; + message:string; + archivos:any; + directorioActual:any; + parent:any; + nombre:string; +} +@Component({ + selector: 'confirm', + template: `<div class="modal-dialog" style="margin-top:100px;"> + <div class="modal-content"> + <div class="modal-header"> + <h6 class="modal-title pull-lefth">¿Dónde quieres crear el archivo?</h6> + <button type="button" class="close" (click)="close()" style="margin-rigth:8px;">×</button> + </div> + <div class="modal-body" style="height:350px;overflow-y: scroll;"> + <div> + <div class="form-group"> + <label for="file-name" class="form-control-label">Nombre:</label> + <input type="text" class="form-control" id="file-name" [(ngModel)]="nombre" > + </div> + <div class="list-group" > + <button *ngFor="let arch of directorioActual.archivos" type="button" (click)="navToDir(arch)" style="cursor:pointer" class="list-group-item list-group-item-action"> + <i *ngIf="arch.directorio" class="fa fa-folder" style="margin-right:10px; font-size: 3em; cursor: pointer;" aria-hidden="true" ></i> + <i *ngIf="!arch.directorio" class="fa fa-file-text" style="margin-right:10px;font-size: 3em; cursor: pointer;" aria-hidden="true"></i> + {{arch.nombre}} + </button> + </div> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" (click)="navBack()">Atras</button> + <button type="button" class="btn btn-primary" (click)="confirm()">Crear</button> + </div> + </div> + </div>` +}) +export class SeleccionarDirectorioComp extends DialogComponent<ConfirmModel, boolean> implements ConfirmModel { + title: string; + message: string; + archivos:any; + directorioActual:any; + parent:any; + nombre:string; + + constructor(dialogService: DialogService) { + super(dialogService); + } + confirm() { + // we set dialog result as true on click on confirm button, + // then we can get dialog result from caller code + var regex = /^[A-Z]/ + if(this.nombre==undefined || this.nombre==""){ + this.parent.notifService.error("Nombre de archivo invalido."); + }else if (!regex.test(this.nombre)){ + this.parent.notifService.error("Nombre de archivo debe iniciar con mayusula."); + }else{ + var archivo:Archivo = new Archivo(); + archivo.cedulaCreador = this.parent.authService.getUser().cedula; + archivo.contenido = ""; + archivo.nombre = this.nombre; + archivo.directorio = false; + archivo.padreId = this.directorioActual.id; + archivo.editable = true; + + this.parent.haskellService.crearArchivo(archivo) + .subscribe( + archivo => { + this.parent.archivo = archivo; + this.parent.ghciService.loadFile(archivo.id); + this.parent.sessionService.setArchivo(archivo); + + }, + error => { + this.parent.notifService.error(error); + }); + + this.result = true; + this.close(); + } + } + + navToDir(arch){ + if(arch.directorio){ + this.directorioActual = arch; + } + } + + navBack(){ + var idPadre = this.directorioActual.padreId; + var archivosList = this.parent.sessionService.getArchivosList(); + var nuevoDirectorioActual = archivosList.filter(function(a){return a.id===idPadre})[0]; + this.directorioActual=nuevoDirectorioActual; + } +} \ No newline at end of file diff --git a/src/app/login/login-routing.module.ts b/src/app/login/login-routing.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..0235f7004a94fbde7677a3694ea9187ce3f47f79 --- /dev/null +++ b/src/app/login/login-routing.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { LoginComponent } from './login.component'; + +const routes: Routes = [ + { path: '', component: LoginComponent } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class LoginRoutingModule { } diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html new file mode 100644 index 0000000000000000000000000000000000000000..53a6d149833e6b5c4158816c71e3923a3d0cccc2 --- /dev/null +++ b/src/app/login/login.component.html @@ -0,0 +1,33 @@ +<div class="login-page" style="background: #004869;"> + <div class="row"> + <div class="col-md-4 push-md-4" style="padding: 55px 35px;background: rgba(255,255,255,0.15);border-radius: 8px;"> + <h1>MateFun</h1> + <form role="form"> + <div class="form-content"> + <div class="form-group"> + <input type="text" [(ngModel)]=model.cedula name="cedula" class="form-control input-underline input-lg" placeholder="Usuario"> + </div> + + <div class="form-group"> + <input type="password" [(ngModel)]=model.password (keyup.enter)=login() name="password" class="form-control input-underline input-lg" placeholder="Contraseña"> + </div> + + </div> + + <a class="btn rounded-btn" style="background: transparent;color: white;cursor: pointer;width: 159px;margin-right: 3px;" (click)=login()> Iniciar Sesión </a> + <a class="btn rounded-btn" style="background: transparent;color: white;cursor: pointer;width: 159px;margin-left: 3px;" (click)=invitado()> Invitado </a> + + <div class="loading" *ngIf="loading"> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + </div> + <div class="login-error" *ngIf="error && !loading"> + <ngb-alert [dismissible]="false" [type]="'danger'"> + <strong>Error!</strong> {{errorText}}. + </ngb-alert> + </div> + </form> + </div> + </div> +</div> diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..92ee5466c358a42d782a3892bebf02c3424934c1 --- /dev/null +++ b/src/app/login/login.component.scss @@ -0,0 +1,105 @@ +$topnav-background-color: #222; +:host { + display: block; +} +.login-page { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: auto; + background: $topnav-background-color; + text-align: center; + color: #fff; + padding: 3em; + .col-lg-4{ + padding :0; + } + .input-lg { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 0; + } + .input-underline { + background: 0 0; + border: none; + box-shadow: none; + border-bottom: 2px solid rgba(255,255,255,.5); + color: #FFF; + border-radius: 0; + } + .input-underline:focus { + border-bottom: 2px solid #fff; + box-shadow: none; + } + .rounded-btn{ + -webkit-border-radius: 50px; + border-radius: 50px; + color: rgba(255,255,255,0.8); + background: $topnav-background-color; + border: 2px solid rgba(255,255,255,0.8); + font-size: 18px; + line-height: 40px; + padding: 0 25px; + cursor: default; + } + .rounded-btn:hover,.rounded-btn:focus,.rounded-btn:active,.rounded-btn:visited{ + color: rgba(255,255,255,1); + border: 2px solid rgba(255,255,255,1); + outline: none; + } + + h1 { + font-weight: 300; + margin-top: 20px; + margin-bottom: 10px; + font-size: 36px; + small { + color: rgba(255,255,255,0.7); + } + } + + .form-group { + padding: 8px 0; + input::-webkit-input-placeholder { + color: rgba(255,255,255,0.6) !important; + } + + input:-moz-placeholder { /* Firefox 18- */ + color: rgba(255,255,255,0.6) !important; + } + + input::-moz-placeholder { /* Firefox 19+ */ + color: rgba(255,255,255,0.6) !important; + } + + input:-ms-input-placeholder { + color: rgba(255,255,255,0.6) !important; + } + } + .form-content { + padding: 40px 0; + } + .user-avatar { + -webkit-border-radius: 50%; + border-radius: 50%; + border: 2px solid #FFF; + } +} + +.loading { + position: relative; + top: 50px; + left: 49%; + .loading-bar { + background-color: white; + } +} + +.login-error { + position: relative; + top: 20px; +} diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..d6d85a8465b79ee37cb75c371f6e6e936997c573 --- /dev/null +++ b/src/app/login/login.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture<LoginComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LoginComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..c2a01b4cef5b2efbcee0dcb1b36d650dacc52a09 --- /dev/null +++ b/src/app/login/login.component.ts @@ -0,0 +1,68 @@ +import { Component, OnInit } from '@angular/core'; +import { Router, ActivatedRoute } from '@angular/router'; +import { SessionService } from '../shared/services/session.service'; +import { AuthenticationService } from '../shared/services/authentication.service'; + + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'] + +}) + +export class LoginComponent implements OnInit { + model: any = {}; + loading = false; + error = false; + errorText = ""; + returnUrl: string; + + constructor( + private route: ActivatedRoute, + private router: Router, + private sessionService: SessionService, + private authenticationService: AuthenticationService, + ) { } + + ngOnInit() { + // reset login status + this.authenticationService.logout(); + + // get return url from route parameters or default to '/' + this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/matefun'; + } + + login() { + // this.router.navigate([this.returnUrl]); + this.loading = true; + + var that = this; + this.authenticationService.login(this.model.cedula, this.model.password) + .subscribe( + data => { + //resetSession = true; + this.router.navigate([this.returnUrl]); + that.sessionService.reset(); + }, + error => { + this.loading = false; + this.error = true; + this.errorText = error.text(); + }); + } + + invitado(){ + this.loading = true; + this.authenticationService.login("invitado", "invitado") + .subscribe( + data => { + this.router.navigate([this.returnUrl]); + this.sessionService.reset(); + }, + error => { + this.loading = false; + }); + } + +} diff --git a/src/app/login/login.module.ts b/src/app/login/login.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..8d8c187f673ea0d5097a98198783212ed16f67a9 --- /dev/null +++ b/src/app/login/login.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; + +import { LoginRoutingModule } from './login-routing.module'; +import { LoginComponent } from './login.component'; +import { FormsModule } from '@angular/forms'; +import { AuthenticationService } from '../shared/services/authentication.service'; + +@NgModule({ + imports: [ + FormsModule, + CommonModule, + LoginRoutingModule, + NgbModule.forRoot(), + ], + declarations: [LoginComponent], + providers: [AuthenticationService] +}) +export class LoginModule { } diff --git a/src/app/not-found/not-found-routing.module.ts b/src/app/not-found/not-found-routing.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..ee86f4ae06cb43ea36f9447389b068f566762723 --- /dev/null +++ b/src/app/not-found/not-found-routing.module.ts @@ -0,0 +1,14 @@ +import {NgModule} from "@angular/core"; +import {Routes, RouterModule} from "@angular/router"; +import {NotFoundComponent} from "./not-found.component"; + +const routes: Routes = [ + {path: '', component: NotFoundComponent} +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class NotFoundRoutingModule { +} diff --git a/src/app/not-found/not-found.component.html b/src/app/not-found/not-found.component.html new file mode 100644 index 0000000000000000000000000000000000000000..10f68541474d3c6853f3644cb1c00486ea8f0aeb --- /dev/null +++ b/src/app/not-found/not-found.component.html @@ -0,0 +1,11 @@ +<div class="welcome-page"> + <div class="row"> + <div class="col-md-10 push-md-1"> + <h1>404 - Page Not Found</h1> + <p class="lead">This page does not exist</p> + <p class="lead"> + <a class="btn rounded-btn" [routerLink]="['/login']">Restart</a> + </p> + </div> + </div> +</div> \ No newline at end of file diff --git a/src/app/not-found/not-found.component.scss b/src/app/not-found/not-found.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..3149a41815cff7941921441a07891804bb06b4ab --- /dev/null +++ b/src/app/not-found/not-found.component.scss @@ -0,0 +1,41 @@ +$topnav-background-color: #222; +:host { + display: block; +} +.welcome-page { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: auto; + background: $topnav-background-color; + text-align: center; + color: #fff; + padding: 10em; + .col-lg-8{ + padding :0; + } + .rounded-btn{ + -webkit-border-radius: 50px; + border-radius: 50px; + color: rgba(255,255,255,0.8); + background: $topnav-background-color; + border: 2px solid rgba(255,255,255,0.8); + font-size: 18px; + line-height: 40px; + padding: 0 25px; + } + .rounded-btn:hover,.rounded-btn:focus,.rounded-btn:active,.rounded-btn:visited{ + color: rgba(255,255,255,1); + border: 2px solid rgba(255,255,255,1); + outline: none; + } + + h1 { + font-weight: 300; + margin-top: 20px; + margin-bottom: 10px; + font-size: 36px; + } +} diff --git a/src/app/not-found/not-found.component.ts b/src/app/not-found/not-found.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..00d72a9cfdfa39fefc5054171b4f208acb2a9f7d --- /dev/null +++ b/src/app/not-found/not-found.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-not-found', + templateUrl: './not-found.component.html', + styleUrls: ['not-found.component.scss'] +}) +export class NotFoundComponent { } diff --git a/src/app/not-found/not-found.module.ts b/src/app/not-found/not-found.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..719e5be364cd0d066c51393c6538fb0438cc1727 --- /dev/null +++ b/src/app/not-found/not-found.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { NotFoundComponent } from './not-found.component'; +import { NotFoundRoutingModule } from './not-found-routing.module'; + +@NgModule({ + imports: [ + NotFoundRoutingModule, + RouterModule + ], + declarations: [NotFoundComponent] +}) +export class NotFoundModule {} diff --git a/src/app/notificacion/notificacion.component.html b/src/app/notificacion/notificacion.component.html new file mode 100644 index 0000000000000000000000000000000000000000..6e3bd29413e164bef8653367476677457c861ded --- /dev/null +++ b/src/app/notificacion/notificacion.component.html @@ -0,0 +1,9 @@ +<!-- +<ngb-alert class="alertPosition" *ngFor="let alert of alerts;let i = index" [type]="alert.type" [style.top]="(i*70+50)+'px'" dismissible="true" dismissOnTimeout="5000" (close)="closeAlert(i)"> + {{ alert?.text }} +</ngb-alert> + --> + +<ngb-alert class="alertPosition" *ngFor="let alert of alerts; let i = index" [type]="alert?.type" [style.top]="(i*60+30)+'px'" (close)="closeAlert(i)"> +{{ alert?.text }} +</ngb-alert> diff --git a/src/app/notificacion/notificacion.component.ts b/src/app/notificacion/notificacion.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..5a311f83b0975d1beffe432ef43a7975f2e2be18 --- /dev/null +++ b/src/app/notificacion/notificacion.component.ts @@ -0,0 +1,38 @@ +import { Component, OnInit } from '@angular/core'; + +import { NotificacionService } from '../shared/services/notificacion.service'; + +import 'rxjs/add/operator/debounceTime'; + +@Component({ + moduleId: module.id, + selector: 'notificacion', + templateUrl: 'notificacion.component.html' +}) + +export class NotificacionComponent { + message: any; + + constructor(private notifService: NotificacionService) { + } + + ngOnInit() { + this.notifService.getMessageSubject().subscribe(message => { + this.alerts.push(message); + setTimeout(()=>{ + this.closeAlert(0);//siempre elimino la 0 dado que es la mas antigua que se agrego. + }, 5000); + }); + //Esto es otra forma que lugo de 5 segundos del ultimo mensaje vacia todo el arreglo. + // this.notifService.getMessageSubject().debounceTime(5000).subscribe(() => this.alerts = []); + + } + + public alerts: Array<Object> = []; + + // Alert + public closeAlert(i:number):void { + this.alerts.splice(i, 1); + } + +} \ No newline at end of file diff --git a/src/app/notificacion/notificacion.module.ts b/src/app/notificacion/notificacion.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..c385f9f6b0e0026d91bb5b43eba3fc5480f60e75 --- /dev/null +++ b/src/app/notificacion/notificacion.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { NotificacionComponent } from './notificacion.component'; + +import { NgbAlertModule } from '@ng-bootstrap/ng-bootstrap'; + + +@NgModule({ + imports: [CommonModule, RouterModule, NgbAlertModule], + declarations: [NotificacionComponent], + exports: [NotificacionComponent] +}) + +export class NotificacionModule { } diff --git a/src/app/shared/components/header/header.component.html b/src/app/shared/components/header/header.component.html new file mode 100644 index 0000000000000000000000000000000000000000..7b31ad18af849aed73310ab4aeb0a518445af2dd --- /dev/null +++ b/src/app/shared/components/header/header.component.html @@ -0,0 +1,20 @@ +<div class="pos-f-t fixed-top header" style="z-index: 1100;"> + <nav class="navbar navbar-inverse bg-inverse navbar-toggleable-md" style="background: #0278AE !important;"> + <button class="navbar-toggler navbar-toggler-left" style="display: block;" (click)="toggleSidebar($event)"> + <span class="navbar-toggler-icon"></span> + </button> + <a class="navbar-brand" href="#/matefun" style="margin-left: 3.5em; width: 5em">MateFun</a> + <div class="collapse navbar-collapse" style="position: absolute; right: 10px; display: block; width: 15em; text-align: right;" id="navbarTogglerDemo02"> + <ul class="navbar-nav ml-auto mt-2 mt-md-0"> + <div class="nav-item dropdown" ngbDropdown> + <a href="javascript:void(0)" class="nav-link" ngbDropdownToggle> + <i class="fa fa-user"></i> {{usuario.nombre+' '+usuario.apellido}}<b class="caret"></b> + </a> + <div class="dropdown-menu dropdown-menu-right"> + <a class="dropdown-item" style="cursor: pointer;" (click)=logout() ><i class="fa fa-fw fa-power-off"></i> Salir</a> + </div> + </div> + </ul> + </div> + </nav> +</div> diff --git a/src/app/shared/components/header/header.component.scss b/src/app/shared/components/header/header.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..0be7ecea4e696e1d7b95c2da4f1bec6ccc24ed14 --- /dev/null +++ b/src/app/shared/components/header/header.component.scss @@ -0,0 +1,75 @@ +$topnav-background-color: #222; +.topnav { + background: linear-gradient(#0085ff, #31dc7d); + background: linear-gradient(to right, #0085ff, rgb(95, 0, 0)) !important; + + border-radius: 0; + background-color: $topnav-background-color; + padding : 6px; + z-index:2; + .text-center{ + text-align: center; + padding-left : 0; + cursor: pointer; + } + .top-right-nav{ + .buy-now{ + a{ + color:#999; + } + } + .dropdown-menu{ + top: 40px; + right: -5px; + left : auto; + .message-preview{ + .media{ + .media-body{ + .media-heading{ + font-size: 14px; + font-weight: bold; + margin-bottom : 0; + } + p{ + margin : 0; + } + p.last{ + font-size : 13px; + margin-bottom: 0; + } + } + } + } + hr { + margin-top: 1px; + margin-bottom: 4px; + } + } + } +} +.messages { + width: 300px; + .media { + border-bottom: 1px solid #DDD; + padding: 5px 10px; + &:last-child { + border-bottom: none; + } + } + .media-body { + h5 { + font-size: 13px; + font-weight: 600; + } + .small { + margin: 0; + } + .last { + font-size: 12px; + margin: 0; + } + } +} +.header .navbar { + background: $topnav-background-color !important; +} diff --git a/src/app/shared/components/header/header.component.spec.ts b/src/app/shared/components/header/header.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..2d0479d7d071a5e6d19a74d195ff34b2edeb5616 --- /dev/null +++ b/src/app/shared/components/header/header.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HeaderComponent } from './header.component'; + +describe('HeaderComponent', () => { + let component: HeaderComponent; + let fixture: ComponentFixture<HeaderComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HeaderComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HeaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/header/header.component.ts b/src/app/shared/components/header/header.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..44ea333e5d7eb6dcb61b83b97d74dc190933c35c --- /dev/null +++ b/src/app/shared/components/header/header.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthenticationService } from '../../services/authentication.service'; +import { SessionService } from '../../services/session.service'; +import { Usuario } from '../../objects/usuario'; + +@Component({ + selector: 'app-header', + templateUrl: './header.component.html', + styleUrls: ['./header.component.scss'] +}) +export class HeaderComponent implements OnInit { + usuario: Usuario; + constructor(private authService: AuthenticationService, private router : Router, private sessionService : SessionService) { + this.usuario = authService.getUser(); + } + ngOnInit() {} + + toggleSidebar(event) { + event.stopPropagation(); + const dom: any = document.querySelector('body'); + dom.classList.toggle('push-right'); + } + rltAndLtr() { + const dom: any = document.querySelector('body'); + dom.classList.toggle('rtl'); + } + + logout(){ + this.sessionService.reset(); + this.router.navigate(['/login']); + } +} diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..85f0512037b4e1a3f2d4f17c30b645b9d7f9578f --- /dev/null +++ b/src/app/shared/components/index.ts @@ -0,0 +1,2 @@ +export * from './header/header.component'; +export * from './sidebar/sidebar.component'; diff --git a/src/app/shared/components/sidebar/sidebar.component.html b/src/app/shared/components/sidebar/sidebar.component.html new file mode 100644 index 0000000000000000000000000000000000000000..dd605a31eb2c3c7442e5ef503238e3dbcf6845df --- /dev/null +++ b/src/app/shared/components/sidebar/sidebar.component.html @@ -0,0 +1,15 @@ + <nav class="sidebar" #sidebarNav [ngClass]="{sidebarPushRight: isActive}" style="background: #036b9a !important;"> + <ul class="list-group"> + <a [routerLink]="['/matefun']" (click)=toggleSidebar() [routerLinkActive]="['router-link-active']" class="list-group-item" + style="color: white;"> + <i class="fa fa-fw fa-desktop"></i> Programa + </a> + <a [routerLink]="['/archivos']" (click)=toggleSidebar() [routerLinkActive]="['router-link-active']" class="list-group-item" style="color: white;"> + <i class="fa fa-fw fa-file-o"></i> Archivos + </a> + <a *ngIf="esDocente()" [routerLink]="['/grupos']" (click)=toggleSidebar() [routerLinkActive]="['router-link-active']" class="list-group-item" style="color: white;"> + <i class="fa fa-fw fa-users"></i> Grupos + </a> + + </ul> +</nav> diff --git a/src/app/shared/components/sidebar/sidebar.component.scss b/src/app/shared/components/sidebar/sidebar.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..fed31138c6b5e898b9215f4ed06ef5b9709d5960 --- /dev/null +++ b/src/app/shared/components/sidebar/sidebar.component.scss @@ -0,0 +1,123 @@ +$topnav-background-color: #036b9a; +.sidebar{ + border-radius: 0; + position: fixed; + z-index: 1000; + top: 55px; + left: 235px; + width: 235px; + margin-left: -235px; + border: none; + border-radius: 0; + overflow-y: auto; + background-color: $topnav-background-color; + bottom: 0; + overflow-x: hidden; + padding-bottom: 40px; + -webkit-transition: all 0.2s ease-in-out; + -moz-transition: all 0.2s ease-in-out; + -ms-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + // border-top: 1px solid rgba(255,255,255,0.3); + .list-group{ + a.list-group-item{ + background: $topnav-background-color; + border: 0; + border-radius: 0; + color: #999; + text-decoration: none; + } + a:hover{ + background: darken($topnav-background-color, 5%); + color: #fff; + } + a.router-link-active{ + background: darken($topnav-background-color, 5%); + color: #fff; + } + } + .sidebar-dropdown{ + *:focus{ + border-radius: none; + border:none; + } + .panel-title{ + font-size : 1rem; + height : 50px; + margin-bottom:0; + a{ + color : #999; + text-decoration : none; + font-weight:400; + background:$topnav-background-color; + span{ + position: relative; + display: block; + padding: .75rem 1.5rem; + padding-top:1rem; + } + } + a:hover,a:focus{ + color: #fff; + outline: none; + outline-offset: -2px; + } + } + .panel-title:hover{ + background: darken($topnav-background-color, 5%); + } + .panel-collapse{ + border-radious :0; + border : none; + .panel-body{ + .list-group-item{ + border-radius : 0; + background-color: $topnav-background-color; + border: 0 solid transparent; + a{ + color:#999; + } + a:hover{ + color:#FFF; + } + } + .list-group-item:hover{ + background : darken($topnav-background-color, 5%); + } + } + } + } +} +.nested-menu { + .list-group-item { + cursor: pointer; + } + .nested { + list-style-type: none; + } + ul.submenu { + display: none; + height: 0; + } + & .expand { + ul.submenu { + display: block; + list-style-type: none; + height: auto; + li { + a { + color: #FFF; + padding: 10px; + display: block; + } + } + } + } +} +// @media screen and (max-width: 992px) { + .sidebar { + top: 54px; + left: 0px; + } +// } diff --git a/src/app/shared/components/sidebar/sidebar.component.spec.ts b/src/app/shared/components/sidebar/sidebar.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..f29709fd1f42214cfdeca486b3d7e335f99ec858 --- /dev/null +++ b/src/app/shared/components/sidebar/sidebar.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SidebarComponent } from './sidebar.component'; + +describe('SidebarComponent', () => { + let component: SidebarComponent; + let fixture: ComponentFixture<SidebarComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SidebarComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SidebarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/sidebar/sidebar.component.ts b/src/app/shared/components/sidebar/sidebar.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..fa8516b949e047ad45b9a14d090994b6b88c994e --- /dev/null +++ b/src/app/shared/components/sidebar/sidebar.component.ts @@ -0,0 +1,62 @@ +import { Component, HostListener, ViewChild, ElementRef } from '@angular/core'; + +import { AuthenticationService } from '../../services/authentication.service'; +import { Usuario } from '../../objects/usuario'; + +@Component({ + selector: 'app-sidebar', + templateUrl: './sidebar.component.html', + styleUrls: ['./sidebar.component.scss'] +}) +export class SidebarComponent { + isActive = false; + showMenu = ''; + + usuario: Usuario; + + constructor(private authService: AuthenticationService) { + this.usuario = authService.getUser(); + } + + eventCalled() { + this.isActive = !this.isActive; + } + + addExpandClass(element: any) { + if (element === this.showMenu) { + this.showMenu = '0'; + } else { + this.showMenu = element; + } + } + + toggleSidebar() { + const dom: any = document.querySelector('body'); + dom.classList.toggle('push-right'); + } + + esAlumno(){ + return this.usuario.tipo == "alumno"; + } + + esDocente(){ + return this.usuario.tipo == "docente"; + } + + @ViewChild('sidebarNav') sidebarNav: ElementRef; + + @HostListener('document:click', ['$event']) + private documentClicked(event: MouseEvent): void { + + // Nav is open + const dom: any = document.querySelector('body'); + if(dom.classList.contains('push-right')){ + // Not clicked on self element + if (!this.sidebarNav.nativeElement.contains(event.target)) { + + dom.classList.remove('push-right'); + } + } + } + +} diff --git a/src/app/shared/config.ts b/src/app/shared/config.ts new file mode 100644 index 0000000000000000000000000000000000000000..269b0832a942fff97480651e814f48f506bb2fa2 --- /dev/null +++ b/src/app/shared/config.ts @@ -0,0 +1,9 @@ +//export const SERVER = 'https://matefun.mybluemix.net'; +//export const GHCI_URL = 'wss://matefun.mybluemix.net/endpoint'; + +//export const SERVER = 'http://localhost:9090'; +//export const GHCI_URL = 'ws://localhost:9090/endpoint'; + +//Configuracion dinamica pensando en servidor con ip dinamica +export const SERVER = window.location.protocol + '//' + window.location.host;//'http://localhost:9090'; +export const GHCI_URL = window.location.protocol == 'http:'? 'ws://'+window.location.host+'/endpoint': 'wss://'+window.location.host+'/endpoint'; diff --git a/src/app/shared/guards/auth.guard.spec.ts b/src/app/shared/guards/auth.guard.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..7ed05ee8111b45cec3834ee041fda1ec5a79d210 --- /dev/null +++ b/src/app/shared/guards/auth.guard.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, async, inject } from '@angular/core/testing'; + +import { AuthGuard } from './auth.guard'; + +describe('AuthGuard', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [AuthGuard] + }); + }); + + it('should ...', inject([AuthGuard], (guard: AuthGuard) => { + expect(guard).toBeTruthy(); + })); +}); diff --git a/src/app/shared/guards/auth.guard.ts b/src/app/shared/guards/auth.guard.ts new file mode 100644 index 0000000000000000000000000000000000000000..22a684d1c5e31bd9468cf58d13bbb0a4fedf1f7d --- /dev/null +++ b/src/app/shared/guards/auth.guard.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class AuthGuard implements CanActivate { + + constructor(private router: Router){ + + } + + canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { + + if(sessionStorage.getItem('currentUser')){ + return true; + } + this.router.navigate(['/login']); + return false; + } +} \ No newline at end of file diff --git a/src/app/shared/index.ts b/src/app/shared/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1bd6837c027e4fe47f9044fbc61921e567901fb --- /dev/null +++ b/src/app/shared/index.ts @@ -0,0 +1,3 @@ +export * from './pipes/shared-pipes.module'; +export * from './components'; +export * from './modules'; diff --git a/src/app/shared/modal/confirm.component.ts b/src/app/shared/modal/confirm.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..24b626427aa060c9faedd0e68e469f7dc5c628cc --- /dev/null +++ b/src/app/shared/modal/confirm.component.ts @@ -0,0 +1,42 @@ +import { Component } from '@angular/core'; +import { DialogComponent, DialogService } from "ng2-bootstrap-modal"; +export interface ConfirmModel { + title:string; + message:string; + confirmText: string; + cancelText:string; +} +@Component({ + selector: 'confirm', + template: `<div class="modal-dialog" style="margin-top:100px;"> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title">{{title || 'Confirm'}}</h4> + <button type="button" class="close" (click)="close()" >×</button> + </div> + <div class="modal-body"> + <p style="white-space: pre;">{{message || ''}}</p> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" (click)="confirm()">{{confirmText || 'Confirmar'}}</button> + <button type="button" class="btn btn-default" (click)="close()" >{{cancelText || 'Cancelar'}}</button> + </div> + </div> + </div>` +}) +export class ConfirmComponent extends DialogComponent<ConfirmModel, boolean> implements ConfirmModel { + title: string; + message: string; + confirmText: string; + cancelText:string; + + constructor(dialogService: DialogService) { + super(dialogService); + } + confirm() { + // we set dialog result as true on click on confirm button, + // then we can get dialog result from caller code + this.result = true; + this.close(); + } +} \ No newline at end of file diff --git a/src/app/shared/modules/index.ts b/src/app/shared/modules/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..7afde6126c9534188e86e3872197a7e7d91888d4 --- /dev/null +++ b/src/app/shared/modules/index.ts @@ -0,0 +1,2 @@ +export * from './stat/stat.module'; +export * from './page-header/page-header.module'; diff --git a/src/app/shared/modules/page-header/page-header.component.html b/src/app/shared/modules/page-header/page-header.component.html new file mode 100644 index 0000000000000000000000000000000000000000..8cd0c21e97990219d1ebe78e58384e42f5da548a --- /dev/null +++ b/src/app/shared/modules/page-header/page-header.component.html @@ -0,0 +1,13 @@ +<div class="row"> + <div class="col-xl-12"> + <h2 class="page-header"> + {{heading}} + </h2> + <ol class="breadcrumb"> + <li class="breadcrumb-item"> + <i class="fa fa-dashboard"></i> <a href="Javascript:void(0)" [routerLink]="['/dashboard']">Dashboard</a> + </li> + <li class="breadcrumb-item active"><i class="fa {{icon}}"></i> {{heading}}</li> + </ol> + </div> +</div> \ No newline at end of file diff --git a/src/app/shared/modules/page-header/page-header.component.scss b/src/app/shared/modules/page-header/page-header.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/shared/modules/page-header/page-header.component.spec.ts b/src/app/shared/modules/page-header/page-header.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..8b7949dafc8efcd7cd5291e0fa290dccedcce02d --- /dev/null +++ b/src/app/shared/modules/page-header/page-header.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageHeaderComponent } from './page-header.component'; + +describe('PageHeaderComponent', () => { + let component: PageHeaderComponent; + let fixture: ComponentFixture<PageHeaderComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PageHeaderComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PageHeaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/modules/page-header/page-header.component.ts b/src/app/shared/modules/page-header/page-header.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..7bc689cd5ff0b25e27790277654228f5264ec1c9 --- /dev/null +++ b/src/app/shared/modules/page-header/page-header.component.ts @@ -0,0 +1,11 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-page-header', + templateUrl: './page-header.component.html', + styleUrls: ['./page-header.component.scss'] +}) +export class PageHeaderComponent { + @Input() heading: string; + @Input() icon: string; +} diff --git a/src/app/shared/modules/page-header/page-header.module.ts b/src/app/shared/modules/page-header/page-header.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..a6ee69a6695ce903a97f6b73e2cd0fdfd67ed721 --- /dev/null +++ b/src/app/shared/modules/page-header/page-header.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; + +import { PageHeaderComponent } from './page-header.component'; + +@NgModule({ + imports: [ + CommonModule, + RouterModule + ], + declarations: [PageHeaderComponent], + exports: [PageHeaderComponent] +}) +export class PageHeaderModule { } diff --git a/src/app/shared/modules/stat/stat.component.html b/src/app/shared/modules/stat/stat.component.html new file mode 100644 index 0000000000000000000000000000000000000000..08ba30f16e455ea21d244e2529943e26b294c50e --- /dev/null +++ b/src/app/shared/modules/stat/stat.component.html @@ -0,0 +1,19 @@ +<div class="card card-inverse {{bgClass}}"> + <div class="card-header"> + <div class="row"> + <div class="col col-xs-3"> + <i class="fa {{icon}} fa-5x"></i> + </div> + <div class="col col-xs-9 text-right"> + <div class="d-block huge">{{count}}</div> + <div class="d-block">{{label}}</div> + </div> + </div> + </div> + <div class="card-footer"> + <span class="float-left">View Details {{data}}</span> + <a href="javascript:void(0)" class="float-right card-inverse"> + <span ><i class="fa fa-arrow-circle-right"></i></span> + </a> + </div> +</div> diff --git a/src/app/shared/modules/stat/stat.component.scss b/src/app/shared/modules/stat/stat.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/shared/modules/stat/stat.component.spec.ts b/src/app/shared/modules/stat/stat.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..4a9b50f6c6e41afc51dad59d287898b6ce721adc --- /dev/null +++ b/src/app/shared/modules/stat/stat.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StatComponent } from './stat.component'; + +describe('StatComponent', () => { + let component: StatComponent; + let fixture: ComponentFixture<StatComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ StatComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StatComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/modules/stat/stat.component.ts b/src/app/shared/modules/stat/stat.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..12aaca07fe52cf773f0a1cce101d1732c4b2e986 --- /dev/null +++ b/src/app/shared/modules/stat/stat.component.ts @@ -0,0 +1,19 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'app-stat', + templateUrl: './stat.component.html', + styleUrls: ['./stat.component.scss'] +}) +export class StatComponent implements OnInit { + @Input() bgClass: string; + @Input() icon: string; + @Input() count: number; + @Input() label: string; + @Input() data: number; + @Output() event: EventEmitter<any> = new EventEmitter(); + + constructor() { } + + ngOnInit() {} +} diff --git a/src/app/shared/modules/stat/stat.module.ts b/src/app/shared/modules/stat/stat.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..7b30f1b3d058341d1d4d599d82ba35e894e5c929 --- /dev/null +++ b/src/app/shared/modules/stat/stat.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { StatComponent } from './stat.component'; + +@NgModule({ + imports: [ + CommonModule + ], + declarations: [StatComponent], + exports: [StatComponent] +}) +export class StatModule { } diff --git a/src/app/shared/objects/archivo.ts b/src/app/shared/objects/archivo.ts new file mode 100644 index 0000000000000000000000000000000000000000..6090668eff1dae65e4c98ad3b0f22e47a5004ab2 --- /dev/null +++ b/src/app/shared/objects/archivo.ts @@ -0,0 +1,28 @@ +export class Evaluacion{ + evaluacionId: number; + cedulaDocente: string; + fecha: Date; + nota: number; + descripcion: string; +} + +export class Archivo { + id: number; + nombre: string; + contenido: string; + fechaCreacion: Date; + cedulaCreador: string; + editable: boolean; + padreId: number; + archivoOrigenId: number; + archivos: Archivo[]; + directorio :boolean; + estado: string; + eliminado:boolean; + evaluacion: Evaluacion; + + constructor(){ + + } + +} \ No newline at end of file diff --git a/src/app/shared/objects/grupo.ts b/src/app/shared/objects/grupo.ts new file mode 100644 index 0000000000000000000000000000000000000000..43bff4770c2bdd741cb72eca7daaad2695295d47 --- /dev/null +++ b/src/app/shared/objects/grupo.ts @@ -0,0 +1,21 @@ +import { Archivo } from './archivo'; +import { Usuario } from './usuario'; + +export class Grupo { + anio: number; + grado: number; + grupo: string; + liceoId: number; + archivos: Archivo[]; + alumnos: Usuario[]; + + constructor(anio: number, grado: number, grupo: string,liceoId:number, archivos: Archivo[], alumnos:Usuario[]){ + this.anio = anio; + this.grado = grado; + this.grupo = grupo; + this.liceoId = liceoId; + this.archivos = archivos; + this.alumnos = alumnos; + } + +} \ No newline at end of file diff --git a/src/app/shared/objects/usuario.ts b/src/app/shared/objects/usuario.ts new file mode 100644 index 0000000000000000000000000000000000000000..4218c62bf91b1294d4d0f5a291e1436ba63ce558 --- /dev/null +++ b/src/app/shared/objects/usuario.ts @@ -0,0 +1,15 @@ +export class Configuracion{ + themeEditor: string; + fontSizeEditor: number; + argumentoI:boolean; + argumentoF:boolean; +} + +export class Usuario{ + token: string; + cedula: string; + nombre: string; + apellido: string; + tipo: string; + configuracion: Configuracion; +} \ No newline at end of file diff --git a/src/app/shared/pipes/filter.pipe.ts b/src/app/shared/pipes/filter.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..0faa74c0d09bbb1f211f23e88718caa4b77126a3 --- /dev/null +++ b/src/app/shared/pipes/filter.pipe.ts @@ -0,0 +1,12 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'filter', + pure: false +}) +export class FilterPipe implements PipeTransform { + transform(items: any[], field : string, value : string): any[] { + if (!items) return []; + return items.filter(it => it[field].toLowerCase().indexOf(value.toLowerCase()) != -1); + } +} \ No newline at end of file diff --git a/src/app/shared/pipes/shared-pipes.module.ts b/src/app/shared/pipes/shared-pipes.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..b4b1c16631a25cfbc53c7b5369ee3f52ac24d656 --- /dev/null +++ b/src/app/shared/pipes/shared-pipes.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + imports: [ + CommonModule + ], + declarations: [] +}) +export class SharedPipesModule { } diff --git a/src/app/shared/services/authentication.service.ts b/src/app/shared/services/authentication.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..23c2a24b908e3431f000a2762e7ab12403c87218 --- /dev/null +++ b/src/app/shared/services/authentication.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@angular/core'; +import { Http, Headers, Response, RequestOptions } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; +import { hash } from '../utils/sha1' +import { Usuario } from '../objects/usuario'; +import 'rxjs/add/operator/map' +import { SERVER } from '../config'; + +@Injectable() +export class AuthenticationService { + constructor(private http: Http) { } + + login(cedula: string, password: string) { + let headers = new Headers({ 'Content-Type': 'application/json' }); + let options = new RequestOptions({ headers: headers }); + return this.http.post(SERVER+'/servicios/login', JSON.stringify({ "cedula": cedula, "password": password }),options) + .map((response: Response) => { + // login successful if there's a jwt token in the response + let user = response.json(); + // if (user && user.token) { + // store user details and jwt token in local storage to keep user logged in between page refreshes + sessionStorage.setItem('currentUser', JSON.stringify(user)); + // } + }); + } + + getUser():Usuario{ + return JSON.parse(sessionStorage.getItem('currentUser')); + } + + getUserConfig(){ + return JSON.parse(sessionStorage.getItem('currentUser')).configuracion;; + } + + getToken(){ + return JSON.parse(sessionStorage.getItem('currentUser')).token; + } + + setUserConfig(config){ + var user = JSON.parse(sessionStorage.getItem('currentUser')); + user.configuracion = config; + sessionStorage.setItem('currentUser', JSON.stringify(user)); + } + + + logout() { + // remove user from local storage to log user out + sessionStorage.removeItem('currentUser'); + } +} \ No newline at end of file diff --git a/src/app/shared/services/ghci.service.ts b/src/app/shared/services/ghci.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..6bb4f0e80cf57fa91a0cca7509215911eab3fcb2 --- /dev/null +++ b/src/app/shared/services/ghci.service.ts @@ -0,0 +1,458 @@ +import { Injectable,ViewChild,ElementRef } from '@angular/core'; +import { Observable, Subject } from 'rxjs/Rx'; +import { WebsocketService } from './websocket.service'; +import { AuthenticationService } from './authentication.service'; +import { GHCI_URL } from '../config'; + +declare var $:any; +declare var that :any ; +const regex = /^color (errores|input|output|logs) (\d)$/g; + +import 'tippy.js/dist/tippy'; + +@Injectable() +export class GHCIService { + public messages: Subject<any> = new Subject<any>(); + private connection = undefined; + private cons = undefined; + private modoAvanzado: boolean = false; // indica si debe mostrarse todo lo que ocurre en el intérprete. + private clear:boolean = false; + private error :string =""; + private warnings: any = []; + private codemirrorRef :any = null; + + private waitingForError : boolean = false; + private waitingForWarning : boolean = false; + private waitingForWarning2 : boolean = false; + private warningText :string = ""; + private lastError : number = -1; + private lastWarning :number = -1; + + private console_error_class : string = "jqconsole-asd"; + + consoleBuffer = []; + + constructor(private authService:AuthenticationService){ + + this.conectarWS(GHCI_URL, authService.getUser().cedula, authService.getToken()); + setInterval( this.checkConnection.bind(this), 5000); + setInterval( this.doPing.bind(this), 30000); + } + + setCodemirrorRef(instance){ + this.codemirrorRef = instance; + } + + clearWarnings(){ + this.warnings = []; + } + + getWarnings(){ + return this.warnings; + } + + loadFile(fileId, dependencias) { + var message = { + 'token': this.authService.getToken(), + 'load': fileId, + 'dependencias' :[] + + }; + for(var i in dependencias){ + message.dependencias.push(dependencias[i]); + }; + this.connection.send(JSON.stringify(message)); + } + copyFile(fileId){ + var message = { + 'token': this.authService.getToken(), + 'copy': fileId + }; + this.connection.send(JSON.stringify(message)); + } + reiniciarInterprete(){ + var message = { + 'token': this.authService.getToken(), + 'restart': '' + }; + console.log(message); + this.connection.send(JSON.stringify(message)); + } + + regex: string = '/(<svg.*\s*.*<\/svg>)/g'; + + consoleRef:any; + + conectarWS(wsUrl, cedula, token){ + if(cedula && !this.connection || this.connection.readyState == WebSocket.CLOSED){ + this.connection = new WebSocket(wsUrl+"/"+cedula+"/"+token); + + this.connection.onopen = function(){ + console.log('Conexión con web socket exitosa'); + } + this.connection.onclose = function(){ + console.log('Conexión con web socket cerrada'); + } + this.connection.onmessage = this.onMessage.bind(this); + } + } + + logConsole(text){ + if(this.consoleRef){ + this.consoleRef.Write(text, 'jqconsole-logs'); + }else{ + this.consoleBuffer.unshift({text: text, type:'jqconsole-logs'}) + setTimeout(this.checkConsole.bind(this),100); + } + } + + outputConsole(text){ + if(this.consoleRef){ + this.consoleRef.Write(text, 'jqconsole-output'); + }else{ + this.consoleBuffer.unshift({text: text, type:'jqconsole-output'}) + setTimeout(this.checkConsole.bind(this),100); + } + } + + errorConsole(text){ + if(this.consoleRef){ + this.consoleRef.Write(text, 'jqconsole-errors'); + }else{ + this.consoleBuffer.unshift({text: text, type:'jqconsole-errors'}) + setTimeout(this.checkConsole.bind(this),100); + } + } + hayError(text){ + var line = -1; + if(this.waitingForError){ + var line = this.lastError; + if(this.codemirrorRef!==null){ + var makeMarker = function() { + var marker = document.createElement("div"); + marker.id = "error_" + line.toString(); + marker.style.width = "15px"; + marker.title = JSON.parse(text).resultado.split("OUT")[1].trim(); + marker.style.height = "15px"; + marker.style.marginLeft = "-5px"; + marker.style.cursor = "pointer"; + marker.style["background-image"] = "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=')"; + marker.innerHTML = "<a href='@' title='cuidado , advertencia matefun'></a>"; + return marker; + } + this.codemirrorRef.setGutterMarker(line, "breakpoints", makeMarker()); + this.waitingForError = false; + this.lastError = -1; + } + }else { + try{ + var line = Number(JSON.parse(text).resultado.split("en lÃnea")[1].split(",")[0].trim())-1; + this.waitingForError = true; + this.lastError = line; + + }catch(err){ + } + return false; + + } + + } + resetGutters(){ + if(this.codemirrorRef!==null){ + this.codemirrorRef.clearGutter("breakpoints"); + } + } + hayWarnings(text){ + var line = -1; + if(this.waitingForWarning){ + if(this.waitingForWarning2){ + //se completa el mensaje de warning + this.warningText = this.warningText.trim() + "\n" + JSON.parse(text).resultado.split("OUT")[1].trim(); + this.waitingForWarning = false; + this.waitingForWarning2 = false; + var line = this.lastWarning; + this.lastWarning = -1; + var title = this.warningText; + if(this.codemirrorRef!==null){ + var makeMarker = function() { + var marker = document.createElement("div"); + marker.style.width = "15px"; + marker.style.height = "15px"; + marker.style.marginLeft = "-5px"; + marker.style.cursor = "pointer"; + marker.innerHTML = "<a href='@' title='cuidado , advertencia matefun'></a>"; + marker.title = title; + marker.style["background-image"] = "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=')"; + marker.innerHTML = ""; + return marker; + } + this.codemirrorRef.setGutterMarker(line, "breakpoints", makeMarker()); + } + }else { + this.waitingForWarning2 = true; + this.warningText = JSON.parse(text).resultado.split("OUT")[1]; + } + }else { + try{ + line = Number(JSON.parse(text).resultado.split("OUTAdvertencia:")[1].trim().split("lÃnea:")[1].split(" ")[1])-1; + this.waitingForWarning = true; + this.waitingForWarning2 = false; + this.lastWarning = line; + + }catch(err){ + line = -1 + } + this.warnings.push(line); + return line!==-1; + } + + } + + onMessage(e){ + if(this.modoAvanzado){ + this.logConsole('Respuesta: ' + e.data + '\n'); + } + if(this.clear){ + this.clearConsole(); + } + var server_message = e.data; + + if(this.hayError(server_message)){ + this.error = "Error"; + } else { + this.error = ""; + } + if(this.hayWarnings(server_message)){ + //parseo los warnings. + //los cargo en la variable de warnings. + } + + var json_server_message = JSON.parse(server_message); + if(json_server_message.tipo=='salida'){ + + if(json_server_message.resultado.includes("<svg")){ + var svg = json_server_message.resultado; + $("#svgHaskell").html(svg); + } else { + var line = json_server_message.resultado.trim(); + if(line.startsWith("OUT")){ + this.outputConsole(line.substring(3) + '\n'); + }else if(line.startsWith("IN")){ + var promptText = line.substring(3); + if(this.consoleRef===undefined){ + this.renderConsole(); + } + this.consoleRef.SetPromptLabel(promptText); + this.consoleRef.SetPromptText(''); + this.startPrompt.bind(this); + this.startPrompt(); + } + + } + + } else if (json_server_message.tipo=='error'){ + if(this.modoAvanzado){ + this.errorConsole(json_server_message.resultado + '\n'); + } + } else if (json_server_message.tipo == 'prompt'){ + // console.log(json_server_message.resultado); + // console.log(this.consoleRef); + // console.log(this.consoleRef.SetPromptLabel); + // this.consoleRef.SetPromptLabel.bind(this); + this.consoleRef.SetPromptLabel(json_server_message.resultado+'>'); + this.consoleRef.SetPromptText(''); + this.startPrompt.bind(this); + this.startPrompt(); + // console.log(x); + }else if (json_server_message.tipo == 'canvas' || json_server_message.tipo == 'animacion' || json_server_message.tipo == 'graph'){ + this.messages.next(json_server_message); + } + + } + + checkConsole(){ + if(this.consoleRef){ + while(this.consoleBuffer.length > 0){ + var bufferedMessage = this.consoleBuffer.pop(); + this.consoleRef.Write(bufferedMessage.text,bufferedMessage.type); + } + }else{ + setTimeout(this.checkConsole.bind(this),500); + } + } + + checkConnection(){ + if(!this.connection || this.connection.readyState == WebSocket.CLOSED){ + this.conectarWS(GHCI_URL, this.authService.getUser().cedula, this.authService.getToken()); + } + } + + doPing(){ + if(this.connection && this.connection.readyState == WebSocket.OPEN){ + var message = { + 'token': this.authService.getToken(), + 'ping': '' + }; + this.connection.send(JSON.stringify(message)); + } + } + + sendLine(line) { + if(line.trim()!==""){ + var message = { + 'token': this.authService.getToken(), + 'comando': line + }; + console.log(message); + if(this.connection && this.connection.readyState == WebSocket.OPEN){ + this.connection.send(JSON.stringify(message)); + }else{ + this.errorConsole("Sin conexión al servidor..."); + } + } + } + + startPrompt() { + // Start the prompt with history enabled. + this.jqconsole.Prompt(true, this.callback.bind(this)); + }; + + focusConsole(){ + this.jqconsole.Focus(); + }; + + clearConsole(){ + this.consoleRef.Reset(); + this.startPrompt.bind(this); + this.startPrompt(); + this.clear = false; + }; + + callback(input){ + // Output input with the class jqconsole-output. + var ejecutar: boolean; + ejecutar = this.procesarInput(input); + if(ejecutar) { + if(this.modoAvanzado){ + this.logConsole("Ejecutar: " + input + '\n'); + } + this.sendLine.bind(this); + this.sendLine(input); + } + this.startPrompt.bind(this); + this.startPrompt(); + } + + procesarInput(input){ + var _input: string; + var send: boolean = false; + _input = input.trim().toLocaleLowerCase(); + if(_input==="limpiar"){ + this.clearConsole(); + } else if(_input==="modo avanzado") { + this.modoAvanzado= true; + this.logConsole("Modo avanzado activado\n"); + } else if(_input==="modo normal"){ + this.modoAvanzado= false; + this.logConsole("Modo avanzado desactivado\n"); + } else if(_input==="listar colores"){ + this.outputConsole("1 - Azul\n"); + this.outputConsole("2 - Rojo\n"); + this.outputConsole("3 - Verde\n"); + this.outputConsole("4 - Verde oscuro\n"); + this.outputConsole("5 - Blanco\n"); + this.outputConsole("6 - Naranja\n"); + this.outputConsole("7 - Gris\n"); + this.outputConsole("8 - Gris oscuro\n"); + this.outputConsole("9 - Marrón\n"); + } else if(_input.match(regex)!==null) { + var _tipoTexto :string = _input.split(" ")[1]; + var _color : string = input.split(" ")[2]; + this.jqconsoleColor(_color,_tipoTexto); + if(this.modoAvanzado){ + this.logConsole("Color " + _tipoTexto + " seleccionado\n"); + } + } else { + send = true; + } + return send; + } + + getCSSColorName(n){ + if(n==="1"){ + return "rgb(77, 77, 255)"; + } else if(n==="2"){ + return "rgb(255, 26, 26)"; + } else if (n==="3"){ + return "rgb(0, 179, 60)"; + } else if(n==="4"){ + return "rgb(0, 77, 0)"; + } else if(n==="5"){ + return "rgb(255, 255, 255)"; + } else if(n==="6"){ + return "rgb(255, 133, 51)"; + } else if(n==="7"){ + return "rgb(204, 204, 179)"; + } else if(n==="8"){ + return "rgb(102, 102, 102)"; + } else if(n==="9"){ + return "rgb(101, 27, 27)"; + } + } + + getJQConsoleClass(jqconsoletext){ + if(jqconsoletext==="input"){ + return '.jqconsole-prompt'; + } else if(jqconsoletext==="error"){ + return '.jqconsole-error'; + } else if (jqconsoletext==="logs"){ + return '.jqconsole-logs'; + } else if(jqconsoletext==="output"){ + return '.jqconsole-output'; + } + } + + jqconsoleColor(color, clase){ + var cssColor:string = this.getCSSColorName(color); + var jqConsoleClass: string = this.getJQConsoleClass(clase); + + + var style = document.createElement('style'); + style.type = 'text/css'; + style.innerHTML = jqConsoleClass +' { color: ' + cssColor + '; }'; + document.getElementsByTagName('head')[0].appendChild(style); + + if(jqConsoleClass==='.jqconsole-prompt'){ + var style2 = document.createElement('style'); + style2.type = 'text/css'; + style2.innerHTML = '.jqconsole-old-prompt { color: ' + cssColor + '; }'; + document.getElementsByTagName('head')[0].appendChild(style2); + } + + } + + consola: any=undefined; + renderConsole(){ + if(this.jqconsole){ + $('#console').replaceWith(this.consola); + }else{ + if ($("#console").jqconsole!=undefined){ // Check if element has been found + this.jqconsole = $('#console').jqconsole(''); + this.consoleRef = this.jqconsole; + this.startPrompt.bind(this); + this.startPrompt(); + } else { + this.rendered(); + } + this.consola = $("#console"); + } + } + + jqconsole:any = undefined; + rendered(){ + setTimeout(this.renderConsole.bind(this),1000); + } + + + +} \ No newline at end of file diff --git a/src/app/shared/services/haskell.service.ts b/src/app/shared/services/haskell.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..97265288dcc157ec0c5e3ce33f44e7b4fe61f186 --- /dev/null +++ b/src/app/shared/services/haskell.service.ts @@ -0,0 +1,119 @@ +import { Injectable } from '@angular/core'; +import { Http, Response, Headers, RequestOptions, URLSearchParams } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; +import { Archivo, Evaluacion } from '../objects/archivo'; +import { Grupo } from '../objects/grupo'; + +import { SERVER } from '../config'; + +import { AuthenticationService } from './authentication.service'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/catch'; + +@Injectable() +export class HaskellService { + + /** + * Creates a new HaskellService with the injected Http. + * @param {Http} http - The injected Http. + * @constructor + */ + constructor(private http: Http, private authService: AuthenticationService) {} + + getArchivos(cedula:string): Observable<Archivo[]> { + let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization':'Bearer '+this.authService.getToken() }); + let params: URLSearchParams = new URLSearchParams(); + params.set('cedula', cedula); + let options = new RequestOptions({ headers: headers, search: params }); + return this.http.get(SERVER+'/servicios/archivo',options) + .map((res: Response) => res.json()) + .catch(this.handleError); + } + + getArchivosCompartidosAlumno(cedula:string): Observable<Archivo[]> { + let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization':'Bearer '+this.authService.getToken() }); + let params: URLSearchParams = new URLSearchParams(); + params.set('cedula', cedula); + params.set('compartidos','true'); + let options = new RequestOptions({ headers: headers, search: params }); + return this.http.get(SERVER+'/servicios/archivo',options) + .map((res: Response) => res.json()) + .catch(this.handleError); + } + + crearArchivo(archivo: Archivo): Observable<Archivo> { + let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization':'Bearer '+this.authService.getToken() }); + let options = new RequestOptions({ headers: headers }); + return this.http.post(SERVER+'/servicios/archivo', archivo, options) + .map((res: Response) => res.json()) + .catch(this.handleError); + } + + editarArchivo(archivoId, archivo: Archivo): Observable<Archivo> { + let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization':'Bearer '+this.authService.getToken() }); + let options = new RequestOptions({ headers: headers }); + return this.http.put(SERVER+'/servicios/archivo/'+archivoId, archivo, options) + .map((res: Response) => res.json()) + .catch(this.handleError); + } + + eliminarArchivo(archivoId): Observable<Response> { + let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization':'Bearer '+this.authService.getToken() }); + let options = new RequestOptions({ headers: headers }); + return this.http.delete(SERVER+'/servicios/archivo/'+archivoId, options) + .map((res: Response) => res.json()) + .catch(this.handleError); + } + + getCopiaArchivoCompartidoGrupo(cedula, archivoId): Observable<Archivo> { + let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization':'Bearer '+this.authService.getToken() }); + let params: URLSearchParams = new URLSearchParams(); + params.set('cedula', cedula); + let options = new RequestOptions({ headers: headers, search: params }); + return this.http.get(SERVER+'/servicios/archivo/compartido/'+archivoId, options) + .map((res: Response) => res.json()) + .catch(this.handleError); + } + + compartirArchivoGrupo(grupo,archivoId): Observable<Archivo> { + let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization':'Bearer '+this.authService.getToken() }); + let options = new RequestOptions({ headers: headers }); + var archId = { + id:archivoId + } + return this.http.post(SERVER+'/servicios/grupo/'+grupo.liceoId+'/'+grupo.anio+'/'+grupo.grado+'/'+grupo.grupo+'/archivo', archId, options) + .map((res: Response) => res.json()) + .catch(this.handleError); + } + + calificarArchivo(archivoId, evaluacion): Observable<Evaluacion> { + let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization':'Bearer '+this.authService.getToken() }); + let options = new RequestOptions({ headers: headers }); + return this.http.post(SERVER+'/servicios/archivo/'+archivoId+'/evaluacion', evaluacion, options) + .map((res: Response) => res.json()) + .catch(this.handleError); + } + + + getGrupos(cedula:string): Observable<Grupo[]> { + let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization':'Bearer '+this.authService.getToken() }); + let params: URLSearchParams = new URLSearchParams(); + params.set('cedula', cedula); + let options = new RequestOptions({ headers: headers, search: params }); + return this.http.get(SERVER+'/servicios/grupo',options) + .map((res: Response) => res.json()) + .catch(this.handleError); + } + + /** + * Handle HTTP error + */ + private handleError (error: any) { + // In a real world app, we might use a remote logging infrastructure + // We'd also dig deeper into the error to get a better message + let errMsg = (error.message) ? error.message : + error.status ? `${error.status} - ${error.statusText}` : 'Server error'; + console.error(errMsg); // log to console instead + return Observable.throw(errMsg); + } + } diff --git a/src/app/shared/services/notificacion.service.ts b/src/app/shared/services/notificacion.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..0d61b31acfffc18fadd507d6501f482b3c79c3ba --- /dev/null +++ b/src/app/shared/services/notificacion.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import { Router, NavigationStart } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; +import { Subject } from 'rxjs/Subject'; + +@Injectable() +export class NotificacionService { + private subject = new Subject<any>(); + private keepAfterNavigationChange = false; + + constructor(private router: Router) { + // clear alert message on route change + router.events.subscribe(event => { + if (event instanceof NavigationStart) { + if (this.keepAfterNavigationChange) { + // only keep for a single location change + this.keepAfterNavigationChange = false; + } else { + // clear alert + this.subject.next(); + } + } + }); + } + + success(message: string, keepAfterNavigationChange = false) { + this.keepAfterNavigationChange = keepAfterNavigationChange; + this.subject.next({ type: 'success', text: message }); + } + + info(message: string, keepAfterNavigationChange = false) { + this.keepAfterNavigationChange = keepAfterNavigationChange; + this.subject.next({ type: 'info', text: message }); + } + + warning(message: string, keepAfterNavigationChange = false) { + this.keepAfterNavigationChange = keepAfterNavigationChange; + this.subject.next({ type: 'warning', text: message }); + } + + error(message: string, keepAfterNavigationChange = false) { + this.keepAfterNavigationChange = keepAfterNavigationChange; + this.subject.next({ type: 'danger', text: message }); + } + + getMessage(): Observable<any> { + return this.subject.asObservable(); + } + + getMessageSubject() { + return this.subject; + } +} \ No newline at end of file diff --git a/src/app/shared/services/session.service.ts b/src/app/shared/services/session.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..84417842a27f992f6168b0136c1a195b323b65c1 --- /dev/null +++ b/src/app/shared/services/session.service.ts @@ -0,0 +1,149 @@ +import { Injectable } from '@angular/core'; +import { Archivo } from '../objects/archivo'; +import { Grupo } from '../objects/grupo'; + +@Injectable() +export class SessionService { + archivo: Archivo = new Archivo(); + archivos: any; + dependencias :any; + archivosList :any; + directorioActual : any; + grupos: Grupo[]; + + public setArchivo(archivo){ + this.archivo = archivo; + } + public getArchivo(){ + return this.archivo; + } + public setArchivosTree(tree){ + this.archivos = tree; + } + public getArchivos(service){ + if(this.archivos!=undefined){ + return this.archivos; + } + if(service!==undefined){ + return this.archivos; + } + } + + public setGrupos(grupos: Grupo[]){ + this.grupos = grupos; + } + + public getGrupos(){ + return this.grupos; + } + + + public setArchivosList(l){ + this.archivosList=l; + } + public getArchivosList(){ + return this.archivosList; + } + public getDependencias(){ + return this.dependencias; + } + public setDependencias(d){ + this.dependencias = d; + } + public reset(){ + this.archivo = new Archivo(); + this.archivos = []; + this.dependencias = []; + this.archivosList = []; + this.grupos = undefined; + } + public setDirectorioActual(d){ + this.directorioActual = d; + } + + public cargarDependencias(arch){ + + var resultado = this.buildDependenciesTree(arch,[]); + if(resultado["status"]==="miss"){ + return resultado; + } + var list = this.toList(resultado["dependencies"]); + this.setDependencias(list); + return resultado; + } + + //logica necesaria para cargar un archivo. "resuelve las dependencias 'incluir'" + buildDependenciesTree(pivot,archivosRecorridos){ + var content = pivot.contenido; + var contentIncludes = this.extractIncludes(content); + var hijos:any = []; + var result : any = {}; + var resultObject = {}; + result.id = pivot.id; + result.nombre = pivot.nombre; + result.contenido = pivot.contenido; + for(var i in contentIncludes){ + if(!archivosRecorridos.includes(contentIncludes[i])){ + try{ + var archivo = this.directorioActual.archivos.filter( + function(a){ + return a.nombre === contentIncludes[i]; + } + )[0]; + archivosRecorridos.push(pivot.nombre); + } catch(err){ + + } + if(archivo){ + var resultado_ = this.buildDependenciesTree(archivo,archivosRecorridos); + if(resultado_["status"]==="ok"){ + hijos.push(resultado_["dependencies"]); + } else{ + return resultado_; + } + + + }else { + resultObject["status"] = "miss"; + resultObject["nombre"] = contentIncludes[i]; + return resultObject; + } + } + } + result.hijos = hijos; + resultObject["status"] = "ok"; + resultObject["dependencies"] = result; + return resultObject; + } + + toList(root){ + var result:any = []; + result.push(root); + for(var i in root.hijos){ + var sub = this.toList(root.hijos[i]); + result = result.concat(sub); + } + return result; + } + + extractIncludes(contenido){ + var regex = /.*incluir\s*(\w*).*/gm; + let m; + var includes: any = []; + + while ((m = regex.exec(contenido)) !== null) { + // This is necessary to avoid infinite loops with zero-width matches + if (m.index === regex.lastIndex) { + regex.lastIndex++; + } + + // The result can be accessed through the `m`-variable. + m.forEach((match, groupIndex) => { + if(groupIndex===1){ + includes.push(match); + } + }); + } + return includes; + } +} diff --git a/src/app/shared/services/usuario.service.ts b/src/app/shared/services/usuario.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..1cce787b26cf12c698616c0a161a84f8e2fb102d --- /dev/null +++ b/src/app/shared/services/usuario.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { Http, Headers, Response, RequestOptions } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; +import { Usuario, Configuracion } from '../objects/usuario'; + +import 'rxjs/add/operator/map' +import 'rxjs/add/operator/catch' + +import { SERVER } from '../config'; + +@Injectable() +export class UsuarioService { + constructor(private http: Http) {} + + actualizarConfiguracion(cedula: string, config: Configuracion) { + let headers = new Headers({ 'Content-Type': 'application/json' }); + let options = new RequestOptions({ headers: headers }); + return this.http.put(SERVER + '/servicios/usuario/' + cedula + "/configuracion", config, options) + .map(this.extractData) + .catch(this.handleError); + } + + private extractData(res: Response) { + let body = res.json(); + return body || []; + } + + private handleError(error: any) { + let errMsg = (error.message) ? error.message : + error.status ? `${error.status} - ${error.statusText}` : 'Server error'; + console.error(errMsg); // log to console instead + return Observable.throw(errMsg); + } +} diff --git a/src/app/shared/services/websocket.service.ts b/src/app/shared/services/websocket.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..f9d9aa801e92ac8d03b84ac8e0b3bd8cf2a07186 --- /dev/null +++ b/src/app/shared/services/websocket.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; +import * as Rx from 'rxjs/Rx'; + +@Injectable() +export class WebsocketService { + constructor() { } + + private subject: Rx.Subject<MessageEvent>; + + public connect(url: string): Rx.Subject<MessageEvent> { + if (!this.subject) { + this.subject = this.create(url); + console.log("Successfully connected: " + url); + } + return this.subject; + } + + private create(url: string): Rx.Subject<MessageEvent> { + let ws = new WebSocket(url); + + let observable = Rx.Observable.create( + (obs: Rx.Observer<MessageEvent>) => { + ws.onmessage = obs.next.bind(obs); + ws.onerror = obs.error.bind(obs); + ws.onclose = obs.complete.bind(obs); + return ws.close.bind(ws); + }) + let observer = { + next: (data: Object) => { + if (ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(data)); + }else{ + console.log("Se perdio la conexion"); + } + } + + } + return Rx.Subject.create(observer, observable); + } + +} \ No newline at end of file diff --git a/src/app/shared/utils/sha1.ts b/src/app/shared/utils/sha1.ts new file mode 100644 index 0000000000000000000000000000000000000000..c5bf08221d0940fc4243cd40070b3067d89b8fd6 --- /dev/null +++ b/src/app/shared/utils/sha1.ts @@ -0,0 +1,165 @@ + + + var POW_2_24 = Math.pow(2, 24); + + var POW_2_32 = Math.pow(2, 32); + + function hex(n: number): string + { + var s = "", + v: number; + for (var i = 7; i >= 0; --i) + { + v = (n >>> (i << 2)) & 0xF; + s += v.toString(16); + } + return s; + } + + function lrot(n: number, bits: number): number + { + return ((n << bits) | (n >>> (32 - bits))); + } + + class Uint32ArrayBigEndian + { + bytes: Uint8Array; + constructor(length: number) + { + this.bytes = new Uint8Array(length << 2); + } + get(index: number): number + { + index <<= 2; + return (this.bytes[index] * POW_2_24) + + ((this.bytes[index + 1] << 16) + | (this.bytes[index + 2] << 8) + | this.bytes[index + 3]); + } + set(index: number, value: number) + { + var high = Math.floor(value / POW_2_24), + rest = value - (high * POW_2_24); + index <<= 2; + this.bytes[index] = high; + this.bytes[index + 1] = rest >> 16; + this.bytes[index + 2] = (rest >> 8) & 0xFF; + this.bytes[index + 3] = rest & 0xFF; + } + } + + function string2ArrayBuffer(s: string): ArrayBuffer + { + s = s.replace(/[\u0080-\u07ff]/g, + function(c: string) + { + var code = c.charCodeAt(0); + return String.fromCharCode(0xC0 | code >> 6, 0x80 | code & 0x3F); + }); + s = s.replace(/[\u0080-\uffff]/g, + function(c: string) + { + var code = c.charCodeAt(0); + return String.fromCharCode(0xE0 | code >> 12, 0x80 | code >> 6 & 0x3F, 0x80 | code & 0x3F); + }); + var n = s.length, + array = new Uint8Array(n); + for (var i = 0; i < n; ++i) + { + array[i] = s.charCodeAt(i); + } + return array.buffer; + } + + export function hash(bufferOrString: any): string + { + var source: ArrayBuffer; + if (bufferOrString instanceof ArrayBuffer) + { + source = <ArrayBuffer> bufferOrString; + } + else + { + source = string2ArrayBuffer(String(bufferOrString)); + } + + var h0 = 0x67452301, + h1 = 0xEFCDAB89, + h2 = 0x98BADCFE, + h3 = 0x10325476, + h4 = 0xC3D2E1F0, + i: number, + sbytes = source.byteLength, + sbits = sbytes << 3, + minbits = sbits + 65, + bits = Math.ceil(minbits / 512) << 9, + bytes = bits >>> 3, + slen = bytes >>> 2, + s = new Uint32ArrayBigEndian(slen), + s8 = s.bytes, + j: number, + w = new Uint32Array(80), + sourceArray = new Uint8Array(source); + for (i = 0; i < sbytes; ++i) + { + s8[i] = sourceArray[i]; + } + s8[sbytes] = 0x80; + s.set(slen - 2, Math.floor(sbits / POW_2_32)); + s.set(slen - 1, sbits & 0xFFFFFFFF); + for (i = 0; i < slen; i += 16) + { + for (j = 0; j < 16; ++j) + { + w[j] = s.get(i + j); + } + for ( ; j < 80; ++j) + { + w[j] = lrot(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1); + } + var a = h0, + b = h1, + c = h2, + d = h3, + e = h4, + f: number, + k: number, + temp: number; + for (j = 0; j < 80; ++j) + { + if (j < 20) + { + f = (b & c) | ((~b) & d); + k = 0x5A827999; + } + else if (j < 40) + { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } + else if (j < 60) + { + f = (b & c) ^ (b & d) ^ (c & d); + k = 0x8F1BBCDC; + } + else + { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + + temp = (lrot(a, 5) + f + e + k + w[j]) & 0xFFFFFFFF; + e = d; + d = c; + c = lrot(b, 30); + b = a; + a = temp; + } + h0 = (h0 + a) & 0xFFFFFFFF; + h1 = (h1 + b) & 0xFFFFFFFF; + h2 = (h2 + c) & 0xFFFFFFFF; + h3 = (h3 + d) & 0xFFFFFFFF; + h4 = (h4 + e) & 0xFFFFFFFF; + } + return hex(h0) + hex(h1) + hex(h2) + hex(h3) + hex(h4); + } diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts new file mode 100644 index 0000000000000000000000000000000000000000..3612073bc31cd4c1f5d6cbb00318521e9a61bd8a --- /dev/null +++ b/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/src/environments/environment.ts b/src/environments/environment.ts new file mode 100644 index 0000000000000000000000000000000000000000..b7f639aecac5c903f5449c7d14846f92b0a9b342 --- /dev/null +++ b/src/environments/environment.ts @@ -0,0 +1,8 @@ +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `environment.ts`, but if you do +// `ng build --env=prod` then `environment.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `.angular-cli.json`. + +export const environment = { + production: false +}; diff --git a/src/favicon.ico b/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..444fdd4ab74564eb687272240ec077d93ead5269 Binary files /dev/null and b/src/favicon.ico differ diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000000000000000000000000000000000000..1fccac3c133148367c45a6177348ebab925d15d8 --- /dev/null +++ b/src/index.html @@ -0,0 +1,28 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>Proyecto MateFun</title> + <base href="/"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="icon" type="image/x-icon" href="favicon.ico"> + <!-- despues lo saco de aca --> + <!-- <link rel="stylesheet" type="text/css" href="node_modules/codemirror/addon/hint/show-hint.css"> --> + <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script> +<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script> +<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script> + +</head> +<body> + <app-root> + <div class="loading"> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + <div class="loading-bar"></div> + </div> + </app-root> +</body> + +</html> diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000000000000000000000000000000000000..a9ca1caf8ceebeb432bbf02b6a2b18378653fbd0 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,11 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/src/polyfills.ts b/src/polyfills.ts new file mode 100644 index 0000000000000000000000000000000000000000..53bdaf1b86424df5230596f8352e78e4a4de66f5 --- /dev/null +++ b/src/polyfills.ts @@ -0,0 +1,68 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// import 'core-js/es6/symbol'; +// import 'core-js/es6/object'; +// import 'core-js/es6/function'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following to support `@angular/animation`. */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + +/** Evergreen browsers require these. **/ +import 'core-js/es6/reflect'; +import 'core-js/es7/reflect'; + + +/** ALL Firefox browsers require the following to support `@angular/animation`. **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. diff --git a/src/styles/_responsive.scss b/src/styles/_responsive.scss new file mode 100644 index 0000000000000000000000000000000000000000..89aacd9b51ee0f41fcf9d5b263700bf43a601c20 --- /dev/null +++ b/src/styles/_responsive.scss @@ -0,0 +1,82 @@ +// @media screen and (max-width: 992px) { + .push-right { + .sidebar { + left: 235px !important; + } + } +// } + + +.CodeMirror { + height: 400px !important; +} + +#console { + height: 400px !important; +} + +.contenedor-canvas{ + height: 400px !important; +} + +.listado-archivos{ + height: 400px !important; +} + +.previewArchivoNoSeleccionado{ + height: 400px !important; +} + +.listado-grupos { + height: 400px !important; +} + +.previewArchivoNoSeleccionado{ + height: 400px !important; +} + +.listadoEntregasAlumnoGrupos{ + height: 400px !important; +} + +@media screen and (min-width: 992px) { + .codemirrorArchivo .CodeMirror { + height: calc(100vh - 235px) !important; + } + + .codemirrorPrograma .CodeMirror { + height: calc(100vh - 197px) !important; + } + + .codemirrorGrupos .CodeMirror { + height: calc(100vh - 232px) !important; + } + + #console { + height: calc(100vh - 79px) !important; + } + + .contenedor-canvas{ + height: calc(100vh - 121px) !important; + } + + .listado-archivos{ + height: calc(100vh - 265px) !important; + } + + .previewArchivoNoSeleccionado{ + height: calc(100vh - 275px) !important; + } + + .previewArchivoNoSeleccionadoGrupos{ + height: calc(100vh - 225px) !important; + } + + .listadoEntregasAlumnoGrupos{ + height: calc(100vh - 225px) !important; + } + + .listado-grupos { + height: calc(100vh - 270px) !important; + } +} \ No newline at end of file diff --git a/src/styles/_rtl.scss b/src/styles/_rtl.scss new file mode 100644 index 0000000000000000000000000000000000000000..5e8070154d060472198860b3c58d40310ee8483b --- /dev/null +++ b/src/styles/_rtl.scss @@ -0,0 +1,35 @@ +.rtl { + .sidebar { + left: auto !important; + right: 0 !important; + > ul.list-group { + padding: 0; + } + } + .main-container { + margin-left: 0 !important; + margin-right: 235px; + } + * { + direction: rtl; + } +} +@media screen and (max-width: 992px) { + .rtl { + .navbar-brand { + direction: ltr; + } + .sidebar { + right: -235px !important; + } + .main-container { + margin-right: 0; + } + &.push-right { + .sidebar { + left: auto !important; + right: 0 !important; + } + } + } +} diff --git a/src/styles/_spinner.scss b/src/styles/_spinner.scss new file mode 100644 index 0000000000000000000000000000000000000000..46499815d31a3cfff5b9ff9b7c87eae33ae8be17 --- /dev/null +++ b/src/styles/_spinner.scss @@ -0,0 +1,42 @@ +.loading { + position: absolute; + top: 50%; + left: 50%; + -ms-transform: translate(-50%,-50%); /* IE 9 */ + -webkit-transform: translate(-50%,-50%); /* Safari */ + transform: translate(-50%,-50%); /* Standard syntax */ + .loading-bar { + display: inline-block; + width: 6px; + height: 30px; + border-radius: 4px; + animation: loading 1s ease-in-out infinite; + &:nth-child(1) { + background-color: #222; + animation-delay: 0; + } + &:nth-child(2) { + background-color: #222; + animation-delay: 0.09s; + } + &:nth-child(3) { + background-color: #222; + animation-delay: .18s; + } + &:nth-child(4) { + background-color: #222; + animation-delay: .27s; + } + } +} +@keyframes loading { + 0% { + transform: scale(1); + } + 20% { + transform: scale(1, 2.2); + } + 40% { + transform: scale(1); + } +} diff --git a/src/styles/_utils.scss b/src/styles/_utils.scss new file mode 100644 index 0000000000000000000000000000000000000000..6d1f1308c386bc23cffd569bf04bb98d07dc39a7 --- /dev/null +++ b/src/styles/_utils.scss @@ -0,0 +1,7 @@ +.fs-12 { + font-size: 12px; +} + +.errorConsole{ + color: red; +} \ No newline at end of file diff --git a/src/styles/app.scss b/src/styles/app.scss new file mode 100644 index 0000000000000000000000000000000000000000..3322214e234fc5cdfc4f44f741a7607e5f05ee5b --- /dev/null +++ b/src/styles/app.scss @@ -0,0 +1,56 @@ +/* You can add global styles to this file, and also import other style files */ +@import "bootstrap/bootstrap"; +@import "spinner"; +@import "utils"; +@import "rtl"; +@import "responsive"; + +@import "~codemirror/theme/dracula.css"; +@import "~codemirror/theme/3024-day.css"; +@import "~codemirror/theme/3024-night.css"; +@import "~codemirror/theme/abcdef.css"; +@import "~codemirror/theme/ambiance-mobile.css"; +@import "~codemirror/theme/ambiance.css"; +@import "~codemirror/theme/base16-dark.css"; +@import "~codemirror/theme/base16-light.css"; +@import "~codemirror/theme/bespin.css"; +@import "~codemirror/theme/blackboard.css"; +@import "~codemirror/theme/cobalt.css"; +@import "~codemirror/theme/colorforth.css"; +@import "~codemirror/theme/dracula.css"; +@import "~codemirror/theme/duotone-dark.css"; +@import "~codemirror/theme/duotone-light.css"; +@import "~codemirror/theme/eclipse.css"; +@import "~codemirror/theme/elegant.css"; +@import "~codemirror/theme/erlang-dark.css"; +@import "~codemirror/theme/hopscotch.css"; +@import "~codemirror/theme/icecoder.css"; +@import "~codemirror/theme/isotope.css"; +@import "~codemirror/theme/lesser-dark.css"; +@import "~codemirror/theme/liquibyte.css"; +@import "~codemirror/theme/material.css"; +@import "~codemirror/theme/mbo.css"; +@import "~codemirror/theme/mdn-like.css"; +@import "~codemirror/theme/midnight.css"; +@import "~codemirror/theme/monokai.css"; +@import "~codemirror/theme/neat.css"; +@import "~codemirror/theme/neo.css"; +@import "~codemirror/theme/night.css"; +@import "~codemirror/theme/panda-syntax.css"; +@import "~codemirror/theme/paraiso-dark.css"; +@import "~codemirror/theme/paraiso-light.css"; +@import "~codemirror/theme/pastel-on-dark.css"; +@import "~codemirror/theme/railscasts.css"; +@import "~codemirror/theme/rubyblue.css"; +@import "~codemirror/theme/seti.css"; +@import "~codemirror/theme/solarized.css"; +@import "~codemirror/theme/the-matrix.css"; +@import "~codemirror/theme/tomorrow-night-bright.css"; +@import "~codemirror/theme/tomorrow-night-eighties.css"; +@import "~codemirror/theme/ttcn.css"; +@import "~codemirror/theme/twilight.css"; +@import "~codemirror/theme/vibrant-ink.css"; +@import "~codemirror/theme/xq-dark.css"; +@import "~codemirror/theme/xq-light.css"; +@import "~codemirror/theme/yeti.css"; +@import "~codemirror/theme/zenburn.css"; diff --git a/src/styles/bootstrap/_alert.scss b/src/styles/bootstrap/_alert.scss new file mode 100755 index 0000000000000000000000000000000000000000..d9b4e9b2764828174b288aa04f7a83506b466c47 --- /dev/null +++ b/src/styles/bootstrap/_alert.scss @@ -0,0 +1,55 @@ +// +// Base styles +// + +.alert { + padding: $alert-padding-y $alert-padding-x; + margin-bottom: $alert-margin-bottom; + border: $alert-border-width solid transparent; + @include border-radius($alert-border-radius); +} + +// Headings for larger alerts +.alert-heading { + // Specified to prevent conflicts of changing $headings-color + color: inherit; +} + +// Provide class for links that match alerts +.alert-link { + font-weight: $alert-link-font-weight; +} + + +// Dismissible alerts +// +// Expand the right padding and account for the close button's positioning. + +.alert-dismissible { + // Adjust close link position + .close { + position: relative; + top: -$alert-padding-y; + right: -$alert-padding-x; + padding: $alert-padding-y $alert-padding-x; + color: inherit; + } +} + + +// Alternate styles +// +// Generate contextual modifier classes for colorizing the alert. + +.alert-success { + @include alert-variant($alert-success-bg, $alert-success-border, $alert-success-text); +} +.alert-info { + @include alert-variant($alert-info-bg, $alert-info-border, $alert-info-text); +} +.alert-warning { + @include alert-variant($alert-warning-bg, $alert-warning-border, $alert-warning-text); +} +.alert-danger { + @include alert-variant($alert-danger-bg, $alert-danger-border, $alert-danger-text); +} diff --git a/src/styles/bootstrap/_badge.scss b/src/styles/bootstrap/_badge.scss new file mode 100755 index 0000000000000000000000000000000000000000..e5a3298937c5ee641cf51637b1945796050da4b9 --- /dev/null +++ b/src/styles/bootstrap/_badge.scss @@ -0,0 +1,77 @@ +// Base class +// +// Requires one of the contextual, color modifier classes for `color` and +// `background-color`. + +.badge { + display: inline-block; + padding: $badge-padding-y $badge-padding-x; + font-size: $badge-font-size; + font-weight: $badge-font-weight; + line-height: 1; + color: $badge-color; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + @include border-radius(); + + // Empty badges collapse automatically + &:empty { + display: none; + } +} + +// Quick fix for badges in buttons +.btn .badge { + position: relative; + top: -1px; +} + +// scss-lint:disable QualifyingElement +// Add hover effects, but only for links +a.badge { + @include hover-focus { + color: $badge-link-hover-color; + text-decoration: none; + cursor: pointer; + } +} +// scss-lint:enable QualifyingElement + +// Pill badges +// +// Make them extra rounded with a modifier to replace v3's badges. + +.badge-pill { + padding-right: $badge-pill-padding-x; + padding-left: $badge-pill-padding-x; + @include border-radius($badge-pill-border-radius); +} + +// Colors +// +// Contextual variations (linked badges get darker on :hover). + +.badge-default { + @include badge-variant($badge-default-bg); +} + +.badge-primary { + @include badge-variant($badge-primary-bg); +} + +.badge-success { + @include badge-variant($badge-success-bg); +} + +.badge-info { + @include badge-variant($badge-info-bg); +} + +.badge-warning { + @include badge-variant($badge-warning-bg); +} + +.badge-danger { + @include badge-variant($badge-danger-bg); +} diff --git a/src/styles/bootstrap/_breadcrumb.scss b/src/styles/bootstrap/_breadcrumb.scss new file mode 100755 index 0000000000000000000000000000000000000000..1a09bba20a993cdb339d9329e21b83353ee9144b --- /dev/null +++ b/src/styles/bootstrap/_breadcrumb.scss @@ -0,0 +1,38 @@ +.breadcrumb { + padding: $breadcrumb-padding-y $breadcrumb-padding-x; + margin-bottom: $spacer-y; + list-style: none; + background-color: $breadcrumb-bg; + @include border-radius($border-radius); + @include clearfix; +} + +.breadcrumb-item { + float: left; + + // The separator between breadcrumbs (by default, a forward-slash: "/") + + .breadcrumb-item::before { + display: inline-block; // Suppress underlining of the separator in modern browsers + padding-right: $breadcrumb-item-padding; + padding-left: $breadcrumb-item-padding; + color: $breadcrumb-divider-color; + content: "#{$breadcrumb-divider}"; + } + + // IE9-11 hack to properly handle hyperlink underlines for breadcrumbs built + // without `<ul>`s. The `::before` pseudo-element generates an element + // *within* the .breadcrumb-item and thereby inherits the `text-decoration`. + // + // To trick IE into suppressing the underline, we give the pseudo-element an + // underline and then immediately remove it. + + .breadcrumb-item:hover::before { + text-decoration: underline; + } + + .breadcrumb-item:hover::before { + text-decoration: none; + } + + &.active { + color: $breadcrumb-active-color; + } +} diff --git a/src/styles/bootstrap/_button-group.scss b/src/styles/bootstrap/_button-group.scss new file mode 100755 index 0000000000000000000000000000000000000000..584ed151377cea71f96024e198def3ce8feaff81 --- /dev/null +++ b/src/styles/bootstrap/_button-group.scss @@ -0,0 +1,202 @@ +// scss-lint:disable QualifyingElement + +// Make the div behave like a button +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-flex; + vertical-align: middle; // match .btn alignment given font-size hack above + + > .btn { + position: relative; + flex: 0 1 auto; + + // Bring the hover, focused, and "active" buttons to the fron to overlay + // the borders properly + @include hover { + z-index: 2; + } + &:focus, + &:active, + &.active { + z-index: 2; + } + } + + // Prevent double borders when buttons are next to each other + .btn + .btn, + .btn + .btn-group, + .btn-group + .btn, + .btn-group + .btn-group { + margin-left: -$input-btn-border-width; + } +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + display: flex; + justify-content: flex-start; + + .input-group { + width: auto; + } +} + +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} + +// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match +.btn-group > .btn:first-child { + margin-left: 0; + + &:not(:last-child):not(.dropdown-toggle) { + @include border-right-radius(0); + } +} +// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + @include border-left-radius(0); +} + +// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group) +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) { + > .btn:last-child, + > .dropdown-toggle { + @include border-right-radius(0); + } +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + @include border-left-radius(0); +} + +// On active and open, don't show outline +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + + +// Sizing +// +// Remix the default button sizing classes into new ones for easier manipulation. + +.btn-group-sm > .btn { @extend .btn-sm; } +.btn-group-lg > .btn { @extend .btn-lg; } + + +// +// Split button dropdowns +// + +.btn + .dropdown-toggle-split { + padding-right: $btn-padding-x * .75; + padding-left: $btn-padding-x * .75; + + &::after { + margin-left: 0; + } +} + +.btn-sm + .dropdown-toggle-split { + padding-right: $btn-padding-x-sm * .75; + padding-left: $btn-padding-x-sm * .75; +} + +.btn-lg + .dropdown-toggle-split { + padding-right: $btn-padding-x-lg * .75; + padding-left: $btn-padding-x-lg * .75; +} + + +// The clickable button for toggling the menu +// Remove the gradient and set the same inset shadow as the :active state +.btn-group.open .dropdown-toggle { + @include box-shadow($btn-active-box-shadow); + + // Show no shadow for `.btn-link` since it has no other button styles. + &.btn-link { + @include box-shadow(none); + } +} + + +// +// Vertical button groups +// + +.btn-group-vertical { + display: inline-flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + + .btn, + .btn-group { + width: 100%; + } + + > .btn + .btn, + > .btn + .btn-group, + > .btn-group + .btn, + > .btn-group + .btn-group { + margin-top: -$input-btn-border-width; + margin-left: 0; + } +} + +.btn-group-vertical > .btn { + &:not(:first-child):not(:last-child) { + border-radius: 0; + } + &:first-child:not(:last-child) { + @include border-bottom-radius(0); + } + &:last-child:not(:first-child) { + @include border-top-radius(0); + } +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) { + > .btn:last-child, + > .dropdown-toggle { + @include border-bottom-radius(0); + } +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + @include border-top-radius(0); +} + + +// Checkbox and radio options +// +// In order to support the browser's form validation feedback, powered by the +// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use +// `display: none;` or `visibility: hidden;` as that also hides the popover. +// Simply visually hiding the inputs via `opacity` would leave them clickable in +// certain cases which is prevented by using `clip` and `pointer-events`. +// This way, we ensure a DOM element is visible to position the popover from. +// +// See https://github.com/twbs/bootstrap/pull/12794 and +// https://github.com/twbs/bootstrap/pull/14559 for more information. + +[data-toggle="buttons"] { + > .btn, + > .btn-group > .btn { + input[type="radio"], + input[type="checkbox"] { + position: absolute; + clip: rect(0,0,0,0); + pointer-events: none; + } + } +} diff --git a/src/styles/bootstrap/_buttons.scss b/src/styles/bootstrap/_buttons.scss new file mode 100755 index 0000000000000000000000000000000000000000..e36ff0f1f6645f1bf8c8bf88a4c81dfb59e4dbb2 --- /dev/null +++ b/src/styles/bootstrap/_buttons.scss @@ -0,0 +1,170 @@ +// scss-lint:disable QualifyingElement + +// +// Base styles +// + +.btn { + display: inline-block; + font-weight: $btn-font-weight; + line-height: $btn-line-height; + text-align: center; + white-space: nowrap; + vertical-align: middle; + user-select: none; + border: $input-btn-border-width solid transparent; + @include button-size($btn-padding-y, $btn-padding-x, $font-size-base, $btn-border-radius); + @include transition($btn-transition); + + // Share hover and focus styles + @include hover-focus { + text-decoration: none; + } + &:focus, + &.focus { + outline: 0; + box-shadow: $btn-focus-box-shadow; + } + + // Disabled comes first so active can properly restyle + &.disabled, + &:disabled { + cursor: $cursor-disabled; + opacity: .65; + @include box-shadow(none); + } + + &:active, + &.active { + background-image: none; + @include box-shadow($btn-focus-box-shadow, $btn-active-box-shadow); + } +} + +// Future-proof disabling of clicks on `<a>` elements +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} + + +// +// Alternate buttons +// + +.btn-primary { + @include button-variant($btn-primary-color, $btn-primary-bg, $btn-primary-border); +} +.btn-secondary { + @include button-variant($btn-secondary-color, $btn-secondary-bg, $btn-secondary-border); +} +.btn-info { + @include button-variant($btn-info-color, $btn-info-bg, $btn-info-border); +} +.btn-success { + @include button-variant($btn-success-color, $btn-success-bg, $btn-success-border); +} +.btn-warning { + @include button-variant($btn-warning-color, $btn-warning-bg, $btn-warning-border); +} +.btn-danger { + @include button-variant($btn-danger-color, $btn-danger-bg, $btn-danger-border); +} + +// Remove all backgrounds +.btn-outline-primary { + @include button-outline-variant($btn-primary-bg); +} +.btn-outline-secondary { + @include button-outline-variant($btn-secondary-border); +} +.btn-outline-info { + @include button-outline-variant($btn-info-bg); +} +.btn-outline-success { + @include button-outline-variant($btn-success-bg); +} +.btn-outline-warning { + @include button-outline-variant($btn-warning-bg); +} +.btn-outline-danger { + @include button-outline-variant($btn-danger-bg); +} + + +// +// Link buttons +// + +// Make a button look and behave like a link +.btn-link { + font-weight: $font-weight-normal; + color: $link-color; + border-radius: 0; + + &, + &:active, + &.active, + &:disabled { + background-color: transparent; + @include box-shadow(none); + } + &, + &:focus, + &:active { + border-color: transparent; + } + @include hover { + border-color: transparent; + } + @include hover-focus { + color: $link-hover-color; + text-decoration: $link-hover-decoration; + background-color: transparent; + } + &:disabled { + color: $btn-link-disabled-color; + + @include hover-focus { + text-decoration: none; + } + } +} + + +// +// Button Sizes +// + +.btn-lg { + // line-height: ensure even-numbered height of button next to large input + @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $font-size-lg, $btn-border-radius-lg); +} +.btn-sm { + // line-height: ensure proper height of button next to small input + @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $font-size-sm, $btn-border-radius-sm); +} + + +// +// Block button +// + +.btn-block { + display: block; + width: 100%; +} + +// Vertically space out multiple block buttons +.btn-block + .btn-block { + margin-top: $btn-block-spacing-y; +} + +// Specificity overrides +input[type="submit"], +input[type="reset"], +input[type="button"] { + &.btn-block { + width: 100%; + } +} diff --git a/src/styles/bootstrap/_card.scss b/src/styles/bootstrap/_card.scss new file mode 100755 index 0000000000000000000000000000000000000000..9fe70e8cf6d262e4c6de6e8529c9b74f65c7897e --- /dev/null +++ b/src/styles/bootstrap/_card.scss @@ -0,0 +1,276 @@ +// +// Base styles +// + +.card { + position: relative; + display: flex; + flex-direction: column; + background-color: $card-bg; + border: $card-border-width solid $card-border-color; + @include border-radius($card-border-radius); +} + +.card-block { + // Enable `flex-grow: 1` for decks and groups so that card blocks take up + // as much space as possible, ensuring footers are aligned to the bottom. + flex: 1 1 auto; + padding: $card-spacer-x; +} + +.card-title { + margin-bottom: $card-spacer-y; +} + +.card-subtitle { + margin-top: -($card-spacer-y / 2); + margin-bottom: 0; +} + +.card-text:last-child { + margin-bottom: 0; +} + +.card-link { + @include hover { + text-decoration: none; + } + + + .card-link { + margin-left: $card-spacer-x; + } +} + +.card { + > .list-group:first-child { + .list-group-item:first-child { + @include border-top-radius($card-border-radius); + } + } + + > .list-group:last-child { + .list-group-item:last-child { + @include border-bottom-radius($card-border-radius); + } + } +} + + +// +// Optional textual caps +// + +.card-header { + padding: $card-spacer-y $card-spacer-x; + margin-bottom: 0; // Removes the default margin-bottom of <hN> + background-color: $card-cap-bg; + border-bottom: $card-border-width solid $card-border-color; + + &:first-child { + @include border-radius($card-border-radius-inner $card-border-radius-inner 0 0); + } +} + +.card-footer { + padding: $card-spacer-y $card-spacer-x; + background-color: $card-cap-bg; + border-top: $card-border-width solid $card-border-color; + + &:last-child { + @include border-radius(0 0 $card-border-radius-inner $card-border-radius-inner); + } +} + + +// +// Header navs +// + +.card-header-tabs { + margin-right: -($card-spacer-x / 2); + margin-bottom: -$card-spacer-y; + margin-left: -($card-spacer-x / 2); + border-bottom: 0; +} + +.card-header-pills { + margin-right: -($card-spacer-x / 2); + margin-left: -($card-spacer-x / 2); +} + + +// +// Background variations +// + +.card-primary { + @include card-variant($brand-primary, $brand-primary); +} +.card-success { + @include card-variant($brand-success, $brand-success); +} +.card-info { + @include card-variant($brand-info, $brand-info); +} +.card-warning { + @include card-variant($brand-warning, $brand-warning); +} +.card-danger { + @include card-variant($brand-danger, $brand-danger); +} + +// Remove all backgrounds +.card-outline-primary { + @include card-outline-variant($btn-primary-bg); +} +.card-outline-secondary { + @include card-outline-variant($btn-secondary-border); +} +.card-outline-info { + @include card-outline-variant($btn-info-bg); +} +.card-outline-success { + @include card-outline-variant($btn-success-bg); +} +.card-outline-warning { + @include card-outline-variant($btn-warning-bg); +} +.card-outline-danger { + @include card-outline-variant($btn-danger-bg); +} + +// +// Inverse text within a card for use with dark backgrounds +// + +.card-inverse { + @include card-inverse; +} + +// +// Blockquote +// + +.card-blockquote { + padding: 0; + margin-bottom: 0; + border-left: 0; +} + +// Card image +.card-img { + // margin: -1.325rem; + @include border-radius($card-border-radius-inner); +} +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: $card-img-overlay-padding; +} + + + +// Card image caps +.card-img-top { + @include border-top-radius($card-border-radius-inner); +} +.card-img-bottom { + @include border-bottom-radius($card-border-radius-inner); +} + + +// Card deck + +@include media-breakpoint-up(sm) { + .card-deck { + display: flex; + flex-flow: row wrap; + + .card { + display: flex; + flex: 1 0 0; + flex-direction: column; + + // Selectively apply horizontal margins to cards to avoid doing the + // negative margin dance like our grid. This differs from the grid + // due to the use of margins as gutters instead of padding. + &:not(:first-child) { margin-left: $card-deck-margin; } + &:not(:last-child) { margin-right: $card-deck-margin; } + } + } +} + + +// +// Card groups +// + +@include media-breakpoint-up(sm) { + .card-group { + display: flex; + flex-flow: row wrap; + + .card { + flex: 1 0 0; + + + .card { + margin-left: 0; + border-left: 0; + } + + // Handle rounded corners + @if $enable-rounded { + &:first-child { + @include border-right-radius(0); + + .card-img-top { + border-top-right-radius: 0; + } + .card-img-bottom { + border-bottom-right-radius: 0; + } + } + &:last-child { + @include border-left-radius(0); + + .card-img-top { + border-top-left-radius: 0; + } + .card-img-bottom { + border-bottom-left-radius: 0; + } + } + + &:not(:first-child):not(:last-child) { + border-radius: 0; + + .card-img-top, + .card-img-bottom { + border-radius: 0; + } + } + } + } + } +} + + +// +// Columns +// + +@include media-breakpoint-up(sm) { + .card-columns { + column-count: $card-columns-count; + column-gap: $card-columns-gap; + + .card { + display: inline-block; // Don't let them vertically span multiple columns + width: 100%; // Don't let their width change + margin-bottom: $card-columns-margin; + } + } +} diff --git a/src/styles/bootstrap/_carousel.scss b/src/styles/bootstrap/_carousel.scss new file mode 100755 index 0000000000000000000000000000000000000000..54478e450425672eb7e4de7838a9ac14da8b5533 --- /dev/null +++ b/src/styles/bootstrap/_carousel.scss @@ -0,0 +1,178 @@ +// Wrapper for the slide container and indicators +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} + +.carousel-item { + position: relative; + display: none; + width: 100%; + + @include if-supports-3d-transforms() { + @include transition($carousel-transition); + backface-visibility: hidden; + perspective: 1000px; + } +} + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: flex; +} + +.carousel-item-next, +.carousel-item-prev { + position: absolute; + top: 0; +} + +// CSS3 transforms when supported by the browser +@include if-supports-3d-transforms() { + .carousel-item-next.carousel-item-left, + .carousel-item-prev.carousel-item-right { + transform: translate3d(0, 0, 0); + } + + .carousel-item-next, + .active.carousel-item-right { + transform: translate3d(100%, 0, 0); + } + + .carousel-item-prev, + .active.carousel-item-left { + transform: translate3d(-100%, 0, 0); + } +} + + +// +// Left/right controls for nav +// + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + // Use flex for alignment (1-3) + display: flex; // 1. allow flex styles + align-items: center; // 2. vertically center contents + justify-content: center; // 3. horizontally center contents + width: $carousel-control-width; + color: $carousel-control-color; + text-align: center; + opacity: $carousel-control-opacity; + // We can't have a transition here because WebKit cancels the carousel + // animation if you trip this while in the middle of another animation. + + // Hover/focus state + @include hover-focus { + color: $carousel-control-color; + text-decoration: none; + outline: 0; + opacity: .9; + } +} +.carousel-control-prev { + left: 0; +} +.carousel-control-next { + right: 0; +} + +// Icons for within +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: $carousel-control-icon-width; + height: $carousel-control-icon-width; + background: transparent no-repeat center center; + background-size: 100% 100%; +} +.carousel-control-prev-icon { + background-image: $carousel-control-prev-icon-bg; +} +.carousel-control-next-icon { + background-image: $carousel-control-next-icon-bg; +} + + +// Optional indicator pips +// +// Add an ordered list with the following class and add a list item for each +// slide your carousel holds. + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 10px; + left: 0; + z-index: 15; + display: flex; + justify-content: center; + padding-left: 0; // override <ol> default + // Use the .carousel-control's width as margin so we don't overlay those + margin-right: $carousel-control-width; + margin-left: $carousel-control-width; + list-style: none; + + li { + position: relative; + flex: 1 0 auto; + max-width: $carousel-indicator-width; + height: $carousel-indicator-height; + margin-right: $carousel-indicator-spacer; + margin-left: $carousel-indicator-spacer; + text-indent: -999px; + cursor: pointer; + background-color: rgba($carousel-indicator-active-bg, .5); + + // Use pseudo classes to increase the hit area by 10px on top and bottom. + &::before { + position: absolute; + top: -10px; + left: 0; + display: inline-block; + width: 100%; + height: 10px; + content: ""; + } + &::after { + position: absolute; + bottom: -10px; + left: 0; + display: inline-block; + width: 100%; + height: 10px; + content: ""; + } + } + + .active { + background-color: $carousel-indicator-active-bg; + } +} + + +// Optional captions +// +// + +.carousel-caption { + position: absolute; + right: ((100% - $carousel-caption-width) / 2); + bottom: 20px; + left: ((100% - $carousel-caption-width) / 2); + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: $carousel-caption-color; + text-align: center; +} diff --git a/src/styles/bootstrap/_close.scss b/src/styles/bootstrap/_close.scss new file mode 100755 index 0000000000000000000000000000000000000000..859990e3105abe1506516e1c7cdfeb44422711c8 --- /dev/null +++ b/src/styles/bootstrap/_close.scss @@ -0,0 +1,31 @@ +.close { + float: right; + font-size: $close-font-size; + font-weight: $close-font-weight; + line-height: 1; + color: $close-color; + text-shadow: $close-text-shadow; + opacity: .5; + + @include hover-focus { + color: $close-color; + text-decoration: none; + cursor: pointer; + opacity: .75; + } +} + +// Additional properties for button version +// iOS requires the button element instead of an anchor tag. +// If you want the anchor version, it requires `href="#"`. +// See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile + +// scss-lint:disable QualifyingElement +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} +// scss-lint:enable QualifyingElement diff --git a/src/styles/bootstrap/_code.scss b/src/styles/bootstrap/_code.scss new file mode 100755 index 0000000000000000000000000000000000000000..759da15b791e70d7f7f9e8f9ea8877e2449a74a1 --- /dev/null +++ b/src/styles/bootstrap/_code.scss @@ -0,0 +1,64 @@ +// Inline and block code styles +code, +kbd, +pre, +samp { + font-family: $font-family-monospace; +} + +// Inline code +code { + padding: $code-padding-y $code-padding-x; + font-size: $code-font-size; + color: $code-color; + background-color: $code-bg; + @include border-radius($border-radius); + + // Streamline the style when inside anchors to avoid broken underline and more + a > & { + padding: 0; + color: inherit; + background-color: inherit; + } +} + +// User input typically entered via keyboard +kbd { + padding: $code-padding-y $code-padding-x; + font-size: $code-font-size; + color: $kbd-color; + background-color: $kbd-bg; + @include border-radius($border-radius-sm); + @include box-shadow($kbd-box-shadow); + + kbd { + padding: 0; + font-size: 100%; + font-weight: $nested-kbd-font-weight; + @include box-shadow(none); + } +} + +// Blocks of code +pre { + display: block; + margin-top: 0; + margin-bottom: 1rem; + font-size: $code-font-size; + color: $pre-color; + + // Account for some code outputs that place code tags in pre tags + code { + padding: 0; + font-size: inherit; + color: inherit; + background-color: transparent; + border-radius: 0; + } +} + +// Enable scrollable blocks of code +.pre-scrollable { + max-height: $pre-scrollable-max-height; + overflow-y: scroll; +} diff --git a/src/styles/bootstrap/_custom-forms.scss b/src/styles/bootstrap/_custom-forms.scss new file mode 100755 index 0000000000000000000000000000000000000000..ef2aab3544d0376b681652fbcdd6b4b0e7dbbe22 --- /dev/null +++ b/src/styles/bootstrap/_custom-forms.scss @@ -0,0 +1,263 @@ +// scss-lint:disable PropertyCount + +// Embedded icons from Open Iconic. +// Released under MIT and copyright 2014 Waybury. +// https://useiconic.com/open + + +// Checkboxes and radios +// +// Base class takes care of all the key behavioral aspects. + +.custom-control { + position: relative; + display: inline-flex; + min-height: (1rem * $line-height-base); + padding-left: $custom-control-gutter; + margin-right: $custom-control-spacer-x; + cursor: pointer; +} + +.custom-control-input { + position: absolute; + z-index: -1; // Put the input behind the label so it doesn't overlay text + opacity: 0; + + &:checked ~ .custom-control-indicator { + color: $custom-control-checked-indicator-color; + background-color: $custom-control-checked-indicator-bg; + @include box-shadow($custom-control-checked-indicator-box-shadow); + } + + &:focus ~ .custom-control-indicator { + // the mixin is not used here to make sure there is feedback + box-shadow: $custom-control-focus-indicator-box-shadow; + } + + &:active ~ .custom-control-indicator { + color: $custom-control-active-indicator-color; + background-color: $custom-control-active-indicator-bg; + @include box-shadow($custom-control-active-indicator-box-shadow); + } + + &:disabled { + ~ .custom-control-indicator { + cursor: $custom-control-disabled-cursor; + background-color: $custom-control-disabled-indicator-bg; + } + + ~ .custom-control-description { + color: $custom-control-disabled-description-color; + cursor: $custom-control-disabled-cursor; + } + } +} + +// Custom indicator +// +// Generates a shadow element to create our makeshift checkbox/radio background. + +.custom-control-indicator { + position: absolute; + top: (($line-height-base - $custom-control-indicator-size) / 2); + left: 0; + display: block; + width: $custom-control-indicator-size; + height: $custom-control-indicator-size; + pointer-events: none; + user-select: none; + background-color: $custom-control-indicator-bg; + background-repeat: no-repeat; + background-position: center center; + background-size: $custom-control-indicator-bg-size; + @include box-shadow($custom-control-indicator-box-shadow); +} + +// Checkboxes +// +// Tweak just a few things for checkboxes. + +.custom-checkbox { + .custom-control-indicator { + @include border-radius($custom-checkbox-radius); + } + + .custom-control-input:checked ~ .custom-control-indicator { + background-image: $custom-checkbox-checked-icon; + } + + .custom-control-input:indeterminate ~ .custom-control-indicator { + background-color: $custom-checkbox-indeterminate-bg; + background-image: $custom-checkbox-indeterminate-icon; + @include box-shadow($custom-checkbox-indeterminate-box-shadow); + } +} + +// Radios +// +// Tweak just a few things for radios. + +.custom-radio { + .custom-control-indicator { + border-radius: $custom-radio-radius; + } + + .custom-control-input:checked ~ .custom-control-indicator { + background-image: $custom-radio-checked-icon; + } +} + + +// Layout options +// +// By default radios and checkboxes are `inline-block` with no additional spacing +// set. Use these optional classes to tweak the layout. + +.custom-controls-stacked { + display: flex; + flex-direction: column; + + .custom-control { + margin-bottom: $custom-control-spacer-y; + + + .custom-control { + margin-left: 0; + } + } +} + + +// Select +// +// Replaces the browser default select with a custom one, mostly pulled from +// http://primercss.io. +// + +.custom-select { + display: inline-block; + max-width: 100%; + $select-border-width: ($border-width * 2); + height: calc(#{$input-height} + #{$select-border-width}); + padding: $custom-select-padding-y ($custom-select-padding-x + $custom-select-indicator-padding) $custom-select-padding-y $custom-select-padding-x; + line-height: $custom-select-line-height; + color: $custom-select-color; + vertical-align: middle; + background: $custom-select-bg $custom-select-indicator no-repeat right $custom-select-padding-x center; + background-size: $custom-select-bg-size; + border: $custom-select-border-width solid $custom-select-border-color; + @include border-radius($custom-select-border-radius); + // Use vendor prefixes as `appearance` isn't part of the CSS spec. + -moz-appearance: none; + -webkit-appearance: none; + + &:focus { + border-color: $custom-select-focus-border-color; + outline: none; + @include box-shadow($custom-select-focus-box-shadow); + + &::-ms-value { + // For visual consistency with other platforms/browsers, + // supress the default white text on blue background highlight given to + // the selected option text when the (still closed) <select> receives focus + // in IE and (under certain conditions) Edge. + // See https://github.com/twbs/bootstrap/issues/19398. + color: $input-color; + background-color: $input-bg; + } + } + + &:disabled { + color: $custom-select-disabled-color; + cursor: $cursor-disabled; + background-color: $custom-select-disabled-bg; + } + + // Hides the default caret in IE11 + &::-ms-expand { + opacity: 0; + } +} + +.custom-select-sm { + padding-top: $custom-select-padding-y; + padding-bottom: $custom-select-padding-y; + font-size: $custom-select-sm-font-size; + + // &:not([multiple]) { + // height: 26px; + // min-height: 26px; + // } +} + + +// File +// +// Custom file input. + +.custom-file { + position: relative; + display: inline-block; + max-width: 100%; + height: $custom-file-height; + margin-bottom: 0; + cursor: pointer; +} + +.custom-file-input { + min-width: $custom-file-width; + max-width: 100%; + height: $custom-file-height; + margin: 0; + filter: alpha(opacity = 0); + opacity: 0; + + &:focus ~ .custom-file-control { + @include box-shadow($custom-file-focus-box-shadow); + } +} + +.custom-file-control { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 5; + height: $custom-file-height; + padding: $custom-file-padding-x $custom-file-padding-y; + line-height: $custom-file-line-height; + color: $custom-file-color; + pointer-events: none; + user-select: none; + background-color: $custom-file-bg; + border: $custom-file-border-width solid $custom-file-border-color; + @include border-radius($custom-file-border-radius); + @include box-shadow($custom-file-box-shadow); + + @each $lang, $text in map-get($custom-file-text, placeholder) { + &:lang(#{$lang})::after { + content: $text; + } + } + + &::before { + position: absolute; + top: -$custom-file-border-width; + right: -$custom-file-border-width; + bottom: -$custom-file-border-width; + z-index: 6; + display: block; + height: $custom-file-height; + padding: $custom-file-padding-x $custom-file-padding-y; + line-height: $custom-file-line-height; + color: $custom-file-button-color; + background-color: $custom-file-button-bg; + border: $custom-file-border-width solid $custom-file-border-color; + @include border-radius(0 $custom-file-border-radius $custom-file-border-radius 0); + } + + @each $lang, $text in map-get($custom-file-text, button-label) { + &:lang(#{$lang})::before { + content: $text; + } + } +} diff --git a/src/styles/bootstrap/_custom.scss b/src/styles/bootstrap/_custom.scss new file mode 100755 index 0000000000000000000000000000000000000000..88ccf202e4462a04df892f8558b127f083ae55a8 --- /dev/null +++ b/src/styles/bootstrap/_custom.scss @@ -0,0 +1,4 @@ +// Bootstrap overrides +// +// Copy variables from `_variables.scss` to this file to override default values +// without modifying source files. diff --git a/src/styles/bootstrap/_dropdown.scss b/src/styles/bootstrap/_dropdown.scss new file mode 100755 index 0000000000000000000000000000000000000000..1c2741a2e36a2d3bdba7dfed81d0d4e01e814422 --- /dev/null +++ b/src/styles/bootstrap/_dropdown.scss @@ -0,0 +1,161 @@ +// The dropdown wrapper (`<div>`) +.dropup, +.dropdown { + position: relative; +} + +.dropdown-toggle { + // Generate the caret automatically + &::after { + display: inline-block; + width: 0; + height: 0; + margin-left: $caret-width; + vertical-align: middle; + content: ""; + border-top: $caret-width solid; + border-right: $caret-width solid transparent; + border-left: $caret-width solid transparent; + } + + // Prevent the focus on the dropdown toggle when closing dropdowns + &:focus { + outline: 0; + } +} + +.dropup { + .dropdown-toggle { + &::after { + border-top: 0; + border-bottom: $caret-width solid; + } + } +} + +// The dropdown menu +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: $zindex-dropdown; + display: none; // none by default, but block on "open" of the menu + float: left; + min-width: $dropdown-min-width; + padding: $dropdown-padding-y 0; + margin: $dropdown-margin-top 0 0; // override default ul + font-size: $font-size-base; // Redeclare because nesting can cause inheritance issues + color: $body-color; + text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer) + list-style: none; + background-color: $dropdown-bg; + background-clip: padding-box; + border: $dropdown-border-width solid $dropdown-border-color; + @include border-radius($border-radius); + @include box-shadow($dropdown-box-shadow); +} + +// Dividers (basically an `<hr>`) within the dropdown +.dropdown-divider { + @include nav-divider($dropdown-divider-bg); +} + +// Links, buttons, and more within the dropdown menu +// +// `<button>`-specific styles are denoted with `// For <button>s` +.dropdown-item { + display: block; + width: 100%; // For `<button>`s + padding: 3px $dropdown-item-padding-x; + clear: both; + font-weight: $font-weight-normal; + color: $dropdown-link-color; + text-align: inherit; // For `<button>`s + white-space: nowrap; // prevent links from randomly breaking onto new lines + background: none; // For `<button>`s + border: 0; // For `<button>`s + + @include hover-focus { + color: $dropdown-link-hover-color; + text-decoration: none; + background-color: $dropdown-link-hover-bg; + } + + &.active, + &:active { + color: $dropdown-link-active-color; + text-decoration: none; + background-color: $dropdown-link-active-bg; + } + + &.disabled, + &:disabled { + color: $dropdown-link-disabled-color; + cursor: $cursor-disabled; + background-color: transparent; + // Remove CSS gradients if they're enabled + @if $enable-gradients { + background-image: none; + } + } +} + +// Open state for the dropdown +.show { + // Show the menu + > .dropdown-menu { + display: block; + } + + // Remove the outline when :focus is triggered + > a { + outline: 0; + } +} + +// Menu positioning +// +// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown +// menu with the parent. +.dropdown-menu-right { + right: 0; + left: auto; // Reset the default from `.dropdown-menu` +} + +.dropdown-menu-left { + right: auto; + left: 0; +} + +// Dropdown section headers +.dropdown-header { + display: block; + padding: $dropdown-padding-y $dropdown-item-padding-x; + margin-bottom: 0; // for use with heading elements + font-size: $font-size-sm; + color: $dropdown-header-color; + white-space: nowrap; // as with > li > a +} + +// Backdrop to catch body clicks on mobile, etc. +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: $zindex-dropdown-backdrop; +} + +// Allow for dropdowns to go bottom up (aka, dropup-menu) +// +// Just add .dropup after the standard .dropdown class and you're set. + +.dropup { + // Different positioning for bottom up menu + .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: $dropdown-margin-top; + } +} diff --git a/src/styles/bootstrap/_forms.scss b/src/styles/bootstrap/_forms.scss new file mode 100755 index 0000000000000000000000000000000000000000..7be62bde662e83cb996c84cbeccfc2e1f98d3fd8 --- /dev/null +++ b/src/styles/bootstrap/_forms.scss @@ -0,0 +1,388 @@ +// scss-lint:disable QualifyingElement + +// +// Textual form controls +// + +.form-control { + display: block; + width: 100%; + // // Make inputs at least the height of their button counterpart (base line-height + padding + border) + // height: $input-height; + padding: $input-padding-y $input-padding-x; + font-size: $font-size-base; + line-height: $input-line-height; + color: $input-color; + background-color: $input-bg; + // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214. + background-image: none; + background-clip: padding-box; + border: $input-btn-border-width solid $input-border-color; + + // Note: This has no effect on <select>s in some browsers, due to the limited stylability of `<select>`s in CSS. + @if $enable-rounded { + // Manually use the if/else instead of the mixin to account for iOS override + border-radius: $input-border-radius; + } @else { + // Otherwise undo the iOS default + border-radius: 0; + } + + @include box-shadow($input-box-shadow); + @include transition($input-transition); + + // Unstyle the caret on `<select>`s in IE10+. + &::-ms-expand { + background-color: transparent; + border: 0; + } + + // Customize the `:focus` state to imitate native WebKit styles. + @include form-control-focus(); + + // Placeholder + &::placeholder { + color: $input-color-placeholder; + // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526. + opacity: 1; + } + + // Disabled and read-only inputs + // + // HTML5 says that controls under a fieldset > legend:first-child won't be + // disabled if the fieldset is disabled. Due to implementation difficulty, we + // don't honor that edge case; we style them as disabled anyway. + &:disabled, + &[readonly] { + background-color: $input-bg-disabled; + // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655. + opacity: 1; + } + + &:disabled { + cursor: $cursor-disabled; + } +} + +select.form-control { + &:not([size]):not([multiple]) { + $select-border-width: ($border-width * 2); + height: calc(#{$input-height} + #{$select-border-width}); + } + + &:focus::-ms-value { + // Suppress the nested default white text on blue background highlight given to + // the selected option text when the (still closed) <select> receives focus + // in IE and (under certain conditions) Edge, as it looks bad and cannot be made to + // match the appearance of the native widget. + // See https://github.com/twbs/bootstrap/issues/19398. + color: $input-color; + background-color: $input-bg; + } +} + +// Make file inputs better match text inputs by forcing them to new lines. +.form-control-file, +.form-control-range { + display: block; +} + + +// +// Labels +// + +// For use with horizontal and inline forms, when you need the label text to +// align with the form controls. +.col-form-label { + padding-top: calc(#{$input-padding-y} - #{$input-btn-border-width} * 2); + padding-bottom: calc(#{$input-padding-y} - #{$input-btn-border-width} * 2); + margin-bottom: 0; // Override the `<label>` default +} + +.col-form-label-lg { + padding-top: calc(#{$input-padding-y-lg} - #{$input-btn-border-width} * 2); + padding-bottom: calc(#{$input-padding-y-lg} - #{$input-btn-border-width} * 2); + font-size: $font-size-lg; +} + +.col-form-label-sm { + padding-top: calc(#{$input-padding-y-sm} - #{$input-btn-border-width} * 2); + padding-bottom: calc(#{$input-padding-y-sm} - #{$input-btn-border-width} * 2); + font-size: $font-size-sm; +} + + +// +// Legends +// + +// For use with horizontal and inline forms, when you need the legend text to +// be the same size as regular labels, and to align with the form controls. +.col-form-legend { + padding-top: $input-padding-y; + padding-bottom: $input-padding-y; + margin-bottom: 0; + font-size: $font-size-base; +} + + +// Static form control text +// +// Apply class to an element to make any string of text align with labels in a +// horizontal form layout. + +.form-control-static { + padding-top: $input-padding-y; + padding-bottom: $input-padding-y; + margin-bottom: 0; // match inputs if this class comes on inputs with default margins + line-height: $input-line-height; + border: solid transparent; + border-width: $input-btn-border-width 0; + + &.form-control-sm, + &.form-control-lg { + padding-right: 0; + padding-left: 0; + } +} + + +// Form control sizing +// +// Build on `.form-control` with modifier classes to decrease or increase the +// height and font-size of form controls. +// +// The `.form-group-* form-control` variations are sadly duplicated to avoid the +// issue documented in https://github.com/twbs/bootstrap/issues/15074. + +.form-control-sm { + padding: $input-padding-y-sm $input-padding-x-sm; + font-size: $font-size-sm; + @include border-radius($input-border-radius-sm); +} + +select.form-control-sm { + &:not([size]):not([multiple]) { + height: $input-height-sm; + } +} + +.form-control-lg { + padding: $input-padding-y-lg $input-padding-x-lg; + font-size: $font-size-lg; + @include border-radius($input-border-radius-lg); +} + +select.form-control-lg { + &:not([size]):not([multiple]) { + height: $input-height-lg; + } +} + + +// Form groups +// +// Designed to help with the organization and spacing of vertical forms. For +// horizontal forms, use the predefined grid classes. + +.form-group { + margin-bottom: $form-group-margin-bottom; +} + +.form-text { + display: block; + margin-top: $form-text-margin-top; +} + + +// Checkboxes and radios +// +// Indent the labels to position radios/checkboxes as hanging controls. + +.form-check { + position: relative; + display: block; + margin-bottom: $form-check-margin-bottom; + + &.disabled { + .form-check-label { + color: $text-muted; + cursor: $cursor-disabled; + } + } +} + +.form-check-label { + padding-left: $form-check-input-gutter; + margin-bottom: 0; // Override default `<label>` bottom margin + cursor: pointer; +} + +.form-check-input { + position: absolute; + margin-top: $form-check-input-margin-y; + margin-left: -$form-check-input-gutter; + + &:only-child { + position: static; + } +} + +// Radios and checkboxes on same line +.form-check-inline { + display: inline-block; + + .form-check-label { + vertical-align: middle; + } + + + .form-check-inline { + margin-left: $form-check-inline-margin-x; + } +} + + +// Form control feedback states +// +// Apply contextual and semantic states to individual form controls. + +.form-control-feedback { + margin-top: $form-feedback-margin-top; +} + +.form-control-success, +.form-control-warning, +.form-control-danger { + padding-right: ($input-padding-x * 3); + background-repeat: no-repeat; + background-position: center right ($input-height / 4); + background-size: ($input-height / 2) ($input-height / 2); +} + +// Form validation states +.has-success { + @include form-control-validation($brand-success); + + .form-control-success { + background-image: $form-icon-success; + } +} + +.has-warning { + @include form-control-validation($brand-warning); + + .form-control-warning { + background-image: $form-icon-warning; + } +} + +.has-danger { + @include form-control-validation($brand-danger); + + .form-control-danger { + background-image: $form-icon-danger; + } +} + + +// Inline forms +// +// Make forms appear inline(-block) by adding the `.form-inline` class. Inline +// forms begin stacked on extra small (mobile) devices and then go inline when +// viewports reach <768px. +// +// Requires wrapping inputs and labels with `.form-group` for proper display of +// default HTML form controls and our custom form controls (e.g., input groups). + +.form-inline { + display: flex; + flex-flow: row wrap; + align-items: center; // Prevent shorter elements from growing to same height as others (e.g., small buttons growing to normal sized button height) + + // Because we use flex, the initial sizing of checkboxes is collapsed and + // doesn't occupy the full-width (which is what we want for xs grid tier), + // so we force that here. + .form-check { + width: 100%; + } + + // Kick in the inline + @include media-breakpoint-up(sm) { + label { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 0; + } + + // Inline-block all the things for "inline" + .form-group { + display: flex; + flex: 0 0 auto; + flex-flow: row wrap; + align-items: center; + margin-bottom: 0; + } + + // Allow folks to *not* use `.form-group` + .form-control { + display: inline-block; + width: auto; // Prevent labels from stacking above inputs in `.form-group` + vertical-align: middle; + } + + // Make static controls behave like regular ones + .form-control-static { + display: inline-block; + } + + .input-group { + width: auto; + } + + .form-control-label { + margin-bottom: 0; + vertical-align: middle; + } + + // Remove default margin on radios/checkboxes that were used for stacking, and + // then undo the floating of radios and checkboxes to match. + .form-check { + display: flex; + align-items: center; + justify-content: center; + width: auto; + margin-top: 0; + margin-bottom: 0; + } + .form-check-label { + padding-left: 0; + } + .form-check-input { + position: relative; + margin-top: 0; + margin-right: $form-check-input-margin-x; + margin-left: 0; + } + + // Custom form controls + .custom-control { + display: flex; + align-items: center; + justify-content: center; + padding-left: 0; + } + .custom-control-indicator { + position: static; + display: inline-block; + margin-right: $form-check-input-margin-x; // Flexbox alignment means we lose our HTML space here, so we compensate. + vertical-align: text-bottom; + } + + // Re-override the feedback icon. + .has-feedback .form-control-feedback { + top: 0; + } + } +} diff --git a/src/styles/bootstrap/_grid.scss b/src/styles/bootstrap/_grid.scss new file mode 100755 index 0000000000000000000000000000000000000000..8c7a9ee3188e183f5240c2ef6432bc4e5954ac60 --- /dev/null +++ b/src/styles/bootstrap/_grid.scss @@ -0,0 +1,52 @@ +// Container widths +// +// Set the container width, and override it for fixed navbars in media queries. + +@if $enable-grid-classes { + .container { + @include make-container(); + @include make-container-max-widths(); + } +} + +// Fluid container +// +// Utilizes the mixin meant for fixed width containers, but without any defined +// width for fluid, full width layouts. + +@if $enable-grid-classes { + .container-fluid { + @include make-container(); + } +} + +// Row +// +// Rows contain and clear the floats of your columns. + +@if $enable-grid-classes { + .row { + @include make-row(); + } + + // Remove the negative margin from default .row, then the horizontal padding + // from all immediate children columns (to prevent runaway style inheritance). + .no-gutters { + margin-right: 0; + margin-left: 0; + + > .col, + > [class*="col-"] { + padding-right: 0; + padding-left: 0; + } + } +} + +// Columns +// +// Common styles for small and large grid columns + +@if $enable-grid-classes { + @include make-grid-columns(); +} diff --git a/src/styles/bootstrap/_images.scss b/src/styles/bootstrap/_images.scss new file mode 100755 index 0000000000000000000000000000000000000000..a8135a6c35d4f4ec2e6a951ab0a35b56e0bcc30c --- /dev/null +++ b/src/styles/bootstrap/_images.scss @@ -0,0 +1,43 @@ +// Responsive images (ensure images don't scale beyond their parents) +// +// This is purposefully opt-in via an explicit class rather than being the default for all `<img>`s. +// We previously tried the "images are responsive by default" approach in Bootstrap v2, +// and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps) +// which weren't expecting the images within themselves to be involuntarily resized. +// See also https://github.com/twbs/bootstrap/issues/18178 +.img-fluid { + @include img-fluid; +} + + +// Image thumbnails +.img-thumbnail { + padding: $thumbnail-padding; + background-color: $thumbnail-bg; + border: $thumbnail-border-width solid $thumbnail-border-color; + @include border-radius($thumbnail-border-radius); + @include transition($thumbnail-transition); + @include box-shadow($thumbnail-box-shadow); + + // Keep them at most 100% wide + @include img-fluid; +} + +// +// Figures +// + +.figure { + // Ensures the caption's text aligns with the image. + display: inline-block; +} + +.figure-img { + margin-bottom: ($spacer-y / 2); + line-height: 1; +} + +.figure-caption { + font-size: $figure-caption-font-size; + color: $figure-caption-color; +} diff --git a/src/styles/bootstrap/_input-group.scss b/src/styles/bootstrap/_input-group.scss new file mode 100755 index 0000000000000000000000000000000000000000..ab44883bd1960ff63a5dcd58226f6ddc33c618c0 --- /dev/null +++ b/src/styles/bootstrap/_input-group.scss @@ -0,0 +1,178 @@ +// +// Base styles +// + +.input-group { + position: relative; + display: flex; + width: 100%; + + .form-control { + // Ensure that the input is always above the *appended* addon button for + // proper border colors. + position: relative; + z-index: 2; + flex: 1 1 auto; + // Add width 1% and flex-basis auto to ensure that button will not wrap out + // the column. Applies to IE Edge+ and Firefox. Chrome does not require this. + width: 1%; + margin-bottom: 0; + + // Bring the "active" form control to the front + @include hover-focus-active { + z-index: 3; + } + } +} + +.input-group-addon, +.input-group-btn, +.input-group .form-control { + // Vertically centers the content of the addons within the input group + display: flex; + flex-direction: column; + justify-content: center; + + &:not(:first-child):not(:last-child) { + @include border-radius(0); + } +} + +.input-group-addon, +.input-group-btn { + white-space: nowrap; + vertical-align: middle; // Match the inputs +} + + +// Sizing options +// +// Remix the default form control sizing classes into new ones for easier +// manipulation. + +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + @extend .form-control-lg; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + @extend .form-control-sm; +} + + +// +// Text input groups +// + +.input-group-addon { + padding: $input-padding-y $input-padding-x; + margin-bottom: 0; // Allow use of <label> elements by overriding our default margin-bottom + font-size: $font-size-base; // Match inputs + font-weight: $font-weight-normal; + line-height: $input-line-height; + color: $input-color; + text-align: center; + background-color: $input-group-addon-bg; + border: $input-btn-border-width solid $input-group-addon-border-color; + @include border-radius($input-border-radius); + + // Sizing + &.form-control-sm { + padding: $input-padding-y-sm $input-padding-x-sm; + font-size: $font-size-sm; + @include border-radius($input-border-radius-sm); + } + &.form-control-lg { + padding: $input-padding-y-lg $input-padding-x-lg; + font-size: $font-size-lg; + @include border-radius($input-border-radius-lg); + } + + // scss-lint:disable QualifyingElement + // Nuke default margins from checkboxes and radios to vertically center within. + input[type="radio"], + input[type="checkbox"] { + margin-top: 0; + } + // scss-lint:enable QualifyingElement +} + + +// +// Reset rounded corners +// + +.input-group .form-control:not(:last-child), +.input-group-addon:not(:last-child), +.input-group-btn:not(:last-child) > .btn, +.input-group-btn:not(:last-child) > .btn-group > .btn, +.input-group-btn:not(:last-child) > .dropdown-toggle, +.input-group-btn:not(:first-child) > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:not(:first-child) > .btn-group:not(:last-child) > .btn { + @include border-right-radius(0); +} +.input-group-addon:not(:last-child) { + border-right: 0; +} +.input-group .form-control:not(:first-child), +.input-group-addon:not(:first-child), +.input-group-btn:not(:first-child) > .btn, +.input-group-btn:not(:first-child) > .btn-group > .btn, +.input-group-btn:not(:first-child) > .dropdown-toggle, +.input-group-btn:not(:last-child) > .btn:not(:first-child), +.input-group-btn:not(:last-child) > .btn-group:not(:first-child) > .btn { + @include border-left-radius(0); +} +.form-control + .input-group-addon:not(:first-child) { + border-left: 0; +} + +// +// Button input groups +// + +.input-group-btn { + position: relative; + // Jankily prevent input button groups from wrapping with `white-space` and + // `font-size` in combination with `inline-block` on buttons. + font-size: 0; + white-space: nowrap; + + // Negative margin for spacing, position for bringing hovered/focused/actived + // element above the siblings. + > .btn { + position: relative; + // Vertically stretch the button and center its content + flex: 1; + + + .btn { + margin-left: (-$input-btn-border-width); + } + + // Bring the "active" button to the front + @include hover-focus-active { + z-index: 3; + } + } + + // Negative margin to only have a single, shared border between the two + &:not(:last-child) { + > .btn, + > .btn-group { + margin-right: (-$input-btn-border-width); + } + } + &:not(:first-child) { + > .btn, + > .btn-group { + z-index: 2; + margin-left: (-$input-btn-border-width); + // Because specificity + @include hover-focus-active { + z-index: 3; + } + } + } +} diff --git a/src/styles/bootstrap/_jumbotron.scss b/src/styles/bootstrap/_jumbotron.scss new file mode 100755 index 0000000000000000000000000000000000000000..b12d465d973790bfda1fc2ddc9512e21115fadc9 --- /dev/null +++ b/src/styles/bootstrap/_jumbotron.scss @@ -0,0 +1,20 @@ +.jumbotron { + padding: $jumbotron-padding ($jumbotron-padding / 2); + margin-bottom: $jumbotron-padding; + background-color: $jumbotron-bg; + @include border-radius($border-radius-lg); + + @include media-breakpoint-up(sm) { + padding: ($jumbotron-padding * 2) $jumbotron-padding; + } +} + +.jumbotron-hr { + border-top-color: darken($jumbotron-bg, 10%); +} + +.jumbotron-fluid { + padding-right: 0; + padding-left: 0; + @include border-radius(0); +} diff --git a/src/styles/bootstrap/_list-group.scss b/src/styles/bootstrap/_list-group.scss new file mode 100755 index 0000000000000000000000000000000000000000..ec813c807ed91e412fd5c236a1a0b8ec5a525e9d --- /dev/null +++ b/src/styles/bootstrap/_list-group.scss @@ -0,0 +1,141 @@ +// Base class +// +// Easily usable on <ul>, <ol>, or <div>. + +.list-group { + display: flex; + flex-direction: column; + + // No need to set list-style: none; since .list-group-item is block level + padding-left: 0; // reset padding because ul and ol + margin-bottom: 0; +} + + +// Interactive list items +// +// Use anchor or button elements instead of `li`s or `div`s to create interactive +// list items. Includes an extra `.active` modifier class for selected items. + +.list-group-item-action { + width: 100%; // For `<button>`s (anchors become 100% by default though) + color: $list-group-link-color; + text-align: inherit; // For `<button>`s (anchors inherit) + + .list-group-item-heading { + color: $list-group-link-heading-color; + } + + // Hover state + @include hover-focus { + color: $list-group-link-hover-color; + text-decoration: none; + background-color: $list-group-hover-bg; + } + + &:active { + color: $list-group-link-active-color; + background-color: $list-group-link-active-bg; + } +} + + +// Individual list items +// +// Use on `li`s or `div`s within the `.list-group` parent. + +.list-group-item { + position: relative; + display: flex; + flex-flow: row wrap; + align-items: center; + padding: $list-group-item-padding-y $list-group-item-padding-x; + // Place the border on the list items and negative margin up for better styling + margin-bottom: -$list-group-border-width; + background-color: $list-group-bg; + border: $list-group-border-width solid $list-group-border-color; + + &:first-child { + @include border-top-radius($list-group-border-radius); + } + + &:last-child { + margin-bottom: 0; + @include border-bottom-radius($list-group-border-radius); + } + + @include hover-focus { + text-decoration: none; + } + + &.disabled, + &:disabled { + color: $list-group-disabled-color; + cursor: $cursor-disabled; + background-color: $list-group-disabled-bg; + + // Force color to inherit for custom content + .list-group-item-heading { + color: inherit; + } + .list-group-item-text { + color: $list-group-disabled-text-color; + } + } + + // Include both here for `<a>`s and `<button>`s + &.active { + z-index: 2; // Place active items above their siblings for proper border styling + color: $list-group-active-color; + background-color: $list-group-active-bg; + border-color: $list-group-active-border; + + // Force color to inherit for custom content + .list-group-item-heading, + .list-group-item-heading > small, + .list-group-item-heading > .small { + color: inherit; + } + + .list-group-item-text { + color: $list-group-active-text-color; + } + } +} + + +// Flush list items +// +// Remove borders and border-radius to keep list group items edge-to-edge. Most +// useful within other components (e.g., cards). + +.list-group-flush { + .list-group-item { + border-right: 0; + border-left: 0; + border-radius: 0; + } + + &:first-child { + .list-group-item:first-child { + border-top: 0; + } + } + + &:last-child { + .list-group-item:last-child { + border-bottom: 0; + } + } +} + + +// Contextual variants +// +// Add modifier classes to change text and background color on individual items. +// Organizationally, this must come after the `:hover` states. + +@include list-group-item-variant(success, $state-success-bg, $state-success-text); +@include list-group-item-variant(info, $state-info-bg, $state-info-text); +@include list-group-item-variant(warning, $state-warning-bg, $state-warning-text); +@include list-group-item-variant(danger, $state-danger-bg, $state-danger-text); diff --git a/src/styles/bootstrap/_media.scss b/src/styles/bootstrap/_media.scss new file mode 100755 index 0000000000000000000000000000000000000000..b573052c14affa5bdca02ac9e3e7a4168768925b --- /dev/null +++ b/src/styles/bootstrap/_media.scss @@ -0,0 +1,8 @@ +.media { + display: flex; + align-items: flex-start; +} + +.media-body { + flex: 1; +} diff --git a/src/styles/bootstrap/_mixins.scss b/src/styles/bootstrap/_mixins.scss new file mode 100755 index 0000000000000000000000000000000000000000..da4738297cf3714ca5f9dc98ff6cc1d6ecdcb98e --- /dev/null +++ b/src/styles/bootstrap/_mixins.scss @@ -0,0 +1,57 @@ +// Toggles +// +// Used in conjunction with global variables to enable certain theme features. + +@mixin box-shadow($shadow...) { + @if $enable-shadows { + box-shadow: $shadow; + } +} + +@mixin transition($transition...) { + @if $enable-transitions { + @if length($transition) == 0 { + transition: $transition-base; + } @else { + transition: $transition; + } + } +} + +// Utilities +@import "mixins/breakpoints"; +@import "mixins/hover"; +@import "mixins/image"; +@import "mixins/badge"; +@import "mixins/resize"; +@import "mixins/screen-reader"; +@import "mixins/size"; +@import "mixins/reset-text"; +@import "mixins/text-emphasis"; +@import "mixins/text-hide"; +@import "mixins/text-truncate"; +@import "mixins/transforms"; +@import "mixins/visibility"; + +// // Components +@import "mixins/alert"; +@import "mixins/buttons"; +@import "mixins/cards"; +@import "mixins/pagination"; +@import "mixins/lists"; +@import "mixins/list-group"; +@import "mixins/nav-divider"; +@import "mixins/forms"; +@import "mixins/table-row"; + +// // Skins +@import "mixins/background-variant"; +@import "mixins/border-radius"; +@import "mixins/gradients"; + +// // Layout +@import "mixins/clearfix"; +// @import "mixins/navbar-align"; +@import "mixins/grid-framework"; +@import "mixins/grid"; +@import "mixins/float"; diff --git a/src/styles/bootstrap/_modal.scss b/src/styles/bootstrap/_modal.scss new file mode 100755 index 0000000000000000000000000000000000000000..9d2a86776da60bd0c5ed98723b8941901d10ec19 --- /dev/null +++ b/src/styles/bootstrap/_modal.scss @@ -0,0 +1,142 @@ +// .modal-open - body class for killing the scroll +// .modal - container to scroll within +// .modal-dialog - positioning shell for the actual modal +// .modal-content - actual modal w/ bg and corners and stuff + + +// Kill the scroll on the body +.modal-open { + overflow: hidden; +} + +// Container that the modal scrolls within +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: $zindex-modal; + display: none; + overflow: hidden; + // Prevent Chrome on Windows from adding a focus outline. For details, see + // https://github.com/twbs/bootstrap/pull/10951. + outline: 0; + // We deliberately don't use `-webkit-overflow-scrolling: touch;` due to a + // gnarly iOS Safari bug: https://bugs.webkit.org/show_bug.cgi?id=158342 + // See also https://github.com/twbs/bootstrap/issues/17695 + + // When fading in the modal, animate it to slide down + &.fade .modal-dialog { + @include transition($modal-transition); + transform: translate(0, -25%); + } + &.show .modal-dialog { transform: translate(0, 0); } +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} + +// Shell div to position the modal with bottom padding +.modal-dialog { + position: relative; + width: auto; + margin: $modal-dialog-margin; +} + +// Actual modal +.modal-content { + position: relative; + display: flex; + flex-direction: column; + background-color: $modal-content-bg; + background-clip: padding-box; + border: $modal-content-border-width solid $modal-content-border-color; + @include border-radius($border-radius-lg); + @include box-shadow($modal-content-xs-box-shadow); + // Remove focus outline from opened modal + outline: 0; +} + +// Modal background +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: $zindex-modal-backdrop; + background-color: $modal-backdrop-bg; + + // Fade for backdrop + &.fade { opacity: 0; } + &.show { opacity: $modal-backdrop-opacity; } +} + +// Modal header +// Top section of the modal w/ title and dismiss +.modal-header { + display: flex; + align-items: center; // vertically center it + justify-content: space-between; // Put modal header elements (title and dismiss) on opposite ends + padding: $modal-header-padding; + border-bottom: $modal-header-border-width solid $modal-header-border-color; +} + +// Title text within header +.modal-title { + margin-bottom: 0; + line-height: $modal-title-line-height; +} + +// Modal body +// Where all modal content resides (sibling of .modal-header and .modal-footer) +.modal-body { + position: relative; + // Enable `flex-grow: 1` so that the body take up as much space as possible + // when should there be a fixed height on `.modal-dialog`. + flex: 1 1 auto; + padding: $modal-inner-padding; +} + +// Footer (for actions) +.modal-footer { + display: flex; + align-items: center; // vertically center + justify-content: flex-end; // Right align buttons with flex property because text-align doesn't work on flex items + padding: $modal-inner-padding; + border-top: $modal-footer-border-width solid $modal-footer-border-color; + + // Easily place margin between footer elements + > :not(:first-child) { margin-left: .25rem; } + > :not(:last-child) { margin-right: .25rem; } +} + +// Measure scrollbar width for padding body during modal show/hide +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} + +// Scale up the modal +@include media-breakpoint-up(sm) { + // Automatically set modal's width for larger viewports + .modal-dialog { + max-width: $modal-md; + margin: $modal-dialog-sm-up-margin-y auto; + } + + .modal-content { + @include box-shadow($modal-content-sm-up-box-shadow); + } + + .modal-sm { max-width: $modal-sm; } +} + +@include media-breakpoint-up(lg) { + .modal-lg { max-width: $modal-lg; } +} diff --git a/src/styles/bootstrap/_nav.scss b/src/styles/bootstrap/_nav.scss new file mode 100755 index 0000000000000000000000000000000000000000..eb316bb27b33ca1531cca5b11984a399e1477654 --- /dev/null +++ b/src/styles/bootstrap/_nav.scss @@ -0,0 +1,119 @@ +// Base class +// +// Kickstart any navigation component with a set of style resets. Works with +// `<nav>`s or `<ul>`s. + +.nav { + display: flex; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav-link { + display: block; + padding: $nav-link-padding; + + @include hover-focus { + text-decoration: none; + } + + // Disabled state lightens text and removes hover/tab effects + &.disabled { + color: $nav-disabled-link-color; + cursor: $cursor-disabled; + } +} + + +// +// Tabs +// + +.nav-tabs { + border-bottom: $nav-tabs-border-width solid $nav-tabs-border-color; + + .nav-item { + margin-bottom: -$nav-tabs-border-width; + } + + .nav-link { + border: $nav-tabs-border-width solid transparent; + @include border-top-radius($nav-tabs-border-radius); + + @include hover-focus { + border-color: $nav-tabs-link-hover-border-color $nav-tabs-link-hover-border-color $nav-tabs-border-color; + } + + &.disabled { + color: $nav-disabled-link-color; + background-color: transparent; + border-color: transparent; + } + } + + .nav-link.active, + .nav-item.show .nav-link { + color: $nav-tabs-active-link-hover-color; + background-color: $nav-tabs-active-link-hover-bg; + border-color: $nav-tabs-active-link-hover-border-color $nav-tabs-active-link-hover-border-color $nav-tabs-active-link-hover-bg; + } + + .dropdown-menu { + // Make dropdown border overlap tab border + margin-top: -$nav-tabs-border-width; + // Remove the top rounded corners here since there is a hard edge above the menu + @include border-top-radius(0); + } +} + + +// +// Pills +// + +.nav-pills { + .nav-link { + @include border-radius($nav-pills-border-radius); + } + + .nav-link.active, + .nav-item.show .nav-link { + color: $nav-pills-active-link-color; + cursor: default; + background-color: $nav-pills-active-link-bg; + } +} + + +// +// Justified variants +// + +.nav-fill { + .nav-item { + flex: 1 1 auto; + text-align: center; + } +} + +.nav-justified { + .nav-item { + flex: 1 1 100%; + text-align: center; + } +} + + +// Tabbable tabs +// +// Hide tabbable panes to start, show them when `.active` + +.tab-content { + > .tab-pane { + display: none; + } + > .active { + display: block; + } +} diff --git a/src/styles/bootstrap/_navbar.scss b/src/styles/bootstrap/_navbar.scss new file mode 100755 index 0000000000000000000000000000000000000000..80beec8f3e1caba8547150591a24747a8db9566c --- /dev/null +++ b/src/styles/bootstrap/_navbar.scss @@ -0,0 +1,268 @@ +// Contents +// +// Navbar +// Navbar brand +// Navbar nav +// Navbar text +// Navbar divider +// Responsive navbar +// Navbar position +// Navbar themes + + +// Navbar +// +// Provide a static navbar from which we expand to create full-width, fixed, and +// other navbar variations. + +.navbar { + position: relative; + display: flex; + flex-direction: column; + padding: $navbar-padding-y $navbar-padding-x; +} + + +// Navbar brand +// +// Used for brand, project, or site names. + +.navbar-brand { + display: inline-block; + padding-top: .25rem; + padding-bottom: .25rem; + margin-right: $navbar-padding-x; + font-size: $font-size-lg; + line-height: inherit; + white-space: nowrap; + + @include hover-focus { + text-decoration: none; + } +} + + +// Navbar nav +// +// Custom navbar navigation (doesn't require `.nav`, but does make use of `.nav-link`). + +.navbar-nav { + display: flex; + flex-direction: column; // cannot use `inherit` to get the `.navbar`s value + padding-left: 0; + margin-bottom: 0; + list-style: none; + + .nav-link { + padding-right: 0; + padding-left: 0; + } +} + + +// Navbar text +// +// + +.navbar-text { + display: inline-block; + padding-top: .425rem; + padding-bottom: .425rem; +} + + +// Responsive navbar +// +// Custom styles for responsive collapsing and toggling of navbar contents. +// Powered by the collapse Bootstrap JavaScript plugin. + +// Button for toggling the navbar when in its collapsed state +.navbar-toggler { + align-self: flex-start; // Prevent toggler from growing to full width when it's the only visible navbar child + padding: $navbar-toggler-padding-y $navbar-toggler-padding-x; + font-size: $navbar-toggler-font-size; + line-height: 1; + background: transparent; // remove default button style + border: $border-width solid transparent; // remove default button style + @include border-radius($navbar-toggler-border-radius); + + @include hover-focus { + text-decoration: none; + } +} + +// Keep as a separate element so folks can easily override it with another icon +// or image file as needed. +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + content: ""; + background: no-repeat center center; + background-size: 100% 100%; +} + +// Use `position` on the toggler to prevent it from being auto placed as a flex +// item and allow easy placement. +.navbar-toggler-left { + position: absolute; + left: $navbar-padding-x; +} +.navbar-toggler-right { + position: absolute; + right: $navbar-padding-x; +} + +// Generate series of `.navbar-toggleable-*` responsive classes for configuring +// where your navbar collapses. +.navbar-toggleable { + @each $breakpoint in map-keys($grid-breakpoints) { + $next: breakpoint-next($breakpoint, $grid-breakpoints); + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + &#{$infix} { + @include media-breakpoint-down($breakpoint) { + .navbar-nav { + .dropdown-menu { + position: static; + float: none; + } + } + + > .container { + padding-right: 0; + padding-left: 0; + } + } + + @include media-breakpoint-up($next) { + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + + .navbar-nav { + flex-direction: row; + + .nav-link { + padding-right: .5rem; + padding-left: .5rem; + } + } + + // For nesting containers, have to redeclare for alignment purposes + > .container { + display: flex; + flex-wrap: nowrap; + align-items: center; + } + + // scss-lint:disable ImportantRule + .navbar-collapse { + display: flex !important; + width: 100%; + } + // scss-lint:enable ImportantRule + + .navbar-toggler { + display: none; + } + } + } + } +} + + +// Navbar themes +// +// Styles for switching between navbars with light or dark background. + +// Dark links against a light background +.navbar-light { + .navbar-brand, + .navbar-toggler { + color: $navbar-light-active-color; + + @include hover-focus { + color: $navbar-light-active-color; + } + } + + .navbar-nav { + .nav-link { + color: $navbar-light-color; + + @include hover-focus { + color: $navbar-light-hover-color; + } + + &.disabled { + color: $navbar-light-disabled-color; + } + } + + .open > .nav-link, + .active > .nav-link, + .nav-link.open, + .nav-link.active { + color: $navbar-light-active-color; + } + } + + .navbar-toggler { + border-color: $navbar-light-toggler-border; + } + + .navbar-toggler-icon { + background-image: $navbar-light-toggler-bg; + } + + .navbar-text { + color: $navbar-light-color; + } +} + +// White links against a dark background +.navbar-inverse { + .navbar-brand, + .navbar-toggler { + color: $navbar-inverse-active-color; + + @include hover-focus { + color: $navbar-inverse-active-color; + } + } + + .navbar-nav { + .nav-link { + color: $navbar-inverse-color; + + @include hover-focus { + color: $navbar-inverse-hover-color; + } + + &.disabled { + color: $navbar-inverse-disabled-color; + } + } + + .open > .nav-link, + .active > .nav-link, + .nav-link.open, + .nav-link.active { + color: $navbar-inverse-active-color; + } + } + + .navbar-toggler { + border-color: $navbar-inverse-toggler-border; + } + + .navbar-toggler-icon { + background-image: $navbar-inverse-toggler-bg; + } + + .navbar-text { + color: $navbar-inverse-color; + } +} diff --git a/src/styles/bootstrap/_normalize.scss b/src/styles/bootstrap/_normalize.scss new file mode 100755 index 0000000000000000000000000000000000000000..6bafd53f63a5a3ceb166648590131ad332fe48f4 --- /dev/null +++ b/src/styles/bootstrap/_normalize.scss @@ -0,0 +1,461 @@ +/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */ + +// +// 1. Change the default font family in all browsers (opinionated). +// 2. Correct the line height in all browsers. +// 3. Prevent adjustments of font size after orientation changes in +// IE on Windows Phone and in iOS. +// + +// Document +// ========================================================================== + +html { + font-family: sans-serif; // 1 + line-height: 1.15; // 2 + -ms-text-size-adjust: 100%; // 3 + -webkit-text-size-adjust: 100%; // 3 +} + +// Sections +// ========================================================================== + +// +// Remove the margin in all browsers (opinionated). +// + +body { + margin: 0; +} + +// +// Add the correct display in IE 9-. +// + +article, +aside, +footer, +header, +nav, +section { + display: block; +} + +// +// Correct the font size and margin on `h1` elements within `section` and +// `article` contexts in Chrome, Firefox, and Safari. +// + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +// Grouping content +// ========================================================================== + +// +// Add the correct display in IE 9-. +// 1. Add the correct display in IE. +// + +figcaption, +figure, +main { // 1 + display: block; +} + +// +// Add the correct margin in IE 8. +// + +figure { + margin: 1em 40px; +} + +// +// 1. Add the correct box sizing in Firefox. +// 2. Show the overflow in Edge and IE. +// + +hr { + box-sizing: content-box; // 1 + height: 0; // 1 + overflow: visible; // 2 +} + +// +// 1. Correct the inheritance and scaling of font size in all browsers. +// 2. Correct the odd `em` font sizing in all browsers. +// + +pre { + font-family: monospace, monospace; // 1 + font-size: 1em; // 2 +} + +// Text-level semantics +// ========================================================================== + +// +// 1. Remove the gray background on active links in IE 10. +// 2. Remove gaps in links underline in iOS 8+ and Safari 8+. +// + +a { + background-color: transparent; // 1 + -webkit-text-decoration-skip: objects; // 2 +} + +// +// Remove the outline on focused links when they are also active or hovered +// in all browsers (opinionated). +// + +a:active, +a:hover { + outline-width: 0; +} + +// +// 1. Remove the bottom border in Firefox 39-. +// 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. +// + +abbr[title] { + border-bottom: none; // 1 + text-decoration: underline; // 2 + text-decoration: underline dotted; // 2 +} + +// +// Prevent the duplicate application of `bolder` by the next rule in Safari 6. +// + +b, +strong { + font-weight: inherit; +} + +// +// Add the correct font weight in Chrome, Edge, and Safari. +// + +b, +strong { + font-weight: bolder; +} + +// +// 1. Correct the inheritance and scaling of font size in all browsers. +// 2. Correct the odd `em` font sizing in all browsers. +// + +code, +kbd, +samp { + font-family: monospace, monospace; // 1 + font-size: 1em; // 2 +} + +// +// Add the correct font style in Android 4.3-. +// + +dfn { + font-style: italic; +} + +// +// Add the correct background and color in IE 9-. +// + +mark { + background-color: #ff0; + color: #000; +} + +// +// Add the correct font size in all browsers. +// + +small { + font-size: 80%; +} + +// +// Prevent `sub` and `sup` elements from affecting the line height in +// all browsers. +// + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +// Embedded content +// ========================================================================== + +// +// Add the correct display in IE 9-. +// + +audio, +video { + display: inline-block; +} + +// +// Add the correct display in iOS 4-7. +// + +audio:not([controls]) { + display: none; + height: 0; +} + +// +// Remove the border on images inside links in IE 10-. +// + +img { + border-style: none; +} + +// +// Hide the overflow in IE. +// + +svg:not(:root) { + overflow: hidden; +} + +// Forms +// ========================================================================== + +// +// 1. Change the font styles in all browsers (opinionated). +// 2. Remove the margin in Firefox and Safari. +// + +button, +input, +optgroup, +select, +textarea { + font-family: sans-serif; // 1 + font-size: 100%; // 1 + line-height: 1.15; // 1 + margin: 0; // 2 +} + +// +// Show the overflow in IE. +// 1. Show the overflow in Edge. +// + +button, +input { // 1 + overflow: visible; +} + +// +// Remove the inheritance of text transform in Edge, Firefox, and IE. +// 1. Remove the inheritance of text transform in Firefox. +// + +button, +select { // 1 + text-transform: none; +} + +// +// 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` +// controls in Android 4. +// 2. Correct the inability to style clickable types in iOS and Safari. +// + +button, +html [type="button"], // 1 +[type="reset"], +[type="submit"] { + -webkit-appearance: button; // 2 +} + +// +// Remove the inner border and padding in Firefox. +// + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +// +// Restore the focus styles unset by the previous rule. +// + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +// +// Change the border, margin, and padding in all browsers (opinionated). +// + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +// +// 1. Correct the text wrapping in Edge and IE. +// 2. Correct the color inheritance from `fieldset` elements in IE. +// 3. Remove the padding so developers are not caught out when they zero out +// `fieldset` elements in all browsers. +// + +legend { + box-sizing: border-box; // 1 + color: inherit; // 2 + display: table; // 1 + max-width: 100%; // 1 + padding: 0; // 3 + white-space: normal; // 1 +} + +// +// 1. Add the correct display in IE 9-. +// 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. +// + +progress { + display: inline-block; // 1 + vertical-align: baseline; // 2 +} + +// +// Remove the default vertical scrollbar in IE. +// + +textarea { + overflow: auto; +} + +// +// 1. Add the correct box sizing in IE 10-. +// 2. Remove the padding in IE 10-. +// + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; // 1 + padding: 0; // 2 +} + +// +// Correct the cursor style of increment and decrement buttons in Chrome. +// + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +// +// 1. Correct the odd appearance in Chrome and Safari. +// 2. Correct the outline style in Safari. +// + +[type="search"] { + -webkit-appearance: textfield; // 1 + outline-offset: -2px; // 2 +} + +// +// Remove the inner padding and cancel buttons in Chrome and Safari on macOS. +// + +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +// +// 1. Correct the inability to style clickable types in iOS and Safari. +// 2. Change font properties to `inherit` in Safari. +// + +::-webkit-file-upload-button { + -webkit-appearance: button; // 1 + font: inherit; // 2 +} + +// Interactive +// ========================================================================== + +// +// Add the correct display in IE 9-. +// 1. Add the correct display in Edge, IE, and Firefox. +// + +details, // 1 +menu { + display: block; +} + +// +// Add the correct display in all browsers. +// + +summary { + display: list-item; +} + +// Scripting +// ========================================================================== + +// +// Add the correct display in IE 9-. +// + +canvas { + display: inline-block; +} + +// +// Add the correct display in IE. +// + +template { + display: none; +} + +// Hidden +// ========================================================================== + +// +// Add the correct display in IE 10-. +// + +[hidden] { + display: none; +} diff --git a/src/styles/bootstrap/_pagination.scss b/src/styles/bootstrap/_pagination.scss new file mode 100755 index 0000000000000000000000000000000000000000..24aa028d1f901a12378339b4f3334e8a44027b6a --- /dev/null +++ b/src/styles/bootstrap/_pagination.scss @@ -0,0 +1,67 @@ +.pagination { + display: flex; + // 1-2: Disable browser default list styles + padding-left: 0; // 1 + list-style: none; // 2 + @include border-radius(); +} + +.page-item { + &:first-child { + .page-link { + margin-left: 0; + @include border-left-radius($border-radius); + } + } + &:last-child { + .page-link { + @include border-right-radius($border-radius); + } + } + + &.active .page-link { + z-index: 2; + color: $pagination-active-color; + background-color: $pagination-active-bg; + border-color: $pagination-active-border; + } + + &.disabled .page-link { + color: $pagination-disabled-color; + pointer-events: none; + cursor: $cursor-disabled; // While `pointer-events: none` removes the cursor in modern browsers, we provide a disabled cursor as a fallback. + background-color: $pagination-disabled-bg; + border-color: $pagination-disabled-border; + } +} + +.page-link { + position: relative; + display: block; + padding: $pagination-padding-y $pagination-padding-x; + margin-left: -1px; + line-height: $pagination-line-height; + color: $pagination-color; + background-color: $pagination-bg; + border: $pagination-border-width solid $pagination-border-color; + + @include hover-focus { + color: $pagination-hover-color; + text-decoration: none; + background-color: $pagination-hover-bg; + border-color: $pagination-hover-border; + } +} + + +// +// Sizing +// + +.pagination-lg { + @include pagination-size($pagination-padding-y-lg, $pagination-padding-x-lg, $font-size-lg, $line-height-lg, $border-radius-lg); +} + +.pagination-sm { + @include pagination-size($pagination-padding-y-sm, $pagination-padding-x-sm, $font-size-sm, $line-height-sm, $border-radius-sm); +} diff --git a/src/styles/bootstrap/_popover.scss b/src/styles/bootstrap/_popover.scss new file mode 100755 index 0000000000000000000000000000000000000000..1b6363405ce939ff4b07e672542a0b15a82c3d28 --- /dev/null +++ b/src/styles/bootstrap/_popover.scss @@ -0,0 +1,171 @@ +.popover { + position: absolute; + top: 0; + left: 0; + z-index: $zindex-popover; + display: block; + max-width: $popover-max-width; + padding: $popover-inner-padding; + // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element. + // So reset our font and text properties to avoid inheriting weird values. + @include reset-text(); + font-size: $font-size-sm; + // Allow breaking very long words so they don't overflow the popover's bounds + word-wrap: break-word; + background-color: $popover-bg; + background-clip: padding-box; + border: $popover-border-width solid $popover-border-color; + @include border-radius($border-radius-lg); + @include box-shadow($popover-box-shadow); + + + // Popover directions + + &.popover-top, + &.bs-tether-element-attached-bottom { + margin-top: -$popover-arrow-width; + + &::before, + &::after { + left: 50%; + border-bottom-width: 0; + } + + &::before { + bottom: -$popover-arrow-outer-width; + margin-left: -$popover-arrow-outer-width; + border-top-color: $popover-arrow-outer-color; + } + + &::after { + bottom: -($popover-arrow-outer-width - 1); + margin-left: -$popover-arrow-width; + border-top-color: $popover-arrow-color; + } + } + + &.popover-right, + &.bs-tether-element-attached-left { + margin-left: $popover-arrow-width; + + &::before, + &::after { + top: 50%; + border-left-width: 0; + } + + &::before { + left: -$popover-arrow-outer-width; + margin-top: -$popover-arrow-outer-width; + border-right-color: $popover-arrow-outer-color; + } + + &::after { + left: -($popover-arrow-outer-width - 1); + margin-top: -($popover-arrow-outer-width - 1); + border-right-color: $popover-arrow-color; + } + } + + &.popover-bottom, + &.bs-tether-element-attached-top { + margin-top: $popover-arrow-width; + + &::before, + &::after { + left: 50%; + border-top-width: 0; + } + + &::before { + top: -$popover-arrow-outer-width; + margin-left: -$popover-arrow-outer-width; + border-bottom-color: $popover-arrow-outer-color; + } + + &::after { + top: -($popover-arrow-outer-width - 1); + margin-left: -$popover-arrow-width; + border-bottom-color: $popover-title-bg; + } + + // This will remove the popover-title's border just below the arrow + .popover-title::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: 20px; + margin-left: -10px; + content: ""; + border-bottom: 1px solid $popover-title-bg; + } + } + + &.popover-left, + &.bs-tether-element-attached-right { + margin-left: -$popover-arrow-width; + + &::before, + &::after { + top: 50%; + border-right-width: 0; + } + + &::before { + right: -$popover-arrow-outer-width; + margin-top: -$popover-arrow-outer-width; + border-left-color: $popover-arrow-outer-color; + } + + &::after { + right: -($popover-arrow-outer-width - 1); + margin-top: -($popover-arrow-outer-width - 1); + border-left-color: $popover-arrow-color; + } + } +} + + +// Offset the popover to account for the popover arrow +.popover-title { + padding: $popover-title-padding-y $popover-title-padding-x; + margin-bottom: 0; // Reset the default from Reboot + font-size: $font-size-base; + background-color: $popover-title-bg; + border-bottom: $popover-border-width solid darken($popover-title-bg, 5%); + $offset-border-width: calc(#{$border-radius-lg} - #{$popover-border-width}); + @include border-top-radius($offset-border-width); + + &:empty { + display: none; + } +} + +.popover-content { + padding: $popover-content-padding-y $popover-content-padding-x; +} + + +// Arrows +// +// .popover-arrow is outer, .popover-arrow::after is inner + +.popover::before, +.popover::after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.popover::before { + content: ""; + border-width: $popover-arrow-outer-width; +} +.popover::after { + content: ""; + border-width: $popover-arrow-width; +} diff --git a/src/styles/bootstrap/_print.scss b/src/styles/bootstrap/_print.scss new file mode 100755 index 0000000000000000000000000000000000000000..e20219a38bd7c8924fb7013d278b608afe6b3a5e --- /dev/null +++ b/src/styles/bootstrap/_print.scss @@ -0,0 +1,119 @@ +// scss-lint:disable QualifyingElement + +// Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css + +// ========================================================================== +// Print styles. +// Inlined to avoid the additional HTTP request: +// http://www.phpied.com/delay-loading-your-print-css/ +// ========================================================================== + +@if $enable-print-styles { + @media print { + *, + *::before, + *::after, + p::first-letter, + div::first-letter, + blockquote::first-letter, + li::first-letter, + p::first-line, + div::first-line, + blockquote::first-line, + li::first-line { + // Bootstrap specific; comment out `color` and `background` + //color: #000 !important; // Black prints faster: + // http://www.sanbeiji.com/archives/953 + text-shadow: none !important; + //background: transparent !important; + box-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + // Bootstrap specific; comment the following selector out + //a[href]::after { + // content: " (" attr(href) ")"; + //} + + abbr[title]::after { + content: " (" attr(title) ")"; + } + + // Bootstrap specific; comment the following selector out + // + // Don't show links that are fragment identifiers, + // or use the `javascript:` pseudo protocol + // + + //a[href^="#"]::after, + //a[href^="javascript:"]::after { + // content: ""; + //} + + pre { + white-space: pre-wrap !important; + } + pre, + blockquote { + border: $border-width solid #999; // Bootstrap custom code; using `$border-width` instead of 1px + page-break-inside: avoid; + } + + // + // Printing Tables: + // http://css-discuss.incutio.com/wiki/Printing_Tables + // + + thead { + display: table-header-group; + } + + tr, + img { + page-break-inside: avoid; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } + + // Bootstrap specific changes start + + // Bootstrap components + .navbar { + display: none; + } + .badge { + border: $border-width solid #000; + } + + .table { + border-collapse: collapse !important; + + td, + th { + background-color: #fff !important; + } + } + .table-bordered { + th, + td { + border: 1px solid #ddd !important; + } + } + + // Bootstrap specific changes end + } +} diff --git a/src/styles/bootstrap/_progress.scss b/src/styles/bootstrap/_progress.scss new file mode 100755 index 0000000000000000000000000000000000000000..02e4c3bd2db8b1b95e5b6d71873b57f187ef7c0f --- /dev/null +++ b/src/styles/bootstrap/_progress.scss @@ -0,0 +1,32 @@ +// Progress animations +@keyframes progress-bar-stripes { + from { background-position: $progress-height 0; } + to { background-position: 0 0; } +} + +// Basic progress bar +.progress { + display: flex; + overflow: hidden; // force rounded corners by cropping it + font-size: $progress-font-size; + line-height: $progress-height; + text-align: center; + background-color: $progress-bg; + @include border-radius($progress-border-radius); +} +.progress-bar { + height: $progress-height; + color: $progress-bar-color; + background-color: $progress-bar-bg; +} + +// Striped +.progress-bar-striped { + @include gradient-striped(); + background-size: $progress-height $progress-height; +} + +// Animated +.progress-bar-animated { + animation: progress-bar-stripes $progress-bar-animation-timing; +} diff --git a/src/styles/bootstrap/_reboot.scss b/src/styles/bootstrap/_reboot.scss new file mode 100755 index 0000000000000000000000000000000000000000..557829f25ce0d493486cabf271e12f0bdfec3607 --- /dev/null +++ b/src/styles/bootstrap/_reboot.scss @@ -0,0 +1,389 @@ +// scss-lint:disable QualifyingElement, DuplicateProperty + +// Reboot +// +// Global resets to common HTML elements and more for easier usage by Bootstrap. +// Adds additional rules on top of Normalize.css, including several overrides. + + +// Reset the box-sizing +// +// Change from `box-sizing: content-box` to `border-box` so that when you add +// `padding` or `border`s to an element, the overall declared `width` does not +// change. For example, `width: 100px;` will always be `100px` despite the +// `border: 10px solid red;` and `padding: 20px;`. +// +// Heads up! This reset may cause conflicts with some third-party widgets. For +// recommendations on resolving such conflicts, see +// https://getbootstrap.com/getting-started/#third-box-sizing. +// +// Credit: https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/ + +html { + box-sizing: border-box; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + + +// Make viewport responsive +// +// @viewport is needed because IE 10+ doesn't honor <meta name="viewport"> in +// some cases. See https://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/. +// Eventually @viewport will replace <meta name="viewport">. +// +// However, `device-width` is broken on IE 10 on Windows (Phone) 8, +// (see https://timkadlec.com/2013/01/windows-phone-8-and-device-width/ and https://github.com/twbs/bootstrap/issues/10497) +// and the fix for that involves a snippet of JavaScript to sniff the user agent +// and apply some conditional CSS. +// +// See https://getbootstrap.com/getting-started/#support-ie10-width for the relevant hack. +// +// Wrap `@viewport` with `@at-root` for when folks do a nested import (e.g., +// `.class-name { @import "bootstrap"; }`). +@at-root { + @-ms-viewport { width: device-width; } +} + + +// +// Reset HTML, body, and more +// + +html { + // We assume no initial pixel `font-size` for accessibility reasons. This + // allows web visitors to customize their browser default font-size, making + // your project more inclusive and accessible to everyone. + + // As a side-effect of setting the @viewport above, + // IE11 & Edge make the scrollbar overlap the content and automatically hide itself when not in use. + // Unfortunately, the auto-showing of the scrollbar is sometimes too sensitive, + // thus making it hard to click on stuff near the right edge of the page. + // So we add this style to force IE11 & Edge to use a "normal", non-overlapping, non-auto-hiding scrollbar. + // See https://github.com/twbs/bootstrap/issues/18543 + // and https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7165383/ + -ms-overflow-style: scrollbar; + + // Changes the default tap highlight to be completely transparent in iOS. + -webkit-tap-highlight-color: rgba(0,0,0,0); +} + +body { + font-family: $font-family-base; + font-size: $font-size-base; + font-weight: $font-weight-base; + line-height: $line-height-base; + // Go easy on the eyes and use something other than `#000` for text + color: $body-color; + // By default, `<body>` has no `background-color` so we set one as a best practice. + background-color: $body-bg; +} + +// Suppress the focus outline on elements that cannot be accessed via keyboard. +// This prevents an unwanted focus outline from appearing around elements that +// might still respond to pointer events. +// +// Credit: https://github.com/suitcss/base +[tabindex="-1"]:focus { + outline: none !important; +} + + +// +// Typography +// + +// Remove top margins from headings +// +// By default, `<h1>`-`<h6>` all receive top and bottom margins. We nuke the top +// margin for easier control within type scales as it avoids margin collapsing. +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: .5rem; +} + +// Reset margins on paragraphs +// +// Similarly, the top margin on `<p>`s get reset. However, we also reset the +// bottom margin to use `rem` units instead of `em`. +p { + margin-top: 0; + margin-bottom: 1rem; +} + +// Abbreviations +abbr[title], +// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257 +abbr[data-original-title] { + cursor: help; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; +} + +dt { + font-weight: $dt-font-weight; +} + +dd { + margin-bottom: .5rem; + margin-left: 0; // Undo browser default +} + +blockquote { + margin: 0 0 1rem; +} + + +// +// Links +// + +a { + color: $link-color; + text-decoration: $link-decoration; + + @include hover-focus { + color: $link-hover-color; + text-decoration: $link-hover-decoration; + } +} + +// And undo these styles for placeholder links/named anchors (without href) +// which have not been made explicitly keyboard-focusable (without tabindex). +// It would be more straightforward to just use a[href] in previous block, but that +// causes specificity issues in many other styles that are too complex to fix. +// See https://github.com/twbs/bootstrap/issues/19402 + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; + + @include hover-focus { + color: inherit; + text-decoration: none; + } + + &:focus { + outline: 0; + } +} + + +// +// Code +// + +pre { + // Remove browser default top margin + margin-top: 0; + // Reset browser default of `1em` to use `rem`s + margin-bottom: 1rem; + // Normalize v4 removed this property, causing `<pre>` content to break out of wrapping code snippets + overflow: auto; +} + + +// +// Figures +// + +figure { + // Normalize adds `margin` to `figure`s as browsers apply it inconsistently. + // We reset that to create a better flow in-page. + margin: 0 0 1rem; +} + + +// +// Images +// + +img { + // By default, `<img>`s are `inline-block`. This assumes that, and vertically + // centers them. This won't apply should you reset them to `block` level. + vertical-align: middle; + // Note: `<img>`s are deliberately not made responsive by default. + // For the rationale behind this, see the comments on the `.img-fluid` class. +} + + +// iOS "clickable elements" fix for role="button" +// +// Fixes "clickability" issue (and more generally, the firing of events such as focus as well) +// for traditionally non-focusable elements with role="button" +// see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile + +[role="button"] { + cursor: pointer; +} + + +// Avoid 300ms click delay on touch devices that support the `touch-action` CSS property. +// +// In particular, unlike most other browsers, IE11+Edge on Windows 10 on touch devices and IE Mobile 10-11 +// DON'T remove the click delay when `<meta name="viewport" content="width=device-width">` is present. +// However, they DO support removing the click delay via `touch-action: manipulation`. +// See: +// * https://v4-alpha.getbootstrap.com/content/reboot/#click-delay-optimization-for-touch +// * http://caniuse.com/#feat=css-touch-action +// * https://patrickhlauke.github.io/touch/tests/results/#suppressing-300ms-delay + +a, +area, +button, +[role="button"], +input, +label, +select, +summary, +textarea { + touch-action: manipulation; +} + + +// +// Tables +// + +table { + // No longer part of Normalize since v4 + border-collapse: collapse; + // Reset for nesting within parents with `background-color`. + background-color: $table-bg; +} + +caption { + padding-top: $table-cell-padding; + padding-bottom: $table-cell-padding; + color: $text-muted; + text-align: left; + caption-side: bottom; +} + +th { + // Centered by default, but left-align-ed to match the `td`s below. + text-align: left; +} + + +// +// Forms +// + +label { + // Allow labels to use `margin` for spacing. + display: inline-block; + margin-bottom: .5rem; +} + +// Work around a Firefox/IE bug where the transparent `button` background +// results in a loss of the default `button` focus styles. +// +// Credit: https://github.com/suitcss/base/ +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; +} + +input, +button, +select, +textarea { + // Normalize includes `font: inherit;`, so `font-family`. `font-size`, etc are + // properly inherited. However, `line-height` isn't inherited there. + line-height: inherit; +} + +input[type="radio"], +input[type="checkbox"] { + // Apply a disabled cursor for radios and checkboxes. + // + // Note: Neither radios nor checkboxes can be readonly. + &:disabled { + cursor: $cursor-disabled; + } +} + + +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + // Remove the default appearance of temporal inputs to avoid a Mobile Safari + // bug where setting a custom line-height prevents text from being vertically + // centered within the input. + // See https://bugs.webkit.org/show_bug.cgi?id=139848 + // and https://github.com/twbs/bootstrap/issues/11266 + -webkit-appearance: listbox; +} + +textarea { + // Textareas should really only resize vertically so they don't break their (horizontal) containers. + resize: vertical; +} + +fieldset { + // Browsers set a default `min-width: min-content;` on fieldsets, + // unlike e.g. `<div>`s, which have `min-width: 0;` by default. + // So we reset that to ensure fieldsets behave more like a standard block element. + // See https://github.com/twbs/bootstrap/issues/12359 + // and https://html.spec.whatwg.org/multipage/#the-fieldset-and-legend-elements + min-width: 0; + // Reset the default outline behavior of fieldsets so they don't affect page layout. + padding: 0; + margin: 0; + border: 0; +} + +legend { + // Reset the entire legend element to match the `fieldset` + display: block; + width: 100%; + padding: 0; + margin-bottom: .5rem; + font-size: 1.5rem; + line-height: inherit; +} + +input[type="search"] { + // This overrides the extra rounded corners on search inputs in iOS so that our + // `.form-control` class can properly style them. Note that this cannot simply + // be added to `.form-control` as it's not specific enough. For details, see + // https://github.com/twbs/bootstrap/issues/11586. + -webkit-appearance: none; +} + +// todo: needed? +output { + display: inline-block; +// font-size: $font-size-base; +// line-height: $line-height; +// color: $input-color; +} + +// Always hide an element with the `hidden` HTML attribute (from PureCSS). +[hidden] { + display: none !important; +} diff --git a/src/styles/bootstrap/_responsive-embed.scss b/src/styles/bootstrap/_responsive-embed.scss new file mode 100755 index 0000000000000000000000000000000000000000..d3362b6fdb19fff6c8d5998ceefdb41c7538eb5c --- /dev/null +++ b/src/styles/bootstrap/_responsive-embed.scss @@ -0,0 +1,52 @@ +// Credit: Nicolas Gallagher and SUIT CSS. + +.embed-responsive { + position: relative; + display: block; + width: 100%; + padding: 0; + overflow: hidden; + + &::before { + display: block; + content: ""; + } + + .embed-responsive-item, + iframe, + embed, + object, + video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; + } +} + +.embed-responsive-21by9 { + &::before { + padding-top: percentage(9 / 21); + } +} + +.embed-responsive-16by9 { + &::before { + padding-top: percentage(9 / 16); + } +} + +.embed-responsive-4by3 { + &::before { + padding-top: percentage(3 / 4); + } +} + +.embed-responsive-1by1 { + &::before { + padding-top: percentage(1 / 1); + } +} diff --git a/src/styles/bootstrap/_tables.scss b/src/styles/bootstrap/_tables.scss new file mode 100755 index 0000000000000000000000000000000000000000..47be2c508474848260aea0850105898310db31e0 --- /dev/null +++ b/src/styles/bootstrap/_tables.scss @@ -0,0 +1,153 @@ +// +// Basic Bootstrap table +// + +.table { + width: 100%; + max-width: 100%; + margin-bottom: $spacer; + + th, + td { + padding: $table-cell-padding; + vertical-align: top; + border-top: $table-border-width solid $table-border-color; + } + + thead th { + vertical-align: bottom; + border-bottom: (2 * $table-border-width) solid $table-border-color; + } + + tbody + tbody { + border-top: (2 * $table-border-width) solid $table-border-color; + } + + .table { + background-color: $body-bg; + } +} + + +// +// Condensed table w/ half padding +// + +.table-sm { + th, + td { + padding: $table-sm-cell-padding; + } +} + + +// Bordered version +// +// Add borders all around the table and between all the columns. + +.table-bordered { + border: $table-border-width solid $table-border-color; + + th, + td { + border: $table-border-width solid $table-border-color; + } + + thead { + th, + td { + border-bottom-width: (2 * $table-border-width); + } + } +} + + +// Zebra-striping +// +// Default zebra-stripe styles (alternating gray and transparent backgrounds) + +.table-striped { + tbody tr:nth-of-type(odd) { + background-color: $table-bg-accent; + } +} + + +// Hover effect +// +// Placed here since it has to come after the potential zebra striping + +.table-hover { + tbody tr { + @include hover { + background-color: $table-bg-hover; + } + } +} + + +// Table backgrounds +// +// Exact selectors below required to override `.table-striped` and prevent +// inheritance to nested tables. + +// Generate the contextual variants +@include table-row-variant(active, $table-bg-active); +@include table-row-variant(success, $state-success-bg); +@include table-row-variant(info, $state-info-bg); +@include table-row-variant(warning, $state-warning-bg); +@include table-row-variant(danger, $state-danger-bg); + + +// Inverse styles +// +// Same table markup, but inverted color scheme: dark background and light text. + +.thead-inverse { + th { + color: $table-inverse-color; + background-color: $table-inverse-bg; + } +} + +.thead-default { + th { + color: $table-head-color; + background-color: $table-head-bg; + } +} + +.table-inverse { + color: $table-inverse-color; + background-color: $table-inverse-bg; + + th, + td, + thead th { + border-color: $body-bg; + } + + &.table-bordered { + border: 0; + } +} + + + +// Responsive tables +// +// Add `.table-responsive` to `.table`s and we'll make them mobile friendly by +// enabling horizontal scrolling. Only applies <768px. Everything above that +// will display normally. + +.table-responsive { + display: block; + width: 100%; + overflow-x: auto; + -ms-overflow-style: -ms-autohiding-scrollbar; // See https://github.com/twbs/bootstrap/pull/10057 + + // Prevent double border on horizontal scroll due to use of `display: block;` + &.table-bordered { + border: 0; + } +} diff --git a/src/styles/bootstrap/_tooltip.scss b/src/styles/bootstrap/_tooltip.scss new file mode 100755 index 0000000000000000000000000000000000000000..24e198d464e2907d1817ddd0975b046652cbb92b --- /dev/null +++ b/src/styles/bootstrap/_tooltip.scss @@ -0,0 +1,90 @@ +// Base class +.tooltip { + position: absolute; + z-index: $zindex-tooltip; + display: block; + // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element. + // So reset our font and text properties to avoid inheriting weird values. + @include reset-text(); + font-size: $font-size-sm; + // Allow breaking very long words so they don't overflow the tooltip's bounds + word-wrap: break-word; + opacity: 0; + + &.show { opacity: $tooltip-opacity; } + + &.tooltip-top, + &.bs-tether-element-attached-bottom { + padding: $tooltip-arrow-width 0; + margin-top: -$tooltip-margin; + + .tooltip-inner::before { + bottom: 0; + left: 50%; + margin-left: -$tooltip-arrow-width; + content: ""; + border-width: $tooltip-arrow-width $tooltip-arrow-width 0; + border-top-color: $tooltip-arrow-color; + } + } + &.tooltip-right, + &.bs-tether-element-attached-left { + padding: 0 $tooltip-arrow-width; + margin-left: $tooltip-margin; + + .tooltip-inner::before { + top: 50%; + left: 0; + margin-top: -$tooltip-arrow-width; + content: ""; + border-width: $tooltip-arrow-width $tooltip-arrow-width $tooltip-arrow-width 0; + border-right-color: $tooltip-arrow-color; + } + } + &.tooltip-bottom, + &.bs-tether-element-attached-top { + padding: $tooltip-arrow-width 0; + margin-top: $tooltip-margin; + + .tooltip-inner::before { + top: 0; + left: 50%; + margin-left: -$tooltip-arrow-width; + content: ""; + border-width: 0 $tooltip-arrow-width $tooltip-arrow-width; + border-bottom-color: $tooltip-arrow-color; + } + } + &.tooltip-left, + &.bs-tether-element-attached-right { + padding: 0 $tooltip-arrow-width; + margin-left: -$tooltip-margin; + + .tooltip-inner::before { + top: 50%; + right: 0; + margin-top: -$tooltip-arrow-width; + content: ""; + border-width: $tooltip-arrow-width 0 $tooltip-arrow-width $tooltip-arrow-width; + border-left-color: $tooltip-arrow-color; + } + } +} + +// Wrapper for the tooltip content +.tooltip-inner { + max-width: $tooltip-max-width; + padding: $tooltip-padding-y $tooltip-padding-x; + color: $tooltip-color; + text-align: center; + background-color: $tooltip-bg; + @include border-radius($border-radius); + + &::before { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + } +} diff --git a/src/styles/bootstrap/_transitions.scss b/src/styles/bootstrap/_transitions.scss new file mode 100755 index 0000000000000000000000000000000000000000..86c04a5f8b20c4fc580bee9e60c227ae61846394 --- /dev/null +++ b/src/styles/bootstrap/_transitions.scss @@ -0,0 +1,34 @@ +.fade { + opacity: 0; + @include transition($transition-fade); + + &.show { + opacity: 1; + } +} + +.collapse { + display: none; + &.show { + display: block; + } +} + +tr { + &.collapse.show { + display: table-row; + } +} + +tbody { + &.collapse.show { + display: table-row-group; + } +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + @include transition($transition-collapse); +} diff --git a/src/styles/bootstrap/_type.scss b/src/styles/bootstrap/_type.scss new file mode 100755 index 0000000000000000000000000000000000000000..13a64b06f38a14123822fb4fae7cc128680dd8f7 --- /dev/null +++ b/src/styles/bootstrap/_type.scss @@ -0,0 +1,143 @@ +// +// Headings +// + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + margin-bottom: $headings-margin-bottom; + font-family: $headings-font-family; + font-weight: $headings-font-weight; + line-height: $headings-line-height; + color: $headings-color; +} + +h1, .h1 { font-size: $font-size-h1; } +h2, .h2 { font-size: $font-size-h2; } +h3, .h3 { font-size: $font-size-h3; } +h4, .h4 { font-size: $font-size-h4; } +h5, .h5 { font-size: $font-size-h5; } +h6, .h6 { font-size: $font-size-h6; } + +.lead { + font-size: $lead-font-size; + font-weight: $lead-font-weight; +} + +// Type display classes +.display-1 { + font-size: $display1-size; + font-weight: $display1-weight; + line-height: $display-line-height; +} +.display-2 { + font-size: $display2-size; + font-weight: $display2-weight; + line-height: $display-line-height; +} +.display-3 { + font-size: $display3-size; + font-weight: $display3-weight; + line-height: $display-line-height; +} +.display-4 { + font-size: $display4-size; + font-weight: $display4-weight; + line-height: $display-line-height; +} + + +// +// Horizontal rules +// + +hr { + margin-top: $spacer-y; + margin-bottom: $spacer-y; + border: 0; + border-top: $hr-border-width solid $hr-border-color; +} + + +// +// Emphasis +// + +small, +.small { + font-size: $small-font-size; + font-weight: $font-weight-normal; +} + +mark, +.mark { + padding: $mark-padding; + background-color: $mark-bg; +} + + +// +// Lists +// + +.list-unstyled { + @include list-unstyled; +} + +// Inline turns list items into inline-block +.list-inline { + @include list-unstyled; +} +.list-inline-item { + display: inline-block; + + &:not(:last-child) { + margin-right: $list-inline-padding; + } +} + + +// +// Misc +// + +// Builds on `abbr` +.initialism { + font-size: 90%; + text-transform: uppercase; +} + +// Blockquotes +.blockquote { + padding: ($spacer / 2) $spacer; + margin-bottom: $spacer; + font-size: $blockquote-font-size; + border-left: $blockquote-border-width solid $blockquote-border-color; +} + +.blockquote-footer { + display: block; + font-size: 80%; // back to default font-size + color: $blockquote-small-color; + + &::before { + content: "\2014 \00A0"; // em dash, nbsp + } +} + +// Opposite alignment of blockquote +.blockquote-reverse { + padding-right: $spacer; + padding-left: 0; + text-align: right; + border-right: $blockquote-border-width solid $blockquote-border-color; + border-left: 0; +} + +.blockquote-reverse .blockquote-footer { + &::before { + content: ""; + } + &::after { + content: "\00A0 \2014"; // nbsp, em dash + } +} diff --git a/src/styles/bootstrap/_utilities.scss b/src/styles/bootstrap/_utilities.scss new file mode 100755 index 0000000000000000000000000000000000000000..7d08ff2f8ca0c158ff8abaeaa99ab927c6453c72 --- /dev/null +++ b/src/styles/bootstrap/_utilities.scss @@ -0,0 +1,13 @@ +@import "utilities/align"; +@import "utilities/background"; +@import "utilities/borders"; +@import "utilities/clearfix"; +@import "utilities/display"; +@import "utilities/flex"; +@import "utilities/float"; +@import "utilities/position"; +@import "utilities/screenreaders"; +@import "utilities/sizing"; +@import "utilities/spacing"; +@import "utilities/text"; +@import "utilities/visibility"; diff --git a/src/styles/bootstrap/_variables.scss b/src/styles/bootstrap/_variables.scss new file mode 100755 index 0000000000000000000000000000000000000000..386f287a0535dfab680a130bae037437f1306fca --- /dev/null +++ b/src/styles/bootstrap/_variables.scss @@ -0,0 +1,962 @@ +@import url(https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,400,300,600); +// Variables +// +// Copy settings from this file into the provided `_custom.scss` to override +// the Bootstrap defaults without modifying key, versioned files. + + +// Table of Contents +// +// Colors +// Options +// Spacing +// Body +// Links +// Grid breakpoints +// Grid containers +// Grid columns +// Fonts +// Components +// Tables +// Buttons +// Forms +// Dropdowns +// Z-index master list +// Navbar +// Navs +// Pagination +// Jumbotron +// Form states and alerts +// Cards +// Tooltips +// Popovers +// Badges +// Modals +// Alerts +// Progress bars +// List group +// Image thumbnails +// Figures +// Breadcrumbs +// Carousel +// Close +// Code + +@mixin _assert-ascending($map, $map-name) { + $prev-key: null; + $prev-num: null; + @each $key, $num in $map { + @if $prev-num == null { + // Do nothing + } @else if not comparable($prev-num, $num) { + @warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !"; + } @else if $prev-num >= $num { + @warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !"; + } + $prev-key: $key; + $prev-num: $num; + } +} + +// Replace `$search` with `$replace` in `$string` +// @author Hugo Giraudel +// @param {String} $string - Initial string +// @param {String} $search - Substring to replace +// @param {String} $replace ('') - New value +// @return {String} - Updated string +@function str-replace($string, $search, $replace: "") { + $index: str-index($string, $search); + + @if $index { + @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); + } + + @return $string; +} + +@mixin _assert-starts-at-zero($map) { + $values: map-values($map); + $first-value: nth($values, 1); + @if $first-value != 0 { + @warn "First breakpoint in `$grid-breakpoints` must start at 0, but starts at #{$first-value}."; + } +} + + +// General variable structure +// +// Variable format should follow the `$component-modifier-state-property` order. + + +// Colors +// +// Grayscale and brand colors for use across Bootstrap. + +// Start with assigning color names to specific hex values. +$white: #fff !default; +$black: #000 !default; +$red: #d9534f !default; +$orange: #f0ad4e !default; +$yellow: #ffd500 !default; +$green: #5cb85c !default; +$blue: #0275d8 !default; +$teal: #5bc0de !default; +$pink: #ff5b77 !default; +$purple: #613d7c !default; + +// Create grayscale +$gray-dark: #292b2c !default; +$gray: #464a4c !default; +$gray-light: #636c72 !default; +$gray-lighter: #eceeef !default; +$gray-lightest: #f7f7f9 !default; + +// Reassign color vars to semantic color scheme +$brand-primary: $blue !default; +$brand-success: $green !default; +$brand-info: $teal !default; +$brand-warning: $orange !default; +$brand-danger: $red !default; +$brand-inverse: $gray-dark !default; + + +// Options +// +// Quickly modify global styling by enabling or disabling optional features. + +$enable-rounded: true !default; +$enable-shadows: false !default; +$enable-gradients: false !default; +$enable-transitions: true !default; +$enable-hover-media-query: false !default; +$enable-grid-classes: true !default; +$enable-print-styles: true !default; + + +// Spacing +// +// Control the default styling of most Bootstrap elements by modifying these +// variables. Mostly focused on spacing. +// You can add more entries to the $spacers map, should you need more variation. + +$spacer: 1rem !default; +$spacer-x: $spacer !default; +$spacer-y: $spacer !default; +$spacers: ( + 0: ( + x: 0, + y: 0 + ), + 1: ( + x: ($spacer-x * .25), + y: ($spacer-y * .25) + ), + 2: ( + x: ($spacer-x * .5), + y: ($spacer-y * .5) + ), + 3: ( + x: $spacer-x, + y: $spacer-y + ), + 4: ( + x: ($spacer-x * 1.5), + y: ($spacer-y * 1.5) + ), + 5: ( + x: ($spacer-x * 3), + y: ($spacer-y * 3) + ) +) !default; +$border-width: 1px !default; + +// This variable affects the `.h-*` and `.w-*` classes. +$sizes: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100% +) !default; + +// Body +// +// Settings for the `<body>` element. + +$body-bg: $white !default; +$body-color: $gray-dark !default; +$inverse-bg: $gray-dark !default; +$inverse-color: $gray-lighter !default; + + +// Links +// +// Style anchor elements. + +$link-color: $brand-primary !default; +$link-decoration: none !default; +$link-hover-color: darken($link-color, 15%) !default; +$link-hover-decoration: underline !default; + + +// Grid breakpoints +// +// Define the minimum dimensions at which your layout will change, +// adapting to different screen sizes, for use in media queries. + +$grid-breakpoints: ( + xs: 0, + sm: 576px, + md: 768px, + lg: 992px, + xl: 1200px +) !default; +@include _assert-ascending($grid-breakpoints, "$grid-breakpoints"); +@include _assert-starts-at-zero($grid-breakpoints); + + +// Grid containers +// +// Define the maximum width of `.container` for different screen sizes. + +$container-max-widths: ( + sm: 540px, + md: 720px, + lg: 960px, + xl: 1140px +) !default; +@include _assert-ascending($container-max-widths, "$container-max-widths"); + + +// Grid columns +// +// Set the number of columns and specify the width of the gutters. + +$grid-columns: 12 !default; +$grid-gutter-width-base: 15px !default; +$grid-gutter-widths: ( + xs: $grid-gutter-width-base, + sm: $grid-gutter-width-base, + md: $grid-gutter-width-base, + lg: $grid-gutter-width-base, + xl: $grid-gutter-width-base +) !default; + +// Fonts +// +// Font, line-height, and color for body text, headings, and more. + +$font-family-sans-serif: "Open Sans", -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !default; +$font-family-serif: "Open Sans", Georgia, "Times New Roman", Times, serif !default; +$font-family-monospace: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default; +$font-family-base: $font-family-sans-serif !default; + +$font-size-base: 1rem !default; // Assumes the browser default, typically `16px` +$font-size-lg: 1.25rem !default; +$font-size-sm: .875rem !default; +$font-size-xs: .75rem !default; + +$font-weight-normal: normal !default; +$font-weight-bold: bold !default; + +$font-weight-base: $font-weight-normal !default; +$line-height-base: 1.5 !default; + +$font-size-h1: 2.5rem !default; +$font-size-h2: 2rem !default; +$font-size-h3: 1.75rem !default; +$font-size-h4: 1.5rem !default; +$font-size-h5: 1.25rem !default; +$font-size-h6: 1rem !default; + +$headings-margin-bottom: ($spacer / 2) !default; +$headings-font-family: inherit !default; +$headings-font-weight: 500 !default; +$headings-line-height: 1.1 !default; +$headings-color: inherit !default; + +$display1-size: 6rem !default; +$display2-size: 5.5rem !default; +$display3-size: 4.5rem !default; +$display4-size: 3.5rem !default; + +$display1-weight: 300 !default; +$display2-weight: 300 !default; +$display3-weight: 300 !default; +$display4-weight: 300 !default; +$display-line-height: $headings-line-height !default; + +$lead-font-size: 1.25rem !default; +$lead-font-weight: 300 !default; + +$small-font-size: 80% !default; + +$text-muted: $gray-light !default; + +$abbr-border-color: $gray-light !default; + +$blockquote-small-color: $gray-light !default; +$blockquote-font-size: ($font-size-base * 1.25) !default; +$blockquote-border-color: $gray-lighter !default; +$blockquote-border-width: .25rem !default; + +$hr-border-color: rgba($black,.1) !default; +$hr-border-width: $border-width !default; + +$mark-padding: .2em !default; + +$dt-font-weight: $font-weight-bold !default; + +$kbd-box-shadow: inset 0 -.1rem 0 rgba($black,.25) !default; +$nested-kbd-font-weight: $font-weight-bold !default; + +$list-inline-padding: 5px !default; + + +// Components +// +// Define common padding and border radius sizes and more. + +$line-height-lg: (4 / 3) !default; +$line-height-sm: 1.5 !default; + +$border-radius: .25rem !default; +$border-radius-lg: .3rem !default; +$border-radius-sm: .2rem !default; + +$component-active-color: $white !default; +$component-active-bg: $brand-primary !default; + +$caret-width: .3em !default; + +$transition-base: all .2s ease-in-out !default; +$transition-fade: opacity .15s linear !default; +$transition-collapse: height .35s ease !default; + + +// Tables +// +// Customizes the `.table` component with basic values, each used across all table variations. + +$table-cell-padding: .75rem !default; +$table-sm-cell-padding: .3rem !default; + +$table-bg: transparent !default; + +$table-inverse-bg: $gray-dark !default; +$table-inverse-color: $body-bg !default; + +$table-bg-accent: rgba($black,.05) !default; +$table-bg-hover: rgba($black,.075) !default; +$table-bg-active: $table-bg-hover !default; + +$table-head-bg: $gray-lighter !default; +$table-head-color: $gray !default; + +$table-border-width: $border-width !default; +$table-border-color: $gray-lighter !default; + + +// Buttons +// +// For each of Bootstrap's buttons, define text, background and border color. + +$btn-padding-x: 1rem !default; +$btn-padding-y: .5rem !default; +$btn-line-height: 1.25 !default; +$btn-font-weight: $font-weight-normal !default; +$btn-box-shadow: inset 0 1px 0 rgba($white,.15), 0 1px 1px rgba($black,.075) !default; +$btn-focus-box-shadow: 0 0 0 2px rgba($brand-primary, .25) !default; +$btn-active-box-shadow: inset 0 3px 5px rgba($black,.125) !default; + +$btn-primary-color: $white !default; +$btn-primary-bg: $brand-primary !default; +$btn-primary-border: $btn-primary-bg !default; + +$btn-secondary-color: $gray-dark !default; +$btn-secondary-bg: $white !default; +$btn-secondary-border: #ccc !default; + +$btn-info-color: $white !default; +$btn-info-bg: $brand-info !default; +$btn-info-border: $btn-info-bg !default; + +$btn-success-color: $white !default; +$btn-success-bg: $brand-success !default; +$btn-success-border: $btn-success-bg !default; + +$btn-warning-color: $white !default; +$btn-warning-bg: $brand-warning !default; +$btn-warning-border: $btn-warning-bg !default; + +$btn-danger-color: $white !default; +$btn-danger-bg: $brand-danger !default; +$btn-danger-border: $btn-danger-bg !default; + +$btn-link-disabled-color: $gray-light !default; + +$btn-padding-x-sm: .5rem !default; +$btn-padding-y-sm: .25rem !default; + +$btn-padding-x-lg: 1.5rem !default; +$btn-padding-y-lg: .75rem !default; + +$btn-block-spacing-y: .5rem !default; +$btn-toolbar-margin: .5rem !default; + +// Allows for customizing button radius independently from global border radius +$btn-border-radius: $border-radius !default; +$btn-border-radius-lg: $border-radius-lg !default; +$btn-border-radius-sm: $border-radius-sm !default; + +$btn-transition: all .2s ease-in-out !default; + + +// Forms + +$input-padding-x: .75rem !default; +$input-padding-y: .5rem !default; +$input-line-height: 1.25 !default; + +$input-bg: $white !default; +$input-bg-disabled: $gray-lighter !default; + +$input-color: $gray !default; +$input-border-color: rgba($black,.15) !default; +$input-btn-border-width: $border-width !default; // For form controls and buttons +$input-box-shadow: inset 0 1px 1px rgba($black,.075) !default; + +$input-border-radius: $border-radius !default; +$input-border-radius-lg: $border-radius-lg !default; +$input-border-radius-sm: $border-radius-sm !default; + +$input-bg-focus: $input-bg !default; +$input-border-focus: lighten($brand-primary, 25%) !default; +$input-box-shadow-focus: $input-box-shadow, rgba($input-border-focus, .6) !default; +$input-color-focus: $input-color !default; + +$input-color-placeholder: $gray-light !default; + +$input-padding-x-sm: .5rem !default; +$input-padding-y-sm: .25rem !default; + +$input-padding-x-lg: 1.5rem !default; +$input-padding-y-lg: .75rem !default; + +$input-height: (($font-size-base * $input-line-height) + ($input-padding-y * 2)) !default; +$input-height-lg: (($font-size-lg * $line-height-lg) + ($input-padding-y-lg * 2)) !default; +$input-height-sm: (($font-size-sm * $line-height-sm) + ($input-padding-y-sm * 2)) !default; + +$input-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s !default; + +$form-text-margin-top: .25rem !default; +$form-feedback-margin-top: $form-text-margin-top !default; + +$form-check-margin-bottom: .5rem !default; +$form-check-input-gutter: 1.25rem !default; +$form-check-input-margin-y: .25rem !default; +$form-check-input-margin-x: .25rem !default; + +$form-check-inline-margin-x: .75rem !default; + +$form-group-margin-bottom: $spacer-y !default; + +$input-group-addon-bg: $gray-lighter !default; +$input-group-addon-border-color: $input-border-color !default; + +$cursor-disabled: not-allowed !default; + +$custom-control-gutter: 1.5rem !default; +$custom-control-spacer-x: 1rem !default; +$custom-control-spacer-y: .25rem !default; + +$custom-control-indicator-size: 1rem !default; +$custom-control-indicator-margin-y: (($line-height-base * 1rem) - $custom-control-indicator-size) / -2 !default; +$custom-control-indicator-bg: #ddd !default; +$custom-control-indicator-bg-size: 50% 50% !default; +$custom-control-indicator-box-shadow: inset 0 .25rem .25rem rgba($black,.1) !default; + +$custom-control-disabled-cursor: $cursor-disabled !default; +$custom-control-disabled-indicator-bg: $gray-lighter !default; +$custom-control-disabled-description-color: $gray-light !default; + +$custom-control-checked-indicator-color: $white !default; +$custom-control-checked-indicator-bg: $brand-primary !default; +$custom-control-checked-indicator-box-shadow: none !default; + +$custom-control-focus-indicator-box-shadow: 0 0 0 1px $body-bg, 0 0 0 3px $brand-primary !default; + +$custom-control-active-indicator-color: $white !default; +$custom-control-active-indicator-bg: lighten($brand-primary, 35%) !default; +$custom-control-active-indicator-box-shadow: none !default; + +$custom-checkbox-radius: $border-radius !default; +$custom-checkbox-checked-icon: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#{$custom-control-checked-indicator-color}' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"), "#", "%23") !default; + +$custom-checkbox-indeterminate-bg: $brand-primary !default; +$custom-checkbox-indeterminate-indicator-color: $custom-control-checked-indicator-color !default; +$custom-checkbox-indeterminate-icon: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='#{$custom-checkbox-indeterminate-indicator-color}' d='M0 2h4'/%3E%3C/svg%3E"), "#", "%23") !default; +$custom-checkbox-indeterminate-box-shadow: none !default; + +$custom-radio-radius: 50% !default; +$custom-radio-checked-icon: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='#{$custom-control-checked-indicator-color}'/%3E%3C/svg%3E"), "#", "%23") !default; + +$custom-select-padding-x: .75rem !default; +$custom-select-padding-y: .375rem !default; +$custom-select-indicator-padding: 1rem !default; // Extra padding to account for the presence of the background-image based indicator +$custom-select-line-height: $input-line-height !default; +$custom-select-color: $input-color !default; +$custom-select-disabled-color: $gray-light !default; +$custom-select-bg: $white !default; +$custom-select-disabled-bg: $gray-lighter !default; +$custom-select-bg-size: 8px 10px !default; // In pixels because image dimensions +$custom-select-indicator-color: #333 !default; +$custom-select-indicator: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='#{$custom-select-indicator-color}' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E"), "#", "%23") !default; +$custom-select-border-width: $input-btn-border-width !default; +$custom-select-border-color: $input-border-color !default; +$custom-select-border-radius: $border-radius !default; + +$custom-select-focus-border-color: lighten($brand-primary, 25%) !default; +$custom-select-focus-box-shadow: inset 0 1px 2px rgba($black, .075), 0 0 5px rgba($custom-select-focus-border-color, .5) !default; + +$custom-select-sm-padding-y: .2rem !default; +$custom-select-sm-font-size: 75% !default; + +$custom-file-height: 2.5rem !default; +$custom-file-width: 14rem !default; +$custom-file-focus-box-shadow: 0 0 0 .075rem $white, 0 0 0 .2rem $brand-primary !default; + +$custom-file-padding-x: .5rem !default; +$custom-file-padding-y: 1rem !default; +$custom-file-line-height: 1.5 !default; +$custom-file-color: $gray !default; +$custom-file-bg: $white !default; +$custom-file-border-width: $border-width !default; +$custom-file-border-color: $input-border-color !default; +$custom-file-border-radius: $border-radius !default; +$custom-file-box-shadow: inset 0 .2rem .4rem rgba($black,.05) !default; +$custom-file-button-color: $custom-file-color !default; +$custom-file-button-bg: $gray-lighter !default; +$custom-file-text: ( + placeholder: ( + en: "Choose file..." + ), + button-label: ( + en: "Browse" + ) +) !default; + + +// Form validation icons +$form-icon-success-color: $brand-success !default; +$form-icon-success: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#{$form-icon-success-color}' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E"), "#", "%23") !default; + +$form-icon-warning-color: $brand-warning !default; +$form-icon-warning: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#{$form-icon-warning-color}' d='M4.4 5.324h-.8v-2.46h.8zm0 1.42h-.8V5.89h.8zM3.76.63L.04 7.075c-.115.2.016.425.26.426h7.397c.242 0 .372-.226.258-.426C6.726 4.924 5.47 2.79 4.253.63c-.113-.174-.39-.174-.494 0z'/%3E%3C/svg%3E"), "#", "%23") !default; + +$form-icon-danger-color: $brand-danger !default; +$form-icon-danger: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$form-icon-danger-color}' viewBox='-2 -2 7 7'%3E%3Cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3E%3Ccircle r='.5'/%3E%3Ccircle cx='3' r='.5'/%3E%3Ccircle cy='3' r='.5'/%3E%3Ccircle cx='3' cy='3' r='.5'/%3E%3C/svg%3E"), "#", "%23") !default; + + +// Dropdowns +// +// Dropdown menu container and contents. + +$dropdown-min-width: 10rem !default; +$dropdown-padding-y: .5rem !default; +$dropdown-margin-top: .125rem !default; +$dropdown-bg: $white !default; +$dropdown-border-color: rgba($black,.15) !default; +$dropdown-border-width: $border-width !default; +$dropdown-divider-bg: $gray-lighter !default; +$dropdown-box-shadow: 0 .5rem 1rem rgba($black,.175) !default; + +$dropdown-link-color: $gray-dark !default; +$dropdown-link-hover-color: darken($gray-dark, 5%) !default; +$dropdown-link-hover-bg: $gray-lightest !default; + +$dropdown-link-active-color: $component-active-color !default; +$dropdown-link-active-bg: $component-active-bg !default; + +$dropdown-link-disabled-color: $gray-light !default; + +$dropdown-item-padding-x: 1.5rem !default; + +$dropdown-header-color: $gray-light !default; + + +// Z-index master list +// +// Warning: Avoid customizing these values. They're used for a bird's eye view +// of components dependent on the z-axis and are designed to all work together. + +$zindex-dropdown-backdrop: 990 !default; +$zindex-navbar: 1000 !default; +$zindex-dropdown: 1000 !default; +$zindex-fixed: 1030 !default; +$zindex-sticky: 1030 !default; +$zindex-modal-backdrop: 1040 !default; +$zindex-modal: 1050 !default; +$zindex-popover: 1060 !default; +$zindex-tooltip: 1070 !default; + + +// Navbar + +$navbar-border-radius: $border-radius !default; +$navbar-padding-x: $spacer !default; +$navbar-padding-y: ($spacer / 2) !default; + +$navbar-brand-padding-y: .25rem !default; + +$navbar-toggler-padding-x: .75rem !default; +$navbar-toggler-padding-y: .25rem !default; +$navbar-toggler-font-size: $font-size-lg !default; +$navbar-toggler-border-radius: $btn-border-radius !default; + +$navbar-inverse-color: rgba($white,.5) !default; +$navbar-inverse-hover-color: rgba($white,.75) !default; +$navbar-inverse-active-color: rgba($white,1) !default; +$navbar-inverse-disabled-color: rgba($white,.25) !default; +$navbar-inverse-toggler-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-inverse-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E"), "#", "%23") !default; +$navbar-inverse-toggler-border: rgba($white,.1) !default; + +$navbar-light-color: rgba($black,.5) !default; +$navbar-light-hover-color: rgba($black,.7) !default; +$navbar-light-active-color: rgba($black,.9) !default; +$navbar-light-disabled-color: rgba($black,.3) !default; +$navbar-light-toggler-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-light-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E"), "#", "%23") !default; +$navbar-light-toggler-border: rgba($black,.1) !default; + +// Navs + +$nav-item-margin: .2rem !default; +$nav-item-inline-spacer: 1rem !default; +$nav-link-padding: .5em 1em !default; +$nav-link-hover-bg: $gray-lighter !default; +$nav-disabled-link-color: $gray-light !default; + +$nav-tabs-border-color: #ddd !default; +$nav-tabs-border-width: $border-width !default; +$nav-tabs-border-radius: $border-radius !default; +$nav-tabs-link-hover-border-color: $gray-lighter !default; +$nav-tabs-active-link-hover-color: $gray !default; +$nav-tabs-active-link-hover-bg: $body-bg !default; +$nav-tabs-active-link-hover-border-color: #ddd !default; +$nav-tabs-justified-link-border-color: #ddd !default; +$nav-tabs-justified-active-link-border-color: $body-bg !default; + +$nav-pills-border-radius: $border-radius !default; +$nav-pills-active-link-color: $component-active-color !default; +$nav-pills-active-link-bg: $component-active-bg !default; + + +// Pagination + +$pagination-padding-x: .75rem !default; +$pagination-padding-y: .5rem !default; +$pagination-padding-x-sm: .5rem !default; +$pagination-padding-y-sm: .25rem !default; +$pagination-padding-x-lg: 1.5rem !default; +$pagination-padding-y-lg: .75rem !default; +$pagination-line-height: 1.25 !default; + +$pagination-color: $link-color !default; +$pagination-bg: $white !default; +$pagination-border-width: $border-width !default; +$pagination-border-color: #ddd !default; + +$pagination-hover-color: $link-hover-color !default; +$pagination-hover-bg: $gray-lighter !default; +$pagination-hover-border: #ddd !default; + +$pagination-active-color: $white !default; +$pagination-active-bg: $brand-primary !default; +$pagination-active-border: $brand-primary !default; + +$pagination-disabled-color: $gray-light !default; +$pagination-disabled-bg: $white !default; +$pagination-disabled-border: #ddd !default; + + +// Jumbotron + +$jumbotron-padding: 2rem !default; +$jumbotron-bg: $gray-lighter !default; + + +// Form states and alerts +// +// Define colors for form feedback states and, by default, alerts. + +$state-success-text: #FFFFFF !default; +$state-success-bg: $brand-success !default; +$state-success-border: darken($state-success-bg, 5%) !default; + +$state-info-text: #FFFFFF !default; +$state-info-bg: $brand-info !default; +$state-info-border: darken($state-info-bg, 7%) !default; + +$state-warning-text: #FFFFFF !default; +$state-warning-bg: $brand-warning !default; +$mark-bg: $state-warning-bg !default; +$state-warning-border: darken($state-warning-bg, 5%) !default; + +$state-danger-text: #FFFFFF !default; +$state-danger-bg: $brand-danger !default; +$state-danger-border: darken($state-danger-bg, 5%) !default; + + +// Cards + +$card-spacer-x: 1.25rem !default; +$card-spacer-y: .75rem !default; +$card-border-width: 1px !default; +$card-border-radius: $border-radius !default; +$card-border-color: rgba($black,.125) !default; +$card-border-radius-inner: calc(#{$card-border-radius} - #{$card-border-width}) !default; +$card-cap-bg: $gray-lightest !default; +$card-bg: $white !default; + +$card-link-hover-color: $white !default; + +$card-img-overlay-padding: 1.25rem !default; + +$card-deck-margin: ($grid-gutter-width-base / 2) !default; + +$card-columns-count: 3 !default; +$card-columns-gap: 1.25rem !default; +$card-columns-margin: $card-spacer-y !default; + + +// Tooltips + +$tooltip-max-width: 200px !default; +$tooltip-color: $white !default; +$tooltip-bg: $black !default; +$tooltip-opacity: .9 !default; +$tooltip-padding-y: 3px !default; +$tooltip-padding-x: 8px !default; +$tooltip-margin: 3px !default; + +$tooltip-arrow-width: 5px !default; +$tooltip-arrow-color: $tooltip-bg !default; + + +// Popovers + +$popover-inner-padding: 1px !default; +$popover-bg: $white !default; +$popover-max-width: 276px !default; +$popover-border-width: $border-width !default; +$popover-border-color: rgba($black,.2) !default; +$popover-box-shadow: 0 5px 10px rgba($black,.2) !default; + +$popover-title-bg: darken($popover-bg, 3%) !default; +$popover-title-padding-x: 14px !default; +$popover-title-padding-y: 8px !default; + +$popover-content-padding-x: 14px !default; +$popover-content-padding-y: 9px !default; + +$popover-arrow-width: 10px !default; +$popover-arrow-color: $popover-bg !default; + +$popover-arrow-outer-width: ($popover-arrow-width + 1px) !default; +$popover-arrow-outer-color: fade-in($popover-border-color, .05) !default; + + +// Badges + +$badge-default-bg: $gray-light !default; +$badge-primary-bg: $brand-primary !default; +$badge-success-bg: $brand-success !default; +$badge-info-bg: $brand-info !default; +$badge-warning-bg: $brand-warning !default; +$badge-danger-bg: $brand-danger !default; + +$badge-color: $white !default; +$badge-link-hover-color: $white !default; +$badge-font-size: 75% !default; +$badge-font-weight: $font-weight-bold !default; +$badge-padding-x: .4em !default; +$badge-padding-y: .25em !default; + +$badge-pill-padding-x: .6em !default; +// Use a higher than normal value to ensure completely rounded edges when +// customizing padding or font-size on labels. +$badge-pill-border-radius: 10rem !default; + + +// Modals + +// Padding applied to the modal body +$modal-inner-padding: 15px !default; + +$modal-dialog-margin: 10px !default; +$modal-dialog-sm-up-margin-y: 30px !default; + +$modal-title-line-height: $line-height-base !default; + +$modal-content-bg: $white !default; +$modal-content-border-color: rgba($black,.2) !default; +$modal-content-border-width: $border-width !default; +$modal-content-xs-box-shadow: 0 3px 9px rgba($black,.5) !default; +$modal-content-sm-up-box-shadow: 0 5px 15px rgba($black,.5) !default; + +$modal-backdrop-bg: $black !default; +$modal-backdrop-opacity: .5 !default; +$modal-header-border-color: $gray-lighter !default; +$modal-footer-border-color: $modal-header-border-color !default; +$modal-header-border-width: $modal-content-border-width !default; +$modal-footer-border-width: $modal-header-border-width !default; +$modal-header-padding: 15px !default; + +$modal-lg: 800px !default; +$modal-md: 500px !default; +$modal-sm: 300px !default; + +$modal-transition: transform .3s ease-out !default; + + +// Alerts +// +// Define alert colors, border radius, and padding. + +$alert-padding-x: 1.25rem !default; +$alert-padding-y: .75rem !default; +$alert-margin-bottom: $spacer-y !default; +$alert-border-radius: $border-radius !default; +$alert-link-font-weight: $font-weight-bold !default; +$alert-border-width: $border-width !default; + +$alert-success-bg: $state-success-bg !default; +$alert-success-text: $state-success-text !default; +$alert-success-border: $state-success-border !default; + +$alert-info-bg: $state-info-bg !default; +$alert-info-text: $state-info-text !default; +$alert-info-border: $state-info-border !default; + +$alert-warning-bg: $state-warning-bg !default; +$alert-warning-text: $state-warning-text !default; +$alert-warning-border: $state-warning-border !default; + +$alert-danger-bg: $state-danger-bg !default; +$alert-danger-text: $state-danger-text !default; +$alert-danger-border: $state-danger-border !default; + + +// Progress bars + +$progress-height: 1rem !default; +$progress-font-size: .75rem !default; +$progress-bg: $gray-lighter !default; +$progress-border-radius: $border-radius !default; +$progress-box-shadow: inset 0 .1rem .1rem rgba($black,.1) !default; +$progress-bar-color: $white !default; +$progress-bar-bg: $brand-primary !default; +$progress-bar-animation-timing: 1s linear infinite !default; + +// List group + +$list-group-color: $body-color !default; +$list-group-bg: $white !default; +$list-group-border-color: rgba($black,.125) !default; +$list-group-border-width: $border-width !default; +$list-group-border-radius: $border-radius !default; + +$list-group-item-padding-x: 1.25rem !default; +$list-group-item-padding-y: .75rem !default; + +$list-group-hover-bg: $gray-lightest !default; +$list-group-active-color: $component-active-color !default; +$list-group-active-bg: $component-active-bg !default; +$list-group-active-border: $list-group-active-bg !default; +$list-group-active-text-color: lighten($list-group-active-bg, 50%) !default; + +$list-group-disabled-color: $gray-light !default; +$list-group-disabled-bg: $list-group-bg !default; +$list-group-disabled-text-color: $list-group-disabled-color !default; + +$list-group-link-color: $gray !default; +$list-group-link-heading-color: $gray-dark !default; +$list-group-link-hover-color: $list-group-link-color !default; + +$list-group-link-active-color: $list-group-color !default; +$list-group-link-active-bg: $gray-lighter !default; + + +// Image thumbnails + +$thumbnail-padding: .25rem !default; +$thumbnail-bg: $body-bg !default; +$thumbnail-border-width: $border-width !default; +$thumbnail-border-color: #ddd !default; +$thumbnail-border-radius: $border-radius !default; +$thumbnail-box-shadow: 0 1px 2px rgba($black,.075) !default; +$thumbnail-transition: all .2s ease-in-out !default; + + +// Figures + +$figure-caption-font-size: 90% !default; +$figure-caption-color: $gray-light !default; + + +// Breadcrumbs + +$breadcrumb-padding-y: .75rem !default; +$breadcrumb-padding-x: 1rem !default; +$breadcrumb-item-padding: .5rem !default; + +$breadcrumb-bg: $gray-lighter !default; +$breadcrumb-divider-color: $gray-light !default; +$breadcrumb-active-color: $gray-light !default; +$breadcrumb-divider: "/" !default; + + +// Carousel + +$carousel-control-color: $white !default; +$carousel-control-width: 15% !default; +$carousel-control-opacity: .5 !default; + +$carousel-indicator-width: 30px !default; +$carousel-indicator-height: 3px !default; +$carousel-indicator-spacer: 3px !default; +$carousel-indicator-active-bg: $white !default; + +$carousel-caption-width: 70% !default; +$carousel-caption-color: $white !default; + +$carousel-control-icon-width: 20px !default; + +$carousel-control-prev-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M4 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"), "#", "%23") !default; +$carousel-control-next-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M1.5 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"), "#", "%23") !default; + +$carousel-transition: transform .6s ease-in-out !default; + + +// Close + +$close-font-size: $font-size-base * 1.5 !default; +$close-font-weight: $font-weight-bold !default; +$close-color: $black !default; +$close-text-shadow: 0 1px 0 $white !default; + + +// Code + +$code-font-size: 90% !default; +$code-padding-x: .4rem !default; +$code-padding-y: .2rem !default; +$code-color: #bd4147 !default; +$code-bg: $gray-lightest !default; + +$kbd-color: $white !default; +$kbd-bg: $gray-dark !default; + +$pre-bg: $gray-lightest !default; +$pre-color: $gray-dark !default; +$pre-border-color: #ccc !default; +$pre-scrollable-max-height: 340px !default; diff --git a/src/styles/bootstrap/bootstrap-grid.scss b/src/styles/bootstrap/bootstrap-grid.scss new file mode 100755 index 0000000000000000000000000000000000000000..182b9626b60d4224ba190800e53eb1fe8d12bea9 --- /dev/null +++ b/src/styles/bootstrap/bootstrap-grid.scss @@ -0,0 +1,43 @@ +// Bootstrap Grid only +// +// Includes relevant variables and mixins for the flexbox grid +// system, as well as the generated predefined classes (e.g., `.col-sm-4`). + +// +// Box sizing, responsive, and more +// + +@at-root { + @-ms-viewport { width: device-width; } +} + +html { + box-sizing: border-box; + -ms-overflow-style: scrollbar; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + + +// +// Variables +// + +@import "variables"; + +// +// Grid mixins +// + +@import "mixins/clearfix"; +@import "mixins/breakpoints"; +@import "mixins/grid-framework"; +@import "mixins/grid"; + +@import "custom"; + +@import "grid"; diff --git a/src/styles/bootstrap/bootstrap-reboot.scss b/src/styles/bootstrap/bootstrap-reboot.scss new file mode 100755 index 0000000000000000000000000000000000000000..978b086a1a28d9ed2a6241e57349f3ba5a9dde45 --- /dev/null +++ b/src/styles/bootstrap/bootstrap-reboot.scss @@ -0,0 +1,10 @@ +// Bootstrap Reboot only +// +// Includes only Normalize and our custom Reboot reset. + +@import "variables"; +@import "mixins"; +@import "custom"; + +@import "normalize"; +@import "reboot"; diff --git a/src/styles/bootstrap/bootstrap.scss b/src/styles/bootstrap/bootstrap.scss new file mode 100755 index 0000000000000000000000000000000000000000..88a60cafa0b9cf25580bee8bc74dadb0e9d6b236 --- /dev/null +++ b/src/styles/bootstrap/bootstrap.scss @@ -0,0 +1,54 @@ +/*! + * Bootstrap v4.0.0-alpha.6 (https://getbootstrap.com) + * Copyright 2011-2017 The Bootstrap Authors + * Copyright 2011-2017 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +// Core variables and mixins +@import "variables"; +@import "mixins"; +@import "custom"; + +// Reset and dependencies +@import "normalize"; +@import "print"; + +// Core CSS +@import "reboot"; +@import "type"; +@import "images"; +@import "code"; +@import "grid"; +@import "tables"; +@import "forms"; +@import "buttons"; + +// Components +@import "transitions"; +@import "dropdown"; +@import "button-group"; +@import "input-group"; +@import "custom-forms"; +@import "nav"; +@import "navbar"; +@import "card"; +@import "breadcrumb"; +@import "pagination"; +@import "badge"; +@import "jumbotron"; +@import "alert"; +@import "progress"; +@import "media"; +@import "list-group"; +@import "responsive-embed"; +@import "close"; + +// Components w/ JavaScript +@import "modal"; +@import "tooltip"; +@import "popover"; +@import "carousel"; + +// Utility classes +@import "utilities"; diff --git a/src/styles/bootstrap/mixins/_alert.scss b/src/styles/bootstrap/mixins/_alert.scss new file mode 100755 index 0000000000000000000000000000000000000000..6ed3a81ab1d015aaaf2074fe6d028fdb88b105a4 --- /dev/null +++ b/src/styles/bootstrap/mixins/_alert.scss @@ -0,0 +1,14 @@ +// Alerts + +@mixin alert-variant($background, $border, $body-color) { + background-color: $background; + border-color: $border; + color: $body-color; + + hr { + border-top-color: darken($border, 5%); + } + .alert-link { + color: darken($body-color, 10%); + } +} diff --git a/src/styles/bootstrap/mixins/_background-variant.scss b/src/styles/bootstrap/mixins/_background-variant.scss new file mode 100755 index 0000000000000000000000000000000000000000..54a734dcc82f5a0226df8c09bd03c4a9381d245e --- /dev/null +++ b/src/styles/bootstrap/mixins/_background-variant.scss @@ -0,0 +1,12 @@ +// Contextual backgrounds + +@mixin bg-variant($parent, $color) { + #{$parent} { + background-color: $color !important; + } + a#{$parent} { + @include hover-focus { + background-color: darken($color, 10%) !important; + } + } +} diff --git a/src/styles/bootstrap/mixins/_badge.scss b/src/styles/bootstrap/mixins/_badge.scss new file mode 100755 index 0000000000000000000000000000000000000000..9fa44b647813b390ac3c71ac0e7e41bb135f7544 --- /dev/null +++ b/src/styles/bootstrap/mixins/_badge.scss @@ -0,0 +1,11 @@ +// Badges + +@mixin badge-variant($color) { + background-color: $color; + + &[href] { + @include hover-focus { + background-color: darken($color, 10%); + } + } +} diff --git a/src/styles/bootstrap/mixins/_border-radius.scss b/src/styles/bootstrap/mixins/_border-radius.scss new file mode 100755 index 0000000000000000000000000000000000000000..54f29f41da40170873dca54b089a60a41e123f98 --- /dev/null +++ b/src/styles/bootstrap/mixins/_border-radius.scss @@ -0,0 +1,35 @@ +// Single side border-radius + +@mixin border-radius($radius: $border-radius) { + @if $enable-rounded { + border-radius: $radius; + } +} + +@mixin border-top-radius($radius) { + @if $enable-rounded { + border-top-right-radius: $radius; + border-top-left-radius: $radius; + } +} + +@mixin border-right-radius($radius) { + @if $enable-rounded { + border-bottom-right-radius: $radius; + border-top-right-radius: $radius; + } +} + +@mixin border-bottom-radius($radius) { + @if $enable-rounded { + border-bottom-right-radius: $radius; + border-bottom-left-radius: $radius; + } +} + +@mixin border-left-radius($radius) { + @if $enable-rounded { + border-bottom-left-radius: $radius; + border-top-left-radius: $radius; + } +} diff --git a/src/styles/bootstrap/mixins/_breakpoints.scss b/src/styles/bootstrap/mixins/_breakpoints.scss new file mode 100755 index 0000000000000000000000000000000000000000..6fd2e8e1e86f1cdf3aed7c381af68a072a679f94 --- /dev/null +++ b/src/styles/bootstrap/mixins/_breakpoints.scss @@ -0,0 +1,95 @@ +// Breakpoint viewport sizes and media queries. +// +// Breakpoints are defined as a map of (name: minimum width), order from small to large: +// +// (xs: 0, sm: 576px, md: 768px) +// +// The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default. + +// Name of the next breakpoint, or null for the last breakpoint. +// +// >> breakpoint-next(sm) +// md +// >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px)) +// md +// >> breakpoint-next(sm, $breakpoint-names: (xs sm md)) +// md +@function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) { + $n: index($breakpoint-names, $name); + @return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null); +} + +// Minimum breakpoint width. Null for the smallest (first) breakpoint. +// +// >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px)) +// 576px +@function breakpoint-min($name, $breakpoints: $grid-breakpoints) { + $min: map-get($breakpoints, $name); + @return if($min != 0, $min, null); +} + +// Maximum breakpoint width. Null for the largest (last) breakpoint. +// The maximum value is calculated as the minimum of the next one less 0.1. +// +// >> breakpoint-max(sm, (xs: 0, sm: 576px, md: 768px)) +// 767px +@function breakpoint-max($name, $breakpoints: $grid-breakpoints) { + $next: breakpoint-next($name, $breakpoints); + @return if($next, breakpoint-min($next, $breakpoints) - 1px, null); +} + +// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash infront. +// Useful for making responsive utilities. +// +// >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px)) +// "" (Returns a blank string) +// >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px)) +// "-sm" +@function breakpoint-infix($name, $breakpoints: $grid-breakpoints) { + @return if(breakpoint-min($name, $breakpoints) == null, "", "-#{$name}"); +} + +// Media of at least the minimum breakpoint width. No query for the smallest breakpoint. +// Makes the @content apply to the given breakpoint and wider. +@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) { + $min: breakpoint-min($name, $breakpoints); + @if $min { + @media (min-width: $min) { + @content; + } + } @else { + @content; + } +} + +// Media of at most the maximum breakpoint width. No query for the largest breakpoint. +// Makes the @content apply to the given breakpoint and narrower. +@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) { + $max: breakpoint-max($name, $breakpoints); + @if $max { + @media (max-width: $max) { + @content; + } + } @else { + @content; + } +} + +// Media that spans multiple breakpoint widths. +// Makes the @content apply between the min and max breakpoints +@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) { + @include media-breakpoint-up($lower, $breakpoints) { + @include media-breakpoint-down($upper, $breakpoints) { + @content; + } + } +} + +// Media between the breakpoint's minimum and maximum widths. +// No minimum for the smallest breakpoint, and no maximum for the largest one. +// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower. +@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) { + @include media-breakpoint-between($name, $name, $breakpoints) { + @content; + } +} diff --git a/src/styles/bootstrap/mixins/_buttons.scss b/src/styles/bootstrap/mixins/_buttons.scss new file mode 100755 index 0000000000000000000000000000000000000000..f9981e326ae932142f3d1ec614ec1272349957b3 --- /dev/null +++ b/src/styles/bootstrap/mixins/_buttons.scss @@ -0,0 +1,86 @@ +// Button variants +// +// Easily pump out default styles, as well as :hover, :focus, :active, +// and disabled options for all buttons + +@mixin button-variant($color, $background, $border) { + $active-background: darken($background, 10%); + $active-border: darken($border, 12%); + + color: $color; + background-color: $background; + border-color: $border; + @include box-shadow($btn-box-shadow); + + // Hover and focus styles are shared + @include hover { + color: $color; + background-color: $active-background; + border-color: $active-border; + } + &:focus, + &.focus { + // Avoid using mixin so we can pass custom focus shadow properly + @if $enable-shadows { + box-shadow: $btn-box-shadow, 0 0 0 2px rgba($border, .5); + } @else { + box-shadow: 0 0 0 2px rgba($border, .5); + } + } + + // Disabled comes first so active can properly restyle + &.disabled, + &:disabled { + background-color: $background; + border-color: $border; + } + + &:active, + &.active, + .show > &.dropdown-toggle { + color: $color; + background-color: $active-background; + background-image: none; // Remove the gradient for the pressed/active state + border-color: $active-border; + @include box-shadow($btn-active-box-shadow); + } +} + +@mixin button-outline-variant($color, $color-hover: #fff) { + color: $color; + background-image: none; + background-color: transparent; + border-color: $color; + + @include hover { + color: $color-hover; + background-color: $color; + border-color: $color; + } + + &:focus, + &.focus { + box-shadow: 0 0 0 2px rgba($color, .5); + } + + &.disabled, + &:disabled { + color: $color; + background-color: transparent; + } + + &:active, + &.active, + .show > &.dropdown-toggle { + color: $color-hover; + background-color: $color; + border-color: $color; + } +} + +// Button sizes +@mixin button-size($padding-y, $padding-x, $font-size, $border-radius) { + padding: $padding-y $padding-x; + font-size: $font-size; + @include border-radius($border-radius); +} diff --git a/src/styles/bootstrap/mixins/_cards.scss b/src/styles/bootstrap/mixins/_cards.scss new file mode 100755 index 0000000000000000000000000000000000000000..4b1232d8b20094db228d7da58353f4bf2b9cb639 --- /dev/null +++ b/src/styles/bootstrap/mixins/_cards.scss @@ -0,0 +1,47 @@ +// Card variants + +@mixin card-variant($background, $border) { + background-color: $background; + border-color: $border; + + .card-header, + .card-footer { + background-color: transparent; + } +} + +@mixin card-outline-variant($color) { + background-color: transparent; + border-color: $color; +} + +// +// Inverse text within a card for use with dark backgrounds +// + +@mixin card-inverse { + color: rgba(255,255,255,.65); + + .card-header, + .card-footer { + background-color: transparent; + border-color: rgba(255,255,255,.2); + } + .card-header, + .card-footer, + .card-title, + .card-blockquote { + color: #fff; + } + .card-link, + .card-text, + .card-subtitle, + .card-blockquote .blockquote-footer { + color: rgba(255,255,255,.65); + } + .card-link { + @include hover-focus { + color: $card-link-hover-color; + } + } +} diff --git a/src/styles/bootstrap/mixins/_clearfix.scss b/src/styles/bootstrap/mixins/_clearfix.scss new file mode 100755 index 0000000000000000000000000000000000000000..b72cf27128aaf88939ffdc18d1a8ab643b3c8f3f --- /dev/null +++ b/src/styles/bootstrap/mixins/_clearfix.scss @@ -0,0 +1,7 @@ +@mixin clearfix() { + &::after { + display: block; + content: ""; + clear: both; + } +} diff --git a/src/styles/bootstrap/mixins/_float.scss b/src/styles/bootstrap/mixins/_float.scss new file mode 100755 index 0000000000000000000000000000000000000000..b43116fa6cb6693cfe093c3b8e5ded326d3a318c --- /dev/null +++ b/src/styles/bootstrap/mixins/_float.scss @@ -0,0 +1,9 @@ +@mixin float-left { + float: left !important; +} +@mixin float-right { + float: right !important; +} +@mixin float-none { + float: none !important; +} diff --git a/src/styles/bootstrap/mixins/_forms.scss b/src/styles/bootstrap/mixins/_forms.scss new file mode 100755 index 0000000000000000000000000000000000000000..c8aea9669d821edec49e233bc9d4fb97dbb0440a --- /dev/null +++ b/src/styles/bootstrap/mixins/_forms.scss @@ -0,0 +1,79 @@ +// Form validation states +// +// Used in _forms.scss to generate the form validation CSS for warnings, errors, +// and successes. + +@mixin form-control-validation($color) { + // Color the label and help text + .form-control-feedback, + .form-control-label, + .col-form-label, + .form-check-label, + .custom-control { + color: $color; + } + + // Set the border and box shadow on specific inputs to match + .form-control { + border-color: $color; + + &:focus { + @include box-shadow($input-box-shadow, 0 0 6px lighten($color, 20%)); + } + } + + // Set validation states also for addons + .input-group-addon { + color: $color; + border-color: $color; + background-color: lighten($color, 40%); + } +} + +// Form control focus state +// +// Generate a customized focus state and for any input with the specified color, +// which defaults to the `@input-border-focus` variable. +// +// We highly encourage you to not customize the default value, but instead use +// this to tweak colors on an as-needed basis. This aesthetic change is based on +// WebKit's default styles, but applicable to a wider range of browsers. Its +// usability and accessibility should be taken into account with any change. +// +// Example usage: change the default blue border and shadow to white for better +// contrast against a dark gray background. +@mixin form-control-focus() { + &:focus { + color: $input-color-focus; + background-color: $input-bg-focus; + border-color: $input-border-focus; + outline: none; + @include box-shadow($input-box-shadow-focus); + } +} + +// Form control sizing +// +// Relative text size, padding, and border-radii changes for form controls. For +// horizontal sizing, wrap controls in the predefined grid classes. `<select>` +// element gets special love because it's special, and that's a fact! + +@mixin input-size($parent, $input-height, $padding-y, $padding-x, $font-size, $line-height, $border-radius) { + #{$parent} { + height: $input-height; + padding: $padding-y $padding-x; + font-size: $font-size; + line-height: $line-height; + @include border-radius($border-radius); + } + + select#{$parent} { + height: $input-height; + line-height: $input-height; + } + + textarea#{$parent}, + select[multiple]#{$parent} { + height: auto; + } +} diff --git a/src/styles/bootstrap/mixins/_gradients.scss b/src/styles/bootstrap/mixins/_gradients.scss new file mode 100755 index 0000000000000000000000000000000000000000..8bfd97c4d8de8a8b19c24a727335177275689cd5 --- /dev/null +++ b/src/styles/bootstrap/mixins/_gradients.scss @@ -0,0 +1,37 @@ +// Gradients + +// Horizontal gradient, from left to right +// +// Creates two color stops, start and end, by specifying a color and position for each color stop. +@mixin gradient-x($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) { + background-image: linear-gradient(to right, $start-color $start-percent, $end-color $end-percent); + background-repeat: repeat-x; +} + +// Vertical gradient, from top to bottom +// +// Creates two color stops, start and end, by specifying a color and position for each color stop. +@mixin gradient-y($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) { + background-image: linear-gradient(to bottom, $start-color $start-percent, $end-color $end-percent); + background-repeat: repeat-x; +} + +@mixin gradient-directional($start-color: #555, $end-color: #333, $deg: 45deg) { + background-repeat: repeat-x; + background-image: linear-gradient($deg, $start-color, $end-color); +} +@mixin gradient-x-three-colors($start-color: #00b3ee, $mid-color: #7a43b6, $color-stop: 50%, $end-color: #c3325f) { + background-image: linear-gradient(to right, $start-color, $mid-color $color-stop, $end-color); + background-repeat: no-repeat; +} +@mixin gradient-y-three-colors($start-color: #00b3ee, $mid-color: #7a43b6, $color-stop: 50%, $end-color: #c3325f) { + background-image: linear-gradient($start-color, $mid-color $color-stop, $end-color); + background-repeat: no-repeat; +} +@mixin gradient-radial($inner-color: #555, $outer-color: #333) { + background-image: radial-gradient(circle, $inner-color, $outer-color); + background-repeat: no-repeat; +} +@mixin gradient-striped($color: rgba(255,255,255,.15), $angle: 45deg) { + background-image: linear-gradient($angle, $color 25%, transparent 25%, transparent 50%, $color 50%, $color 75%, transparent 75%, transparent); +} diff --git a/src/styles/bootstrap/mixins/_grid-framework.scss b/src/styles/bootstrap/mixins/_grid-framework.scss new file mode 100755 index 0000000000000000000000000000000000000000..0aa814ab2d89a95166b367db58eaca0968bb5bcf --- /dev/null +++ b/src/styles/bootstrap/mixins/_grid-framework.scss @@ -0,0 +1,65 @@ +// Framework grid generation +// +// Used only by Bootstrap to generate the correct number of grid classes given +// any value of `$grid-columns`. + +@mixin make-grid-columns($columns: $grid-columns, $gutters: $grid-gutter-widths, $breakpoints: $grid-breakpoints) { + // Common properties for all breakpoints + %grid-column { + position: relative; + width: 100%; + min-height: 1px; // Prevent columns from collapsing when empty + + @include make-gutters($gutters); + } + + @each $breakpoint in map-keys($breakpoints) { + $infix: breakpoint-infix($breakpoint, $breakpoints); + + // Allow columns to stretch full width below their breakpoints + @for $i from 1 through $columns { + .col#{$infix}-#{$i} { + @extend %grid-column; + } + } + .col#{$infix} { + @extend %grid-column; + } + + @include media-breakpoint-up($breakpoint, $breakpoints) { + // Provide basic `.col-{bp}` classes for equal-width flexbox columns + .col#{$infix} { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; + } + .col#{$infix}-auto { + flex: 0 0 auto; + width: auto; + } + + @for $i from 1 through $columns { + .col#{$infix}-#{$i} { + @include make-col($i, $columns); + } + } + + @each $modifier in (pull, push) { + @for $i from 0 through $columns { + .#{$modifier}#{$infix}-#{$i} { + @include make-col-modifier($modifier, $i, $columns) + } + } + } + + // `$columns - 1` because offsetting by the width of an entire row isn't possible + @for $i from 0 through ($columns - 1) { + @if not ($infix == "" and $i == 0) { // Avoid emitting useless .offset-xs-0 + .offset#{$infix}-#{$i} { + @include make-col-modifier(offset, $i, $columns) + } + } + } + } + } +} diff --git a/src/styles/bootstrap/mixins/_grid.scss b/src/styles/bootstrap/mixins/_grid.scss new file mode 100755 index 0000000000000000000000000000000000000000..9cd8c7bbbbd2df3307ee7925dc463d3e798661db --- /dev/null +++ b/src/styles/bootstrap/mixins/_grid.scss @@ -0,0 +1,100 @@ +/// Grid system +// +// Generate semantic grid columns with these mixins. + +@mixin make-container($gutters: $grid-gutter-widths) { + position: relative; + margin-left: auto; + margin-right: auto; + + @each $breakpoint in map-keys($gutters) { + @include media-breakpoint-up($breakpoint) { + $gutter: map-get($gutters, $breakpoint); + padding-right: ($gutter / 2); + padding-left: ($gutter / 2); + } + } +} + + +// For each breakpoint, define the maximum width of the container in a media query +@mixin make-container-max-widths($max-widths: $container-max-widths, $breakpoints: $grid-breakpoints) { + @each $breakpoint, $container-max-width in $max-widths { + @include media-breakpoint-up($breakpoint, $breakpoints) { + width: $container-max-width; + max-width: 100%; + } + } +} + +@mixin make-gutters($gutters: $grid-gutter-widths) { + @each $breakpoint in map-keys($gutters) { + @include media-breakpoint-up($breakpoint) { + $gutter: map-get($gutters, $breakpoint); + padding-right: ($gutter / 2); + padding-left: ($gutter / 2); + } + } +} + +@mixin make-row($gutters: $grid-gutter-widths) { + display: flex; + flex-wrap: wrap; + + @each $breakpoint in map-keys($gutters) { + @include media-breakpoint-up($breakpoint) { + $gutter: map-get($gutters, $breakpoint); + margin-right: ($gutter / -2); + margin-left: ($gutter / -2); + } + } +} + +@mixin make-col-ready($gutters: $grid-gutter-widths) { + position: relative; + // Prevent columns from becoming too narrow when at smaller grid tiers by + // always setting `width: 100%;`. This works because we use `flex` values + // later on to override this initial width. + width: 100%; + min-height: 1px; // Prevent collapsing + + @each $breakpoint in map-keys($gutters) { + @include media-breakpoint-up($breakpoint) { + $gutter: map-get($gutters, $breakpoint); + padding-right: ($gutter / 2); + padding-left: ($gutter / 2); + } + } +} + +@mixin make-col($size, $columns: $grid-columns) { + flex: 0 0 percentage($size / $columns); + // width: percentage($size / $columns); + // Add a `max-width` to ensure content within each column does not blow out + // the width of the column. Applies to IE10+ and Firefox. Chrome and Safari + // do not appear to require this. + max-width: percentage($size / $columns); +} + +@mixin make-col-offset($size, $columns: $grid-columns) { + margin-left: percentage($size / $columns); +} + +@mixin make-col-push($size, $columns: $grid-columns) { + left: if($size > 0, percentage($size / $columns), auto); +} + +@mixin make-col-pull($size, $columns: $grid-columns) { + right: if($size > 0, percentage($size / $columns), auto); +} + +@mixin make-col-modifier($type, $size, $columns) { + // Work around the lack of dynamic mixin @include support (https://github.com/sass/sass/issues/626) + @if $type == push { + @include make-col-push($size, $columns); + } @else if $type == pull { + @include make-col-pull($size, $columns); + } @else if $type == offset { + @include make-col-offset($size, $columns); + } +} diff --git a/src/styles/bootstrap/mixins/_hover.scss b/src/styles/bootstrap/mixins/_hover.scss new file mode 100755 index 0000000000000000000000000000000000000000..6dd55e705a6e177d1c62c4dcdf02d2d61f33d54a --- /dev/null +++ b/src/styles/bootstrap/mixins/_hover.scss @@ -0,0 +1,60 @@ +@mixin hover { + // TODO: re-enable along with mq4-hover-shim +// @if $enable-hover-media-query { +// // See Media Queries Level 4: https://drafts.csswg.org/mediaqueries/#hover +// // Currently shimmed by https://github.com/twbs/mq4-hover-shim +// @media (hover: hover) { +// &:hover { @content } +// } +// } +// @else { + &:hover { @content } +// } +} + +@mixin hover-focus { + @if $enable-hover-media-query { + &:focus { @content } + @include hover { @content } + } + @else { + &:focus, + &:hover { + @content + } + } +} + +@mixin plain-hover-focus { + @if $enable-hover-media-query { + &, + &:focus { + @content + } + @include hover { @content } + } + @else { + &, + &:focus, + &:hover { + @content + } + } +} + +@mixin hover-focus-active { + @if $enable-hover-media-query { + &:focus, + &:active { + @content + } + @include hover { @content } + } + @else { + &:focus, + &:active, + &:hover { + @content + } + } +} diff --git a/src/styles/bootstrap/mixins/_image.scss b/src/styles/bootstrap/mixins/_image.scss new file mode 100755 index 0000000000000000000000000000000000000000..c2b45f2ceadf974db39e4b4b6d3e592785bffb05 --- /dev/null +++ b/src/styles/bootstrap/mixins/_image.scss @@ -0,0 +1,36 @@ +// Image Mixins +// - Responsive image +// - Retina image + + +// Responsive image +// +// Keep images from scaling beyond the width of their parents. + +@mixin img-fluid { + // Part 1: Set a maximum relative to the parent + max-width: 100%; + // Part 2: Override the height to auto, otherwise images will be stretched + // when setting a width and height attribute on the img element. + height: auto; +} + + +// Retina image +// +// Short retina mixin for setting background-image and -size. + +@mixin img-retina($file-1x, $file-2x, $width-1x, $height-1x) { + background-image: url($file-1x); + + // Autoprefixer takes care of adding -webkit-min-device-pixel-ratio and -o-min-device-pixel-ratio, + // but doesn't convert dppx=>dpi. + // There's no such thing as unprefixed min-device-pixel-ratio since it's nonstandard. + // Compatibility info: http://caniuse.com/#feat=css-media-resolution + @media + only screen and (min-resolution: 192dpi), // IE9-11 don't support dppx + only screen and (min-resolution: 2dppx) { // Standardized + background-image: url($file-2x); + background-size: $width-1x $height-1x; + } +} diff --git a/src/styles/bootstrap/mixins/_list-group.scss b/src/styles/bootstrap/mixins/_list-group.scss new file mode 100755 index 0000000000000000000000000000000000000000..3db5b096a421072587ca56dd21108ae37a415145 --- /dev/null +++ b/src/styles/bootstrap/mixins/_list-group.scss @@ -0,0 +1,28 @@ +// List Groups + +@mixin list-group-item-variant($state, $background, $color) { + .list-group-item-#{$state} { + color: $color; + background-color: $background; + } + + a.list-group-item-#{$state}, + button.list-group-item-#{$state} { + color: $color; + + .list-group-item-heading { + color: inherit; + } + + @include hover-focus { + color: $color; + background-color: darken($background, 5%); + } + + &.active { + color: #fff; + background-color: $color; + border-color: $color; + } + } +} diff --git a/src/styles/bootstrap/mixins/_lists.scss b/src/styles/bootstrap/mixins/_lists.scss new file mode 100755 index 0000000000000000000000000000000000000000..25185626698393b1365199f93aadd8d3350dc9d5 --- /dev/null +++ b/src/styles/bootstrap/mixins/_lists.scss @@ -0,0 +1,7 @@ +// Lists + +// Unstyled keeps list items block level, just removes default browser padding and list-style +@mixin list-unstyled { + padding-left: 0; + list-style: none; +} diff --git a/src/styles/bootstrap/mixins/_nav-divider.scss b/src/styles/bootstrap/mixins/_nav-divider.scss new file mode 100755 index 0000000000000000000000000000000000000000..fb3d12e9f69253bb53e35d69688c6cbf6cc153ac --- /dev/null +++ b/src/styles/bootstrap/mixins/_nav-divider.scss @@ -0,0 +1,10 @@ +// Horizontal dividers +// +// Dividers (basically an hr) within dropdowns and nav lists + +@mixin nav-divider($color: #e5e5e5) { + height: 1px; + margin: ($spacer-y / 2) 0; + overflow: hidden; + background-color: $color; +} diff --git a/src/styles/bootstrap/mixins/_navbar-align.scss b/src/styles/bootstrap/mixins/_navbar-align.scss new file mode 100755 index 0000000000000000000000000000000000000000..c454a4ffe9a477bcee78b107eb83ce9c375a8e75 --- /dev/null +++ b/src/styles/bootstrap/mixins/_navbar-align.scss @@ -0,0 +1,9 @@ +// Navbar vertical align +// +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin. + +// @mixin navbar-vertical-align($element-height) { +// margin-top: (($navbar-height - $element-height) / 2); +// margin-bottom: (($navbar-height - $element-height) / 2); +// } diff --git a/src/styles/bootstrap/mixins/_pagination.scss b/src/styles/bootstrap/mixins/_pagination.scss new file mode 100755 index 0000000000000000000000000000000000000000..8cd9317cf5226e2f35d19cefa637feb73cfe4a8b --- /dev/null +++ b/src/styles/bootstrap/mixins/_pagination.scss @@ -0,0 +1,21 @@ +// Pagination + +@mixin pagination-size($padding-y, $padding-x, $font-size, $line-height, $border-radius) { + .page-link { + padding: $padding-y $padding-x; + font-size: $font-size; + } + + .page-item { + &:first-child { + .page-link { + @include border-left-radius($border-radius); + } + } + &:last-child { + .page-link { + @include border-right-radius($border-radius); + } + } + } +} diff --git a/src/styles/bootstrap/mixins/_reset-text.scss b/src/styles/bootstrap/mixins/_reset-text.scss new file mode 100755 index 0000000000000000000000000000000000000000..b952730977bde7abbfc2b33eb07abf63d6456d50 --- /dev/null +++ b/src/styles/bootstrap/mixins/_reset-text.scss @@ -0,0 +1,17 @@ +@mixin reset-text { + font-family: $font-family-base; + // We deliberately do NOT reset font-size or word-wrap. + font-style: normal; + font-weight: $font-weight-normal; + letter-spacing: normal; + line-break: auto; + line-height: $line-height-base; + text-align: left; // Fallback for where `start` is not supported + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; +} diff --git a/src/styles/bootstrap/mixins/_resize.scss b/src/styles/bootstrap/mixins/_resize.scss new file mode 100755 index 0000000000000000000000000000000000000000..83fa6379179cba67dbd3b3fb1b1d167380f361d4 --- /dev/null +++ b/src/styles/bootstrap/mixins/_resize.scss @@ -0,0 +1,6 @@ +// Resize anything + +@mixin resizable($direction) { + resize: $direction; // Options: horizontal, vertical, both + overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible` +} diff --git a/src/styles/bootstrap/mixins/_screen-reader.scss b/src/styles/bootstrap/mixins/_screen-reader.scss new file mode 100755 index 0000000000000000000000000000000000000000..c208583249e27836ace927941cd07db5216497f6 --- /dev/null +++ b/src/styles/bootstrap/mixins/_screen-reader.scss @@ -0,0 +1,32 @@ +// Only display content to screen readers +// +// See: http://a11yproject.com/posts/how-to-hide-content + +@mixin sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// +// Useful for "Skip to main content" links; see https://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// +// Credit: HTML5 Boilerplate + +@mixin sr-only-focusable { + &:active, + &:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } +} diff --git a/src/styles/bootstrap/mixins/_size.scss b/src/styles/bootstrap/mixins/_size.scss new file mode 100755 index 0000000000000000000000000000000000000000..b9dd48e8dfdacc6104c2cf360ea30bb65970a32b --- /dev/null +++ b/src/styles/bootstrap/mixins/_size.scss @@ -0,0 +1,6 @@ +// Sizing shortcuts + +@mixin size($width, $height: $width) { + width: $width; + height: $height; +} diff --git a/src/styles/bootstrap/mixins/_table-row.scss b/src/styles/bootstrap/mixins/_table-row.scss new file mode 100755 index 0000000000000000000000000000000000000000..84f1d305aaf5287dbdf5baea2ff561ff57e425ec --- /dev/null +++ b/src/styles/bootstrap/mixins/_table-row.scss @@ -0,0 +1,30 @@ +// Tables + +@mixin table-row-variant($state, $background) { + // Exact selectors below required to override `.table-striped` and prevent + // inheritance to nested tables. + .table-#{$state} { + &, + > th, + > td { + background-color: $background; + } + } + + // Hover states for `.table-hover` + // Note: this is not available for cells or rows within `thead` or `tfoot`. + .table-hover { + $hover-background: darken($background, 5%); + + .table-#{$state} { + @include hover { + background-color: $hover-background; + + > td, + > th { + background-color: $hover-background; + } + } + } + } +} diff --git a/src/styles/bootstrap/mixins/_text-emphasis.scss b/src/styles/bootstrap/mixins/_text-emphasis.scss new file mode 100755 index 0000000000000000000000000000000000000000..9cd4b6a4f008acb58dcfd415893df420edf6efd2 --- /dev/null +++ b/src/styles/bootstrap/mixins/_text-emphasis.scss @@ -0,0 +1,12 @@ +// Typography + +@mixin text-emphasis-variant($parent, $color) { + #{$parent} { + color: $color !important; + } + a#{$parent} { + @include hover-focus { + color: darken($color, 10%) !important; + } + } +} diff --git a/src/styles/bootstrap/mixins/_text-hide.scss b/src/styles/bootstrap/mixins/_text-hide.scss new file mode 100755 index 0000000000000000000000000000000000000000..52a38a906989ed0e4e7b19933c36a73209956056 --- /dev/null +++ b/src/styles/bootstrap/mixins/_text-hide.scss @@ -0,0 +1,8 @@ +// CSS image replacement +@mixin text-hide() { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} diff --git a/src/styles/bootstrap/mixins/_text-truncate.scss b/src/styles/bootstrap/mixins/_text-truncate.scss new file mode 100755 index 0000000000000000000000000000000000000000..5a40bf533a9281f5ef015fdadc4a98ce169d9c2e --- /dev/null +++ b/src/styles/bootstrap/mixins/_text-truncate.scss @@ -0,0 +1,8 @@ +// Text truncate +// Requires inline-block or block for proper styling + +@mixin text-truncate() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} \ No newline at end of file diff --git a/src/styles/bootstrap/mixins/_transforms.scss b/src/styles/bootstrap/mixins/_transforms.scss new file mode 100755 index 0000000000000000000000000000000000000000..4005c9d028d3d8f3fcc94d5fa3d1cc6192bb2829 --- /dev/null +++ b/src/styles/bootstrap/mixins/_transforms.scss @@ -0,0 +1,14 @@ +// Applies the given styles only when the browser support CSS3 3D transforms. +@mixin if-supports-3d-transforms() { + @media (-webkit-transform-3d) { + // Old Safari, Old Android + // http://caniuse.com/#feat=css-featurequeries + // https://developer.mozilla.org/en-US/docs/Web/CSS/@media/-webkit-transform-3d + @content; + } + + @supports (transform: translate3d(0,0,0)) { + // The Proper Way: Using a CSS feature query + @content; + } +} diff --git a/src/styles/bootstrap/mixins/_visibility.scss b/src/styles/bootstrap/mixins/_visibility.scss new file mode 100755 index 0000000000000000000000000000000000000000..88c50b05d5ced1df322e324996e2d30928dba85b --- /dev/null +++ b/src/styles/bootstrap/mixins/_visibility.scss @@ -0,0 +1,5 @@ +// Visibility + +@mixin invisible { + visibility: hidden !important; +} diff --git a/src/styles/bootstrap/utilities/_align.scss b/src/styles/bootstrap/utilities/_align.scss new file mode 100755 index 0000000000000000000000000000000000000000..4dbbbc2dbc47ed53d177ad1bc78d0c06605663e9 --- /dev/null +++ b/src/styles/bootstrap/utilities/_align.scss @@ -0,0 +1,6 @@ +.align-baseline { vertical-align: baseline !important; } // Browser default +.align-top { vertical-align: top !important; } +.align-middle { vertical-align: middle !important; } +.align-bottom { vertical-align: bottom !important; } +.align-text-bottom { vertical-align: text-bottom !important; } +.align-text-top { vertical-align: text-top !important; } diff --git a/src/styles/bootstrap/utilities/_background.scss b/src/styles/bootstrap/utilities/_background.scss new file mode 100755 index 0000000000000000000000000000000000000000..b9ac295231cfa4cffb6a8ce7d30cce429c829c69 --- /dev/null +++ b/src/styles/bootstrap/utilities/_background.scss @@ -0,0 +1,19 @@ +// +// Contextual backgrounds +// + +.bg-faded { + background-color: darken($body-bg, 3%); +} + +@include bg-variant('.bg-primary', $brand-primary); + +@include bg-variant('.bg-success', $brand-success); + +@include bg-variant('.bg-info', $brand-info); + +@include bg-variant('.bg-warning', $brand-warning); + +@include bg-variant('.bg-danger', $brand-danger); + +@include bg-variant('.bg-inverse', $brand-inverse); diff --git a/src/styles/bootstrap/utilities/_borders.scss b/src/styles/bootstrap/utilities/_borders.scss new file mode 100755 index 0000000000000000000000000000000000000000..b256881e5ece38fcd323bc67665b9221b254e069 --- /dev/null +++ b/src/styles/bootstrap/utilities/_borders.scss @@ -0,0 +1,37 @@ +// +// Border +// + +.border-0 { border: 0 !important; } +.border-top-0 { border-top: 0 !important; } +.border-right-0 { border-right: 0 !important; } +.border-bottom-0 { border-bottom: 0 !important; } +.border-left-0 { border-left: 0 !important; } + +// +// Border-radius +// + +.rounded { + @include border-radius($border-radius); +} +.rounded-top { + @include border-top-radius($border-radius); +} +.rounded-right { + @include border-right-radius($border-radius); +} +.rounded-bottom { + @include border-bottom-radius($border-radius); +} +.rounded-left { + @include border-left-radius($border-radius); +} + +.rounded-circle { + border-radius: 50%; +} + +.rounded-0 { + border-radius: 0; +} diff --git a/src/styles/bootstrap/utilities/_clearfix.scss b/src/styles/bootstrap/utilities/_clearfix.scss new file mode 100755 index 0000000000000000000000000000000000000000..e92522a94d82a571b84ac1de470bcb70b176023c --- /dev/null +++ b/src/styles/bootstrap/utilities/_clearfix.scss @@ -0,0 +1,3 @@ +.clearfix { + @include clearfix(); +} diff --git a/src/styles/bootstrap/utilities/_display.scss b/src/styles/bootstrap/utilities/_display.scss new file mode 100755 index 0000000000000000000000000000000000000000..ae942a6fb97d71d4e274ecf3b7f79966a03d3eba --- /dev/null +++ b/src/styles/bootstrap/utilities/_display.scss @@ -0,0 +1,18 @@ +// +// Display utilities +// + +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + .d#{$infix}-none { display: none !important; } + .d#{$infix}-inline { display: inline !important; } + .d#{$infix}-inline-block { display: inline-block !important; } + .d#{$infix}-block { display: block !important; } + .d#{$infix}-table { display: table !important; } + .d#{$infix}-table-cell { display: table-cell !important; } + .d#{$infix}-flex { display: flex !important; } + .d#{$infix}-inline-flex { display: inline-flex !important; } + } +} diff --git a/src/styles/bootstrap/utilities/_flex.scss b/src/styles/bootstrap/utilities/_flex.scss new file mode 100755 index 0000000000000000000000000000000000000000..1b98aaa3fa8bcbd15dd458276d0c2015b5ac833e --- /dev/null +++ b/src/styles/bootstrap/utilities/_flex.scss @@ -0,0 +1,48 @@ +// Flex variation +// +// Custom styles for additional flex alignment options. + +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + .flex#{$infix}-first { order: -1; } + .flex#{$infix}-last { order: 1; } + .flex#{$infix}-unordered { order: 0; } + + .flex#{$infix}-row { flex-direction: row !important; } + .flex#{$infix}-column { flex-direction: column !important; } + .flex#{$infix}-row-reverse { flex-direction: row-reverse !important; } + .flex#{$infix}-column-reverse { flex-direction: column-reverse !important; } + + .flex#{$infix}-wrap { flex-wrap: wrap !important; } + .flex#{$infix}-nowrap { flex-wrap: nowrap !important; } + .flex#{$infix}-wrap-reverse { flex-wrap: wrap-reverse !important; } + + .justify-content#{$infix}-start { justify-content: flex-start !important; } + .justify-content#{$infix}-end { justify-content: flex-end !important; } + .justify-content#{$infix}-center { justify-content: center !important; } + .justify-content#{$infix}-between { justify-content: space-between !important; } + .justify-content#{$infix}-around { justify-content: space-around !important; } + + .align-items#{$infix}-start { align-items: flex-start !important; } + .align-items#{$infix}-end { align-items: flex-end !important; } + .align-items#{$infix}-center { align-items: center !important; } + .align-items#{$infix}-baseline { align-items: baseline !important; } + .align-items#{$infix}-stretch { align-items: stretch !important; } + + .align-content#{$infix}-start { align-content: flex-start !important; } + .align-content#{$infix}-end { align-content: flex-end !important; } + .align-content#{$infix}-center { align-content: center !important; } + .align-content#{$infix}-between { align-content: space-between !important; } + .align-content#{$infix}-around { align-content: space-around !important; } + .align-content#{$infix}-stretch { align-content: stretch !important; } + + .align-self#{$infix}-auto { align-self: auto !important; } + .align-self#{$infix}-start { align-self: flex-start !important; } + .align-self#{$infix}-end { align-self: flex-end !important; } + .align-self#{$infix}-center { align-self: center !important; } + .align-self#{$infix}-baseline { align-self: baseline !important; } + .align-self#{$infix}-stretch { align-self: stretch !important; } + } +} diff --git a/src/styles/bootstrap/utilities/_float.scss b/src/styles/bootstrap/utilities/_float.scss new file mode 100755 index 0000000000000000000000000000000000000000..01655e9a5212731038b8a99633a8a7ed73ecf6a1 --- /dev/null +++ b/src/styles/bootstrap/utilities/_float.scss @@ -0,0 +1,9 @@ +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + .float#{$infix}-left { @include float-left; } + .float#{$infix}-right { @include float-right; } + .float#{$infix}-none { @include float-none; } + } +} diff --git a/src/styles/bootstrap/utilities/_position.scss b/src/styles/bootstrap/utilities/_position.scss new file mode 100755 index 0000000000000000000000000000000000000000..2cf08bfa013d9a9bdd4c0a852ba2f380e68ccd78 --- /dev/null +++ b/src/styles/bootstrap/utilities/_position.scss @@ -0,0 +1,23 @@ +// Positioning + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: $zindex-fixed; +} + +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: $zindex-fixed; +} + +.sticky-top { + position: sticky; + top: 0; + z-index: $zindex-sticky; +} diff --git a/src/styles/bootstrap/utilities/_screenreaders.scss b/src/styles/bootstrap/utilities/_screenreaders.scss new file mode 100755 index 0000000000000000000000000000000000000000..9f26fde03538350e750a76cf2415a62f960976af --- /dev/null +++ b/src/styles/bootstrap/utilities/_screenreaders.scss @@ -0,0 +1,11 @@ +// +// Screenreaders +// + +.sr-only { + @include sr-only(); +} + +.sr-only-focusable { + @include sr-only-focusable(); +} diff --git a/src/styles/bootstrap/utilities/_sizing.scss b/src/styles/bootstrap/utilities/_sizing.scss new file mode 100755 index 0000000000000000000000000000000000000000..a7dc3e49b8e1c9f280ae7815c4ae02ef63dbacf7 --- /dev/null +++ b/src/styles/bootstrap/utilities/_sizing.scss @@ -0,0 +1,10 @@ +// Width and height + +@each $prop, $abbrev in (width: w, height: h) { + @each $size, $length in $sizes { + .#{$abbrev}-#{$size} { #{$prop}: $length !important; } + } +} + +.mw-100 { max-width: 100% !important; } +.mh-100 { max-height: 100% !important; } diff --git a/src/styles/bootstrap/utilities/_spacing.scss b/src/styles/bootstrap/utilities/_spacing.scss new file mode 100755 index 0000000000000000000000000000000000000000..6056e2b7e27fbdb401a61832b32499dc352c62b3 --- /dev/null +++ b/src/styles/bootstrap/utilities/_spacing.scss @@ -0,0 +1,43 @@ +// Margin and Padding + +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + @each $prop, $abbrev in (margin: m, padding: p) { + @each $size, $lengths in $spacers { + $length-x: map-get($lengths, x); + $length-y: map-get($lengths, y); + + .#{$abbrev}#{$infix}-#{$size} { #{$prop}: $length-y $length-x !important; } + .#{$abbrev}t#{$infix}-#{$size} { #{$prop}-top: $length-y !important; } + .#{$abbrev}r#{$infix}-#{$size} { #{$prop}-right: $length-x !important; } + .#{$abbrev}b#{$infix}-#{$size} { #{$prop}-bottom: $length-y !important; } + .#{$abbrev}l#{$infix}-#{$size} { #{$prop}-left: $length-x !important; } + .#{$abbrev}x#{$infix}-#{$size} { + #{$prop}-right: $length-x !important; + #{$prop}-left: $length-x !important; + } + .#{$abbrev}y#{$infix}-#{$size} { + #{$prop}-top: $length-y !important; + #{$prop}-bottom: $length-y !important; + } + } + } + + // Some special margin utils + .m#{$infix}-auto { margin: auto !important; } + .mt#{$infix}-auto { margin-top: auto !important; } + .mr#{$infix}-auto { margin-right: auto !important; } + .mb#{$infix}-auto { margin-bottom: auto !important; } + .ml#{$infix}-auto { margin-left: auto !important; } + .mx#{$infix}-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my#{$infix}-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + } +} diff --git a/src/styles/bootstrap/utilities/_text.scss b/src/styles/bootstrap/utilities/_text.scss new file mode 100755 index 0000000000000000000000000000000000000000..4ac90533acb9376b3a31016fb70c56803f7ef199 --- /dev/null +++ b/src/styles/bootstrap/utilities/_text.scss @@ -0,0 +1,61 @@ +// +// Text +// + +// Alignment + +.text-justify { text-align: justify !important; } +.text-nowrap { white-space: nowrap !important; } +.text-truncate { @include text-truncate; } + +// Responsive alignment + +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + .text#{$infix}-left { text-align: left !important; } + .text#{$infix}-right { text-align: right !important; } + .text#{$infix}-center { text-align: center !important; } + } +} + +// Transformation + +.text-lowercase { text-transform: lowercase !important; } +.text-uppercase { text-transform: uppercase !important; } +.text-capitalize { text-transform: capitalize !important; } + +// Weight and italics + +.font-weight-normal { font-weight: $font-weight-normal; } +.font-weight-bold { font-weight: $font-weight-bold; } +.font-italic { font-style: italic; } + +// Contextual colors + +.text-white { + color: #fff !important; +} + +@include text-emphasis-variant('.text-muted', $text-muted); + +@include text-emphasis-variant('.text-primary', $brand-primary); + +@include text-emphasis-variant('.text-success', $brand-success); + +@include text-emphasis-variant('.text-info', $brand-info); + +@include text-emphasis-variant('.text-warning', $brand-warning); + +@include text-emphasis-variant('.text-danger', $brand-danger); + +// Font color + +@include text-emphasis-variant('.text-gray-dark', $gray-dark); + +// Misc + +.text-hide { + @include text-hide(); +} diff --git a/src/styles/bootstrap/utilities/_visibility.scss b/src/styles/bootstrap/utilities/_visibility.scss new file mode 100755 index 0000000000000000000000000000000000000000..fcedc9cb91233651d010415efd14a23afab67306 --- /dev/null +++ b/src/styles/bootstrap/utilities/_visibility.scss @@ -0,0 +1,55 @@ +// +// Visibility utilities +// + +.invisible { + @include invisible(); +} + +// Responsive visibility utilities + +@each $bp in map-keys($grid-breakpoints) { + .hidden-#{$bp}-up { + @include media-breakpoint-up($bp) { + display: none !important; + } + } + .hidden-#{$bp}-down { + @include media-breakpoint-down($bp) { + display: none !important; + } + } +} + + +// Print utilities +// +// Media queries are placed on the inside to be mixin-friendly. + +.visible-print-block { + display: none !important; + + @media print { + display: block !important; + } +} +.visible-print-inline { + display: none !important; + + @media print { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; + + @media print { + display: inline-block !important; + } +} + +.hidden-print { + @media print { + display: none !important; + } +} diff --git a/src/styles/console.css b/src/styles/console.css new file mode 100644 index 0000000000000000000000000000000000000000..3020e3eb8d0ca61b41e3cd16cf4dd532235af85f --- /dev/null +++ b/src/styles/console.css @@ -0,0 +1,176 @@ + #console { + height: 352px; + width: 100%; + /*margin-left: -10px !important;*/ + position: relative; + background-color: black; + border: 2px solid #CCC; + margin: 0 auto; + } + /* The inner console element. */ + + .jqconsole { + padding: 10px; + } + /* The cursor. */ + + .jqconsole-cursor { + background-color: gray; + } + /* The cursor color when the console looses focus. */ + + .jqconsole-blurred .jqconsole-cursor { + background-color: #666; + } + /* The current prompt text color */ + + .jqconsole-prompt { + color: rgb(11, 204, 50); + font-style: italic; + } + /* The command history */ + + .jqconsole-old-prompt { + color: rgb(11, 204, 50); + /* color: rgb(11, 204, 2); */ + /*color: rgba(140, 136, 187, 0.84);*/ + /* font-weight: normal;*/ + } + + .jqconsole-prompt, .jqconsole-old-prompt, .jqconsole-output { + word-wrap: break-word; + white-space: pre-wrap; + + } + + /* The text color when in input mode. */ + + .jqconsole-input { + + } + /* Previously entered input. */ + + .jqconsole-old-input { + color: #bb0; + font-weight: normal; + } + /* The text color of the output. */ + + + .jqconsole-header{ + color: #d0d0d0; + } + + .nomArchivoInp { + /*width: 55% !important;*/ + width: calc(100% - 250px); + float: left; + } + + /*.CodeMirror { + height: 450px !important; + }*/ + + /* custom clases */ + + /* normal output */ + .jqconsole-output{ + color:white; + } + + /* logs */ + .jqconsole-logs{ + color:brown; + } + +/* CSS Necesario para los paneles del editor */ + .border { + border: 1px solid #f7f7f7; + } + .add-panel { + background: orange; + padding: 3px 6px; + color: white !important; + border-radius: 3px; + } + .add-panel, .remove-panel { + cursor: pointer; + } + .remove-panel { + float: right; + } + .panel { + background: #f7f7f7; + padding: 3px 7px; + font-size: 0.85em; + } + .panel.top, .panel.after-top { + border-bottom: 1px solid #ddd; + } + .panel.bottom, .panel.before-bottom { + border-top: 1px solid #ddd; + } +/* FIN CSS Paneles **************************/ + + +.alertPosition{ + position: absolute; + left: 50%; + z-index: 1500; + transform: translateX(-50%); +} + + +.breakpoints {width: .8em;} + .breakpoint { color: white !important; } + .CodeMirror {border: 1px solid #aaa;} +.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning { + background-position: left bottom; + background-repeat: repeat-x; +} + +.CodeMirror-lint-mark-error { + background-image: + url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==") + ; +} + +.CodeMirror-lint-mark-warning { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); +} + +.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning { + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; + display: inline-block; + height: 16px; + width: 16px; + vertical-align: middle; + position: relative; +} + +.tooltip { + position: relative; + display: inline-block; + border-bottom: 1px dotted black; + cursor:pointer; +} + +.tooltiptext { + visibility: hidden; + width: 120px; + background-color: white; + color: brown; + text-align: center; + border-radius: 6px; + padding: 5px 0; + + /* Position the tooltip */ + position: absolute; + z-index: 10000; +} + +.tooltip:hover .tooltiptext { + visibility: visible; +} \ No newline at end of file diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000000000000000000000000000000000000..9bf72267e9b1ada4f46ff6a7729ef5f272f7c947 --- /dev/null +++ b/src/test.ts @@ -0,0 +1,32 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy.js'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/jasmine-patch'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. +declare var __karma__: any; +declare var require: any; + +// Prevent Karma from running prematurely. +__karma__.loaded = function () {}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); +// Finally, start Karma to run the tests. +__karma__.start(); diff --git a/src/tsconfig.app.json b/src/tsconfig.app.json new file mode 100644 index 0000000000000000000000000000000000000000..5e2507db58c96c20ac2015842994a5af0e39734b --- /dev/null +++ b/src/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "es2015", + "baseUrl": "", + "types": [] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/src/tsconfig.spec.json b/src/tsconfig.spec.json new file mode 100644 index 0000000000000000000000000000000000000000..510e3f1fdae16c04db35a215260837dedf236a43 --- /dev/null +++ b/src/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "module": "commonjs", + "target": "es5", + "baseUrl": "", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/src/typings.d.ts b/src/typings.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..403b22fee09b241bf3a56ec1c6ed0fe2ccc7959c --- /dev/null +++ b/src/typings.d.ts @@ -0,0 +1,5 @@ +/* SystemJS module definition */ +declare var module: NodeModule; +interface NodeModule { + id: string; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..a35a8ee3a40d4326972a6d31edf57a9bce437520 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "baseUrl": "src", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2016", + "dom" + ] + } +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000000000000000000000000000000000000..9113f1368b224fe758fc39b529c6e9089fd06280 --- /dev/null +++ b/tslint.json @@ -0,0 +1,116 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "callable-types": true, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "import-blacklist": [true, "rxjs"], + "import-spacing": true, + "indent": [ + true, + "spaces" + ], + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [ + true, + 140 + ], + "member-access": false, + "member-ordering": [ + true, + "static-before-instance", + "variables-before-functions" + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-empty-interface": true, + "no-eval": true, + "no-inferrable-types": [true, "ignore-params"], + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "prefer-const": true, + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "typeof-compare": true, + "unified-signatures": true, + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + + "directive-selector": [true, "attribute", "app", "camelCase"], + "component-selector": [true, "element", "app", "kebab-case"], + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true, + "no-access-missing-member": true, + "templates-use-public": true, + "invoke-injectable": true + } +}