mirror of
https://github.com/farcasclaudiu/kanban2.git
synced 2026-06-22 07:01:37 +03:00
Migrate Angular 2 to Angular 19 with standalone components
- Upgrade Angular, Firebase, and AngularFire to latest versions - Replace NgModules with standalone components and bootstrapApplication - Replace angular-cli.json with angular.json and ESLint - Add Firebase App Check with reCAPTCHA v3 for abuse protection - Move Firebase config out of version control (firebaseConfig.example.ts) - Fix AngularFire injection context errors using runInInjectionContext - Implement drag-and-drop reordering within and across columns - Add inline editing for card title and description - Add subtask deletion with confirmation modal - Refresh UI with modern card-based look and feel Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+5
-1
@@ -1,4 +1,4 @@
|
|||||||
# Editor configuration, see http://editorconfig.org
|
# Editor configuration, see https://editorconfig.org
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
@@ -8,6 +8,10 @@ indent_size = 2
|
|||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
ij_typescript_use_double_quotes = false
|
||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
max_line_length = off
|
max_line_length = off
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|||||||
+24
-14
@@ -1,35 +1,45 @@
|
|||||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
||||||
|
|
||||||
# compiled output
|
# Compiled output
|
||||||
/dist
|
/dist
|
||||||
/tmp
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
/bazel-out
|
||||||
|
|
||||||
# dependencies
|
# Node
|
||||||
/node_modules
|
/node_modules
|
||||||
/bower_components
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
# IDEs and editors
|
# IDEs and editors
|
||||||
/.idea
|
.idea/
|
||||||
/.vscode
|
|
||||||
.project
|
.project
|
||||||
.classpath
|
.classpath
|
||||||
.c9/
|
.c9/
|
||||||
*.launch
|
*.launch
|
||||||
.settings/
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
# misc
|
# Visual Studio Code
|
||||||
/.sass-cache
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.history/*
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
/.angular/cache
|
||||||
|
.sass-cache/
|
||||||
/connect.lock
|
/connect.lock
|
||||||
/coverage/*
|
/coverage
|
||||||
/libpeerconnection.log
|
/libpeerconnection.log
|
||||||
npm-debug.log
|
|
||||||
testem.log
|
testem.log
|
||||||
/typings
|
/typings
|
||||||
|
|
||||||
# e2e
|
# Firebase config (contains secrets)
|
||||||
/e2e/*.js
|
src/environments/firebaseConfig.ts
|
||||||
/e2e/*.map
|
|
||||||
|
|
||||||
#System Files
|
# System files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|||||||
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
|
||||||
|
"recommendations": ["angular.ng-template"]
|
||||||
|
}
|
||||||
Vendored
+20
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "ng serve",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "npm: start",
|
||||||
|
"url": "http://localhost:4200/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ng test",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "npm: test",
|
||||||
|
"url": "http://localhost:9876/debug.html"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+42
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "start",
|
||||||
|
"isBackground": true,
|
||||||
|
"problemMatcher": {
|
||||||
|
"owner": "typescript",
|
||||||
|
"pattern": "$tsc",
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": {
|
||||||
|
"regexp": "(.*?)"
|
||||||
|
},
|
||||||
|
"endsPattern": {
|
||||||
|
"regexp": "bundle generation complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "test",
|
||||||
|
"isBackground": true,
|
||||||
|
"problemMatcher": {
|
||||||
|
"owner": "typescript",
|
||||||
|
"pattern": "$tsc",
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": {
|
||||||
|
"regexp": "(.*?)"
|
||||||
|
},
|
||||||
|
"endsPattern": {
|
||||||
|
"regexp": "bundle generation complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
{
|
|
||||||
"project": {
|
|
||||||
"version": "1.0.0-beta.21",
|
|
||||||
"name": "kanban2"
|
|
||||||
},
|
|
||||||
"apps": [
|
|
||||||
{
|
|
||||||
"root": "src",
|
|
||||||
"outDir": "dist",
|
|
||||||
"assets": [
|
|
||||||
"assets",
|
|
||||||
"favicon.ico"
|
|
||||||
],
|
|
||||||
"index": "index.html",
|
|
||||||
"main": "main.ts",
|
|
||||||
"test": "test.ts",
|
|
||||||
"tsconfig": "tsconfig.json",
|
|
||||||
"prefix": "app",
|
|
||||||
"mobile": false,
|
|
||||||
"styles": [
|
|
||||||
"styles.css",
|
|
||||||
"../node_modules/bootstrap/dist/css/bootstrap.min.css"
|
|
||||||
],
|
|
||||||
"scripts": [],
|
|
||||||
"environments": {
|
|
||||||
"source": "environments/environment.ts",
|
|
||||||
"dev": "environments/environment.ts",
|
|
||||||
"prod": "environments/environment.prod.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"addons": [],
|
|
||||||
"packages": [],
|
|
||||||
"e2e": {
|
|
||||||
"protractor": {
|
|
||||||
"config": "./protractor.conf.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"karma": {
|
|
||||||
"config": "./karma.conf.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaults": {
|
|
||||||
"styleExt": "css",
|
|
||||||
"prefixInterfaces": false,
|
|
||||||
"inline": {
|
|
||||||
"style": false,
|
|
||||||
"template": false
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"class": false,
|
|
||||||
"component": true,
|
|
||||||
"directive": true,
|
|
||||||
"module": false,
|
|
||||||
"pipe": true,
|
|
||||||
"service": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+135
@@ -0,0 +1,135 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"kanban2": {
|
||||||
|
"projectType": "application",
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:class": {
|
||||||
|
"skipTests": true
|
||||||
|
},
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"skipTests": true
|
||||||
|
},
|
||||||
|
"@schematics/angular:directive": {
|
||||||
|
"skipTests": true
|
||||||
|
},
|
||||||
|
"@schematics/angular:guard": {
|
||||||
|
"skipTests": true
|
||||||
|
},
|
||||||
|
"@schematics/angular:interceptor": {
|
||||||
|
"skipTests": true
|
||||||
|
},
|
||||||
|
"@schematics/angular:pipe": {
|
||||||
|
"skipTests": true
|
||||||
|
},
|
||||||
|
"@schematics/angular:resolver": {
|
||||||
|
"skipTests": true
|
||||||
|
},
|
||||||
|
"@schematics/angular:service": {
|
||||||
|
"skipTests": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"prefix": "app",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:application",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/kanban2",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"browser": "src/main.ts",
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "public"
|
||||||
|
},
|
||||||
|
"src/favicon.ico"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||||
|
"src/styles.css"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "1MB",
|
||||||
|
"maximumError": "1.5MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "4kB",
|
||||||
|
"maximumError": "8kB"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"optimization": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "kanban2:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "kanban2:build:development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js",
|
||||||
|
"zone.js/testing"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.spec.json",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "public"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"src/styles.css"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-eslint/builder:lint",
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.html"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"analytics": false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { ValuationJsPage } from './app.po';
|
|
||||||
|
|
||||||
describe('kanban2 App', function() {
|
|
||||||
let page: ValuationJsPage;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
page = new ValuationJsPage();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display message saying app works', () => {
|
|
||||||
page.navigateTo();
|
|
||||||
expect(page.getParagraphText()).toEqual('app works!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { browser, element, by } from 'protractor';
|
|
||||||
|
|
||||||
export class ValuationJsPage {
|
|
||||||
navigateTo() {
|
|
||||||
return browser.get('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
getParagraphText() {
|
|
||||||
return element(by.css('app-root h1')).getText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"compileOnSave": false,
|
|
||||||
"compilerOptions": {
|
|
||||||
"declaration": false,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"module": "commonjs",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"outDir": "../dist/out-tsc-e2e",
|
|
||||||
"sourceMap": true,
|
|
||||||
"target": "es5",
|
|
||||||
"typeRoots": [
|
|
||||||
"../node_modules/@types"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// @ts-check
|
||||||
|
const eslint = require("@eslint/js");
|
||||||
|
const { defineConfig } = require("eslint/config");
|
||||||
|
const tseslint = require("typescript-eslint");
|
||||||
|
const angular = require("angular-eslint");
|
||||||
|
|
||||||
|
module.exports = defineConfig([
|
||||||
|
{
|
||||||
|
files: ["**/*.ts"],
|
||||||
|
extends: [
|
||||||
|
eslint.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
tseslint.configs.stylistic,
|
||||||
|
angular.configs.tsRecommended,
|
||||||
|
],
|
||||||
|
processor: angular.processInlineTemplates,
|
||||||
|
rules: {
|
||||||
|
"@angular-eslint/directive-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
type: "attribute",
|
||||||
|
prefix: "app",
|
||||||
|
style: "camelCase",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@angular-eslint/component-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
prefix: "app",
|
||||||
|
style: "kebab-case",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["**/*.html"],
|
||||||
|
extends: [
|
||||||
|
angular.configs.templateRecommended,
|
||||||
|
angular.configs.templateAccessibility,
|
||||||
|
],
|
||||||
|
rules: {},
|
||||||
|
}
|
||||||
|
]);
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// 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-remap-istanbul'),
|
|
||||||
require('angular-cli/plugins/karma')
|
|
||||||
],
|
|
||||||
files: [
|
|
||||||
{ pattern: './src/test.ts', watched: false }
|
|
||||||
],
|
|
||||||
preprocessors: {
|
|
||||||
'./src/test.ts': ['angular-cli']
|
|
||||||
},
|
|
||||||
mime: {
|
|
||||||
'text/x-typescript': ['ts','tsx']
|
|
||||||
},
|
|
||||||
remapIstanbulReporter: {
|
|
||||||
reports: {
|
|
||||||
html: 'coverage',
|
|
||||||
lcovonly: './coverage/coverage.lcov'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
angularCli: {
|
|
||||||
config: './angular-cli.json',
|
|
||||||
environment: 'dev'
|
|
||||||
},
|
|
||||||
reporters: config.angularCli && config.angularCli.codeCoverage
|
|
||||||
? ['progress', 'karma-remap-istanbul']
|
|
||||||
: ['progress'],
|
|
||||||
port: 9876,
|
|
||||||
colors: true,
|
|
||||||
logLevel: config.LOG_INFO,
|
|
||||||
autoWatch: true,
|
|
||||||
browsers: ['Chrome'],
|
|
||||||
singleRun: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Generated
+17299
File diff suppressed because it is too large
Load Diff
+35
-39
@@ -1,51 +1,47 @@
|
|||||||
{
|
{
|
||||||
"name": "kanban2",
|
"name": "kanban2",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
|
||||||
"angular-cli": {},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
"lint": "tslint \"src/**/*.ts\"",
|
"build": "ng build",
|
||||||
"test": "ng test",
|
"watch": "ng build --watch --configuration development",
|
||||||
"pree2e": "webdriver-manager update",
|
"test": "ng test"
|
||||||
"e2e": "protractor"
|
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/common": "19.2.16",
|
"@angular/animations": "^19.2.21",
|
||||||
"@angular/compiler": "2.4.7",
|
"@angular/cdk": "^19.2.19",
|
||||||
"@angular/core": "10.2.5",
|
"@angular/common": "^19.2.0",
|
||||||
"@angular/forms": "2.4.7",
|
"@angular/compiler": "^19.2.0",
|
||||||
"@angular/http": "2.4.7",
|
"@angular/core": "^19.2.0",
|
||||||
"@angular/platform-browser": "2.4.7",
|
"@angular/fire": "^19.2.0",
|
||||||
"@angular/platform-browser-dynamic": "2.4.7",
|
"@angular/forms": "^19.2.0",
|
||||||
"@angular/router": "3.4.7",
|
"@angular/platform-browser": "^19.2.0",
|
||||||
"angularfire2": "^2.0.0-beta.7",
|
"@angular/platform-browser-dynamic": "^19.2.0",
|
||||||
"bootstrap": "^4.1.3",
|
"@angular/router": "^19.2.0",
|
||||||
"core-js": "2.4.1",
|
"bootstrap": "^5.3.8",
|
||||||
"firebase": "10.9.0",
|
"firebase": "^11.10.0",
|
||||||
"ng2-bootstrap": "^1.3.3",
|
"rxjs": "~7.8.0",
|
||||||
"ng2-dnd": "^2.2.2",
|
"tslib": "^2.3.0",
|
||||||
"rxjs": "5.0.3",
|
"zone.js": "~0.15.0"
|
||||||
"ts-helpers": "1.1.2",
|
|
||||||
"zone.js": "0.7.6"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/compiler-cli": "2.4.7",
|
"@angular-devkit/build-angular": "^19.2.24",
|
||||||
"@types/jasmine": "2.5.38",
|
"@angular/cli": "^19.2.24",
|
||||||
"@types/node": "^6.0.60",
|
"@angular/compiler-cli": "^19.2.0",
|
||||||
"angular-cli": "1.0.0-beta.28.3",
|
"@types/jasmine": "~5.1.0",
|
||||||
"codelyzer": "~1.0.0-beta.3",
|
"angular-eslint": "^21.0.1",
|
||||||
"jasmine-core": "2.5.2",
|
"jasmine-core": "~5.6.0",
|
||||||
"jasmine-spec-reporter": "2.5.0",
|
"karma": "~6.4.0",
|
||||||
"karma": "6.3.16",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
"karma-chrome-launcher": "2.0.0",
|
"karma-coverage": "~2.2.0",
|
||||||
"karma-cli": "1.0.1",
|
"karma-jasmine": "~5.1.0",
|
||||||
"karma-jasmine": "1.0.2",
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
"karma-remap-istanbul": "0.2.1",
|
"typescript": "~5.7.2"
|
||||||
"protractor": "4.0.13",
|
},
|
||||||
"ts-node": "1.2.1",
|
"overrides": {
|
||||||
"tslint": "4.2.0",
|
"serialize-javascript": "^7.0.5",
|
||||||
"typescript": "~2.1.6"
|
"tar": "^7.5.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
// Protractor configuration file, see link for more information
|
|
||||||
// https://github.com/angular/protractor/blob/master/docs/referenceConf.js
|
|
||||||
|
|
||||||
/*global jasmine */
|
|
||||||
var SpecReporter = require('jasmine-spec-reporter');
|
|
||||||
|
|
||||||
exports.config = {
|
|
||||||
allScriptsTimeout: 11000,
|
|
||||||
specs: [
|
|
||||||
'./e2e/**/*.e2e-spec.ts'
|
|
||||||
],
|
|
||||||
capabilities: {
|
|
||||||
'browserName': 'chrome'
|
|
||||||
},
|
|
||||||
directConnect: true,
|
|
||||||
baseUrl: 'http://localhost:4200/',
|
|
||||||
framework: 'jasmine',
|
|
||||||
jasmineNodeOpts: {
|
|
||||||
showColors: true,
|
|
||||||
defaultTimeoutInterval: 30000,
|
|
||||||
print: function() {}
|
|
||||||
},
|
|
||||||
useAllAngular2AppRoots: true,
|
|
||||||
beforeLaunch: function() {
|
|
||||||
require('ts-node').register({
|
|
||||||
project: 'e2e'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onPrepare: function() {
|
|
||||||
jasmine.getEnv().addReporter(new SpecReporter());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,31 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-header {
|
||||||
|
padding: 16px 0 12px;
|
||||||
|
border-bottom: 3px solid #5ba4cf;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-header h1 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #172b4d;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
height: calc(100% - 60px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-col {
|
||||||
|
flex: 0 0 320px;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|||||||
+11
-11
@@ -1,12 +1,12 @@
|
|||||||
<div class="container">
|
<div class="container board-header">
|
||||||
<h1 class="page-header">
|
<h1>{{title}}</h1>
|
||||||
{{title}}
|
</div>
|
||||||
</h1>
|
<div class="container board">
|
||||||
<div class="row">
|
@for (cardlist of cardlists; track cardlist.$key) {
|
||||||
|
<div class="board-col">
|
||||||
<div *ngFor="let cardlist of cardlists" class="col-sm-4">
|
<app-cardlist [item]="cardlist"
|
||||||
<cardlist [item]="cardlist">
|
[connectedDropLists]="getConnectedDropLists(cardlist.$key!)">
|
||||||
</cardlist>
|
</app-cardlist>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
</div>
|
</div>
|
||||||
+25
-36
@@ -1,61 +1,51 @@
|
|||||||
import {Component, OnInit} from "@angular/core";
|
import { Component, OnInit, inject } from '@angular/core';
|
||||||
import {DataService} from "app/shared/data.service";
|
import { DataService } from './shared/data.service';
|
||||||
import {Observable} from "rxjs";
|
import { Project } from './models/project-info';
|
||||||
|
import { CardList } from './models/cardlist-info';
|
||||||
import {Project} from "app/models/project-info";
|
import { CardListComponent } from './cardlist/cardlist.component';
|
||||||
import {CardList} from "app/models/cardlist-info";
|
|
||||||
import {Card} from "app/models/card-info";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CardListComponent],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.css']
|
styleUrls: ['./app.component.css']
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
title = 'The Kanban Board';
|
title = 'The Kanban Board';
|
||||||
projects: Project[];
|
projects: Project[] = [];
|
||||||
cardlists: CardList[];
|
cardlists: CardList[] = [];
|
||||||
|
|
||||||
constructor(private dataService: DataService) {
|
private dataService = inject(DataService);
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(){
|
ngOnInit(): void {
|
||||||
this.dataService.getProjects()
|
this.dataService.getProjects()
|
||||||
.subscribe(data => {
|
.subscribe(data => {
|
||||||
this.projects = data;
|
this.projects = data;
|
||||||
let firstProject = this.projects[0];
|
|
||||||
//console.log(firstProject);
|
|
||||||
// this.addAddCardList(
|
|
||||||
// 'Done',
|
|
||||||
// firstProject.$key,
|
|
||||||
// 'green'
|
|
||||||
// );
|
|
||||||
});
|
});
|
||||||
this.dataService.getCardLists()
|
this.dataService.getCardLists()
|
||||||
.subscribe(c => this.cardlists = c)
|
.subscribe(c => this.cardlists = c);
|
||||||
;
|
|
||||||
this.dataService.getCards();
|
this.dataService.getCards();
|
||||||
this.dataService.getTasks();
|
this.dataService.getTasks();
|
||||||
//this.addProject("TestProject1");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addProject(name: string)
|
addProject(name: string): void {
|
||||||
{
|
const created_at = new Date().toString();
|
||||||
let created_at = new Date().toString();
|
const newProject = new Project();
|
||||||
let newProject:Project = new Project();
|
|
||||||
newProject.name = name;
|
newProject.name = name;
|
||||||
newProject.created_at= created_at;
|
newProject.created_at = created_at;
|
||||||
this.dataService.addProject(newProject);
|
this.dataService.addProject(newProject);
|
||||||
}
|
}
|
||||||
|
|
||||||
addCardList(
|
getConnectedDropLists(currentKey: string): string[] {
|
||||||
name: string,
|
return this.cardlists
|
||||||
projectId: string,
|
.filter(c => c.$key !== currentKey)
|
||||||
color: string,
|
.map(c => c.$key!);
|
||||||
order: number)
|
}
|
||||||
{
|
|
||||||
let created_at = new Date().toString();
|
addCardList(name: string, projectId: string, color: string, order: number): void {
|
||||||
let newCardList:CardList = new CardList();
|
const created_at = new Date().toString();
|
||||||
|
const newCardList = new CardList();
|
||||||
newCardList.name = name;
|
newCardList.name = name;
|
||||||
newCardList.projectId = projectId;
|
newCardList.projectId = projectId;
|
||||||
newCardList.color = color;
|
newCardList.color = color;
|
||||||
@@ -63,5 +53,4 @@ export class AppComponent implements OnInit {
|
|||||||
newCardList.created_at = created_at;
|
newCardList.created_at = created_at;
|
||||||
this.dataService.addCardList(newCardList);
|
this.dataService.addCardList(newCardList);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import {BrowserModule} from "@angular/platform-browser";
|
|
||||||
import {NgModule} from "@angular/core";
|
|
||||||
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
|
||||||
import {HttpModule} from "@angular/http";
|
|
||||||
import {AppComponent} from "./app.component";
|
|
||||||
import {authConfig, firebaseConfig} from "environments/firebaseConfig";
|
|
||||||
import {AngularFireModule} from "angularfire2";
|
|
||||||
import {AlertModule} from "ng2-bootstrap";
|
|
||||||
import {ModalModule} from 'ng2-bootstrap';
|
|
||||||
|
|
||||||
import {DataService} from "app/shared/data.service";
|
|
||||||
import {CardListComponent} from "app/cardlist/cardlist.component";
|
|
||||||
import {CardComponent} from "app/card/card.component";
|
|
||||||
import {DndModule} from 'ng2-dnd';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [
|
|
||||||
AppComponent,
|
|
||||||
CardListComponent,
|
|
||||||
CardComponent
|
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
BrowserModule,
|
|
||||||
FormsModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
HttpModule,
|
|
||||||
AlertModule.forRoot(),
|
|
||||||
ModalModule.forRoot(),
|
|
||||||
DndModule.forRoot(),
|
|
||||||
AngularFireModule.initializeApp(firebaseConfig, authConfig)
|
|
||||||
],
|
|
||||||
providers: [DataService],
|
|
||||||
bootstrap: [AppComponent]
|
|
||||||
})
|
|
||||||
export class AppModule {
|
|
||||||
}
|
|
||||||
+231
-30
@@ -1,38 +1,239 @@
|
|||||||
.cardTitle{
|
/* Card Title */
|
||||||
margin-left: 10px;
|
.cardTitle {
|
||||||
font-size: 1em;
|
display: flex;
|
||||||
font-weight: bold;
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #172b4d;
|
||||||
}
|
}
|
||||||
.cardDesc{
|
|
||||||
margin-left: 10px;
|
.carret {
|
||||||
}
|
|
||||||
.tasklist{
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
.newtask
|
|
||||||
{
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
.link{
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.carret{
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
color: #5e6c84;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
.titleText{
|
.carret:hover {
|
||||||
display: inline-block;
|
background-color: rgba(9, 30, 66, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.titleText {
|
||||||
|
flex: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
.titleText:hover {
|
||||||
|
background-color: rgba(9, 30, 66, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
color: #b0b8c4;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card Description */
|
||||||
|
.cardDesc {
|
||||||
|
margin: 6px 0 0 18px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #5e6c84;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 3px 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.cardDesc:hover {
|
||||||
|
background-color: rgba(9, 30, 66, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edit inputs */
|
||||||
|
.edit-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px 7px;
|
||||||
|
border: 2px solid #5ba4cf;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: inherit;
|
||||||
|
font-family: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.title-input {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.desc-input {
|
||||||
|
resize: vertical;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Task list */
|
||||||
|
.tasklist {
|
||||||
|
margin: 8px 0 4px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 3px 0;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #172b4d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item.completed .task-text {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: #b0b8c4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-trash {
|
||||||
|
color: #b0b8c4;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
.task-item:hover .task-trash {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.task-trash:hover {
|
||||||
|
color: #e44;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* New task input */
|
||||||
|
.newtask-input {
|
||||||
|
margin-top: 6px;
|
||||||
|
margin-left: 18px;
|
||||||
|
}
|
||||||
|
.newtask-input input {
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid #dfe1e6;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
padding: 4px 2px;
|
||||||
|
background: transparent;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.newtask-input input:focus {
|
||||||
|
border-bottom-color: #5ba4cf;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delete Modal */
|
||||||
|
.modal-backdrop-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
z-index: 100;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 101;
|
||||||
|
pointer-events: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-modal-overlay .create-card-modal {
|
||||||
|
pointer-events: auto;
|
||||||
|
width: 340px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25);
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-header {
|
.modal-header {
|
||||||
padding:9px 15px;
|
display: flex;
|
||||||
border-bottom:1px solid #eee;
|
align-items: center;
|
||||||
background-color: #ff4040;
|
justify-content: space-between;
|
||||||
-webkit-border-top-left-radius: 5px;
|
padding: 12px 16px;
|
||||||
-webkit-border-top-right-radius: 5px;
|
background-color: #e44;
|
||||||
-moz-border-radius-topleft: 5px;
|
border-top-left-radius: 12px;
|
||||||
-moz-border-radius-topright: 5px;
|
border-top-right-radius: 12px;
|
||||||
border-top-left-radius: 5px;
|
|
||||||
border-top-right-radius: 5px;
|
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.modal-header h4 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.modal-header .close {
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0.8;
|
||||||
|
font-size: 1rem;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: none;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s;
|
||||||
|
}
|
||||||
|
.modal-header .close:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.35);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.modal-header .close:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body p {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
color: #172b4d;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete {
|
||||||
|
background: #e44;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s;
|
||||||
|
}
|
||||||
|
.btn-delete:hover {
|
||||||
|
background: #d33;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background: #f4f5f7;
|
||||||
|
color: #5e6c84;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s;
|
||||||
|
}
|
||||||
|
.btn-cancel:hover {
|
||||||
|
background: #ebecf0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,48 +1,76 @@
|
|||||||
<div class="cardTitle inline">
|
<div class="cardTitle">
|
||||||
<div class="carret" (click)="clickCarret()">
|
<div class="carret" (click)="clickCarret()" tabindex="0" role="button" (keydown.enter)="clickCarret()" (keydown.space)="clickCarret()">
|
||||||
<i class="fa fa-caret-right" *ngIf="!item.isExpanded"></i>
|
@if (!item.isExpanded) {
|
||||||
<i class="fa fa-caret-down" *ngIf="item.isExpanded"></i>
|
<i class="fa fa-caret-right"></i>
|
||||||
|
}
|
||||||
|
@if (item.isExpanded) {
|
||||||
|
<i class="fa fa-caret-down"></i>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="titleText">
|
@if (editingTitle) {
|
||||||
|
<input class="edit-input title-input"
|
||||||
|
[(ngModel)]="editTitle"
|
||||||
|
(blur)="saveTitle()"
|
||||||
|
(keydown.enter)="saveTitle()"
|
||||||
|
(keydown.escape)="cancelEditTitle()">
|
||||||
|
} @else {
|
||||||
|
<div class="titleText" tabindex="0" role="button" (click)="startEditTitle()" (keydown.enter)="startEditTitle()">
|
||||||
|
@if (item.name) {
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
|
} @else {
|
||||||
|
<span class="placeholder">Click to add title</span>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="item.isExpanded">
|
@if (item.isExpanded) {
|
||||||
<div class="cardDesc">
|
<div>
|
||||||
|
@if (editingDesc) {
|
||||||
|
<textarea class="edit-input desc-input"
|
||||||
|
[(ngModel)]="editDesc"
|
||||||
|
(blur)="saveDesc()"
|
||||||
|
(keydown.escape)="cancelEditDesc()"
|
||||||
|
rows="3">
|
||||||
|
</textarea>
|
||||||
|
} @else {
|
||||||
|
<div class="cardDesc" tabindex="0" role="button" (click)="startEditDesc()" (keydown.enter)="startEditDesc()">
|
||||||
{{item.description}}
|
{{item.description}}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
<div class="tasklist">
|
<div class="tasklist">
|
||||||
<div *ngFor="let task of tasks" class="newtask">
|
@for (task of tasks; track task.$key) {
|
||||||
<input type="checkbox" [(ngModel)]="task.isCompleted" (ngModelChange)="changeTaskCompleted(task)" class="inline-block">
|
<div class="task-item" [class.completed]="task.isCompleted">
|
||||||
<span class="inline-block">
|
<input type="checkbox" [(ngModel)]="task.isCompleted" (ngModelChange)="changeTaskCompleted(task)">
|
||||||
{{task.description}}
|
<span class="task-text">{{task.description}}</span>
|
||||||
</span>
|
<span class="task-trash fa fa-trash" (click)="deleteTask(task)" tabindex="0" role="button" (keydown.enter)="deleteTask(task)"></span>
|
||||||
<span class="inline-block glyphicon glyphicon-trash link" (click)="deleteTask(task)"></span>
|
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="newtask">
|
<div class="newtask-input">
|
||||||
<form (submit)="addNewTask()">
|
<form (submit)="addNewTask()">
|
||||||
<input type="text" class="form-control newtask" id="newtask" placeholder="Add subtask and hit enter" [(ngModel)]="newtaskdesc" name="newtask">
|
<input type="text" id="newtask" placeholder="Add subtask and hit enter" [(ngModel)]="newtaskdesc" name="newtask">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (showModal) {
|
||||||
|
<div class="modal-backdrop-overlay" (click)="hideModal()" (mousedown)="$event.stopPropagation()" tabindex="0" role="button" (keydown.enter)="hideModal()"></div>
|
||||||
<div bsModal #childModal="bs-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
|
<div class="delete-modal-overlay" (mousedown)="$event.stopPropagation()">
|
||||||
<div class="modal-dialog modal-sm">
|
<div class="create-card-modal">
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title pull-left">Delete task</h4>
|
<h4 class="modal-title pull-left">Delete subtask</h4>
|
||||||
<button type="button" class="close pull-right" aria-label="Close" (click)="hideChildModal()">
|
<button type="button" class="close pull-right" aria-label="Close" (click)="hideModal()">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body" style="padding: 16px;">
|
||||||
Not implement!<br>
|
<p>Delete "{{taskToDelete?.description}}"?</p>
|
||||||
Works as per specs!<br>
|
<div class="modal-actions">
|
||||||
Carret is not carrot :)
|
<button type="button" class="btn-delete" (click)="confirmDeleteTask()">Delete</button>
|
||||||
|
<button type="button" class="btn-cancel" (click)="hideModal()">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,40 +1,85 @@
|
|||||||
import {Component, OnInit, Input, ViewChild} from "@angular/core";
|
import { Component, OnInit, Input, HostListener, inject } from '@angular/core';
|
||||||
import {DataService} from "app/shared/data.service";
|
import { FormsModule } from '@angular/forms';
|
||||||
import {Observable} from "rxjs";
|
import { DataService } from '../shared/data.service';
|
||||||
import {CardList} from "app/models/cardlist-info";
|
import { Card } from '../models/card-info';
|
||||||
import {Card} from "app/models/card-info";
|
import { Task } from '../models/task-info';
|
||||||
import {Task} from "app/models/task-info";
|
|
||||||
import { ModalDirective } from 'ng2-bootstrap/modal';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'card',
|
selector: 'app-card',
|
||||||
|
standalone: true,
|
||||||
|
imports: [FormsModule],
|
||||||
templateUrl: './card.component.html',
|
templateUrl: './card.component.html',
|
||||||
styleUrls: ['./card.component.css']
|
styleUrls: ['./card.component.css']
|
||||||
})
|
})
|
||||||
export class CardComponent implements OnInit {
|
export class CardComponent implements OnInit {
|
||||||
@ViewChild('childModal') public childModal:ModalDirective;
|
@Input() item!: Card;
|
||||||
@Input() item: Card;
|
tasks: Task[] = [];
|
||||||
tasks : Task[]
|
showModal = false;
|
||||||
|
taskToDelete: Task | null = null;
|
||||||
|
newtaskdesc = '';
|
||||||
|
|
||||||
newtaskdesc;
|
editingTitle = false;
|
||||||
|
editTitle = '';
|
||||||
|
editingDesc = false;
|
||||||
|
editDesc = '';
|
||||||
|
|
||||||
|
private dataService = inject(DataService);
|
||||||
|
|
||||||
constructor(private dataService: DataService) {
|
@HostListener('document:keydown.escape')
|
||||||
//console.log(this.item);
|
onEscape(): void {
|
||||||
|
if (this.showModal) {
|
||||||
|
this.hideModal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit(): void {
|
||||||
//console.log(this.item);
|
this.dataService.getTasksByCardId(this.item.$key!)
|
||||||
this.dataService.getTasksByCardId(this.item.$key)
|
|
||||||
.subscribe(data => {
|
.subscribe(data => {
|
||||||
this.tasks = data;
|
this.tasks = data;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addNewTask(){
|
startEditTitle(): void {
|
||||||
//console.log('Add new subtask!');
|
this.editTitle = this.item.name ?? '';
|
||||||
let newTask = new Task();
|
this.editingTitle = true;
|
||||||
newTask.cardId = this.item.$key;
|
}
|
||||||
|
|
||||||
|
saveTitle(): void {
|
||||||
|
if (!this.editingTitle) return;
|
||||||
|
this.editingTitle = false;
|
||||||
|
const trimmed = this.editTitle.trim();
|
||||||
|
if (trimmed && trimmed !== this.item.name) {
|
||||||
|
this.item.name = trimmed;
|
||||||
|
this.dataService.updateCard(this.item.$key!, this.item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelEditTitle(): void {
|
||||||
|
this.editingTitle = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
startEditDesc(): void {
|
||||||
|
this.editDesc = this.item.description ?? '';
|
||||||
|
this.editingDesc = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveDesc(): void {
|
||||||
|
if (!this.editingDesc) return;
|
||||||
|
this.editingDesc = false;
|
||||||
|
const trimmed = this.editDesc.trim();
|
||||||
|
if (trimmed !== this.item.description) {
|
||||||
|
this.item.description = trimmed;
|
||||||
|
this.dataService.updateCard(this.item.$key!, this.item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelEditDesc(): void {
|
||||||
|
this.editingDesc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
addNewTask(): void {
|
||||||
|
const newTask = new Task();
|
||||||
|
newTask.cardId = this.item.$key!;
|
||||||
newTask.description = this.newtaskdesc;
|
newTask.description = this.newtaskdesc;
|
||||||
newTask.isCompleted = false;
|
newTask.isCompleted = false;
|
||||||
newTask.order = 0;
|
newTask.order = 0;
|
||||||
@@ -45,21 +90,29 @@ export class CardComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteTask(task){
|
deleteTask(task: Task): void {
|
||||||
//console.log(task);
|
this.taskToDelete = task;
|
||||||
this.childModal.show();
|
this.showModal = true;
|
||||||
}
|
|
||||||
public hideChildModal():void {
|
|
||||||
this.childModal.hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changeTaskCompleted(task){
|
hideModal(): void {
|
||||||
//console.log(task);
|
this.showModal = false;
|
||||||
this.dataService.updateTask(task.$key, task);
|
this.taskToDelete = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
clickCarret(){
|
confirmDeleteTask(): void {
|
||||||
|
if (this.taskToDelete?.$key) {
|
||||||
|
this.dataService.deleteTask(this.taskToDelete.$key);
|
||||||
|
}
|
||||||
|
this.hideModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
changeTaskCompleted(task: Task): void {
|
||||||
|
this.dataService.updateTask(task.$key!, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
clickCarret(): void {
|
||||||
this.item.isExpanded = !this.item.isExpanded;
|
this.item.isExpanded = !this.item.isExpanded;
|
||||||
this.dataService.updateCard(this.item.$key,this.item);
|
this.dataService.updateCard(this.item.$key!, this.item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +1,231 @@
|
|||||||
.card{
|
/* Column Panel */
|
||||||
background-color: lightblue;
|
.column-panel {
|
||||||
border: solid 1px;
|
background: #ebecf0;
|
||||||
border-left-width: 5px;
|
border-radius: 12px;
|
||||||
margin: 15px;
|
max-height: calc(100vh - 140px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
.cardTitle{
|
|
||||||
margin-left: 10px;
|
.column-header {
|
||||||
font-size: 1em;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 12px 6px;
|
||||||
|
border-top: 3px solid #ccc;
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
}
|
}
|
||||||
.cardDesc{
|
|
||||||
margin-left: 10px;
|
.column-header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
.createCard{
|
|
||||||
padding: 10px;
|
.column-dot {
|
||||||
/*background-color: lightcyan;*/
|
font-size: 0.55rem;
|
||||||
}
|
}
|
||||||
.fullScreen{
|
|
||||||
|
.column-title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #172b4d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-count {
|
||||||
|
background: rgba(9, 30, 66, 0.08);
|
||||||
|
color: #5e6c84;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 1px 8px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add button */
|
||||||
|
.add-card-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #5e6c84;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
transition: background-color 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
.add-card-btn:hover {
|
||||||
|
background-color: rgba(9, 30, 66, 0.08);
|
||||||
|
color: #172b4d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card list */
|
||||||
|
.card-drop-zone {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 4px 8px 8px;
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-item {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left-width: 4px;
|
||||||
|
border-left-style: solid;
|
||||||
|
padding: 8px 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
cursor: grab;
|
||||||
|
box-shadow: 0 1px 2px rgba(9, 30, 66, 0.1);
|
||||||
|
transition: box-shadow 0.15s;
|
||||||
|
}
|
||||||
|
.card-item:hover {
|
||||||
|
box-shadow: 0 4px 12px rgba(9, 30, 66, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create Card Modal */
|
||||||
|
.modal-backdrop-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 100%;
|
inset: 0;
|
||||||
height: 100%;
|
background: rgba(0, 0, 0, 0.4);
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
clear: both;
|
|
||||||
background: black;
|
|
||||||
opacity: 0.3;
|
|
||||||
padding-left: 35%;
|
|
||||||
padding-right: 35%;
|
|
||||||
padding-top: 35%;
|
|
||||||
padding-bottom: 15%;
|
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
.fullScreentransparent{
|
|
||||||
position: absolute;
|
.modal-overlay {
|
||||||
clear: both;
|
position: fixed;
|
||||||
width: 100%;
|
inset: 0;
|
||||||
height: 100%;
|
display: flex;
|
||||||
left: 0;
|
align-items: center;
|
||||||
top: 0;
|
justify-content: center;
|
||||||
background: transparent;
|
|
||||||
z-index: 101;
|
z-index: 101;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.link{
|
|
||||||
|
.create-card-modal {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
width: 400px;
|
||||||
|
max-width: 90vw;
|
||||||
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 14px 16px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title-text {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close-btn {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
opacity: 0.85;
|
||||||
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
.createTitle{
|
.modal-close-btn:hover {
|
||||||
font-weight: bold;
|
opacity: 1;
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.formfields{
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
.inline{
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.listTitle{
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-form {
|
||||||
|
padding: 16px;
|
||||||
.dnd-drag-start {
|
|
||||||
-moz-transform:scale(0.8);
|
|
||||||
-webkit-transform:scale(0.8);
|
|
||||||
transform:scale(0.8);
|
|
||||||
opacity:0.7;
|
|
||||||
border: 2px dashed #000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dnd-drag-enter {
|
.modal-form label {
|
||||||
opacity:0.7;
|
font-size: 0.8rem;
|
||||||
border: 2px dashed #000;
|
font-weight: 600;
|
||||||
|
color: #5e6c84;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dnd-drag-over {
|
.modal-form .form-control {
|
||||||
border: 2px dashed #000;
|
border: 2px solid #dfe1e6;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 8px 10px;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
.modal-form .form-control:focus {
|
||||||
|
border-color: #5ba4cf;
|
||||||
|
box-shadow: 0 0 0 1px #5ba4cf;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dnd-sortable-drag {
|
.modal-form .form-group + .form-group {
|
||||||
-moz-transform:scale(0.9);
|
margin-top: 12px;
|
||||||
-webkit-transform:scale(0.9);
|
}
|
||||||
transform:scale(0.9);
|
|
||||||
opacity:0.7;
|
.modal-actions {
|
||||||
border: 1px dashed #000;
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-create {
|
||||||
|
background: #5ba4cf;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s;
|
||||||
|
}
|
||||||
|
.btn-create:hover {
|
||||||
|
background: #4a93be;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background: #f4f5f7;
|
||||||
|
color: #5e6c84;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s;
|
||||||
|
}
|
||||||
|
.btn-cancel:hover {
|
||||||
|
background: #ebecf0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CDK Drag-Drop */
|
||||||
|
.cdk-drop-list-receiving .card-drop-zone {
|
||||||
|
background-color: rgba(91, 164, 207, 0.1);
|
||||||
|
border: 2px dashed rgba(91, 164, 207, 0.5);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cdk-drag-preview {
|
||||||
|
opacity: 0.9;
|
||||||
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cdk-drag-placeholder {
|
||||||
|
opacity: 0.2;
|
||||||
|
border: 2px dashed #dfe1e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f4f5f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cdk-drag-animating {
|
||||||
|
transition: transform 200ms cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cdk-drop-list-dragging .cdk-drag {
|
||||||
|
transition: transform 200ms cubic-bezier(0, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
@@ -1,53 +1,58 @@
|
|||||||
<div class="panel panel-info"
|
<div class="column-panel"
|
||||||
dnd-droppable
|
cdkDropList
|
||||||
[allowDrop]="allowDropFunction()"
|
[id]="item.$key!"
|
||||||
(onDropSuccess)="cardDropped($event)"
|
[cdkDropListData]="cards"
|
||||||
>
|
[cdkDropListConnectedTo]="connectedDropLists"
|
||||||
<div class="panel-heading">
|
(cdkDropListDropped)="onCardDrop($event)">
|
||||||
<div>
|
<div class="column-header" [style.border-top-color]="item.color">
|
||||||
<i class="fa fa-1 fa-circle inline" aria-hidden="true" [style.color]="item.color"></i>
|
<div class="column-header-left">
|
||||||
<span class="inline listTitle">
|
<i class="fa fa-circle column-dot" aria-hidden="true" [style.color]="item.color"></i>
|
||||||
{{item.name}}
|
<span class="column-title">{{item.name}}</span>
|
||||||
</span>
|
<span class="column-count">{{cards.length}}</span>
|
||||||
<button type="button" class="btn btn-default btn-xs inline" (click)="showAddCard()">
|
</div>
|
||||||
|
<button type="button" class="add-card-btn" (click)="showAddCard()" title="Add card">
|
||||||
<i class="fa fa-plus"></i>
|
<i class="fa fa-plus"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="card-drop-zone">
|
||||||
<div class="">
|
<ul class="card-list">
|
||||||
<ul class="list-group">
|
@for (card of cards; track card.$key) {
|
||||||
<li *ngFor="let card of cards" class="list-group-item panel card" [style.border-left-color]="item.color"
|
<li class="card-item"
|
||||||
dnd-draggable
|
[style.border-left-color]="item.color"
|
||||||
[dragData]="card"
|
cdkDrag
|
||||||
[dragEnabled]="allowDragFunction(card)"
|
[cdkDragData]="card">
|
||||||
>
|
<app-card [item]="card">
|
||||||
<card [item]="card">
|
</app-card>
|
||||||
</card>
|
|
||||||
</li>
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fullScreen" *ngIf="toShowAddCard" (click)="cancelAddCard()">
|
@if (toShowAddCard) {
|
||||||
</div>
|
<div class="modal-backdrop-overlay" (click)="cancelAddCard()" tabindex="0" role="button" (keydown.enter)="cancelAddCard()"></div>
|
||||||
<div class="fullScreentransparent" *ngIf="toShowAddCard">
|
<div class="modal-overlay">
|
||||||
<div class="panel panel-default createCard well">
|
<div class="create-card-modal">
|
||||||
<div class="panel-heading" [style.background-color]="item.color">
|
<div class="modal-header-bar" [style.background-color]="item.color">
|
||||||
<h4 class="createTitle">New task - {{ item.name }}
|
<h4 class="modal-title-text">New task - {{ item.name }}</h4>
|
||||||
<div class="pull-right link" (click)="cancelAddCard()">
|
<div class="modal-close-btn" (click)="cancelAddCard()" tabindex="0" role="button" (keydown.enter)="cancelAddCard()">
|
||||||
<i class="fa fa-window-close"></i>
|
<i class="fa fa-window-close"></i>
|
||||||
</div>
|
</div>
|
||||||
</h4>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group formfields">
|
<div class="modal-form">
|
||||||
|
<div class="form-group">
|
||||||
<label for="taskname">Name</label>
|
<label for="taskname">Name</label>
|
||||||
<input type="text" class="form-control" id="taskname" placeholder="task name" [(ngModel)]="cardname">
|
<input type="text" class="form-control" id="taskname" placeholder="Task name" [(ngModel)]="cardname">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
<label for="taskdescription">Description</label>
|
<label for="taskdescription">Description</label>
|
||||||
<textarea cols="39" rows="6" class="form-control" id="taskdescription" placeholder="description" [(ngModel)] = "carddescription"></textarea>
|
<textarea class="form-control" id="taskdescription" rows="4" placeholder="Description" [(ngModel)]="carddescription"></textarea>
|
||||||
<!--<input type="text" class="form-control" id="taskdescription" placeholder="description" [(ngModel)]="carddescription">-->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center">
|
<div class="modal-actions">
|
||||||
<button type="button" class="btn btn-primary" (click)="saveAddCard()">CREATE</button>
|
<button type="button" class="btn-create" (click)="saveAddCard()">Create</button>
|
||||||
|
<button type="button" class="btn-cancel" (click)="cancelAddCard()">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,82 +1,55 @@
|
|||||||
import {Component, OnInit, Input} from "@angular/core";
|
import { Component, OnInit, Input, Output, EventEmitter, inject } from '@angular/core';
|
||||||
import {DataService} from "app/shared/data.service";
|
import { FormsModule } from '@angular/forms';
|
||||||
import {Observable} from "rxjs";
|
import { CdkDragDrop, transferArrayItem, moveItemInArray, CdkDrag, CdkDropList } from '@angular/cdk/drag-drop';
|
||||||
import {CardList} from "app/models/cardlist-info";
|
import { DataService } from '../shared/data.service';
|
||||||
import {Card} from "app/models/card-info";
|
import { CardList } from '../models/cardlist-info';
|
||||||
import {Task} from "app/models/task-info";
|
import { Card } from '../models/card-info';
|
||||||
|
import { CardComponent } from '../card/card.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'cardlist',
|
selector: 'app-cardlist',
|
||||||
|
standalone: true,
|
||||||
|
imports: [FormsModule, CdkDropList, CdkDrag, CardComponent],
|
||||||
templateUrl: './cardlist.component.html',
|
templateUrl: './cardlist.component.html',
|
||||||
styleUrls: ['./cardlist.component.css']
|
styleUrls: ['./cardlist.component.css']
|
||||||
})
|
})
|
||||||
export class CardListComponent implements OnInit {
|
export class CardListComponent implements OnInit {
|
||||||
@Input() item: CardList;
|
@Input() item!: CardList;
|
||||||
cards : Card[]
|
@Input() connectedDropLists: string[] = [];
|
||||||
|
@Output() cardDropped = new EventEmitter<void>();
|
||||||
|
cards: Card[] = [];
|
||||||
|
|
||||||
toShowAddCard:boolean;
|
toShowAddCard = false;
|
||||||
editCard: Card;
|
cardname = '';
|
||||||
cardname;
|
carddescription = '';
|
||||||
carddescription;
|
|
||||||
allowedDropFrom = [];
|
|
||||||
allowedDragTo = false;
|
|
||||||
|
|
||||||
|
private dataService = inject(DataService);
|
||||||
|
|
||||||
constructor(private dataService: DataService) {
|
ngOnInit(): void {
|
||||||
}
|
this.dataService.getCardsByListId(this.item.$key!)
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.dataService.getCardsByListId(this.item.$key)
|
|
||||||
.subscribe(data => {
|
.subscribe(data => {
|
||||||
this.cards = data;
|
this.cards = data;
|
||||||
});
|
});
|
||||||
//fill allowed drop-from containers
|
|
||||||
this.dataService.getCardListsByOrder(this.item.order-1)
|
|
||||||
.subscribe(d => {
|
|
||||||
if(d.length>0)
|
|
||||||
this.allowedDropFrom.push(d[0].$key);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
//fill if it has next containers
|
|
||||||
this.dataService.getCardListsByOrder(this.item.order+1)
|
|
||||||
.subscribe(d => {
|
|
||||||
if(d.length>0)
|
|
||||||
this.allowedDragTo = true;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showAddCard(){
|
showAddCard(): void {
|
||||||
this.cardname = '';
|
this.cardname = '';
|
||||||
this.carddescription = '';
|
this.carddescription = '';
|
||||||
this.toShowAddCard = true;
|
this.toShowAddCard = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelAddCard(){
|
cancelAddCard(): void {
|
||||||
this.toShowAddCard = false;
|
|
||||||
}
|
|
||||||
saveAddCard(){
|
|
||||||
//console.log('save card');
|
|
||||||
this.addCard(
|
|
||||||
this.cardname,
|
|
||||||
this.carddescription,
|
|
||||||
true,
|
|
||||||
this.item.$key,
|
|
||||||
0);
|
|
||||||
this.toShowAddCard = false;
|
this.toShowAddCard = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveAddCard(): void {
|
||||||
|
this.addCard(this.cardname, this.carddescription, true, this.item.$key!, 0);
|
||||||
|
this.toShowAddCard = false;
|
||||||
|
}
|
||||||
|
|
||||||
addCard(
|
addCard(name: string, description: string, isExpanded: boolean, cardListId: string, order: number): void {
|
||||||
name: string,
|
const created_at = new Date().toString();
|
||||||
description: string,
|
const newCard = new Card();
|
||||||
isExpanded: boolean,
|
|
||||||
cardListId: string,
|
|
||||||
order: number
|
|
||||||
)
|
|
||||||
{
|
|
||||||
let created_at = new Date().toString();
|
|
||||||
let newCard:Card = new Card();
|
|
||||||
newCard.name = name;
|
newCard.name = name;
|
||||||
newCard.description = description;
|
newCard.description = description;
|
||||||
newCard.cardListId = cardListId;
|
newCard.cardListId = cardListId;
|
||||||
@@ -86,22 +59,22 @@ export class CardListComponent implements OnInit {
|
|||||||
this.dataService.addCard(newCard);
|
this.dataService.addCard(newCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
cardDropped(ev){
|
onCardDrop(event: CdkDragDrop<Card[]>): void {
|
||||||
let card:Card = ev.dragData;
|
if (event.previousContainer === event.container) {
|
||||||
if(card.cardListId !== this.item.$key){
|
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
|
||||||
card.cardListId = this.item.$key;
|
} else {
|
||||||
this.dataService.updateCard(card.$key, card);
|
transferArrayItem(
|
||||||
|
event.previousContainer.data,
|
||||||
|
event.container.data,
|
||||||
|
event.previousIndex,
|
||||||
|
event.currentIndex
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
// Update order for all cards in this list
|
||||||
|
this.cards.forEach((card, i) => {
|
||||||
|
card.cardListId = this.item.$key!;
|
||||||
|
card.order = i;
|
||||||
|
this.dataService.updateCard(card.$key!, card);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
allowDragFunction(card: Card){
|
|
||||||
return this.allowedDragTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
allowDropFunction(): any {
|
|
||||||
return (dragData: Card) => {
|
|
||||||
return this.allowedDropFrom.indexOf(dragData.cardListId) > -1;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './app.component';
|
|
||||||
export * from './app.module';
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
export class Task {
|
export class Task {
|
||||||
$key?: string;
|
$key?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
isCompleted: boolean;
|
isCompleted = false;
|
||||||
cardId?: string;
|
cardId?: string;
|
||||||
order?: number;
|
order?: number;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
|
|||||||
+110
-100
@@ -1,122 +1,132 @@
|
|||||||
import {Injectable, EventEmitter, Output} from "@angular/core";
|
import { Injectable, inject, Injector, runInInjectionContext } from '@angular/core';
|
||||||
import {AngularFire, FirebaseObjectObservable, FirebaseListObservable} from "angularfire2";
|
import { AngularFireDatabase, AngularFireList, QueryFn } from '@angular/fire/compat/database';
|
||||||
import {BehaviorSubject} from "rxjs/BehaviorSubject";
|
import { Observable } from 'rxjs';
|
||||||
import {Observable, Subject, ReplaySubject, AsyncSubject} from "rxjs";
|
import { map } from 'rxjs/operators';
|
||||||
import {Project} from "../models/project-info";
|
import { Project } from '../models/project-info';
|
||||||
import {CardList} from "../models/cardlist-info";
|
import { CardList } from '../models/cardlist-info';
|
||||||
import {Card} from "../models/card-info";
|
import { Card } from '../models/card-info';
|
||||||
import {Task} from "../models/task-info";
|
import { Task } from '../models/task-info';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
export class DataService {
|
export class DataService {
|
||||||
|
|
||||||
projects: FirebaseListObservable<Project[]>;
|
private db = inject(AngularFireDatabase);
|
||||||
cardlists: FirebaseListObservable<CardList[]>;
|
private injector = inject(Injector);
|
||||||
cards: FirebaseListObservable<Card[]>;
|
|
||||||
tasks: FirebaseListObservable<Task[]>;
|
|
||||||
|
|
||||||
constructor(private af: AngularFire) {
|
private projectsRef: AngularFireList<Project>;
|
||||||
//console.log("DataService");
|
private cardlistsRef: AngularFireList<CardList>;
|
||||||
|
private cardsRef: AngularFireList<Card>;
|
||||||
|
private tasksRef: AngularFireList<Task>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.projectsRef = this.db.list('/projects');
|
||||||
|
this.cardlistsRef = this.db.list('/cardlist', ref => ref.orderByChild('order'));
|
||||||
|
this.cardsRef = this.db.list('/cards');
|
||||||
|
this.tasksRef = this.db.list('/tasks');
|
||||||
}
|
}
|
||||||
|
|
||||||
getProjects(){
|
private stripKey<T extends { $key?: string }>(obj: T): Omit<T, '$key'> {
|
||||||
this.projects = this.af.database.list('/projects') as
|
const copy = { ...obj };
|
||||||
FirebaseListObservable<Project[]>;
|
delete copy.$key;
|
||||||
return this.projects;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
addProject(project) {
|
private queryList<T>(path: string, queryFn: QueryFn): Observable<T[]> {
|
||||||
return this.projects.push(project);
|
return runInInjectionContext(this.injector, () => {
|
||||||
|
const ref = this.db.list(path, queryFn);
|
||||||
|
return ref.snapshotChanges().pipe(
|
||||||
|
map(changes =>
|
||||||
|
changes.map(c => ({ $key: c.payload.key, ...(c.payload.val() as object) } as T))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private snapshotsWithKey<T>(ref: AngularFireList<unknown>): Observable<T[]> {
|
||||||
|
return ref.snapshotChanges().pipe(
|
||||||
getCardLists(){
|
map(changes =>
|
||||||
this.cardlists = this.af.database.list('/cardlist',{
|
changes.map(c => ({ $key: c.payload.key, ...(c.payload.val() as object) } as T))
|
||||||
query: {
|
)
|
||||||
orderByChild: 'order'
|
);
|
||||||
}}
|
|
||||||
) as
|
|
||||||
FirebaseListObservable<CardList[]>;
|
|
||||||
return this.cardlists;
|
|
||||||
}
|
|
||||||
getCardListsById(cardListId:string): FirebaseObjectObservable<CardList> {
|
|
||||||
return this.af.database.object(`/cardlist/${cardListId}`) as FirebaseObjectObservable<CardList>;
|
|
||||||
}
|
|
||||||
getCardListsByOrder(order:number): FirebaseListObservable<CardList[]> {
|
|
||||||
let _cardlist = this.af.database.list('/cardlist',{
|
|
||||||
query: {
|
|
||||||
orderByChild: 'order',
|
|
||||||
equalTo: order,
|
|
||||||
}}
|
|
||||||
) as FirebaseListObservable<CardList[]>;
|
|
||||||
return _cardlist;
|
|
||||||
}
|
|
||||||
getCachedCardListsById(cardListId:string):CardList {
|
|
||||||
return this.cardlists
|
|
||||||
.filter(d => d.$key == cardListId)
|
|
||||||
.map(d=> d.$key)
|
|
||||||
;
|
|
||||||
//.first();
|
|
||||||
}
|
|
||||||
getCardListsByProject(projectId: string){
|
|
||||||
let _cardlist = this.af.database.list('/cardlist',{
|
|
||||||
query: {
|
|
||||||
orderByChild: 'projectId',
|
|
||||||
equalTo: projectId,
|
|
||||||
}}
|
|
||||||
) as FirebaseListObservable<CardList[]>;
|
|
||||||
return _cardlist
|
|
||||||
}
|
|
||||||
addCardList(cardlist){
|
|
||||||
return this.cardlists.push(cardlist);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Projects ---
|
||||||
|
|
||||||
|
getProjects(): Observable<Project[]> {
|
||||||
|
return this.snapshotsWithKey<Project>(this.projectsRef);
|
||||||
getCards(){
|
|
||||||
this.cards = this.af.database.list('/cards') as
|
|
||||||
FirebaseListObservable<Card[]>;
|
|
||||||
return this.cards;
|
|
||||||
}
|
|
||||||
getCardsByListId(listId:string){
|
|
||||||
this.cards = this.af.database.list('/cards',{
|
|
||||||
query: {
|
|
||||||
orderByChild: 'cardListId',
|
|
||||||
equalTo: listId,
|
|
||||||
}}
|
|
||||||
) as
|
|
||||||
FirebaseListObservable<Card[]>;
|
|
||||||
return this.cards;
|
|
||||||
}
|
|
||||||
addCard(card){
|
|
||||||
return this.cards.push(card);
|
|
||||||
}
|
|
||||||
updateCard(key, updCard){
|
|
||||||
return this.cards.update(key, updCard);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addProject(project: Project) {
|
||||||
|
return this.projectsRef.push(this.stripKey(project));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CardLists ---
|
||||||
|
|
||||||
getTasks(){
|
getCardLists(): Observable<CardList[]> {
|
||||||
this.tasks = this.af.database.list('/tasks') as
|
return this.snapshotsWithKey<CardList>(this.cardlistsRef);
|
||||||
FirebaseListObservable<Task[]>;
|
|
||||||
return this.cards;
|
|
||||||
}
|
}
|
||||||
getTasksByCardId(cardId:string){
|
|
||||||
let _tasks = this.af.database.list('/tasks',{
|
getCardListsById(cardListId: string): Observable<CardList | null> {
|
||||||
query: {
|
return this.db.object<CardList>(`/cardlist/${cardListId}`).snapshotChanges().pipe(
|
||||||
orderByChild: 'cardId',
|
map(c => ({ $key: c.payload.key, ...c.payload.val() } as CardList))
|
||||||
equalTo: cardId,
|
);
|
||||||
}}
|
|
||||||
) as FirebaseListObservable<Task[]>;
|
|
||||||
return _tasks;
|
|
||||||
}
|
}
|
||||||
addTask(task){
|
|
||||||
return this.tasks.push(task);
|
getCardListsByOrder(order: number): Observable<CardList[]> {
|
||||||
|
return this.queryList<CardList>('/cardlist', ref => ref.orderByChild('order').equalTo(order));
|
||||||
}
|
}
|
||||||
updateTask(key, updTask){
|
|
||||||
return this.tasks.update(key, updTask);
|
getCardListsByProject(projectId: string): Observable<CardList[]> {
|
||||||
|
return this.queryList<CardList>('/cardlist', ref => ref.orderByChild('projectId').equalTo(projectId));
|
||||||
|
}
|
||||||
|
|
||||||
|
addCardList(cardlist: CardList) {
|
||||||
|
return this.cardlistsRef.push(this.stripKey(cardlist));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Cards ---
|
||||||
|
|
||||||
|
getCards(): Observable<Card[]> {
|
||||||
|
return this.snapshotsWithKey<Card>(this.cardsRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCardsByListId(listId: string): Observable<Card[]> {
|
||||||
|
return this.queryList<Card>('/cards', ref => ref.orderByChild('cardListId').equalTo(listId)).pipe(
|
||||||
|
map(cards => cards.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addCard(card: Card) {
|
||||||
|
return this.cardsRef.push(this.stripKey(card));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCard(key: string, updCard: Card) {
|
||||||
|
return this.cardsRef.update(key, this.stripKey(updCard));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Tasks ---
|
||||||
|
|
||||||
|
getTasks(): Observable<Task[]> {
|
||||||
|
return this.snapshotsWithKey<Task>(this.tasksRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTasksByCardId(cardId: string): Observable<Task[]> {
|
||||||
|
return this.queryList<Task>('/tasks', ref => ref.orderByChild('cardId').equalTo(cardId));
|
||||||
|
}
|
||||||
|
|
||||||
|
addTask(task: Task) {
|
||||||
|
return this.tasksRef.push(this.stripKey(task));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTask(key: string, updTask: Task) {
|
||||||
|
return this.tasksRef.update(key, this.stripKey(updTask));
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteTask(key: string) {
|
||||||
|
return runInInjectionContext(this.injector, () => {
|
||||||
|
return this.db.object('/tasks/' + key).remove();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,3 @@
|
|||||||
// 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 = {
|
export const environment = {
|
||||||
production: false
|
production: false
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export const firebaseConfig = {
|
||||||
|
apiKey: '',
|
||||||
|
authDomain: '',
|
||||||
|
databaseURL: '',
|
||||||
|
storageBucket: '',
|
||||||
|
messagingSenderId: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
export const recaptchaSiteKey = '';
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
|
|
||||||
import {AuthMethods, AuthProviders} from "angularfire2";
|
|
||||||
|
|
||||||
|
|
||||||
export const firebaseConfig = {
|
|
||||||
//get these from your created firebase project at https://console.firebase.google.com
|
|
||||||
// Paste all this from the Firebase console...
|
|
||||||
apiKey: "",
|
|
||||||
authDomain: "",
|
|
||||||
databaseURL: "",
|
|
||||||
storageBucket: "",
|
|
||||||
messagingSenderId: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
export const authConfig = {
|
|
||||||
provider: AuthProviders.Password,
|
|
||||||
method: AuthMethods.Password
|
|
||||||
};
|
|
||||||
+2
-3
@@ -1,15 +1,14 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>The Kanban Board</title>
|
<title>The Kanban Board</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root>Loading...</app-root>
|
<app-root></app-root>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+34
-6
@@ -1,12 +1,40 @@
|
|||||||
import './polyfills.ts';
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
|
import { enableProdMode, importProvidersFrom } from '@angular/core';
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { enableProdMode } from '@angular/core';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { AngularFireModule } from '@angular/fire/compat';
|
||||||
|
import { AngularFireDatabaseModule } from '@angular/fire/compat/database';
|
||||||
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||||
|
import { initializeApp, getApps } from 'firebase/app';
|
||||||
|
import { initializeAppCheck, ReCaptchaV3Provider } from '@firebase/app-check';
|
||||||
import { environment } from './environments/environment';
|
import { environment } from './environments/environment';
|
||||||
import { AppModule } from './app/';
|
import { firebaseConfig, recaptchaSiteKey } from './environments/firebaseConfig';
|
||||||
|
import { AppComponent } from './app/app.component';
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
enableProdMode();
|
enableProdMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
// Initialize Firebase and App Check before Angular bootstrap
|
||||||
|
if (getApps().length === 0) {
|
||||||
|
const app = initializeApp(firebaseConfig);
|
||||||
|
if (recaptchaSiteKey) {
|
||||||
|
initializeAppCheck(app, {
|
||||||
|
provider: new ReCaptchaV3Provider(recaptchaSiteKey),
|
||||||
|
isTokenAutoRefreshEnabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrapApplication(AppComponent, {
|
||||||
|
providers: [
|
||||||
|
importProvidersFrom(
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
AngularFireModule.initializeApp(firebaseConfig),
|
||||||
|
AngularFireDatabaseModule,
|
||||||
|
DragDropModule
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}).catch(err => console.error(err));
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
// This file includes polyfills needed by Angular 2 and is loaded before
|
|
||||||
// the app. You can add your own extra polyfills to this file.
|
|
||||||
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';
|
|
||||||
import 'core-js/es6/reflect';
|
|
||||||
|
|
||||||
import 'core-js/es7/reflect';
|
|
||||||
import 'zone.js/dist/zone';
|
|
||||||
+7
-1
@@ -1 +1,7 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
/* Global styles */
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
background-color: #f4f5f7;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||||
|
}
|
||||||
|
|||||||
-32
@@ -1,32 +0,0 @@
|
|||||||
import './polyfills.ts';
|
|
||||||
|
|
||||||
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.
|
|
||||||
let context = require.context('./', true, /\.spec\.ts/);
|
|
||||||
// And load the modules.
|
|
||||||
context.keys().map(context);
|
|
||||||
// Finally, start Karma to run the tests.
|
|
||||||
__karma__.start();
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": "",
|
|
||||||
"declaration": false,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"lib": ["es6", "dom"],
|
|
||||||
"mapRoot": "./",
|
|
||||||
"module": "es6",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"outDir": "../dist/out-tsc",
|
|
||||||
"sourceMap": true,
|
|
||||||
"target": "es5",
|
|
||||||
"typeRoots": [
|
|
||||||
"../node_modules/@types"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-2
@@ -1,2 +0,0 @@
|
|||||||
// Typings reference file, you can add your own global typings here
|
|
||||||
// https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||||
|
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/app",
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/main.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||||
|
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/out-tsc",
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"importHelpers": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ES2022"
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||||
|
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/spec",
|
||||||
|
"types": [
|
||||||
|
"jasmine"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
-114
@@ -1,114 +0,0 @@
|
|||||||
{
|
|
||||||
"rulesDirectory": [
|
|
||||||
"node_modules/codelyzer"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"class-name": true,
|
|
||||||
"comment-format": [
|
|
||||||
true,
|
|
||||||
"check-space"
|
|
||||||
],
|
|
||||||
"curly": true,
|
|
||||||
"eofline": true,
|
|
||||||
"forin": true,
|
|
||||||
"indent": [
|
|
||||||
true,
|
|
||||||
"spaces"
|
|
||||||
],
|
|
||||||
"label-position": true,
|
|
||||||
"label-undefined": 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-key": true,
|
|
||||||
"no-duplicate-variable": true,
|
|
||||||
"no-empty": false,
|
|
||||||
"no-eval": true,
|
|
||||||
"no-inferrable-types": true,
|
|
||||||
"no-shadowed-variable": true,
|
|
||||||
"no-string-literal": false,
|
|
||||||
"no-switch-case-fall-through": true,
|
|
||||||
"no-trailing-whitespace": true,
|
|
||||||
"no-unused-expression": true,
|
|
||||||
"no-unused-variable": true,
|
|
||||||
"no-unreachable": 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"
|
|
||||||
],
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"variable-name": false,
|
|
||||||
"whitespace": [
|
|
||||||
true,
|
|
||||||
"check-branch",
|
|
||||||
"check-decl",
|
|
||||||
"check-operator",
|
|
||||||
"check-separator",
|
|
||||||
"check-type"
|
|
||||||
],
|
|
||||||
|
|
||||||
"directive-selector-prefix": [true, "app"],
|
|
||||||
"component-selector-prefix": [true, "app"],
|
|
||||||
"directive-selector-name": [true, "camelCase"],
|
|
||||||
"component-selector-name": [true, "kebab-case"],
|
|
||||||
"directive-selector-type": [true, "attribute"],
|
|
||||||
"component-selector-type": [true, "element"],
|
|
||||||
"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,
|
|
||||||
"templates-use-public": true,
|
|
||||||
"invoke-injectable": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user